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;