From c26ec4d511fc00f38cee7fa38e7aa23527057653 Mon Sep 17 00:00:00 2001 From: "bzbarsky%mit.edu" Date: Fri, 5 May 2006 17:01:53 +0000 Subject: [PATCH] Clean up event dispatch code in XMLHttpRequest, make onreadystatechange an EventListener, fire upload progress events. Bug 198595 and bug 312213, patch by me for the cleanup and onreadystatechange and by Rob Adams for the upload progress changes, r=sicking, sr=peterv git-svn-id: svn://10.0.0.236/trunk@196032 18797224-902f-48f8-a5cc-f745e15eee43 --- .../content/base/public/nsIXMLHttpRequest.idl | 34 ++- mozilla/content/base/src/nsXMLHttpRequest.cpp | 243 ++++++++++-------- mozilla/content/base/src/nsXMLHttpRequest.h | 28 +- 3 files changed, 175 insertions(+), 130 deletions(-) diff --git a/mozilla/content/base/public/nsIXMLHttpRequest.idl b/mozilla/content/base/public/nsIXMLHttpRequest.idl index 58f484433c3..08810da498b 100644 --- a/mozilla/content/base/public/nsIXMLHttpRequest.idl +++ b/mozilla/content/base/public/nsIXMLHttpRequest.idl @@ -75,8 +75,11 @@ interface nsIVariant; * where mylistener is your event listener object that implements the * interface nsIDOMEventListener. * - * The 'onload' and 'onerror' attributes moved to nsIJSXMLHttRequest, - * but if you're coding in C++ you should avoid using those. + * The 'onload', 'onerror', and 'onreadystatechange' attributes moved to + * nsIJSXMLHttRequest, but if you're coding in C++ you should avoid using + * those. Though actually, if you use addEventListener from C++ weird things + * will happen too, since the result will depend on what JS happens to be on + * the stack when you do it.... */ [scriptable, uuid(7b3b3c62-9d53-4abc-bc89-c33ce78f439f)] interface nsIXMLHttpRequest : nsISupports @@ -283,17 +286,7 @@ interface nsIXMLHttpRequest : nsISupports attribute boolean multipart; }; - -[scriptable, function, uuid(6459B7CE-6B57-4934-A0AF-0133BA6F9085)] -interface nsIOnReadyStateChangeHandler : nsISupports { - /** - * Helper to implement the onreadystatechange callback member. - * You should not need to use this. - */ - void handleEvent(); -}; - -[scriptable, uuid(9deabc90-28d5-41d3-a660-474f2254f4ba)] +[scriptable, uuid(261676b4-d508-43bf-b099-74635a0ee2e9)] interface nsIJSXMLHttpRequest : nsISupports { /** * Meant to be a script-only mechanism for setting a load event listener. @@ -335,6 +328,19 @@ interface nsIJSXMLHttpRequest : nsISupports { */ attribute nsIDOMEventListener onprogress; + /** + * Meant to be a script-only mechanism for setting an upload progress event + * listener. + * This attribute should not be used from native code!! + * This event listener may be called multiple times during the upload.. + * + * After the initial response, all event listeners will be cleared. + * Call open() before setting new event listeners. + * + * Mozilla only. + */ + attribute nsIDOMEventListener onuploadprogress; + /** * Meant to be a script-only mechanism for setting a callback function. * The attribute is expected to be JavaScript function object. When the @@ -344,7 +350,7 @@ interface nsIJSXMLHttpRequest : nsISupports { * After the initial response, all event listeners will be cleared. * Call open() before setting new event listeners. */ - attribute nsIOnReadyStateChangeHandler onreadystatechange; + attribute nsIDOMEventListener onreadystatechange; }; %{ C++ diff --git a/mozilla/content/base/src/nsXMLHttpRequest.cpp b/mozilla/content/base/src/nsXMLHttpRequest.cpp index c44db7b4fd1..1d33badaf5a 100644 --- a/mozilla/content/base/src/nsXMLHttpRequest.cpp +++ b/mozilla/content/base/src/nsXMLHttpRequest.cpp @@ -77,10 +77,15 @@ #include "nsIStreamConverterService.h" #include "nsICachingChannel.h" #include "nsContentUtils.h" +#include "nsEventDispatcher.h" static const char* kLoadAsData = "loadAsData"; -#define LOADSTR NS_LITERAL_STRING("load") -#define ERRORSTR NS_LITERAL_STRING("error") + +#define LOAD_STR "load" +#define ERROR_STR "error" +#define PROGRESS_STR "progress" +#define UPLOADPROGRESS_STR "uploadprogress" +#define READYSTATE_STR "readystatechange" // CIDs static NS_DEFINE_CID(kCharsetAliasCID, NS_CHARSETALIAS_CID); @@ -314,27 +319,26 @@ nsXMLHttpRequest::AddEventListener(const nsAString& type, PRBool useCapture) { NS_ENSURE_ARG(listener); - nsresult rv; - if (type.Equals(LOADSTR)) { - if (!mLoadEventListeners) { - rv = NS_NewISupportsArray(getter_AddRefs(mLoadEventListeners)); - NS_ENSURE_SUCCESS(rv, rv); - } - mLoadEventListeners->AppendElement(listener); - } - else if (type.Equals(ERRORSTR)) { - if (!mErrorEventListeners) { - rv = NS_NewISupportsArray(getter_AddRefs(mErrorEventListeners)); - NS_ENSURE_SUCCESS(rv, rv); - } - mErrorEventListeners->AppendElement(listener); - } - else { +#define IMPL_ADD_LISTENER(_type, _member) \ + if (type.EqualsLiteral(_type)) { \ + if (!(_member).AppendObject(listener)) { \ + return NS_ERROR_OUT_OF_MEMORY; \ + } \ + } else + + IMPL_ADD_LISTENER(LOAD_STR, mLoadEventListeners) + IMPL_ADD_LISTENER(ERROR_STR, mErrorEventListeners) + IMPL_ADD_LISTENER(PROGRESS_STR, mProgressEventListeners) + IMPL_ADD_LISTENER(UPLOADPROGRESS_STR, mUploadProgressEventListeners) + IMPL_ADD_LISTENER(READYSTATE_STR, mReadystatechangeEventListeners) + { return NS_ERROR_INVALID_ARG; } mScriptContext = GetCurrentContext(); +#undef IMPL_ADD_LISTENER + return NS_OK; } @@ -347,17 +351,17 @@ nsXMLHttpRequest::RemoveEventListener(const nsAString & type, { NS_ENSURE_ARG(listener); - if (type.Equals(LOADSTR)) { - if (mLoadEventListeners) { - mLoadEventListeners->RemoveElement(listener); - } - } - else if (type.Equals(ERRORSTR)) { - if (mErrorEventListeners) { - mErrorEventListeners->RemoveElement(listener); - } - } - else { +#define IMPL_REMOVE_LISTENER(_type, _member) \ + if (type.EqualsLiteral(_type)) { \ + (_member).RemoveObject(listener); \ + } else + + IMPL_REMOVE_LISTENER(LOAD_STR, mLoadEventListeners) + IMPL_REMOVE_LISTENER(ERROR_STR, mErrorEventListeners) + IMPL_REMOVE_LISTENER(PROGRESS_STR, mProgressEventListeners) + IMPL_REMOVE_LISTENER(UPLOADPROGRESS_STR, mUploadProgressEventListeners) + IMPL_REMOVE_LISTENER(READYSTATE_STR, mReadystatechangeEventListeners) + { return NS_ERROR_INVALID_ARG; } @@ -373,9 +377,9 @@ nsXMLHttpRequest::DispatchEvent(nsIDOMEvent *evt, PRBool *_retval) return NS_OK; } -/* attribute nsIOnReadyStateChangeHandler onreadystatechange; */ +/* attribute nsIDOMEventListener onreadystatechange; */ NS_IMETHODIMP -nsXMLHttpRequest::GetOnreadystatechange(nsIOnReadyStateChangeHandler * *aOnreadystatechange) +nsXMLHttpRequest::GetOnreadystatechange(nsIDOMEventListener * *aOnreadystatechange) { NS_ENSURE_ARG_POINTER(aOnreadystatechange); @@ -386,7 +390,7 @@ nsXMLHttpRequest::GetOnreadystatechange(nsIOnReadyStateChangeHandler * *aOnready } NS_IMETHODIMP -nsXMLHttpRequest::SetOnreadystatechange(nsIOnReadyStateChangeHandler * aOnreadystatechange) +nsXMLHttpRequest::SetOnreadystatechange(nsIDOMEventListener * aOnreadystatechange) { mOnReadystatechangeListener = aOnreadystatechange; @@ -462,6 +466,28 @@ nsXMLHttpRequest::SetOnprogress(nsIDOMEventListener * aOnprogress) return NS_OK; } +/* attribute nsIDOMEventListener onuploadprogress; */ +NS_IMETHODIMP +nsXMLHttpRequest::GetOnuploadprogress(nsIDOMEventListener * *aOnuploadprogress) +{ + NS_ENSURE_ARG_POINTER(aOnuploadprogress); + + *aOnuploadprogress = mOnUploadProgressListener; + NS_IF_ADDREF(*aOnuploadprogress); + + return NS_OK; +} + +NS_IMETHODIMP +nsXMLHttpRequest::SetOnuploadprogress(nsIDOMEventListener * aOnuploadprogress) +{ + mOnUploadProgressListener = aOnuploadprogress; + + mScriptContext = GetCurrentContext(); + + return NS_OK; +} + /* readonly attribute nsIChannel channel; */ NS_IMETHODIMP nsXMLHttpRequest::GetChannel(nsIChannel **aChannel) @@ -776,24 +802,12 @@ nsXMLHttpRequest::GetBaseURI() } nsresult -nsXMLHttpRequest::CreateEvent(nsEvent* aEvent, nsIDOMEvent** aDOMEvent) +nsXMLHttpRequest::CreateEvent(nsEvent* aEvent, const nsAString& aType, + nsIDOMEvent** aDOMEvent) { - nsresult rv; - - nsCOMPtr receiver(do_QueryInterface(mDocument)); - if (!receiver) { - return NS_ERROR_FAILURE; - } - - nsCOMPtr manager; - rv = receiver->GetListenerManager(PR_TRUE, getter_AddRefs(manager)); - if (!manager) { - return NS_ERROR_FAILURE; - } - - rv = manager->CreateEvent(nsnull, aEvent, - NS_LITERAL_STRING("HTMLEvents"), - aDOMEvent); + nsresult rv = nsEventDispatcher::CreateEvent(nsnull, aEvent, + NS_LITERAL_STRING("HTMLEvents"), + aDOMEvent); if (NS_FAILED(rv)) { return rv; } @@ -803,25 +817,28 @@ nsXMLHttpRequest::CreateEvent(nsEvent* aEvent, nsIDOMEvent** aDOMEvent) NS_IF_RELEASE(*aDOMEvent); return NS_ERROR_FAILURE; } + + if (!aType.IsEmpty()) { + (*aDOMEvent)->InitEvent(aType, PR_FALSE, PR_FALSE); + } + privevent->SetTarget(this); privevent->SetCurrentTarget(this); privevent->SetOriginalTarget(this); - // We're not marking these events trusted as there is no way for us - // to tell if we're called through the code that normally calls us - // (i.e. necko) or if script on a webpage QI'd this object to say, - // nsIProgressEventSink and called onProgress(...). - // - // privevent->SetTrusted(?); + // We assume anyone who managed to call CreateEvent is trusted + privevent->SetTrusted(PR_TRUE); return NS_OK; } void nsXMLHttpRequest::NotifyEventListeners(nsIDOMEventListener* aHandler, - nsISupportsArray* aListeners, + nsCOMArray& aListeners, nsIDOMEvent* aEvent) { + // XXXbz wouldn't it be easier to just have an actual nsEventListenerManager + // to work with or something? I feel like we're duplicating code here... if (!aEvent) return; @@ -844,16 +861,12 @@ nsXMLHttpRequest::NotifyEventListeners(nsIDOMEventListener* aHandler, aHandler->HandleEvent(aEvent); } - if (aListeners) { - PRUint32 index, count; - - aListeners->Count(&count); - for (index = 0; index < count; index++) { - nsCOMPtr listener = do_QueryElementAt(aListeners, index); - - if (listener) { - listener->HandleEvent(aEvent); - } + PRInt32 count = aListeners.Count(); + for (PRInt32 index = 0; index < count; ++index) { + nsIDOMEventListener* listener = aListeners[index]; + + if (listener) { + listener->HandleEvent(aEvent); } } @@ -865,13 +878,16 @@ nsXMLHttpRequest::NotifyEventListeners(nsIDOMEventListener* aHandler, void nsXMLHttpRequest::ClearEventListeners() { - mLoadEventListeners = nsnull; - mErrorEventListeners = nsnull; + mLoadEventListeners.Clear(); + mErrorEventListeners.Clear(); + mProgressEventListeners.Clear(); + mUploadProgressEventListeners.Clear(); + mReadystatechangeEventListeners.Clear(); mOnLoadListener = nsnull; mOnErrorListener = nsnull; - mOnReadystatechangeListener = nsnull; mOnProgressListener = nsnull; - + mOnUploadProgressListener = nsnull; + mOnReadystatechangeListener = nsnull; } already_AddRefed @@ -966,7 +982,9 @@ nsXMLHttpRequest::OpenRequest(const nsACString& method, // a progress event handler we must load with nsIRequest::LOAD_NORMAL or // necko won't generate any progress notifications nsLoadFlags loadFlags; - if (mOnProgressListener) { + if (mOnProgressListener || mOnUploadProgressListener || + mProgressEventListeners.Count() != 0 || + mUploadProgressEventListeners.Count() != 0) { loadFlags = nsIRequest::LOAD_NORMAL; } else { loadFlags = nsIRequest::LOAD_BACKGROUND; @@ -1249,7 +1267,7 @@ nsXMLHttpRequest::OnStartRequest(nsIRequest *request, nsISupports *ctxt) privImpl->Init(uri, uri, principal); } - // Create an empty document from it (resets current document as well) + // Create an empty document from it const nsAString& emptyStr = EmptyString(); rv = implementation->CreateDocument(emptyStr, emptyStr, @@ -1421,7 +1439,9 @@ nsXMLHttpRequest::RequestCompleted() // We need to create the event before nulling out mDocument nsEvent evt(PR_TRUE, NS_PAGE_LOAD); nsCOMPtr domevent; - rv = CreateEvent(&evt, getter_AddRefs(domevent)); + if (mOnLoadListener || mLoadEventListeners.Count()) { + rv = CreateEvent(&evt, EmptyString(), getter_AddRefs(domevent)); + } // We might have been sent non-XML data. If that was the case, // we should null out the document member. The idea in this @@ -1435,15 +1455,18 @@ nsXMLHttpRequest::RequestCompleted() } } - // Grab hold of the event listener lists we will need + // Grab hold of the event listeners we will need before we possibly clear + // them. nsCOMPtr onLoadListener = mOnLoadListener; - nsCOMPtr loadEventListeners = mLoadEventListeners; + nsCOMArray loadEventListeners(mLoadEventListeners); // Clear listeners here unless we're multipart ChangeState(XML_HTTP_REQUEST_COMPLETED, PR_TRUE, !(mState & XML_HTTP_REQUEST_MULTIPART)); - NotifyEventListeners(onLoadListener, loadEventListeners, domevent); + if (NS_SUCCEEDED(rv) && domevent) { + NotifyEventListeners(onLoadListener, loadEventListeners, domevent); + } if (mState & XML_HTTP_REQUEST_MULTIPART) { // We're a multipart request, so we're not done. Reset to opened. @@ -1834,11 +1857,12 @@ nsresult nsXMLHttpRequest::Error(nsIDOMEvent* aEvent) { // We need to create the event before nulling out mDocument - nsCOMPtr event(do_QueryInterface(aEvent)); + nsCOMPtr event = aEvent; // There is no NS_PAGE_ERROR event but NS_SCRIPT_ERROR should be ok. nsEvent evt(PR_TRUE, NS_SCRIPT_ERROR); - if (!event) { - CreateEvent(&evt, getter_AddRefs(event)); + if (!event && + (mOnErrorListener || mErrorEventListeners.Count())) { + CreateEvent(&evt, EmptyString(), getter_AddRefs(event)); } mDocument = nsnull; @@ -1847,11 +1871,13 @@ nsXMLHttpRequest::Error(nsIDOMEvent* aEvent) mState &= ~XML_HTTP_REQUEST_SYNCLOOPING; nsCOMPtr onErrorListener = mOnErrorListener; - nsCOMPtr errorEventListeners = mErrorEventListeners; + nsCOMArray errorEventListeners(mErrorEventListeners); ClearEventListeners(); - NotifyEventListeners(onErrorListener, errorEventListeners, event); + if (event) { + NotifyEventListeners(onErrorListener, errorEventListeners, event); + } return NS_OK; } @@ -1868,9 +1894,11 @@ nsXMLHttpRequest::ChangeState(PRUint32 aState, PRBool aBroadcast, mState |= aState; nsresult rv = NS_OK; - // Take ref to the one listener we need - nsCOMPtr onReadyStateChangeListener = + // Grab private copies of the listeners we need + nsCOMPtr onReadyStateChangeListener = mOnReadystatechangeListener; + nsCOMArray readystatechangeEventListeners( + mReadystatechangeEventListeners); if (aClearEventListeners) { ClearEventListeners(); @@ -1879,27 +1907,15 @@ nsXMLHttpRequest::ChangeState(PRUint32 aState, PRBool aBroadcast, if ((mState & XML_HTTP_REQUEST_ASYNC) && (aState & XML_HTTP_REQUEST_LOADSTATES) && // Broadcast load states only aBroadcast && - onReadyStateChangeListener) { - nsCOMPtr stack; - JSContext *cx = nsnull; + (onReadyStateChangeListener || readystatechangeEventListeners.Count())) { + nsCOMPtr event; + rv = CreateEvent(nsnull, NS_LITERAL_STRING(READYSTATE_STR), + getter_AddRefs(event)); + NS_ENSURE_SUCCESS(rv, rv); - if (mScriptContext) { - stack = do_GetService("@mozilla.org/js/xpc/ContextStack;1"); - - if (stack) { - cx = (JSContext *)mScriptContext->GetNativeContext(); - - if (cx) { - stack->Push(cx); - } - } - } - - rv = onReadyStateChangeListener->HandleEvent(); - - if (cx) { - stack->Pop(&cx); - } + NotifyEventListeners(onReadyStateChangeListener, + readystatechangeEventListeners, + event); } return rv; @@ -1966,18 +1982,27 @@ nsXMLHttpRequest::OnChannelRedirect(nsIChannel *aOldChannel, NS_IMETHODIMP nsXMLHttpRequest::OnProgress(nsIRequest *aRequest, nsISupports *aContext, PRUint64 aProgress, PRUint64 aProgressMax) { - if (mOnProgressListener) { + PRBool downloading = (XML_HTTP_REQUEST_LOADED & mState); + nsIDOMEventListener* progressListener = + downloading ? mOnProgressListener : mOnUploadProgressListener; + nsCOMArray & progressListenerArray = + downloading ? mProgressEventListeners : mUploadProgressEventListeners; + + if (progressListener || progressListenerArray.Count()) { nsCOMPtr event; - nsEvent evt(PR_TRUE, NS_EVENT_NULL); // what name do we make up here? - nsresult rv = CreateEvent(&evt, getter_AddRefs(event)); + nsresult rv = CreateEvent(nsnull, NS_LITERAL_STRING(PROGRESS_STR), + getter_AddRefs(event)); NS_ENSURE_SUCCESS(rv, rv); - nsXMLHttpProgressEvent * progressEvent = new nsXMLHttpProgressEvent(event, aProgress, aProgressMax); + nsXMLHttpProgressEvent * progressEvent = + new nsXMLHttpProgressEvent(event, aProgress, aProgressMax); if (!progressEvent) return NS_ERROR_OUT_OF_MEMORY; - event = do_QueryInterface(progressEvent); - NotifyEventListeners(mOnProgressListener, nsnull, event); + event = progressEvent; + nsCOMPtr onProgressListener = progressListener; + nsCOMArray progressListeners(progressListenerArray); + NotifyEventListeners(onProgressListener, progressListeners, event); } if (mProgressEventSink) { diff --git a/mozilla/content/base/src/nsXMLHttpRequest.h b/mozilla/content/base/src/nsXMLHttpRequest.h index 0fcfd2f7096..d993e14fcc2 100644 --- a/mozilla/content/base/src/nsXMLHttpRequest.h +++ b/mozilla/content/base/src/nsXMLHttpRequest.h @@ -51,13 +51,13 @@ #include "nsIStreamListener.h" #include "nsIEventQueueService.h" #include "nsWeakReference.h" -#include "nsISupportsArray.h" #include "jsapi.h" #include "nsIScriptContext.h" #include "nsIChannelEventSink.h" #include "nsIInterfaceRequestor.h" #include "nsIHttpHeaderVisitor.h" #include "nsIProgressEventSink.h" +#include "nsCOMArray.h" #include "nsIDOMLSProgressEvent.h" @@ -135,9 +135,19 @@ protected: nsresult RequestCompleted(); nsresult GetLoadGroup(nsILoadGroup **aLoadGroup); nsIURI *GetBaseURI(); - nsresult CreateEvent(nsEvent* event, nsIDOMEvent** domevent); + + // Passing a null |event| is OK. In that case, a vanilla HTMLEvents event + // will be created. If aType is non-empty, InitEvent will be called with + // that type. Don't call this if we have no event listeners, since this may + // use our script context, which is not set in that case. + nsresult CreateEvent(nsEvent* event, const nsAString& aType, + nsIDOMEvent** domevent); + + // aListeners must be a "non-live" list (i.e., addEventListener and + // removeEventListener should not affect it). void NotifyEventListeners(nsIDOMEventListener* aHandler, - nsISupportsArray* aListeners, nsIDOMEvent* aEvent); + nsCOMArray& aListeners, + nsIDOMEvent* aEvent); void ClearEventListeners(); already_AddRefed GetCurrentHttpChannel(); @@ -146,15 +156,19 @@ protected: nsCOMPtr mReadRequest; nsCOMPtr mDocument; - nsCOMPtr mLoadEventListeners; - nsCOMPtr mErrorEventListeners; + nsCOMArray mLoadEventListeners; + nsCOMArray mErrorEventListeners; + nsCOMArray mProgressEventListeners; + nsCOMArray mUploadProgressEventListeners; + nsCOMArray mReadystatechangeEventListeners; + nsCOMPtr mScriptContext; nsCOMPtr mOnLoadListener; nsCOMPtr mOnErrorListener; nsCOMPtr mOnProgressListener; - - nsCOMPtr mOnReadystatechangeListener; + nsCOMPtr mOnUploadProgressListener; + nsCOMPtr mOnReadystatechangeListener; nsCOMPtr mXMLParserStreamListener; nsCOMPtr mEventQService;