diff --git a/mozilla/extensions/xmlextras/base/src/Makefile.in b/mozilla/extensions/xmlextras/base/src/Makefile.in index f158fe8c7b6..1816b64ccaa 100644 --- a/mozilla/extensions/xmlextras/base/src/Makefile.in +++ b/mozilla/extensions/xmlextras/base/src/Makefile.in @@ -34,6 +34,7 @@ CPPSRCS = \ nsDOMSerializer.cpp \ nsXMLHttpRequest.cpp \ nsDOMParser.cpp \ + nsLoadListenerProxy.cpp \ $(NULL) # we don't want the shared lib, but we want to force the creation of a diff --git a/mozilla/extensions/xmlextras/base/src/makefile.win b/mozilla/extensions/xmlextras/base/src/makefile.win index 56bbc2577b8..c0a113872aa 100644 --- a/mozilla/extensions/xmlextras/base/src/makefile.win +++ b/mozilla/extensions/xmlextras/base/src/makefile.win @@ -30,6 +30,7 @@ CPPSRCS= \ nsDOMSerializer.cpp \ nsXMLHttpRequest.cpp \ nsDOMParser.cpp \ + nsLoadListenerProxy.cpp \ $(NULL) @@ -37,6 +38,7 @@ CPP_OBJS= \ .\$(OBJDIR)\nsDOMSerializer.obj \ .\$(OBJDIR)\nsXMLHttpRequest.obj \ .\$(OBJDIR)\nsDOMParser.obj \ + .\$(OBJDIR)\nsLoadListenerProxy.obj \ $(NULL) EXPORTS = \ diff --git a/mozilla/extensions/xmlextras/base/src/nsDOMParser.cpp b/mozilla/extensions/xmlextras/base/src/nsDOMParser.cpp index 426aa9e37a5..b4724c228f3 100644 --- a/mozilla/extensions/xmlextras/base/src/nsDOMParser.cpp +++ b/mozilla/extensions/xmlextras/base/src/nsDOMParser.cpp @@ -41,6 +41,20 @@ #include "nsICodebasePrincipal.h" #include "nsIDOMClassInfo.h" +#ifdef IMPLEMENT_SYNC_LOAD +#include "nsIScriptContext.h" +#include "nsIScriptGlobalObject.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIEventQueueService.h" +#include "nsIInterfaceRequestor.h" +#include "nsIDOMEventReceiver.h" +#include "jsapi.h" +#include "nsLoadListenerProxy.h" +static NS_DEFINE_IID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID); +#endif + static const char* kLoadAsData = "loadAsData"; static NS_DEFINE_CID(kIDOMDOMImplementationCID, NS_DOM_IMPLEMENTATION_CID); @@ -252,6 +266,71 @@ NS_IMETHODIMP nsDOMParserChannel::AsyncOpen(nsIStreamListener *listener, nsISupp // ///////////////////////////////////////////// +#ifdef IMPLEMENT_SYNC_LOAD +// See if we have a modal event loop +static inline PRBool IsModal(nsIWebBrowserChrome *aWindow) +{ + if (aWindow) { + PRBool isModal; + if (NS_SUCCEEDED(aWindow->IsWindowModal(&isModal))) { + return isModal; + } + } + + return PR_FALSE; +} + +// nsIDOMEventListener +nsresult +nsDOMParser::HandleEvent(nsIDOMEvent* aEvent) +{ + return NS_OK; +} + +// nsIDOMLoadListener +nsresult +nsDOMParser::Load(nsIDOMEvent* aEvent) +{ + if (IsModal(mChromeWindow)) { + mChromeWindow->ExitModalEventLoop(NS_OK); + } + + mChromeWindow = nsnull; + + return NS_OK; +} + +nsresult +nsDOMParser::Unload(nsIDOMEvent* aEvent) +{ + return NS_OK; +} + +nsresult +nsDOMParser::Abort(nsIDOMEvent* aEvent) +{ + if (IsModal(mChromeWindow)) { + mChromeWindow->ExitModalEventLoop(NS_OK); + } + + mChromeWindow = nsnull; + + return NS_OK; +} + +nsresult +nsDOMParser::Error(nsIDOMEvent* aEvent) +{ + if (IsModal(mChromeWindow)) { + mChromeWindow->ExitModalEventLoop(NS_OK); + } + + mChromeWindow = nsnull; + + return NS_OK; +} +#endif + nsDOMParser::nsDOMParser() { NS_INIT_ISUPPORTS(); @@ -259,13 +338,22 @@ nsDOMParser::nsDOMParser() nsDOMParser::~nsDOMParser() { +#ifdef IMPLEMENT_SYNC_LOAD + if (IsModal(mChromeWindow)) { + mChromeWindow->ExitModalEventLoop(NS_OK); + } +#endif } // QueryInterface implementation for nsDOMParser NS_INTERFACE_MAP_BEGIN(nsDOMParser) - NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMParser) NS_INTERFACE_MAP_ENTRY(nsIDOMParser) +#ifdef IMPLEMENT_SYNC_LOAD + NS_INTERFACE_MAP_ENTRY(nsIDOMLoadListener) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +#endif NS_INTERFACE_MAP_ENTRY_DOM_CLASSINFO(DOMParser) NS_INTERFACE_MAP_END @@ -435,6 +523,22 @@ nsDOMParser::ParseFromStream(nsIInputStream *stream, getter_AddRefs(domDocument)); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; +#ifdef IMPLEMENT_SYNC_LOAD + // Register as a load listener on the document + nsCOMPtr target(do_QueryInterface(domDocument)); + if (target) { + nsWeakPtr requestWeak(getter_AddRefs(NS_GetWeakReference(NS_STATIC_CAST(nsIDOMParser*, this)))); + nsLoadListenerProxy* proxy = new nsLoadListenerProxy(requestWeak); + if (!proxy) return NS_ERROR_OUT_OF_MEMORY; + + // This will addref the proxy + rv = target->AddEventListenerByIID(NS_STATIC_CAST(nsIDOMEventListener*, + proxy), + NS_GET_IID(nsIDOMLoadListener)); + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + } +#endif + // Create a fake channel nsDOMParserChannel* parserChannel = new nsDOMParserChannel(baseURI, contentType); if (!parserChannel) return NS_ERROR_OUT_OF_MEMORY; @@ -449,14 +553,69 @@ nsDOMParser::ParseFromStream(nsIInputStream *stream, // Tell the document to start loading nsCOMPtr listener; nsCOMPtr document(do_QueryInterface(domDocument)); - if (!document) return NS_ERROR_FAILURE; + +#ifdef IMPLEMENT_SYNC_LOAD + nsCOMPtr modalEventQueue; + nsCOMPtr eventQService; + + if (cc) { + JSContext* cx; + rv = cc->GetJSContext(&cx); + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + + // We can only do this if we're called from a DOM script context + nsIScriptContext* scriptCX = (nsIScriptContext*)JS_GetContextPrivate(cx); + if (!scriptCX) return NS_OK; + + // Get the nsIDocShellTreeOwner associated with the window + // containing this script context + // XXX Need to find a better way to do this rather than + // chaining through a bunch of getters and QIs + nsCOMPtr global; + scriptCX->GetGlobalObject(getter_AddRefs(global)); + if (!global) return NS_ERROR_FAILURE; + + nsCOMPtr docshell; + rv = global->GetDocShell(getter_AddRefs(docshell)); + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + + nsCOMPtr item = do_QueryInterface(docshell); + if (!item) return NS_ERROR_FAILURE; + + nsCOMPtr treeOwner; + rv = item->GetTreeOwner(getter_AddRefs(treeOwner)); + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + + nsCOMPtr treeRequestor(do_GetInterface(treeOwner)); + if (!treeRequestor) return NS_ERROR_FAILURE; + + treeRequestor->GetInterface(NS_GET_IID(nsIWebBrowserChrome), getter_AddRefs(mChromeWindow)); + if (!mChromeWindow) return NS_ERROR_FAILURE; + + eventQService = do_GetService(kEventQueueServiceCID); + if(!eventQService || + NS_FAILED(eventQService->PushThreadEventQueue(getter_AddRefs(modalEventQueue)))) { + return NS_ERROR_FAILURE; + } + } +#endif + rv = document->StartDocumentLoad(kLoadAsData, channel, nsnull, nsnull, getter_AddRefs(listener), PR_FALSE); +#ifdef IMPLEMENT_SYNC_LOAD + if (NS_FAILED(rv) || !listener) { + if (modalEventQueue) { + eventQService->PopThreadEventQueue(modalEventQueue); + } + return NS_ERROR_FAILURE; + } +#else if (NS_FAILED(rv) || !listener) return NS_ERROR_FAILURE; +#endif // Now start pumping data to the listener nsresult status; @@ -470,7 +629,29 @@ nsDOMParser::ParseFromStream(nsIInputStream *stream, } rv = listener->OnStopRequest(request, nsnull, status); +#ifdef IMPLEMENT_SYNC_LOAD + if (NS_FAILED(rv)) { + if (modalEventQueue) { + eventQService->PopThreadEventQueue(modalEventQueue); + } + return NS_ERROR_FAILURE; + } +#else if (NS_FAILED(rv)) return NS_ERROR_FAILURE; +#endif + +#ifdef IMPLEMENT_SYNC_LOAD + // Spin an event loop here and wait + if (mChromeWindow) { + rv = mChromeWindow->ShowAsModal(); + + eventQService->PopThreadEventQueue(modalEventQueue); + + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + } else if (modalEventQueue) { + eventQService->PopThreadEventQueue(modalEventQueue); + } +#endif *_retval = domDocument; NS_ADDREF(*_retval); diff --git a/mozilla/extensions/xmlextras/base/src/nsDOMParser.h b/mozilla/extensions/xmlextras/base/src/nsDOMParser.h index 498d3e568d4..501b0732f71 100644 --- a/mozilla/extensions/xmlextras/base/src/nsDOMParser.h +++ b/mozilla/extensions/xmlextras/base/src/nsDOMParser.h @@ -29,7 +29,18 @@ #include "nsCOMPtr.h" #include "nsIURI.h" +#define IMPLEMENT_SYNC_LOAD +#ifdef IMPLEMENT_SYNC_LOAD +#include "nsIWebBrowserChrome.h" +#include "nsIDOMLoadListener.h" +#include "nsWeakReference.h" +#endif + class nsDOMParser : public nsIDOMParser +#ifdef IMPLEMENT_SYNC_LOAD + , public nsIDOMLoadListener + , public nsSupportsWeakReference +#endif { public: nsDOMParser(); @@ -40,8 +51,23 @@ public: // nsIDOMParser NS_DECL_NSIDOMPARSER +#ifdef IMPLEMENT_SYNC_LOAD + // nsIDOMEventListener + NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent); + + // nsIDOMLoadListener + NS_IMETHOD Load(nsIDOMEvent* aEvent); + NS_IMETHOD Unload(nsIDOMEvent* aEvent); + NS_IMETHOD Abort(nsIDOMEvent* aEvent); + NS_IMETHOD Error(nsIDOMEvent* aEvent); +#endif + private: nsCOMPtr mBaseURI; + +#ifdef IMPLEMENT_SYNC_LOAD + nsCOMPtr mChromeWindow; +#endif }; #endif diff --git a/mozilla/extensions/xmlextras/base/src/nsXMLHttpRequest.cpp b/mozilla/extensions/xmlextras/base/src/nsXMLHttpRequest.cpp index 353b4eb65ea..7a63f23e827 100644 --- a/mozilla/extensions/xmlextras/base/src/nsXMLHttpRequest.cpp +++ b/mozilla/extensions/xmlextras/base/src/nsXMLHttpRequest.cpp @@ -58,6 +58,8 @@ #endif #include "nsIDOMClassInfo.h" #include "nsIDOMElement.h" +#include "nsIParser.h" +#include "nsLoadListenerProxy.h" static const char* kLoadAsData = "loadAsData"; #define LOADSTR NS_LITERAL_STRING("load") @@ -104,109 +106,6 @@ GetCurrentContext(nsIScriptContext **aScriptContext) return; } -///////////////////////////////////////////// -// -// This class exists to prevent a circular reference between -// the loaded document and the nsXMLHttpRequest instance. The -// request owns the document. While the document is loading, -// the request is a load listener, held onto by the document. -// The proxy class breaks the circularity by filling in as the -// load listener and holding a weak reference to the request -// object. -// -///////////////////////////////////////////// - -class nsLoadListenerProxy : public nsIDOMLoadListener { -public: - nsLoadListenerProxy(nsWeakPtr aParent); - virtual ~nsLoadListenerProxy(); - - NS_DECL_ISUPPORTS - - // nsIDOMEventListener - NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent); - - // nsIDOMLoadListener - NS_IMETHOD Load(nsIDOMEvent* aEvent); - NS_IMETHOD Unload(nsIDOMEvent* aEvent); - NS_IMETHOD Abort(nsIDOMEvent* aEvent); - NS_IMETHOD Error(nsIDOMEvent* aEvent); - -protected: - nsWeakPtr mParent; -}; - -nsLoadListenerProxy::nsLoadListenerProxy(nsWeakPtr aParent) -{ - NS_INIT_ISUPPORTS(); - mParent = aParent; -} - -nsLoadListenerProxy::~nsLoadListenerProxy() -{ -} - -NS_IMPL_ISUPPORTS1(nsLoadListenerProxy, nsIDOMLoadListener) - -NS_IMETHODIMP -nsLoadListenerProxy::HandleEvent(nsIDOMEvent* aEvent) -{ - nsCOMPtr listener(do_QueryReferent(mParent)); - - if (listener) { - return listener->HandleEvent(aEvent); - } - - return NS_OK; -} - -NS_IMETHODIMP -nsLoadListenerProxy::Load(nsIDOMEvent* aEvent) -{ - nsCOMPtr listener(do_QueryReferent(mParent)); - - if (listener) { - return listener->Load(aEvent); - } - - return NS_OK; -} - -NS_IMETHODIMP -nsLoadListenerProxy::Unload(nsIDOMEvent* aEvent) -{ - nsCOMPtr listener(do_QueryReferent(mParent)); - - if (listener) { - return listener->Unload(aEvent); - } - - return NS_OK; -} - -NS_IMETHODIMP -nsLoadListenerProxy::Abort(nsIDOMEvent* aEvent) -{ - nsCOMPtr listener(do_QueryReferent(mParent)); - - if (listener) { - return listener->Abort(aEvent); - } - - return NS_OK; -} - -NS_IMETHODIMP -nsLoadListenerProxy::Error(nsIDOMEvent* aEvent) -{ - nsCOMPtr listener(do_QueryReferent(mParent)); - - if (listener) { - return listener->Error(aEvent); - } - - return NS_OK; -} ///////////////////////////////////////////// // @@ -924,12 +823,23 @@ nsXMLHttpRequest::OnStartRequest(nsIRequest *request, nsISupports *ctxt) NS_IMETHODIMP nsXMLHttpRequest::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status) { + nsCOMPtr parser(do_QueryInterface(mXMLParserStreamListener)); + NS_ABORT_IF_FALSE(parser, "stream listener was expected to be a parser"); + nsresult rv = mXMLParserStreamListener->OnStopRequest(request,ctxt,status); mXMLParserStreamListener = nsnull; mReadRequest = nsnull; mContext = nsnull; - RequestCompleted(); + // The parser needs to be enabled for us to safely call RequestCompleted(). + // If the parser is not enabled, it means it was blocked, by xml-stylesheet PI + // for example, and is still building the document. RequestCompleted() must be + // called later, when we get the load event from the document. + if (parser && parser->IsParserEnabled()) { + RequestCompleted(); + } else { + ChangeState(XML_HTTP_REQUEST_STOPPED,PR_FALSE); + } return rv; } @@ -940,8 +850,10 @@ nsXMLHttpRequest::RequestCompleted() nsresult rv = NS_OK; // If we're uninitialized at this point, we encountered an error - // earlier and listeners have already been notified. - if (mStatus == XML_HTTP_REQUEST_UNINITIALIZED) { + // earlier and listeners have already been notified. Also we do + // not want to do this if we already completed. + if ((mStatus == XML_HTTP_REQUEST_UNINITIALIZED) || + (mStatus == XML_HTTP_REQUEST_COMPLETED)) { return NS_OK; } @@ -1267,10 +1179,17 @@ NS_IMETHODIMP nsXMLHttpRequest::GetReadyState(PRInt32 *aState) { NS_ENSURE_ARG_POINTER(aState); - if (mStatus == XML_HTTP_REQUEST_SENT) { - *aState = XML_HTTP_REQUEST_OPENED; - } else { - *aState = mStatus; + // Translate some of our internal states for external consumers + switch (mStatus) { + case XML_HTTP_REQUEST_SENT: + *aState = XML_HTTP_REQUEST_OPENED; + break; + case XML_HTTP_REQUEST_STOPPED: + *aState = XML_HTTP_REQUEST_INTERACTIVE; + break; + default: + *aState = mStatus; + break; } return NS_OK; } @@ -1293,6 +1212,16 @@ nsXMLHttpRequest::Load(nsIDOMEvent* aEvent) // sending the load event until OnStopRequest(). In normal case // there is no harm done, we will get OnStopRequest() immediately // after the load event. + // + // However, if the data we were loading caused the parser to stop, + // for example when loading external stylesheets, we can receive + // the OnStopRequest() call before the parser has finished building + // the document. In that case, we obviously should not fire the event + // in OnStopRequest(). For those documents, we must wait for the load + // event from the document to fire our RequestCompleted(). + if (mStatus == XML_HTTP_REQUEST_STOPPED) { + RequestCompleted(); + } return NS_OK; } diff --git a/mozilla/extensions/xmlextras/base/src/nsXMLHttpRequest.h b/mozilla/extensions/xmlextras/base/src/nsXMLHttpRequest.h index 964a4014e93..87568db5768 100644 --- a/mozilla/extensions/xmlextras/base/src/nsXMLHttpRequest.h +++ b/mozilla/extensions/xmlextras/base/src/nsXMLHttpRequest.h @@ -51,7 +51,8 @@ enum nsXMLHttpRequestState { XML_HTTP_REQUEST_LOADED, XML_HTTP_REQUEST_INTERACTIVE, XML_HTTP_REQUEST_COMPLETED, - XML_HTTP_REQUEST_SENT // This is Mozilla-internal only, LOADING in IE and external view + XML_HTTP_REQUEST_SENT, // This is Mozilla-internal only, LOADING in IE and external view + XML_HTTP_REQUEST_STOPPED // This is Mozilla-internal only, INTERACTIVE in IE and external view }; class nsXMLHttpRequest : public nsIXMLHttpRequest, diff --git a/mozilla/extensions/xmlextras/tests/Makefile.in b/mozilla/extensions/xmlextras/tests/Makefile.in index 9b39968cb25..8795f27f654 100644 --- a/mozilla/extensions/xmlextras/tests/Makefile.in +++ b/mozilla/extensions/xmlextras/tests/Makefile.in @@ -26,7 +26,15 @@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk -REQUIRES = xpcom string xmlextras necko dom layout widget +REQUIRES = xpcom \ + string \ + xmlextras \ + necko \ + dom \ + layout \ + content \ + widget \ + $(NULL) CPPSRCS = \ TestXMLExtras.cpp \ diff --git a/mozilla/extensions/xmlextras/tests/get.html b/mozilla/extensions/xmlextras/tests/get.html index 8d167a6ad12..d56c8e7a190 100644 --- a/mozilla/extensions/xmlextras/tests/get.html +++ b/mozilla/extensions/xmlextras/tests/get.html @@ -18,17 +18,26 @@ pre {