Bumping manifests a=b2g-bump
[gecko.git] / dom / xul / XULDocument.cpp
blobd2212b3c91e79348b30dc1461afb3645c7f84761
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim: set ts=4 sw=4 et tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
9 An implementation for the XUL document. This implementation serves
10 as the basis for generating an NGLayout content model.
12 Notes
13 -----
15 1. We do some monkey business in the document observer methods to
16 keep the element map in sync for HTML elements. Why don't we just
17 do it for _all_ elements? Well, in the case of XUL elements,
18 which may be lazily created during frame construction, the
19 document observer methods will never be called because we'll be
20 adding the XUL nodes into the content model "quietly".
24 #include "mozilla/ArrayUtils.h"
26 #include "XULDocument.h"
28 #include "nsError.h"
29 #include "nsIBoxObject.h"
30 #include "nsIChromeRegistry.h"
31 #include "nsView.h"
32 #include "nsViewManager.h"
33 #include "nsIContentViewer.h"
34 #include "nsIDOMXULElement.h"
35 #include "nsIStreamListener.h"
36 #include "nsITimer.h"
37 #include "nsDocShell.h"
38 #include "nsGkAtoms.h"
39 #include "nsXMLContentSink.h"
40 #include "nsXULContentSink.h"
41 #include "nsXULContentUtils.h"
42 #include "nsIXULOverlayProvider.h"
43 #include "nsIStringEnumerator.h"
44 #include "nsNetUtil.h"
45 #include "nsParserCIID.h"
46 #include "nsPIBoxObject.h"
47 #include "mozilla/dom/BoxObject.h"
48 #include "nsXPIDLString.h"
49 #include "nsPIDOMWindow.h"
50 #include "nsPIWindowRoot.h"
51 #include "nsXULCommandDispatcher.h"
52 #include "nsXULElement.h"
53 #include "prlog.h"
54 #include "rdf.h"
55 #include "nsIFrame.h"
56 #include "nsXBLService.h"
57 #include "nsCExternalHandlerService.h"
58 #include "nsMimeTypes.h"
59 #include "nsIObjectInputStream.h"
60 #include "nsIObjectOutputStream.h"
61 #include "nsContentList.h"
62 #include "nsIScriptGlobalObject.h"
63 #include "nsIScriptSecurityManager.h"
64 #include "nsNodeInfoManager.h"
65 #include "nsContentCreatorFunctions.h"
66 #include "nsContentUtils.h"
67 #include "nsIParser.h"
68 #include "nsCharsetSource.h"
69 #include "nsIParserService.h"
70 #include "mozilla/CSSStyleSheet.h"
71 #include "mozilla/css/Loader.h"
72 #include "nsIScriptError.h"
73 #include "nsIStyleSheetLinkingElement.h"
74 #include "nsIObserverService.h"
75 #include "nsNodeUtils.h"
76 #include "nsIDocShellTreeOwner.h"
77 #include "nsIXULWindow.h"
78 #include "nsXULPopupManager.h"
79 #include "nsCCUncollectableMarker.h"
80 #include "nsURILoader.h"
81 #include "mozilla/AddonPathService.h"
82 #include "mozilla/BasicEvents.h"
83 #include "mozilla/dom/Element.h"
84 #include "mozilla/dom/NodeInfoInlines.h"
85 #include "mozilla/dom/ProcessingInstruction.h"
86 #include "mozilla/dom/ScriptSettings.h"
87 #include "mozilla/dom/XULDocumentBinding.h"
88 #include "mozilla/EventDispatcher.h"
89 #include "mozilla/LoadInfo.h"
90 #include "mozilla/Preferences.h"
91 #include "nsTextNode.h"
92 #include "nsJSUtils.h"
93 #include "mozilla/dom/URL.h"
94 #include "nsIContentPolicy.h"
96 using namespace mozilla;
97 using namespace mozilla::dom;
99 //----------------------------------------------------------------------
101 // CIDs
104 static NS_DEFINE_CID(kParserCID, NS_PARSER_CID);
106 static bool IsOverlayAllowed(nsIURI* aURI)
108 bool canOverlay = false;
109 if (NS_SUCCEEDED(aURI->SchemeIs("about", &canOverlay)) && canOverlay)
110 return true;
111 if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &canOverlay)) && canOverlay)
112 return true;
113 return false;
116 //----------------------------------------------------------------------
118 // Miscellaneous Constants
121 const nsForwardReference::Phase nsForwardReference::kPasses[] = {
122 nsForwardReference::eConstruction,
123 nsForwardReference::eHookup,
124 nsForwardReference::eDone
127 //----------------------------------------------------------------------
129 // Statics
132 int32_t XULDocument::gRefCnt = 0;
134 PRLogModuleInfo* XULDocument::gXULLog;
136 //----------------------------------------------------------------------
138 struct BroadcasterMapEntry : public PLDHashEntryHdr {
139 Element* mBroadcaster; // [WEAK]
140 nsSmallVoidArray mListeners; // [OWNING] of BroadcastListener objects
143 struct BroadcastListener {
144 nsWeakPtr mListener;
145 nsCOMPtr<nsIAtom> mAttribute;
148 Element*
149 nsRefMapEntry::GetFirstElement()
151 return static_cast<Element*>(mRefContentList.SafeElementAt(0));
154 void
155 nsRefMapEntry::AppendAll(nsCOMArray<nsIContent>* aElements)
157 for (int32_t i = 0; i < mRefContentList.Count(); ++i) {
158 aElements->AppendObject(static_cast<nsIContent*>(mRefContentList[i]));
162 bool
163 nsRefMapEntry::AddElement(Element* aElement)
165 if (mRefContentList.IndexOf(aElement) >= 0)
166 return true;
167 return mRefContentList.AppendElement(aElement);
170 bool
171 nsRefMapEntry::RemoveElement(Element* aElement)
173 mRefContentList.RemoveElement(aElement);
174 return mRefContentList.Count() == 0;
177 //----------------------------------------------------------------------
179 // ctors & dtors
182 namespace mozilla {
183 namespace dom {
185 XULDocument::XULDocument(void)
186 : XMLDocument("application/vnd.mozilla.xul+xml"),
187 mDocLWTheme(Doc_Theme_Uninitialized),
188 mState(eState_Master),
189 mResolutionPhase(nsForwardReference::eStart)
191 // NOTE! nsDocument::operator new() zeroes out all members, so don't
192 // bother initializing members to 0.
194 // Override the default in nsDocument
195 mCharacterSet.AssignLiteral("UTF-8");
197 mDefaultElementType = kNameSpaceID_XUL;
198 mType = eXUL;
200 mDelayFrameLoaderInitialization = true;
202 mAllowXULXBL = eTriTrue;
205 XULDocument::~XULDocument()
207 NS_ASSERTION(mNextSrcLoadWaiter == nullptr,
208 "unreferenced document still waiting for script source to load?");
210 // In case we failed somewhere early on and the forward observer
211 // decls never got resolved.
212 mForwardReferences.Clear();
213 // Likewise for any references we have to IDs where we might
214 // look for persisted data:
215 mPersistenceIds.Clear();
217 // Destroy our broadcaster map.
218 if (mBroadcasterMap) {
219 PL_DHashTableDestroy(mBroadcasterMap);
222 delete mTemplateBuilderTable;
224 Preferences::UnregisterCallback(XULDocument::DirectionChanged,
225 "intl.uidirection.", this);
227 if (mOffThreadCompileStringBuf) {
228 js_free(mOffThreadCompileStringBuf);
232 } // namespace dom
233 } // namespace mozilla
235 nsresult
236 NS_NewXULDocument(nsIXULDocument** result)
238 NS_PRECONDITION(result != nullptr, "null ptr");
239 if (! result)
240 return NS_ERROR_NULL_POINTER;
242 XULDocument* doc = new XULDocument();
243 if (! doc)
244 return NS_ERROR_OUT_OF_MEMORY;
246 NS_ADDREF(doc);
248 nsresult rv;
249 if (NS_FAILED(rv = doc->Init())) {
250 NS_RELEASE(doc);
251 return rv;
254 *result = doc;
255 return NS_OK;
259 namespace mozilla {
260 namespace dom {
262 //----------------------------------------------------------------------
264 // nsISupports interface
267 static PLDHashOperator
268 TraverseTemplateBuilders(nsISupports* aKey, nsIXULTemplateBuilder* aData,
269 void* aContext)
271 nsCycleCollectionTraversalCallback *cb =
272 static_cast<nsCycleCollectionTraversalCallback*>(aContext);
274 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mTemplateBuilderTable key");
275 cb->NoteXPCOMChild(aKey);
276 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mTemplateBuilderTable value");
277 cb->NoteXPCOMChild(aData);
279 return PL_DHASH_NEXT;
282 static PLDHashOperator
283 TraverseObservers(nsIURI* aKey, nsIObserver* aData, void* aContext)
285 nsCycleCollectionTraversalCallback *cb =
286 static_cast<nsCycleCollectionTraversalCallback*>(aContext);
288 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mOverlayLoadObservers/mPendingOverlayLoadNotifications value");
289 cb->NoteXPCOMChild(aData);
291 return PL_DHASH_NEXT;
294 NS_IMPL_CYCLE_COLLECTION_CLASS(XULDocument)
296 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XULDocument, XMLDocument)
297 NS_ASSERTION(!nsCCUncollectableMarker::InGeneration(cb, tmp->GetMarkedCCGeneration()),
298 "Shouldn't traverse XULDocument!");
299 // XXX tmp->mForwardReferences?
300 // XXX tmp->mContextStack?
302 // An element will only have a template builder as long as it's in the
303 // document, so we'll traverse the table here instead of from the element.
304 if (tmp->mTemplateBuilderTable)
305 tmp->mTemplateBuilderTable->EnumerateRead(TraverseTemplateBuilders, &cb);
307 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCurrentPrototype)
308 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMasterPrototype)
309 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
310 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypes);
311 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStore)
313 if (tmp->mOverlayLoadObservers) {
314 tmp->mOverlayLoadObservers->EnumerateRead(TraverseObservers, &cb);
316 if (tmp->mPendingOverlayLoadNotifications) {
317 tmp->mPendingOverlayLoadNotifications->EnumerateRead(TraverseObservers, &cb);
319 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
321 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULDocument, XMLDocument)
322 delete tmp->mTemplateBuilderTable;
323 tmp->mTemplateBuilderTable = nullptr;
325 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
326 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStore)
327 //XXX We should probably unlink all the objects we traverse.
328 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
330 NS_IMPL_ADDREF_INHERITED(XULDocument, XMLDocument)
331 NS_IMPL_RELEASE_INHERITED(XULDocument, XMLDocument)
334 // QueryInterface implementation for XULDocument
335 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(XULDocument)
336 NS_INTERFACE_TABLE_INHERITED(XULDocument, nsIXULDocument,
337 nsIDOMXULDocument, nsIStreamLoaderObserver,
338 nsICSSLoaderObserver, nsIOffThreadScriptReceiver)
339 NS_INTERFACE_TABLE_TAIL_INHERITING(XMLDocument)
342 //----------------------------------------------------------------------
344 // nsIDocument interface
347 void
348 XULDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup)
350 NS_NOTREACHED("Reset");
353 void
354 XULDocument::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
355 nsIPrincipal* aPrincipal)
357 NS_NOTREACHED("ResetToURI");
360 void
361 XULDocument::SetContentType(const nsAString& aContentType)
363 NS_ASSERTION(aContentType.EqualsLiteral("application/vnd.mozilla.xul+xml"),
364 "xul-documents always has content-type application/vnd.mozilla.xul+xml");
365 // Don't do anything, xul always has the mimetype
366 // application/vnd.mozilla.xul+xml
369 // This is called when the master document begins loading, whether it's
370 // being cached or not.
371 nsresult
372 XULDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
373 nsILoadGroup* aLoadGroup,
374 nsISupports* aContainer,
375 nsIStreamListener **aDocListener,
376 bool aReset, nsIContentSink* aSink)
378 #ifdef PR_LOGGING
379 if (PR_LOG_TEST(gXULLog, PR_LOG_WARNING)) {
381 nsCOMPtr<nsIURI> uri;
382 nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(uri));
383 if (NS_SUCCEEDED(rv)) {
384 nsAutoCString urlspec;
385 rv = uri->GetSpec(urlspec);
386 if (NS_SUCCEEDED(rv)) {
387 PR_LOG(gXULLog, PR_LOG_WARNING,
388 ("xul: load document '%s'", urlspec.get()));
392 #endif
393 // NOTE: If this ever starts calling nsDocument::StartDocumentLoad
394 // we'll possibly need to reset our content type afterwards.
395 mStillWalking = true;
396 mMayStartLayout = false;
397 mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
399 mChannel = aChannel;
401 mHaveInputEncoding = true;
403 // Get the URI. Note that this should match nsDocShell::OnLoadingSite
404 nsresult rv =
405 NS_GetFinalChannelURI(aChannel, getter_AddRefs(mDocumentURI));
406 NS_ENSURE_SUCCESS(rv, rv);
408 ResetStylesheetsToURI(mDocumentURI);
410 RetrieveRelevantHeaders(aChannel);
412 // Look in the chrome cache: we've got this puppy loaded
413 // already.
414 nsXULPrototypeDocument* proto = IsChromeURI(mDocumentURI) ?
415 nsXULPrototypeCache::GetInstance()->GetPrototype(mDocumentURI) :
416 nullptr;
418 // Same comment as nsChromeProtocolHandler::NewChannel and
419 // XULDocument::ResumeWalk
420 // - Ben Goodger
422 // We don't abort on failure here because there are too many valid
423 // cases that can return failure, and the null-ness of |proto| is enough
424 // to trigger the fail-safe parse-from-disk solution. Example failure cases
425 // (for reference) include:
427 // NS_ERROR_NOT_AVAILABLE: the URI cannot be found in the startup cache,
428 // parse from disk
429 // other: the startup cache file could not be found, probably
430 // due to being accessed before a profile has been selected (e.g.
431 // loading chrome for the profile manager itself). This must be
432 // parsed from disk.
434 if (proto) {
435 // If we're racing with another document to load proto, wait till the
436 // load has finished loading before trying to add cloned style sheets.
437 // XULDocument::EndLoad will call proto->NotifyLoadDone, which will
438 // find all racing documents and notify them via OnPrototypeLoadDone,
439 // which will add style sheet clones to each document.
440 bool loaded;
441 rv = proto->AwaitLoadDone(this, &loaded);
442 if (NS_FAILED(rv)) return rv;
444 mMasterPrototype = mCurrentPrototype = proto;
446 // Set up the right principal on ourselves.
447 SetPrincipal(proto->DocumentPrincipal());
449 // We need a listener, even if proto is not yet loaded, in which
450 // event the listener's OnStopRequest method does nothing, and all
451 // the interesting work happens below XULDocument::EndLoad, from
452 // the call there to mCurrentPrototype->NotifyLoadDone().
453 *aDocListener = new CachedChromeStreamListener(this, loaded);
454 if (! *aDocListener)
455 return NS_ERROR_OUT_OF_MEMORY;
457 else {
458 bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
459 bool fillXULCache = (useXULCache && IsChromeURI(mDocumentURI));
462 // It's just a vanilla document load. Create a parser to deal
463 // with the stream n' stuff.
465 nsCOMPtr<nsIParser> parser;
466 rv = PrepareToLoad(aContainer, aCommand, aChannel, aLoadGroup,
467 getter_AddRefs(parser));
468 if (NS_FAILED(rv)) return rv;
470 // Predicate mIsWritingFastLoad on the XUL cache being enabled,
471 // so we don't have to re-check whether the cache is enabled all
472 // the time.
473 mIsWritingFastLoad = useXULCache;
475 nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(parser, &rv);
476 NS_ASSERTION(NS_SUCCEEDED(rv), "parser doesn't support nsIStreamListener");
477 if (NS_FAILED(rv)) return rv;
479 *aDocListener = listener;
481 parser->Parse(mDocumentURI);
483 // Put the current prototype, created under PrepareToLoad, into the
484 // XUL prototype cache now. We can't do this under PrepareToLoad or
485 // overlay loading will break; search for PutPrototype in ResumeWalk
486 // and see the comment there.
487 if (fillXULCache) {
488 nsXULPrototypeCache::GetInstance()->PutPrototype(mCurrentPrototype);
492 NS_IF_ADDREF(*aDocListener);
493 return NS_OK;
496 // This gets invoked after a prototype for this document or one of
497 // its overlays is fully built in the content sink.
498 void
499 XULDocument::EndLoad()
501 // This can happen if an overlay fails to load
502 if (!mCurrentPrototype)
503 return;
505 nsresult rv;
507 // Whack the prototype document into the cache so that the next
508 // time somebody asks for it, they don't need to load it by hand.
510 nsCOMPtr<nsIURI> uri = mCurrentPrototype->GetURI();
511 bool isChrome = IsChromeURI(uri);
513 // Remember if the XUL cache is on
514 bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
516 // If the current prototype is an overlay document (non-master prototype)
517 // and we're filling the FastLoad disk cache, tell the cache we're done
518 // loading it, and write the prototype. The master prototype is put into
519 // the cache earlier in XULDocument::StartDocumentLoad.
520 if (useXULCache && mIsWritingFastLoad && isChrome &&
521 mMasterPrototype != mCurrentPrototype) {
522 nsXULPrototypeCache::GetInstance()->WritePrototype(mCurrentPrototype);
525 if (IsOverlayAllowed(uri)) {
526 nsCOMPtr<nsIXULOverlayProvider> reg =
527 mozilla::services::GetXULOverlayProviderService();
529 if (reg) {
530 nsCOMPtr<nsISimpleEnumerator> overlays;
531 rv = reg->GetStyleOverlays(uri, getter_AddRefs(overlays));
532 if (NS_FAILED(rv)) return;
534 bool moreSheets;
535 nsCOMPtr<nsISupports> next;
536 nsCOMPtr<nsIURI> sheetURI;
538 while (NS_SUCCEEDED(rv = overlays->HasMoreElements(&moreSheets)) &&
539 moreSheets) {
540 overlays->GetNext(getter_AddRefs(next));
542 sheetURI = do_QueryInterface(next);
543 if (!sheetURI) {
544 NS_ERROR("Chrome registry handed me a non-nsIURI object!");
545 continue;
548 if (IsChromeURI(sheetURI)) {
549 mCurrentPrototype->AddStyleSheetReference(sheetURI);
554 if (isChrome && useXULCache) {
555 // If it's a chrome prototype document, then notify any
556 // documents that raced to load the prototype, and awaited
557 // its load completion via proto->AwaitLoadDone().
558 rv = mCurrentPrototype->NotifyLoadDone();
559 if (NS_FAILED(rv)) return;
563 OnPrototypeLoadDone(true);
564 #ifdef PR_LOGGING
565 if (PR_LOG_TEST(gXULLog, PR_LOG_WARNING)) {
566 nsAutoCString urlspec;
567 rv = uri->GetSpec(urlspec);
568 if (NS_SUCCEEDED(rv)) {
569 PR_LOG(gXULLog, PR_LOG_WARNING,
570 ("xul: Finished loading document '%s'", urlspec.get()));
573 #endif
576 NS_IMETHODIMP
577 XULDocument::OnPrototypeLoadDone(bool aResumeWalk)
579 nsresult rv;
581 // Add the style overlays from chrome registry, if any.
582 rv = AddPrototypeSheets();
583 if (NS_FAILED(rv)) return rv;
585 rv = PrepareToWalk();
586 NS_ASSERTION(NS_SUCCEEDED(rv), "unable to prepare for walk");
587 if (NS_FAILED(rv)) return rv;
589 if (aResumeWalk) {
590 rv = ResumeWalk();
592 return rv;
595 // called when an error occurs parsing a document
596 bool
597 XULDocument::OnDocumentParserError()
599 // don't report errors that are from overlays
600 if (mCurrentPrototype && mMasterPrototype != mCurrentPrototype) {
601 nsCOMPtr<nsIURI> uri = mCurrentPrototype->GetURI();
602 if (IsChromeURI(uri)) {
603 nsCOMPtr<nsIObserverService> os =
604 mozilla::services::GetObserverService();
605 if (os)
606 os->NotifyObservers(uri, "xul-overlay-parsererror",
607 EmptyString().get());
610 return false;
613 return true;
616 static void
617 ClearBroadcasterMapEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
619 BroadcasterMapEntry* entry =
620 static_cast<BroadcasterMapEntry*>(aEntry);
621 for (int32_t i = entry->mListeners.Count() - 1; i >= 0; --i) {
622 delete (BroadcastListener*)entry->mListeners[i];
625 // N.B. that we need to manually run the dtor because we
626 // constructed the nsSmallVoidArray object in-place.
627 entry->mListeners.~nsSmallVoidArray();
630 static bool
631 CanBroadcast(int32_t aNameSpaceID, nsIAtom* aAttribute)
633 // Don't push changes to the |id|, |ref|, |persist|, |command| or
634 // |observes| attribute.
635 if (aNameSpaceID == kNameSpaceID_None) {
636 if ((aAttribute == nsGkAtoms::id) ||
637 (aAttribute == nsGkAtoms::ref) ||
638 (aAttribute == nsGkAtoms::persist) ||
639 (aAttribute == nsGkAtoms::command) ||
640 (aAttribute == nsGkAtoms::observes)) {
641 return false;
644 return true;
647 struct nsAttrNameInfo
649 nsAttrNameInfo(int32_t aNamespaceID, nsIAtom* aName, nsIAtom* aPrefix) :
650 mNamespaceID(aNamespaceID), mName(aName), mPrefix(aPrefix) {}
651 nsAttrNameInfo(const nsAttrNameInfo& aOther) :
652 mNamespaceID(aOther.mNamespaceID), mName(aOther.mName),
653 mPrefix(aOther.mPrefix) {}
654 int32_t mNamespaceID;
655 nsCOMPtr<nsIAtom> mName;
656 nsCOMPtr<nsIAtom> mPrefix;
659 void
660 XULDocument::SynchronizeBroadcastListener(Element *aBroadcaster,
661 Element *aListener,
662 const nsAString &aAttr)
664 if (!nsContentUtils::IsSafeToRunScript()) {
665 nsDelayedBroadcastUpdate delayedUpdate(aBroadcaster, aListener,
666 aAttr);
667 mDelayedBroadcasters.AppendElement(delayedUpdate);
668 MaybeBroadcast();
669 return;
671 bool notify = mDocumentLoaded || mHandlingDelayedBroadcasters;
673 if (aAttr.EqualsLiteral("*")) {
674 uint32_t count = aBroadcaster->GetAttrCount();
675 nsTArray<nsAttrNameInfo> attributes(count);
676 for (uint32_t i = 0; i < count; ++i) {
677 const nsAttrName* attrName = aBroadcaster->GetAttrNameAt(i);
678 int32_t nameSpaceID = attrName->NamespaceID();
679 nsIAtom* name = attrName->LocalName();
681 // _Don't_ push the |id|, |ref|, or |persist| attribute's value!
682 if (! CanBroadcast(nameSpaceID, name))
683 continue;
685 attributes.AppendElement(nsAttrNameInfo(nameSpaceID, name,
686 attrName->GetPrefix()));
689 count = attributes.Length();
690 while (count-- > 0) {
691 int32_t nameSpaceID = attributes[count].mNamespaceID;
692 nsIAtom* name = attributes[count].mName;
693 nsAutoString value;
694 if (aBroadcaster->GetAttr(nameSpaceID, name, value)) {
695 aListener->SetAttr(nameSpaceID, name, attributes[count].mPrefix,
696 value, notify);
699 #if 0
700 // XXX we don't fire the |onbroadcast| handler during
701 // initial hookup: doing so would potentially run the
702 // |onbroadcast| handler before the |onload| handler,
703 // which could define JS properties that mask XBL
704 // properties, etc.
705 ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
706 #endif
709 else {
710 // Find out if the attribute is even present at all.
711 nsCOMPtr<nsIAtom> name = do_GetAtom(aAttr);
713 nsAutoString value;
714 if (aBroadcaster->GetAttr(kNameSpaceID_None, name, value)) {
715 aListener->SetAttr(kNameSpaceID_None, name, value, notify);
716 } else {
717 aListener->UnsetAttr(kNameSpaceID_None, name, notify);
720 #if 0
721 // XXX we don't fire the |onbroadcast| handler during initial
722 // hookup: doing so would potentially run the |onbroadcast|
723 // handler before the |onload| handler, which could define JS
724 // properties that mask XBL properties, etc.
725 ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
726 #endif
730 NS_IMETHODIMP
731 XULDocument::AddBroadcastListenerFor(nsIDOMElement* aBroadcaster,
732 nsIDOMElement* aListener,
733 const nsAString& aAttr)
735 ErrorResult rv;
736 nsCOMPtr<Element> broadcaster = do_QueryInterface(aBroadcaster);
737 nsCOMPtr<Element> listener = do_QueryInterface(aListener);
738 NS_ENSURE_ARG(broadcaster && listener);
739 AddBroadcastListenerFor(*broadcaster, *listener, aAttr, rv);
740 return rv.ErrorCode();
743 void
744 XULDocument::AddBroadcastListenerFor(Element& aBroadcaster, Element& aListener,
745 const nsAString& aAttr, ErrorResult& aRv)
747 nsresult rv =
748 nsContentUtils::CheckSameOrigin(this, &aBroadcaster);
750 if (NS_FAILED(rv)) {
751 aRv.Throw(rv);
752 return;
755 rv = nsContentUtils::CheckSameOrigin(this, &aListener);
757 if (NS_FAILED(rv)) {
758 aRv.Throw(rv);
759 return;
762 static const PLDHashTableOps gOps = {
763 PL_DHashAllocTable,
764 PL_DHashFreeTable,
765 PL_DHashVoidPtrKeyStub,
766 PL_DHashMatchEntryStub,
767 PL_DHashMoveEntryStub,
768 ClearBroadcasterMapEntry,
769 PL_DHashFinalizeStub,
770 nullptr
773 if (! mBroadcasterMap) {
774 mBroadcasterMap =
775 PL_NewDHashTable(&gOps, nullptr, sizeof(BroadcasterMapEntry));
777 if (! mBroadcasterMap) {
778 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
779 return;
783 BroadcasterMapEntry* entry =
784 static_cast<BroadcasterMapEntry*>
785 (PL_DHashTableLookup(mBroadcasterMap, &aBroadcaster));
787 if (PL_DHASH_ENTRY_IS_FREE(entry)) {
788 entry =
789 static_cast<BroadcasterMapEntry*>
790 (PL_DHashTableAdd(mBroadcasterMap, &aBroadcaster));
792 if (! entry) {
793 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
794 return;
797 entry->mBroadcaster = &aBroadcaster;
799 // N.B. placement new to construct the nsSmallVoidArray object
800 // in-place
801 new (&entry->mListeners) nsSmallVoidArray();
804 // Only add the listener if it's not there already!
805 nsCOMPtr<nsIAtom> attr = do_GetAtom(aAttr);
807 BroadcastListener* bl;
808 for (int32_t i = entry->mListeners.Count() - 1; i >= 0; --i) {
809 bl = static_cast<BroadcastListener*>(entry->mListeners[i]);
811 nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
813 if (blListener == &aListener && bl->mAttribute == attr)
814 return;
817 bl = new BroadcastListener;
819 bl->mListener = do_GetWeakReference(&aListener);
820 bl->mAttribute = attr;
822 entry->mListeners.AppendElement(bl);
824 SynchronizeBroadcastListener(&aBroadcaster, &aListener, aAttr);
827 NS_IMETHODIMP
828 XULDocument::RemoveBroadcastListenerFor(nsIDOMElement* aBroadcaster,
829 nsIDOMElement* aListener,
830 const nsAString& aAttr)
832 nsCOMPtr<Element> broadcaster = do_QueryInterface(aBroadcaster);
833 nsCOMPtr<Element> listener = do_QueryInterface(aListener);
834 NS_ENSURE_ARG(broadcaster && listener);
835 RemoveBroadcastListenerFor(*broadcaster, *listener, aAttr);
836 return NS_OK;
839 void
840 XULDocument::RemoveBroadcastListenerFor(Element& aBroadcaster,
841 Element& aListener,
842 const nsAString& aAttr)
844 // If we haven't added any broadcast listeners, then there sure
845 // aren't any to remove.
846 if (! mBroadcasterMap)
847 return;
849 BroadcasterMapEntry* entry =
850 static_cast<BroadcasterMapEntry*>
851 (PL_DHashTableLookup(mBroadcasterMap, &aBroadcaster));
853 if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
854 nsCOMPtr<nsIAtom> attr = do_GetAtom(aAttr);
855 for (int32_t i = entry->mListeners.Count() - 1; i >= 0; --i) {
856 BroadcastListener* bl =
857 static_cast<BroadcastListener*>(entry->mListeners[i]);
859 nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
861 if (blListener == &aListener && bl->mAttribute == attr) {
862 entry->mListeners.RemoveElementAt(i);
863 delete bl;
865 if (entry->mListeners.Count() == 0)
866 PL_DHashTableRemove(mBroadcasterMap, &aBroadcaster);
868 break;
874 nsresult
875 XULDocument::ExecuteOnBroadcastHandlerFor(Element* aBroadcaster,
876 Element* aListener,
877 nsIAtom* aAttr)
879 // Now we execute the onchange handler in the context of the
880 // observer. We need to find the observer in order to
881 // execute the handler.
883 for (nsIContent* child = aListener->GetFirstChild();
884 child;
885 child = child->GetNextSibling()) {
887 // Look for an <observes> element beneath the listener. This
888 // ought to have an |element| attribute that refers to
889 // aBroadcaster, and an |attribute| element that tells us what
890 // attriubtes we're listening for.
891 if (!child->NodeInfo()->Equals(nsGkAtoms::observes, kNameSpaceID_XUL))
892 continue;
894 // Is this the element that was listening to us?
895 nsAutoString listeningToID;
896 child->GetAttr(kNameSpaceID_None, nsGkAtoms::element, listeningToID);
898 nsAutoString broadcasterID;
899 aBroadcaster->GetAttr(kNameSpaceID_None, nsGkAtoms::id, broadcasterID);
901 if (listeningToID != broadcasterID)
902 continue;
904 // We are observing the broadcaster, but is this the right
905 // attribute?
906 nsAutoString listeningToAttribute;
907 child->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute,
908 listeningToAttribute);
910 if (!aAttr->Equals(listeningToAttribute) &&
911 !listeningToAttribute.EqualsLiteral("*")) {
912 continue;
915 // This is the right <observes> element. Execute the
916 // |onbroadcast| event handler
917 WidgetEvent event(true, NS_XUL_BROADCAST);
919 nsCOMPtr<nsIPresShell> shell = GetShell();
920 if (shell) {
921 nsRefPtr<nsPresContext> aPresContext = shell->GetPresContext();
923 // Handle the DOM event
924 nsEventStatus status = nsEventStatus_eIgnore;
925 EventDispatcher::Dispatch(child, aPresContext, &event, nullptr,
926 &status);
930 return NS_OK;
933 void
934 XULDocument::AttributeWillChange(nsIDocument* aDocument,
935 Element* aElement, int32_t aNameSpaceID,
936 nsIAtom* aAttribute, int32_t aModType)
938 NS_ABORT_IF_FALSE(aElement, "Null content!");
939 NS_PRECONDITION(aAttribute, "Must have an attribute that's changing!");
941 // XXXbz check aNameSpaceID, dammit!
942 // See if we need to update our ref map.
943 if (aAttribute == nsGkAtoms::ref) {
944 // Might not need this, but be safe for now.
945 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
946 RemoveElementFromRefMap(aElement);
950 void
951 XULDocument::AttributeChanged(nsIDocument* aDocument,
952 Element* aElement, int32_t aNameSpaceID,
953 nsIAtom* aAttribute, int32_t aModType)
955 NS_ASSERTION(aDocument == this, "unexpected doc");
957 // Might not need this, but be safe for now.
958 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
960 // XXXbz check aNameSpaceID, dammit!
961 // See if we need to update our ref map.
962 if (aAttribute == nsGkAtoms::ref) {
963 AddElementToRefMap(aElement);
966 nsresult rv;
968 // Synchronize broadcast listeners
969 if (mBroadcasterMap &&
970 CanBroadcast(aNameSpaceID, aAttribute)) {
971 BroadcasterMapEntry* entry =
972 static_cast<BroadcasterMapEntry*>
973 (PL_DHashTableLookup(mBroadcasterMap, aElement));
975 if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
976 // We've got listeners: push the value.
977 nsAutoString value;
978 bool attrSet = aElement->GetAttr(kNameSpaceID_None, aAttribute, value);
980 int32_t i;
981 for (i = entry->mListeners.Count() - 1; i >= 0; --i) {
982 BroadcastListener* bl =
983 static_cast<BroadcastListener*>(entry->mListeners[i]);
985 if ((bl->mAttribute == aAttribute) ||
986 (bl->mAttribute == nsGkAtoms::_asterix)) {
987 nsCOMPtr<Element> listenerEl
988 = do_QueryReferent(bl->mListener);
989 if (listenerEl) {
990 nsAutoString currentValue;
991 bool hasAttr = listenerEl->GetAttr(kNameSpaceID_None,
992 aAttribute,
993 currentValue);
994 // We need to update listener only if we're
995 // (1) removing an existing attribute,
996 // (2) adding a new attribute or
997 // (3) changing the value of an attribute.
998 bool needsAttrChange =
999 attrSet != hasAttr || !value.Equals(currentValue);
1000 nsDelayedBroadcastUpdate delayedUpdate(aElement,
1001 listenerEl,
1002 aAttribute,
1003 value,
1004 attrSet,
1005 needsAttrChange);
1007 size_t index =
1008 mDelayedAttrChangeBroadcasts.IndexOf(delayedUpdate,
1009 0, nsDelayedBroadcastUpdate::Comparator());
1010 if (index != mDelayedAttrChangeBroadcasts.NoIndex) {
1011 if (mHandlingDelayedAttrChange) {
1012 NS_WARNING("Broadcasting loop!");
1013 continue;
1015 mDelayedAttrChangeBroadcasts.RemoveElementAt(index);
1018 mDelayedAttrChangeBroadcasts.AppendElement(delayedUpdate);
1025 // checks for modifications in broadcasters
1026 bool listener, resolved;
1027 CheckBroadcasterHookup(aElement, &listener, &resolved);
1029 // See if there is anything we need to persist in the localstore.
1031 // XXX Namespace handling broken :-(
1032 nsAutoString persist;
1033 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist);
1034 if (!persist.IsEmpty()) {
1035 // XXXldb This should check that it's a token, not just a substring.
1036 if (persist.Find(nsDependentAtomString(aAttribute)) >= 0) {
1037 rv = Persist(aElement, kNameSpaceID_None, aAttribute);
1038 if (NS_FAILED(rv)) return;
1043 void
1044 XULDocument::ContentAppended(nsIDocument* aDocument,
1045 nsIContent* aContainer,
1046 nsIContent* aFirstNewContent,
1047 int32_t aNewIndexInContainer)
1049 NS_ASSERTION(aDocument == this, "unexpected doc");
1051 // Might not need this, but be safe for now.
1052 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
1054 // Update our element map
1055 nsresult rv = NS_OK;
1056 for (nsIContent* cur = aFirstNewContent; cur && NS_SUCCEEDED(rv);
1057 cur = cur->GetNextSibling()) {
1058 rv = AddSubtreeToDocument(cur);
1062 void
1063 XULDocument::ContentInserted(nsIDocument* aDocument,
1064 nsIContent* aContainer,
1065 nsIContent* aChild,
1066 int32_t aIndexInContainer)
1068 NS_ASSERTION(aDocument == this, "unexpected doc");
1070 // Might not need this, but be safe for now.
1071 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
1073 AddSubtreeToDocument(aChild);
1076 void
1077 XULDocument::ContentRemoved(nsIDocument* aDocument,
1078 nsIContent* aContainer,
1079 nsIContent* aChild,
1080 int32_t aIndexInContainer,
1081 nsIContent* aPreviousSibling)
1083 NS_ASSERTION(aDocument == this, "unexpected doc");
1085 // Might not need this, but be safe for now.
1086 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
1088 RemoveSubtreeFromDocument(aChild);
1091 //----------------------------------------------------------------------
1093 // nsIXULDocument interface
1096 void
1097 XULDocument::GetElementsForID(const nsAString& aID,
1098 nsCOMArray<nsIContent>& aElements)
1100 aElements.Clear();
1102 nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aID);
1103 if (entry) {
1104 entry->AppendAllIdContent(&aElements);
1106 nsRefMapEntry *refEntry = mRefMap.GetEntry(aID);
1107 if (refEntry) {
1108 refEntry->AppendAll(&aElements);
1112 nsresult
1113 XULDocument::AddForwardReference(nsForwardReference* aRef)
1115 if (mResolutionPhase < aRef->GetPhase()) {
1116 if (!mForwardReferences.AppendElement(aRef)) {
1117 delete aRef;
1118 return NS_ERROR_OUT_OF_MEMORY;
1121 else {
1122 NS_ERROR("forward references have already been resolved");
1123 delete aRef;
1126 return NS_OK;
1129 nsresult
1130 XULDocument::ResolveForwardReferences()
1132 if (mResolutionPhase == nsForwardReference::eDone)
1133 return NS_OK;
1135 NS_ASSERTION(mResolutionPhase == nsForwardReference::eStart,
1136 "nested ResolveForwardReferences()");
1138 // Resolve each outstanding 'forward' reference. We iterate
1139 // through the list of forward references until no more forward
1140 // references can be resolved. This annealing process is
1141 // guaranteed to converge because we've "closed the gate" to new
1142 // forward references.
1144 const nsForwardReference::Phase* pass = nsForwardReference::kPasses;
1145 while ((mResolutionPhase = *pass) != nsForwardReference::eDone) {
1146 uint32_t previous = 0;
1147 while (mForwardReferences.Length() &&
1148 mForwardReferences.Length() != previous) {
1149 previous = mForwardReferences.Length();
1151 for (uint32_t i = 0; i < mForwardReferences.Length(); ++i) {
1152 nsForwardReference* fwdref = mForwardReferences[i];
1154 if (fwdref->GetPhase() == *pass) {
1155 nsForwardReference::Result result = fwdref->Resolve();
1157 switch (result) {
1158 case nsForwardReference::eResolve_Succeeded:
1159 case nsForwardReference::eResolve_Error:
1160 mForwardReferences.RemoveElementAt(i);
1162 // fixup because we removed from list
1163 --i;
1164 break;
1166 case nsForwardReference::eResolve_Later:
1167 // do nothing. we'll try again later
1171 if (mResolutionPhase == nsForwardReference::eStart) {
1172 // Resolve() loaded a dynamic overlay,
1173 // (see XULDocument::LoadOverlayInternal()).
1174 // Return for now, we will be called again.
1175 return NS_OK;
1181 ++pass;
1184 mForwardReferences.Clear();
1185 return NS_OK;
1188 //----------------------------------------------------------------------
1190 // nsIDOMDocument interface
1193 NS_IMETHODIMP
1194 XULDocument::GetElementsByAttribute(const nsAString& aAttribute,
1195 const nsAString& aValue,
1196 nsIDOMNodeList** aReturn)
1198 *aReturn = GetElementsByAttribute(aAttribute, aValue).take();
1199 return NS_OK;
1202 already_AddRefed<nsINodeList>
1203 XULDocument::GetElementsByAttribute(const nsAString& aAttribute,
1204 const nsAString& aValue)
1206 nsCOMPtr<nsIAtom> attrAtom(do_GetAtom(aAttribute));
1207 void* attrValue = new nsString(aValue);
1208 nsRefPtr<nsContentList> list = new nsContentList(this,
1209 MatchAttribute,
1210 nsContentUtils::DestroyMatchString,
1211 attrValue,
1212 true,
1213 attrAtom,
1214 kNameSpaceID_Unknown);
1216 return list.forget();
1219 NS_IMETHODIMP
1220 XULDocument::GetElementsByAttributeNS(const nsAString& aNamespaceURI,
1221 const nsAString& aAttribute,
1222 const nsAString& aValue,
1223 nsIDOMNodeList** aReturn)
1225 ErrorResult rv;
1226 *aReturn = GetElementsByAttributeNS(aNamespaceURI, aAttribute,
1227 aValue, rv).take();
1228 return rv.ErrorCode();
1231 already_AddRefed<nsINodeList>
1232 XULDocument::GetElementsByAttributeNS(const nsAString& aNamespaceURI,
1233 const nsAString& aAttribute,
1234 const nsAString& aValue,
1235 ErrorResult& aRv)
1237 nsCOMPtr<nsIAtom> attrAtom(do_GetAtom(aAttribute));
1238 void* attrValue = new nsString(aValue);
1240 int32_t nameSpaceId = kNameSpaceID_Wildcard;
1241 if (!aNamespaceURI.EqualsLiteral("*")) {
1242 nsresult rv =
1243 nsContentUtils::NameSpaceManager()->RegisterNameSpace(aNamespaceURI,
1244 nameSpaceId);
1245 if (NS_FAILED(rv)) {
1246 aRv.Throw(rv);
1247 return nullptr;
1251 nsRefPtr<nsContentList> list = new nsContentList(this,
1252 MatchAttribute,
1253 nsContentUtils::DestroyMatchString,
1254 attrValue,
1255 true,
1256 attrAtom,
1257 nameSpaceId);
1258 return list.forget();
1261 NS_IMETHODIMP
1262 XULDocument::Persist(const nsAString& aID,
1263 const nsAString& aAttr)
1265 // If we're currently reading persisted attributes out of the
1266 // localstore, _don't_ re-enter and try to set them again!
1267 if (mApplyingPersistedAttrs)
1268 return NS_OK;
1270 Element* element = nsDocument::GetElementById(aID);
1271 if (!element)
1272 return NS_OK;
1274 nsCOMPtr<nsIAtom> tag;
1275 int32_t nameSpaceID;
1277 nsRefPtr<mozilla::dom::NodeInfo> ni = element->GetExistingAttrNameFromQName(aAttr);
1278 nsresult rv;
1279 if (ni) {
1280 tag = ni->NameAtom();
1281 nameSpaceID = ni->NamespaceID();
1283 else {
1284 // Make sure that this QName is going to be valid.
1285 const char16_t *colon;
1286 rv = nsContentUtils::CheckQName(PromiseFlatString(aAttr), true, &colon);
1288 if (NS_FAILED(rv)) {
1289 // There was an invalid character or it was malformed.
1290 return NS_ERROR_INVALID_ARG;
1293 if (colon) {
1294 // We don't really handle namespace qualifiers in attribute names.
1295 return NS_ERROR_NOT_IMPLEMENTED;
1298 tag = do_GetAtom(aAttr);
1299 NS_ENSURE_TRUE(tag, NS_ERROR_OUT_OF_MEMORY);
1301 nameSpaceID = kNameSpaceID_None;
1304 return Persist(element, nameSpaceID, tag);
1307 nsresult
1308 XULDocument::Persist(nsIContent* aElement, int32_t aNameSpaceID,
1309 nsIAtom* aAttribute)
1311 // For non-chrome documents, persistance is simply broken
1312 if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()))
1313 return NS_ERROR_NOT_AVAILABLE;
1315 if (!mLocalStore) {
1316 mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
1317 if (NS_WARN_IF(!mLocalStore)) {
1318 return NS_ERROR_NOT_INITIALIZED;
1322 nsAutoString id;
1324 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
1325 nsAtomString attrstr(aAttribute);
1327 nsAutoString valuestr;
1328 aElement->GetAttr(kNameSpaceID_None, aAttribute, valuestr);
1330 nsAutoCString utf8uri;
1331 nsresult rv = mDocumentURI->GetSpec(utf8uri);
1332 if (NS_WARN_IF(NS_FAILED(rv))) {
1333 return rv;
1335 NS_ConvertUTF8toUTF16 uri(utf8uri);
1337 bool hasAttr;
1338 rv = mLocalStore->HasValue(uri, id, attrstr, &hasAttr);
1339 if (NS_WARN_IF(NS_FAILED(rv))) {
1340 return rv;
1343 if (hasAttr && valuestr.IsEmpty()) {
1344 return mLocalStore->RemoveValue(uri, id, attrstr);
1345 } else {
1346 return mLocalStore->SetValue(uri, id, attrstr, valuestr);
1351 nsresult
1352 XULDocument::GetViewportSize(int32_t* aWidth,
1353 int32_t* aHeight)
1355 *aWidth = *aHeight = 0;
1357 FlushPendingNotifications(Flush_Layout);
1359 nsIPresShell *shell = GetShell();
1360 NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
1362 nsIFrame* frame = shell->GetRootFrame();
1363 NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
1365 nsSize size = frame->GetSize();
1367 *aWidth = nsPresContext::AppUnitsToIntCSSPixels(size.width);
1368 *aHeight = nsPresContext::AppUnitsToIntCSSPixels(size.height);
1370 return NS_OK;
1373 NS_IMETHODIMP
1374 XULDocument::GetWidth(int32_t* aWidth)
1376 NS_ENSURE_ARG_POINTER(aWidth);
1378 int32_t height;
1379 return GetViewportSize(aWidth, &height);
1382 int32_t
1383 XULDocument::GetWidth(ErrorResult& aRv)
1385 int32_t width;
1386 aRv = GetWidth(&width);
1387 return width;
1390 NS_IMETHODIMP
1391 XULDocument::GetHeight(int32_t* aHeight)
1393 NS_ENSURE_ARG_POINTER(aHeight);
1395 int32_t width;
1396 return GetViewportSize(&width, aHeight);
1399 int32_t
1400 XULDocument::GetHeight(ErrorResult& aRv)
1402 int32_t height;
1403 aRv = GetHeight(&height);
1404 return height;
1407 JSObject*
1408 GetScopeObjectOfNode(nsIDOMNode* node)
1410 MOZ_ASSERT(node, "Must not be called with null.");
1412 // Window root occasionally keeps alive a node of a document whose
1413 // window is already dead. If in this brief period someone calls
1414 // GetPopupNode and we return that node, nsNodeSH::PreCreate will throw,
1415 // because it will not know which scope this node belongs to. Returning
1416 // an orphan node like that to JS would be a bug anyway, so to avoid
1417 // this, let's do the same check as nsNodeSH::PreCreate does to
1418 // determine the scope and if it fails let's just return null in
1419 // XULDocument::GetPopupNode.
1420 nsCOMPtr<nsINode> inode = do_QueryInterface(node);
1421 MOZ_ASSERT(inode, "How can this happen?");
1423 nsIDocument* doc = inode->OwnerDoc();
1424 MOZ_ASSERT(inode, "This should never happen.");
1426 nsIGlobalObject* global = doc->GetScopeObject();
1427 return global ? global->GetGlobalJSObject() : nullptr;
1430 //----------------------------------------------------------------------
1432 // nsIDOMXULDocument interface
1435 NS_IMETHODIMP
1436 XULDocument::GetPopupNode(nsIDOMNode** aNode)
1438 *aNode = nullptr;
1440 nsCOMPtr<nsIDOMNode> node;
1441 nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
1442 if (rootWin)
1443 node = rootWin->GetPopupNode(); // addref happens here
1445 if (!node) {
1446 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1447 if (pm) {
1448 node = pm->GetLastTriggerPopupNode(this);
1452 if (node && nsContentUtils::CanCallerAccess(node)
1453 && GetScopeObjectOfNode(node)) {
1454 node.swap(*aNode);
1457 return NS_OK;
1460 already_AddRefed<nsINode>
1461 XULDocument::GetPopupNode()
1463 nsCOMPtr<nsIDOMNode> node;
1464 DebugOnly<nsresult> rv = GetPopupNode(getter_AddRefs(node));
1465 MOZ_ASSERT(NS_SUCCEEDED(rv));
1466 nsCOMPtr<nsINode> retval(do_QueryInterface(node));
1467 return retval.forget();
1470 NS_IMETHODIMP
1471 XULDocument::SetPopupNode(nsIDOMNode* aNode)
1473 if (aNode) {
1474 // only allow real node objects
1475 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
1476 NS_ENSURE_ARG(node);
1479 nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
1480 if (rootWin)
1481 rootWin->SetPopupNode(aNode); // addref happens here
1483 return NS_OK;
1486 void
1487 XULDocument::SetPopupNode(nsINode* aNode)
1489 nsCOMPtr<nsIDOMNode> node(do_QueryInterface(aNode));
1490 DebugOnly<nsresult> rv = SetPopupNode(node);
1491 MOZ_ASSERT(NS_SUCCEEDED(rv));
1494 // Returns the rangeOffset element from the XUL Popup Manager. This is for
1495 // chrome callers only.
1496 NS_IMETHODIMP
1497 XULDocument::GetPopupRangeParent(nsIDOMNode** aRangeParent)
1499 NS_ENSURE_ARG_POINTER(aRangeParent);
1500 *aRangeParent = nullptr;
1502 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1503 if (!pm)
1504 return NS_ERROR_FAILURE;
1506 int32_t offset;
1507 pm->GetMouseLocation(aRangeParent, &offset);
1509 if (*aRangeParent && !nsContentUtils::CanCallerAccess(*aRangeParent)) {
1510 NS_RELEASE(*aRangeParent);
1511 return NS_ERROR_DOM_SECURITY_ERR;
1514 return NS_OK;
1517 already_AddRefed<nsINode>
1518 XULDocument::GetPopupRangeParent(ErrorResult& aRv)
1520 nsCOMPtr<nsIDOMNode> node;
1521 aRv = GetPopupRangeParent(getter_AddRefs(node));
1522 nsCOMPtr<nsINode> retval(do_QueryInterface(node));
1523 return retval.forget();
1527 // Returns the rangeOffset element from the XUL Popup Manager. We check the
1528 // rangeParent to determine if the caller has rights to access to the data.
1529 NS_IMETHODIMP
1530 XULDocument::GetPopupRangeOffset(int32_t* aRangeOffset)
1532 ErrorResult rv;
1533 *aRangeOffset = GetPopupRangeOffset(rv);
1534 return rv.ErrorCode();
1537 int32_t
1538 XULDocument::GetPopupRangeOffset(ErrorResult& aRv)
1540 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1541 if (!pm) {
1542 aRv.Throw(NS_ERROR_FAILURE);
1543 return 0;
1546 int32_t offset;
1547 nsCOMPtr<nsIDOMNode> parent;
1548 pm->GetMouseLocation(getter_AddRefs(parent), &offset);
1550 if (parent && !nsContentUtils::CanCallerAccess(parent)) {
1551 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
1552 return 0;
1554 return offset;
1557 NS_IMETHODIMP
1558 XULDocument::GetTooltipNode(nsIDOMNode** aNode)
1560 *aNode = nullptr;
1562 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1563 if (pm) {
1564 nsCOMPtr<nsIDOMNode> node = pm->GetLastTriggerTooltipNode(this);
1565 if (node && nsContentUtils::CanCallerAccess(node))
1566 node.swap(*aNode);
1569 return NS_OK;
1572 already_AddRefed<nsINode>
1573 XULDocument::GetTooltipNode()
1575 nsCOMPtr<nsIDOMNode> node;
1576 DebugOnly<nsresult> rv = GetTooltipNode(getter_AddRefs(node));
1577 MOZ_ASSERT(NS_SUCCEEDED(rv));
1578 nsCOMPtr<nsINode> retval(do_QueryInterface(node));
1579 return retval.forget();
1582 NS_IMETHODIMP
1583 XULDocument::SetTooltipNode(nsIDOMNode* aNode)
1585 // do nothing
1586 return NS_OK;
1590 NS_IMETHODIMP
1591 XULDocument::GetCommandDispatcher(nsIDOMXULCommandDispatcher** aTracker)
1593 *aTracker = mCommandDispatcher;
1594 NS_IF_ADDREF(*aTracker);
1595 return NS_OK;
1598 Element*
1599 XULDocument::GetElementById(const nsAString& aId)
1601 if (!CheckGetElementByIdArg(aId))
1602 return nullptr;
1604 nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aId);
1605 if (entry) {
1606 Element* element = entry->GetIdElement();
1607 if (element)
1608 return element;
1611 nsRefMapEntry* refEntry = mRefMap.GetEntry(aId);
1612 if (refEntry) {
1613 NS_ASSERTION(refEntry->GetFirstElement(),
1614 "nsRefMapEntries should have nonempty content lists");
1615 return refEntry->GetFirstElement();
1617 return nullptr;
1620 nsresult
1621 XULDocument::AddElementToDocumentPre(Element* aElement)
1623 // Do a bunch of work that's necessary when an element gets added
1624 // to the XUL Document.
1625 nsresult rv;
1627 // 1. Add the element to the resource-to-element map. Also add it to
1628 // the id map, since it seems this can be called when creating
1629 // elements from prototypes.
1630 nsIAtom* id = aElement->GetID();
1631 if (id) {
1632 // FIXME: Shouldn't BindToTree take care of this?
1633 nsAutoScriptBlocker scriptBlocker;
1634 AddToIdTable(aElement, id);
1636 rv = AddElementToRefMap(aElement);
1637 if (NS_FAILED(rv)) return rv;
1639 // 2. If the element is a 'command updater' (i.e., has a
1640 // "commandupdater='true'" attribute), then add the element to the
1641 // document's command dispatcher
1642 if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::commandupdater,
1643 nsGkAtoms::_true, eCaseMatters)) {
1644 rv = nsXULContentUtils::SetCommandUpdater(this, aElement);
1645 if (NS_FAILED(rv)) return rv;
1648 // 3. Check for a broadcaster hookup attribute, in which case
1649 // we'll hook the node up as a listener on a broadcaster.
1650 bool listener, resolved;
1651 rv = CheckBroadcasterHookup(aElement, &listener, &resolved);
1652 if (NS_FAILED(rv)) return rv;
1654 // If it's not there yet, we may be able to defer hookup until
1655 // later.
1656 if (listener && !resolved && (mResolutionPhase != nsForwardReference::eDone)) {
1657 BroadcasterHookup* hookup = new BroadcasterHookup(this, aElement);
1658 if (! hookup)
1659 return NS_ERROR_OUT_OF_MEMORY;
1661 rv = AddForwardReference(hookup);
1662 if (NS_FAILED(rv)) return rv;
1665 return NS_OK;
1668 nsresult
1669 XULDocument::AddElementToDocumentPost(Element* aElement)
1671 // We need to pay special attention to the keyset tag to set up a listener
1672 if (aElement->NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
1673 // Create our XUL key listener and hook it up.
1674 nsXBLService::AttachGlobalKeyHandler(aElement);
1677 // See if we need to attach a XUL template to this node
1678 bool needsHookup;
1679 nsresult rv = CheckTemplateBuilderHookup(aElement, &needsHookup);
1680 if (NS_FAILED(rv))
1681 return rv;
1683 if (needsHookup) {
1684 if (mResolutionPhase == nsForwardReference::eDone) {
1685 rv = CreateTemplateBuilder(aElement);
1686 if (NS_FAILED(rv))
1687 return rv;
1689 else {
1690 TemplateBuilderHookup* hookup = new TemplateBuilderHookup(aElement);
1691 if (! hookup)
1692 return NS_ERROR_OUT_OF_MEMORY;
1694 rv = AddForwardReference(hookup);
1695 if (NS_FAILED(rv))
1696 return rv;
1700 return NS_OK;
1703 NS_IMETHODIMP
1704 XULDocument::AddSubtreeToDocument(nsIContent* aContent)
1706 NS_ASSERTION(aContent->GetUncomposedDoc() == this, "Element not in doc!");
1707 // From here on we only care about elements.
1708 if (!aContent->IsElement()) {
1709 return NS_OK;
1712 Element* aElement = aContent->AsElement();
1714 // Do pre-order addition magic
1715 nsresult rv = AddElementToDocumentPre(aElement);
1716 if (NS_FAILED(rv)) return rv;
1718 // Recurse to children
1719 for (nsIContent* child = aElement->GetLastChild();
1720 child;
1721 child = child->GetPreviousSibling()) {
1723 rv = AddSubtreeToDocument(child);
1724 if (NS_FAILED(rv))
1725 return rv;
1728 // Do post-order addition magic
1729 return AddElementToDocumentPost(aElement);
1732 NS_IMETHODIMP
1733 XULDocument::RemoveSubtreeFromDocument(nsIContent* aContent)
1735 // From here on we only care about elements.
1736 if (!aContent->IsElement()) {
1737 return NS_OK;
1740 Element* aElement = aContent->AsElement();
1742 // Do a bunch of cleanup to remove an element from the XUL
1743 // document.
1744 nsresult rv;
1746 if (aElement->NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
1747 nsXBLService::DetachGlobalKeyHandler(aElement);
1750 // 1. Remove any children from the document.
1751 for (nsIContent* child = aElement->GetLastChild();
1752 child;
1753 child = child->GetPreviousSibling()) {
1755 rv = RemoveSubtreeFromDocument(child);
1756 if (NS_FAILED(rv))
1757 return rv;
1760 // 2. Remove the element from the resource-to-element map.
1761 // Also remove it from the id map, since we added it in
1762 // AddElementToDocumentPre().
1763 RemoveElementFromRefMap(aElement);
1764 nsIAtom* id = aElement->GetID();
1765 if (id) {
1766 // FIXME: Shouldn't UnbindFromTree take care of this?
1767 nsAutoScriptBlocker scriptBlocker;
1768 RemoveFromIdTable(aElement, id);
1771 // 3. If the element is a 'command updater', then remove the
1772 // element from the document's command dispatcher.
1773 if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::commandupdater,
1774 nsGkAtoms::_true, eCaseMatters)) {
1775 nsCOMPtr<nsIDOMElement> domelement = do_QueryInterface(aElement);
1776 NS_ASSERTION(domelement != nullptr, "not a DOM element");
1777 if (! domelement)
1778 return NS_ERROR_UNEXPECTED;
1780 rv = mCommandDispatcher->RemoveCommandUpdater(domelement);
1781 if (NS_FAILED(rv)) return rv;
1784 // 4. Remove the element from our broadcaster map, since it is no longer
1785 // in the document.
1786 nsCOMPtr<Element> broadcaster, listener;
1787 nsAutoString attribute, broadcasterID;
1788 rv = FindBroadcaster(aElement, getter_AddRefs(listener),
1789 broadcasterID, attribute, getter_AddRefs(broadcaster));
1790 if (rv == NS_FINDBROADCASTER_FOUND) {
1791 RemoveBroadcastListenerFor(*broadcaster, *listener, attribute);
1794 return NS_OK;
1797 NS_IMETHODIMP
1798 XULDocument::SetTemplateBuilderFor(nsIContent* aContent,
1799 nsIXULTemplateBuilder* aBuilder)
1801 if (! mTemplateBuilderTable) {
1802 if (!aBuilder) {
1803 return NS_OK;
1805 mTemplateBuilderTable = new BuilderTable;
1808 if (aBuilder) {
1809 mTemplateBuilderTable->Put(aContent, aBuilder);
1811 else {
1812 mTemplateBuilderTable->Remove(aContent);
1815 return NS_OK;
1818 NS_IMETHODIMP
1819 XULDocument::GetTemplateBuilderFor(nsIContent* aContent,
1820 nsIXULTemplateBuilder** aResult)
1822 if (mTemplateBuilderTable) {
1823 mTemplateBuilderTable->Get(aContent, aResult);
1825 else
1826 *aResult = nullptr;
1828 return NS_OK;
1831 static void
1832 GetRefMapAttribute(Element* aElement, nsAutoString* aValue)
1834 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::ref, *aValue);
1837 nsresult
1838 XULDocument::AddElementToRefMap(Element* aElement)
1840 // Look at the element's 'ref' attribute, and if set,
1841 // add an entry in the resource-to-element map to the element.
1842 nsAutoString value;
1843 GetRefMapAttribute(aElement, &value);
1844 if (!value.IsEmpty()) {
1845 nsRefMapEntry *entry = mRefMap.PutEntry(value);
1846 if (!entry)
1847 return NS_ERROR_OUT_OF_MEMORY;
1848 if (!entry->AddElement(aElement))
1849 return NS_ERROR_OUT_OF_MEMORY;
1852 return NS_OK;
1855 void
1856 XULDocument::RemoveElementFromRefMap(Element* aElement)
1858 // Remove the element from the resource-to-element map.
1859 nsAutoString value;
1860 GetRefMapAttribute(aElement, &value);
1861 if (!value.IsEmpty()) {
1862 nsRefMapEntry *entry = mRefMap.GetEntry(value);
1863 if (!entry)
1864 return;
1865 if (entry->RemoveElement(aElement)) {
1866 mRefMap.RawRemoveEntry(entry);
1871 //----------------------------------------------------------------------
1873 // nsIDOMNode interface
1876 nsresult
1877 XULDocument::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const
1879 // We don't allow cloning of a XUL document
1880 *aResult = nullptr;
1881 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
1885 //----------------------------------------------------------------------
1887 // Implementation methods
1890 nsresult
1891 XULDocument::Init()
1893 nsresult rv = XMLDocument::Init();
1894 NS_ENSURE_SUCCESS(rv, rv);
1896 // Create our command dispatcher and hook it up.
1897 mCommandDispatcher = new nsXULCommandDispatcher(this);
1898 NS_ENSURE_TRUE(mCommandDispatcher, NS_ERROR_OUT_OF_MEMORY);
1900 if (gRefCnt++ == 0) {
1901 // ensure that the XUL prototype cache is instantiated successfully,
1902 // so that we can use nsXULPrototypeCache::GetInstance() without
1903 // null-checks in the rest of the class.
1904 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
1905 if (!cache) {
1906 NS_ERROR("Could not instantiate nsXULPrototypeCache");
1907 return NS_ERROR_FAILURE;
1911 Preferences::RegisterCallback(XULDocument::DirectionChanged,
1912 "intl.uidirection.", this);
1914 #ifdef PR_LOGGING
1915 if (! gXULLog)
1916 gXULLog = PR_NewLogModule("XULDocument");
1917 #endif
1919 return NS_OK;
1923 nsresult
1924 XULDocument::StartLayout(void)
1926 mMayStartLayout = true;
1927 nsCOMPtr<nsIPresShell> shell = GetShell();
1928 if (shell) {
1929 // Resize-reflow this time
1930 nsPresContext *cx = shell->GetPresContext();
1931 NS_ASSERTION(cx != nullptr, "no pres context");
1932 if (! cx)
1933 return NS_ERROR_UNEXPECTED;
1935 nsCOMPtr<nsIDocShell> docShell = cx->GetDocShell();
1936 NS_ASSERTION(docShell != nullptr, "container is not a docshell");
1937 if (! docShell)
1938 return NS_ERROR_UNEXPECTED;
1940 nsresult rv = NS_OK;
1941 nsRect r = cx->GetVisibleArea();
1942 rv = shell->Initialize(r.width, r.height);
1943 NS_ENSURE_SUCCESS(rv, rv);
1946 return NS_OK;
1949 /* static */
1950 bool
1951 XULDocument::MatchAttribute(nsIContent* aContent,
1952 int32_t aNamespaceID,
1953 nsIAtom* aAttrName,
1954 void* aData)
1956 NS_PRECONDITION(aContent, "Must have content node to work with!");
1957 nsString* attrValue = static_cast<nsString*>(aData);
1958 if (aNamespaceID != kNameSpaceID_Unknown &&
1959 aNamespaceID != kNameSpaceID_Wildcard) {
1960 return attrValue->EqualsLiteral("*") ?
1961 aContent->HasAttr(aNamespaceID, aAttrName) :
1962 aContent->AttrValueIs(aNamespaceID, aAttrName, *attrValue,
1963 eCaseMatters);
1966 // Qualified name match. This takes more work.
1968 uint32_t count = aContent->GetAttrCount();
1969 for (uint32_t i = 0; i < count; ++i) {
1970 const nsAttrName* name = aContent->GetAttrNameAt(i);
1971 bool nameMatch;
1972 if (name->IsAtom()) {
1973 nameMatch = name->Atom() == aAttrName;
1974 } else if (aNamespaceID == kNameSpaceID_Wildcard) {
1975 nameMatch = name->NodeInfo()->Equals(aAttrName);
1976 } else {
1977 nameMatch = name->NodeInfo()->QualifiedNameEquals(aAttrName);
1980 if (nameMatch) {
1981 return attrValue->EqualsLiteral("*") ||
1982 aContent->AttrValueIs(name->NamespaceID(), name->LocalName(),
1983 *attrValue, eCaseMatters);
1987 return false;
1990 nsresult
1991 XULDocument::PrepareToLoad(nsISupports* aContainer,
1992 const char* aCommand,
1993 nsIChannel* aChannel,
1994 nsILoadGroup* aLoadGroup,
1995 nsIParser** aResult)
1997 // Get the document's principal
1998 nsCOMPtr<nsIPrincipal> principal;
1999 nsContentUtils::GetSecurityManager()->
2000 GetChannelResultPrincipal(aChannel, getter_AddRefs(principal));
2001 return PrepareToLoadPrototype(mDocumentURI, aCommand, principal, aResult);
2005 nsresult
2006 XULDocument::PrepareToLoadPrototype(nsIURI* aURI, const char* aCommand,
2007 nsIPrincipal* aDocumentPrincipal,
2008 nsIParser** aResult)
2010 nsresult rv;
2012 // Create a new prototype document.
2013 rv = NS_NewXULPrototypeDocument(getter_AddRefs(mCurrentPrototype));
2014 if (NS_FAILED(rv)) return rv;
2016 rv = mCurrentPrototype->InitPrincipal(aURI, aDocumentPrincipal);
2017 if (NS_FAILED(rv)) {
2018 mCurrentPrototype = nullptr;
2019 return rv;
2022 // Bootstrap the master document prototype.
2023 if (! mMasterPrototype) {
2024 mMasterPrototype = mCurrentPrototype;
2025 // Set our principal based on the master proto.
2026 SetPrincipal(aDocumentPrincipal);
2029 // Create a XUL content sink, a parser, and kick off a load for
2030 // the overlay.
2031 nsRefPtr<XULContentSinkImpl> sink = new XULContentSinkImpl();
2032 if (!sink) return NS_ERROR_OUT_OF_MEMORY;
2034 rv = sink->Init(this, mCurrentPrototype);
2035 NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to initialize datasource sink");
2036 if (NS_FAILED(rv)) return rv;
2038 nsCOMPtr<nsIParser> parser = do_CreateInstance(kParserCID, &rv);
2039 NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create parser");
2040 if (NS_FAILED(rv)) return rv;
2042 parser->SetCommand(nsCRT::strcmp(aCommand, "view-source") ? eViewNormal :
2043 eViewSource);
2045 parser->SetDocumentCharset(NS_LITERAL_CSTRING("UTF-8"),
2046 kCharsetFromDocTypeDefault);
2047 parser->SetContentSink(sink); // grabs a reference to the parser
2049 *aResult = parser;
2050 NS_ADDREF(*aResult);
2051 return NS_OK;
2055 nsresult
2056 XULDocument::ApplyPersistentAttributes()
2058 // For non-chrome documents, persistance is simply broken
2059 if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()))
2060 return NS_ERROR_NOT_AVAILABLE;
2062 // Add all of the 'persisted' attributes into the content
2063 // model.
2064 if (!mLocalStore) {
2065 mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
2066 if (NS_WARN_IF(!mLocalStore)) {
2067 return NS_ERROR_NOT_INITIALIZED;
2071 mApplyingPersistedAttrs = true;
2072 ApplyPersistentAttributesInternal();
2073 mApplyingPersistedAttrs = false;
2075 // After we've applied persistence once, we should only reapply
2076 // it to nodes created by overlays
2077 mRestrictPersistence = true;
2078 mPersistenceIds.Clear();
2080 return NS_OK;
2084 nsresult
2085 XULDocument::ApplyPersistentAttributesInternal()
2087 nsCOMArray<nsIContent> elements;
2089 nsAutoCString utf8uri;
2090 nsresult rv = mDocumentURI->GetSpec(utf8uri);
2091 if (NS_WARN_IF(NS_FAILED(rv))) {
2092 return rv;
2094 NS_ConvertUTF8toUTF16 uri(utf8uri);
2096 // Get a list of element IDs for which persisted values are available
2097 nsCOMPtr<nsIStringEnumerator> ids;
2098 rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids));
2099 if (NS_WARN_IF(NS_FAILED(rv))) {
2100 return rv;
2103 while (1) {
2104 bool hasmore = false;
2105 ids->HasMore(&hasmore);
2106 if (!hasmore) {
2107 break;
2110 nsAutoString id;
2111 ids->GetNext(id);
2113 if (mRestrictPersistence && !mPersistenceIds.Contains(id)) {
2114 continue;
2117 // This will clear the array if there are no elements.
2118 GetElementsForID(id, elements);
2119 if (!elements.Count()) {
2120 continue;
2123 rv = ApplyPersistentAttributesToElements(id, elements);
2124 if (NS_WARN_IF(NS_FAILED(rv))) {
2125 return rv;
2129 return NS_OK;
2133 nsresult
2134 XULDocument::ApplyPersistentAttributesToElements(const nsAString &aID,
2135 nsCOMArray<nsIContent>& aElements)
2137 nsAutoCString utf8uri;
2138 nsresult rv = mDocumentURI->GetSpec(utf8uri);
2139 if (NS_WARN_IF(NS_FAILED(rv))) {
2140 return rv;
2142 NS_ConvertUTF8toUTF16 uri(utf8uri);
2144 // Get a list of attributes for which persisted values are available
2145 nsCOMPtr<nsIStringEnumerator> attrs;
2146 rv = mLocalStore->GetAttributeEnumerator(uri, aID, getter_AddRefs(attrs));
2147 if (NS_WARN_IF(NS_FAILED(rv))) {
2148 return rv;
2151 while (1) {
2152 bool hasmore = PR_FALSE;
2153 attrs->HasMore(&hasmore);
2154 if (!hasmore) {
2155 break;
2158 nsAutoString attrstr;
2159 attrs->GetNext(attrstr);
2161 nsAutoString value;
2162 rv = mLocalStore->GetValue(uri, aID, attrstr, value);
2163 if (NS_WARN_IF(NS_FAILED(rv))) {
2164 return rv;
2167 nsCOMPtr<nsIAtom> attr = do_GetAtom(attrstr);
2168 if (NS_WARN_IF(!attr)) {
2169 return NS_ERROR_OUT_OF_MEMORY;
2172 uint32_t cnt = aElements.Count();
2174 for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
2175 nsCOMPtr<nsIContent> element = aElements.SafeObjectAt(i);
2176 if (!element) {
2177 continue;
2180 rv = element->SetAttr(kNameSpaceID_None, attr, value, PR_TRUE);
2184 return NS_OK;
2187 void
2188 XULDocument::TraceProtos(JSTracer* aTrc, uint32_t aGCNumber)
2190 uint32_t i, count = mPrototypes.Length();
2191 for (i = 0; i < count; ++i) {
2192 mPrototypes[i]->TraceProtos(aTrc, aGCNumber);
2196 //----------------------------------------------------------------------
2198 // XULDocument::ContextStack
2201 XULDocument::ContextStack::ContextStack()
2202 : mTop(nullptr), mDepth(0)
2206 XULDocument::ContextStack::~ContextStack()
2208 while (mTop) {
2209 Entry* doomed = mTop;
2210 mTop = mTop->mNext;
2211 NS_IF_RELEASE(doomed->mElement);
2212 delete doomed;
2216 nsresult
2217 XULDocument::ContextStack::Push(nsXULPrototypeElement* aPrototype,
2218 nsIContent* aElement)
2220 Entry* entry = new Entry;
2221 if (! entry)
2222 return NS_ERROR_OUT_OF_MEMORY;
2224 entry->mPrototype = aPrototype;
2225 entry->mElement = aElement;
2226 NS_IF_ADDREF(entry->mElement);
2227 entry->mIndex = 0;
2229 entry->mNext = mTop;
2230 mTop = entry;
2232 ++mDepth;
2233 return NS_OK;
2236 nsresult
2237 XULDocument::ContextStack::Pop()
2239 if (mDepth == 0)
2240 return NS_ERROR_UNEXPECTED;
2242 Entry* doomed = mTop;
2243 mTop = mTop->mNext;
2244 --mDepth;
2246 NS_IF_RELEASE(doomed->mElement);
2247 delete doomed;
2248 return NS_OK;
2251 nsresult
2252 XULDocument::ContextStack::Peek(nsXULPrototypeElement** aPrototype,
2253 nsIContent** aElement,
2254 int32_t* aIndex)
2256 if (mDepth == 0)
2257 return NS_ERROR_UNEXPECTED;
2259 *aPrototype = mTop->mPrototype;
2260 *aElement = mTop->mElement;
2261 NS_IF_ADDREF(*aElement);
2262 *aIndex = mTop->mIndex;
2264 return NS_OK;
2268 nsresult
2269 XULDocument::ContextStack::SetTopIndex(int32_t aIndex)
2271 if (mDepth == 0)
2272 return NS_ERROR_UNEXPECTED;
2274 mTop->mIndex = aIndex;
2275 return NS_OK;
2279 //----------------------------------------------------------------------
2281 // Content model walking routines
2284 nsresult
2285 XULDocument::PrepareToWalk()
2287 // Prepare to walk the mCurrentPrototype
2288 nsresult rv;
2290 // Keep an owning reference to the prototype document so that its
2291 // elements aren't yanked from beneath us.
2292 mPrototypes.AppendElement(mCurrentPrototype);
2294 // Get the prototype's root element and initialize the context
2295 // stack for the prototype walk.
2296 nsXULPrototypeElement* proto = mCurrentPrototype->GetRootElement();
2298 if (! proto) {
2299 #ifdef PR_LOGGING
2300 if (PR_LOG_TEST(gXULLog, PR_LOG_ERROR)) {
2301 nsCOMPtr<nsIURI> url = mCurrentPrototype->GetURI();
2303 nsAutoCString urlspec;
2304 rv = url->GetSpec(urlspec);
2305 if (NS_FAILED(rv)) return rv;
2307 PR_LOG(gXULLog, PR_LOG_ERROR,
2308 ("xul: error parsing '%s'", urlspec.get()));
2310 #endif
2312 return NS_OK;
2315 uint32_t piInsertionPoint = 0;
2316 if (mState != eState_Master) {
2317 int32_t indexOfRoot = IndexOf(GetRootElement());
2318 NS_ASSERTION(indexOfRoot >= 0,
2319 "No root content when preparing to walk overlay!");
2320 piInsertionPoint = indexOfRoot;
2323 const nsTArray<nsRefPtr<nsXULPrototypePI> >& processingInstructions =
2324 mCurrentPrototype->GetProcessingInstructions();
2326 uint32_t total = processingInstructions.Length();
2327 for (uint32_t i = 0; i < total; ++i) {
2328 rv = CreateAndInsertPI(processingInstructions[i],
2329 this, piInsertionPoint + i);
2330 if (NS_FAILED(rv)) return rv;
2333 // Now check the chrome registry for any additional overlays.
2334 rv = AddChromeOverlays();
2335 if (NS_FAILED(rv)) return rv;
2337 // Do one-time initialization if we're preparing to walk the
2338 // master document's prototype.
2339 nsRefPtr<Element> root;
2341 if (mState == eState_Master) {
2342 // Add the root element
2343 rv = CreateElementFromPrototype(proto, getter_AddRefs(root), true);
2344 if (NS_FAILED(rv)) return rv;
2346 rv = AppendChildTo(root, false);
2347 if (NS_FAILED(rv)) return rv;
2349 rv = AddElementToRefMap(root);
2350 if (NS_FAILED(rv)) return rv;
2352 // Block onload until we've finished building the complete
2353 // document content model.
2354 BlockOnload();
2357 // There'd better not be anything on the context stack at this
2358 // point! This is the basis case for our "induction" in
2359 // ResumeWalk(), below, which'll assume that there's always a
2360 // content element on the context stack if either 1) we're in the
2361 // "master" document, or 2) we're in an overlay, and we've got
2362 // more than one prototype element (the single, root "overlay"
2363 // element) on the stack.
2364 NS_ASSERTION(mContextStack.Depth() == 0, "something's on the context stack already");
2365 if (mContextStack.Depth() != 0)
2366 return NS_ERROR_UNEXPECTED;
2368 rv = mContextStack.Push(proto, root);
2369 if (NS_FAILED(rv)) return rv;
2371 return NS_OK;
2374 nsresult
2375 XULDocument::CreateAndInsertPI(const nsXULPrototypePI* aProtoPI,
2376 nsINode* aParent, uint32_t aIndex)
2378 NS_PRECONDITION(aProtoPI, "null ptr");
2379 NS_PRECONDITION(aParent, "null ptr");
2381 nsRefPtr<ProcessingInstruction> node =
2382 NS_NewXMLProcessingInstruction(mNodeInfoManager, aProtoPI->mTarget,
2383 aProtoPI->mData);
2385 nsresult rv;
2386 if (aProtoPI->mTarget.EqualsLiteral("xml-stylesheet")) {
2387 rv = InsertXMLStylesheetPI(aProtoPI, aParent, aIndex, node);
2388 } else if (aProtoPI->mTarget.EqualsLiteral("xul-overlay")) {
2389 rv = InsertXULOverlayPI(aProtoPI, aParent, aIndex, node);
2390 } else {
2391 // No special processing, just add the PI to the document.
2392 rv = aParent->InsertChildAt(node, aIndex, false);
2395 return rv;
2398 nsresult
2399 XULDocument::InsertXMLStylesheetPI(const nsXULPrototypePI* aProtoPI,
2400 nsINode* aParent,
2401 uint32_t aIndex,
2402 nsIContent* aPINode)
2404 nsCOMPtr<nsIStyleSheetLinkingElement> ssle(do_QueryInterface(aPINode));
2405 NS_ASSERTION(ssle, "passed XML Stylesheet node does not "
2406 "implement nsIStyleSheetLinkingElement!");
2408 nsresult rv;
2410 ssle->InitStyleLinkElement(false);
2411 // We want to be notified when the style sheet finishes loading, so
2412 // disable style sheet loading for now.
2413 ssle->SetEnableUpdates(false);
2414 ssle->OverrideBaseURI(mCurrentPrototype->GetURI());
2416 rv = aParent->InsertChildAt(aPINode, aIndex, false);
2417 if (NS_FAILED(rv)) return rv;
2419 ssle->SetEnableUpdates(true);
2421 // load the stylesheet if necessary, passing ourselves as
2422 // nsICSSObserver
2423 bool willNotify;
2424 bool isAlternate;
2425 rv = ssle->UpdateStyleSheet(this, &willNotify, &isAlternate);
2426 if (NS_SUCCEEDED(rv) && willNotify && !isAlternate) {
2427 ++mPendingSheets;
2430 // Ignore errors from UpdateStyleSheet; we don't want failure to
2431 // do that to break the XUL document load. But do propagate out
2432 // NS_ERROR_OUT_OF_MEMORY.
2433 if (rv == NS_ERROR_OUT_OF_MEMORY) {
2434 return rv;
2437 return NS_OK;
2440 nsresult
2441 XULDocument::InsertXULOverlayPI(const nsXULPrototypePI* aProtoPI,
2442 nsINode* aParent,
2443 uint32_t aIndex,
2444 nsIContent* aPINode)
2446 nsresult rv;
2448 rv = aParent->InsertChildAt(aPINode, aIndex, false);
2449 if (NS_FAILED(rv)) return rv;
2451 // xul-overlay PI is special only in prolog
2452 if (!nsContentUtils::InProlog(aPINode)) {
2453 return NS_OK;
2456 nsAutoString href;
2457 nsContentUtils::GetPseudoAttributeValue(aProtoPI->mData,
2458 nsGkAtoms::href,
2459 href);
2461 // If there was no href, we can't do anything with this PI
2462 if (href.IsEmpty()) {
2463 return NS_OK;
2466 // Add the overlay to our list of overlays that need to be processed.
2467 nsCOMPtr<nsIURI> uri;
2469 rv = NS_NewURI(getter_AddRefs(uri), href, nullptr,
2470 mCurrentPrototype->GetURI());
2471 if (NS_SUCCEEDED(rv)) {
2472 // We insert overlays into mUnloadedOverlays at the same index in
2473 // document order, so they end up in the reverse of the document
2474 // order in mUnloadedOverlays.
2475 // This is needed because the code in ResumeWalk loads the overlays
2476 // by processing the last item of mUnloadedOverlays and removing it
2477 // from the array.
2478 mUnloadedOverlays.InsertElementAt(0, uri);
2479 rv = NS_OK;
2480 } else if (rv == NS_ERROR_MALFORMED_URI) {
2481 // The URL is bad, move along. Don't propagate for now.
2482 // XXX report this to the Error Console (bug 359846)
2483 rv = NS_OK;
2486 return rv;
2489 nsresult
2490 XULDocument::AddChromeOverlays()
2492 nsresult rv;
2494 nsCOMPtr<nsIURI> docUri = mCurrentPrototype->GetURI();
2496 /* overlays only apply to chrome or about URIs */
2497 if (!IsOverlayAllowed(docUri)) return NS_OK;
2499 nsCOMPtr<nsIXULOverlayProvider> chromeReg =
2500 mozilla::services::GetXULOverlayProviderService();
2501 // In embedding situations, the chrome registry may not provide overlays,
2502 // or even exist at all; that's OK.
2503 NS_ENSURE_TRUE(chromeReg, NS_OK);
2505 nsCOMPtr<nsISimpleEnumerator> overlays;
2506 rv = chromeReg->GetXULOverlays(docUri, getter_AddRefs(overlays));
2507 NS_ENSURE_SUCCESS(rv, rv);
2509 bool moreOverlays;
2510 nsCOMPtr<nsISupports> next;
2511 nsCOMPtr<nsIURI> uri;
2513 while (NS_SUCCEEDED(rv = overlays->HasMoreElements(&moreOverlays)) &&
2514 moreOverlays) {
2516 rv = overlays->GetNext(getter_AddRefs(next));
2517 if (NS_FAILED(rv) || !next) break;
2519 uri = do_QueryInterface(next);
2520 if (!uri) {
2521 NS_ERROR("Chrome registry handed me a non-nsIURI object!");
2522 continue;
2525 // Same comment as in XULDocument::InsertXULOverlayPI
2526 mUnloadedOverlays.InsertElementAt(0, uri);
2529 return rv;
2532 NS_IMETHODIMP
2533 XULDocument::LoadOverlay(const nsAString& aURL, nsIObserver* aObserver)
2535 nsresult rv;
2537 nsCOMPtr<nsIURI> uri;
2538 rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr);
2539 if (NS_FAILED(rv)) return rv;
2541 if (aObserver) {
2542 nsIObserver* obs = nullptr;
2543 if (!mOverlayLoadObservers) {
2544 mOverlayLoadObservers = new nsInterfaceHashtable<nsURIHashKey,nsIObserver>;
2546 obs = mOverlayLoadObservers->GetWeak(uri);
2548 if (obs) {
2549 // We don't support loading the same overlay twice into the same
2550 // document - that doesn't make sense anyway.
2551 return NS_ERROR_FAILURE;
2553 mOverlayLoadObservers->Put(uri, aObserver);
2555 bool shouldReturn, failureFromContent;
2556 rv = LoadOverlayInternal(uri, true, &shouldReturn, &failureFromContent);
2557 if (NS_FAILED(rv) && mOverlayLoadObservers)
2558 mOverlayLoadObservers->Remove(uri); // remove the observer if LoadOverlayInternal generated an error
2559 return rv;
2562 nsresult
2563 XULDocument::LoadOverlayInternal(nsIURI* aURI, bool aIsDynamic,
2564 bool* aShouldReturn,
2565 bool* aFailureFromContent)
2567 nsresult rv;
2569 *aShouldReturn = false;
2570 *aFailureFromContent = false;
2572 #ifdef PR_LOGGING
2573 if (PR_LOG_TEST(gXULLog, PR_LOG_DEBUG)) {
2574 nsAutoCString urlspec;
2575 aURI->GetSpec(urlspec);
2576 nsAutoCString parentDoc;
2577 nsCOMPtr<nsIURI> uri;
2578 nsresult rv = mChannel->GetOriginalURI(getter_AddRefs(uri));
2579 if (NS_SUCCEEDED(rv))
2580 rv = uri->GetSpec(parentDoc);
2581 if (!(parentDoc.get()))
2582 parentDoc = "";
2584 PR_LOG(gXULLog, PR_LOG_DEBUG,
2585 ("xul: %s loading overlay %s", parentDoc.get(), urlspec.get()));
2587 #endif
2589 if (aIsDynamic)
2590 mResolutionPhase = nsForwardReference::eStart;
2592 // Chrome documents are allowed to load overlays from anywhere.
2593 // In all other cases, the overlay is only allowed to load if
2594 // the master document and prototype document have the same origin.
2596 bool documentIsChrome = IsChromeURI(mDocumentURI);
2597 if (!documentIsChrome) {
2598 // Make sure we're allowed to load this overlay.
2599 rv = NodePrincipal()->CheckMayLoad(aURI, true, false);
2600 if (NS_FAILED(rv)) {
2601 *aFailureFromContent = true;
2602 return rv;
2606 // Look in the prototype cache for the prototype document with
2607 // the specified overlay URI. Only use the cache if the containing
2608 // document is chrome otherwise it may not have a system principal and
2609 // the cached document will, see bug 565610.
2610 bool overlayIsChrome = IsChromeURI(aURI);
2611 mCurrentPrototype = overlayIsChrome && documentIsChrome ?
2612 nsXULPrototypeCache::GetInstance()->GetPrototype(aURI) : nullptr;
2614 // Same comment as nsChromeProtocolHandler::NewChannel and
2615 // XULDocument::StartDocumentLoad
2616 // - Ben Goodger
2618 // We don't abort on failure here because there are too many valid
2619 // cases that can return failure, and the null-ness of |proto| is
2620 // enough to trigger the fail-safe parse-from-disk solution.
2621 // Example failure cases (for reference) include:
2623 // NS_ERROR_NOT_AVAILABLE: the URI was not found in the FastLoad file,
2624 // parse from disk
2625 // other: the FastLoad file, XUL.mfl, could not be found, probably
2626 // due to being accessed before a profile has been selected
2627 // (e.g. loading chrome for the profile manager itself).
2628 // The .xul file must be parsed from disk.
2630 bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
2631 if (useXULCache && mCurrentPrototype) {
2632 bool loaded;
2633 rv = mCurrentPrototype->AwaitLoadDone(this, &loaded);
2634 if (NS_FAILED(rv)) return rv;
2636 if (! loaded) {
2637 // Return to the main event loop and eagerly await the
2638 // prototype overlay load's completion. When the content
2639 // sink completes, it will trigger an EndLoad(), which'll
2640 // wind us back up here, in ResumeWalk().
2641 *aShouldReturn = true;
2642 return NS_OK;
2645 PR_LOG(gXULLog, PR_LOG_DEBUG, ("xul: overlay was cached"));
2647 // Found the overlay's prototype in the cache, fully loaded. If
2648 // this is a dynamic overlay, this will call ResumeWalk.
2649 // Otherwise, we'll return to ResumeWalk, which called us.
2650 return OnPrototypeLoadDone(aIsDynamic);
2652 else {
2653 // Not there. Initiate a load.
2654 PR_LOG(gXULLog, PR_LOG_DEBUG, ("xul: overlay was not cached"));
2656 if (mIsGoingAway) {
2657 PR_LOG(gXULLog, PR_LOG_DEBUG, ("xul: ...and document already destroyed"));
2658 return NS_ERROR_NOT_AVAILABLE;
2661 // We'll set the right principal on the proto doc when we get
2662 // OnStartRequest from the parser, so just pass in a null principal for
2663 // now.
2664 nsCOMPtr<nsIParser> parser;
2665 rv = PrepareToLoadPrototype(aURI, "view", nullptr, getter_AddRefs(parser));
2666 if (NS_FAILED(rv)) return rv;
2668 // Predicate mIsWritingFastLoad on the XUL cache being enabled,
2669 // so we don't have to re-check whether the cache is enabled all
2670 // the time.
2671 mIsWritingFastLoad = useXULCache;
2673 nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(parser);
2674 if (! listener)
2675 return NS_ERROR_UNEXPECTED;
2677 // Add an observer to the parser; this'll get called when
2678 // Necko fires its On[Start|Stop]Request() notifications,
2679 // and will let us recover from a missing overlay.
2680 ParserObserver* parserObserver =
2681 new ParserObserver(this, mCurrentPrototype);
2682 if (! parserObserver)
2683 return NS_ERROR_OUT_OF_MEMORY;
2685 NS_ADDREF(parserObserver);
2686 parser->Parse(aURI, parserObserver);
2687 NS_RELEASE(parserObserver);
2689 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
2690 nsCOMPtr<nsIChannel> channel;
2691 // Set the owner of the channel to be our principal so
2692 // that the overlay's JSObjects etc end up being created
2693 // with the right principal and in the correct
2694 // compartment.
2695 rv = NS_NewChannel(getter_AddRefs(channel),
2696 aURI,
2697 NodePrincipal(),
2698 nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
2699 nsIContentPolicy::TYPE_OTHER,
2700 group);
2702 if (NS_SUCCEEDED(rv)) {
2703 rv = channel->AsyncOpen(listener, nullptr);
2706 if (NS_FAILED(rv)) {
2707 // Abandon this prototype
2708 mCurrentPrototype = nullptr;
2710 // The parser won't get an OnStartRequest and
2711 // OnStopRequest, so it needs a Terminate.
2712 parser->Terminate();
2714 // Just move on to the next overlay.
2715 ReportMissingOverlay(aURI);
2717 // XXX the error could indicate an internal error as well...
2718 *aFailureFromContent = true;
2719 return rv;
2722 // If it's a 'chrome:' prototype document, then put it into
2723 // the prototype cache; other XUL documents will be reloaded
2724 // each time. We must do this after AsyncOpen,
2725 // or chrome code will wrongly create a cached chrome channel
2726 // instead of a real one. Prototypes are only cached when the
2727 // document to be overlayed is chrome to avoid caching overlay
2728 // scripts with incorrect principals, see bug 565610.
2729 if (useXULCache && overlayIsChrome && documentIsChrome) {
2730 nsXULPrototypeCache::GetInstance()->PutPrototype(mCurrentPrototype);
2733 // Return to the main event loop and eagerly await the
2734 // overlay load's completion. When the content sink
2735 // completes, it will trigger an EndLoad(), which'll wind
2736 // us back in ResumeWalk().
2737 if (!aIsDynamic)
2738 *aShouldReturn = true;
2740 return NS_OK;
2743 static PLDHashOperator
2744 FirePendingMergeNotification(nsIURI* aKey, nsCOMPtr<nsIObserver>& aObserver, void* aClosure)
2746 aObserver->Observe(aKey, "xul-overlay-merged", EmptyString().get());
2748 typedef nsInterfaceHashtable<nsURIHashKey,nsIObserver> table;
2749 table* observers = static_cast<table*>(aClosure);
2750 if (observers) {
2751 observers->Remove(aKey);
2754 return PL_DHASH_REMOVE;
2757 nsresult
2758 XULDocument::ResumeWalk()
2760 // Walk the prototype and build the delegate content model. The
2761 // walk is performed in a top-down, left-to-right fashion. That
2762 // is, a parent is built before any of its children; a node is
2763 // only built after all of its siblings to the left are fully
2764 // constructed.
2766 // It is interruptable so that transcluded documents (e.g.,
2767 // <html:script src="..." />) can be properly re-loaded if the
2768 // cached copy of the document becomes stale.
2769 nsresult rv;
2770 nsCOMPtr<nsIURI> overlayURI =
2771 mCurrentPrototype ? mCurrentPrototype->GetURI() : nullptr;
2773 while (1) {
2774 // Begin (or resume) walking the current prototype.
2776 while (mContextStack.Depth() > 0) {
2777 // Look at the top of the stack to determine what we're
2778 // currently working on.
2779 // This will always be a node already constructed and
2780 // inserted to the actual document.
2781 nsXULPrototypeElement* proto;
2782 nsCOMPtr<nsIContent> element;
2783 int32_t indx; // all children of proto before indx (not
2784 // inclusive) have already been constructed
2785 rv = mContextStack.Peek(&proto, getter_AddRefs(element), &indx);
2786 if (NS_FAILED(rv)) return rv;
2788 if (indx >= (int32_t)proto->mChildren.Length()) {
2789 if (element) {
2790 // We've processed all of the prototype's children. If
2791 // we're in the master prototype, do post-order
2792 // document-level hookup. (An overlay will get its
2793 // document hookup done when it's successfully
2794 // resolved.)
2795 if (mState == eState_Master) {
2796 AddElementToDocumentPost(element->AsElement());
2798 if (element->NodeInfo()->Equals(nsGkAtoms::style,
2799 kNameSpaceID_XHTML) ||
2800 element->NodeInfo()->Equals(nsGkAtoms::style,
2801 kNameSpaceID_SVG)) {
2802 // XXX sucks that we have to do this -
2803 // see bug 370111
2804 nsCOMPtr<nsIStyleSheetLinkingElement> ssle =
2805 do_QueryInterface(element);
2806 NS_ASSERTION(ssle, "<html:style> doesn't implement "
2807 "nsIStyleSheetLinkingElement?");
2808 bool willNotify;
2809 bool isAlternate;
2810 ssle->UpdateStyleSheet(nullptr, &willNotify,
2811 &isAlternate);
2815 // Now pop the context stack back up to the parent
2816 // element and continue the prototype walk.
2817 mContextStack.Pop();
2818 continue;
2821 // Grab the next child, and advance the current context stack
2822 // to the next sibling to our right.
2823 nsXULPrototypeNode* childproto = proto->mChildren[indx];
2824 mContextStack.SetTopIndex(++indx);
2826 // Whether we're in the "first ply" of an overlay:
2827 // the "hookup" nodes. In the case !processingOverlayHookupNodes,
2828 // we're in the master document -or- we're in an overlay, and far
2829 // enough down into the overlay's content that we can simply build
2830 // the delegates and attach them to the parent node.
2831 bool processingOverlayHookupNodes = (mState == eState_Overlay) &&
2832 (mContextStack.Depth() == 1);
2834 NS_ASSERTION(element || processingOverlayHookupNodes,
2835 "no element on context stack");
2837 switch (childproto->mType) {
2838 case nsXULPrototypeNode::eType_Element: {
2839 // An 'element', which may contain more content.
2840 nsXULPrototypeElement* protoele =
2841 static_cast<nsXULPrototypeElement*>(childproto);
2843 nsRefPtr<Element> child;
2845 if (!processingOverlayHookupNodes) {
2846 rv = CreateElementFromPrototype(protoele,
2847 getter_AddRefs(child),
2848 false);
2849 if (NS_FAILED(rv)) return rv;
2851 // ...and append it to the content model.
2852 rv = element->AppendChildTo(child, false);
2853 if (NS_FAILED(rv)) return rv;
2855 // If we're only restoring persisted things on
2856 // some elements, store the ID here to do that.
2857 if (mRestrictPersistence) {
2858 nsIAtom* id = child->GetID();
2859 if (id) {
2860 mPersistenceIds.PutEntry(nsDependentAtomString(id));
2864 // do pre-order document-level hookup, but only if
2865 // we're in the master document. For an overlay,
2866 // this will happen when the overlay is
2867 // successfully resolved.
2868 if (mState == eState_Master)
2869 AddElementToDocumentPre(child);
2871 else {
2872 // We're in the "first ply" of an overlay: the
2873 // "hookup" nodes. Create an 'overlay' element so
2874 // that we can continue to build content, and
2875 // enter a forward reference so we can hook it up
2876 // later.
2877 rv = CreateOverlayElement(protoele, getter_AddRefs(child));
2878 if (NS_FAILED(rv)) return rv;
2881 // If it has children, push the element onto the context
2882 // stack and begin to process them.
2883 if (protoele->mChildren.Length() > 0) {
2884 rv = mContextStack.Push(protoele, child);
2885 if (NS_FAILED(rv)) return rv;
2887 else {
2888 if (mState == eState_Master) {
2889 // If there are no children, and we're in the
2890 // master document, do post-order document hookup
2891 // immediately.
2892 AddElementToDocumentPost(child);
2896 break;
2898 case nsXULPrototypeNode::eType_Script: {
2899 // A script reference. Execute the script immediately;
2900 // this may have side effects in the content model.
2901 nsXULPrototypeScript* scriptproto =
2902 static_cast<nsXULPrototypeScript*>(childproto);
2904 if (scriptproto->mSrcURI) {
2905 // A transcluded script reference; this may
2906 // "block" our prototype walk if the script isn't
2907 // cached, or the cached copy of the script is
2908 // stale and must be reloaded.
2909 bool blocked;
2910 rv = LoadScript(scriptproto, &blocked);
2911 // If the script cannot be loaded, just keep going!
2913 if (NS_SUCCEEDED(rv) && blocked)
2914 return NS_OK;
2916 else if (scriptproto->GetScriptObject()) {
2917 // An inline script
2918 rv = ExecuteScript(scriptproto);
2919 if (NS_FAILED(rv)) return rv;
2922 break;
2924 case nsXULPrototypeNode::eType_Text: {
2925 // A simple text node.
2927 if (!processingOverlayHookupNodes) {
2928 // This does mean that text nodes that are direct children
2929 // of <overlay> get ignored.
2931 nsRefPtr<nsTextNode> text =
2932 new nsTextNode(mNodeInfoManager);
2934 nsXULPrototypeText* textproto =
2935 static_cast<nsXULPrototypeText*>(childproto);
2936 text->SetText(textproto->mValue, false);
2938 rv = element->AppendChildTo(text, false);
2939 NS_ENSURE_SUCCESS(rv, rv);
2942 break;
2944 case nsXULPrototypeNode::eType_PI: {
2945 nsXULPrototypePI* piProto =
2946 static_cast<nsXULPrototypePI*>(childproto);
2948 // <?xul-overlay?> and <?xml-stylesheet?> don't have effect
2949 // outside the prolog, like they used to. Issue a warning.
2951 if (piProto->mTarget.EqualsLiteral("xml-stylesheet") ||
2952 piProto->mTarget.EqualsLiteral("xul-overlay")) {
2954 const char16_t* params[] = { piProto->mTarget.get() };
2956 nsContentUtils::ReportToConsole(
2957 nsIScriptError::warningFlag,
2958 NS_LITERAL_CSTRING("XUL Document"), nullptr,
2959 nsContentUtils::eXUL_PROPERTIES,
2960 "PINotInProlog",
2961 params, ArrayLength(params),
2962 overlayURI);
2965 nsIContent* parent = processingOverlayHookupNodes ?
2966 GetRootElement() : element.get();
2968 if (parent) {
2969 // an inline script could have removed the root element
2970 rv = CreateAndInsertPI(piProto, parent,
2971 parent->GetChildCount());
2972 NS_ENSURE_SUCCESS(rv, rv);
2975 break;
2977 default:
2978 NS_NOTREACHED("Unexpected nsXULPrototypeNode::Type value");
2982 // Once we get here, the context stack will have been
2983 // depleted. That means that the entire prototype has been
2984 // walked and content has been constructed.
2986 // If we're not already, mark us as now processing overlays.
2987 mState = eState_Overlay;
2989 // If there are no overlay URIs, then we're done.
2990 uint32_t count = mUnloadedOverlays.Length();
2991 if (! count)
2992 break;
2994 nsCOMPtr<nsIURI> uri = mUnloadedOverlays[count-1];
2995 mUnloadedOverlays.RemoveElementAt(count - 1);
2997 bool shouldReturn, failureFromContent;
2998 rv = LoadOverlayInternal(uri, false, &shouldReturn,
2999 &failureFromContent);
3000 if (failureFromContent)
3001 // The failure |rv| was the result of a problem in the content
3002 // rather than an unexpected problem in our implementation, so
3003 // just continue with the next overlay.
3004 continue;
3005 if (NS_FAILED(rv))
3006 return rv;
3007 if (mOverlayLoadObservers) {
3008 nsIObserver *obs = mOverlayLoadObservers->GetWeak(overlayURI);
3009 if (obs) {
3010 // This overlay has an unloaded overlay, so it will never
3011 // notify. The best we can do is to notify for the unloaded
3012 // overlay instead, assuming nobody is already notifiable
3013 // for it. Note that this will confuse the observer.
3014 if (!mOverlayLoadObservers->GetWeak(uri))
3015 mOverlayLoadObservers->Put(uri, obs);
3016 mOverlayLoadObservers->Remove(overlayURI);
3019 if (shouldReturn)
3020 return NS_OK;
3021 overlayURI.swap(uri);
3024 // If we get here, there is nothing left for us to walk. The content
3025 // model is built and ready for layout.
3026 rv = ResolveForwardReferences();
3027 if (NS_FAILED(rv)) return rv;
3029 ApplyPersistentAttributes();
3031 mStillWalking = false;
3032 if (mPendingSheets == 0) {
3033 rv = DoneWalking();
3035 return rv;
3038 nsresult
3039 XULDocument::DoneWalking()
3041 NS_PRECONDITION(mPendingSheets == 0, "there are sheets to be loaded");
3042 NS_PRECONDITION(!mStillWalking, "walk not done");
3044 // XXXldb This is where we should really be setting the chromehidden
3045 // attribute.
3047 uint32_t count = mOverlaySheets.Length();
3048 for (uint32_t i = 0; i < count; ++i) {
3049 AddStyleSheet(mOverlaySheets[i]);
3051 mOverlaySheets.Clear();
3053 if (!mDocumentLoaded) {
3054 // Make sure we don't reenter here from StartLayout(). Note that
3055 // setting mDocumentLoaded to true here means that if StartLayout()
3056 // causes ResumeWalk() to be reentered, we'll take the other branch of
3057 // the |if (!mDocumentLoaded)| check above and since
3058 // mInitialLayoutComplete will be false will follow the else branch
3059 // there too. See the big comment there for how such reentry can
3060 // happen.
3061 mDocumentLoaded = true;
3063 NotifyPossibleTitleChange(false);
3065 // Before starting layout, check whether we're a toplevel chrome
3066 // window. If we are, set our chrome flags now, so that we don't have
3067 // to restyle the whole frame tree after StartLayout.
3068 nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
3069 if (item) {
3070 nsCOMPtr<nsIDocShellTreeOwner> owner;
3071 item->GetTreeOwner(getter_AddRefs(owner));
3072 nsCOMPtr<nsIXULWindow> xulWin = do_GetInterface(owner);
3073 if (xulWin) {
3074 nsCOMPtr<nsIDocShell> xulWinShell;
3075 xulWin->GetDocShell(getter_AddRefs(xulWinShell));
3076 if (SameCOMIdentity(xulWinShell, item)) {
3077 // We're the chrome document! Apply our chrome flags now.
3078 xulWin->ApplyChromeFlags();
3083 StartLayout();
3085 if (mIsWritingFastLoad && IsChromeURI(mDocumentURI))
3086 nsXULPrototypeCache::GetInstance()->WritePrototype(mMasterPrototype);
3088 NS_ASSERTION(mDelayFrameLoaderInitialization,
3089 "mDelayFrameLoaderInitialization should be true!");
3090 mDelayFrameLoaderInitialization = false;
3091 NS_WARN_IF_FALSE(mUpdateNestLevel == 0,
3092 "Constructing XUL document in middle of an update?");
3093 if (mUpdateNestLevel == 0) {
3094 MaybeInitializeFinalizeFrameLoaders();
3097 NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
3099 // DispatchContentLoadedEvents undoes the onload-blocking we
3100 // did in PrepareToWalk().
3101 DispatchContentLoadedEvents();
3103 mInitialLayoutComplete = true;
3105 // Walk the set of pending load notifications and notify any observers.
3106 // See below for detail.
3107 if (mPendingOverlayLoadNotifications)
3108 mPendingOverlayLoadNotifications->Enumerate(
3109 FirePendingMergeNotification, mOverlayLoadObservers.get());
3111 else {
3112 if (mOverlayLoadObservers) {
3113 nsCOMPtr<nsIURI> overlayURI = mCurrentPrototype->GetURI();
3114 nsCOMPtr<nsIObserver> obs;
3115 if (mInitialLayoutComplete) {
3116 // We have completed initial layout, so just send the notification.
3117 mOverlayLoadObservers->Get(overlayURI, getter_AddRefs(obs));
3118 if (obs)
3119 obs->Observe(overlayURI, "xul-overlay-merged", EmptyString().get());
3120 mOverlayLoadObservers->Remove(overlayURI);
3122 else {
3123 // If we have not yet displayed the document for the first time
3124 // (i.e. we came in here as the result of a dynamic overlay load
3125 // which was spawned by a binding-attached event caused by
3126 // StartLayout() on the master prototype - we must remember that
3127 // this overlay has been merged and tell the listeners after
3128 // StartLayout() is completely finished rather than doing so
3129 // immediately - otherwise we may be executing code that needs to
3130 // access XBL Binding implementations on nodes for which frames
3131 // have not yet been constructed because their bindings have not
3132 // yet been attached. This can be a race condition because dynamic
3133 // overlay loading can take varying amounts of time depending on
3134 // whether or not the overlay prototype is in the XUL cache. The
3135 // most likely effect of this bug is odd UI initialization due to
3136 // methods and properties that do not work.
3137 // XXXbz really, we shouldn't be firing binding constructors
3138 // until after StartLayout returns!
3140 if (!mPendingOverlayLoadNotifications) {
3141 mPendingOverlayLoadNotifications =
3142 new nsInterfaceHashtable<nsURIHashKey,nsIObserver>;
3145 mPendingOverlayLoadNotifications->Get(overlayURI, getter_AddRefs(obs));
3146 if (!obs) {
3147 mOverlayLoadObservers->Get(overlayURI, getter_AddRefs(obs));
3148 NS_ASSERTION(obs, "null overlay load observer?");
3149 mPendingOverlayLoadNotifications->Put(overlayURI, obs);
3155 return NS_OK;
3158 NS_IMETHODIMP
3159 XULDocument::StyleSheetLoaded(CSSStyleSheet* aSheet,
3160 bool aWasAlternate,
3161 nsresult aStatus)
3163 if (!aWasAlternate) {
3164 // Don't care about when alternate sheets finish loading
3166 NS_ASSERTION(mPendingSheets > 0,
3167 "Unexpected StyleSheetLoaded notification");
3169 --mPendingSheets;
3171 if (!mStillWalking && mPendingSheets == 0) {
3172 return DoneWalking();
3176 return NS_OK;
3179 void
3180 XULDocument::MaybeBroadcast()
3182 // Only broadcast when not in an update and when safe to run scripts.
3183 if (mUpdateNestLevel == 0 &&
3184 (mDelayedAttrChangeBroadcasts.Length() ||
3185 mDelayedBroadcasters.Length())) {
3186 if (!nsContentUtils::IsSafeToRunScript()) {
3187 if (!mInDestructor) {
3188 nsContentUtils::AddScriptRunner(
3189 NS_NewRunnableMethod(this, &XULDocument::MaybeBroadcast));
3191 return;
3193 if (!mHandlingDelayedAttrChange) {
3194 mHandlingDelayedAttrChange = true;
3195 for (uint32_t i = 0; i < mDelayedAttrChangeBroadcasts.Length(); ++i) {
3196 nsIAtom* attrName = mDelayedAttrChangeBroadcasts[i].mAttrName;
3197 if (mDelayedAttrChangeBroadcasts[i].mNeedsAttrChange) {
3198 nsCOMPtr<nsIContent> listener =
3199 do_QueryInterface(mDelayedAttrChangeBroadcasts[i].mListener);
3200 nsString value = mDelayedAttrChangeBroadcasts[i].mAttr;
3201 if (mDelayedAttrChangeBroadcasts[i].mSetAttr) {
3202 listener->SetAttr(kNameSpaceID_None, attrName, value,
3203 true);
3204 } else {
3205 listener->UnsetAttr(kNameSpaceID_None, attrName,
3206 true);
3209 ExecuteOnBroadcastHandlerFor(mDelayedAttrChangeBroadcasts[i].mBroadcaster,
3210 mDelayedAttrChangeBroadcasts[i].mListener,
3211 attrName);
3213 mDelayedAttrChangeBroadcasts.Clear();
3214 mHandlingDelayedAttrChange = false;
3217 uint32_t length = mDelayedBroadcasters.Length();
3218 if (length) {
3219 bool oldValue = mHandlingDelayedBroadcasters;
3220 mHandlingDelayedBroadcasters = true;
3221 nsTArray<nsDelayedBroadcastUpdate> delayedBroadcasters;
3222 mDelayedBroadcasters.SwapElements(delayedBroadcasters);
3223 for (uint32_t i = 0; i < length; ++i) {
3224 SynchronizeBroadcastListener(delayedBroadcasters[i].mBroadcaster,
3225 delayedBroadcasters[i].mListener,
3226 delayedBroadcasters[i].mAttr);
3228 mHandlingDelayedBroadcasters = oldValue;
3233 void
3234 XULDocument::EndUpdate(nsUpdateType aUpdateType)
3236 XMLDocument::EndUpdate(aUpdateType);
3238 MaybeBroadcast();
3241 void
3242 XULDocument::ReportMissingOverlay(nsIURI* aURI)
3244 NS_PRECONDITION(aURI, "Must have a URI");
3246 nsAutoCString spec;
3247 aURI->GetSpec(spec);
3249 NS_ConvertUTF8toUTF16 utfSpec(spec);
3250 const char16_t* params[] = { utfSpec.get() };
3251 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3252 NS_LITERAL_CSTRING("XUL Document"), this,
3253 nsContentUtils::eXUL_PROPERTIES,
3254 "MissingOverlay",
3255 params, ArrayLength(params));
3258 nsresult
3259 XULDocument::LoadScript(nsXULPrototypeScript* aScriptProto, bool* aBlock)
3261 // Load a transcluded script
3262 nsresult rv;
3264 bool isChromeDoc = IsChromeURI(mDocumentURI);
3266 if (isChromeDoc && aScriptProto->GetScriptObject()) {
3267 rv = ExecuteScript(aScriptProto);
3269 // Ignore return value from execution, and don't block
3270 *aBlock = false;
3271 return NS_OK;
3274 // Try the XUL script cache, in case two XUL documents source the same
3275 // .js file (e.g., strres.js from navigator.xul and utilityOverlay.xul).
3276 // XXXbe the cache relies on aScriptProto's GC root!
3277 bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
3279 if (isChromeDoc && useXULCache) {
3280 JSScript* newScriptObject =
3281 nsXULPrototypeCache::GetInstance()->GetScript(
3282 aScriptProto->mSrcURI);
3283 if (newScriptObject) {
3284 // The script language for a proto must remain constant - we
3285 // can't just change it for this unexpected language.
3286 aScriptProto->Set(newScriptObject);
3289 if (aScriptProto->GetScriptObject()) {
3290 rv = ExecuteScript(aScriptProto);
3292 // Ignore return value from execution, and don't block
3293 *aBlock = false;
3294 return NS_OK;
3298 // Allow security manager and content policies to veto the load. Note that
3299 // at this point we already lost context information of the script.
3300 rv = nsScriptLoader::ShouldLoadScript(
3301 this,
3302 static_cast<nsIDocument*>(this),
3303 aScriptProto->mSrcURI,
3304 NS_LITERAL_STRING("application/x-javascript"));
3305 if (NS_FAILED(rv)) {
3306 *aBlock = false;
3307 return rv;
3310 // Release script objects from FastLoad since we decided against using them
3311 aScriptProto->UnlinkJSObjects();
3313 // Set the current script prototype so that OnStreamComplete can report
3314 // the right file if there are errors in the script.
3315 NS_ASSERTION(!mCurrentScriptProto,
3316 "still loading a script when starting another load?");
3317 mCurrentScriptProto = aScriptProto;
3319 if (aScriptProto->mSrcLoading) {
3320 // Another XULDocument load has started, which is still in progress.
3321 // Remember to ResumeWalk this document when the load completes.
3322 mNextSrcLoadWaiter = aScriptProto->mSrcLoadWaiters;
3323 aScriptProto->mSrcLoadWaiters = this;
3324 NS_ADDREF_THIS();
3326 else {
3327 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
3329 // Note: the loader will keep itself alive while it's loading.
3330 nsCOMPtr<nsIStreamLoader> loader;
3331 rv = NS_NewStreamLoader(getter_AddRefs(loader),
3332 aScriptProto->mSrcURI,
3333 this, // aObserver
3334 this, // aRequestingContext
3335 nsILoadInfo::SEC_NORMAL,
3336 nsIContentPolicy::TYPE_OTHER,
3337 nullptr, // aContext
3338 group);
3340 if (NS_FAILED(rv)) {
3341 mCurrentScriptProto = nullptr;
3342 return rv;
3345 aScriptProto->mSrcLoading = true;
3348 // Block until OnStreamComplete resumes us.
3349 *aBlock = true;
3350 return NS_OK;
3353 NS_IMETHODIMP
3354 XULDocument::OnStreamComplete(nsIStreamLoader* aLoader,
3355 nsISupports* context,
3356 nsresult aStatus,
3357 uint32_t stringLen,
3358 const uint8_t* string)
3360 nsCOMPtr<nsIRequest> request;
3361 aLoader->GetRequest(getter_AddRefs(request));
3362 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
3364 #ifdef DEBUG
3365 // print a load error on bad status
3366 if (NS_FAILED(aStatus)) {
3367 if (channel) {
3368 nsCOMPtr<nsIURI> uri;
3369 channel->GetURI(getter_AddRefs(uri));
3370 if (uri) {
3371 nsAutoCString uriSpec;
3372 uri->GetSpec(uriSpec);
3373 printf("Failed to load %s\n", uriSpec.get());
3377 #endif
3379 // This is the completion routine that will be called when a
3380 // transcluded script completes. Compile and execute the script
3381 // if the load was successful, then continue building content
3382 // from the prototype.
3383 nsresult rv = aStatus;
3385 NS_ASSERTION(mCurrentScriptProto && mCurrentScriptProto->mSrcLoading,
3386 "script source not loading on unichar stream complete?");
3387 if (!mCurrentScriptProto) {
3388 // XXX Wallpaper for bug 270042
3389 return NS_OK;
3392 if (NS_SUCCEEDED(aStatus)) {
3393 // If the including XUL document is a FastLoad document, and we're
3394 // compiling an out-of-line script (one with src=...), then we must
3395 // be writing a new FastLoad file. If we were reading this script
3396 // from the FastLoad file, XULContentSinkImpl::OpenScript (over in
3397 // nsXULContentSink.cpp) would have already deserialized a non-null
3398 // script->mScriptObject, causing control flow at the top of LoadScript
3399 // not to reach here.
3400 nsCOMPtr<nsIURI> uri = mCurrentScriptProto->mSrcURI;
3402 // XXX should also check nsIHttpChannel::requestSucceeded
3404 MOZ_ASSERT(!mOffThreadCompiling && (mOffThreadCompileStringLength == 0 &&
3405 !mOffThreadCompileStringBuf),
3406 "XULDocument can't load multiple scripts at once");
3408 rv = nsScriptLoader::ConvertToUTF16(channel, string, stringLen,
3409 EmptyString(), this,
3410 mOffThreadCompileStringBuf,
3411 mOffThreadCompileStringLength);
3412 if (NS_SUCCEEDED(rv)) {
3413 // Attempt to give ownership of the buffer to the JS engine. If
3414 // we hit offthread compilation, however, we will have to take it
3415 // back below in order to keep the memory alive until compilation
3416 // completes.
3417 JS::SourceBufferHolder srcBuf(mOffThreadCompileStringBuf,
3418 mOffThreadCompileStringLength,
3419 JS::SourceBufferHolder::GiveOwnership);
3420 mOffThreadCompileStringBuf = nullptr;
3421 mOffThreadCompileStringLength = 0;
3423 rv = mCurrentScriptProto->Compile(srcBuf, uri, 1, this, this);
3424 if (NS_SUCCEEDED(rv) && !mCurrentScriptProto->GetScriptObject()) {
3425 // We will be notified via OnOffThreadCompileComplete when the
3426 // compile finishes. Keep the contents of the compiled script
3427 // alive until the compilation finishes.
3428 mOffThreadCompiling = true;
3429 // If the JS engine did not take the source buffer, then take
3430 // it back here to ensure it remains alive.
3431 mOffThreadCompileStringBuf = srcBuf.take();
3432 if (mOffThreadCompileStringBuf) {
3433 mOffThreadCompileStringLength = srcBuf.length();
3435 BlockOnload();
3436 return NS_OK;
3441 return OnScriptCompileComplete(mCurrentScriptProto->GetScriptObject(), rv);
3444 NS_IMETHODIMP
3445 XULDocument::OnScriptCompileComplete(JSScript* aScript, nsresult aStatus)
3447 // When compiling off thread the script will not have been attached to the
3448 // script proto yet.
3449 if (aScript && !mCurrentScriptProto->GetScriptObject())
3450 mCurrentScriptProto->Set(aScript);
3452 // Allow load events to be fired once off thread compilation finishes.
3453 if (mOffThreadCompiling) {
3454 mOffThreadCompiling = false;
3455 UnblockOnload(false);
3458 // After compilation finishes the script's characters are no longer needed.
3459 if (mOffThreadCompileStringBuf) {
3460 js_free(mOffThreadCompileStringBuf);
3461 mOffThreadCompileStringBuf = nullptr;
3462 mOffThreadCompileStringLength = 0;
3465 // Clear mCurrentScriptProto now, but save it first for use below in
3466 // the execute code, and in the while loop that resumes walks of other
3467 // documents that raced to load this script.
3468 nsXULPrototypeScript* scriptProto = mCurrentScriptProto;
3469 mCurrentScriptProto = nullptr;
3471 // Clear the prototype's loading flag before executing the script or
3472 // resuming document walks, in case any of those control flows starts a
3473 // new script load.
3474 scriptProto->mSrcLoading = false;
3476 nsresult rv = aStatus;
3477 if (NS_SUCCEEDED(rv)) {
3478 rv = ExecuteScript(scriptProto);
3480 // If the XUL cache is enabled, save the script object there in
3481 // case different XUL documents source the same script.
3483 // But don't save the script in the cache unless the master XUL
3484 // document URL is a chrome: URL. It is valid for a URL such as
3485 // about:config to translate into a master document URL, whose
3486 // prototype document nodes -- including prototype scripts that
3487 // hold GC roots protecting their mJSObject pointers -- are not
3488 // cached in the XUL prototype cache. See StartDocumentLoad,
3489 // the fillXULCache logic.
3491 // A document such as about:config is free to load a script via
3492 // a URL such as chrome://global/content/config.js, and we must
3493 // not cache that script object without a prototype cache entry
3494 // containing a companion nsXULPrototypeScript node that owns a
3495 // GC root protecting the script object. Otherwise, the script
3496 // cache entry will dangle once the uncached prototype document
3497 // is released when its owning XULDocument is unloaded.
3499 // (See http://bugzilla.mozilla.org/show_bug.cgi?id=98207 for
3500 // the true crime story.)
3501 bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
3503 if (useXULCache && IsChromeURI(mDocumentURI) && scriptProto->GetScriptObject()) {
3504 nsXULPrototypeCache::GetInstance()->PutScript(
3505 scriptProto->mSrcURI,
3506 scriptProto->GetScriptObject());
3509 if (mIsWritingFastLoad && mCurrentPrototype != mMasterPrototype) {
3510 // If we are loading an overlay script, try to serialize
3511 // it to the FastLoad file here. Master scripts will be
3512 // serialized when the master prototype document gets
3513 // written, at the bottom of ResumeWalk. That way, master
3514 // out-of-line scripts are serialized in the same order that
3515 // they'll be read, in the FastLoad file, which reduces the
3516 // number of seeks that dump the underlying stream's buffer.
3518 // Ignore the return value, as we don't need to propagate
3519 // a failure to write to the FastLoad file, because this
3520 // method aborts that whole process on error.
3521 scriptProto->SerializeOutOfLine(nullptr, mCurrentPrototype);
3523 // ignore any evaluation errors
3526 rv = ResumeWalk();
3528 // Load a pointer to the prototype-script's list of XULDocuments who
3529 // raced to load the same script
3530 XULDocument** docp = &scriptProto->mSrcLoadWaiters;
3532 // Resume walking other documents that waited for this one's load, first
3533 // executing the script we just compiled, in each doc's script context
3534 XULDocument* doc;
3535 while ((doc = *docp) != nullptr) {
3536 NS_ASSERTION(doc->mCurrentScriptProto == scriptProto,
3537 "waiting for wrong script to load?");
3538 doc->mCurrentScriptProto = nullptr;
3540 // Unlink doc from scriptProto's list before executing and resuming
3541 *docp = doc->mNextSrcLoadWaiter;
3542 doc->mNextSrcLoadWaiter = nullptr;
3544 // Execute only if we loaded and compiled successfully, then resume
3545 if (NS_SUCCEEDED(aStatus) && scriptProto->GetScriptObject()) {
3546 doc->ExecuteScript(scriptProto);
3548 doc->ResumeWalk();
3549 NS_RELEASE(doc);
3552 return rv;
3555 nsresult
3556 XULDocument::ExecuteScript(nsXULPrototypeScript *aScript)
3558 NS_PRECONDITION(aScript != nullptr, "null ptr");
3559 NS_ENSURE_TRUE(aScript, NS_ERROR_NULL_POINTER);
3560 NS_ENSURE_TRUE(mScriptGlobalObject, NS_ERROR_NOT_INITIALIZED);
3562 nsresult rv;
3563 rv = mScriptGlobalObject->EnsureScriptEnvironment();
3564 NS_ENSURE_SUCCESS(rv, rv);
3566 JS::HandleScript scriptObject = aScript->GetScriptObject();
3567 NS_ENSURE_TRUE(scriptObject, NS_ERROR_UNEXPECTED);
3569 // Execute the precompiled script with the given version
3570 nsAutoMicroTask mt;
3572 // We're about to run script via JS::CloneAndExecuteScript, so we need an
3573 // AutoEntryScript. This is Gecko specific and not in any spec.
3574 AutoEntryScript aes(mScriptGlobalObject);
3575 aes.TakeOwnershipOfErrorReporting();
3576 JSContext* cx = aes.cx();
3577 JS::Rooted<JSObject*> baseGlobal(cx, JS::CurrentGlobalOrNull(cx));
3578 NS_ENSURE_TRUE(nsContentUtils::GetSecurityManager()->ScriptAllowed(baseGlobal), NS_OK);
3580 JSAddonId* addonId = mCurrentPrototype ? MapURIToAddonID(mCurrentPrototype->GetURI()) : nullptr;
3581 JS::Rooted<JSObject*> global(cx, xpc::GetAddonScope(cx, baseGlobal, addonId));
3582 NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
3584 JS::ExposeObjectToActiveJS(global);
3585 xpc_UnmarkGrayScript(scriptObject);
3586 JSAutoCompartment ac(cx, global);
3588 // The script is in the compilation scope. Clone it into the target scope
3589 // and execute it. On failure, ~AutoScriptEntry will handle exceptions, so
3590 // there is no need to manually check the return value.
3591 JS::CloneAndExecuteScript(cx, global, scriptObject);
3593 return NS_OK;
3597 nsresult
3598 XULDocument::CreateElementFromPrototype(nsXULPrototypeElement* aPrototype,
3599 Element** aResult,
3600 bool aIsRoot)
3602 // Create a content model element from a prototype element.
3603 NS_PRECONDITION(aPrototype != nullptr, "null ptr");
3604 if (! aPrototype)
3605 return NS_ERROR_NULL_POINTER;
3607 *aResult = nullptr;
3608 nsresult rv = NS_OK;
3610 #ifdef PR_LOGGING
3611 if (PR_LOG_TEST(gXULLog, PR_LOG_NOTICE)) {
3612 PR_LOG(gXULLog, PR_LOG_NOTICE,
3613 ("xul: creating <%s> from prototype",
3614 NS_ConvertUTF16toUTF8(aPrototype->mNodeInfo->QualifiedName()).get()));
3616 #endif
3618 nsRefPtr<Element> result;
3620 if (aPrototype->mNodeInfo->NamespaceEquals(kNameSpaceID_XUL)) {
3621 // If it's a XUL element, it'll be lightweight until somebody
3622 // monkeys with it.
3623 rv = nsXULElement::Create(aPrototype, this, true, aIsRoot, getter_AddRefs(result));
3624 if (NS_FAILED(rv)) return rv;
3626 else {
3627 // If it's not a XUL element, it's gonna be heavyweight no matter
3628 // what. So we need to copy everything out of the prototype
3629 // into the element. Get a nodeinfo from our nodeinfo manager
3630 // for this node.
3631 nsRefPtr<mozilla::dom::NodeInfo> newNodeInfo;
3632 newNodeInfo = mNodeInfoManager->GetNodeInfo(aPrototype->mNodeInfo->NameAtom(),
3633 aPrototype->mNodeInfo->GetPrefixAtom(),
3634 aPrototype->mNodeInfo->NamespaceID(),
3635 nsIDOMNode::ELEMENT_NODE);
3636 if (!newNodeInfo) return NS_ERROR_OUT_OF_MEMORY;
3637 nsRefPtr<mozilla::dom::NodeInfo> xtfNi = newNodeInfo;
3638 rv = NS_NewElement(getter_AddRefs(result), newNodeInfo.forget(),
3639 NOT_FROM_PARSER);
3640 if (NS_FAILED(rv))
3641 return rv;
3643 rv = AddAttributes(aPrototype, result);
3644 if (NS_FAILED(rv)) return rv;
3647 result.swap(*aResult);
3649 return NS_OK;
3652 nsresult
3653 XULDocument::CreateOverlayElement(nsXULPrototypeElement* aPrototype,
3654 Element** aResult)
3656 nsresult rv;
3658 nsRefPtr<Element> element;
3659 rv = CreateElementFromPrototype(aPrototype, getter_AddRefs(element), false);
3660 if (NS_FAILED(rv)) return rv;
3662 OverlayForwardReference* fwdref =
3663 new OverlayForwardReference(this, element);
3664 if (! fwdref)
3665 return NS_ERROR_OUT_OF_MEMORY;
3667 // transferring ownership to ya...
3668 rv = AddForwardReference(fwdref);
3669 if (NS_FAILED(rv)) return rv;
3671 NS_ADDREF(*aResult = element);
3672 return NS_OK;
3675 nsresult
3676 XULDocument::AddAttributes(nsXULPrototypeElement* aPrototype,
3677 nsIContent* aElement)
3679 nsresult rv;
3681 for (uint32_t i = 0; i < aPrototype->mNumAttributes; ++i) {
3682 nsXULPrototypeAttribute* protoattr = &(aPrototype->mAttributes[i]);
3683 nsAutoString valueStr;
3684 protoattr->mValue.ToString(valueStr);
3686 rv = aElement->SetAttr(protoattr->mName.NamespaceID(),
3687 protoattr->mName.LocalName(),
3688 protoattr->mName.GetPrefix(),
3689 valueStr,
3690 false);
3691 if (NS_FAILED(rv)) return rv;
3694 return NS_OK;
3698 nsresult
3699 XULDocument::CheckTemplateBuilderHookup(nsIContent* aElement,
3700 bool* aNeedsHookup)
3702 // See if the element already has a `database' attribute. If it
3703 // does, then the template builder has already been created.
3705 // XXX This approach will crash and burn (well, maybe not _that_
3706 // bad) if aElement is not a XUL element.
3708 // XXXvarga Do we still want to support non XUL content?
3709 nsCOMPtr<nsIDOMXULElement> xulElement = do_QueryInterface(aElement);
3710 if (xulElement) {
3711 nsCOMPtr<nsIRDFCompositeDataSource> ds;
3712 xulElement->GetDatabase(getter_AddRefs(ds));
3713 if (ds) {
3714 *aNeedsHookup = false;
3715 return NS_OK;
3719 // Check aElement for a 'datasources' attribute, if it has
3720 // one a XUL template builder needs to be hooked up.
3721 *aNeedsHookup = aElement->HasAttr(kNameSpaceID_None,
3722 nsGkAtoms::datasources);
3723 return NS_OK;
3726 /* static */ nsresult
3727 XULDocument::CreateTemplateBuilder(nsIContent* aElement)
3729 // Check if need to construct a tree builder or content builder.
3730 bool isTreeBuilder = false;
3732 // return successful if the element is not is a document, as an inline
3733 // script could have removed it
3734 nsIDocument* document = aElement->GetUncomposedDoc();
3735 NS_ENSURE_TRUE(document, NS_OK);
3737 int32_t nameSpaceID;
3738 nsIAtom* baseTag = document->BindingManager()->
3739 ResolveTag(aElement, &nameSpaceID);
3741 if ((nameSpaceID == kNameSpaceID_XUL) && (baseTag == nsGkAtoms::tree)) {
3742 // By default, we build content for a tree and then we attach
3743 // the tree content view. However, if the `dont-build-content'
3744 // flag is set, then we we'll attach a tree builder which
3745 // directly implements the tree view.
3747 nsAutoString flags;
3748 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::flags, flags);
3749 if (flags.Find(NS_LITERAL_STRING("dont-build-content")) >= 0) {
3750 isTreeBuilder = true;
3754 if (isTreeBuilder) {
3755 // Create and initialize a tree builder.
3756 nsCOMPtr<nsIXULTemplateBuilder> builder =
3757 do_CreateInstance("@mozilla.org/xul/xul-tree-builder;1");
3759 if (! builder)
3760 return NS_ERROR_FAILURE;
3762 builder->Init(aElement);
3764 // Create a <treechildren> if one isn't there already.
3765 // XXXvarga what about attributes?
3766 nsCOMPtr<nsIContent> bodyContent;
3767 nsXULContentUtils::FindChildByTag(aElement, kNameSpaceID_XUL,
3768 nsGkAtoms::treechildren,
3769 getter_AddRefs(bodyContent));
3771 if (! bodyContent) {
3772 nsresult rv =
3773 document->CreateElem(nsDependentAtomString(nsGkAtoms::treechildren),
3774 nullptr, kNameSpaceID_XUL,
3775 getter_AddRefs(bodyContent));
3776 NS_ENSURE_SUCCESS(rv, rv);
3778 aElement->AppendChildTo(bodyContent, false);
3781 else {
3782 // Create and initialize a content builder.
3783 nsCOMPtr<nsIXULTemplateBuilder> builder
3784 = do_CreateInstance("@mozilla.org/xul/xul-template-builder;1");
3786 if (! builder)
3787 return NS_ERROR_FAILURE;
3789 builder->Init(aElement);
3790 builder->CreateContents(aElement, false);
3793 return NS_OK;
3797 nsresult
3798 XULDocument::AddPrototypeSheets()
3800 nsresult rv;
3802 const nsCOMArray<nsIURI>& sheets = mCurrentPrototype->GetStyleSheetReferences();
3804 for (int32_t i = 0; i < sheets.Count(); i++) {
3805 nsCOMPtr<nsIURI> uri = sheets[i];
3807 nsRefPtr<CSSStyleSheet> incompleteSheet;
3808 rv = CSSLoader()->LoadSheet(uri,
3809 mCurrentPrototype->DocumentPrincipal(),
3810 EmptyCString(), this,
3811 getter_AddRefs(incompleteSheet));
3813 // XXXldb We need to prevent bogus sheets from being held in the
3814 // prototype's list, but until then, don't propagate the failure
3815 // from LoadSheet (and thus exit the loop).
3816 if (NS_SUCCEEDED(rv)) {
3817 ++mPendingSheets;
3818 if (!mOverlaySheets.AppendElement(incompleteSheet)) {
3819 return NS_ERROR_OUT_OF_MEMORY;
3824 return NS_OK;
3828 //----------------------------------------------------------------------
3830 // XULDocument::OverlayForwardReference
3833 nsForwardReference::Result
3834 XULDocument::OverlayForwardReference::Resolve()
3836 // Resolve a forward reference from an overlay element; attempt to
3837 // hook it up into the main document.
3838 nsresult rv;
3839 nsCOMPtr<nsIContent> target;
3841 nsIPresShell *shell = mDocument->GetShell();
3842 bool notify = shell && shell->DidInitialize();
3844 nsAutoString id;
3845 mOverlay->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
3846 if (id.IsEmpty()) {
3847 // mOverlay is a direct child of <overlay> and has no id.
3848 // Insert it under the root element in the base document.
3849 Element* root = mDocument->GetRootElement();
3850 if (!root) {
3851 return eResolve_Error;
3854 rv = mDocument->InsertElement(root, mOverlay, notify);
3855 if (NS_FAILED(rv)) return eResolve_Error;
3857 target = mOverlay;
3859 else {
3860 // The hook-up element has an id, try to match it with an element
3861 // with the same id in the base document.
3862 target = mDocument->GetElementById(id);
3864 // If we can't find the element in the document, defer the hookup
3865 // until later.
3866 if (!target)
3867 return eResolve_Later;
3869 rv = Merge(target, mOverlay, notify);
3870 if (NS_FAILED(rv)) return eResolve_Error;
3873 // Check if 'target' is still in our document --- it might not be!
3874 if (!notify && target->GetUncomposedDoc() == mDocument) {
3875 // Add child and any descendants to the element map
3876 // XXX this is bogus, the content in 'target' might already be
3877 // in the document
3878 rv = mDocument->AddSubtreeToDocument(target);
3879 if (NS_FAILED(rv)) return eResolve_Error;
3882 #ifdef PR_LOGGING
3883 if (PR_LOG_TEST(gXULLog, PR_LOG_NOTICE)) {
3884 nsAutoCString idC;
3885 idC.AssignWithConversion(id);
3886 PR_LOG(gXULLog, PR_LOG_NOTICE,
3887 ("xul: overlay resolved '%s'",
3888 idC.get()));
3890 #endif
3892 mResolved = true;
3893 return eResolve_Succeeded;
3898 nsresult
3899 XULDocument::OverlayForwardReference::Merge(nsIContent* aTargetNode,
3900 nsIContent* aOverlayNode,
3901 bool aNotify)
3903 // This function is given:
3904 // aTargetNode: the node in the document whose 'id' attribute
3905 // matches a toplevel node in our overlay.
3906 // aOverlayNode: the node in the overlay document that matches
3907 // a node in the actual document.
3908 // aNotify: whether or not content manipulation methods should
3909 // use the aNotify parameter. After the initial
3910 // reflow (i.e. in the dynamic overlay merge case),
3911 // we want all the content manipulation methods we
3912 // call to notify so that frames are constructed
3913 // etc. Otherwise do not, since that's during initial
3914 // document construction before StartLayout has been
3915 // called which will do everything for us.
3917 // This function merges the tree from the overlay into the tree in
3918 // the document, overwriting attributes and appending child content
3919 // nodes appropriately. (See XUL overlay reference for details)
3921 nsresult rv;
3923 // Merge attributes from the overlay content node to that of the
3924 // actual document.
3925 uint32_t i;
3926 const nsAttrName* name;
3927 for (i = 0; (name = aOverlayNode->GetAttrNameAt(i)); ++i) {
3928 // We don't want to swap IDs, they should be the same.
3929 if (name->Equals(nsGkAtoms::id))
3930 continue;
3932 // In certain cases merging command or observes is unsafe, so don't.
3933 if (!aNotify) {
3934 if (aTargetNode->NodeInfo()->Equals(nsGkAtoms::observes,
3935 kNameSpaceID_XUL))
3936 continue;
3938 if (name->Equals(nsGkAtoms::observes) &&
3939 aTargetNode->HasAttr(kNameSpaceID_None, nsGkAtoms::observes))
3940 continue;
3942 if (name->Equals(nsGkAtoms::command) &&
3943 aTargetNode->HasAttr(kNameSpaceID_None, nsGkAtoms::command) &&
3944 !aTargetNode->NodeInfo()->Equals(nsGkAtoms::key,
3945 kNameSpaceID_XUL) &&
3946 !aTargetNode->NodeInfo()->Equals(nsGkAtoms::menuitem,
3947 kNameSpaceID_XUL))
3948 continue;
3951 int32_t nameSpaceID = name->NamespaceID();
3952 nsIAtom* attr = name->LocalName();
3953 nsIAtom* prefix = name->GetPrefix();
3955 nsAutoString value;
3956 aOverlayNode->GetAttr(nameSpaceID, attr, value);
3958 // Element in the overlay has the 'removeelement' attribute set
3959 // so remove it from the actual document.
3960 if (attr == nsGkAtoms::removeelement &&
3961 value.EqualsLiteral("true")) {
3963 nsCOMPtr<nsINode> parent = aTargetNode->GetParentNode();
3964 if (!parent) return NS_ERROR_FAILURE;
3965 rv = RemoveElement(parent, aTargetNode);
3966 if (NS_FAILED(rv)) return rv;
3968 return NS_OK;
3971 rv = aTargetNode->SetAttr(nameSpaceID, attr, prefix, value, aNotify);
3972 if (!NS_FAILED(rv) && !aNotify)
3973 rv = mDocument->BroadcastAttributeChangeFromOverlay(aTargetNode,
3974 nameSpaceID,
3975 attr, prefix,
3976 value);
3977 if (NS_FAILED(rv)) return rv;
3981 // Walk our child nodes, looking for elements that have the 'id'
3982 // attribute set. If we find any, we must do a parent check in the
3983 // actual document to ensure that the structure matches that of
3984 // the actual document. If it does, we can call ourselves and attempt
3985 // to merge inside that subtree. If not, we just append the tree to
3986 // the parent like any other.
3988 uint32_t childCount = aOverlayNode->GetChildCount();
3990 // This must be a strong reference since it will be the only
3991 // reference to a content object during part of this loop.
3992 nsCOMPtr<nsIContent> currContent;
3994 for (i = 0; i < childCount; ++i) {
3995 currContent = aOverlayNode->GetFirstChild();
3997 nsIAtom *idAtom = currContent->GetID();
3999 nsIContent *elementInDocument = nullptr;
4000 if (idAtom) {
4001 nsDependentAtomString id(idAtom);
4003 if (!id.IsEmpty()) {
4004 nsIDocument *doc = aTargetNode->GetUncomposedDoc();
4005 //XXXsmaug should we use ShadowRoot::GetElementById()
4006 // if doc is null?
4007 if (!doc) return NS_ERROR_FAILURE;
4009 elementInDocument = doc->GetElementById(id);
4013 // The item has an 'id' attribute set, and we need to check with
4014 // the actual document to see if an item with this id exists at
4015 // this locale. If so, we want to merge the subtree under that
4016 // node. Otherwise, we just do an append as if the element had
4017 // no id attribute.
4018 if (elementInDocument) {
4019 // Given two parents, aTargetNode and aOverlayNode, we want
4020 // to call merge on currContent if we find an associated
4021 // node in the document with the same id as currContent that
4022 // also has aTargetNode as its parent.
4024 nsIContent *elementParent = elementInDocument->GetParent();
4026 nsIAtom *parentID = elementParent->GetID();
4027 if (parentID &&
4028 aTargetNode->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id,
4029 nsDependentAtomString(parentID),
4030 eCaseMatters)) {
4031 // The element matches. "Go Deep!"
4032 rv = Merge(elementInDocument, currContent, aNotify);
4033 if (NS_FAILED(rv)) return rv;
4034 aOverlayNode->RemoveChildAt(0, false);
4036 continue;
4040 aOverlayNode->RemoveChildAt(0, false);
4042 rv = InsertElement(aTargetNode, currContent, aNotify);
4043 if (NS_FAILED(rv)) return rv;
4046 return NS_OK;
4051 XULDocument::OverlayForwardReference::~OverlayForwardReference()
4053 #ifdef PR_LOGGING
4054 if (PR_LOG_TEST(gXULLog, PR_LOG_WARNING) && !mResolved) {
4055 nsAutoString id;
4056 mOverlay->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
4058 nsAutoCString idC;
4059 idC.AssignWithConversion(id);
4061 nsIURI *protoURI = mDocument->mCurrentPrototype->GetURI();
4062 nsAutoCString urlspec;
4063 protoURI->GetSpec(urlspec);
4065 nsCOMPtr<nsIURI> docURI;
4066 nsAutoCString parentDoc;
4067 nsresult rv = mDocument->mChannel->GetOriginalURI(getter_AddRefs(docURI));
4068 if (NS_SUCCEEDED(rv))
4069 docURI->GetSpec(parentDoc);
4070 PR_LOG(gXULLog, PR_LOG_WARNING,
4071 ("xul: %s overlay failed to resolve '%s' in %s",
4072 urlspec.get(), idC.get(), parentDoc.get()));
4074 #endif
4078 //----------------------------------------------------------------------
4080 // XULDocument::BroadcasterHookup
4083 nsForwardReference::Result
4084 XULDocument::BroadcasterHookup::Resolve()
4086 nsresult rv;
4088 bool listener;
4089 rv = mDocument->CheckBroadcasterHookup(mObservesElement, &listener, &mResolved);
4090 if (NS_FAILED(rv)) return eResolve_Error;
4092 return mResolved ? eResolve_Succeeded : eResolve_Later;
4096 XULDocument::BroadcasterHookup::~BroadcasterHookup()
4098 #ifdef PR_LOGGING
4099 if (PR_LOG_TEST(gXULLog, PR_LOG_WARNING) && !mResolved) {
4100 // Tell the world we failed
4101 nsIAtom *tag = mObservesElement->Tag();
4103 nsAutoString broadcasterID;
4104 nsAutoString attribute;
4106 if (tag == nsGkAtoms::observes) {
4107 mObservesElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, broadcasterID);
4108 mObservesElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, attribute);
4110 else {
4111 mObservesElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, broadcasterID);
4112 attribute.Assign('*');
4115 nsAutoCString attributeC,broadcasteridC;
4116 attributeC.AssignWithConversion(attribute);
4117 broadcasteridC.AssignWithConversion(broadcasterID);
4118 PR_LOG(gXULLog, PR_LOG_WARNING,
4119 ("xul: broadcaster hookup failed <%s attribute='%s'> to %s",
4120 nsAtomCString(tag).get(),
4121 attributeC.get(),
4122 broadcasteridC.get()));
4124 #endif
4128 //----------------------------------------------------------------------
4130 // XULDocument::TemplateBuilderHookup
4133 nsForwardReference::Result
4134 XULDocument::TemplateBuilderHookup::Resolve()
4136 bool needsHookup;
4137 nsresult rv = CheckTemplateBuilderHookup(mElement, &needsHookup);
4138 if (NS_FAILED(rv))
4139 return eResolve_Error;
4141 if (needsHookup) {
4142 rv = CreateTemplateBuilder(mElement);
4143 if (NS_FAILED(rv))
4144 return eResolve_Error;
4147 return eResolve_Succeeded;
4151 //----------------------------------------------------------------------
4153 nsresult
4154 XULDocument::BroadcastAttributeChangeFromOverlay(nsIContent* aNode,
4155 int32_t aNameSpaceID,
4156 nsIAtom* aAttribute,
4157 nsIAtom* aPrefix,
4158 const nsAString& aValue)
4160 nsresult rv = NS_OK;
4162 if (!mBroadcasterMap || !CanBroadcast(aNameSpaceID, aAttribute))
4163 return rv;
4165 if (!aNode->IsElement())
4166 return rv;
4168 BroadcasterMapEntry* entry = static_cast<BroadcasterMapEntry*>
4169 (PL_DHashTableLookup(mBroadcasterMap, aNode->AsElement()));
4170 if (!PL_DHASH_ENTRY_IS_BUSY(entry))
4171 return rv;
4173 // We've got listeners: push the value.
4174 int32_t i;
4175 for (i = entry->mListeners.Count() - 1; i >= 0; --i) {
4176 BroadcastListener* bl = static_cast<BroadcastListener*>
4177 (entry->mListeners[i]);
4179 if ((bl->mAttribute != aAttribute) &&
4180 (bl->mAttribute != nsGkAtoms::_asterix))
4181 continue;
4183 nsCOMPtr<nsIContent> l = do_QueryReferent(bl->mListener);
4184 if (l) {
4185 rv = l->SetAttr(aNameSpaceID, aAttribute,
4186 aPrefix, aValue, false);
4187 if (NS_FAILED(rv)) return rv;
4190 return rv;
4193 nsresult
4194 XULDocument::FindBroadcaster(Element* aElement,
4195 Element** aListener,
4196 nsString& aBroadcasterID,
4197 nsString& aAttribute,
4198 Element** aBroadcaster)
4200 mozilla::dom::NodeInfo *ni = aElement->NodeInfo();
4201 *aListener = nullptr;
4202 *aBroadcaster = nullptr;
4204 if (ni->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
4205 // It's an <observes> element, which means that the actual
4206 // listener is the _parent_ node. This element should have an
4207 // 'element' attribute that specifies the ID of the
4208 // broadcaster element, and an 'attribute' element, which
4209 // specifies the name of the attribute to observe.
4210 nsIContent* parent = aElement->GetParent();
4211 if (!parent) {
4212 // <observes> is the root element
4213 return NS_FINDBROADCASTER_NOT_FOUND;
4216 // If we're still parented by an 'overlay' tag, then we haven't
4217 // made it into the real document yet. Defer hookup.
4218 if (parent->NodeInfo()->Equals(nsGkAtoms::overlay,
4219 kNameSpaceID_XUL)) {
4220 return NS_FINDBROADCASTER_AWAIT_OVERLAYS;
4223 *aListener = parent->IsElement() ? parent->AsElement() : nullptr;
4224 NS_IF_ADDREF(*aListener);
4226 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, aBroadcasterID);
4227 if (aBroadcasterID.IsEmpty()) {
4228 return NS_FINDBROADCASTER_NOT_FOUND;
4230 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, aAttribute);
4232 else {
4233 // It's a generic element, which means that we'll use the
4234 // value of the 'observes' attribute to determine the ID of
4235 // the broadcaster element, and we'll watch _all_ of its
4236 // values.
4237 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, aBroadcasterID);
4239 // Bail if there's no aBroadcasterID
4240 if (aBroadcasterID.IsEmpty()) {
4241 // Try the command attribute next.
4242 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::command, aBroadcasterID);
4243 if (!aBroadcasterID.IsEmpty()) {
4244 // We've got something in the command attribute. We
4245 // only treat this as a normal broadcaster if we are
4246 // not a menuitem or a key.
4248 if (ni->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
4249 ni->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) {
4250 return NS_FINDBROADCASTER_NOT_FOUND;
4253 else {
4254 return NS_FINDBROADCASTER_NOT_FOUND;
4258 *aListener = aElement;
4259 NS_ADDREF(*aListener);
4261 aAttribute.Assign('*');
4264 // Make sure we got a valid listener.
4265 NS_ENSURE_TRUE(*aListener, NS_ERROR_UNEXPECTED);
4267 // Try to find the broadcaster element in the document.
4268 *aBroadcaster = GetElementById(aBroadcasterID);
4270 // If we can't find the broadcaster, then we'll need to defer the
4271 // hookup. We may need to resolve some of the other overlays
4272 // first.
4273 if (! *aBroadcaster) {
4274 return NS_FINDBROADCASTER_AWAIT_OVERLAYS;
4277 NS_ADDREF(*aBroadcaster);
4279 return NS_FINDBROADCASTER_FOUND;
4282 nsresult
4283 XULDocument::CheckBroadcasterHookup(Element* aElement,
4284 bool* aNeedsHookup,
4285 bool* aDidResolve)
4287 // Resolve a broadcaster hookup. Look at the element that we're
4288 // trying to resolve: it could be an '<observes>' element, or just
4289 // a vanilla element with an 'observes' attribute on it.
4290 nsresult rv;
4292 *aDidResolve = false;
4294 nsCOMPtr<Element> listener;
4295 nsAutoString broadcasterID;
4296 nsAutoString attribute;
4297 nsCOMPtr<Element> broadcaster;
4299 rv = FindBroadcaster(aElement, getter_AddRefs(listener),
4300 broadcasterID, attribute, getter_AddRefs(broadcaster));
4301 switch (rv) {
4302 case NS_FINDBROADCASTER_NOT_FOUND:
4303 *aNeedsHookup = false;
4304 return NS_OK;
4305 case NS_FINDBROADCASTER_AWAIT_OVERLAYS:
4306 *aNeedsHookup = true;
4307 return NS_OK;
4308 case NS_FINDBROADCASTER_FOUND:
4309 break;
4310 default:
4311 return rv;
4314 NS_ENSURE_ARG(broadcaster && listener);
4315 ErrorResult domRv;
4316 AddBroadcastListenerFor(*broadcaster, *listener, attribute, domRv);
4317 if (domRv.Failed()) {
4318 return domRv.ErrorCode();
4321 #ifdef PR_LOGGING
4322 // Tell the world we succeeded
4323 if (PR_LOG_TEST(gXULLog, PR_LOG_NOTICE)) {
4324 nsCOMPtr<nsIContent> content =
4325 do_QueryInterface(listener);
4327 NS_ASSERTION(content != nullptr, "not an nsIContent");
4328 if (! content)
4329 return rv;
4331 nsAutoCString attributeC,broadcasteridC;
4332 attributeC.AssignWithConversion(attribute);
4333 broadcasteridC.AssignWithConversion(broadcasterID);
4334 PR_LOG(gXULLog, PR_LOG_NOTICE,
4335 ("xul: broadcaster hookup <%s attribute='%s'> to %s",
4336 nsAtomCString(content->Tag()).get(),
4337 attributeC.get(),
4338 broadcasteridC.get()));
4340 #endif
4342 *aNeedsHookup = false;
4343 *aDidResolve = true;
4344 return NS_OK;
4347 nsresult
4348 XULDocument::InsertElement(nsINode* aParent, nsIContent* aChild,
4349 bool aNotify)
4351 // Insert aChild appropriately into aParent, accounting for a
4352 // 'pos' attribute set on aChild.
4354 nsAutoString posStr;
4355 bool wasInserted = false;
4357 // insert after an element of a given id
4358 aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::insertafter, posStr);
4359 bool isInsertAfter = true;
4361 if (posStr.IsEmpty()) {
4362 aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::insertbefore, posStr);
4363 isInsertAfter = false;
4366 if (!posStr.IsEmpty()) {
4367 nsIDocument *document = aParent->OwnerDoc();
4369 nsIContent *content = nullptr;
4371 char* str = ToNewCString(posStr);
4372 char* rest;
4373 char* token = nsCRT::strtok(str, ", ", &rest);
4375 while (token) {
4376 content = document->GetElementById(NS_ConvertASCIItoUTF16(token));
4377 if (content)
4378 break;
4380 token = nsCRT::strtok(rest, ", ", &rest);
4382 nsMemory::Free(str);
4384 if (content) {
4385 int32_t pos = aParent->IndexOf(content);
4387 if (pos != -1) {
4388 pos = isInsertAfter ? pos + 1 : pos;
4389 nsresult rv = aParent->InsertChildAt(aChild, pos, aNotify);
4390 if (NS_FAILED(rv))
4391 return rv;
4393 wasInserted = true;
4398 if (!wasInserted) {
4400 aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::position, posStr);
4401 if (!posStr.IsEmpty()) {
4402 nsresult rv;
4403 // Positions are one-indexed.
4404 int32_t pos = posStr.ToInteger(&rv);
4405 // Note: if the insertion index (which is |pos - 1|) would be less
4406 // than 0 or greater than the number of children aParent has, then
4407 // don't insert, since the position is bogus. Just skip on to
4408 // appending.
4409 if (NS_SUCCEEDED(rv) && pos > 0 &&
4410 uint32_t(pos - 1) <= aParent->GetChildCount()) {
4411 rv = aParent->InsertChildAt(aChild, pos - 1, aNotify);
4412 if (NS_SUCCEEDED(rv))
4413 wasInserted = true;
4414 // If the insertion fails, then we should still
4415 // attempt an append. Thus, rather than returning rv
4416 // immediately, we fall through to the final
4417 // "catch-all" case that just does an AppendChildTo.
4422 if (!wasInserted) {
4423 return aParent->AppendChildTo(aChild, aNotify);
4425 return NS_OK;
4428 nsresult
4429 XULDocument::RemoveElement(nsINode* aParent, nsINode* aChild)
4431 int32_t nodeOffset = aParent->IndexOf(aChild);
4433 aParent->RemoveChildAt(nodeOffset, true);
4434 return NS_OK;
4437 //----------------------------------------------------------------------
4439 // CachedChromeStreamListener
4442 XULDocument::CachedChromeStreamListener::CachedChromeStreamListener(XULDocument* aDocument, bool aProtoLoaded)
4443 : mDocument(aDocument),
4444 mProtoLoaded(aProtoLoaded)
4446 NS_ADDREF(mDocument);
4450 XULDocument::CachedChromeStreamListener::~CachedChromeStreamListener()
4452 NS_RELEASE(mDocument);
4456 NS_IMPL_ISUPPORTS(XULDocument::CachedChromeStreamListener,
4457 nsIRequestObserver, nsIStreamListener)
4459 NS_IMETHODIMP
4460 XULDocument::CachedChromeStreamListener::OnStartRequest(nsIRequest *request,
4461 nsISupports* acontext)
4463 return NS_ERROR_PARSED_DATA_CACHED;
4467 NS_IMETHODIMP
4468 XULDocument::CachedChromeStreamListener::OnStopRequest(nsIRequest *request,
4469 nsISupports* aContext,
4470 nsresult aStatus)
4472 if (! mProtoLoaded)
4473 return NS_OK;
4475 return mDocument->OnPrototypeLoadDone(true);
4479 NS_IMETHODIMP
4480 XULDocument::CachedChromeStreamListener::OnDataAvailable(nsIRequest *request,
4481 nsISupports* aContext,
4482 nsIInputStream* aInStr,
4483 uint64_t aSourceOffset,
4484 uint32_t aCount)
4486 NS_NOTREACHED("CachedChromeStream doesn't receive data");
4487 return NS_ERROR_UNEXPECTED;
4490 //----------------------------------------------------------------------
4492 // ParserObserver
4495 XULDocument::ParserObserver::ParserObserver(XULDocument* aDocument,
4496 nsXULPrototypeDocument* aPrototype)
4497 : mDocument(aDocument), mPrototype(aPrototype)
4501 XULDocument::ParserObserver::~ParserObserver()
4505 NS_IMPL_ISUPPORTS(XULDocument::ParserObserver, nsIRequestObserver)
4507 NS_IMETHODIMP
4508 XULDocument::ParserObserver::OnStartRequest(nsIRequest *request,
4509 nsISupports* aContext)
4511 // Guard against buggy channels calling OnStartRequest multiple times.
4512 if (mPrototype) {
4513 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
4514 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
4515 if (channel && secMan) {
4516 nsCOMPtr<nsIPrincipal> principal;
4517 secMan->GetChannelResultPrincipal(channel, getter_AddRefs(principal));
4519 // Failure there is ok -- it'll just set a (safe) null principal
4520 mPrototype->SetDocumentPrincipal(principal);
4523 // Make sure to avoid cycles
4524 mPrototype = nullptr;
4527 return NS_OK;
4530 NS_IMETHODIMP
4531 XULDocument::ParserObserver::OnStopRequest(nsIRequest *request,
4532 nsISupports* aContext,
4533 nsresult aStatus)
4535 nsresult rv = NS_OK;
4537 if (NS_FAILED(aStatus)) {
4538 // If an overlay load fails, we need to nudge the prototype
4539 // walk along.
4540 nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
4541 if (aChannel) {
4542 nsCOMPtr<nsIURI> uri;
4543 aChannel->GetOriginalURI(getter_AddRefs(uri));
4544 if (uri) {
4545 mDocument->ReportMissingOverlay(uri);
4549 rv = mDocument->ResumeWalk();
4552 // Drop the reference to the document to break cycle between the
4553 // document, the parser, the content sink, and the parser
4554 // observer.
4555 mDocument = nullptr;
4557 return rv;
4560 already_AddRefed<nsPIWindowRoot>
4561 XULDocument::GetWindowRoot()
4563 if (!mDocumentContainer) {
4564 return nullptr;
4567 nsCOMPtr<nsPIDOMWindow> piWin = mDocumentContainer->GetWindow();
4568 return piWin ? piWin->GetTopWindowRoot() : nullptr;
4571 bool
4572 XULDocument::IsDocumentRightToLeft()
4574 // setting the localedir attribute on the root element forces a
4575 // specific direction for the document.
4576 Element* element = GetRootElement();
4577 if (element) {
4578 static nsIContent::AttrValuesArray strings[] =
4579 {&nsGkAtoms::ltr, &nsGkAtoms::rtl, nullptr};
4580 switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
4581 strings, eCaseMatters)) {
4582 case 0: return false;
4583 case 1: return true;
4584 default: break; // otherwise, not a valid value, so fall through
4588 // otherwise, get the locale from the chrome registry and
4589 // look up the intl.uidirection.<locale> preference
4590 nsCOMPtr<nsIXULChromeRegistry> reg =
4591 mozilla::services::GetXULChromeRegistryService();
4592 if (!reg)
4593 return false;
4595 nsAutoCString package;
4596 bool isChrome;
4597 if (NS_SUCCEEDED(mDocumentURI->SchemeIs("chrome", &isChrome)) &&
4598 isChrome) {
4599 mDocumentURI->GetHostPort(package);
4601 else {
4602 // use the 'global' package for about and resource uris.
4603 // otherwise, just default to left-to-right.
4604 bool isAbout, isResource;
4605 if (NS_SUCCEEDED(mDocumentURI->SchemeIs("about", &isAbout)) &&
4606 isAbout) {
4607 package.AssignLiteral("global");
4609 else if (NS_SUCCEEDED(mDocumentURI->SchemeIs("resource", &isResource)) &&
4610 isResource) {
4611 package.AssignLiteral("global");
4613 else {
4614 return false;
4618 bool isRTL = false;
4619 reg->IsLocaleRTL(package, &isRTL);
4620 return isRTL;
4623 void
4624 XULDocument::ResetDocumentDirection()
4626 DocumentStatesChanged(NS_DOCUMENT_STATE_RTL_LOCALE);
4629 void
4630 XULDocument::DirectionChanged(const char* aPrefName, void* aData)
4632 // Reset the direction and restyle the document if necessary.
4633 XULDocument* doc = (XULDocument *)aData;
4634 if (doc) {
4635 doc->ResetDocumentDirection();
4640 XULDocument::GetDocumentLWTheme()
4642 if (mDocLWTheme == Doc_Theme_Uninitialized) {
4643 mDocLWTheme = Doc_Theme_None; // No lightweight theme by default
4645 Element* element = GetRootElement();
4646 nsAutoString hasLWTheme;
4647 if (element &&
4648 element->GetAttr(kNameSpaceID_None, nsGkAtoms::lwtheme, hasLWTheme) &&
4649 !(hasLWTheme.IsEmpty()) &&
4650 hasLWTheme.EqualsLiteral("true")) {
4651 mDocLWTheme = Doc_Theme_Neutral;
4652 nsAutoString lwTheme;
4653 element->GetAttr(kNameSpaceID_None, nsGkAtoms::lwthemetextcolor, lwTheme);
4654 if (!(lwTheme.IsEmpty())) {
4655 if (lwTheme.EqualsLiteral("dark"))
4656 mDocLWTheme = Doc_Theme_Dark;
4657 else if (lwTheme.EqualsLiteral("bright"))
4658 mDocLWTheme = Doc_Theme_Bright;
4662 return mDocLWTheme;
4665 NS_IMETHODIMP
4666 XULDocument::GetBoxObjectFor(nsIDOMElement* aElement, nsIBoxObject** aResult)
4668 ErrorResult rv;
4669 nsCOMPtr<Element> el = do_QueryInterface(aElement);
4670 *aResult = GetBoxObjectFor(el, rv).take();
4671 return rv.ErrorCode();
4674 JSObject*
4675 XULDocument::WrapNode(JSContext *aCx)
4677 return XULDocumentBinding::Wrap(aCx, this);
4680 } // namespace dom
4681 } // namespace mozilla