From 7563b3836a36e441524e86a6f5982147cc5bc9d0 Mon Sep 17 00:00:00 2001 From: "ben%bengoodger.com" Date: Fri, 25 Feb 2005 09:06:34 +0000 Subject: [PATCH] 282103 - dynamic overlays: implement "loadOverlay" method on nsIDOMXULDocument which loads and merges an overlay into a XUL document. r=jst sr=bryner --- content/xul/document/src/nsXULDocument.cpp | 321 ++++++++++++++++++--- content/xul/document/src/nsXULDocument.h | 17 +- dom/public/idl/xul/nsIDOMXULDocument.idl | 6 +- 3 files changed, 303 insertions(+), 41 deletions(-) diff --git a/content/xul/document/src/nsXULDocument.cpp b/content/xul/document/src/nsXULDocument.cpp index 7dcccc520315..c251eb6e658a 100644 --- a/content/xul/document/src/nsXULDocument.cpp +++ b/content/xul/document/src/nsXULDocument.cpp @@ -2654,6 +2654,193 @@ nsXULDocument::AddChromeOverlays() return NS_OK; } +NS_IMETHODIMP +nsXULDocument::LoadOverlay(const nsAString& aURL, nsIObserver* aObserver) +{ + nsresult rv; + + nsCOMPtr uri; + rv = NS_NewURI(getter_AddRefs(uri), aURL, nsnull); + if (NS_FAILED(rv)) return rv; + + if (aObserver) { + nsIObserver* obs = nsnull; + if (!mOverlayLoadObservers.IsInitialized()) + mOverlayLoadObservers.Init(); + else + obs = mOverlayLoadObservers.GetWeak(uri); + + if (obs) { + // We don't support loading the same overlay twice into the same + // document - that doesn't make sense anyway. + return NS_ERROR_FAILURE; + } + mOverlayLoadObservers.Put(uri, aObserver); + } + PRBool shouldReturn; + return LoadOverlayInternal(uri, PR_TRUE, &shouldReturn); +} + +nsresult +nsXULDocument::LoadOverlayInternal(nsIURI* aURI, PRBool aIsDynamic, PRBool* aShouldReturn) +{ + nsresult rv; + + *aShouldReturn = PR_FALSE; + + nsCOMPtr secMan = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + +#ifdef PR_LOGGING + if (PR_LOG_TEST(gXULLog, PR_LOG_DEBUG)) { + nsCAutoString urlspec; + aURI->GetSpec(urlspec); + + PR_LOG(gXULLog, PR_LOG_DEBUG, + ("xul: loading overlay %s", urlspec.get())); + } +#endif + + mResolutionPhase = nsForwardReference::eStart; + + // Chrome documents are allowed to load overlays from anywhere. + // Also, any document may load a chrome:// overlay. + // In all other cases, the overlay is only allowed to load if + // the master document and prototype document have the same origin. + + PRBool overlayIsChrome = IsChromeURI(aURI); + if (!IsChromeURI(mDocumentURI) && !overlayIsChrome) { + // Make sure we're allowed to load this overlay. + rv = secMan->CheckSameOriginURI(mDocumentURI, aURI); + if (NS_FAILED(rv)) return rv; + } + + // Look in the prototype cache for the prototype document with + // the specified overlay URI. + if (overlayIsChrome) + gXULCache->GetPrototype(aURI, getter_AddRefs(mCurrentPrototype)); + else + mCurrentPrototype = nsnull; + + // Same comment as nsChromeProtocolHandler::NewChannel and + // nsXULDocument::StartDocumentLoad + // - Ben Goodger + // + // We don't abort on failure here because there are too many valid + // cases that can return failure, and the null-ness of |proto| is + // enough to trigger the fail-safe parse-from-disk solution. + // Example failure cases (for reference) include: + // + // NS_ERROR_NOT_AVAILABLE: the URI was not found in the FastLoad file, + // parse from disk + // other: the FastLoad file, XUL.mfl, could not be found, probably + // due to being accessed before a profile has been selected + // (e.g. loading chrome for the profile manager itself). + // The .xul file must be parsed from disk. + + PRBool useXULCache; + gXULCache->GetEnabled(&useXULCache); + mIsWritingFastLoad = useXULCache; + + if (useXULCache && mCurrentPrototype) { + PRBool loaded; + rv = mCurrentPrototype->AwaitLoadDone(this, &loaded); + if (NS_FAILED(rv)) return rv; + + if (! loaded) { + // Return to the main event loop and eagerly await the + // prototype overlay load's completion. When the content + // sink completes, it will trigger an EndLoad(), which'll + // wind us back up here, in ResumeWalk(). + *aShouldReturn = PR_TRUE; + return NS_OK; + } + + // Found the overlay's prototype in the cache, fully loaded. + rv = AddPrototypeSheets(); + if (NS_FAILED(rv)) return rv; + + // Now prepare to walk the prototype to create its content + rv = PrepareToWalk(); + if (NS_FAILED(rv)) return rv; + + PR_LOG(gXULLog, PR_LOG_DEBUG, ("xul: overlay was cached")); + + // If this is a dynamic overlay and we have the prototype in the chrome + // cache already, we must manually call ResumeWalk. + if (aIsDynamic) + return ResumeWalk(); + } + else { + // Not there. Initiate a load. + PR_LOG(gXULLog, PR_LOG_DEBUG, ("xul: overlay was not cached")); + + nsCOMPtr parser; + rv = PrepareToLoadPrototype(aURI, "view", nsnull, getter_AddRefs(parser)); + if (NS_FAILED(rv)) return rv; + + // Predicate mIsWritingFastLoad on the XUL cache being enabled, + // so we don't have to re-check whether the cache is enabled all + // the time. + mIsWritingFastLoad = useXULCache; + + nsCOMPtr listener = do_QueryInterface(parser); + if (! listener) + return NS_ERROR_UNEXPECTED; + + // Add an observer to the parser; this'll get called when + // Necko fires its On[Start|Stop]Request() notifications, + // and will let us recover from a missing overlay. + ParserObserver* parserObserver = new ParserObserver(this); + if (! parserObserver) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(parserObserver); + parser->Parse(aURI, parserObserver); + NS_RELEASE(parserObserver); + + nsCOMPtr group = do_QueryReferent(mDocumentLoadGroup); + rv = NS_OpenURI(listener, nsnull, aURI, nsnull, group); + if (NS_FAILED(rv)) { + // Just move on to the next overlay. NS_OpenURI could fail + // just because a channel could not be opened, which can happen + // if a file or chrome package does not exist. + ReportMissingOverlay(aURI); + return rv; + } + + // If it's a 'chrome:' prototype document, then put it into + // the prototype cache; other XUL documents will be reloaded + // each time. We must do this after NS_OpenURI and AsyncOpen, + // or chrome code will wrongly create a cached chrome channel + // instead of a real one. + if (useXULCache && overlayIsChrome) { + rv = gXULCache->PutPrototype(mCurrentPrototype); + if (NS_FAILED(rv)) return rv; + } + + // Return to the main event loop and eagerly await the + // overlay load's completion. When the content sink + // completes, it will trigger an EndLoad(), which'll wind + // us back in ResumeWalk(). + if (!aIsDynamic) + *aShouldReturn = PR_TRUE; + } + return NS_OK; +} + +PR_STATIC_CALLBACK(PLDHashOperator) +FirePendingMergeNotification(nsIURI* aKey, nsCOMPtr& aObserver, void* aClosure) +{ + aObserver->Observe(aKey, "xul-overlay-merged", EmptyString().get()); + + typedef nsInterfaceHashtable table; + table* observers = NS_STATIC_CAST(table*, aClosure); + observers->Remove(aKey); + + return PL_DHASH_REMOVE; +} + nsresult nsXULDocument::ResumeWalk() { @@ -2970,33 +3157,83 @@ nsXULDocument::ResumeWalk() rv = ApplyPersistentAttributes(); if (NS_FAILED(rv)) return rv; - // Everything after this point we only want to do once we're - // certain that we've been embedded in a presentation shell. + PRBool didInitialReflow = PR_TRUE; + nsIPresShell *shell = GetShellAt(0); + if (shell) + shell->GetDidInitialReflow(&didInitialReflow); - nsAutoString title; - mRootContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::title, title); - SetTitle(title); + if (!didInitialReflow) { + // Everything after this point we only want to do once we're + // certain that we've been embedded in a presentation shell. - StartLayout(); + nsAutoString title; + mRootContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::title, title); + SetTitle(title); - if (mIsWritingFastLoad && IsChromeURI(mDocumentURI)) - gXULCache->WritePrototype(mMasterPrototype); + StartLayout(); - for (PRInt32 i = mObservers.Count() - 1; i >= 0; --i) { - nsIDocumentObserver* observer = (nsIDocumentObserver*) mObservers[i]; - observer->EndLoad(this); + if (mIsWritingFastLoad && IsChromeURI(mDocumentURI)) + gXULCache->WritePrototype(mMasterPrototype); + + for (PRInt32 i = mObservers.Count() - 1; i >= 0; --i) { + nsIDocumentObserver* observer = (nsIDocumentObserver*) mObservers[i]; + observer->EndLoad(this); + } + NS_ASSERTION(mPlaceHolderRequest, "Bug 119310, perhaps overlayinfo referenced a overlay that doesn't exist"); + if (mPlaceHolderRequest) { + // Remove the placeholder channel; if we're the last channel in the + // load group, this will fire the OnEndDocumentLoad() method in the + // docshell, and run the onload handlers, etc. + nsCOMPtr group = do_QueryReferent(mDocumentLoadGroup); + if (group) { + rv = group->RemoveRequest(mPlaceHolderRequest, nsnull, NS_OK); + if (NS_FAILED(rv)) return rv; + } + } + mInitialLayoutComplete = PR_TRUE; + + // Walk the set of pending load notifications and notify any observers. + // See below for detail. + if (mPendingOverlayLoadNotifications.IsInitialized()) + mPendingOverlayLoadNotifications.Enumerate(FirePendingMergeNotification, (void*)&mOverlayLoadObservers); } - NS_ASSERTION(mPlaceHolderRequest, "Bug 119310, perhaps overlayinfo referenced a overlay that doesn't exist"); - if (mPlaceHolderRequest) { - // Remove the placeholder channel; if we're the last channel in the - // load group, this will fire the OnEndDocumentLoad() method in the - // docshell, and run the onload handlers, etc. - nsCOMPtr group = do_QueryReferent(mDocumentLoadGroup); - if (group) { - rv = group->RemoveRequest(mPlaceHolderRequest, nsnull, NS_OK); - if (NS_FAILED(rv)) return rv; - - mPlaceHolderRequest = nsnull; + else { + if (mOverlayLoadObservers.IsInitialized()) { + nsCOMPtr overlayURI; + mCurrentPrototype->GetURI(getter_AddRefs(overlayURI)); + nsCOMPtr obs; + if (mInitialLayoutComplete) { + // We have completed initial layout, so just send the notification. + mOverlayLoadObservers.Get(overlayURI, getter_AddRefs(obs)); + NS_ASSERTION(obs, "null overlay load observer?!"); + obs->Observe(overlayURI, "xul-overlay-merged", EmptyString().get()); + mOverlayLoadObservers.Remove(overlayURI); + } + else { + // If we have not yet displayed the document for the first time + // (i.e. we came in here as the result of a dynamic overlay load + // which was spawned by a binding-attached event caused by + // StartLayout() on the master prototype - we must remember that + // this overlay has been merged and tell the listeners after + // StartLayout() is completely finished rather than doing so + // immediately - otherwise we may be executing code that needs to + // access XBL Binding implementations on nodes for which frames + // have not yet been constructed because their bindings have not + // yet been attached. This can be a race condition because dynamic + // overlay loading can take varying amounts of time depending on + // whether or not the overlay prototype is in the XUL cache. The + // most likely effect of this bug is odd UI initialization due to + // methods and properties that do not work. + if (!mPendingOverlayLoadNotifications.IsInitialized()) + mPendingOverlayLoadNotifications.Init(); + else + mPendingOverlayLoadNotifications.Get(overlayURI, getter_AddRefs(obs)); + if (!obs) { + mOverlayLoadObservers.Get(overlayURI, getter_AddRefs(obs)); + NS_ASSERTION(obs, "null overlay load observer?"); + mPendingOverlayLoadNotifications.Put(overlayURI, obs); + } + } } } @@ -3559,6 +3796,11 @@ nsXULDocument::OverlayForwardReference::Resolve() // Resolve a forward reference from an overlay element; attempt to // hook it up into the main document. nsresult rv; + + PRBool notify = PR_FALSE; + nsIPresShell *shell = mDocument->GetShellAt(0); + if (shell) + shell->GetDidInitialReflow(¬ify); nsAutoString id; rv = mOverlay->GetAttr(kNameSpaceID_None, nsXULAtoms::id, id); @@ -3566,7 +3808,7 @@ nsXULDocument::OverlayForwardReference::Resolve() if (id.IsEmpty()) { // overlay had no id, use the root element - mDocument->InsertElement(mDocument->mRootContent, mOverlay); + mDocument->InsertElement(mDocument->mRootContent, mOverlay, notify); mResolved = PR_TRUE; return eResolve_Succeeded; } @@ -3585,7 +3827,7 @@ nsXULDocument::OverlayForwardReference::Resolve() if (! target) return eResolve_Error; - rv = Merge(target, mOverlay); + rv = Merge(target, mOverlay, notify); if (NS_FAILED(rv)) return eResolve_Error; // Add child and any descendants to the element map @@ -3610,13 +3852,22 @@ nsXULDocument::OverlayForwardReference::Resolve() nsresult nsXULDocument::OverlayForwardReference::Merge(nsIContent* aTargetNode, - nsIContent* aOverlayNode) + nsIContent* aOverlayNode, + PRBool aNotify) { // This function is given: // aTargetNode: the node in the document whose 'id' attribute // matches a toplevel node in our overlay. // aOverlayNode: the node in the overlay document that matches // a node in the actual document. + // aNotify: whether or not content manipulation methods should + // use the aNotify parameter. After the initial + // reflow (i.e. in the dynamic overlay merge case), + // we want all the content manipulation methods we + // call to notify so that frames are constructed + // etc. Otherwise do not, since that's during initial + // document construction before StartLayout has been + // called which will do everything for us. // // This function merges the tree from the overlay into the tree in // the document, overwriting attributes and appending child content @@ -3652,13 +3903,13 @@ nsXULDocument::OverlayForwardReference::Merge(nsIContent* aTargetNode, if (attr == nsXULAtoms::removeelement && value.EqualsLiteral("true")) { - rv = RemoveElement(aTargetNode->GetParent(), aTargetNode); + rv = RemoveElement(aTargetNode->GetParent(), aTargetNode, aNotify); if (NS_FAILED(rv)) return rv; return NS_OK; } - rv = aTargetNode->SetAttr(nameSpaceID, attr, prefix, value, PR_FALSE); + rv = aTargetNode->SetAttr(nameSpaceID, attr, prefix, value, aNotify); if (NS_FAILED(rv)) return rv; } @@ -3719,7 +3970,7 @@ nsXULDocument::OverlayForwardReference::Merge(nsIContent* aTargetNode, if (parentID.Equals(documentParentID)) { // The element matches. "Go Deep!" nsCOMPtr childDocumentContent(do_QueryInterface(nodeInDocument)); - rv = Merge(childDocumentContent, currContent); + rv = Merge(childDocumentContent, currContent, aNotify); if (NS_FAILED(rv)) return rv; rv = aOverlayNode->RemoveChildAt(0, PR_FALSE); if (NS_FAILED(rv)) return rv; @@ -3731,7 +3982,7 @@ nsXULDocument::OverlayForwardReference::Merge(nsIContent* aTargetNode, rv = aOverlayNode->RemoveChildAt(0, PR_FALSE); if (NS_FAILED(rv)) return rv; - rv = InsertElement(aTargetNode, currContent); + rv = InsertElement(aTargetNode, currContent, aNotify); if (NS_FAILED(rv)) return rv; } @@ -3977,7 +4228,7 @@ nsXULDocument::CheckBroadcasterHookup(nsXULDocument* aDocument, } nsresult -nsXULDocument::InsertElement(nsIContent* aParent, nsIContent* aChild) +nsXULDocument::InsertElement(nsIContent* aParent, nsIContent* aChild, PRBool aNotify) { // Insert aChild appropriately into aParent, accounting for a // 'pos' attribute set on aChild. @@ -4029,7 +4280,7 @@ nsXULDocument::InsertElement(nsIContent* aParent, nsIContent* aChild) if (pos != -1) { pos = isInsertAfter ? pos + 1 : pos; - rv = aParent->InsertChildAt(aChild, pos, PR_FALSE, PR_TRUE); + rv = aParent->InsertChildAt(aChild, pos, aNotify, PR_TRUE); if (NS_FAILED(rv)) return rv; @@ -4047,7 +4298,7 @@ nsXULDocument::InsertElement(nsIContent* aParent, nsIContent* aChild) // Positions are one-indexed. PRInt32 pos = posStr.ToInteger(NS_REINTERPRET_CAST(PRInt32*, &rv)); if (NS_SUCCEEDED(rv)) { - rv = aParent->InsertChildAt(aChild, pos - 1, PR_FALSE, + rv = aParent->InsertChildAt(aChild, pos - 1, aNotify, PR_TRUE); if (NS_SUCCEEDED(rv)) wasInserted = PR_TRUE; @@ -4060,18 +4311,18 @@ nsXULDocument::InsertElement(nsIContent* aParent, nsIContent* aChild) } if (! wasInserted) { - rv = aParent->AppendChildTo(aChild, PR_FALSE, PR_TRUE); + rv = aParent->AppendChildTo(aChild, aNotify, PR_TRUE); if (NS_FAILED(rv)) return rv; } return NS_OK; } nsresult -nsXULDocument::RemoveElement(nsIContent* aParent, nsIContent* aChild) +nsXULDocument::RemoveElement(nsIContent* aParent, nsIContent* aChild, PRBool aNotify) { PRInt32 nodeOffset = aParent->IndexOf(aChild); - return aParent->RemoveChildAt(nodeOffset, PR_TRUE); + return aParent->RemoveChildAt(nodeOffset, aNotify); } //---------------------------------------------------------------------- diff --git a/content/xul/document/src/nsXULDocument.h b/content/xul/document/src/nsXULDocument.h index 31fe3ca3d612..8ac3953eb400 100644 --- a/content/xul/document/src/nsXULDocument.h +++ b/content/xul/document/src/nsXULDocument.h @@ -69,7 +69,9 @@ class nsIXULPrototypeScript; #include "nsIObjectOutputStream.h" #include "nsXULElement.h" #endif - +#include "nsURIHashKey.h" +#include "nsInterfaceHashtable.h" + struct JSObject; struct PRLogModuleInfo; @@ -205,6 +207,9 @@ protected: nsIPrincipal* aDocumentPrincipal, nsIParser** aResult); + nsresult + LoadOverlayInternal(nsIURI* aURI, PRBool aIsDynamic, PRBool* aShouldReturn); + nsresult ApplyPersistentAttributes(); nsresult ApplyPersistentAttributesToElements(nsIRDFResource* aResource, nsISupportsArray* aElements); @@ -441,7 +446,7 @@ protected: nsCOMPtr mOverlay; // [OWNER] PRBool mResolved; - nsresult Merge(nsIContent* aTargetNode, nsIContent* aOverlayNode); + nsresult Merge(nsIContent* aTargetNode, nsIContent* aOverlayNode, PRBool aNotify); public: OverlayForwardReference(nsXULDocument* aDocument, nsIContent* aOverlay) @@ -484,11 +489,11 @@ protected: static nsresult - InsertElement(nsIContent* aParent, nsIContent* aChild); + InsertElement(nsIContent* aParent, nsIContent* aChild, PRBool aNotify); static nsresult - RemoveElement(nsIContent* aParent, nsIContent* aChild); + RemoveElement(nsIContent* aParent, nsIContent* aChild, PRBool aNotify); /** * The current prototype that we are walking to construct the @@ -574,6 +579,10 @@ protected: */ PLDHashTable* mBroadcasterMap; + nsInterfaceHashtable mOverlayLoadObservers; + nsInterfaceHashtable mPendingOverlayLoadNotifications; + + PRBool mInitialLayoutComplete; private: // helpers diff --git a/dom/public/idl/xul/nsIDOMXULDocument.idl b/dom/public/idl/xul/nsIDOMXULDocument.idl index 21964ff0e5e9..e8dbff16501d 100644 --- a/dom/public/idl/xul/nsIDOMXULDocument.idl +++ b/dom/public/idl/xul/nsIDOMXULDocument.idl @@ -40,9 +40,9 @@ #include "domstubs.idl" interface nsIDOMXULCommandDispatcher; +interface nsIObserver; - -[scriptable, uuid(17ddd8c0-c5f8-11d2-a6ae-00104bde6048)] +[scriptable, uuid(e64bb081-13ba-430e-ab70-73a9f1d3de58)] interface nsIDOMXULDocument : nsISupports { attribute nsIDOMNode popupNode; @@ -65,4 +65,6 @@ interface nsIDOMXULDocument : nsISupports in DOMString attr); void persist(in DOMString id, in DOMString attr); + + void loadOverlay(in DOMString url, in nsIObserver aObserver); };