From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- dom/xul/XULDocument.cpp | 4591 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4591 insertions(+) create mode 100644 dom/xul/XULDocument.cpp (limited to 'dom/xul/XULDocument.cpp') diff --git a/dom/xul/XULDocument.cpp b/dom/xul/XULDocument.cpp new file mode 100644 index 000000000..ae3cdb7eb --- /dev/null +++ b/dom/xul/XULDocument.cpp @@ -0,0 +1,4591 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=4 sw=4 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + An implementation for the XUL document. This implementation serves + as the basis for generating an NGLayout content model. + + Notes + ----- + + 1. We do some monkey business in the document observer methods to + keep the element map in sync for HTML elements. Why don't we just + do it for _all_ elements? Well, in the case of XUL elements, + which may be lazily created during frame construction, the + document observer methods will never be called because we'll be + adding the XUL nodes into the content model "quietly". + +*/ + +#include "mozilla/ArrayUtils.h" + +#include "XULDocument.h" + +#include "nsError.h" +#include "nsIBoxObject.h" +#include "nsIChromeRegistry.h" +#include "nsView.h" +#include "nsViewManager.h" +#include "nsIContentViewer.h" +#include "nsIDOMXULElement.h" +#include "nsIStreamListener.h" +#include "nsITimer.h" +#include "nsDocShell.h" +#include "nsGkAtoms.h" +#include "nsXMLContentSink.h" +#include "nsXULContentSink.h" +#include "nsXULContentUtils.h" +#include "nsIXULOverlayProvider.h" +#include "nsIStringEnumerator.h" +#include "nsNetUtil.h" +#include "nsParserCIID.h" +#include "nsPIBoxObject.h" +#include "mozilla/dom/BoxObject.h" +#include "nsXPIDLString.h" +#include "nsPIDOMWindow.h" +#include "nsPIWindowRoot.h" +#include "nsXULCommandDispatcher.h" +#include "nsXULElement.h" +#include "mozilla/Logging.h" +#include "rdf.h" +#include "nsIFrame.h" +#include "nsXBLService.h" +#include "nsCExternalHandlerService.h" +#include "nsMimeTypes.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsContentList.h" +#include "nsIScriptGlobalObject.h" +#include "nsIScriptSecurityManager.h" +#include "nsNodeInfoManager.h" +#include "nsContentCreatorFunctions.h" +#include "nsContentUtils.h" +#include "nsIParser.h" +#include "nsCharsetSource.h" +#include "nsIParserService.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/css/Loader.h" +#include "nsIScriptError.h" +#include "nsIStyleSheetLinkingElement.h" +#include "nsIObserverService.h" +#include "nsNodeUtils.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIXULWindow.h" +#include "nsXULPopupManager.h" +#include "nsCCUncollectableMarker.h" +#include "nsURILoader.h" +#include "mozilla/AddonPathService.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/NodeInfoInlines.h" +#include "mozilla/dom/ProcessingInstruction.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/XULDocumentBinding.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/Preferences.h" +#include "nsTextNode.h" +#include "nsJSUtils.h" +#include "mozilla/dom/URL.h" +#include "nsIContentPolicy.h" +#include "mozAutoDocUpdate.h" +#include "xpcpublic.h" +#include "mozilla/StyleSheet.h" +#include "mozilla/StyleSheetInlines.h" + +using namespace mozilla; +using namespace mozilla::dom; + +//---------------------------------------------------------------------- +// +// CIDs +// + +static NS_DEFINE_CID(kParserCID, NS_PARSER_CID); + +static bool IsOverlayAllowed(nsIURI* aURI) +{ + bool canOverlay = false; + if (NS_SUCCEEDED(aURI->SchemeIs("about", &canOverlay)) && canOverlay) + return true; + if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &canOverlay)) && canOverlay) + return true; + return false; +} + +//---------------------------------------------------------------------- +// +// Miscellaneous Constants +// + +const nsForwardReference::Phase nsForwardReference::kPasses[] = { + nsForwardReference::eConstruction, + nsForwardReference::eHookup, + nsForwardReference::eDone +}; + +//---------------------------------------------------------------------- +// +// Statics +// + +int32_t XULDocument::gRefCnt = 0; + +LazyLogModule XULDocument::gXULLog("XULDocument"); + +//---------------------------------------------------------------------- + +struct BroadcastListener { + nsWeakPtr mListener; + nsCOMPtr mAttribute; +}; + +struct BroadcasterMapEntry : public PLDHashEntryHdr +{ + Element* mBroadcaster; // [WEAK] + nsTArray mListeners; // [OWNING] of BroadcastListener objects +}; + +Element* +nsRefMapEntry::GetFirstElement() +{ + return mRefContentList.SafeElementAt(0); +} + +void +nsRefMapEntry::AppendAll(nsCOMArray* aElements) +{ + for (size_t i = 0; i < mRefContentList.Length(); ++i) { + aElements->AppendObject(mRefContentList[i]); + } +} + +bool +nsRefMapEntry::AddElement(Element* aElement) +{ + if (mRefContentList.Contains(aElement)) { + return true; + } + return mRefContentList.AppendElement(aElement); +} + +bool +nsRefMapEntry::RemoveElement(Element* aElement) +{ + mRefContentList.RemoveElement(aElement); + return mRefContentList.IsEmpty(); +} + +//---------------------------------------------------------------------- +// +// ctors & dtors +// + +namespace mozilla { +namespace dom { + +XULDocument::XULDocument(void) + : XMLDocument("application/vnd.mozilla.xul+xml"), + mDocLWTheme(Doc_Theme_Uninitialized), + mState(eState_Master), + mResolutionPhase(nsForwardReference::eStart) +{ + // NOTE! nsDocument::operator new() zeroes out all members, so don't + // bother initializing members to 0. + + // Override the default in nsDocument + mCharacterSet.AssignLiteral("UTF-8"); + + mDefaultElementType = kNameSpaceID_XUL; + mType = eXUL; + + mDelayFrameLoaderInitialization = true; + + mAllowXULXBL = eTriTrue; +} + +XULDocument::~XULDocument() +{ + NS_ASSERTION(mNextSrcLoadWaiter == nullptr, + "unreferenced document still waiting for script source to load?"); + + // In case we failed somewhere early on and the forward observer + // decls never got resolved. + mForwardReferences.Clear(); + // Likewise for any references we have to IDs where we might + // look for persisted data: + mPersistenceIds.Clear(); + + // Destroy our broadcaster map. + delete mBroadcasterMap; + + delete mTemplateBuilderTable; + + Preferences::UnregisterCallback(XULDocument::DirectionChanged, + "intl.uidirection.", this); + + if (mOffThreadCompileStringBuf) { + js_free(mOffThreadCompileStringBuf); + } +} + +} // namespace dom +} // namespace mozilla + +nsresult +NS_NewXULDocument(nsIXULDocument** result) +{ + NS_PRECONDITION(result != nullptr, "null ptr"); + if (! result) + return NS_ERROR_NULL_POINTER; + + RefPtr doc = new XULDocument(); + + nsresult rv; + if (NS_FAILED(rv = doc->Init())) { + return rv; + } + + doc.forget(result); + return NS_OK; +} + + +namespace mozilla { +namespace dom { + +//---------------------------------------------------------------------- +// +// nsISupports interface +// + +NS_IMPL_CYCLE_COLLECTION_CLASS(XULDocument) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XULDocument, XMLDocument) + NS_ASSERTION(!nsCCUncollectableMarker::InGeneration(cb, tmp->GetMarkedCCGeneration()), + "Shouldn't traverse XULDocument!"); + // XXX tmp->mForwardReferences? + // XXX tmp->mContextStack? + + // An element will only have a template builder as long as it's in the + // document, so we'll traverse the table here instead of from the element. + if (tmp->mTemplateBuilderTable) { + for (auto iter = tmp->mTemplateBuilderTable->Iter(); + !iter.Done(); + iter.Next()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mTemplateBuilderTable key"); + cb.NoteXPCOMChild(iter.Key()); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mTemplateBuilderTable value"); + cb.NoteXPCOMChild(iter.UserData()); + } + } + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCurrentPrototype) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMasterPrototype) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypes) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStore) + + if (tmp->mOverlayLoadObservers) { + for (auto iter = tmp->mOverlayLoadObservers->Iter(); + !iter.Done(); + iter.Next()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mOverlayLoadObservers value"); + cb.NoteXPCOMChild(iter.Data()); + } + } + if (tmp->mPendingOverlayLoadNotifications) { + for (auto iter = tmp->mPendingOverlayLoadNotifications->Iter(); + !iter.Done(); + iter.Next()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mPendingOverlayLoadNotifications value"); + cb.NoteXPCOMChild(iter.Data()); + } + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULDocument, XMLDocument) + delete tmp->mTemplateBuilderTable; + tmp->mTemplateBuilderTable = nullptr; + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStore) + //XXX We should probably unlink all the objects we traverse. +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_ADDREF_INHERITED(XULDocument, XMLDocument) +NS_IMPL_RELEASE_INHERITED(XULDocument, XMLDocument) + + +// QueryInterface implementation for XULDocument +NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(XULDocument) + NS_INTERFACE_TABLE_INHERITED(XULDocument, nsIXULDocument, + nsIDOMXULDocument, nsIStreamLoaderObserver, + nsICSSLoaderObserver, nsIOffThreadScriptReceiver) +NS_INTERFACE_TABLE_TAIL_INHERITING(XMLDocument) + + +//---------------------------------------------------------------------- +// +// nsIDocument interface +// + +void +XULDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) +{ + NS_NOTREACHED("Reset"); +} + +void +XULDocument::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup, + nsIPrincipal* aPrincipal) +{ + NS_NOTREACHED("ResetToURI"); +} + +void +XULDocument::SetContentType(const nsAString& aContentType) +{ + NS_ASSERTION(aContentType.EqualsLiteral("application/vnd.mozilla.xul+xml"), + "xul-documents always has content-type application/vnd.mozilla.xul+xml"); + // Don't do anything, xul always has the mimetype + // application/vnd.mozilla.xul+xml +} + +// This is called when the master document begins loading, whether it's +// being cached or not. +nsresult +XULDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel, + nsILoadGroup* aLoadGroup, + nsISupports* aContainer, + nsIStreamListener **aDocListener, + bool aReset, nsIContentSink* aSink) +{ + if (MOZ_LOG_TEST(gXULLog, LogLevel::Warning)) { + + nsCOMPtr uri; + nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(uri)); + if (NS_SUCCEEDED(rv)) { + nsAutoCString urlspec; + rv = uri->GetSpec(urlspec); + if (NS_SUCCEEDED(rv)) { + MOZ_LOG(gXULLog, LogLevel::Warning, + ("xul: load document '%s'", urlspec.get())); + } + } + } + // NOTE: If this ever starts calling nsDocument::StartDocumentLoad + // we'll possibly need to reset our content type afterwards. + mStillWalking = true; + mMayStartLayout = false; + mDocumentLoadGroup = do_GetWeakReference(aLoadGroup); + + mChannel = aChannel; + + // Get the URI. Note that this should match nsDocShell::OnLoadingSite + nsresult rv = + NS_GetFinalChannelURI(aChannel, getter_AddRefs(mDocumentURI)); + NS_ENSURE_SUCCESS(rv, rv); + + ResetStylesheetsToURI(mDocumentURI); + + RetrieveRelevantHeaders(aChannel); + + // Look in the chrome cache: we've got this puppy loaded + // already. + nsXULPrototypeDocument* proto = IsChromeURI(mDocumentURI) ? + nsXULPrototypeCache::GetInstance()->GetPrototype(mDocumentURI) : + nullptr; + + // Same comment as nsChromeProtocolHandler::NewChannel and + // XULDocument::ResumeWalk + // - 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 cannot be found in the startup cache, + // parse from disk + // other: the startup cache file could not be found, probably + // due to being accessed before a profile has been selected (e.g. + // loading chrome for the profile manager itself). This must be + // parsed from disk. + + if (proto) { + // If we're racing with another document to load proto, wait till the + // load has finished loading before trying to add cloned style sheets. + // XULDocument::EndLoad will call proto->NotifyLoadDone, which will + // find all racing documents and notify them via OnPrototypeLoadDone, + // which will add style sheet clones to each document. + bool loaded; + rv = proto->AwaitLoadDone(this, &loaded); + if (NS_FAILED(rv)) return rv; + + mMasterPrototype = mCurrentPrototype = proto; + + // Set up the right principal on ourselves. + SetPrincipal(proto->DocumentPrincipal()); + + // We need a listener, even if proto is not yet loaded, in which + // event the listener's OnStopRequest method does nothing, and all + // the interesting work happens below XULDocument::EndLoad, from + // the call there to mCurrentPrototype->NotifyLoadDone(). + *aDocListener = new CachedChromeStreamListener(this, loaded); + } + else { + bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled(); + bool fillXULCache = (useXULCache && IsChromeURI(mDocumentURI)); + + + // It's just a vanilla document load. Create a parser to deal + // with the stream n' stuff. + + nsCOMPtr parser; + rv = PrepareToLoad(aContainer, aCommand, aChannel, aLoadGroup, + 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, &rv); + NS_ASSERTION(NS_SUCCEEDED(rv), "parser doesn't support nsIStreamListener"); + if (NS_FAILED(rv)) return rv; + + *aDocListener = listener; + + parser->Parse(mDocumentURI); + + // Put the current prototype, created under PrepareToLoad, into the + // XUL prototype cache now. We can't do this under PrepareToLoad or + // overlay loading will break; search for PutPrototype in ResumeWalk + // and see the comment there. + if (fillXULCache) { + nsXULPrototypeCache::GetInstance()->PutPrototype(mCurrentPrototype); + } + } + + NS_IF_ADDREF(*aDocListener); + return NS_OK; +} + +// This gets invoked after a prototype for this document or one of +// its overlays is fully built in the content sink. +void +XULDocument::EndLoad() +{ + // This can happen if an overlay fails to load + if (!mCurrentPrototype) + return; + + nsresult rv; + + // Whack the prototype document into the cache so that the next + // time somebody asks for it, they don't need to load it by hand. + + nsCOMPtr uri = mCurrentPrototype->GetURI(); + bool isChrome = IsChromeURI(uri); + + // Remember if the XUL cache is on + bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled(); + + // If the current prototype is an overlay document (non-master prototype) + // and we're filling the FastLoad disk cache, tell the cache we're done + // loading it, and write the prototype. The master prototype is put into + // the cache earlier in XULDocument::StartDocumentLoad. + if (useXULCache && mIsWritingFastLoad && isChrome && + mMasterPrototype != mCurrentPrototype) { + nsXULPrototypeCache::GetInstance()->WritePrototype(mCurrentPrototype); + } + + if (IsOverlayAllowed(uri)) { + nsCOMPtr reg = + mozilla::services::GetXULOverlayProviderService(); + + if (reg) { + nsCOMPtr overlays; + rv = reg->GetStyleOverlays(uri, getter_AddRefs(overlays)); + if (NS_FAILED(rv)) return; + + bool moreSheets; + nsCOMPtr next; + nsCOMPtr sheetURI; + + while (NS_SUCCEEDED(rv = overlays->HasMoreElements(&moreSheets)) && + moreSheets) { + overlays->GetNext(getter_AddRefs(next)); + + sheetURI = do_QueryInterface(next); + if (!sheetURI) { + NS_ERROR("Chrome registry handed me a non-nsIURI object!"); + continue; + } + + if (IsChromeURI(sheetURI)) { + mCurrentPrototype->AddStyleSheetReference(sheetURI); + } + } + } + + if (isChrome && useXULCache) { + // If it's a chrome prototype document, then notify any + // documents that raced to load the prototype, and awaited + // its load completion via proto->AwaitLoadDone(). + rv = mCurrentPrototype->NotifyLoadDone(); + if (NS_FAILED(rv)) return; + } + } + + OnPrototypeLoadDone(true); + if (MOZ_LOG_TEST(gXULLog, LogLevel::Warning)) { + nsAutoCString urlspec; + rv = uri->GetSpec(urlspec); + if (NS_SUCCEEDED(rv)) { + MOZ_LOG(gXULLog, LogLevel::Warning, + ("xul: Finished loading document '%s'", urlspec.get())); + } + } +} + +NS_IMETHODIMP +XULDocument::OnPrototypeLoadDone(bool aResumeWalk) +{ + nsresult rv; + + // Add the style overlays from chrome registry, if any. + rv = AddPrototypeSheets(); + if (NS_FAILED(rv)) return rv; + + rv = PrepareToWalk(); + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to prepare for walk"); + if (NS_FAILED(rv)) return rv; + + if (aResumeWalk) { + rv = ResumeWalk(); + } + return rv; +} + +// called when an error occurs parsing a document +bool +XULDocument::OnDocumentParserError() +{ + // don't report errors that are from overlays + if (mCurrentPrototype && mMasterPrototype != mCurrentPrototype) { + nsCOMPtr uri = mCurrentPrototype->GetURI(); + if (IsChromeURI(uri)) { + nsCOMPtr os = + mozilla::services::GetObserverService(); + if (os) + os->NotifyObservers(uri, "xul-overlay-parsererror", + EmptyString().get()); + } + + return false; + } + + return true; +} + +static void +ClearBroadcasterMapEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry) +{ + BroadcasterMapEntry* entry = + static_cast(aEntry); + for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) { + delete entry->mListeners[i]; + } + entry->mListeners.Clear(); + + // N.B. that we need to manually run the dtor because we + // constructed the nsTArray object in-place. + entry->mListeners.~nsTArray(); +} + +static bool +CanBroadcast(int32_t aNameSpaceID, nsIAtom* aAttribute) +{ + // Don't push changes to the |id|, |ref|, |persist|, |command| or + // |observes| attribute. + if (aNameSpaceID == kNameSpaceID_None) { + if ((aAttribute == nsGkAtoms::id) || + (aAttribute == nsGkAtoms::ref) || + (aAttribute == nsGkAtoms::persist) || + (aAttribute == nsGkAtoms::command) || + (aAttribute == nsGkAtoms::observes)) { + return false; + } + } + return true; +} + +struct nsAttrNameInfo +{ + nsAttrNameInfo(int32_t aNamespaceID, nsIAtom* aName, nsIAtom* aPrefix) : + mNamespaceID(aNamespaceID), mName(aName), mPrefix(aPrefix) {} + nsAttrNameInfo(const nsAttrNameInfo& aOther) : + mNamespaceID(aOther.mNamespaceID), mName(aOther.mName), + mPrefix(aOther.mPrefix) {} + int32_t mNamespaceID; + nsCOMPtr mName; + nsCOMPtr mPrefix; +}; + +void +XULDocument::SynchronizeBroadcastListener(Element *aBroadcaster, + Element *aListener, + const nsAString &aAttr) +{ + if (!nsContentUtils::IsSafeToRunScript()) { + nsDelayedBroadcastUpdate delayedUpdate(aBroadcaster, aListener, + aAttr); + mDelayedBroadcasters.AppendElement(delayedUpdate); + MaybeBroadcast(); + return; + } + bool notify = mDocumentLoaded || mHandlingDelayedBroadcasters; + + if (aAttr.EqualsLiteral("*")) { + uint32_t count = aBroadcaster->GetAttrCount(); + nsTArray attributes(count); + for (uint32_t i = 0; i < count; ++i) { + const nsAttrName* attrName = aBroadcaster->GetAttrNameAt(i); + int32_t nameSpaceID = attrName->NamespaceID(); + nsIAtom* name = attrName->LocalName(); + + // _Don't_ push the |id|, |ref|, or |persist| attribute's value! + if (! CanBroadcast(nameSpaceID, name)) + continue; + + attributes.AppendElement(nsAttrNameInfo(nameSpaceID, name, + attrName->GetPrefix())); + } + + count = attributes.Length(); + while (count-- > 0) { + int32_t nameSpaceID = attributes[count].mNamespaceID; + nsIAtom* name = attributes[count].mName; + nsAutoString value; + if (aBroadcaster->GetAttr(nameSpaceID, name, value)) { + aListener->SetAttr(nameSpaceID, name, attributes[count].mPrefix, + value, notify); + } + +#if 0 + // XXX we don't fire the |onbroadcast| handler during + // initial hookup: doing so would potentially run the + // |onbroadcast| handler before the |onload| handler, + // which could define JS properties that mask XBL + // properties, etc. + ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name); +#endif + } + } + else { + // Find out if the attribute is even present at all. + nsCOMPtr name = NS_Atomize(aAttr); + + nsAutoString value; + if (aBroadcaster->GetAttr(kNameSpaceID_None, name, value)) { + aListener->SetAttr(kNameSpaceID_None, name, value, notify); + } else { + aListener->UnsetAttr(kNameSpaceID_None, name, notify); + } + +#if 0 + // XXX we don't fire the |onbroadcast| handler during initial + // hookup: doing so would potentially run the |onbroadcast| + // handler before the |onload| handler, which could define JS + // properties that mask XBL properties, etc. + ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name); +#endif + } +} + +NS_IMETHODIMP +XULDocument::AddBroadcastListenerFor(nsIDOMElement* aBroadcaster, + nsIDOMElement* aListener, + const nsAString& aAttr) +{ + ErrorResult rv; + nsCOMPtr broadcaster = do_QueryInterface(aBroadcaster); + nsCOMPtr listener = do_QueryInterface(aListener); + NS_ENSURE_ARG(broadcaster && listener); + AddBroadcastListenerFor(*broadcaster, *listener, aAttr, rv); + return rv.StealNSResult(); +} + +void +XULDocument::AddBroadcastListenerFor(Element& aBroadcaster, Element& aListener, + const nsAString& aAttr, ErrorResult& aRv) +{ + nsresult rv = + nsContentUtils::CheckSameOrigin(this, &aBroadcaster); + + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return; + } + + rv = nsContentUtils::CheckSameOrigin(this, &aListener); + + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return; + } + + static const PLDHashTableOps gOps = { + PLDHashTable::HashVoidPtrKeyStub, + PLDHashTable::MatchEntryStub, + PLDHashTable::MoveEntryStub, + ClearBroadcasterMapEntry, + nullptr + }; + + if (! mBroadcasterMap) { + mBroadcasterMap = new PLDHashTable(&gOps, sizeof(BroadcasterMapEntry)); + } + + auto entry = static_cast + (mBroadcasterMap->Search(&aBroadcaster)); + if (!entry) { + entry = static_cast + (mBroadcasterMap->Add(&aBroadcaster, fallible)); + + if (! entry) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + + entry->mBroadcaster = &aBroadcaster; + + // N.B. placement new to construct the nsTArray object in-place + new (&entry->mListeners) nsTArray(); + } + + // Only add the listener if it's not there already! + nsCOMPtr attr = NS_Atomize(aAttr); + + for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) { + BroadcastListener* bl = entry->mListeners[i]; + nsCOMPtr blListener = do_QueryReferent(bl->mListener); + + if (blListener == &aListener && bl->mAttribute == attr) + return; + } + + BroadcastListener* bl = new BroadcastListener; + bl->mListener = do_GetWeakReference(&aListener); + bl->mAttribute = attr; + + entry->mListeners.AppendElement(bl); + + SynchronizeBroadcastListener(&aBroadcaster, &aListener, aAttr); +} + +NS_IMETHODIMP +XULDocument::RemoveBroadcastListenerFor(nsIDOMElement* aBroadcaster, + nsIDOMElement* aListener, + const nsAString& aAttr) +{ + nsCOMPtr broadcaster = do_QueryInterface(aBroadcaster); + nsCOMPtr listener = do_QueryInterface(aListener); + NS_ENSURE_ARG(broadcaster && listener); + RemoveBroadcastListenerFor(*broadcaster, *listener, aAttr); + return NS_OK; +} + +void +XULDocument::RemoveBroadcastListenerFor(Element& aBroadcaster, + Element& aListener, + const nsAString& aAttr) +{ + // If we haven't added any broadcast listeners, then there sure + // aren't any to remove. + if (! mBroadcasterMap) + return; + + auto entry = static_cast + (mBroadcasterMap->Search(&aBroadcaster)); + if (entry) { + nsCOMPtr attr = NS_Atomize(aAttr); + for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) { + BroadcastListener* bl = entry->mListeners[i]; + nsCOMPtr blListener = do_QueryReferent(bl->mListener); + + if (blListener == &aListener && bl->mAttribute == attr) { + entry->mListeners.RemoveElementAt(i); + delete bl; + + if (entry->mListeners.IsEmpty()) + mBroadcasterMap->RemoveEntry(entry); + + break; + } + } + } +} + +nsresult +XULDocument::ExecuteOnBroadcastHandlerFor(Element* aBroadcaster, + Element* aListener, + nsIAtom* aAttr) +{ + // Now we execute the onchange handler in the context of the + // observer. We need to find the observer in order to + // execute the handler. + + for (nsIContent* child = aListener->GetFirstChild(); + child; + child = child->GetNextSibling()) { + + // Look for an element beneath the listener. This + // ought to have an |element| attribute that refers to + // aBroadcaster, and an |attribute| element that tells us what + // attriubtes we're listening for. + if (!child->NodeInfo()->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) + continue; + + // Is this the element that was listening to us? + nsAutoString listeningToID; + child->GetAttr(kNameSpaceID_None, nsGkAtoms::element, listeningToID); + + nsAutoString broadcasterID; + aBroadcaster->GetAttr(kNameSpaceID_None, nsGkAtoms::id, broadcasterID); + + if (listeningToID != broadcasterID) + continue; + + // We are observing the broadcaster, but is this the right + // attribute? + nsAutoString listeningToAttribute; + child->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, + listeningToAttribute); + + if (!aAttr->Equals(listeningToAttribute) && + !listeningToAttribute.EqualsLiteral("*")) { + continue; + } + + // This is the right element. Execute the + // |onbroadcast| event handler + WidgetEvent event(true, eXULBroadcast); + + nsCOMPtr shell = GetShell(); + if (shell) { + RefPtr aPresContext = shell->GetPresContext(); + + // Handle the DOM event + nsEventStatus status = nsEventStatus_eIgnore; + EventDispatcher::Dispatch(child, aPresContext, &event, nullptr, + &status); + } + } + + return NS_OK; +} + +void +XULDocument::AttributeWillChange(nsIDocument* aDocument, + Element* aElement, int32_t aNameSpaceID, + nsIAtom* aAttribute, int32_t aModType, + const nsAttrValue* aNewValue) +{ + MOZ_ASSERT(aElement, "Null content!"); + NS_PRECONDITION(aAttribute, "Must have an attribute that's changing!"); + + // XXXbz check aNameSpaceID, dammit! + // See if we need to update our ref map. + if (aAttribute == nsGkAtoms::ref) { + // Might not need this, but be safe for now. + nsCOMPtr kungFuDeathGrip(this); + RemoveElementFromRefMap(aElement); + } +} + +static bool +ShouldPersistAttribute(Element* aElement, nsIAtom* aAttribute) +{ + if (aElement->IsXULElement(nsGkAtoms::window)) { + // This is not an element of the top document, its owner is + // not an nsXULWindow. Persist it. + if (aElement->OwnerDoc()->GetParentDocument()) { + return true; + } + // The following attributes of xul:window should be handled in + // nsXULWindow::SavePersistentAttributes instead of here. + if (aAttribute == nsGkAtoms::screenX || + aAttribute == nsGkAtoms::screenY || + aAttribute == nsGkAtoms::width || + aAttribute == nsGkAtoms::height || + aAttribute == nsGkAtoms::sizemode) { + return false; + } + } + return true; +} + +void +XULDocument::AttributeChanged(nsIDocument* aDocument, + Element* aElement, int32_t aNameSpaceID, + nsIAtom* aAttribute, int32_t aModType, + const nsAttrValue* aOldValue) +{ + NS_ASSERTION(aDocument == this, "unexpected doc"); + + // Might not need this, but be safe for now. + nsCOMPtr kungFuDeathGrip(this); + + // XXXbz check aNameSpaceID, dammit! + // See if we need to update our ref map. + if (aAttribute == nsGkAtoms::ref) { + AddElementToRefMap(aElement); + } + + // Synchronize broadcast listeners + if (mBroadcasterMap && + CanBroadcast(aNameSpaceID, aAttribute)) { + auto entry = static_cast + (mBroadcasterMap->Search(aElement)); + + if (entry) { + // We've got listeners: push the value. + nsAutoString value; + bool attrSet = aElement->GetAttr(kNameSpaceID_None, aAttribute, value); + + for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) { + BroadcastListener* bl = entry->mListeners[i]; + if ((bl->mAttribute == aAttribute) || + (bl->mAttribute == nsGkAtoms::_asterisk)) { + nsCOMPtr listenerEl + = do_QueryReferent(bl->mListener); + if (listenerEl) { + nsAutoString currentValue; + bool hasAttr = listenerEl->GetAttr(kNameSpaceID_None, + aAttribute, + currentValue); + // We need to update listener only if we're + // (1) removing an existing attribute, + // (2) adding a new attribute or + // (3) changing the value of an attribute. + bool needsAttrChange = + attrSet != hasAttr || !value.Equals(currentValue); + nsDelayedBroadcastUpdate delayedUpdate(aElement, + listenerEl, + aAttribute, + value, + attrSet, + needsAttrChange); + + size_t index = + mDelayedAttrChangeBroadcasts.IndexOf(delayedUpdate, + 0, nsDelayedBroadcastUpdate::Comparator()); + if (index != mDelayedAttrChangeBroadcasts.NoIndex) { + if (mHandlingDelayedAttrChange) { + NS_WARNING("Broadcasting loop!"); + continue; + } + mDelayedAttrChangeBroadcasts.RemoveElementAt(index); + } + + mDelayedAttrChangeBroadcasts.AppendElement(delayedUpdate); + } + } + } + } + } + + // checks for modifications in broadcasters + bool listener, resolved; + CheckBroadcasterHookup(aElement, &listener, &resolved); + + // See if there is anything we need to persist in the localstore. + // + // XXX Namespace handling broken :-( + nsAutoString persist; + aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist); + // Persistence of attributes of xul:window is handled in nsXULWindow. + if (ShouldPersistAttribute(aElement, aAttribute) && !persist.IsEmpty() && + // XXXldb This should check that it's a token, not just a substring. + persist.Find(nsDependentAtomString(aAttribute)) >= 0) { + nsContentUtils::AddScriptRunner(NewRunnableMethod + + (this, &XULDocument::DoPersist, aElement, kNameSpaceID_None, + aAttribute)); + } +} + +void +XULDocument::ContentAppended(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aFirstNewContent, + int32_t aNewIndexInContainer) +{ + NS_ASSERTION(aDocument == this, "unexpected doc"); + + // Might not need this, but be safe for now. + nsCOMPtr kungFuDeathGrip(this); + + // Update our element map + nsresult rv = NS_OK; + for (nsIContent* cur = aFirstNewContent; cur && NS_SUCCEEDED(rv); + cur = cur->GetNextSibling()) { + rv = AddSubtreeToDocument(cur); + } +} + +void +XULDocument::ContentInserted(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer) +{ + NS_ASSERTION(aDocument == this, "unexpected doc"); + + // Might not need this, but be safe for now. + nsCOMPtr kungFuDeathGrip(this); + + AddSubtreeToDocument(aChild); +} + +void +XULDocument::ContentRemoved(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer, + nsIContent* aPreviousSibling) +{ + NS_ASSERTION(aDocument == this, "unexpected doc"); + + // Might not need this, but be safe for now. + nsCOMPtr kungFuDeathGrip(this); + + RemoveSubtreeFromDocument(aChild); +} + +//---------------------------------------------------------------------- +// +// nsIXULDocument interface +// + +void +XULDocument::GetElementsForID(const nsAString& aID, + nsCOMArray& aElements) +{ + aElements.Clear(); + + nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aID); + if (entry) { + entry->AppendAllIdContent(&aElements); + } + nsRefMapEntry *refEntry = mRefMap.GetEntry(aID); + if (refEntry) { + refEntry->AppendAll(&aElements); + } +} + +nsresult +XULDocument::AddForwardReference(nsForwardReference* aRef) +{ + if (mResolutionPhase < aRef->GetPhase()) { + if (!mForwardReferences.AppendElement(aRef)) { + delete aRef; + return NS_ERROR_OUT_OF_MEMORY; + } + } + else { + NS_ERROR("forward references have already been resolved"); + delete aRef; + } + + return NS_OK; +} + +nsresult +XULDocument::ResolveForwardReferences() +{ + if (mResolutionPhase == nsForwardReference::eDone) + return NS_OK; + + NS_ASSERTION(mResolutionPhase == nsForwardReference::eStart, + "nested ResolveForwardReferences()"); + + // Resolve each outstanding 'forward' reference. We iterate + // through the list of forward references until no more forward + // references can be resolved. This annealing process is + // guaranteed to converge because we've "closed the gate" to new + // forward references. + + const nsForwardReference::Phase* pass = nsForwardReference::kPasses; + while ((mResolutionPhase = *pass) != nsForwardReference::eDone) { + uint32_t previous = 0; + while (mForwardReferences.Length() && + mForwardReferences.Length() != previous) { + previous = mForwardReferences.Length(); + + for (uint32_t i = 0; i < mForwardReferences.Length(); ++i) { + nsForwardReference* fwdref = mForwardReferences[i]; + + if (fwdref->GetPhase() == *pass) { + nsForwardReference::Result result = fwdref->Resolve(); + + switch (result) { + case nsForwardReference::eResolve_Succeeded: + case nsForwardReference::eResolve_Error: + mForwardReferences.RemoveElementAt(i); + + // fixup because we removed from list + --i; + break; + + case nsForwardReference::eResolve_Later: + // do nothing. we'll try again later + ; + } + + if (mResolutionPhase == nsForwardReference::eStart) { + // Resolve() loaded a dynamic overlay, + // (see XULDocument::LoadOverlayInternal()). + // Return for now, we will be called again. + return NS_OK; + } + } + } + } + + ++pass; + } + + mForwardReferences.Clear(); + return NS_OK; +} + +//---------------------------------------------------------------------- +// +// nsIDOMDocument interface +// + +NS_IMETHODIMP +XULDocument::GetElementsByAttribute(const nsAString& aAttribute, + const nsAString& aValue, + nsIDOMNodeList** aReturn) +{ + *aReturn = GetElementsByAttribute(aAttribute, aValue).take(); + return NS_OK; +} + +already_AddRefed +XULDocument::GetElementsByAttribute(const nsAString& aAttribute, + const nsAString& aValue) +{ + nsCOMPtr attrAtom(NS_Atomize(aAttribute)); + void* attrValue = new nsString(aValue); + RefPtr list = new nsContentList(this, + MatchAttribute, + nsContentUtils::DestroyMatchString, + attrValue, + true, + attrAtom, + kNameSpaceID_Unknown); + + return list.forget(); +} + +NS_IMETHODIMP +XULDocument::GetElementsByAttributeNS(const nsAString& aNamespaceURI, + const nsAString& aAttribute, + const nsAString& aValue, + nsIDOMNodeList** aReturn) +{ + ErrorResult rv; + *aReturn = GetElementsByAttributeNS(aNamespaceURI, aAttribute, + aValue, rv).take(); + return rv.StealNSResult(); +} + +already_AddRefed +XULDocument::GetElementsByAttributeNS(const nsAString& aNamespaceURI, + const nsAString& aAttribute, + const nsAString& aValue, + ErrorResult& aRv) +{ + nsCOMPtr attrAtom(NS_Atomize(aAttribute)); + void* attrValue = new nsString(aValue); + + int32_t nameSpaceId = kNameSpaceID_Wildcard; + if (!aNamespaceURI.EqualsLiteral("*")) { + nsresult rv = + nsContentUtils::NameSpaceManager()->RegisterNameSpace(aNamespaceURI, + nameSpaceId); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return nullptr; + } + } + + RefPtr list = new nsContentList(this, + MatchAttribute, + nsContentUtils::DestroyMatchString, + attrValue, + true, + attrAtom, + nameSpaceId); + return list.forget(); +} + +NS_IMETHODIMP +XULDocument::Persist(const nsAString& aID, + const nsAString& aAttr) +{ + // If we're currently reading persisted attributes out of the + // localstore, _don't_ re-enter and try to set them again! + if (mApplyingPersistedAttrs) + return NS_OK; + + Element* element = nsDocument::GetElementById(aID); + if (!element) + return NS_OK; + + nsCOMPtr tag; + int32_t nameSpaceID; + + RefPtr ni = element->GetExistingAttrNameFromQName(aAttr); + nsresult rv; + if (ni) { + tag = ni->NameAtom(); + nameSpaceID = ni->NamespaceID(); + } + else { + // Make sure that this QName is going to be valid. + const char16_t *colon; + rv = nsContentUtils::CheckQName(PromiseFlatString(aAttr), true, &colon); + + if (NS_FAILED(rv)) { + // There was an invalid character or it was malformed. + return NS_ERROR_INVALID_ARG; + } + + if (colon) { + // We don't really handle namespace qualifiers in attribute names. + return NS_ERROR_NOT_IMPLEMENTED; + } + + tag = NS_Atomize(aAttr); + NS_ENSURE_TRUE(tag, NS_ERROR_OUT_OF_MEMORY); + + nameSpaceID = kNameSpaceID_None; + } + + return Persist(element, nameSpaceID, tag); +} + +nsresult +XULDocument::Persist(nsIContent* aElement, int32_t aNameSpaceID, + nsIAtom* aAttribute) +{ + // For non-chrome documents, persistance is simply broken + if (!nsContentUtils::IsSystemPrincipal(NodePrincipal())) + return NS_ERROR_NOT_AVAILABLE; + + if (!mLocalStore) { + mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1"); + if (NS_WARN_IF(!mLocalStore)) { + return NS_ERROR_NOT_INITIALIZED; + } + } + + nsAutoString id; + + aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id); + nsAtomString attrstr(aAttribute); + + nsAutoString valuestr; + aElement->GetAttr(kNameSpaceID_None, aAttribute, valuestr); + + nsAutoCString utf8uri; + nsresult rv = mDocumentURI->GetSpec(utf8uri); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + NS_ConvertUTF8toUTF16 uri(utf8uri); + + bool hasAttr; + rv = mLocalStore->HasValue(uri, id, attrstr, &hasAttr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (hasAttr && valuestr.IsEmpty()) { + return mLocalStore->RemoveValue(uri, id, attrstr); + } else { + return mLocalStore->SetValue(uri, id, attrstr, valuestr); + } +} + + +nsresult +XULDocument::GetViewportSize(int32_t* aWidth, + int32_t* aHeight) +{ + *aWidth = *aHeight = 0; + + FlushPendingNotifications(Flush_Layout); + + nsIPresShell *shell = GetShell(); + NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE); + + nsIFrame* frame = shell->GetRootFrame(); + NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); + + nsSize size = frame->GetSize(); + + *aWidth = nsPresContext::AppUnitsToIntCSSPixels(size.width); + *aHeight = nsPresContext::AppUnitsToIntCSSPixels(size.height); + + return NS_OK; +} + +NS_IMETHODIMP +XULDocument::GetWidth(int32_t* aWidth) +{ + NS_ENSURE_ARG_POINTER(aWidth); + + int32_t height; + return GetViewportSize(aWidth, &height); +} + +int32_t +XULDocument::GetWidth(ErrorResult& aRv) +{ + int32_t width; + aRv = GetWidth(&width); + return width; +} + +NS_IMETHODIMP +XULDocument::GetHeight(int32_t* aHeight) +{ + NS_ENSURE_ARG_POINTER(aHeight); + + int32_t width; + return GetViewportSize(&width, aHeight); +} + +int32_t +XULDocument::GetHeight(ErrorResult& aRv) +{ + int32_t height; + aRv = GetHeight(&height); + return height; +} + +JSObject* +GetScopeObjectOfNode(nsIDOMNode* node) +{ + MOZ_ASSERT(node, "Must not be called with null."); + + // Window root occasionally keeps alive a node of a document whose + // window is already dead. If in this brief period someone calls + // GetPopupNode and we return that node, nsNodeSH::PreCreate will throw, + // because it will not know which scope this node belongs to. Returning + // an orphan node like that to JS would be a bug anyway, so to avoid + // this, let's do the same check as nsNodeSH::PreCreate does to + // determine the scope and if it fails let's just return null in + // XULDocument::GetPopupNode. + nsCOMPtr inode = do_QueryInterface(node); + MOZ_ASSERT(inode, "How can this happen?"); + + nsIDocument* doc = inode->OwnerDoc(); + MOZ_ASSERT(inode, "This should never happen."); + + nsIGlobalObject* global = doc->GetScopeObject(); + return global ? global->GetGlobalJSObject() : nullptr; +} + +//---------------------------------------------------------------------- +// +// nsIDOMXULDocument interface +// + +NS_IMETHODIMP +XULDocument::GetPopupNode(nsIDOMNode** aNode) +{ + *aNode = nullptr; + + nsCOMPtr node; + nsCOMPtr rootWin = GetWindowRoot(); + if (rootWin) + node = rootWin->GetPopupNode(); // addref happens here + + if (!node) { + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + node = pm->GetLastTriggerPopupNode(this); + } + } + + if (node && nsContentUtils::CanCallerAccess(node) + && GetScopeObjectOfNode(node)) { + node.forget(aNode); + } + + return NS_OK; +} + +already_AddRefed +XULDocument::GetPopupNode() +{ + nsCOMPtr node; + DebugOnly rv = GetPopupNode(getter_AddRefs(node)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + nsCOMPtr retval(do_QueryInterface(node)); + return retval.forget(); +} + +NS_IMETHODIMP +XULDocument::SetPopupNode(nsIDOMNode* aNode) +{ + if (aNode) { + // only allow real node objects + nsCOMPtr node = do_QueryInterface(aNode); + NS_ENSURE_ARG(node); + } + + nsCOMPtr rootWin = GetWindowRoot(); + if (rootWin) + rootWin->SetPopupNode(aNode); // addref happens here + + return NS_OK; +} + +void +XULDocument::SetPopupNode(nsINode* aNode) +{ + nsCOMPtr node(do_QueryInterface(aNode)); + DebugOnly rv = SetPopupNode(node); + MOZ_ASSERT(NS_SUCCEEDED(rv)); +} + +// Returns the rangeOffset element from the XUL Popup Manager. This is for +// chrome callers only. +NS_IMETHODIMP +XULDocument::GetPopupRangeParent(nsIDOMNode** aRangeParent) +{ + NS_ENSURE_ARG_POINTER(aRangeParent); + *aRangeParent = nullptr; + + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (!pm) + return NS_ERROR_FAILURE; + + int32_t offset; + pm->GetMouseLocation(aRangeParent, &offset); + + if (*aRangeParent && !nsContentUtils::CanCallerAccess(*aRangeParent)) { + NS_RELEASE(*aRangeParent); + return NS_ERROR_DOM_SECURITY_ERR; + } + + return NS_OK; +} + +already_AddRefed +XULDocument::GetPopupRangeParent(ErrorResult& aRv) +{ + nsCOMPtr node; + aRv = GetPopupRangeParent(getter_AddRefs(node)); + nsCOMPtr retval(do_QueryInterface(node)); + return retval.forget(); +} + + +// Returns the rangeOffset element from the XUL Popup Manager. We check the +// rangeParent to determine if the caller has rights to access to the data. +NS_IMETHODIMP +XULDocument::GetPopupRangeOffset(int32_t* aRangeOffset) +{ + ErrorResult rv; + *aRangeOffset = GetPopupRangeOffset(rv); + return rv.StealNSResult(); +} + +int32_t +XULDocument::GetPopupRangeOffset(ErrorResult& aRv) +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (!pm) { + aRv.Throw(NS_ERROR_FAILURE); + return 0; + } + + int32_t offset; + nsCOMPtr parent; + pm->GetMouseLocation(getter_AddRefs(parent), &offset); + + if (parent && !nsContentUtils::CanCallerAccess(parent)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return 0; + } + return offset; +} + +NS_IMETHODIMP +XULDocument::GetTooltipNode(nsIDOMNode** aNode) +{ + *aNode = nullptr; + + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + nsCOMPtr node = pm->GetLastTriggerTooltipNode(this); + if (node && nsContentUtils::CanCallerAccess(node)) + node.forget(aNode); + } + + return NS_OK; +} + +already_AddRefed +XULDocument::GetTooltipNode() +{ + nsCOMPtr node; + DebugOnly rv = GetTooltipNode(getter_AddRefs(node)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + nsCOMPtr retval(do_QueryInterface(node)); + return retval.forget(); +} + +NS_IMETHODIMP +XULDocument::SetTooltipNode(nsIDOMNode* aNode) +{ + // do nothing + return NS_OK; +} + + +NS_IMETHODIMP +XULDocument::GetCommandDispatcher(nsIDOMXULCommandDispatcher** aTracker) +{ + *aTracker = mCommandDispatcher; + NS_IF_ADDREF(*aTracker); + return NS_OK; +} + +Element* +XULDocument::GetElementById(const nsAString& aId) +{ + if (!CheckGetElementByIdArg(aId)) + return nullptr; + + nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aId); + if (entry) { + Element* element = entry->GetIdElement(); + if (element) + return element; + } + + nsRefMapEntry* refEntry = mRefMap.GetEntry(aId); + if (refEntry) { + NS_ASSERTION(refEntry->GetFirstElement(), + "nsRefMapEntries should have nonempty content lists"); + return refEntry->GetFirstElement(); + } + return nullptr; +} + +nsresult +XULDocument::AddElementToDocumentPre(Element* aElement) +{ + // Do a bunch of work that's necessary when an element gets added + // to the XUL Document. + nsresult rv; + + // 1. Add the element to the resource-to-element map. Also add it to + // the id map, since it seems this can be called when creating + // elements from prototypes. + nsIAtom* id = aElement->GetID(); + if (id) { + // FIXME: Shouldn't BindToTree take care of this? + nsAutoScriptBlocker scriptBlocker; + AddToIdTable(aElement, id); + } + rv = AddElementToRefMap(aElement); + if (NS_FAILED(rv)) return rv; + + // 2. If the element is a 'command updater' (i.e., has a + // "commandupdater='true'" attribute), then add the element to the + // document's command dispatcher + if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::commandupdater, + nsGkAtoms::_true, eCaseMatters)) { + rv = nsXULContentUtils::SetCommandUpdater(this, aElement); + if (NS_FAILED(rv)) return rv; + } + + // 3. Check for a broadcaster hookup attribute, in which case + // we'll hook the node up as a listener on a broadcaster. + bool listener, resolved; + rv = CheckBroadcasterHookup(aElement, &listener, &resolved); + if (NS_FAILED(rv)) return rv; + + // If it's not there yet, we may be able to defer hookup until + // later. + if (listener && !resolved && (mResolutionPhase != nsForwardReference::eDone)) { + BroadcasterHookup* hookup = new BroadcasterHookup(this, aElement); + rv = AddForwardReference(hookup); + if (NS_FAILED(rv)) return rv; + } + + return NS_OK; +} + +nsresult +XULDocument::AddElementToDocumentPost(Element* aElement) +{ + // We need to pay special attention to the keyset tag to set up a listener + if (aElement->NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) { + // Create our XUL key listener and hook it up. + nsXBLService::AttachGlobalKeyHandler(aElement); + } + + // See if we need to attach a XUL template to this node + bool needsHookup; + nsresult rv = CheckTemplateBuilderHookup(aElement, &needsHookup); + if (NS_FAILED(rv)) + return rv; + + if (needsHookup) { + if (mResolutionPhase == nsForwardReference::eDone) { + rv = CreateTemplateBuilder(aElement); + if (NS_FAILED(rv)) + return rv; + } + else { + TemplateBuilderHookup* hookup = new TemplateBuilderHookup(aElement); + rv = AddForwardReference(hookup); + if (NS_FAILED(rv)) + return rv; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +XULDocument::AddSubtreeToDocument(nsIContent* aContent) +{ + NS_ASSERTION(aContent->GetUncomposedDoc() == this, "Element not in doc!"); + // From here on we only care about elements. + if (!aContent->IsElement()) { + return NS_OK; + } + + Element* aElement = aContent->AsElement(); + + // Do pre-order addition magic + nsresult rv = AddElementToDocumentPre(aElement); + if (NS_FAILED(rv)) return rv; + + // Recurse to children + for (nsIContent* child = aElement->GetLastChild(); + child; + child = child->GetPreviousSibling()) { + + rv = AddSubtreeToDocument(child); + if (NS_FAILED(rv)) + return rv; + } + + // Do post-order addition magic + return AddElementToDocumentPost(aElement); +} + +NS_IMETHODIMP +XULDocument::RemoveSubtreeFromDocument(nsIContent* aContent) +{ + // From here on we only care about elements. + if (!aContent->IsElement()) { + return NS_OK; + } + + Element* aElement = aContent->AsElement(); + + // Do a bunch of cleanup to remove an element from the XUL + // document. + nsresult rv; + + if (aElement->NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) { + nsXBLService::DetachGlobalKeyHandler(aElement); + } + + // 1. Remove any children from the document. + for (nsIContent* child = aElement->GetLastChild(); + child; + child = child->GetPreviousSibling()) { + + rv = RemoveSubtreeFromDocument(child); + if (NS_FAILED(rv)) + return rv; + } + + // 2. Remove the element from the resource-to-element map. + // Also remove it from the id map, since we added it in + // AddElementToDocumentPre(). + RemoveElementFromRefMap(aElement); + nsIAtom* id = aElement->GetID(); + if (id) { + // FIXME: Shouldn't UnbindFromTree take care of this? + nsAutoScriptBlocker scriptBlocker; + RemoveFromIdTable(aElement, id); + } + + // 3. If the element is a 'command updater', then remove the + // element from the document's command dispatcher. + if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::commandupdater, + nsGkAtoms::_true, eCaseMatters)) { + nsCOMPtr domelement = do_QueryInterface(aElement); + NS_ASSERTION(domelement != nullptr, "not a DOM element"); + if (! domelement) + return NS_ERROR_UNEXPECTED; + + rv = mCommandDispatcher->RemoveCommandUpdater(domelement); + if (NS_FAILED(rv)) return rv; + } + + // 4. Remove the element from our broadcaster map, since it is no longer + // in the document. + nsCOMPtr broadcaster, listener; + nsAutoString attribute, broadcasterID; + rv = FindBroadcaster(aElement, getter_AddRefs(listener), + broadcasterID, attribute, getter_AddRefs(broadcaster)); + if (rv == NS_FINDBROADCASTER_FOUND) { + RemoveBroadcastListenerFor(*broadcaster, *listener, attribute); + } + + return NS_OK; +} + +NS_IMETHODIMP +XULDocument::SetTemplateBuilderFor(nsIContent* aContent, + nsIXULTemplateBuilder* aBuilder) +{ + if (! mTemplateBuilderTable) { + if (!aBuilder) { + return NS_OK; + } + mTemplateBuilderTable = new BuilderTable; + } + + if (aBuilder) { + mTemplateBuilderTable->Put(aContent, aBuilder); + } + else { + mTemplateBuilderTable->Remove(aContent); + } + + return NS_OK; +} + +NS_IMETHODIMP +XULDocument::GetTemplateBuilderFor(nsIContent* aContent, + nsIXULTemplateBuilder** aResult) +{ + if (mTemplateBuilderTable) { + mTemplateBuilderTable->Get(aContent, aResult); + } + else + *aResult = nullptr; + + return NS_OK; +} + +static void +GetRefMapAttribute(Element* aElement, nsAutoString* aValue) +{ + aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::ref, *aValue); +} + +nsresult +XULDocument::AddElementToRefMap(Element* aElement) +{ + // Look at the element's 'ref' attribute, and if set, + // add an entry in the resource-to-element map to the element. + nsAutoString value; + GetRefMapAttribute(aElement, &value); + if (!value.IsEmpty()) { + nsRefMapEntry *entry = mRefMap.PutEntry(value); + if (!entry) + return NS_ERROR_OUT_OF_MEMORY; + if (!entry->AddElement(aElement)) + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +void +XULDocument::RemoveElementFromRefMap(Element* aElement) +{ + // Remove the element from the resource-to-element map. + nsAutoString value; + GetRefMapAttribute(aElement, &value); + if (!value.IsEmpty()) { + nsRefMapEntry *entry = mRefMap.GetEntry(value); + if (!entry) + return; + if (entry->RemoveElement(aElement)) { + mRefMap.RemoveEntry(entry); + } + } +} + +//---------------------------------------------------------------------- +// +// nsIDOMNode interface +// + +nsresult +XULDocument::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const +{ + // We don't allow cloning of a XUL document + *aResult = nullptr; + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; +} + + +//---------------------------------------------------------------------- +// +// Implementation methods +// + +nsresult +XULDocument::Init() +{ + nsresult rv = XMLDocument::Init(); + NS_ENSURE_SUCCESS(rv, rv); + + // Create our command dispatcher and hook it up. + mCommandDispatcher = new nsXULCommandDispatcher(this); + + if (gRefCnt++ == 0) { + // ensure that the XUL prototype cache is instantiated successfully, + // so that we can use nsXULPrototypeCache::GetInstance() without + // null-checks in the rest of the class. + nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance(); + if (!cache) { + NS_ERROR("Could not instantiate nsXULPrototypeCache"); + return NS_ERROR_FAILURE; + } + } + + Preferences::RegisterCallback(XULDocument::DirectionChanged, + "intl.uidirection.", this); + + return NS_OK; +} + + +nsresult +XULDocument::StartLayout(void) +{ + mMayStartLayout = true; + nsCOMPtr shell = GetShell(); + if (shell) { + // Resize-reflow this time + nsPresContext *cx = shell->GetPresContext(); + NS_ASSERTION(cx != nullptr, "no pres context"); + if (! cx) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr docShell = cx->GetDocShell(); + NS_ASSERTION(docShell != nullptr, "container is not a docshell"); + if (! docShell) + return NS_ERROR_UNEXPECTED; + + nsresult rv = NS_OK; + nsRect r = cx->GetVisibleArea(); + rv = shell->Initialize(r.width, r.height); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +/* static */ +bool +XULDocument::MatchAttribute(nsIContent* aContent, + int32_t aNamespaceID, + nsIAtom* aAttrName, + void* aData) +{ + NS_PRECONDITION(aContent, "Must have content node to work with!"); + nsString* attrValue = static_cast(aData); + if (aNamespaceID != kNameSpaceID_Unknown && + aNamespaceID != kNameSpaceID_Wildcard) { + return attrValue->EqualsLiteral("*") ? + aContent->HasAttr(aNamespaceID, aAttrName) : + aContent->AttrValueIs(aNamespaceID, aAttrName, *attrValue, + eCaseMatters); + } + + // Qualified name match. This takes more work. + + uint32_t count = aContent->GetAttrCount(); + for (uint32_t i = 0; i < count; ++i) { + const nsAttrName* name = aContent->GetAttrNameAt(i); + bool nameMatch; + if (name->IsAtom()) { + nameMatch = name->Atom() == aAttrName; + } else if (aNamespaceID == kNameSpaceID_Wildcard) { + nameMatch = name->NodeInfo()->Equals(aAttrName); + } else { + nameMatch = name->NodeInfo()->QualifiedNameEquals(aAttrName); + } + + if (nameMatch) { + return attrValue->EqualsLiteral("*") || + aContent->AttrValueIs(name->NamespaceID(), name->LocalName(), + *attrValue, eCaseMatters); + } + } + + return false; +} + +nsresult +XULDocument::PrepareToLoad(nsISupports* aContainer, + const char* aCommand, + nsIChannel* aChannel, + nsILoadGroup* aLoadGroup, + nsIParser** aResult) +{ + // Get the document's principal + nsCOMPtr principal; + nsContentUtils::GetSecurityManager()-> + GetChannelResultPrincipal(aChannel, getter_AddRefs(principal)); + return PrepareToLoadPrototype(mDocumentURI, aCommand, principal, aResult); +} + + +nsresult +XULDocument::PrepareToLoadPrototype(nsIURI* aURI, const char* aCommand, + nsIPrincipal* aDocumentPrincipal, + nsIParser** aResult) +{ + nsresult rv; + + // Create a new prototype document. + rv = NS_NewXULPrototypeDocument(getter_AddRefs(mCurrentPrototype)); + if (NS_FAILED(rv)) return rv; + + rv = mCurrentPrototype->InitPrincipal(aURI, aDocumentPrincipal); + if (NS_FAILED(rv)) { + mCurrentPrototype = nullptr; + return rv; + } + + // Bootstrap the master document prototype. + if (! mMasterPrototype) { + mMasterPrototype = mCurrentPrototype; + // Set our principal based on the master proto. + SetPrincipal(aDocumentPrincipal); + } + + // Create a XUL content sink, a parser, and kick off a load for + // the overlay. + RefPtr sink = new XULContentSinkImpl(); + + rv = sink->Init(this, mCurrentPrototype); + NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to initialize datasource sink"); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr parser = do_CreateInstance(kParserCID, &rv); + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create parser"); + if (NS_FAILED(rv)) return rv; + + parser->SetCommand(nsCRT::strcmp(aCommand, "view-source") ? eViewNormal : + eViewSource); + + parser->SetDocumentCharset(NS_LITERAL_CSTRING("UTF-8"), + kCharsetFromDocTypeDefault); + parser->SetContentSink(sink); // grabs a reference to the parser + + parser.forget(aResult); + return NS_OK; +} + + +nsresult +XULDocument::ApplyPersistentAttributes() +{ + // For non-chrome documents, persistance is simply broken + if (!nsContentUtils::IsSystemPrincipal(NodePrincipal())) + return NS_ERROR_NOT_AVAILABLE; + + // Add all of the 'persisted' attributes into the content + // model. + if (!mLocalStore) { + mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1"); + if (NS_WARN_IF(!mLocalStore)) { + return NS_ERROR_NOT_INITIALIZED; + } + } + + mApplyingPersistedAttrs = true; + ApplyPersistentAttributesInternal(); + mApplyingPersistedAttrs = false; + + // After we've applied persistence once, we should only reapply + // it to nodes created by overlays + mRestrictPersistence = true; + mPersistenceIds.Clear(); + + return NS_OK; +} + + +nsresult +XULDocument::ApplyPersistentAttributesInternal() +{ + nsCOMArray elements; + + nsAutoCString utf8uri; + nsresult rv = mDocumentURI->GetSpec(utf8uri); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + NS_ConvertUTF8toUTF16 uri(utf8uri); + + // Get a list of element IDs for which persisted values are available + nsCOMPtr ids; + rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + while (1) { + bool hasmore = false; + ids->HasMore(&hasmore); + if (!hasmore) { + break; + } + + nsAutoString id; + ids->GetNext(id); + + if (mRestrictPersistence && !mPersistenceIds.Contains(id)) { + continue; + } + + // This will clear the array if there are no elements. + GetElementsForID(id, elements); + if (!elements.Count()) { + continue; + } + + rv = ApplyPersistentAttributesToElements(id, elements); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + + +nsresult +XULDocument::ApplyPersistentAttributesToElements(const nsAString &aID, + nsCOMArray& aElements) +{ + nsAutoCString utf8uri; + nsresult rv = mDocumentURI->GetSpec(utf8uri); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + NS_ConvertUTF8toUTF16 uri(utf8uri); + + // Get a list of attributes for which persisted values are available + nsCOMPtr attrs; + rv = mLocalStore->GetAttributeEnumerator(uri, aID, getter_AddRefs(attrs)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + while (1) { + bool hasmore = PR_FALSE; + attrs->HasMore(&hasmore); + if (!hasmore) { + break; + } + + nsAutoString attrstr; + attrs->GetNext(attrstr); + + nsAutoString value; + rv = mLocalStore->GetValue(uri, aID, attrstr, value); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr attr = NS_Atomize(attrstr); + if (NS_WARN_IF(!attr)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + uint32_t cnt = aElements.Count(); + + for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) { + nsCOMPtr element = aElements.SafeObjectAt(i); + if (!element) { + continue; + } + + rv = element->SetAttr(kNameSpaceID_None, attr, value, PR_TRUE); + } + } + + return NS_OK; +} + +void +XULDocument::TraceProtos(JSTracer* aTrc, uint32_t aGCNumber) +{ + uint32_t i, count = mPrototypes.Length(); + for (i = 0; i < count; ++i) { + mPrototypes[i]->TraceProtos(aTrc, aGCNumber); + } + + if (mCurrentPrototype) { + mCurrentPrototype->TraceProtos(aTrc, aGCNumber); + } +} + +//---------------------------------------------------------------------- +// +// XULDocument::ContextStack +// + +XULDocument::ContextStack::ContextStack() + : mTop(nullptr), mDepth(0) +{ +} + +XULDocument::ContextStack::~ContextStack() +{ + while (mTop) { + Entry* doomed = mTop; + mTop = mTop->mNext; + NS_IF_RELEASE(doomed->mElement); + delete doomed; + } +} + +nsresult +XULDocument::ContextStack::Push(nsXULPrototypeElement* aPrototype, + nsIContent* aElement) +{ + Entry* entry = new Entry; + entry->mPrototype = aPrototype; + entry->mElement = aElement; + NS_IF_ADDREF(entry->mElement); + entry->mIndex = 0; + + entry->mNext = mTop; + mTop = entry; + + ++mDepth; + return NS_OK; +} + +nsresult +XULDocument::ContextStack::Pop() +{ + if (mDepth == 0) + return NS_ERROR_UNEXPECTED; + + Entry* doomed = mTop; + mTop = mTop->mNext; + --mDepth; + + NS_IF_RELEASE(doomed->mElement); + delete doomed; + return NS_OK; +} + +nsresult +XULDocument::ContextStack::Peek(nsXULPrototypeElement** aPrototype, + nsIContent** aElement, + int32_t* aIndex) +{ + if (mDepth == 0) + return NS_ERROR_UNEXPECTED; + + *aPrototype = mTop->mPrototype; + *aElement = mTop->mElement; + NS_IF_ADDREF(*aElement); + *aIndex = mTop->mIndex; + + return NS_OK; +} + + +nsresult +XULDocument::ContextStack::SetTopIndex(int32_t aIndex) +{ + if (mDepth == 0) + return NS_ERROR_UNEXPECTED; + + mTop->mIndex = aIndex; + return NS_OK; +} + + +//---------------------------------------------------------------------- +// +// Content model walking routines +// + +nsresult +XULDocument::PrepareToWalk() +{ + // Prepare to walk the mCurrentPrototype + nsresult rv; + + // Keep an owning reference to the prototype document so that its + // elements aren't yanked from beneath us. + mPrototypes.AppendElement(mCurrentPrototype); + + // Get the prototype's root element and initialize the context + // stack for the prototype walk. + nsXULPrototypeElement* proto = mCurrentPrototype->GetRootElement(); + + if (! proto) { + if (MOZ_LOG_TEST(gXULLog, LogLevel::Error)) { + nsCOMPtr url = mCurrentPrototype->GetURI(); + + nsAutoCString urlspec; + rv = url->GetSpec(urlspec); + if (NS_FAILED(rv)) return rv; + + MOZ_LOG(gXULLog, LogLevel::Error, + ("xul: error parsing '%s'", urlspec.get())); + } + + return NS_OK; + } + + uint32_t piInsertionPoint = 0; + if (mState != eState_Master) { + int32_t indexOfRoot = IndexOf(GetRootElement()); + NS_ASSERTION(indexOfRoot >= 0, + "No root content when preparing to walk overlay!"); + piInsertionPoint = indexOfRoot; + } + + const nsTArray >& processingInstructions = + mCurrentPrototype->GetProcessingInstructions(); + + uint32_t total = processingInstructions.Length(); + for (uint32_t i = 0; i < total; ++i) { + rv = CreateAndInsertPI(processingInstructions[i], + this, piInsertionPoint + i); + if (NS_FAILED(rv)) return rv; + } + + // Now check the chrome registry for any additional overlays. + rv = AddChromeOverlays(); + if (NS_FAILED(rv)) return rv; + + // Do one-time initialization if we're preparing to walk the + // master document's prototype. + RefPtr root; + + if (mState == eState_Master) { + // Add the root element + rv = CreateElementFromPrototype(proto, getter_AddRefs(root), true); + if (NS_FAILED(rv)) return rv; + + rv = AppendChildTo(root, false); + if (NS_FAILED(rv)) return rv; + + rv = AddElementToRefMap(root); + if (NS_FAILED(rv)) return rv; + + // Block onload until we've finished building the complete + // document content model. + BlockOnload(); + } + + // There'd better not be anything on the context stack at this + // point! This is the basis case for our "induction" in + // ResumeWalk(), below, which'll assume that there's always a + // content element on the context stack if either 1) we're in the + // "master" document, or 2) we're in an overlay, and we've got + // more than one prototype element (the single, root "overlay" + // element) on the stack. + NS_ASSERTION(mContextStack.Depth() == 0, "something's on the context stack already"); + if (mContextStack.Depth() != 0) + return NS_ERROR_UNEXPECTED; + + rv = mContextStack.Push(proto, root); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + +nsresult +XULDocument::CreateAndInsertPI(const nsXULPrototypePI* aProtoPI, + nsINode* aParent, uint32_t aIndex) +{ + NS_PRECONDITION(aProtoPI, "null ptr"); + NS_PRECONDITION(aParent, "null ptr"); + + RefPtr node = + NS_NewXMLProcessingInstruction(mNodeInfoManager, aProtoPI->mTarget, + aProtoPI->mData); + + nsresult rv; + if (aProtoPI->mTarget.EqualsLiteral("xml-stylesheet")) { + rv = InsertXMLStylesheetPI(aProtoPI, aParent, aIndex, node); + } else if (aProtoPI->mTarget.EqualsLiteral("xul-overlay")) { + rv = InsertXULOverlayPI(aProtoPI, aParent, aIndex, node); + } else { + // No special processing, just add the PI to the document. + rv = aParent->InsertChildAt(node, aIndex, false); + } + + return rv; +} + +nsresult +XULDocument::InsertXMLStylesheetPI(const nsXULPrototypePI* aProtoPI, + nsINode* aParent, + uint32_t aIndex, + nsIContent* aPINode) +{ + nsCOMPtr ssle(do_QueryInterface(aPINode)); + NS_ASSERTION(ssle, "passed XML Stylesheet node does not " + "implement nsIStyleSheetLinkingElement!"); + + nsresult rv; + + ssle->InitStyleLinkElement(false); + // We want to be notified when the style sheet finishes loading, so + // disable style sheet loading for now. + ssle->SetEnableUpdates(false); + ssle->OverrideBaseURI(mCurrentPrototype->GetURI()); + + rv = aParent->InsertChildAt(aPINode, aIndex, false); + if (NS_FAILED(rv)) return rv; + + ssle->SetEnableUpdates(true); + + // load the stylesheet if necessary, passing ourselves as + // nsICSSObserver + bool willNotify; + bool isAlternate; + rv = ssle->UpdateStyleSheet(this, &willNotify, &isAlternate); + if (NS_SUCCEEDED(rv) && willNotify && !isAlternate) { + ++mPendingSheets; + } + + // Ignore errors from UpdateStyleSheet; we don't want failure to + // do that to break the XUL document load. But do propagate out + // NS_ERROR_OUT_OF_MEMORY. + if (rv == NS_ERROR_OUT_OF_MEMORY) { + return rv; + } + + return NS_OK; +} + +nsresult +XULDocument::InsertXULOverlayPI(const nsXULPrototypePI* aProtoPI, + nsINode* aParent, + uint32_t aIndex, + nsIContent* aPINode) +{ + nsresult rv; + + rv = aParent->InsertChildAt(aPINode, aIndex, false); + if (NS_FAILED(rv)) return rv; + + // xul-overlay PI is special only in prolog + if (!nsContentUtils::InProlog(aPINode)) { + return NS_OK; + } + + nsAutoString href; + nsContentUtils::GetPseudoAttributeValue(aProtoPI->mData, + nsGkAtoms::href, + href); + + // If there was no href, we can't do anything with this PI + if (href.IsEmpty()) { + return NS_OK; + } + + // Add the overlay to our list of overlays that need to be processed. + nsCOMPtr uri; + + rv = NS_NewURI(getter_AddRefs(uri), href, nullptr, + mCurrentPrototype->GetURI()); + if (NS_SUCCEEDED(rv)) { + // We insert overlays into mUnloadedOverlays at the same index in + // document order, so they end up in the reverse of the document + // order in mUnloadedOverlays. + // This is needed because the code in ResumeWalk loads the overlays + // by processing the last item of mUnloadedOverlays and removing it + // from the array. + mUnloadedOverlays.InsertElementAt(0, uri); + rv = NS_OK; + } else if (rv == NS_ERROR_MALFORMED_URI) { + // The URL is bad, move along. Don't propagate for now. + // XXX report this to the Error Console (bug 359846) + rv = NS_OK; + } + + return rv; +} + +nsresult +XULDocument::AddChromeOverlays() +{ + nsresult rv; + + nsCOMPtr docUri = mCurrentPrototype->GetURI(); + + /* overlays only apply to chrome or about URIs */ + if (!IsOverlayAllowed(docUri)) return NS_OK; + + nsCOMPtr chromeReg = + mozilla::services::GetXULOverlayProviderService(); + // In embedding situations, the chrome registry may not provide overlays, + // or even exist at all; that's OK. + NS_ENSURE_TRUE(chromeReg, NS_OK); + + nsCOMPtr overlays; + rv = chromeReg->GetXULOverlays(docUri, getter_AddRefs(overlays)); + NS_ENSURE_SUCCESS(rv, rv); + + bool moreOverlays; + nsCOMPtr next; + nsCOMPtr uri; + + while (NS_SUCCEEDED(rv = overlays->HasMoreElements(&moreOverlays)) && + moreOverlays) { + + rv = overlays->GetNext(getter_AddRefs(next)); + if (NS_FAILED(rv) || !next) break; + + uri = do_QueryInterface(next); + if (!uri) { + NS_ERROR("Chrome registry handed me a non-nsIURI object!"); + continue; + } + + // Same comment as in XULDocument::InsertXULOverlayPI + mUnloadedOverlays.InsertElementAt(0, uri); + } + + return rv; +} + +NS_IMETHODIMP +XULDocument::LoadOverlay(const nsAString& aURL, nsIObserver* aObserver) +{ + nsresult rv; + + nsCOMPtr uri; + rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr); + if (NS_FAILED(rv)) return rv; + + if (aObserver) { + nsIObserver* obs = nullptr; + if (!mOverlayLoadObservers) { + mOverlayLoadObservers = new nsInterfaceHashtable; + } + 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); + } + bool shouldReturn, failureFromContent; + rv = LoadOverlayInternal(uri, true, &shouldReturn, &failureFromContent); + if (NS_FAILED(rv) && mOverlayLoadObservers) + mOverlayLoadObservers->Remove(uri); // remove the observer if LoadOverlayInternal generated an error + return rv; +} + +nsresult +XULDocument::LoadOverlayInternal(nsIURI* aURI, bool aIsDynamic, + bool* aShouldReturn, + bool* aFailureFromContent) +{ + nsresult rv; + + *aShouldReturn = false; + *aFailureFromContent = false; + + if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) { + nsCOMPtr uri; + mChannel->GetOriginalURI(getter_AddRefs(uri)); + + MOZ_LOG(gXULLog, LogLevel::Debug, + ("xul: %s loading overlay %s", + uri ? uri->GetSpecOrDefault().get() : "", + aURI->GetSpecOrDefault().get())); + } + + if (aIsDynamic) + mResolutionPhase = nsForwardReference::eStart; + + // Look in the prototype cache for the prototype document with + // the specified overlay URI. Only use the cache if the containing + // document is chrome otherwise it may not have a system principal and + // the cached document will, see bug 565610. + bool overlayIsChrome = IsChromeURI(aURI); + bool documentIsChrome = IsChromeURI(mDocumentURI); + mCurrentPrototype = overlayIsChrome && documentIsChrome ? + nsXULPrototypeCache::GetInstance()->GetPrototype(aURI) : nullptr; + + // Same comment as nsChromeProtocolHandler::NewChannel and + // XULDocument::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. + + bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled(); + if (useXULCache && mCurrentPrototype) { + bool 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 = true; + return NS_OK; + } + + MOZ_LOG(gXULLog, LogLevel::Debug, ("xul: overlay was cached")); + + // Found the overlay's prototype in the cache, fully loaded. If + // this is a dynamic overlay, this will call ResumeWalk. + // Otherwise, we'll return to ResumeWalk, which called us. + return OnPrototypeLoadDone(aIsDynamic); + } + else { + // Not there. Initiate a load. + MOZ_LOG(gXULLog, LogLevel::Debug, ("xul: overlay was not cached")); + + if (mIsGoingAway) { + MOZ_LOG(gXULLog, LogLevel::Debug, ("xul: ...and document already destroyed")); + return NS_ERROR_NOT_AVAILABLE; + } + + // We'll set the right principal on the proto doc when we get + // OnStartRequest from the parser, so just pass in a null principal for + // now. + nsCOMPtr parser; + rv = PrepareToLoadPrototype(aURI, "view", nullptr, 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. + RefPtr parserObserver = + new ParserObserver(this, mCurrentPrototype); + parser->Parse(aURI, parserObserver); + parserObserver = nullptr; + + nsCOMPtr group = do_QueryReferent(mDocumentLoadGroup); + nsCOMPtr channel; + // Set the owner of the channel to be our principal so + // that the overlay's JSObjects etc end up being created + // with the right principal and in the correct + // compartment. + rv = NS_NewChannel(getter_AddRefs(channel), + aURI, + NodePrincipal(), + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS | + nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL, + nsIContentPolicy::TYPE_OTHER, + group); + + if (NS_SUCCEEDED(rv)) { + rv = channel->AsyncOpen2(listener); + } + + if (NS_FAILED(rv)) { + // Abandon this prototype + mCurrentPrototype = nullptr; + + // The parser won't get an OnStartRequest and + // OnStopRequest, so it needs a Terminate. + parser->Terminate(); + + // Just move on to the next overlay. + ReportMissingOverlay(aURI); + + // XXX the error could indicate an internal error as well... + *aFailureFromContent = true; + 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 AsyncOpen, + // or chrome code will wrongly create a cached chrome channel + // instead of a real one. Prototypes are only cached when the + // document to be overlayed is chrome to avoid caching overlay + // scripts with incorrect principals, see bug 565610. + if (useXULCache && overlayIsChrome && documentIsChrome) { + nsXULPrototypeCache::GetInstance()->PutPrototype(mCurrentPrototype); + } + + // 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 = true; + } + return NS_OK; +} + +nsresult +XULDocument::ResumeWalk() +{ + // Walk the prototype and build the delegate content model. The + // walk is performed in a top-down, left-to-right fashion. That + // is, a parent is built before any of its children; a node is + // only built after all of its siblings to the left are fully + // constructed. + // + // It is interruptable so that transcluded documents (e.g., + // ) can be properly re-loaded if the + // cached copy of the document becomes stale. + nsresult rv; + nsCOMPtr overlayURI = + mCurrentPrototype ? mCurrentPrototype->GetURI() : nullptr; + + while (1) { + // Begin (or resume) walking the current prototype. + + while (mContextStack.Depth() > 0) { + // Look at the top of the stack to determine what we're + // currently working on. + // This will always be a node already constructed and + // inserted to the actual document. + nsXULPrototypeElement* proto; + nsCOMPtr element; + int32_t indx; // all children of proto before indx (not + // inclusive) have already been constructed + rv = mContextStack.Peek(&proto, getter_AddRefs(element), &indx); + if (NS_FAILED(rv)) return rv; + + if (indx >= (int32_t)proto->mChildren.Length()) { + if (element) { + // We've processed all of the prototype's children. If + // we're in the master prototype, do post-order + // document-level hookup. (An overlay will get its + // document hookup done when it's successfully + // resolved.) + if (mState == eState_Master) { + AddElementToDocumentPost(element->AsElement()); + + if (element->NodeInfo()->Equals(nsGkAtoms::style, + kNameSpaceID_XHTML) || + element->NodeInfo()->Equals(nsGkAtoms::style, + kNameSpaceID_SVG)) { + // XXX sucks that we have to do this - + // see bug 370111 + nsCOMPtr ssle = + do_QueryInterface(element); + NS_ASSERTION(ssle, " doesn't implement " + "nsIStyleSheetLinkingElement?"); + bool willNotify; + bool isAlternate; + ssle->UpdateStyleSheet(nullptr, &willNotify, + &isAlternate); + } + } + } + // Now pop the context stack back up to the parent + // element and continue the prototype walk. + mContextStack.Pop(); + continue; + } + + // Grab the next child, and advance the current context stack + // to the next sibling to our right. + nsXULPrototypeNode* childproto = proto->mChildren[indx]; + mContextStack.SetTopIndex(++indx); + + // Whether we're in the "first ply" of an overlay: + // the "hookup" nodes. In the case !processingOverlayHookupNodes, + // we're in the master document -or- we're in an overlay, and far + // enough down into the overlay's content that we can simply build + // the delegates and attach them to the parent node. + bool processingOverlayHookupNodes = (mState == eState_Overlay) && + (mContextStack.Depth() == 1); + + NS_ASSERTION(element || processingOverlayHookupNodes, + "no element on context stack"); + + switch (childproto->mType) { + case nsXULPrototypeNode::eType_Element: { + // An 'element', which may contain more content. + nsXULPrototypeElement* protoele = + static_cast(childproto); + + RefPtr child; + + if (!processingOverlayHookupNodes) { + rv = CreateElementFromPrototype(protoele, + getter_AddRefs(child), + false); + if (NS_FAILED(rv)) return rv; + + // ...and append it to the content model. + rv = element->AppendChildTo(child, false); + if (NS_FAILED(rv)) return rv; + + // If we're only restoring persisted things on + // some elements, store the ID here to do that. + if (mRestrictPersistence) { + nsIAtom* id = child->GetID(); + if (id) { + mPersistenceIds.PutEntry(nsDependentAtomString(id)); + } + } + + // do pre-order document-level hookup, but only if + // we're in the master document. For an overlay, + // this will happen when the overlay is + // successfully resolved. + if (mState == eState_Master) + AddElementToDocumentPre(child); + } + else { + // We're in the "first ply" of an overlay: the + // "hookup" nodes. Create an 'overlay' element so + // that we can continue to build content, and + // enter a forward reference so we can hook it up + // later. + rv = CreateOverlayElement(protoele, getter_AddRefs(child)); + if (NS_FAILED(rv)) return rv; + } + + // If it has children, push the element onto the context + // stack and begin to process them. + if (protoele->mChildren.Length() > 0) { + rv = mContextStack.Push(protoele, child); + if (NS_FAILED(rv)) return rv; + } + else { + if (mState == eState_Master) { + // If there are no children, and we're in the + // master document, do post-order document hookup + // immediately. + AddElementToDocumentPost(child); + } + } + } + break; + + case nsXULPrototypeNode::eType_Script: { + // A script reference. Execute the script immediately; + // this may have side effects in the content model. + nsXULPrototypeScript* scriptproto = + static_cast(childproto); + + if (scriptproto->mSrcURI) { + // A transcluded script reference; this may + // "block" our prototype walk if the script isn't + // cached, or the cached copy of the script is + // stale and must be reloaded. + bool blocked; + rv = LoadScript(scriptproto, &blocked); + // If the script cannot be loaded, just keep going! + + if (NS_SUCCEEDED(rv) && blocked) + return NS_OK; + } + else if (scriptproto->HasScriptObject()) { + // An inline script + rv = ExecuteScript(scriptproto); + if (NS_FAILED(rv)) return rv; + } + } + break; + + case nsXULPrototypeNode::eType_Text: { + // A simple text node. + + if (!processingOverlayHookupNodes) { + // This does mean that text nodes that are direct children + // of get ignored. + + RefPtr text = + new nsTextNode(mNodeInfoManager); + + nsXULPrototypeText* textproto = + static_cast(childproto); + text->SetText(textproto->mValue, false); + + rv = element->AppendChildTo(text, false); + NS_ENSURE_SUCCESS(rv, rv); + } + } + break; + + case nsXULPrototypeNode::eType_PI: { + nsXULPrototypePI* piProto = + static_cast(childproto); + + // and don't have effect + // outside the prolog, like they used to. Issue a warning. + + if (piProto->mTarget.EqualsLiteral("xml-stylesheet") || + piProto->mTarget.EqualsLiteral("xul-overlay")) { + + const char16_t* params[] = { piProto->mTarget.get() }; + + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("XUL Document"), nullptr, + nsContentUtils::eXUL_PROPERTIES, + "PINotInProlog", + params, ArrayLength(params), + overlayURI); + } + + nsIContent* parent = processingOverlayHookupNodes ? + GetRootElement() : element.get(); + + if (parent) { + // an inline script could have removed the root element + rv = CreateAndInsertPI(piProto, parent, + parent->GetChildCount()); + NS_ENSURE_SUCCESS(rv, rv); + } + } + break; + + default: + NS_NOTREACHED("Unexpected nsXULPrototypeNode::Type value"); + } + } + + // Once we get here, the context stack will have been + // depleted. That means that the entire prototype has been + // walked and content has been constructed. + + // If we're not already, mark us as now processing overlays. + mState = eState_Overlay; + + // If there are no overlay URIs, then we're done. + uint32_t count = mUnloadedOverlays.Length(); + if (! count) + break; + + nsCOMPtr uri = mUnloadedOverlays[count-1]; + mUnloadedOverlays.RemoveElementAt(count - 1); + + bool shouldReturn, failureFromContent; + rv = LoadOverlayInternal(uri, false, &shouldReturn, + &failureFromContent); + if (failureFromContent) + // The failure |rv| was the result of a problem in the content + // rather than an unexpected problem in our implementation, so + // just continue with the next overlay. + continue; + if (NS_FAILED(rv)) + return rv; + if (mOverlayLoadObservers) { + nsIObserver *obs = mOverlayLoadObservers->GetWeak(overlayURI); + if (obs) { + // This overlay has an unloaded overlay, so it will never + // notify. The best we can do is to notify for the unloaded + // overlay instead, assuming nobody is already notifiable + // for it. Note that this will confuse the observer. + if (!mOverlayLoadObservers->GetWeak(uri)) + mOverlayLoadObservers->Put(uri, obs); + mOverlayLoadObservers->Remove(overlayURI); + } + } + if (shouldReturn) + return NS_OK; + overlayURI.swap(uri); + } + + // If we get here, there is nothing left for us to walk. The content + // model is built and ready for layout. + rv = ResolveForwardReferences(); + if (NS_FAILED(rv)) return rv; + + ApplyPersistentAttributes(); + + mStillWalking = false; + if (mPendingSheets == 0) { + rv = DoneWalking(); + } + return rv; +} + +nsresult +XULDocument::DoneWalking() +{ + NS_PRECONDITION(mPendingSheets == 0, "there are sheets to be loaded"); + NS_PRECONDITION(!mStillWalking, "walk not done"); + + // XXXldb This is where we should really be setting the chromehidden + // attribute. + + { + mozAutoDocUpdate updateBatch(this, UPDATE_STYLE, true); + uint32_t count = mOverlaySheets.Length(); + for (uint32_t i = 0; i < count; ++i) { + AddStyleSheet(mOverlaySheets[i]); + } + } + + mOverlaySheets.Clear(); + + if (!mDocumentLoaded) { + // Make sure we don't reenter here from StartLayout(). Note that + // setting mDocumentLoaded to true here means that if StartLayout() + // causes ResumeWalk() to be reentered, we'll take the other branch of + // the |if (!mDocumentLoaded)| check above and since + // mInitialLayoutComplete will be false will follow the else branch + // there too. See the big comment there for how such reentry can + // happen. + mDocumentLoaded = true; + + NotifyPossibleTitleChange(false); + + // Before starting layout, check whether we're a toplevel chrome + // window. If we are, set our chrome flags now, so that we don't have + // to restyle the whole frame tree after StartLayout. + nsCOMPtr item = GetDocShell(); + if (item) { + nsCOMPtr owner; + item->GetTreeOwner(getter_AddRefs(owner)); + nsCOMPtr xulWin = do_GetInterface(owner); + if (xulWin) { + nsCOMPtr xulWinShell; + xulWin->GetDocShell(getter_AddRefs(xulWinShell)); + if (SameCOMIdentity(xulWinShell, item)) { + // We're the chrome document! Apply our chrome flags now. + xulWin->ApplyChromeFlags(); + } + } + } + + StartLayout(); + + if (mIsWritingFastLoad && IsChromeURI(mDocumentURI)) + nsXULPrototypeCache::GetInstance()->WritePrototype(mMasterPrototype); + + NS_ASSERTION(mDelayFrameLoaderInitialization, + "mDelayFrameLoaderInitialization should be true!"); + mDelayFrameLoaderInitialization = false; + NS_WARNING_ASSERTION( + mUpdateNestLevel == 0, + "Constructing XUL document in middle of an update?"); + if (mUpdateNestLevel == 0) { + MaybeInitializeFinalizeFrameLoaders(); + } + + NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this)); + + // DispatchContentLoadedEvents undoes the onload-blocking we + // did in PrepareToWalk(). + DispatchContentLoadedEvents(); + + mInitialLayoutComplete = true; + + // Walk the set of pending load notifications and notify any observers. + // See below for detail. + if (mPendingOverlayLoadNotifications) { + nsInterfaceHashtable* observers = + mOverlayLoadObservers.get(); + for (auto iter = mPendingOverlayLoadNotifications->Iter(); + !iter.Done(); + iter.Next()) { + nsIURI* aKey = iter.Key(); + iter.Data()->Observe(aKey, "xul-overlay-merged", + EmptyString().get()); + + if (observers) { + observers->Remove(aKey); + } + + iter.Remove(); + } + } + } + else { + if (mOverlayLoadObservers) { + nsCOMPtr overlayURI = mCurrentPrototype->GetURI(); + nsCOMPtr obs; + if (mInitialLayoutComplete) { + // We have completed initial layout, so just send the notification. + mOverlayLoadObservers->Get(overlayURI, getter_AddRefs(obs)); + if (obs) + 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. + // XXXbz really, we shouldn't be firing binding constructors + // until after StartLayout returns! + + if (!mPendingOverlayLoadNotifications) { + mPendingOverlayLoadNotifications = + new nsInterfaceHashtable; + } + + 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); + } + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +XULDocument::StyleSheetLoaded(StyleSheet* aSheet, + bool aWasAlternate, + nsresult aStatus) +{ + if (!aWasAlternate) { + // Don't care about when alternate sheets finish loading + + NS_ASSERTION(mPendingSheets > 0, + "Unexpected StyleSheetLoaded notification"); + + --mPendingSheets; + + if (!mStillWalking && mPendingSheets == 0) { + return DoneWalking(); + } + } + + return NS_OK; +} + +void +XULDocument::MaybeBroadcast() +{ + // Only broadcast when not in an update and when safe to run scripts. + if (mUpdateNestLevel == 0 && + (mDelayedAttrChangeBroadcasts.Length() || + mDelayedBroadcasters.Length())) { + if (!nsContentUtils::IsSafeToRunScript()) { + if (!mInDestructor) { + nsContentUtils::AddScriptRunner( + NewRunnableMethod(this, &XULDocument::MaybeBroadcast)); + } + return; + } + if (!mHandlingDelayedAttrChange) { + mHandlingDelayedAttrChange = true; + for (uint32_t i = 0; i < mDelayedAttrChangeBroadcasts.Length(); ++i) { + nsIAtom* attrName = mDelayedAttrChangeBroadcasts[i].mAttrName; + if (mDelayedAttrChangeBroadcasts[i].mNeedsAttrChange) { + nsCOMPtr listener = + do_QueryInterface(mDelayedAttrChangeBroadcasts[i].mListener); + const nsString& value = mDelayedAttrChangeBroadcasts[i].mAttr; + if (mDelayedAttrChangeBroadcasts[i].mSetAttr) { + listener->SetAttr(kNameSpaceID_None, attrName, value, + true); + } else { + listener->UnsetAttr(kNameSpaceID_None, attrName, + true); + } + } + ExecuteOnBroadcastHandlerFor(mDelayedAttrChangeBroadcasts[i].mBroadcaster, + mDelayedAttrChangeBroadcasts[i].mListener, + attrName); + } + mDelayedAttrChangeBroadcasts.Clear(); + mHandlingDelayedAttrChange = false; + } + + uint32_t length = mDelayedBroadcasters.Length(); + if (length) { + bool oldValue = mHandlingDelayedBroadcasters; + mHandlingDelayedBroadcasters = true; + nsTArray delayedBroadcasters; + mDelayedBroadcasters.SwapElements(delayedBroadcasters); + for (uint32_t i = 0; i < length; ++i) { + SynchronizeBroadcastListener(delayedBroadcasters[i].mBroadcaster, + delayedBroadcasters[i].mListener, + delayedBroadcasters[i].mAttr); + } + mHandlingDelayedBroadcasters = oldValue; + } + } +} + +void +XULDocument::EndUpdate(nsUpdateType aUpdateType) +{ + XMLDocument::EndUpdate(aUpdateType); + + MaybeBroadcast(); +} + +void +XULDocument::ReportMissingOverlay(nsIURI* aURI) +{ + NS_PRECONDITION(aURI, "Must have a URI"); + + NS_ConvertUTF8toUTF16 utfSpec(aURI->GetSpecOrDefault()); + const char16_t* params[] = { utfSpec.get() }; + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("XUL Document"), this, + nsContentUtils::eXUL_PROPERTIES, + "MissingOverlay", + params, ArrayLength(params)); +} + +nsresult +XULDocument::LoadScript(nsXULPrototypeScript* aScriptProto, bool* aBlock) +{ + // Load a transcluded script + nsresult rv; + + bool isChromeDoc = IsChromeURI(mDocumentURI); + + if (isChromeDoc && aScriptProto->HasScriptObject()) { + rv = ExecuteScript(aScriptProto); + + // Ignore return value from execution, and don't block + *aBlock = false; + return NS_OK; + } + + // Try the XUL script cache, in case two XUL documents source the same + // .js file (e.g., strres.js from navigator.xul and utilityOverlay.xul). + // XXXbe the cache relies on aScriptProto's GC root! + bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled(); + + if (isChromeDoc && useXULCache) { + JSScript* newScriptObject = + nsXULPrototypeCache::GetInstance()->GetScript( + aScriptProto->mSrcURI); + if (newScriptObject) { + // The script language for a proto must remain constant - we + // can't just change it for this unexpected language. + aScriptProto->Set(newScriptObject); + } + + if (aScriptProto->HasScriptObject()) { + rv = ExecuteScript(aScriptProto); + + // Ignore return value from execution, and don't block + *aBlock = false; + return NS_OK; + } + } + + // Release script objects from FastLoad since we decided against using them + aScriptProto->UnlinkJSObjects(); + + // Set the current script prototype so that OnStreamComplete can report + // the right file if there are errors in the script. + NS_ASSERTION(!mCurrentScriptProto, + "still loading a script when starting another load?"); + mCurrentScriptProto = aScriptProto; + + if (isChromeDoc && aScriptProto->mSrcLoading) { + // Another XULDocument load has started, which is still in progress. + // Remember to ResumeWalk this document when the load completes. + mNextSrcLoadWaiter = aScriptProto->mSrcLoadWaiters; + aScriptProto->mSrcLoadWaiters = this; + NS_ADDREF_THIS(); + } + else { + nsCOMPtr group = do_QueryReferent(mDocumentLoadGroup); + + // Note: the loader will keep itself alive while it's loading. + nsCOMPtr loader; + rv = NS_NewStreamLoader(getter_AddRefs(loader), + aScriptProto->mSrcURI, + this, // aObserver + this, // aRequestingContext + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS, + nsIContentPolicy::TYPE_INTERNAL_SCRIPT, + group); + + if (NS_FAILED(rv)) { + mCurrentScriptProto = nullptr; + return rv; + } + + aScriptProto->mSrcLoading = true; + } + + // Block until OnStreamComplete resumes us. + *aBlock = true; + return NS_OK; +} + +NS_IMETHODIMP +XULDocument::OnStreamComplete(nsIStreamLoader* aLoader, + nsISupports* context, + nsresult aStatus, + uint32_t stringLen, + const uint8_t* string) +{ + nsCOMPtr request; + aLoader->GetRequest(getter_AddRefs(request)); + nsCOMPtr channel = do_QueryInterface(request); + +#ifdef DEBUG + // print a load error on bad status + if (NS_FAILED(aStatus)) { + if (channel) { + nsCOMPtr uri; + channel->GetURI(getter_AddRefs(uri)); + if (uri) { + printf("Failed to load %s\n", uri->GetSpecOrDefault().get()); + } + } + } +#endif + + // This is the completion routine that will be called when a + // transcluded script completes. Compile and execute the script + // if the load was successful, then continue building content + // from the prototype. + nsresult rv = aStatus; + + NS_ASSERTION(mCurrentScriptProto && mCurrentScriptProto->mSrcLoading, + "script source not loading on unichar stream complete?"); + if (!mCurrentScriptProto) { + // XXX Wallpaper for bug 270042 + return NS_OK; + } + + if (NS_SUCCEEDED(aStatus)) { + // If the including XUL document is a FastLoad document, and we're + // compiling an out-of-line script (one with src=...), then we must + // be writing a new FastLoad file. If we were reading this script + // from the FastLoad file, XULContentSinkImpl::OpenScript (over in + // nsXULContentSink.cpp) would have already deserialized a non-null + // script->mScriptObject, causing control flow at the top of LoadScript + // not to reach here. + nsCOMPtr uri = mCurrentScriptProto->mSrcURI; + + // XXX should also check nsIHttpChannel::requestSucceeded + + MOZ_ASSERT(!mOffThreadCompiling && (mOffThreadCompileStringLength == 0 && + !mOffThreadCompileStringBuf), + "XULDocument can't load multiple scripts at once"); + + rv = nsScriptLoader::ConvertToUTF16(channel, string, stringLen, + EmptyString(), this, + mOffThreadCompileStringBuf, + mOffThreadCompileStringLength); + if (NS_SUCCEEDED(rv)) { + // Attempt to give ownership of the buffer to the JS engine. If + // we hit offthread compilation, however, we will have to take it + // back below in order to keep the memory alive until compilation + // completes. + JS::SourceBufferHolder srcBuf(mOffThreadCompileStringBuf, + mOffThreadCompileStringLength, + JS::SourceBufferHolder::GiveOwnership); + mOffThreadCompileStringBuf = nullptr; + mOffThreadCompileStringLength = 0; + + rv = mCurrentScriptProto->Compile(srcBuf, uri, 1, this, this); + if (NS_SUCCEEDED(rv) && !mCurrentScriptProto->HasScriptObject()) { + // We will be notified via OnOffThreadCompileComplete when the + // compile finishes. Keep the contents of the compiled script + // alive until the compilation finishes. + mOffThreadCompiling = true; + // If the JS engine did not take the source buffer, then take + // it back here to ensure it remains alive. + mOffThreadCompileStringBuf = srcBuf.take(); + if (mOffThreadCompileStringBuf) { + mOffThreadCompileStringLength = srcBuf.length(); + } + BlockOnload(); + return NS_OK; + } + } + } + + return OnScriptCompileComplete(mCurrentScriptProto->GetScriptObject(), rv); +} + +NS_IMETHODIMP +XULDocument::OnScriptCompileComplete(JSScript* aScript, nsresult aStatus) +{ + // When compiling off thread the script will not have been attached to the + // script proto yet. + if (aScript && !mCurrentScriptProto->HasScriptObject()) + mCurrentScriptProto->Set(aScript); + + // Allow load events to be fired once off thread compilation finishes. + if (mOffThreadCompiling) { + mOffThreadCompiling = false; + UnblockOnload(false); + } + + // After compilation finishes the script's characters are no longer needed. + if (mOffThreadCompileStringBuf) { + js_free(mOffThreadCompileStringBuf); + mOffThreadCompileStringBuf = nullptr; + mOffThreadCompileStringLength = 0; + } + + // Clear mCurrentScriptProto now, but save it first for use below in + // the execute code, and in the while loop that resumes walks of other + // documents that raced to load this script. + nsXULPrototypeScript* scriptProto = mCurrentScriptProto; + mCurrentScriptProto = nullptr; + + // Clear the prototype's loading flag before executing the script or + // resuming document walks, in case any of those control flows starts a + // new script load. + scriptProto->mSrcLoading = false; + + nsresult rv = aStatus; + if (NS_SUCCEEDED(rv)) { + rv = ExecuteScript(scriptProto); + + // If the XUL cache is enabled, save the script object there in + // case different XUL documents source the same script. + // + // But don't save the script in the cache unless the master XUL + // document URL is a chrome: URL. It is valid for a URL such as + // about:config to translate into a master document URL, whose + // prototype document nodes -- including prototype scripts that + // hold GC roots protecting their mJSObject pointers -- are not + // cached in the XUL prototype cache. See StartDocumentLoad, + // the fillXULCache logic. + // + // A document such as about:config is free to load a script via + // a URL such as chrome://global/content/config.js, and we must + // not cache that script object without a prototype cache entry + // containing a companion nsXULPrototypeScript node that owns a + // GC root protecting the script object. Otherwise, the script + // cache entry will dangle once the uncached prototype document + // is released when its owning XULDocument is unloaded. + // + // (See http://bugzilla.mozilla.org/show_bug.cgi?id=98207 for + // the true crime story.) + bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled(); + + if (useXULCache && IsChromeURI(mDocumentURI) && scriptProto->HasScriptObject()) { + JS::Rooted script(RootingCx(), scriptProto->GetScriptObject()); + nsXULPrototypeCache::GetInstance()->PutScript( + scriptProto->mSrcURI, script); + } + + if (mIsWritingFastLoad && mCurrentPrototype != mMasterPrototype) { + // If we are loading an overlay script, try to serialize + // it to the FastLoad file here. Master scripts will be + // serialized when the master prototype document gets + // written, at the bottom of ResumeWalk. That way, master + // out-of-line scripts are serialized in the same order that + // they'll be read, in the FastLoad file, which reduces the + // number of seeks that dump the underlying stream's buffer. + // + // Ignore the return value, as we don't need to propagate + // a failure to write to the FastLoad file, because this + // method aborts that whole process on error. + scriptProto->SerializeOutOfLine(nullptr, mCurrentPrototype); + } + // ignore any evaluation errors + } + + rv = ResumeWalk(); + + // Load a pointer to the prototype-script's list of XULDocuments who + // raced to load the same script + XULDocument** docp = &scriptProto->mSrcLoadWaiters; + + // Resume walking other documents that waited for this one's load, first + // executing the script we just compiled, in each doc's script context + XULDocument* doc; + while ((doc = *docp) != nullptr) { + NS_ASSERTION(doc->mCurrentScriptProto == scriptProto, + "waiting for wrong script to load?"); + doc->mCurrentScriptProto = nullptr; + + // Unlink doc from scriptProto's list before executing and resuming + *docp = doc->mNextSrcLoadWaiter; + doc->mNextSrcLoadWaiter = nullptr; + + // Execute only if we loaded and compiled successfully, then resume + if (NS_SUCCEEDED(aStatus) && scriptProto->HasScriptObject()) { + doc->ExecuteScript(scriptProto); + } + doc->ResumeWalk(); + NS_RELEASE(doc); + } + + return rv; +} + +nsresult +XULDocument::ExecuteScript(nsXULPrototypeScript *aScript) +{ + NS_PRECONDITION(aScript != nullptr, "null ptr"); + NS_ENSURE_TRUE(aScript, NS_ERROR_NULL_POINTER); + NS_ENSURE_TRUE(mScriptGlobalObject, NS_ERROR_NOT_INITIALIZED); + + nsresult rv; + rv = mScriptGlobalObject->EnsureScriptEnvironment(); + NS_ENSURE_SUCCESS(rv, rv); + + // Execute the precompiled script with the given version + nsAutoMicroTask mt; + + // We're about to run script via JS::CloneAndExecuteScript, so we need an + // AutoEntryScript. This is Gecko specific and not in any spec. + AutoEntryScript aes(mScriptGlobalObject, "precompiled XUL