282103 - dynamic overlays: implement "loadOverlay" method on nsIDOMXULDocument which loads and merges an overlay into a XUL document. r=jst sr=bryner

This commit is contained in:
ben%bengoodger.com 2005-02-25 09:06:34 +00:00
parent 24a21ad92d
commit 7563b3836a
3 changed files with 303 additions and 41 deletions

View File

@ -2654,6 +2654,193 @@ nsXULDocument::AddChromeOverlays()
return NS_OK;
}
NS_IMETHODIMP
nsXULDocument::LoadOverlay(const nsAString& aURL, nsIObserver* aObserver)
{
nsresult rv;
nsCOMPtr<nsIURI> 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<nsIScriptSecurityManager> 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<nsIParser> 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<nsIStreamListener> 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<nsILoadGroup> 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<nsIObserver>& aObserver, void* aClosure)
{
aObserver->Observe(aKey, "xul-overlay-merged", EmptyString().get());
typedef nsInterfaceHashtable<nsURIHashKey,nsIObserver> 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<nsILoadGroup> 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<nsILoadGroup> 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<nsIURI> overlayURI;
mCurrentPrototype->GetURI(getter_AddRefs(overlayURI));
nsCOMPtr<nsIObserver> 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(&notify);
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<nsIContent> 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);
}
//----------------------------------------------------------------------

View File

@ -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<nsIContent> 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<nsURIHashKey,nsIObserver> mOverlayLoadObservers;
nsInterfaceHashtable<nsURIHashKey,nsIObserver> mPendingOverlayLoadNotifications;
PRBool mInitialLayoutComplete;
private:
// helpers

View File

@ -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);
};