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/. */
6 
7 /*
8 
9   An implementation for the XUL document. This implementation serves
10   as the basis for generating an NGLayout content model.
11 
12   Notes
13   -----
14 
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".
21 
22 */
23 
24 #include "mozilla/ArrayUtils.h"
25 
26 #include "XULDocument.h"
27 
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 "mozilla/Logging.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/StyleSheetInlines.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"
95 #include "mozAutoDocUpdate.h"
96 #include "xpcpublic.h"
97 #include "mozilla/StyleSheet.h"
98 #include "mozilla/StyleSheetInlines.h"
99 
100 using namespace mozilla;
101 using namespace mozilla::dom;
102 
103 //----------------------------------------------------------------------
104 //
105 // CIDs
106 //
107 
108 static NS_DEFINE_CID(kParserCID,                 NS_PARSER_CID);
109 
IsOverlayAllowed(nsIURI * aURI)110 static bool IsOverlayAllowed(nsIURI* aURI)
111 {
112     bool canOverlay = false;
113     if (NS_SUCCEEDED(aURI->SchemeIs("about", &canOverlay)) && canOverlay)
114         return true;
115     if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &canOverlay)) && canOverlay)
116         return true;
117     return false;
118 }
119 
120 //----------------------------------------------------------------------
121 //
122 // Miscellaneous Constants
123 //
124 
125 const nsForwardReference::Phase nsForwardReference::kPasses[] = {
126     nsForwardReference::eConstruction,
127     nsForwardReference::eHookup,
128     nsForwardReference::eDone
129 };
130 
131 //----------------------------------------------------------------------
132 //
133 // Statics
134 //
135 
136 int32_t XULDocument::gRefCnt = 0;
137 
138 LazyLogModule XULDocument::gXULLog("XULDocument");
139 
140 //----------------------------------------------------------------------
141 
142 struct BroadcastListener {
143     nsWeakPtr mListener;
144     nsCOMPtr<nsIAtom> mAttribute;
145 };
146 
147 struct BroadcasterMapEntry : public PLDHashEntryHdr
148 {
149     Element* mBroadcaster;  // [WEAK]
150     nsTArray<BroadcastListener*> mListeners;  // [OWNING] of BroadcastListener objects
151 };
152 
153 Element*
GetFirstElement()154 nsRefMapEntry::GetFirstElement()
155 {
156     return mRefContentList.SafeElementAt(0);
157 }
158 
159 void
AppendAll(nsCOMArray<nsIContent> * aElements)160 nsRefMapEntry::AppendAll(nsCOMArray<nsIContent>* aElements)
161 {
162     for (size_t i = 0; i < mRefContentList.Length(); ++i) {
163         aElements->AppendObject(mRefContentList[i]);
164     }
165 }
166 
167 bool
AddElement(Element * aElement)168 nsRefMapEntry::AddElement(Element* aElement)
169 {
170     if (mRefContentList.Contains(aElement)) {
171         return true;
172     }
173     return mRefContentList.AppendElement(aElement);
174 }
175 
176 bool
RemoveElement(Element * aElement)177 nsRefMapEntry::RemoveElement(Element* aElement)
178 {
179     mRefContentList.RemoveElement(aElement);
180     return mRefContentList.IsEmpty();
181 }
182 
183 //----------------------------------------------------------------------
184 //
185 // ctors & dtors
186 //
187 
188 namespace mozilla {
189 namespace dom {
190 
XULDocument(void)191 XULDocument::XULDocument(void)
192     : XMLDocument("application/vnd.mozilla.xul+xml"),
193       mDocLWTheme(Doc_Theme_Uninitialized),
194       mState(eState_Master),
195       mResolutionPhase(nsForwardReference::eStart)
196 {
197     // NOTE! nsDocument::operator new() zeroes out all members, so don't
198     // bother initializing members to 0.
199 
200     // Override the default in nsDocument
201     mCharacterSet.AssignLiteral("UTF-8");
202 
203     mDefaultElementType = kNameSpaceID_XUL;
204     mType = eXUL;
205 
206     mDelayFrameLoaderInitialization = true;
207 
208     mAllowXULXBL = eTriTrue;
209 }
210 
~XULDocument()211 XULDocument::~XULDocument()
212 {
213     NS_ASSERTION(mNextSrcLoadWaiter == nullptr,
214         "unreferenced document still waiting for script source to load?");
215 
216     // In case we failed somewhere early on and the forward observer
217     // decls never got resolved.
218     mForwardReferences.Clear();
219     // Likewise for any references we have to IDs where we might
220     // look for persisted data:
221     mPersistenceIds.Clear();
222 
223     // Destroy our broadcaster map.
224     delete mBroadcasterMap;
225 
226     delete mTemplateBuilderTable;
227 
228     Preferences::UnregisterCallback(XULDocument::DirectionChanged,
229                                     "intl.uidirection.", this);
230 
231     if (mOffThreadCompileStringBuf) {
232       js_free(mOffThreadCompileStringBuf);
233     }
234 }
235 
236 } // namespace dom
237 } // namespace mozilla
238 
239 nsresult
NS_NewXULDocument(nsIXULDocument ** result)240 NS_NewXULDocument(nsIXULDocument** result)
241 {
242     NS_PRECONDITION(result != nullptr, "null ptr");
243     if (! result)
244         return NS_ERROR_NULL_POINTER;
245 
246     RefPtr<XULDocument> doc = new XULDocument();
247 
248     nsresult rv;
249     if (NS_FAILED(rv = doc->Init())) {
250         return rv;
251     }
252 
253     doc.forget(result);
254     return NS_OK;
255 }
256 
257 
258 namespace mozilla {
259 namespace dom {
260 
261 //----------------------------------------------------------------------
262 //
263 // nsISupports interface
264 //
265 
266 NS_IMPL_CYCLE_COLLECTION_CLASS(XULDocument)
267 
268 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XULDocument, XMLDocument)
269     NS_ASSERTION(!nsCCUncollectableMarker::InGeneration(cb, tmp->GetMarkedCCGeneration()),
270                  "Shouldn't traverse XULDocument!");
271     // XXX tmp->mForwardReferences?
272     // XXX tmp->mContextStack?
273 
274     // An element will only have a template builder as long as it's in the
275     // document, so we'll traverse the table here instead of from the element.
276     if (tmp->mTemplateBuilderTable) {
277         for (auto iter = tmp->mTemplateBuilderTable->Iter();
278              !iter.Done();
279              iter.Next()) {
280             NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mTemplateBuilderTable key");
281             cb.NoteXPCOMChild(iter.Key());
282             NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mTemplateBuilderTable value");
283             cb.NoteXPCOMChild(iter.UserData());
284         }
285     }
286 
287     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCurrentPrototype)
288     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMasterPrototype)
289     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
290     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypes)
291     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStore)
292 
293     if (tmp->mOverlayLoadObservers) {
294         for (auto iter = tmp->mOverlayLoadObservers->Iter();
295              !iter.Done();
296              iter.Next()) {
297             NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mOverlayLoadObservers value");
298             cb.NoteXPCOMChild(iter.Data());
299         }
300     }
301     if (tmp->mPendingOverlayLoadNotifications) {
302         for (auto iter = tmp->mPendingOverlayLoadNotifications->Iter();
303              !iter.Done();
304              iter.Next()) {
305             NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mPendingOverlayLoadNotifications value");
306             cb.NoteXPCOMChild(iter.Data());
307         }
308     }
309 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
310 
311 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULDocument, XMLDocument)
312     delete tmp->mTemplateBuilderTable;
313     tmp->mTemplateBuilderTable = nullptr;
314 
315     NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStore)316     NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStore)
317     //XXX We should probably unlink all the objects we traverse.
318 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
319 
320 NS_IMPL_ADDREF_INHERITED(XULDocument, XMLDocument)
321 NS_IMPL_RELEASE_INHERITED(XULDocument, XMLDocument)
322 
323 
324 // QueryInterface implementation for XULDocument
325 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(XULDocument)
326     NS_INTERFACE_TABLE_INHERITED(XULDocument, nsIXULDocument,
327                                  nsIDOMXULDocument, nsIStreamLoaderObserver,
328                                  nsICSSLoaderObserver, nsIOffThreadScriptReceiver)
329 NS_INTERFACE_TABLE_TAIL_INHERITING(XMLDocument)
330 
331 
332 //----------------------------------------------------------------------
333 //
334 // nsIDocument interface
335 //
336 
337 void
338 XULDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup)
339 {
340     NS_NOTREACHED("Reset");
341 }
342 
343 void
ResetToURI(nsIURI * aURI,nsILoadGroup * aLoadGroup,nsIPrincipal * aPrincipal)344 XULDocument::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
345                         nsIPrincipal* aPrincipal)
346 {
347     NS_NOTREACHED("ResetToURI");
348 }
349 
350 void
SetContentType(const nsAString & aContentType)351 XULDocument::SetContentType(const nsAString& aContentType)
352 {
353     NS_ASSERTION(aContentType.EqualsLiteral("application/vnd.mozilla.xul+xml"),
354                  "xul-documents always has content-type application/vnd.mozilla.xul+xml");
355     // Don't do anything, xul always has the mimetype
356     // application/vnd.mozilla.xul+xml
357 }
358 
359 // This is called when the master document begins loading, whether it's
360 // being cached or not.
361 nsresult
StartDocumentLoad(const char * aCommand,nsIChannel * aChannel,nsILoadGroup * aLoadGroup,nsISupports * aContainer,nsIStreamListener ** aDocListener,bool aReset,nsIContentSink * aSink)362 XULDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
363                                nsILoadGroup* aLoadGroup,
364                                nsISupports* aContainer,
365                                nsIStreamListener **aDocListener,
366                                bool aReset, nsIContentSink* aSink)
367 {
368     if (MOZ_LOG_TEST(gXULLog, LogLevel::Warning)) {
369 
370         nsCOMPtr<nsIURI> uri;
371         nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(uri));
372         if (NS_SUCCEEDED(rv)) {
373             nsAutoCString urlspec;
374             rv = uri->GetSpec(urlspec);
375             if (NS_SUCCEEDED(rv)) {
376                 MOZ_LOG(gXULLog, LogLevel::Warning,
377                        ("xul: load document '%s'", urlspec.get()));
378             }
379         }
380     }
381     // NOTE: If this ever starts calling nsDocument::StartDocumentLoad
382     // we'll possibly need to reset our content type afterwards.
383     mStillWalking = true;
384     mMayStartLayout = false;
385     mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
386 
387     mChannel = aChannel;
388 
389     // Get the URI.  Note that this should match nsDocShell::OnLoadingSite
390     nsresult rv =
391         NS_GetFinalChannelURI(aChannel, getter_AddRefs(mDocumentURI));
392     NS_ENSURE_SUCCESS(rv, rv);
393 
394     ResetStylesheetsToURI(mDocumentURI);
395 
396     RetrieveRelevantHeaders(aChannel);
397 
398     // Look in the chrome cache: we've got this puppy loaded
399     // already.
400     nsXULPrototypeDocument* proto = IsChromeURI(mDocumentURI) ?
401             nsXULPrototypeCache::GetInstance()->GetPrototype(mDocumentURI) :
402             nullptr;
403 
404     // Same comment as nsChromeProtocolHandler::NewChannel and
405     // XULDocument::ResumeWalk
406     // - Ben Goodger
407     //
408     // We don't abort on failure here because there are too many valid
409     // cases that can return failure, and the null-ness of |proto| is enough
410     // to trigger the fail-safe parse-from-disk solution. Example failure cases
411     // (for reference) include:
412     //
413     // NS_ERROR_NOT_AVAILABLE: the URI cannot be found in the startup cache,
414     //                         parse from disk
415     // other: the startup cache file could not be found, probably
416     //        due to being accessed before a profile has been selected (e.g.
417     //        loading chrome for the profile manager itself). This must be
418     //        parsed from disk.
419 
420     if (proto) {
421         // If we're racing with another document to load proto, wait till the
422         // load has finished loading before trying to add cloned style sheets.
423         // XULDocument::EndLoad will call proto->NotifyLoadDone, which will
424         // find all racing documents and notify them via OnPrototypeLoadDone,
425         // which will add style sheet clones to each document.
426         bool loaded;
427         rv = proto->AwaitLoadDone(this, &loaded);
428         if (NS_FAILED(rv)) return rv;
429 
430         mMasterPrototype = mCurrentPrototype = proto;
431 
432         // Set up the right principal on ourselves.
433         SetPrincipal(proto->DocumentPrincipal());
434 
435         // We need a listener, even if proto is not yet loaded, in which
436         // event the listener's OnStopRequest method does nothing, and all
437         // the interesting work happens below XULDocument::EndLoad, from
438         // the call there to mCurrentPrototype->NotifyLoadDone().
439         *aDocListener = new CachedChromeStreamListener(this, loaded);
440     }
441     else {
442         bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
443         bool fillXULCache = (useXULCache && IsChromeURI(mDocumentURI));
444 
445 
446         // It's just a vanilla document load. Create a parser to deal
447         // with the stream n' stuff.
448 
449         nsCOMPtr<nsIParser> parser;
450         rv = PrepareToLoad(aContainer, aCommand, aChannel, aLoadGroup,
451                            getter_AddRefs(parser));
452         if (NS_FAILED(rv)) return rv;
453 
454         // Predicate mIsWritingFastLoad on the XUL cache being enabled,
455         // so we don't have to re-check whether the cache is enabled all
456         // the time.
457         mIsWritingFastLoad = useXULCache;
458 
459         nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(parser, &rv);
460         NS_ASSERTION(NS_SUCCEEDED(rv), "parser doesn't support nsIStreamListener");
461         if (NS_FAILED(rv)) return rv;
462 
463         *aDocListener = listener;
464 
465         parser->Parse(mDocumentURI);
466 
467         // Put the current prototype, created under PrepareToLoad, into the
468         // XUL prototype cache now.  We can't do this under PrepareToLoad or
469         // overlay loading will break; search for PutPrototype in ResumeWalk
470         // and see the comment there.
471         if (fillXULCache) {
472             nsXULPrototypeCache::GetInstance()->PutPrototype(mCurrentPrototype);
473         }
474     }
475 
476     NS_IF_ADDREF(*aDocListener);
477     return NS_OK;
478 }
479 
480 // This gets invoked after a prototype for this document or one of
481 // its overlays is fully built in the content sink.
482 void
EndLoad()483 XULDocument::EndLoad()
484 {
485     // This can happen if an overlay fails to load
486     if (!mCurrentPrototype)
487         return;
488 
489     nsresult rv;
490 
491     // Whack the prototype document into the cache so that the next
492     // time somebody asks for it, they don't need to load it by hand.
493 
494     nsCOMPtr<nsIURI> uri = mCurrentPrototype->GetURI();
495     bool isChrome = IsChromeURI(uri);
496 
497     // Remember if the XUL cache is on
498     bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
499 
500     // If the current prototype is an overlay document (non-master prototype)
501     // and we're filling the FastLoad disk cache, tell the cache we're done
502     // loading it, and write the prototype. The master prototype is put into
503     // the cache earlier in XULDocument::StartDocumentLoad.
504     if (useXULCache && mIsWritingFastLoad && isChrome &&
505         mMasterPrototype != mCurrentPrototype) {
506         nsXULPrototypeCache::GetInstance()->WritePrototype(mCurrentPrototype);
507     }
508 
509     if (IsOverlayAllowed(uri)) {
510         nsCOMPtr<nsIXULOverlayProvider> reg =
511             mozilla::services::GetXULOverlayProviderService();
512 
513         if (reg) {
514             nsCOMPtr<nsISimpleEnumerator> overlays;
515             rv = reg->GetStyleOverlays(uri, getter_AddRefs(overlays));
516             if (NS_FAILED(rv)) return;
517 
518             bool moreSheets;
519             nsCOMPtr<nsISupports> next;
520             nsCOMPtr<nsIURI> sheetURI;
521 
522             while (NS_SUCCEEDED(rv = overlays->HasMoreElements(&moreSheets)) &&
523                    moreSheets) {
524                 overlays->GetNext(getter_AddRefs(next));
525 
526                 sheetURI = do_QueryInterface(next);
527                 if (!sheetURI) {
528                     NS_ERROR("Chrome registry handed me a non-nsIURI object!");
529                     continue;
530                 }
531 
532                 if (IsChromeURI(sheetURI)) {
533                     mCurrentPrototype->AddStyleSheetReference(sheetURI);
534                 }
535             }
536         }
537 
538         if (isChrome && useXULCache) {
539             // If it's a chrome prototype document, then notify any
540             // documents that raced to load the prototype, and awaited
541             // its load completion via proto->AwaitLoadDone().
542             rv = mCurrentPrototype->NotifyLoadDone();
543             if (NS_FAILED(rv)) return;
544         }
545     }
546 
547     OnPrototypeLoadDone(true);
548     if (MOZ_LOG_TEST(gXULLog, LogLevel::Warning)) {
549         nsAutoCString urlspec;
550         rv = uri->GetSpec(urlspec);
551         if (NS_SUCCEEDED(rv)) {
552             MOZ_LOG(gXULLog, LogLevel::Warning,
553                    ("xul: Finished loading document '%s'", urlspec.get()));
554         }
555     }
556 }
557 
558 NS_IMETHODIMP
OnPrototypeLoadDone(bool aResumeWalk)559 XULDocument::OnPrototypeLoadDone(bool aResumeWalk)
560 {
561     nsresult rv;
562 
563     // Add the style overlays from chrome registry, if any.
564     rv = AddPrototypeSheets();
565     if (NS_FAILED(rv)) return rv;
566 
567     rv = PrepareToWalk();
568     NS_ASSERTION(NS_SUCCEEDED(rv), "unable to prepare for walk");
569     if (NS_FAILED(rv)) return rv;
570 
571     if (aResumeWalk) {
572         rv = ResumeWalk();
573     }
574     return rv;
575 }
576 
577 // called when an error occurs parsing a document
578 bool
OnDocumentParserError()579 XULDocument::OnDocumentParserError()
580 {
581   // don't report errors that are from overlays
582   if (mCurrentPrototype && mMasterPrototype != mCurrentPrototype) {
583     nsCOMPtr<nsIURI> uri = mCurrentPrototype->GetURI();
584     if (IsChromeURI(uri)) {
585       nsCOMPtr<nsIObserverService> os =
586         mozilla::services::GetObserverService();
587       if (os)
588         os->NotifyObservers(uri, "xul-overlay-parsererror",
589                             EmptyString().get());
590     }
591 
592     return false;
593   }
594 
595   return true;
596 }
597 
598 static void
ClearBroadcasterMapEntry(PLDHashTable * aTable,PLDHashEntryHdr * aEntry)599 ClearBroadcasterMapEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
600 {
601     BroadcasterMapEntry* entry =
602         static_cast<BroadcasterMapEntry*>(aEntry);
603     for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
604         delete entry->mListeners[i];
605     }
606     entry->mListeners.Clear();
607 
608     // N.B. that we need to manually run the dtor because we
609     // constructed the nsTArray object in-place.
610     entry->mListeners.~nsTArray<BroadcastListener*>();
611 }
612 
613 static bool
CanBroadcast(int32_t aNameSpaceID,nsIAtom * aAttribute)614 CanBroadcast(int32_t aNameSpaceID, nsIAtom* aAttribute)
615 {
616     // Don't push changes to the |id|, |ref|, |persist|, |command| or
617     // |observes| attribute.
618     if (aNameSpaceID == kNameSpaceID_None) {
619         if ((aAttribute == nsGkAtoms::id) ||
620             (aAttribute == nsGkAtoms::ref) ||
621             (aAttribute == nsGkAtoms::persist) ||
622             (aAttribute == nsGkAtoms::command) ||
623             (aAttribute == nsGkAtoms::observes)) {
624             return false;
625         }
626     }
627     return true;
628 }
629 
630 struct nsAttrNameInfo
631 {
nsAttrNameInfomozilla::dom::nsAttrNameInfo632   nsAttrNameInfo(int32_t aNamespaceID, nsIAtom* aName, nsIAtom* aPrefix) :
633     mNamespaceID(aNamespaceID), mName(aName), mPrefix(aPrefix) {}
nsAttrNameInfomozilla::dom::nsAttrNameInfo634   nsAttrNameInfo(const nsAttrNameInfo& aOther) :
635     mNamespaceID(aOther.mNamespaceID), mName(aOther.mName),
636     mPrefix(aOther.mPrefix) {}
637   int32_t           mNamespaceID;
638   nsCOMPtr<nsIAtom> mName;
639   nsCOMPtr<nsIAtom> mPrefix;
640 };
641 
642 void
SynchronizeBroadcastListener(Element * aBroadcaster,Element * aListener,const nsAString & aAttr)643 XULDocument::SynchronizeBroadcastListener(Element *aBroadcaster,
644                                           Element *aListener,
645                                           const nsAString &aAttr)
646 {
647     if (!nsContentUtils::IsSafeToRunScript()) {
648         nsDelayedBroadcastUpdate delayedUpdate(aBroadcaster, aListener,
649                                                aAttr);
650         mDelayedBroadcasters.AppendElement(delayedUpdate);
651         MaybeBroadcast();
652         return;
653     }
654     bool notify = mDocumentLoaded || mHandlingDelayedBroadcasters;
655 
656     if (aAttr.EqualsLiteral("*")) {
657         uint32_t count = aBroadcaster->GetAttrCount();
658         nsTArray<nsAttrNameInfo> attributes(count);
659         for (uint32_t i = 0; i < count; ++i) {
660             const nsAttrName* attrName = aBroadcaster->GetAttrNameAt(i);
661             int32_t nameSpaceID = attrName->NamespaceID();
662             nsIAtom* name = attrName->LocalName();
663 
664             // _Don't_ push the |id|, |ref|, or |persist| attribute's value!
665             if (! CanBroadcast(nameSpaceID, name))
666                 continue;
667 
668             attributes.AppendElement(nsAttrNameInfo(nameSpaceID, name,
669                                                     attrName->GetPrefix()));
670         }
671 
672         count = attributes.Length();
673         while (count-- > 0) {
674             int32_t nameSpaceID = attributes[count].mNamespaceID;
675             nsIAtom* name = attributes[count].mName;
676             nsAutoString value;
677             if (aBroadcaster->GetAttr(nameSpaceID, name, value)) {
678               aListener->SetAttr(nameSpaceID, name, attributes[count].mPrefix,
679                                  value, notify);
680             }
681 
682 #if 0
683             // XXX we don't fire the |onbroadcast| handler during
684             // initial hookup: doing so would potentially run the
685             // |onbroadcast| handler before the |onload| handler,
686             // which could define JS properties that mask XBL
687             // properties, etc.
688             ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
689 #endif
690         }
691     }
692     else {
693         // Find out if the attribute is even present at all.
694         nsCOMPtr<nsIAtom> name = NS_Atomize(aAttr);
695 
696         nsAutoString value;
697         if (aBroadcaster->GetAttr(kNameSpaceID_None, name, value)) {
698             aListener->SetAttr(kNameSpaceID_None, name, value, notify);
699         } else {
700             aListener->UnsetAttr(kNameSpaceID_None, name, notify);
701         }
702 
703 #if 0
704         // XXX we don't fire the |onbroadcast| handler during initial
705         // hookup: doing so would potentially run the |onbroadcast|
706         // handler before the |onload| handler, which could define JS
707         // properties that mask XBL properties, etc.
708         ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
709 #endif
710     }
711 }
712 
713 NS_IMETHODIMP
AddBroadcastListenerFor(nsIDOMElement * aBroadcaster,nsIDOMElement * aListener,const nsAString & aAttr)714 XULDocument::AddBroadcastListenerFor(nsIDOMElement* aBroadcaster,
715                                      nsIDOMElement* aListener,
716                                      const nsAString& aAttr)
717 {
718     ErrorResult rv;
719     nsCOMPtr<Element> broadcaster = do_QueryInterface(aBroadcaster);
720     nsCOMPtr<Element> listener = do_QueryInterface(aListener);
721     NS_ENSURE_ARG(broadcaster && listener);
722     AddBroadcastListenerFor(*broadcaster, *listener, aAttr, rv);
723     return rv.StealNSResult();
724 }
725 
726 void
AddBroadcastListenerFor(Element & aBroadcaster,Element & aListener,const nsAString & aAttr,ErrorResult & aRv)727 XULDocument::AddBroadcastListenerFor(Element& aBroadcaster, Element& aListener,
728                                      const nsAString& aAttr, ErrorResult& aRv)
729 {
730     nsresult rv =
731         nsContentUtils::CheckSameOrigin(this, &aBroadcaster);
732 
733     if (NS_FAILED(rv)) {
734         aRv.Throw(rv);
735         return;
736     }
737 
738     rv = nsContentUtils::CheckSameOrigin(this, &aListener);
739 
740     if (NS_FAILED(rv)) {
741         aRv.Throw(rv);
742         return;
743     }
744 
745     static const PLDHashTableOps gOps = {
746         PLDHashTable::HashVoidPtrKeyStub,
747         PLDHashTable::MatchEntryStub,
748         PLDHashTable::MoveEntryStub,
749         ClearBroadcasterMapEntry,
750         nullptr
751     };
752 
753     if (! mBroadcasterMap) {
754         mBroadcasterMap = new PLDHashTable(&gOps, sizeof(BroadcasterMapEntry));
755     }
756 
757     auto entry = static_cast<BroadcasterMapEntry*>
758                             (mBroadcasterMap->Search(&aBroadcaster));
759     if (!entry) {
760         entry = static_cast<BroadcasterMapEntry*>
761                            (mBroadcasterMap->Add(&aBroadcaster, fallible));
762 
763         if (! entry) {
764             aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
765             return;
766         }
767 
768         entry->mBroadcaster = &aBroadcaster;
769 
770         // N.B. placement new to construct the nsTArray object in-place
771         new (&entry->mListeners) nsTArray<BroadcastListener*>();
772     }
773 
774     // Only add the listener if it's not there already!
775     nsCOMPtr<nsIAtom> attr = NS_Atomize(aAttr);
776 
777     for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
778         BroadcastListener* bl = entry->mListeners[i];
779         nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
780 
781         if (blListener == &aListener && bl->mAttribute == attr)
782             return;
783     }
784 
785     BroadcastListener* bl = new BroadcastListener;
786     bl->mListener  = do_GetWeakReference(&aListener);
787     bl->mAttribute = attr;
788 
789     entry->mListeners.AppendElement(bl);
790 
791     SynchronizeBroadcastListener(&aBroadcaster, &aListener, aAttr);
792 }
793 
794 NS_IMETHODIMP
RemoveBroadcastListenerFor(nsIDOMElement * aBroadcaster,nsIDOMElement * aListener,const nsAString & aAttr)795 XULDocument::RemoveBroadcastListenerFor(nsIDOMElement* aBroadcaster,
796                                         nsIDOMElement* aListener,
797                                         const nsAString& aAttr)
798 {
799     nsCOMPtr<Element> broadcaster = do_QueryInterface(aBroadcaster);
800     nsCOMPtr<Element> listener = do_QueryInterface(aListener);
801     NS_ENSURE_ARG(broadcaster && listener);
802     RemoveBroadcastListenerFor(*broadcaster, *listener, aAttr);
803     return NS_OK;
804 }
805 
806 void
RemoveBroadcastListenerFor(Element & aBroadcaster,Element & aListener,const nsAString & aAttr)807 XULDocument::RemoveBroadcastListenerFor(Element& aBroadcaster,
808                                         Element& aListener,
809                                         const nsAString& aAttr)
810 {
811     // If we haven't added any broadcast listeners, then there sure
812     // aren't any to remove.
813     if (! mBroadcasterMap)
814         return;
815 
816     auto entry = static_cast<BroadcasterMapEntry*>
817                             (mBroadcasterMap->Search(&aBroadcaster));
818     if (entry) {
819         nsCOMPtr<nsIAtom> attr = NS_Atomize(aAttr);
820         for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
821             BroadcastListener* bl = entry->mListeners[i];
822             nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
823 
824             if (blListener == &aListener && bl->mAttribute == attr) {
825                 entry->mListeners.RemoveElementAt(i);
826                 delete bl;
827 
828                 if (entry->mListeners.IsEmpty())
829                     mBroadcasterMap->RemoveEntry(entry);
830 
831                 break;
832             }
833         }
834     }
835 }
836 
837 nsresult
ExecuteOnBroadcastHandlerFor(Element * aBroadcaster,Element * aListener,nsIAtom * aAttr)838 XULDocument::ExecuteOnBroadcastHandlerFor(Element* aBroadcaster,
839                                           Element* aListener,
840                                           nsIAtom* aAttr)
841 {
842     // Now we execute the onchange handler in the context of the
843     // observer. We need to find the observer in order to
844     // execute the handler.
845 
846     for (nsIContent* child = aListener->GetFirstChild();
847          child;
848          child = child->GetNextSibling()) {
849 
850         // Look for an <observes> element beneath the listener. This
851         // ought to have an |element| attribute that refers to
852         // aBroadcaster, and an |attribute| element that tells us what
853         // attriubtes we're listening for.
854         if (!child->NodeInfo()->Equals(nsGkAtoms::observes, kNameSpaceID_XUL))
855             continue;
856 
857         // Is this the element that was listening to us?
858         nsAutoString listeningToID;
859         child->GetAttr(kNameSpaceID_None, nsGkAtoms::element, listeningToID);
860 
861         nsAutoString broadcasterID;
862         aBroadcaster->GetAttr(kNameSpaceID_None, nsGkAtoms::id, broadcasterID);
863 
864         if (listeningToID != broadcasterID)
865             continue;
866 
867         // We are observing the broadcaster, but is this the right
868         // attribute?
869         nsAutoString listeningToAttribute;
870         child->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute,
871                        listeningToAttribute);
872 
873         if (!aAttr->Equals(listeningToAttribute) &&
874             !listeningToAttribute.EqualsLiteral("*")) {
875             continue;
876         }
877 
878         // This is the right <observes> element. Execute the
879         // |onbroadcast| event handler
880         WidgetEvent event(true, eXULBroadcast);
881 
882         nsCOMPtr<nsIPresShell> shell = GetShell();
883         if (shell) {
884             RefPtr<nsPresContext> aPresContext = shell->GetPresContext();
885 
886             // Handle the DOM event
887             nsEventStatus status = nsEventStatus_eIgnore;
888             EventDispatcher::Dispatch(child, aPresContext, &event, nullptr,
889                                       &status);
890         }
891     }
892 
893     return NS_OK;
894 }
895 
896 void
AttributeWillChange(nsIDocument * aDocument,Element * aElement,int32_t aNameSpaceID,nsIAtom * aAttribute,int32_t aModType,const nsAttrValue * aNewValue)897 XULDocument::AttributeWillChange(nsIDocument* aDocument,
898                                  Element* aElement, int32_t aNameSpaceID,
899                                  nsIAtom* aAttribute, int32_t aModType,
900                                  const nsAttrValue* aNewValue)
901 {
902     MOZ_ASSERT(aElement, "Null content!");
903     NS_PRECONDITION(aAttribute, "Must have an attribute that's changing!");
904 
905     // XXXbz check aNameSpaceID, dammit!
906     // See if we need to update our ref map.
907     if (aAttribute == nsGkAtoms::ref) {
908         // Might not need this, but be safe for now.
909         nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
910         RemoveElementFromRefMap(aElement);
911     }
912 }
913 
914 static bool
ShouldPersistAttribute(Element * aElement,nsIAtom * aAttribute)915 ShouldPersistAttribute(Element* aElement, nsIAtom* aAttribute)
916 {
917     if (aElement->IsXULElement(nsGkAtoms::window)) {
918         // This is not an element of the top document, its owner is
919         // not an nsXULWindow. Persist it.
920         if (aElement->OwnerDoc()->GetParentDocument()) {
921             return true;
922         }
923         // The following attributes of xul:window should be handled in
924         // nsXULWindow::SavePersistentAttributes instead of here.
925         if (aAttribute == nsGkAtoms::screenX ||
926             aAttribute == nsGkAtoms::screenY ||
927             aAttribute == nsGkAtoms::width ||
928             aAttribute == nsGkAtoms::height ||
929             aAttribute == nsGkAtoms::sizemode) {
930             return false;
931         }
932     }
933     return true;
934 }
935 
936 void
AttributeChanged(nsIDocument * aDocument,Element * aElement,int32_t aNameSpaceID,nsIAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)937 XULDocument::AttributeChanged(nsIDocument* aDocument,
938                               Element* aElement, int32_t aNameSpaceID,
939                               nsIAtom* aAttribute, int32_t aModType,
940                               const nsAttrValue* aOldValue)
941 {
942     NS_ASSERTION(aDocument == this, "unexpected doc");
943 
944     // Might not need this, but be safe for now.
945     nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
946 
947     // XXXbz check aNameSpaceID, dammit!
948     // See if we need to update our ref map.
949     if (aAttribute == nsGkAtoms::ref) {
950         AddElementToRefMap(aElement);
951     }
952 
953     // Synchronize broadcast listeners
954     if (mBroadcasterMap &&
955         CanBroadcast(aNameSpaceID, aAttribute)) {
956         auto entry = static_cast<BroadcasterMapEntry*>
957                                 (mBroadcasterMap->Search(aElement));
958 
959         if (entry) {
960             // We've got listeners: push the value.
961             nsAutoString value;
962             bool attrSet = aElement->GetAttr(kNameSpaceID_None, aAttribute, value);
963 
964             for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
965                 BroadcastListener* bl = entry->mListeners[i];
966                 if ((bl->mAttribute == aAttribute) ||
967                     (bl->mAttribute == nsGkAtoms::_asterisk)) {
968                     nsCOMPtr<Element> listenerEl
969                         = do_QueryReferent(bl->mListener);
970                     if (listenerEl) {
971                         nsAutoString currentValue;
972                         bool hasAttr = listenerEl->GetAttr(kNameSpaceID_None,
973                                                            aAttribute,
974                                                            currentValue);
975                         // We need to update listener only if we're
976                         // (1) removing an existing attribute,
977                         // (2) adding a new attribute or
978                         // (3) changing the value of an attribute.
979                         bool needsAttrChange =
980                             attrSet != hasAttr || !value.Equals(currentValue);
981                         nsDelayedBroadcastUpdate delayedUpdate(aElement,
982                                                                listenerEl,
983                                                                aAttribute,
984                                                                value,
985                                                                attrSet,
986                                                                needsAttrChange);
987 
988                         size_t index =
989                             mDelayedAttrChangeBroadcasts.IndexOf(delayedUpdate,
990                                 0, nsDelayedBroadcastUpdate::Comparator());
991                         if (index != mDelayedAttrChangeBroadcasts.NoIndex) {
992                             if (mHandlingDelayedAttrChange) {
993                                 NS_WARNING("Broadcasting loop!");
994                                 continue;
995                             }
996                             mDelayedAttrChangeBroadcasts.RemoveElementAt(index);
997                         }
998 
999                         mDelayedAttrChangeBroadcasts.AppendElement(delayedUpdate);
1000                     }
1001                 }
1002             }
1003         }
1004     }
1005 
1006     // checks for modifications in broadcasters
1007     bool listener, resolved;
1008     CheckBroadcasterHookup(aElement, &listener, &resolved);
1009 
1010     // See if there is anything we need to persist in the localstore.
1011     //
1012     // XXX Namespace handling broken :-(
1013     nsAutoString persist;
1014     aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist);
1015     // Persistence of attributes of xul:window is handled in nsXULWindow.
1016     if (ShouldPersistAttribute(aElement, aAttribute) && !persist.IsEmpty() &&
1017         // XXXldb This should check that it's a token, not just a substring.
1018         persist.Find(nsDependentAtomString(aAttribute)) >= 0) {
1019         nsContentUtils::AddScriptRunner(NewRunnableMethod
1020             <nsIContent*, int32_t, nsIAtom*>
1021             (this, &XULDocument::DoPersist, aElement, kNameSpaceID_None,
1022             aAttribute));
1023     }
1024 }
1025 
1026 void
ContentAppended(nsIDocument * aDocument,nsIContent * aContainer,nsIContent * aFirstNewContent,int32_t aNewIndexInContainer)1027 XULDocument::ContentAppended(nsIDocument* aDocument,
1028                              nsIContent* aContainer,
1029                              nsIContent* aFirstNewContent,
1030                              int32_t aNewIndexInContainer)
1031 {
1032     NS_ASSERTION(aDocument == this, "unexpected doc");
1033 
1034     // Might not need this, but be safe for now.
1035     nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
1036 
1037     // Update our element map
1038     nsresult rv = NS_OK;
1039     for (nsIContent* cur = aFirstNewContent; cur && NS_SUCCEEDED(rv);
1040          cur = cur->GetNextSibling()) {
1041         rv = AddSubtreeToDocument(cur);
1042     }
1043 }
1044 
1045 void
ContentInserted(nsIDocument * aDocument,nsIContent * aContainer,nsIContent * aChild,int32_t aIndexInContainer)1046 XULDocument::ContentInserted(nsIDocument* aDocument,
1047                              nsIContent* aContainer,
1048                              nsIContent* aChild,
1049                              int32_t aIndexInContainer)
1050 {
1051     NS_ASSERTION(aDocument == this, "unexpected doc");
1052 
1053     // Might not need this, but be safe for now.
1054     nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
1055 
1056     AddSubtreeToDocument(aChild);
1057 }
1058 
1059 void
ContentRemoved(nsIDocument * aDocument,nsIContent * aContainer,nsIContent * aChild,int32_t aIndexInContainer,nsIContent * aPreviousSibling)1060 XULDocument::ContentRemoved(nsIDocument* aDocument,
1061                             nsIContent* aContainer,
1062                             nsIContent* aChild,
1063                             int32_t aIndexInContainer,
1064                             nsIContent* aPreviousSibling)
1065 {
1066     NS_ASSERTION(aDocument == this, "unexpected doc");
1067 
1068     // Might not need this, but be safe for now.
1069     nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
1070 
1071     RemoveSubtreeFromDocument(aChild);
1072 }
1073 
1074 //----------------------------------------------------------------------
1075 //
1076 // nsIXULDocument interface
1077 //
1078 
1079 void
GetElementsForID(const nsAString & aID,nsCOMArray<nsIContent> & aElements)1080 XULDocument::GetElementsForID(const nsAString& aID,
1081                               nsCOMArray<nsIContent>& aElements)
1082 {
1083     aElements.Clear();
1084 
1085     nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aID);
1086     if (entry) {
1087         entry->AppendAllIdContent(&aElements);
1088     }
1089     nsRefMapEntry *refEntry = mRefMap.GetEntry(aID);
1090     if (refEntry) {
1091         refEntry->AppendAll(&aElements);
1092     }
1093 }
1094 
1095 nsresult
AddForwardReference(nsForwardReference * aRef)1096 XULDocument::AddForwardReference(nsForwardReference* aRef)
1097 {
1098     if (mResolutionPhase < aRef->GetPhase()) {
1099         if (!mForwardReferences.AppendElement(aRef)) {
1100             delete aRef;
1101             return NS_ERROR_OUT_OF_MEMORY;
1102         }
1103     }
1104     else {
1105         NS_ERROR("forward references have already been resolved");
1106         delete aRef;
1107     }
1108 
1109     return NS_OK;
1110 }
1111 
1112 nsresult
ResolveForwardReferences()1113 XULDocument::ResolveForwardReferences()
1114 {
1115     if (mResolutionPhase == nsForwardReference::eDone)
1116         return NS_OK;
1117 
1118     NS_ASSERTION(mResolutionPhase == nsForwardReference::eStart,
1119                  "nested ResolveForwardReferences()");
1120 
1121     // Resolve each outstanding 'forward' reference. We iterate
1122     // through the list of forward references until no more forward
1123     // references can be resolved. This annealing process is
1124     // guaranteed to converge because we've "closed the gate" to new
1125     // forward references.
1126 
1127     const nsForwardReference::Phase* pass = nsForwardReference::kPasses;
1128     while ((mResolutionPhase = *pass) != nsForwardReference::eDone) {
1129         uint32_t previous = 0;
1130         while (mForwardReferences.Length() &&
1131                mForwardReferences.Length() != previous) {
1132             previous = mForwardReferences.Length();
1133 
1134             for (uint32_t i = 0; i < mForwardReferences.Length(); ++i) {
1135                 nsForwardReference* fwdref = mForwardReferences[i];
1136 
1137                 if (fwdref->GetPhase() == *pass) {
1138                     nsForwardReference::Result result = fwdref->Resolve();
1139 
1140                     switch (result) {
1141                     case nsForwardReference::eResolve_Succeeded:
1142                     case nsForwardReference::eResolve_Error:
1143                         mForwardReferences.RemoveElementAt(i);
1144 
1145                         // fixup because we removed from list
1146                         --i;
1147                         break;
1148 
1149                     case nsForwardReference::eResolve_Later:
1150                         // do nothing. we'll try again later
1151                         ;
1152                     }
1153 
1154                     if (mResolutionPhase == nsForwardReference::eStart) {
1155                         // Resolve() loaded a dynamic overlay,
1156                         // (see XULDocument::LoadOverlayInternal()).
1157                         // Return for now, we will be called again.
1158                         return NS_OK;
1159                     }
1160                 }
1161             }
1162         }
1163 
1164         ++pass;
1165     }
1166 
1167     mForwardReferences.Clear();
1168     return NS_OK;
1169 }
1170 
1171 //----------------------------------------------------------------------
1172 //
1173 // nsIDOMDocument interface
1174 //
1175 
1176 NS_IMETHODIMP
GetElementsByAttribute(const nsAString & aAttribute,const nsAString & aValue,nsIDOMNodeList ** aReturn)1177 XULDocument::GetElementsByAttribute(const nsAString& aAttribute,
1178                                     const nsAString& aValue,
1179                                     nsIDOMNodeList** aReturn)
1180 {
1181     *aReturn = GetElementsByAttribute(aAttribute, aValue).take();
1182     return NS_OK;
1183 }
1184 
1185 already_AddRefed<nsINodeList>
GetElementsByAttribute(const nsAString & aAttribute,const nsAString & aValue)1186 XULDocument::GetElementsByAttribute(const nsAString& aAttribute,
1187                                     const nsAString& aValue)
1188 {
1189     nsCOMPtr<nsIAtom> attrAtom(NS_Atomize(aAttribute));
1190     void* attrValue = new nsString(aValue);
1191     RefPtr<nsContentList> list = new nsContentList(this,
1192                                             MatchAttribute,
1193                                             nsContentUtils::DestroyMatchString,
1194                                             attrValue,
1195                                             true,
1196                                             attrAtom,
1197                                             kNameSpaceID_Unknown);
1198 
1199     return list.forget();
1200 }
1201 
1202 NS_IMETHODIMP
GetElementsByAttributeNS(const nsAString & aNamespaceURI,const nsAString & aAttribute,const nsAString & aValue,nsIDOMNodeList ** aReturn)1203 XULDocument::GetElementsByAttributeNS(const nsAString& aNamespaceURI,
1204                                       const nsAString& aAttribute,
1205                                       const nsAString& aValue,
1206                                       nsIDOMNodeList** aReturn)
1207 {
1208     ErrorResult rv;
1209     *aReturn = GetElementsByAttributeNS(aNamespaceURI, aAttribute,
1210                                         aValue, rv).take();
1211     return rv.StealNSResult();
1212 }
1213 
1214 already_AddRefed<nsINodeList>
GetElementsByAttributeNS(const nsAString & aNamespaceURI,const nsAString & aAttribute,const nsAString & aValue,ErrorResult & aRv)1215 XULDocument::GetElementsByAttributeNS(const nsAString& aNamespaceURI,
1216                                       const nsAString& aAttribute,
1217                                       const nsAString& aValue,
1218                                       ErrorResult& aRv)
1219 {
1220     nsCOMPtr<nsIAtom> attrAtom(NS_Atomize(aAttribute));
1221     void* attrValue = new nsString(aValue);
1222 
1223     int32_t nameSpaceId = kNameSpaceID_Wildcard;
1224     if (!aNamespaceURI.EqualsLiteral("*")) {
1225       nsresult rv =
1226         nsContentUtils::NameSpaceManager()->RegisterNameSpace(aNamespaceURI,
1227                                                               nameSpaceId);
1228       if (NS_FAILED(rv)) {
1229           aRv.Throw(rv);
1230           return nullptr;
1231       }
1232     }
1233 
1234     RefPtr<nsContentList> list = new nsContentList(this,
1235                                             MatchAttribute,
1236                                             nsContentUtils::DestroyMatchString,
1237                                             attrValue,
1238                                             true,
1239                                             attrAtom,
1240                                             nameSpaceId);
1241     return list.forget();
1242 }
1243 
1244 NS_IMETHODIMP
Persist(const nsAString & aID,const nsAString & aAttr)1245 XULDocument::Persist(const nsAString& aID,
1246                      const nsAString& aAttr)
1247 {
1248     // If we're currently reading persisted attributes out of the
1249     // localstore, _don't_ re-enter and try to set them again!
1250     if (mApplyingPersistedAttrs)
1251         return NS_OK;
1252 
1253     Element* element = nsDocument::GetElementById(aID);
1254     if (!element)
1255         return NS_OK;
1256 
1257     nsCOMPtr<nsIAtom> tag;
1258     int32_t nameSpaceID;
1259 
1260     RefPtr<mozilla::dom::NodeInfo> ni = element->GetExistingAttrNameFromQName(aAttr);
1261     nsresult rv;
1262     if (ni) {
1263         tag = ni->NameAtom();
1264         nameSpaceID = ni->NamespaceID();
1265     }
1266     else {
1267         // Make sure that this QName is going to be valid.
1268         const char16_t *colon;
1269         rv = nsContentUtils::CheckQName(PromiseFlatString(aAttr), true, &colon);
1270 
1271         if (NS_FAILED(rv)) {
1272             // There was an invalid character or it was malformed.
1273             return NS_ERROR_INVALID_ARG;
1274         }
1275 
1276         if (colon) {
1277             // We don't really handle namespace qualifiers in attribute names.
1278             return NS_ERROR_NOT_IMPLEMENTED;
1279         }
1280 
1281         tag = NS_Atomize(aAttr);
1282         NS_ENSURE_TRUE(tag, NS_ERROR_OUT_OF_MEMORY);
1283 
1284         nameSpaceID = kNameSpaceID_None;
1285     }
1286 
1287     return Persist(element, nameSpaceID, tag);
1288 }
1289 
1290 nsresult
Persist(nsIContent * aElement,int32_t aNameSpaceID,nsIAtom * aAttribute)1291 XULDocument::Persist(nsIContent* aElement, int32_t aNameSpaceID,
1292                      nsIAtom* aAttribute)
1293 {
1294     // For non-chrome documents, persistance is simply broken
1295     if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()))
1296         return NS_ERROR_NOT_AVAILABLE;
1297 
1298     if (!mLocalStore) {
1299         mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
1300         if (NS_WARN_IF(!mLocalStore)) {
1301             return NS_ERROR_NOT_INITIALIZED;
1302         }
1303     }
1304 
1305     nsAutoString id;
1306 
1307     aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
1308     nsAtomString attrstr(aAttribute);
1309 
1310     nsAutoString valuestr;
1311     aElement->GetAttr(kNameSpaceID_None, aAttribute, valuestr);
1312 
1313     nsAutoCString utf8uri;
1314     nsresult rv = mDocumentURI->GetSpec(utf8uri);
1315     if (NS_WARN_IF(NS_FAILED(rv))) {
1316         return rv;
1317     }
1318     NS_ConvertUTF8toUTF16 uri(utf8uri);
1319 
1320     bool hasAttr;
1321     rv = mLocalStore->HasValue(uri, id, attrstr, &hasAttr);
1322     if (NS_WARN_IF(NS_FAILED(rv))) {
1323         return rv;
1324     }
1325 
1326     if (hasAttr && valuestr.IsEmpty()) {
1327         return mLocalStore->RemoveValue(uri, id, attrstr);
1328     } else {
1329         return mLocalStore->SetValue(uri, id, attrstr, valuestr);
1330     }
1331 }
1332 
1333 
1334 nsresult
GetViewportSize(int32_t * aWidth,int32_t * aHeight)1335 XULDocument::GetViewportSize(int32_t* aWidth,
1336                              int32_t* aHeight)
1337 {
1338     *aWidth = *aHeight = 0;
1339 
1340     FlushPendingNotifications(Flush_Layout);
1341 
1342     nsIPresShell *shell = GetShell();
1343     NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
1344 
1345     nsIFrame* frame = shell->GetRootFrame();
1346     NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
1347 
1348     nsSize size = frame->GetSize();
1349 
1350     *aWidth = nsPresContext::AppUnitsToIntCSSPixels(size.width);
1351     *aHeight = nsPresContext::AppUnitsToIntCSSPixels(size.height);
1352 
1353     return NS_OK;
1354 }
1355 
1356 NS_IMETHODIMP
GetWidth(int32_t * aWidth)1357 XULDocument::GetWidth(int32_t* aWidth)
1358 {
1359     NS_ENSURE_ARG_POINTER(aWidth);
1360 
1361     int32_t height;
1362     return GetViewportSize(aWidth, &height);
1363 }
1364 
1365 int32_t
GetWidth(ErrorResult & aRv)1366 XULDocument::GetWidth(ErrorResult& aRv)
1367 {
1368     int32_t width;
1369     aRv = GetWidth(&width);
1370     return width;
1371 }
1372 
1373 NS_IMETHODIMP
GetHeight(int32_t * aHeight)1374 XULDocument::GetHeight(int32_t* aHeight)
1375 {
1376     NS_ENSURE_ARG_POINTER(aHeight);
1377 
1378     int32_t width;
1379     return GetViewportSize(&width, aHeight);
1380 }
1381 
1382 int32_t
GetHeight(ErrorResult & aRv)1383 XULDocument::GetHeight(ErrorResult& aRv)
1384 {
1385     int32_t height;
1386     aRv = GetHeight(&height);
1387     return height;
1388 }
1389 
1390 JSObject*
GetScopeObjectOfNode(nsIDOMNode * node)1391 GetScopeObjectOfNode(nsIDOMNode* node)
1392 {
1393     MOZ_ASSERT(node, "Must not be called with null.");
1394 
1395     // Window root occasionally keeps alive a node of a document whose
1396     // window is already dead. If in this brief period someone calls
1397     // GetPopupNode and we return that node, nsNodeSH::PreCreate will throw,
1398     // because it will not know which scope this node belongs to. Returning
1399     // an orphan node like that to JS would be a bug anyway, so to avoid
1400     // this, let's do the same check as nsNodeSH::PreCreate does to
1401     // determine the scope and if it fails let's just return null in
1402     // XULDocument::GetPopupNode.
1403     nsCOMPtr<nsINode> inode = do_QueryInterface(node);
1404     MOZ_ASSERT(inode, "How can this happen?");
1405 
1406     nsIDocument* doc = inode->OwnerDoc();
1407     MOZ_ASSERT(inode, "This should never happen.");
1408 
1409     nsIGlobalObject* global = doc->GetScopeObject();
1410     return global ? global->GetGlobalJSObject() : nullptr;
1411 }
1412 
1413 //----------------------------------------------------------------------
1414 //
1415 // nsIDOMXULDocument interface
1416 //
1417 
1418 NS_IMETHODIMP
GetPopupNode(nsIDOMNode ** aNode)1419 XULDocument::GetPopupNode(nsIDOMNode** aNode)
1420 {
1421     *aNode = nullptr;
1422 
1423     nsCOMPtr<nsIDOMNode> node;
1424     nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
1425     if (rootWin)
1426         node = rootWin->GetPopupNode(); // addref happens here
1427 
1428     if (!node) {
1429         nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1430         if (pm) {
1431             node = pm->GetLastTriggerPopupNode(this);
1432         }
1433     }
1434 
1435     if (node && nsContentUtils::CanCallerAccess(node)
1436         && GetScopeObjectOfNode(node)) {
1437         node.forget(aNode);
1438     }
1439 
1440     return NS_OK;
1441 }
1442 
1443 already_AddRefed<nsINode>
GetPopupNode()1444 XULDocument::GetPopupNode()
1445 {
1446     nsCOMPtr<nsIDOMNode> node;
1447     DebugOnly<nsresult> rv = GetPopupNode(getter_AddRefs(node));
1448     MOZ_ASSERT(NS_SUCCEEDED(rv));
1449     nsCOMPtr<nsINode> retval(do_QueryInterface(node));
1450     return retval.forget();
1451 }
1452 
1453 NS_IMETHODIMP
SetPopupNode(nsIDOMNode * aNode)1454 XULDocument::SetPopupNode(nsIDOMNode* aNode)
1455 {
1456     if (aNode) {
1457         // only allow real node objects
1458         nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
1459         NS_ENSURE_ARG(node);
1460     }
1461 
1462     nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
1463     if (rootWin)
1464         rootWin->SetPopupNode(aNode); // addref happens here
1465 
1466     return NS_OK;
1467 }
1468 
1469 void
SetPopupNode(nsINode * aNode)1470 XULDocument::SetPopupNode(nsINode* aNode)
1471 {
1472     nsCOMPtr<nsIDOMNode> node(do_QueryInterface(aNode));
1473     DebugOnly<nsresult> rv = SetPopupNode(node);
1474     MOZ_ASSERT(NS_SUCCEEDED(rv));
1475 }
1476 
1477 // Returns the rangeOffset element from the XUL Popup Manager. This is for
1478 // chrome callers only.
1479 NS_IMETHODIMP
GetPopupRangeParent(nsIDOMNode ** aRangeParent)1480 XULDocument::GetPopupRangeParent(nsIDOMNode** aRangeParent)
1481 {
1482     NS_ENSURE_ARG_POINTER(aRangeParent);
1483     *aRangeParent = nullptr;
1484 
1485     nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1486     if (!pm)
1487         return NS_ERROR_FAILURE;
1488 
1489     int32_t offset;
1490     pm->GetMouseLocation(aRangeParent, &offset);
1491 
1492     if (*aRangeParent && !nsContentUtils::CanCallerAccess(*aRangeParent)) {
1493         NS_RELEASE(*aRangeParent);
1494         return NS_ERROR_DOM_SECURITY_ERR;
1495     }
1496 
1497     return NS_OK;
1498 }
1499 
1500 already_AddRefed<nsINode>
GetPopupRangeParent(ErrorResult & aRv)1501 XULDocument::GetPopupRangeParent(ErrorResult& aRv)
1502 {
1503     nsCOMPtr<nsIDOMNode> node;
1504     aRv = GetPopupRangeParent(getter_AddRefs(node));
1505     nsCOMPtr<nsINode> retval(do_QueryInterface(node));
1506     return retval.forget();
1507 }
1508 
1509 
1510 // Returns the rangeOffset element from the XUL Popup Manager. We check the
1511 // rangeParent to determine if the caller has rights to access to the data.
1512 NS_IMETHODIMP
GetPopupRangeOffset(int32_t * aRangeOffset)1513 XULDocument::GetPopupRangeOffset(int32_t* aRangeOffset)
1514 {
1515     ErrorResult rv;
1516     *aRangeOffset = GetPopupRangeOffset(rv);
1517     return rv.StealNSResult();
1518 }
1519 
1520 int32_t
GetPopupRangeOffset(ErrorResult & aRv)1521 XULDocument::GetPopupRangeOffset(ErrorResult& aRv)
1522 {
1523     nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1524     if (!pm) {
1525         aRv.Throw(NS_ERROR_FAILURE);
1526         return 0;
1527     }
1528 
1529     int32_t offset;
1530     nsCOMPtr<nsIDOMNode> parent;
1531     pm->GetMouseLocation(getter_AddRefs(parent), &offset);
1532 
1533     if (parent && !nsContentUtils::CanCallerAccess(parent)) {
1534         aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
1535         return 0;
1536     }
1537     return offset;
1538 }
1539 
1540 NS_IMETHODIMP
GetTooltipNode(nsIDOMNode ** aNode)1541 XULDocument::GetTooltipNode(nsIDOMNode** aNode)
1542 {
1543     *aNode = nullptr;
1544 
1545     nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1546     if (pm) {
1547         nsCOMPtr<nsIDOMNode> node = pm->GetLastTriggerTooltipNode(this);
1548         if (node && nsContentUtils::CanCallerAccess(node))
1549             node.forget(aNode);
1550     }
1551 
1552     return NS_OK;
1553 }
1554 
1555 already_AddRefed<nsINode>
GetTooltipNode()1556 XULDocument::GetTooltipNode()
1557 {
1558     nsCOMPtr<nsIDOMNode> node;
1559     DebugOnly<nsresult> rv = GetTooltipNode(getter_AddRefs(node));
1560     MOZ_ASSERT(NS_SUCCEEDED(rv));
1561     nsCOMPtr<nsINode> retval(do_QueryInterface(node));
1562     return retval.forget();
1563 }
1564 
1565 NS_IMETHODIMP
SetTooltipNode(nsIDOMNode * aNode)1566 XULDocument::SetTooltipNode(nsIDOMNode* aNode)
1567 {
1568     // do nothing
1569     return NS_OK;
1570 }
1571 
1572 
1573 NS_IMETHODIMP
GetCommandDispatcher(nsIDOMXULCommandDispatcher ** aTracker)1574 XULDocument::GetCommandDispatcher(nsIDOMXULCommandDispatcher** aTracker)
1575 {
1576     *aTracker = mCommandDispatcher;
1577     NS_IF_ADDREF(*aTracker);
1578     return NS_OK;
1579 }
1580 
1581 Element*
GetElementById(const nsAString & aId)1582 XULDocument::GetElementById(const nsAString& aId)
1583 {
1584     if (!CheckGetElementByIdArg(aId))
1585         return nullptr;
1586 
1587     nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aId);
1588     if (entry) {
1589         Element* element = entry->GetIdElement();
1590         if (element)
1591             return element;
1592     }
1593 
1594     nsRefMapEntry* refEntry = mRefMap.GetEntry(aId);
1595     if (refEntry) {
1596         NS_ASSERTION(refEntry->GetFirstElement(),
1597                      "nsRefMapEntries should have nonempty content lists");
1598         return refEntry->GetFirstElement();
1599     }
1600     return nullptr;
1601 }
1602 
1603 nsresult
AddElementToDocumentPre(Element * aElement)1604 XULDocument::AddElementToDocumentPre(Element* aElement)
1605 {
1606     // Do a bunch of work that's necessary when an element gets added
1607     // to the XUL Document.
1608     nsresult rv;
1609 
1610     // 1. Add the element to the resource-to-element map. Also add it to
1611     // the id map, since it seems this can be called when creating
1612     // elements from prototypes.
1613     nsIAtom* id = aElement->GetID();
1614     if (id) {
1615         // FIXME: Shouldn't BindToTree take care of this?
1616         nsAutoScriptBlocker scriptBlocker;
1617         AddToIdTable(aElement, id);
1618     }
1619     rv = AddElementToRefMap(aElement);
1620     if (NS_FAILED(rv)) return rv;
1621 
1622     // 2. If the element is a 'command updater' (i.e., has a
1623     // "commandupdater='true'" attribute), then add the element to the
1624     // document's command dispatcher
1625     if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::commandupdater,
1626                               nsGkAtoms::_true, eCaseMatters)) {
1627         rv = nsXULContentUtils::SetCommandUpdater(this, aElement);
1628         if (NS_FAILED(rv)) return rv;
1629     }
1630 
1631     // 3. Check for a broadcaster hookup attribute, in which case
1632     // we'll hook the node up as a listener on a broadcaster.
1633     bool listener, resolved;
1634     rv = CheckBroadcasterHookup(aElement, &listener, &resolved);
1635     if (NS_FAILED(rv)) return rv;
1636 
1637     // If it's not there yet, we may be able to defer hookup until
1638     // later.
1639     if (listener && !resolved && (mResolutionPhase != nsForwardReference::eDone)) {
1640         BroadcasterHookup* hookup = new BroadcasterHookup(this, aElement);
1641         rv = AddForwardReference(hookup);
1642         if (NS_FAILED(rv)) return rv;
1643     }
1644 
1645     return NS_OK;
1646 }
1647 
1648 nsresult
AddElementToDocumentPost(Element * aElement)1649 XULDocument::AddElementToDocumentPost(Element* aElement)
1650 {
1651     // We need to pay special attention to the keyset tag to set up a listener
1652     if (aElement->NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
1653         // Create our XUL key listener and hook it up.
1654         nsXBLService::AttachGlobalKeyHandler(aElement);
1655     }
1656 
1657     // See if we need to attach a XUL template to this node
1658     bool needsHookup;
1659     nsresult rv = CheckTemplateBuilderHookup(aElement, &needsHookup);
1660     if (NS_FAILED(rv))
1661         return rv;
1662 
1663     if (needsHookup) {
1664         if (mResolutionPhase == nsForwardReference::eDone) {
1665             rv = CreateTemplateBuilder(aElement);
1666             if (NS_FAILED(rv))
1667                 return rv;
1668         }
1669         else {
1670             TemplateBuilderHookup* hookup = new TemplateBuilderHookup(aElement);
1671             rv = AddForwardReference(hookup);
1672             if (NS_FAILED(rv))
1673                 return rv;
1674         }
1675     }
1676 
1677     return NS_OK;
1678 }
1679 
1680 NS_IMETHODIMP
AddSubtreeToDocument(nsIContent * aContent)1681 XULDocument::AddSubtreeToDocument(nsIContent* aContent)
1682 {
1683     NS_ASSERTION(aContent->GetUncomposedDoc() == this, "Element not in doc!");
1684     // From here on we only care about elements.
1685     if (!aContent->IsElement()) {
1686         return NS_OK;
1687     }
1688 
1689     Element* aElement = aContent->AsElement();
1690 
1691     // Do pre-order addition magic
1692     nsresult rv = AddElementToDocumentPre(aElement);
1693     if (NS_FAILED(rv)) return rv;
1694 
1695     // Recurse to children
1696     for (nsIContent* child = aElement->GetLastChild();
1697          child;
1698          child = child->GetPreviousSibling()) {
1699 
1700         rv = AddSubtreeToDocument(child);
1701         if (NS_FAILED(rv))
1702             return rv;
1703     }
1704 
1705     // Do post-order addition magic
1706     return AddElementToDocumentPost(aElement);
1707 }
1708 
1709 NS_IMETHODIMP
RemoveSubtreeFromDocument(nsIContent * aContent)1710 XULDocument::RemoveSubtreeFromDocument(nsIContent* aContent)
1711 {
1712     // From here on we only care about elements.
1713     if (!aContent->IsElement()) {
1714         return NS_OK;
1715     }
1716 
1717     Element* aElement = aContent->AsElement();
1718 
1719     // Do a bunch of cleanup to remove an element from the XUL
1720     // document.
1721     nsresult rv;
1722 
1723     if (aElement->NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
1724         nsXBLService::DetachGlobalKeyHandler(aElement);
1725     }
1726 
1727     // 1. Remove any children from the document.
1728     for (nsIContent* child = aElement->GetLastChild();
1729          child;
1730          child = child->GetPreviousSibling()) {
1731 
1732         rv = RemoveSubtreeFromDocument(child);
1733         if (NS_FAILED(rv))
1734             return rv;
1735     }
1736 
1737     // 2. Remove the element from the resource-to-element map.
1738     // Also remove it from the id map, since we added it in
1739     // AddElementToDocumentPre().
1740     RemoveElementFromRefMap(aElement);
1741     nsIAtom* id = aElement->GetID();
1742     if (id) {
1743         // FIXME: Shouldn't UnbindFromTree take care of this?
1744         nsAutoScriptBlocker scriptBlocker;
1745         RemoveFromIdTable(aElement, id);
1746     }
1747 
1748     // 3. If the element is a 'command updater', then remove the
1749     // element from the document's command dispatcher.
1750     if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::commandupdater,
1751                               nsGkAtoms::_true, eCaseMatters)) {
1752         nsCOMPtr<nsIDOMElement> domelement = do_QueryInterface(aElement);
1753         NS_ASSERTION(domelement != nullptr, "not a DOM element");
1754         if (! domelement)
1755             return NS_ERROR_UNEXPECTED;
1756 
1757         rv = mCommandDispatcher->RemoveCommandUpdater(domelement);
1758         if (NS_FAILED(rv)) return rv;
1759     }
1760 
1761     // 4. Remove the element from our broadcaster map, since it is no longer
1762     // in the document.
1763     nsCOMPtr<Element> broadcaster, listener;
1764     nsAutoString attribute, broadcasterID;
1765     rv = FindBroadcaster(aElement, getter_AddRefs(listener),
1766                          broadcasterID, attribute, getter_AddRefs(broadcaster));
1767     if (rv == NS_FINDBROADCASTER_FOUND) {
1768         RemoveBroadcastListenerFor(*broadcaster, *listener, attribute);
1769     }
1770 
1771     return NS_OK;
1772 }
1773 
1774 NS_IMETHODIMP
SetTemplateBuilderFor(nsIContent * aContent,nsIXULTemplateBuilder * aBuilder)1775 XULDocument::SetTemplateBuilderFor(nsIContent* aContent,
1776                                    nsIXULTemplateBuilder* aBuilder)
1777 {
1778     if (! mTemplateBuilderTable) {
1779         if (!aBuilder) {
1780             return NS_OK;
1781         }
1782         mTemplateBuilderTable = new BuilderTable;
1783     }
1784 
1785     if (aBuilder) {
1786         mTemplateBuilderTable->Put(aContent, aBuilder);
1787     }
1788     else {
1789         mTemplateBuilderTable->Remove(aContent);
1790     }
1791 
1792     return NS_OK;
1793 }
1794 
1795 NS_IMETHODIMP
GetTemplateBuilderFor(nsIContent * aContent,nsIXULTemplateBuilder ** aResult)1796 XULDocument::GetTemplateBuilderFor(nsIContent* aContent,
1797                                    nsIXULTemplateBuilder** aResult)
1798 {
1799     if (mTemplateBuilderTable) {
1800         mTemplateBuilderTable->Get(aContent, aResult);
1801     }
1802     else
1803         *aResult = nullptr;
1804 
1805     return NS_OK;
1806 }
1807 
1808 static void
GetRefMapAttribute(Element * aElement,nsAutoString * aValue)1809 GetRefMapAttribute(Element* aElement, nsAutoString* aValue)
1810 {
1811     aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::ref, *aValue);
1812 }
1813 
1814 nsresult
AddElementToRefMap(Element * aElement)1815 XULDocument::AddElementToRefMap(Element* aElement)
1816 {
1817     // Look at the element's 'ref' attribute, and if set,
1818     // add an entry in the resource-to-element map to the element.
1819     nsAutoString value;
1820     GetRefMapAttribute(aElement, &value);
1821     if (!value.IsEmpty()) {
1822         nsRefMapEntry *entry = mRefMap.PutEntry(value);
1823         if (!entry)
1824             return NS_ERROR_OUT_OF_MEMORY;
1825         if (!entry->AddElement(aElement))
1826             return NS_ERROR_OUT_OF_MEMORY;
1827     }
1828 
1829     return NS_OK;
1830 }
1831 
1832 void
RemoveElementFromRefMap(Element * aElement)1833 XULDocument::RemoveElementFromRefMap(Element* aElement)
1834 {
1835     // Remove the element from the resource-to-element map.
1836     nsAutoString value;
1837     GetRefMapAttribute(aElement, &value);
1838     if (!value.IsEmpty()) {
1839         nsRefMapEntry *entry = mRefMap.GetEntry(value);
1840         if (!entry)
1841             return;
1842         if (entry->RemoveElement(aElement)) {
1843             mRefMap.RemoveEntry(entry);
1844         }
1845     }
1846 }
1847 
1848 //----------------------------------------------------------------------
1849 //
1850 // nsIDOMNode interface
1851 //
1852 
1853 nsresult
Clone(mozilla::dom::NodeInfo * aNodeInfo,nsINode ** aResult) const1854 XULDocument::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const
1855 {
1856     // We don't allow cloning of a XUL document
1857     *aResult = nullptr;
1858     return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
1859 }
1860 
1861 
1862 //----------------------------------------------------------------------
1863 //
1864 // Implementation methods
1865 //
1866 
1867 nsresult
Init()1868 XULDocument::Init()
1869 {
1870     nsresult rv = XMLDocument::Init();
1871     NS_ENSURE_SUCCESS(rv, rv);
1872 
1873     // Create our command dispatcher and hook it up.
1874     mCommandDispatcher = new nsXULCommandDispatcher(this);
1875 
1876     if (gRefCnt++ == 0) {
1877         // ensure that the XUL prototype cache is instantiated successfully,
1878         // so that we can use nsXULPrototypeCache::GetInstance() without
1879         // null-checks in the rest of the class.
1880         nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
1881         if (!cache) {
1882           NS_ERROR("Could not instantiate nsXULPrototypeCache");
1883           return NS_ERROR_FAILURE;
1884         }
1885     }
1886 
1887     Preferences::RegisterCallback(XULDocument::DirectionChanged,
1888                                   "intl.uidirection.", this);
1889 
1890     return NS_OK;
1891 }
1892 
1893 
1894 nsresult
StartLayout(void)1895 XULDocument::StartLayout(void)
1896 {
1897     mMayStartLayout = true;
1898     nsCOMPtr<nsIPresShell> shell = GetShell();
1899     if (shell) {
1900         // Resize-reflow this time
1901         nsPresContext *cx = shell->GetPresContext();
1902         NS_ASSERTION(cx != nullptr, "no pres context");
1903         if (! cx)
1904             return NS_ERROR_UNEXPECTED;
1905 
1906         nsCOMPtr<nsIDocShell> docShell = cx->GetDocShell();
1907         NS_ASSERTION(docShell != nullptr, "container is not a docshell");
1908         if (! docShell)
1909             return NS_ERROR_UNEXPECTED;
1910 
1911         nsresult rv = NS_OK;
1912         nsRect r = cx->GetVisibleArea();
1913         rv = shell->Initialize(r.width, r.height);
1914         NS_ENSURE_SUCCESS(rv, rv);
1915     }
1916 
1917     return NS_OK;
1918 }
1919 
1920 /* static */
1921 bool
MatchAttribute(nsIContent * aContent,int32_t aNamespaceID,nsIAtom * aAttrName,void * aData)1922 XULDocument::MatchAttribute(nsIContent* aContent,
1923                             int32_t aNamespaceID,
1924                             nsIAtom* aAttrName,
1925                             void* aData)
1926 {
1927     NS_PRECONDITION(aContent, "Must have content node to work with!");
1928     nsString* attrValue = static_cast<nsString*>(aData);
1929     if (aNamespaceID != kNameSpaceID_Unknown &&
1930         aNamespaceID != kNameSpaceID_Wildcard) {
1931         return attrValue->EqualsLiteral("*") ?
1932             aContent->HasAttr(aNamespaceID, aAttrName) :
1933             aContent->AttrValueIs(aNamespaceID, aAttrName, *attrValue,
1934                                   eCaseMatters);
1935     }
1936 
1937     // Qualified name match. This takes more work.
1938 
1939     uint32_t count = aContent->GetAttrCount();
1940     for (uint32_t i = 0; i < count; ++i) {
1941         const nsAttrName* name = aContent->GetAttrNameAt(i);
1942         bool nameMatch;
1943         if (name->IsAtom()) {
1944             nameMatch = name->Atom() == aAttrName;
1945         } else if (aNamespaceID == kNameSpaceID_Wildcard) {
1946             nameMatch = name->NodeInfo()->Equals(aAttrName);
1947         } else {
1948             nameMatch = name->NodeInfo()->QualifiedNameEquals(aAttrName);
1949         }
1950 
1951         if (nameMatch) {
1952             return attrValue->EqualsLiteral("*") ||
1953                 aContent->AttrValueIs(name->NamespaceID(), name->LocalName(),
1954                                       *attrValue, eCaseMatters);
1955         }
1956     }
1957 
1958     return false;
1959 }
1960 
1961 nsresult
PrepareToLoad(nsISupports * aContainer,const char * aCommand,nsIChannel * aChannel,nsILoadGroup * aLoadGroup,nsIParser ** aResult)1962 XULDocument::PrepareToLoad(nsISupports* aContainer,
1963                            const char* aCommand,
1964                            nsIChannel* aChannel,
1965                            nsILoadGroup* aLoadGroup,
1966                            nsIParser** aResult)
1967 {
1968     // Get the document's principal
1969     nsCOMPtr<nsIPrincipal> principal;
1970     nsContentUtils::GetSecurityManager()->
1971         GetChannelResultPrincipal(aChannel, getter_AddRefs(principal));
1972     return PrepareToLoadPrototype(mDocumentURI, aCommand, principal, aResult);
1973 }
1974 
1975 
1976 nsresult
PrepareToLoadPrototype(nsIURI * aURI,const char * aCommand,nsIPrincipal * aDocumentPrincipal,nsIParser ** aResult)1977 XULDocument::PrepareToLoadPrototype(nsIURI* aURI, const char* aCommand,
1978                                     nsIPrincipal* aDocumentPrincipal,
1979                                     nsIParser** aResult)
1980 {
1981     nsresult rv;
1982 
1983     // Create a new prototype document.
1984     rv = NS_NewXULPrototypeDocument(getter_AddRefs(mCurrentPrototype));
1985     if (NS_FAILED(rv)) return rv;
1986 
1987     rv = mCurrentPrototype->InitPrincipal(aURI, aDocumentPrincipal);
1988     if (NS_FAILED(rv)) {
1989         mCurrentPrototype = nullptr;
1990         return rv;
1991     }
1992 
1993     // Bootstrap the master document prototype.
1994     if (! mMasterPrototype) {
1995         mMasterPrototype = mCurrentPrototype;
1996         // Set our principal based on the master proto.
1997         SetPrincipal(aDocumentPrincipal);
1998     }
1999 
2000     // Create a XUL content sink, a parser, and kick off a load for
2001     // the overlay.
2002     RefPtr<XULContentSinkImpl> sink = new XULContentSinkImpl();
2003 
2004     rv = sink->Init(this, mCurrentPrototype);
2005     NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to initialize datasource sink");
2006     if (NS_FAILED(rv)) return rv;
2007 
2008     nsCOMPtr<nsIParser> parser = do_CreateInstance(kParserCID, &rv);
2009     NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create parser");
2010     if (NS_FAILED(rv)) return rv;
2011 
2012     parser->SetCommand(nsCRT::strcmp(aCommand, "view-source") ? eViewNormal :
2013                        eViewSource);
2014 
2015     parser->SetDocumentCharset(NS_LITERAL_CSTRING("UTF-8"),
2016                                kCharsetFromDocTypeDefault);
2017     parser->SetContentSink(sink); // grabs a reference to the parser
2018 
2019     parser.forget(aResult);
2020     return NS_OK;
2021 }
2022 
2023 
2024 nsresult
ApplyPersistentAttributes()2025 XULDocument::ApplyPersistentAttributes()
2026 {
2027     // For non-chrome documents, persistance is simply broken
2028     if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()))
2029         return NS_ERROR_NOT_AVAILABLE;
2030 
2031     // Add all of the 'persisted' attributes into the content
2032     // model.
2033     if (!mLocalStore) {
2034         mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
2035         if (NS_WARN_IF(!mLocalStore)) {
2036             return NS_ERROR_NOT_INITIALIZED;
2037         }
2038     }
2039 
2040     mApplyingPersistedAttrs = true;
2041     ApplyPersistentAttributesInternal();
2042     mApplyingPersistedAttrs = false;
2043 
2044     // After we've applied persistence once, we should only reapply
2045     // it to nodes created by overlays
2046     mRestrictPersistence = true;
2047     mPersistenceIds.Clear();
2048 
2049     return NS_OK;
2050 }
2051 
2052 
2053 nsresult
ApplyPersistentAttributesInternal()2054 XULDocument::ApplyPersistentAttributesInternal()
2055 {
2056     nsCOMArray<nsIContent> elements;
2057 
2058     nsAutoCString utf8uri;
2059     nsresult rv = mDocumentURI->GetSpec(utf8uri);
2060     if (NS_WARN_IF(NS_FAILED(rv))) {
2061         return rv;
2062     }
2063     NS_ConvertUTF8toUTF16 uri(utf8uri);
2064 
2065     // Get a list of element IDs for which persisted values are available
2066     nsCOMPtr<nsIStringEnumerator> ids;
2067     rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids));
2068     if (NS_WARN_IF(NS_FAILED(rv))) {
2069         return rv;
2070     }
2071 
2072     while (1) {
2073         bool hasmore = false;
2074         ids->HasMore(&hasmore);
2075         if (!hasmore) {
2076             break;
2077         }
2078 
2079         nsAutoString id;
2080         ids->GetNext(id);
2081 
2082         if (mRestrictPersistence && !mPersistenceIds.Contains(id)) {
2083             continue;
2084         }
2085 
2086         // This will clear the array if there are no elements.
2087         GetElementsForID(id, elements);
2088         if (!elements.Count()) {
2089             continue;
2090         }
2091 
2092         rv = ApplyPersistentAttributesToElements(id, elements);
2093         if (NS_WARN_IF(NS_FAILED(rv))) {
2094             return rv;
2095         }
2096     }
2097 
2098     return NS_OK;
2099 }
2100 
2101 
2102 nsresult
ApplyPersistentAttributesToElements(const nsAString & aID,nsCOMArray<nsIContent> & aElements)2103 XULDocument::ApplyPersistentAttributesToElements(const nsAString &aID,
2104                                                  nsCOMArray<nsIContent>& aElements)
2105 {
2106     nsAutoCString utf8uri;
2107     nsresult rv = mDocumentURI->GetSpec(utf8uri);
2108     if (NS_WARN_IF(NS_FAILED(rv))) {
2109         return rv;
2110     }
2111     NS_ConvertUTF8toUTF16 uri(utf8uri);
2112 
2113     // Get a list of attributes for which persisted values are available
2114     nsCOMPtr<nsIStringEnumerator> attrs;
2115     rv = mLocalStore->GetAttributeEnumerator(uri, aID, getter_AddRefs(attrs));
2116     if (NS_WARN_IF(NS_FAILED(rv))) {
2117         return rv;
2118     }
2119 
2120     while (1) {
2121         bool hasmore = PR_FALSE;
2122         attrs->HasMore(&hasmore);
2123         if (!hasmore) {
2124             break;
2125         }
2126 
2127         nsAutoString attrstr;
2128         attrs->GetNext(attrstr);
2129 
2130         nsAutoString value;
2131         rv = mLocalStore->GetValue(uri, aID, attrstr, value);
2132         if (NS_WARN_IF(NS_FAILED(rv))) {
2133             return rv;
2134         }
2135 
2136         nsCOMPtr<nsIAtom> attr = NS_Atomize(attrstr);
2137         if (NS_WARN_IF(!attr)) {
2138             return NS_ERROR_OUT_OF_MEMORY;
2139         }
2140 
2141         uint32_t cnt = aElements.Count();
2142 
2143         for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
2144             nsCOMPtr<nsIContent> element = aElements.SafeObjectAt(i);
2145             if (!element) {
2146                  continue;
2147             }
2148 
2149             rv = element->SetAttr(kNameSpaceID_None, attr, value, PR_TRUE);
2150         }
2151     }
2152 
2153     return NS_OK;
2154 }
2155 
2156 void
TraceProtos(JSTracer * aTrc,uint32_t aGCNumber)2157 XULDocument::TraceProtos(JSTracer* aTrc, uint32_t aGCNumber)
2158 {
2159     uint32_t i, count = mPrototypes.Length();
2160     for (i = 0; i < count; ++i) {
2161         mPrototypes[i]->TraceProtos(aTrc, aGCNumber);
2162     }
2163 
2164     if (mCurrentPrototype) {
2165         mCurrentPrototype->TraceProtos(aTrc, aGCNumber);
2166     }
2167 }
2168 
2169 //----------------------------------------------------------------------
2170 //
2171 // XULDocument::ContextStack
2172 //
2173 
ContextStack()2174 XULDocument::ContextStack::ContextStack()
2175     : mTop(nullptr), mDepth(0)
2176 {
2177 }
2178 
~ContextStack()2179 XULDocument::ContextStack::~ContextStack()
2180 {
2181     while (mTop) {
2182         Entry* doomed = mTop;
2183         mTop = mTop->mNext;
2184         NS_IF_RELEASE(doomed->mElement);
2185         delete doomed;
2186     }
2187 }
2188 
2189 nsresult
Push(nsXULPrototypeElement * aPrototype,nsIContent * aElement)2190 XULDocument::ContextStack::Push(nsXULPrototypeElement* aPrototype,
2191                                 nsIContent* aElement)
2192 {
2193     Entry* entry = new Entry;
2194     entry->mPrototype = aPrototype;
2195     entry->mElement   = aElement;
2196     NS_IF_ADDREF(entry->mElement);
2197     entry->mIndex     = 0;
2198 
2199     entry->mNext = mTop;
2200     mTop = entry;
2201 
2202     ++mDepth;
2203     return NS_OK;
2204 }
2205 
2206 nsresult
Pop()2207 XULDocument::ContextStack::Pop()
2208 {
2209     if (mDepth == 0)
2210         return NS_ERROR_UNEXPECTED;
2211 
2212     Entry* doomed = mTop;
2213     mTop = mTop->mNext;
2214     --mDepth;
2215 
2216     NS_IF_RELEASE(doomed->mElement);
2217     delete doomed;
2218     return NS_OK;
2219 }
2220 
2221 nsresult
Peek(nsXULPrototypeElement ** aPrototype,nsIContent ** aElement,int32_t * aIndex)2222 XULDocument::ContextStack::Peek(nsXULPrototypeElement** aPrototype,
2223                                 nsIContent** aElement,
2224                                 int32_t* aIndex)
2225 {
2226     if (mDepth == 0)
2227         return NS_ERROR_UNEXPECTED;
2228 
2229     *aPrototype = mTop->mPrototype;
2230     *aElement   = mTop->mElement;
2231     NS_IF_ADDREF(*aElement);
2232     *aIndex     = mTop->mIndex;
2233 
2234     return NS_OK;
2235 }
2236 
2237 
2238 nsresult
SetTopIndex(int32_t aIndex)2239 XULDocument::ContextStack::SetTopIndex(int32_t aIndex)
2240 {
2241     if (mDepth == 0)
2242         return NS_ERROR_UNEXPECTED;
2243 
2244     mTop->mIndex = aIndex;
2245     return NS_OK;
2246 }
2247 
2248 
2249 //----------------------------------------------------------------------
2250 //
2251 // Content model walking routines
2252 //
2253 
2254 nsresult
PrepareToWalk()2255 XULDocument::PrepareToWalk()
2256 {
2257     // Prepare to walk the mCurrentPrototype
2258     nsresult rv;
2259 
2260     // Keep an owning reference to the prototype document so that its
2261     // elements aren't yanked from beneath us.
2262     mPrototypes.AppendElement(mCurrentPrototype);
2263 
2264     // Get the prototype's root element and initialize the context
2265     // stack for the prototype walk.
2266     nsXULPrototypeElement* proto = mCurrentPrototype->GetRootElement();
2267 
2268     if (! proto) {
2269         if (MOZ_LOG_TEST(gXULLog, LogLevel::Error)) {
2270             nsCOMPtr<nsIURI> url = mCurrentPrototype->GetURI();
2271 
2272             nsAutoCString urlspec;
2273             rv = url->GetSpec(urlspec);
2274             if (NS_FAILED(rv)) return rv;
2275 
2276             MOZ_LOG(gXULLog, LogLevel::Error,
2277                    ("xul: error parsing '%s'", urlspec.get()));
2278         }
2279 
2280         return NS_OK;
2281     }
2282 
2283     uint32_t piInsertionPoint = 0;
2284     if (mState != eState_Master) {
2285         int32_t indexOfRoot = IndexOf(GetRootElement());
2286         NS_ASSERTION(indexOfRoot >= 0,
2287                      "No root content when preparing to walk overlay!");
2288         piInsertionPoint = indexOfRoot;
2289     }
2290 
2291     const nsTArray<RefPtr<nsXULPrototypePI> >& processingInstructions =
2292         mCurrentPrototype->GetProcessingInstructions();
2293 
2294     uint32_t total = processingInstructions.Length();
2295     for (uint32_t i = 0; i < total; ++i) {
2296         rv = CreateAndInsertPI(processingInstructions[i],
2297                                this, piInsertionPoint + i);
2298         if (NS_FAILED(rv)) return rv;
2299     }
2300 
2301     // Now check the chrome registry for any additional overlays.
2302     rv = AddChromeOverlays();
2303     if (NS_FAILED(rv)) return rv;
2304 
2305     // Do one-time initialization if we're preparing to walk the
2306     // master document's prototype.
2307     RefPtr<Element> root;
2308 
2309     if (mState == eState_Master) {
2310         // Add the root element
2311         rv = CreateElementFromPrototype(proto, getter_AddRefs(root), true);
2312         if (NS_FAILED(rv)) return rv;
2313 
2314         rv = AppendChildTo(root, false);
2315         if (NS_FAILED(rv)) return rv;
2316 
2317         rv = AddElementToRefMap(root);
2318         if (NS_FAILED(rv)) return rv;
2319 
2320         // Block onload until we've finished building the complete
2321         // document content model.
2322         BlockOnload();
2323     }
2324 
2325     // There'd better not be anything on the context stack at this
2326     // point! This is the basis case for our "induction" in
2327     // ResumeWalk(), below, which'll assume that there's always a
2328     // content element on the context stack if either 1) we're in the
2329     // "master" document, or 2) we're in an overlay, and we've got
2330     // more than one prototype element (the single, root "overlay"
2331     // element) on the stack.
2332     NS_ASSERTION(mContextStack.Depth() == 0, "something's on the context stack already");
2333     if (mContextStack.Depth() != 0)
2334         return NS_ERROR_UNEXPECTED;
2335 
2336     rv = mContextStack.Push(proto, root);
2337     if (NS_FAILED(rv)) return rv;
2338 
2339     return NS_OK;
2340 }
2341 
2342 nsresult
CreateAndInsertPI(const nsXULPrototypePI * aProtoPI,nsINode * aParent,uint32_t aIndex)2343 XULDocument::CreateAndInsertPI(const nsXULPrototypePI* aProtoPI,
2344                                nsINode* aParent, uint32_t aIndex)
2345 {
2346     NS_PRECONDITION(aProtoPI, "null ptr");
2347     NS_PRECONDITION(aParent, "null ptr");
2348 
2349     RefPtr<ProcessingInstruction> node =
2350         NS_NewXMLProcessingInstruction(mNodeInfoManager, aProtoPI->mTarget,
2351                                        aProtoPI->mData);
2352 
2353     nsresult rv;
2354     if (aProtoPI->mTarget.EqualsLiteral("xml-stylesheet")) {
2355         rv = InsertXMLStylesheetPI(aProtoPI, aParent, aIndex, node);
2356     } else if (aProtoPI->mTarget.EqualsLiteral("xul-overlay")) {
2357         rv = InsertXULOverlayPI(aProtoPI, aParent, aIndex, node);
2358     } else {
2359         // No special processing, just add the PI to the document.
2360         rv = aParent->InsertChildAt(node, aIndex, false);
2361     }
2362 
2363     return rv;
2364 }
2365 
2366 nsresult
InsertXMLStylesheetPI(const nsXULPrototypePI * aProtoPI,nsINode * aParent,uint32_t aIndex,nsIContent * aPINode)2367 XULDocument::InsertXMLStylesheetPI(const nsXULPrototypePI* aProtoPI,
2368                                    nsINode* aParent,
2369                                    uint32_t aIndex,
2370                                    nsIContent* aPINode)
2371 {
2372     nsCOMPtr<nsIStyleSheetLinkingElement> ssle(do_QueryInterface(aPINode));
2373     NS_ASSERTION(ssle, "passed XML Stylesheet node does not "
2374                        "implement nsIStyleSheetLinkingElement!");
2375 
2376     nsresult rv;
2377 
2378     ssle->InitStyleLinkElement(false);
2379     // We want to be notified when the style sheet finishes loading, so
2380     // disable style sheet loading for now.
2381     ssle->SetEnableUpdates(false);
2382     ssle->OverrideBaseURI(mCurrentPrototype->GetURI());
2383 
2384     rv = aParent->InsertChildAt(aPINode, aIndex, false);
2385     if (NS_FAILED(rv)) return rv;
2386 
2387     ssle->SetEnableUpdates(true);
2388 
2389     // load the stylesheet if necessary, passing ourselves as
2390     // nsICSSObserver
2391     bool willNotify;
2392     bool isAlternate;
2393     rv = ssle->UpdateStyleSheet(this, &willNotify, &isAlternate);
2394     if (NS_SUCCEEDED(rv) && willNotify && !isAlternate) {
2395         ++mPendingSheets;
2396     }
2397 
2398     // Ignore errors from UpdateStyleSheet; we don't want failure to
2399     // do that to break the XUL document load.  But do propagate out
2400     // NS_ERROR_OUT_OF_MEMORY.
2401     if (rv == NS_ERROR_OUT_OF_MEMORY) {
2402         return rv;
2403     }
2404 
2405     return NS_OK;
2406 }
2407 
2408 nsresult
InsertXULOverlayPI(const nsXULPrototypePI * aProtoPI,nsINode * aParent,uint32_t aIndex,nsIContent * aPINode)2409 XULDocument::InsertXULOverlayPI(const nsXULPrototypePI* aProtoPI,
2410                                 nsINode* aParent,
2411                                 uint32_t aIndex,
2412                                 nsIContent* aPINode)
2413 {
2414     nsresult rv;
2415 
2416     rv = aParent->InsertChildAt(aPINode, aIndex, false);
2417     if (NS_FAILED(rv)) return rv;
2418 
2419     // xul-overlay PI is special only in prolog
2420     if (!nsContentUtils::InProlog(aPINode)) {
2421         return NS_OK;
2422     }
2423 
2424     nsAutoString href;
2425     nsContentUtils::GetPseudoAttributeValue(aProtoPI->mData,
2426                                             nsGkAtoms::href,
2427                                             href);
2428 
2429     // If there was no href, we can't do anything with this PI
2430     if (href.IsEmpty()) {
2431         return NS_OK;
2432     }
2433 
2434     // Add the overlay to our list of overlays that need to be processed.
2435     nsCOMPtr<nsIURI> uri;
2436 
2437     rv = NS_NewURI(getter_AddRefs(uri), href, nullptr,
2438                    mCurrentPrototype->GetURI());
2439     if (NS_SUCCEEDED(rv)) {
2440         // We insert overlays into mUnloadedOverlays at the same index in
2441         // document order, so they end up in the reverse of the document
2442         // order in mUnloadedOverlays.
2443         // This is needed because the code in ResumeWalk loads the overlays
2444         // by processing the last item of mUnloadedOverlays and removing it
2445         // from the array.
2446         mUnloadedOverlays.InsertElementAt(0, uri);
2447         rv = NS_OK;
2448     } else if (rv == NS_ERROR_MALFORMED_URI) {
2449         // The URL is bad, move along. Don't propagate for now.
2450         // XXX report this to the Error Console (bug 359846)
2451         rv = NS_OK;
2452     }
2453 
2454     return rv;
2455 }
2456 
2457 nsresult
AddChromeOverlays()2458 XULDocument::AddChromeOverlays()
2459 {
2460     nsresult rv;
2461 
2462     nsCOMPtr<nsIURI> docUri = mCurrentPrototype->GetURI();
2463 
2464     /* overlays only apply to chrome or about URIs */
2465     if (!IsOverlayAllowed(docUri)) return NS_OK;
2466 
2467     nsCOMPtr<nsIXULOverlayProvider> chromeReg =
2468         mozilla::services::GetXULOverlayProviderService();
2469     // In embedding situations, the chrome registry may not provide overlays,
2470     // or even exist at all; that's OK.
2471     NS_ENSURE_TRUE(chromeReg, NS_OK);
2472 
2473     nsCOMPtr<nsISimpleEnumerator> overlays;
2474     rv = chromeReg->GetXULOverlays(docUri, getter_AddRefs(overlays));
2475     NS_ENSURE_SUCCESS(rv, rv);
2476 
2477     bool moreOverlays;
2478     nsCOMPtr<nsISupports> next;
2479     nsCOMPtr<nsIURI> uri;
2480 
2481     while (NS_SUCCEEDED(rv = overlays->HasMoreElements(&moreOverlays)) &&
2482            moreOverlays) {
2483 
2484         rv = overlays->GetNext(getter_AddRefs(next));
2485         if (NS_FAILED(rv) || !next) break;
2486 
2487         uri = do_QueryInterface(next);
2488         if (!uri) {
2489             NS_ERROR("Chrome registry handed me a non-nsIURI object!");
2490             continue;
2491         }
2492 
2493         // Same comment as in XULDocument::InsertXULOverlayPI
2494         mUnloadedOverlays.InsertElementAt(0, uri);
2495     }
2496 
2497     return rv;
2498 }
2499 
2500 NS_IMETHODIMP
LoadOverlay(const nsAString & aURL,nsIObserver * aObserver)2501 XULDocument::LoadOverlay(const nsAString& aURL, nsIObserver* aObserver)
2502 {
2503     nsresult rv;
2504 
2505     nsCOMPtr<nsIURI> uri;
2506     rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr);
2507     if (NS_FAILED(rv)) return rv;
2508 
2509     if (aObserver) {
2510         nsIObserver* obs = nullptr;
2511         if (!mOverlayLoadObservers) {
2512           mOverlayLoadObservers = new nsInterfaceHashtable<nsURIHashKey,nsIObserver>;
2513         }
2514         obs = mOverlayLoadObservers->GetWeak(uri);
2515 
2516         if (obs) {
2517             // We don't support loading the same overlay twice into the same
2518             // document - that doesn't make sense anyway.
2519             return NS_ERROR_FAILURE;
2520         }
2521         mOverlayLoadObservers->Put(uri, aObserver);
2522     }
2523     bool shouldReturn, failureFromContent;
2524     rv = LoadOverlayInternal(uri, true, &shouldReturn, &failureFromContent);
2525     if (NS_FAILED(rv) && mOverlayLoadObservers)
2526         mOverlayLoadObservers->Remove(uri); // remove the observer if LoadOverlayInternal generated an error
2527     return rv;
2528 }
2529 
2530 nsresult
LoadOverlayInternal(nsIURI * aURI,bool aIsDynamic,bool * aShouldReturn,bool * aFailureFromContent)2531 XULDocument::LoadOverlayInternal(nsIURI* aURI, bool aIsDynamic,
2532                                  bool* aShouldReturn,
2533                                  bool* aFailureFromContent)
2534 {
2535     nsresult rv;
2536 
2537     *aShouldReturn = false;
2538     *aFailureFromContent = false;
2539 
2540     if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) {
2541         nsCOMPtr<nsIURI> uri;
2542         mChannel->GetOriginalURI(getter_AddRefs(uri));
2543 
2544         MOZ_LOG(gXULLog, LogLevel::Debug,
2545                 ("xul: %s loading overlay %s",
2546                  uri ? uri->GetSpecOrDefault().get() : "",
2547                  aURI->GetSpecOrDefault().get()));
2548     }
2549 
2550     if (aIsDynamic)
2551         mResolutionPhase = nsForwardReference::eStart;
2552 
2553     // Look in the prototype cache for the prototype document with
2554     // the specified overlay URI. Only use the cache if the containing
2555     // document is chrome otherwise it may not have a system principal and
2556     // the cached document will, see bug 565610.
2557     bool overlayIsChrome = IsChromeURI(aURI);
2558     bool documentIsChrome = IsChromeURI(mDocumentURI);
2559     mCurrentPrototype = overlayIsChrome && documentIsChrome ?
2560         nsXULPrototypeCache::GetInstance()->GetPrototype(aURI) : nullptr;
2561 
2562     // Same comment as nsChromeProtocolHandler::NewChannel and
2563     // XULDocument::StartDocumentLoad
2564     // - Ben Goodger
2565     //
2566     // We don't abort on failure here because there are too many valid
2567     // cases that can return failure, and the null-ness of |proto| is
2568     // enough to trigger the fail-safe parse-from-disk solution.
2569     // Example failure cases (for reference) include:
2570     //
2571     // NS_ERROR_NOT_AVAILABLE: the URI was not found in the FastLoad file,
2572     //                         parse from disk
2573     // other: the FastLoad file, XUL.mfl, could not be found, probably
2574     //        due to being accessed before a profile has been selected
2575     //        (e.g. loading chrome for the profile manager itself).
2576     //        The .xul file must be parsed from disk.
2577 
2578     bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
2579     if (useXULCache && mCurrentPrototype) {
2580         bool loaded;
2581         rv = mCurrentPrototype->AwaitLoadDone(this, &loaded);
2582         if (NS_FAILED(rv)) return rv;
2583 
2584         if (! loaded) {
2585             // Return to the main event loop and eagerly await the
2586             // prototype overlay load's completion. When the content
2587             // sink completes, it will trigger an EndLoad(), which'll
2588             // wind us back up here, in ResumeWalk().
2589             *aShouldReturn = true;
2590             return NS_OK;
2591         }
2592 
2593         MOZ_LOG(gXULLog, LogLevel::Debug, ("xul: overlay was cached"));
2594 
2595         // Found the overlay's prototype in the cache, fully loaded. If
2596         // this is a dynamic overlay, this will call ResumeWalk.
2597         // Otherwise, we'll return to ResumeWalk, which called us.
2598         return OnPrototypeLoadDone(aIsDynamic);
2599     }
2600     else {
2601         // Not there. Initiate a load.
2602         MOZ_LOG(gXULLog, LogLevel::Debug, ("xul: overlay was not cached"));
2603 
2604         if (mIsGoingAway) {
2605             MOZ_LOG(gXULLog, LogLevel::Debug, ("xul: ...and document already destroyed"));
2606             return NS_ERROR_NOT_AVAILABLE;
2607         }
2608 
2609         // We'll set the right principal on the proto doc when we get
2610         // OnStartRequest from the parser, so just pass in a null principal for
2611         // now.
2612         nsCOMPtr<nsIParser> parser;
2613         rv = PrepareToLoadPrototype(aURI, "view", nullptr, getter_AddRefs(parser));
2614         if (NS_FAILED(rv)) return rv;
2615 
2616         // Predicate mIsWritingFastLoad on the XUL cache being enabled,
2617         // so we don't have to re-check whether the cache is enabled all
2618         // the time.
2619         mIsWritingFastLoad = useXULCache;
2620 
2621         nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(parser);
2622         if (! listener)
2623             return NS_ERROR_UNEXPECTED;
2624 
2625         // Add an observer to the parser; this'll get called when
2626         // Necko fires its On[Start|Stop]Request() notifications,
2627         // and will let us recover from a missing overlay.
2628         RefPtr<ParserObserver> parserObserver =
2629             new ParserObserver(this, mCurrentPrototype);
2630         parser->Parse(aURI, parserObserver);
2631         parserObserver = nullptr;
2632 
2633         nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
2634         nsCOMPtr<nsIChannel> channel;
2635         // Set the owner of the channel to be our principal so
2636         // that the overlay's JSObjects etc end up being created
2637         // with the right principal and in the correct
2638         // compartment.
2639         rv = NS_NewChannel(getter_AddRefs(channel),
2640                            aURI,
2641                            NodePrincipal(),
2642                            nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS |
2643                            nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
2644                            nsIContentPolicy::TYPE_OTHER,
2645                            group);
2646 
2647         if (NS_SUCCEEDED(rv)) {
2648             rv = channel->AsyncOpen2(listener);
2649         }
2650 
2651         if (NS_FAILED(rv)) {
2652             // Abandon this prototype
2653             mCurrentPrototype = nullptr;
2654 
2655             // The parser won't get an OnStartRequest and
2656             // OnStopRequest, so it needs a Terminate.
2657             parser->Terminate();
2658 
2659             // Just move on to the next overlay.
2660             ReportMissingOverlay(aURI);
2661 
2662             // XXX the error could indicate an internal error as well...
2663             *aFailureFromContent = true;
2664             return rv;
2665         }
2666 
2667         // If it's a 'chrome:' prototype document, then put it into
2668         // the prototype cache; other XUL documents will be reloaded
2669         // each time.  We must do this after AsyncOpen,
2670         // or chrome code will wrongly create a cached chrome channel
2671         // instead of a real one. Prototypes are only cached when the
2672         // document to be overlayed is chrome to avoid caching overlay
2673         // scripts with incorrect principals, see bug 565610.
2674         if (useXULCache && overlayIsChrome && documentIsChrome) {
2675             nsXULPrototypeCache::GetInstance()->PutPrototype(mCurrentPrototype);
2676         }
2677 
2678         // Return to the main event loop and eagerly await the
2679         // overlay load's completion. When the content sink
2680         // completes, it will trigger an EndLoad(), which'll wind
2681         // us back in ResumeWalk().
2682         if (!aIsDynamic)
2683             *aShouldReturn = true;
2684     }
2685     return NS_OK;
2686 }
2687 
2688 nsresult
ResumeWalk()2689 XULDocument::ResumeWalk()
2690 {
2691     // Walk the prototype and build the delegate content model. The
2692     // walk is performed in a top-down, left-to-right fashion. That
2693     // is, a parent is built before any of its children; a node is
2694     // only built after all of its siblings to the left are fully
2695     // constructed.
2696     //
2697     // It is interruptable so that transcluded documents (e.g.,
2698     // <html:script src="..." />) can be properly re-loaded if the
2699     // cached copy of the document becomes stale.
2700     nsresult rv;
2701     nsCOMPtr<nsIURI> overlayURI =
2702         mCurrentPrototype ? mCurrentPrototype->GetURI() : nullptr;
2703 
2704     while (1) {
2705         // Begin (or resume) walking the current prototype.
2706 
2707         while (mContextStack.Depth() > 0) {
2708             // Look at the top of the stack to determine what we're
2709             // currently working on.
2710             // This will always be a node already constructed and
2711             // inserted to the actual document.
2712             nsXULPrototypeElement* proto;
2713             nsCOMPtr<nsIContent> element;
2714             int32_t indx; // all children of proto before indx (not
2715                           // inclusive) have already been constructed
2716             rv = mContextStack.Peek(&proto, getter_AddRefs(element), &indx);
2717             if (NS_FAILED(rv)) return rv;
2718 
2719             if (indx >= (int32_t)proto->mChildren.Length()) {
2720                 if (element) {
2721                     // We've processed all of the prototype's children. If
2722                     // we're in the master prototype, do post-order
2723                     // document-level hookup. (An overlay will get its
2724                     // document hookup done when it's successfully
2725                     // resolved.)
2726                     if (mState == eState_Master) {
2727                         AddElementToDocumentPost(element->AsElement());
2728 
2729                         if (element->NodeInfo()->Equals(nsGkAtoms::style,
2730                                                         kNameSpaceID_XHTML) ||
2731                             element->NodeInfo()->Equals(nsGkAtoms::style,
2732                                                         kNameSpaceID_SVG)) {
2733                             // XXX sucks that we have to do this -
2734                             // see bug 370111
2735                             nsCOMPtr<nsIStyleSheetLinkingElement> ssle =
2736                                 do_QueryInterface(element);
2737                             NS_ASSERTION(ssle, "<html:style> doesn't implement "
2738                                                "nsIStyleSheetLinkingElement?");
2739                             bool willNotify;
2740                             bool isAlternate;
2741                             ssle->UpdateStyleSheet(nullptr, &willNotify,
2742                                                    &isAlternate);
2743                         }
2744                     }
2745                 }
2746                 // Now pop the context stack back up to the parent
2747                 // element and continue the prototype walk.
2748                 mContextStack.Pop();
2749                 continue;
2750             }
2751 
2752             // Grab the next child, and advance the current context stack
2753             // to the next sibling to our right.
2754             nsXULPrototypeNode* childproto = proto->mChildren[indx];
2755             mContextStack.SetTopIndex(++indx);
2756 
2757             // Whether we're in the "first ply" of an overlay:
2758             // the "hookup" nodes. In the case !processingOverlayHookupNodes,
2759             // we're in the master document -or- we're in an overlay, and far
2760             // enough down into the overlay's content that we can simply build
2761             // the delegates and attach them to the parent node.
2762             bool processingOverlayHookupNodes = (mState == eState_Overlay) &&
2763                                                   (mContextStack.Depth() == 1);
2764 
2765             NS_ASSERTION(element || processingOverlayHookupNodes,
2766                          "no element on context stack");
2767 
2768             switch (childproto->mType) {
2769             case nsXULPrototypeNode::eType_Element: {
2770                 // An 'element', which may contain more content.
2771                 nsXULPrototypeElement* protoele =
2772                     static_cast<nsXULPrototypeElement*>(childproto);
2773 
2774                 RefPtr<Element> child;
2775 
2776                 if (!processingOverlayHookupNodes) {
2777                     rv = CreateElementFromPrototype(protoele,
2778                                                     getter_AddRefs(child),
2779                                                     false);
2780                     if (NS_FAILED(rv)) return rv;
2781 
2782                     // ...and append it to the content model.
2783                     rv = element->AppendChildTo(child, false);
2784                     if (NS_FAILED(rv)) return rv;
2785 
2786                     // If we're only restoring persisted things on
2787                     // some elements, store the ID here to do that.
2788                     if (mRestrictPersistence) {
2789                         nsIAtom* id = child->GetID();
2790                         if (id) {
2791                             mPersistenceIds.PutEntry(nsDependentAtomString(id));
2792                         }
2793                     }
2794 
2795                     // do pre-order document-level hookup, but only if
2796                     // we're in the master document. For an overlay,
2797                     // this will happen when the overlay is
2798                     // successfully resolved.
2799                     if (mState == eState_Master)
2800                         AddElementToDocumentPre(child);
2801                 }
2802                 else {
2803                     // We're in the "first ply" of an overlay: the
2804                     // "hookup" nodes. Create an 'overlay' element so
2805                     // that we can continue to build content, and
2806                     // enter a forward reference so we can hook it up
2807                     // later.
2808                     rv = CreateOverlayElement(protoele, getter_AddRefs(child));
2809                     if (NS_FAILED(rv)) return rv;
2810                 }
2811 
2812                 // If it has children, push the element onto the context
2813                 // stack and begin to process them.
2814                 if (protoele->mChildren.Length() > 0) {
2815                     rv = mContextStack.Push(protoele, child);
2816                     if (NS_FAILED(rv)) return rv;
2817                 }
2818                 else {
2819                     if (mState == eState_Master) {
2820                         // If there are no children, and we're in the
2821                         // master document, do post-order document hookup
2822                         // immediately.
2823                         AddElementToDocumentPost(child);
2824                     }
2825                 }
2826             }
2827             break;
2828 
2829             case nsXULPrototypeNode::eType_Script: {
2830                 // A script reference. Execute the script immediately;
2831                 // this may have side effects in the content model.
2832                 nsXULPrototypeScript* scriptproto =
2833                     static_cast<nsXULPrototypeScript*>(childproto);
2834 
2835                 if (scriptproto->mSrcURI) {
2836                     // A transcluded script reference; this may
2837                     // "block" our prototype walk if the script isn't
2838                     // cached, or the cached copy of the script is
2839                     // stale and must be reloaded.
2840                     bool blocked;
2841                     rv = LoadScript(scriptproto, &blocked);
2842                     // If the script cannot be loaded, just keep going!
2843 
2844                     if (NS_SUCCEEDED(rv) && blocked)
2845                         return NS_OK;
2846                 }
2847                 else if (scriptproto->HasScriptObject()) {
2848                     // An inline script
2849                     rv = ExecuteScript(scriptproto);
2850                     if (NS_FAILED(rv)) return rv;
2851                 }
2852             }
2853             break;
2854 
2855             case nsXULPrototypeNode::eType_Text: {
2856                 // A simple text node.
2857 
2858                 if (!processingOverlayHookupNodes) {
2859                     // This does mean that text nodes that are direct children
2860                     // of <overlay> get ignored.
2861 
2862                     RefPtr<nsTextNode> text =
2863                         new nsTextNode(mNodeInfoManager);
2864 
2865                     nsXULPrototypeText* textproto =
2866                         static_cast<nsXULPrototypeText*>(childproto);
2867                     text->SetText(textproto->mValue, false);
2868 
2869                     rv = element->AppendChildTo(text, false);
2870                     NS_ENSURE_SUCCESS(rv, rv);
2871                 }
2872             }
2873             break;
2874 
2875             case nsXULPrototypeNode::eType_PI: {
2876                 nsXULPrototypePI* piProto =
2877                     static_cast<nsXULPrototypePI*>(childproto);
2878 
2879                 // <?xul-overlay?> and <?xml-stylesheet?> don't have effect
2880                 // outside the prolog, like they used to. Issue a warning.
2881 
2882                 if (piProto->mTarget.EqualsLiteral("xml-stylesheet") ||
2883                     piProto->mTarget.EqualsLiteral("xul-overlay")) {
2884 
2885                     const char16_t* params[] = { piProto->mTarget.get() };
2886 
2887                     nsContentUtils::ReportToConsole(
2888                                         nsIScriptError::warningFlag,
2889                                         NS_LITERAL_CSTRING("XUL Document"), nullptr,
2890                                         nsContentUtils::eXUL_PROPERTIES,
2891                                         "PINotInProlog",
2892                                         params, ArrayLength(params),
2893                                         overlayURI);
2894                 }
2895 
2896                 nsIContent* parent = processingOverlayHookupNodes ?
2897                     GetRootElement() : element.get();
2898 
2899                 if (parent) {
2900                     // an inline script could have removed the root element
2901                     rv = CreateAndInsertPI(piProto, parent,
2902                                            parent->GetChildCount());
2903                     NS_ENSURE_SUCCESS(rv, rv);
2904                 }
2905             }
2906             break;
2907 
2908             default:
2909                 NS_NOTREACHED("Unexpected nsXULPrototypeNode::Type value");
2910             }
2911         }
2912 
2913         // Once we get here, the context stack will have been
2914         // depleted. That means that the entire prototype has been
2915         // walked and content has been constructed.
2916 
2917         // If we're not already, mark us as now processing overlays.
2918         mState = eState_Overlay;
2919 
2920         // If there are no overlay URIs, then we're done.
2921         uint32_t count = mUnloadedOverlays.Length();
2922         if (! count)
2923             break;
2924 
2925         nsCOMPtr<nsIURI> uri = mUnloadedOverlays[count-1];
2926         mUnloadedOverlays.RemoveElementAt(count - 1);
2927 
2928         bool shouldReturn, failureFromContent;
2929         rv = LoadOverlayInternal(uri, false, &shouldReturn,
2930                                  &failureFromContent);
2931         if (failureFromContent)
2932             // The failure |rv| was the result of a problem in the content
2933             // rather than an unexpected problem in our implementation, so
2934             // just continue with the next overlay.
2935             continue;
2936         if (NS_FAILED(rv))
2937             return rv;
2938         if (mOverlayLoadObservers) {
2939             nsIObserver *obs = mOverlayLoadObservers->GetWeak(overlayURI);
2940             if (obs) {
2941                 // This overlay has an unloaded overlay, so it will never
2942                 // notify. The best we can do is to notify for the unloaded
2943                 // overlay instead, assuming nobody is already notifiable
2944                 // for it. Note that this will confuse the observer.
2945                 if (!mOverlayLoadObservers->GetWeak(uri))
2946                     mOverlayLoadObservers->Put(uri, obs);
2947                 mOverlayLoadObservers->Remove(overlayURI);
2948             }
2949         }
2950         if (shouldReturn)
2951             return NS_OK;
2952         overlayURI.swap(uri);
2953     }
2954 
2955     // If we get here, there is nothing left for us to walk. The content
2956     // model is built and ready for layout.
2957     rv = ResolveForwardReferences();
2958     if (NS_FAILED(rv)) return rv;
2959 
2960     ApplyPersistentAttributes();
2961 
2962     mStillWalking = false;
2963     if (mPendingSheets == 0) {
2964         rv = DoneWalking();
2965     }
2966     return rv;
2967 }
2968 
2969 nsresult
DoneWalking()2970 XULDocument::DoneWalking()
2971 {
2972     NS_PRECONDITION(mPendingSheets == 0, "there are sheets to be loaded");
2973     NS_PRECONDITION(!mStillWalking, "walk not done");
2974 
2975     // XXXldb This is where we should really be setting the chromehidden
2976     // attribute.
2977 
2978     {
2979         mozAutoDocUpdate updateBatch(this, UPDATE_STYLE, true);
2980         uint32_t count = mOverlaySheets.Length();
2981         for (uint32_t i = 0; i < count; ++i) {
2982             AddStyleSheet(mOverlaySheets[i]);
2983         }
2984     }
2985 
2986     mOverlaySheets.Clear();
2987 
2988     if (!mDocumentLoaded) {
2989         // Make sure we don't reenter here from StartLayout().  Note that
2990         // setting mDocumentLoaded to true here means that if StartLayout()
2991         // causes ResumeWalk() to be reentered, we'll take the other branch of
2992         // the |if (!mDocumentLoaded)| check above and since
2993         // mInitialLayoutComplete will be false will follow the else branch
2994         // there too.  See the big comment there for how such reentry can
2995         // happen.
2996         mDocumentLoaded = true;
2997 
2998         NotifyPossibleTitleChange(false);
2999 
3000         // Before starting layout, check whether we're a toplevel chrome
3001         // window.  If we are, set our chrome flags now, so that we don't have
3002         // to restyle the whole frame tree after StartLayout.
3003         nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
3004         if (item) {
3005             nsCOMPtr<nsIDocShellTreeOwner> owner;
3006             item->GetTreeOwner(getter_AddRefs(owner));
3007             nsCOMPtr<nsIXULWindow> xulWin = do_GetInterface(owner);
3008             if (xulWin) {
3009                 nsCOMPtr<nsIDocShell> xulWinShell;
3010                 xulWin->GetDocShell(getter_AddRefs(xulWinShell));
3011                 if (SameCOMIdentity(xulWinShell, item)) {
3012                     // We're the chrome document!  Apply our chrome flags now.
3013                     xulWin->ApplyChromeFlags();
3014                 }
3015             }
3016         }
3017 
3018         StartLayout();
3019 
3020         if (mIsWritingFastLoad && IsChromeURI(mDocumentURI))
3021             nsXULPrototypeCache::GetInstance()->WritePrototype(mMasterPrototype);
3022 
3023         NS_ASSERTION(mDelayFrameLoaderInitialization,
3024                      "mDelayFrameLoaderInitialization should be true!");
3025         mDelayFrameLoaderInitialization = false;
3026         NS_WARNING_ASSERTION(
3027           mUpdateNestLevel == 0,
3028           "Constructing XUL document in middle of an update?");
3029         if (mUpdateNestLevel == 0) {
3030             MaybeInitializeFinalizeFrameLoaders();
3031         }
3032 
3033         NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
3034 
3035         // DispatchContentLoadedEvents undoes the onload-blocking we
3036         // did in PrepareToWalk().
3037         DispatchContentLoadedEvents();
3038 
3039         mInitialLayoutComplete = true;
3040 
3041         // Walk the set of pending load notifications and notify any observers.
3042         // See below for detail.
3043         if (mPendingOverlayLoadNotifications) {
3044             nsInterfaceHashtable<nsURIHashKey,nsIObserver>* observers =
3045                 mOverlayLoadObservers.get();
3046             for (auto iter = mPendingOverlayLoadNotifications->Iter();
3047                  !iter.Done();
3048                  iter.Next()) {
3049                 nsIURI* aKey = iter.Key();
3050                 iter.Data()->Observe(aKey, "xul-overlay-merged",
3051                                      EmptyString().get());
3052 
3053                 if (observers) {
3054                   observers->Remove(aKey);
3055                 }
3056 
3057                 iter.Remove();
3058             }
3059         }
3060     }
3061     else {
3062         if (mOverlayLoadObservers) {
3063             nsCOMPtr<nsIURI> overlayURI = mCurrentPrototype->GetURI();
3064             nsCOMPtr<nsIObserver> obs;
3065             if (mInitialLayoutComplete) {
3066                 // We have completed initial layout, so just send the notification.
3067                 mOverlayLoadObservers->Get(overlayURI, getter_AddRefs(obs));
3068                 if (obs)
3069                     obs->Observe(overlayURI, "xul-overlay-merged", EmptyString().get());
3070                 mOverlayLoadObservers->Remove(overlayURI);
3071             }
3072             else {
3073                 // If we have not yet displayed the document for the first time
3074                 // (i.e. we came in here as the result of a dynamic overlay load
3075                 // which was spawned by a binding-attached event caused by
3076                 // StartLayout() on the master prototype - we must remember that
3077                 // this overlay has been merged and tell the listeners after
3078                 // StartLayout() is completely finished rather than doing so
3079                 // immediately - otherwise we may be executing code that needs to
3080                 // access XBL Binding implementations on nodes for which frames
3081                 // have not yet been constructed because their bindings have not
3082                 // yet been attached. This can be a race condition because dynamic
3083                 // overlay loading can take varying amounts of time depending on
3084                 // whether or not the overlay prototype is in the XUL cache. The
3085                 // most likely effect of this bug is odd UI initialization due to
3086                 // methods and properties that do not work.
3087                 // XXXbz really, we shouldn't be firing binding constructors
3088                 // until after StartLayout returns!
3089 
3090                 if (!mPendingOverlayLoadNotifications) {
3091                     mPendingOverlayLoadNotifications =
3092                         new nsInterfaceHashtable<nsURIHashKey,nsIObserver>;
3093                 }
3094 
3095                 mPendingOverlayLoadNotifications->Get(overlayURI, getter_AddRefs(obs));
3096                 if (!obs) {
3097                     mOverlayLoadObservers->Get(overlayURI, getter_AddRefs(obs));
3098                     NS_ASSERTION(obs, "null overlay load observer?");
3099                     mPendingOverlayLoadNotifications->Put(overlayURI, obs);
3100                 }
3101             }
3102         }
3103     }
3104 
3105     return NS_OK;
3106 }
3107 
3108 NS_IMETHODIMP
StyleSheetLoaded(StyleSheet * aSheet,bool aWasAlternate,nsresult aStatus)3109 XULDocument::StyleSheetLoaded(StyleSheet* aSheet,
3110                               bool aWasAlternate,
3111                               nsresult aStatus)
3112 {
3113     if (!aWasAlternate) {
3114         // Don't care about when alternate sheets finish loading
3115 
3116         NS_ASSERTION(mPendingSheets > 0,
3117             "Unexpected StyleSheetLoaded notification");
3118 
3119         --mPendingSheets;
3120 
3121         if (!mStillWalking && mPendingSheets == 0) {
3122             return DoneWalking();
3123         }
3124     }
3125 
3126     return NS_OK;
3127 }
3128 
3129 void
MaybeBroadcast()3130 XULDocument::MaybeBroadcast()
3131 {
3132     // Only broadcast when not in an update and when safe to run scripts.
3133     if (mUpdateNestLevel == 0 &&
3134         (mDelayedAttrChangeBroadcasts.Length() ||
3135          mDelayedBroadcasters.Length())) {
3136         if (!nsContentUtils::IsSafeToRunScript()) {
3137             if (!mInDestructor) {
3138                 nsContentUtils::AddScriptRunner(
3139                     NewRunnableMethod(this, &XULDocument::MaybeBroadcast));
3140             }
3141             return;
3142         }
3143         if (!mHandlingDelayedAttrChange) {
3144             mHandlingDelayedAttrChange = true;
3145             for (uint32_t i = 0; i < mDelayedAttrChangeBroadcasts.Length(); ++i) {
3146                 nsIAtom* attrName = mDelayedAttrChangeBroadcasts[i].mAttrName;
3147                 if (mDelayedAttrChangeBroadcasts[i].mNeedsAttrChange) {
3148                     nsCOMPtr<nsIContent> listener =
3149                         do_QueryInterface(mDelayedAttrChangeBroadcasts[i].mListener);
3150                     const nsString& value = mDelayedAttrChangeBroadcasts[i].mAttr;
3151                     if (mDelayedAttrChangeBroadcasts[i].mSetAttr) {
3152                         listener->SetAttr(kNameSpaceID_None, attrName, value,
3153                                           true);
3154                     } else {
3155                         listener->UnsetAttr(kNameSpaceID_None, attrName,
3156                                             true);
3157                     }
3158                 }
3159                 ExecuteOnBroadcastHandlerFor(mDelayedAttrChangeBroadcasts[i].mBroadcaster,
3160                                              mDelayedAttrChangeBroadcasts[i].mListener,
3161                                              attrName);
3162             }
3163             mDelayedAttrChangeBroadcasts.Clear();
3164             mHandlingDelayedAttrChange = false;
3165         }
3166 
3167         uint32_t length = mDelayedBroadcasters.Length();
3168         if (length) {
3169             bool oldValue = mHandlingDelayedBroadcasters;
3170             mHandlingDelayedBroadcasters = true;
3171             nsTArray<nsDelayedBroadcastUpdate> delayedBroadcasters;
3172             mDelayedBroadcasters.SwapElements(delayedBroadcasters);
3173             for (uint32_t i = 0; i < length; ++i) {
3174                 SynchronizeBroadcastListener(delayedBroadcasters[i].mBroadcaster,
3175                                              delayedBroadcasters[i].mListener,
3176                                              delayedBroadcasters[i].mAttr);
3177             }
3178             mHandlingDelayedBroadcasters = oldValue;
3179         }
3180     }
3181 }
3182 
3183 void
EndUpdate(nsUpdateType aUpdateType)3184 XULDocument::EndUpdate(nsUpdateType aUpdateType)
3185 {
3186     XMLDocument::EndUpdate(aUpdateType);
3187 
3188     MaybeBroadcast();
3189 }
3190 
3191 void
ReportMissingOverlay(nsIURI * aURI)3192 XULDocument::ReportMissingOverlay(nsIURI* aURI)
3193 {
3194     NS_PRECONDITION(aURI, "Must have a URI");
3195 
3196     NS_ConvertUTF8toUTF16 utfSpec(aURI->GetSpecOrDefault());
3197     const char16_t* params[] = { utfSpec.get() };
3198     nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3199                                     NS_LITERAL_CSTRING("XUL Document"), this,
3200                                     nsContentUtils::eXUL_PROPERTIES,
3201                                     "MissingOverlay",
3202                                     params, ArrayLength(params));
3203 }
3204 
3205 nsresult
LoadScript(nsXULPrototypeScript * aScriptProto,bool * aBlock)3206 XULDocument::LoadScript(nsXULPrototypeScript* aScriptProto, bool* aBlock)
3207 {
3208     // Load a transcluded script
3209     nsresult rv;
3210 
3211     bool isChromeDoc = IsChromeURI(mDocumentURI);
3212 
3213     if (isChromeDoc && aScriptProto->HasScriptObject()) {
3214         rv = ExecuteScript(aScriptProto);
3215 
3216         // Ignore return value from execution, and don't block
3217         *aBlock = false;
3218         return NS_OK;
3219     }
3220 
3221     // Try the XUL script cache, in case two XUL documents source the same
3222     // .js file (e.g., strres.js from navigator.xul and utilityOverlay.xul).
3223     // XXXbe the cache relies on aScriptProto's GC root!
3224     bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
3225 
3226     if (isChromeDoc && useXULCache) {
3227         JSScript* newScriptObject =
3228             nsXULPrototypeCache::GetInstance()->GetScript(
3229                                    aScriptProto->mSrcURI);
3230         if (newScriptObject) {
3231             // The script language for a proto must remain constant - we
3232             // can't just change it for this unexpected language.
3233             aScriptProto->Set(newScriptObject);
3234         }
3235 
3236         if (aScriptProto->HasScriptObject()) {
3237             rv = ExecuteScript(aScriptProto);
3238 
3239             // Ignore return value from execution, and don't block
3240             *aBlock = false;
3241             return NS_OK;
3242         }
3243     }
3244 
3245     // Release script objects from FastLoad since we decided against using them
3246     aScriptProto->UnlinkJSObjects();
3247 
3248     // Set the current script prototype so that OnStreamComplete can report
3249     // the right file if there are errors in the script.
3250     NS_ASSERTION(!mCurrentScriptProto,
3251                  "still loading a script when starting another load?");
3252     mCurrentScriptProto = aScriptProto;
3253 
3254     if (isChromeDoc && aScriptProto->mSrcLoading) {
3255         // Another XULDocument load has started, which is still in progress.
3256         // Remember to ResumeWalk this document when the load completes.
3257         mNextSrcLoadWaiter = aScriptProto->mSrcLoadWaiters;
3258         aScriptProto->mSrcLoadWaiters = this;
3259         NS_ADDREF_THIS();
3260     }
3261     else {
3262         nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
3263 
3264         // Note: the loader will keep itself alive while it's loading.
3265         nsCOMPtr<nsIStreamLoader> loader;
3266         rv = NS_NewStreamLoader(getter_AddRefs(loader),
3267                                 aScriptProto->mSrcURI,
3268                                 this, // aObserver
3269                                 this, // aRequestingContext
3270                                 nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS,
3271                                 nsIContentPolicy::TYPE_INTERNAL_SCRIPT,
3272                                 group);
3273 
3274         if (NS_FAILED(rv)) {
3275             mCurrentScriptProto = nullptr;
3276             return rv;
3277         }
3278 
3279         aScriptProto->mSrcLoading = true;
3280     }
3281 
3282     // Block until OnStreamComplete resumes us.
3283     *aBlock = true;
3284     return NS_OK;
3285 }
3286 
3287 NS_IMETHODIMP
OnStreamComplete(nsIStreamLoader * aLoader,nsISupports * context,nsresult aStatus,uint32_t stringLen,const uint8_t * string)3288 XULDocument::OnStreamComplete(nsIStreamLoader* aLoader,
3289                               nsISupports* context,
3290                               nsresult aStatus,
3291                               uint32_t stringLen,
3292                               const uint8_t* string)
3293 {
3294     nsCOMPtr<nsIRequest> request;
3295     aLoader->GetRequest(getter_AddRefs(request));
3296     nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
3297 
3298 #ifdef DEBUG
3299     // print a load error on bad status
3300     if (NS_FAILED(aStatus)) {
3301         if (channel) {
3302             nsCOMPtr<nsIURI> uri;
3303             channel->GetURI(getter_AddRefs(uri));
3304             if (uri) {
3305                 printf("Failed to load %s\n", uri->GetSpecOrDefault().get());
3306             }
3307         }
3308     }
3309 #endif
3310 
3311     // This is the completion routine that will be called when a
3312     // transcluded script completes. Compile and execute the script
3313     // if the load was successful, then continue building content
3314     // from the prototype.
3315     nsresult rv = aStatus;
3316 
3317     NS_ASSERTION(mCurrentScriptProto && mCurrentScriptProto->mSrcLoading,
3318                  "script source not loading on unichar stream complete?");
3319     if (!mCurrentScriptProto) {
3320         // XXX Wallpaper for bug 270042
3321         return NS_OK;
3322     }
3323 
3324     if (NS_SUCCEEDED(aStatus)) {
3325         // If the including XUL document is a FastLoad document, and we're
3326         // compiling an out-of-line script (one with src=...), then we must
3327         // be writing a new FastLoad file.  If we were reading this script
3328         // from the FastLoad file, XULContentSinkImpl::OpenScript (over in
3329         // nsXULContentSink.cpp) would have already deserialized a non-null
3330         // script->mScriptObject, causing control flow at the top of LoadScript
3331         // not to reach here.
3332         nsCOMPtr<nsIURI> uri = mCurrentScriptProto->mSrcURI;
3333 
3334         // XXX should also check nsIHttpChannel::requestSucceeded
3335 
3336         MOZ_ASSERT(!mOffThreadCompiling && (mOffThreadCompileStringLength == 0 &&
3337                                             !mOffThreadCompileStringBuf),
3338                    "XULDocument can't load multiple scripts at once");
3339 
3340         rv = nsScriptLoader::ConvertToUTF16(channel, string, stringLen,
3341                                             EmptyString(), this,
3342                                             mOffThreadCompileStringBuf,
3343                                             mOffThreadCompileStringLength);
3344         if (NS_SUCCEEDED(rv)) {
3345             // Attempt to give ownership of the buffer to the JS engine.  If
3346             // we hit offthread compilation, however, we will have to take it
3347             // back below in order to keep the memory alive until compilation
3348             // completes.
3349             JS::SourceBufferHolder srcBuf(mOffThreadCompileStringBuf,
3350                                           mOffThreadCompileStringLength,
3351                                           JS::SourceBufferHolder::GiveOwnership);
3352             mOffThreadCompileStringBuf = nullptr;
3353             mOffThreadCompileStringLength = 0;
3354 
3355             rv = mCurrentScriptProto->Compile(srcBuf, uri, 1, this, this);
3356             if (NS_SUCCEEDED(rv) && !mCurrentScriptProto->HasScriptObject()) {
3357                 // We will be notified via OnOffThreadCompileComplete when the
3358                 // compile finishes. Keep the contents of the compiled script
3359                 // alive until the compilation finishes.
3360                 mOffThreadCompiling = true;
3361                 // If the JS engine did not take the source buffer, then take
3362                 // it back here to ensure it remains alive.
3363                 mOffThreadCompileStringBuf = srcBuf.take();
3364                 if (mOffThreadCompileStringBuf) {
3365                   mOffThreadCompileStringLength = srcBuf.length();
3366                 }
3367                 BlockOnload();
3368                 return NS_OK;
3369             }
3370         }
3371     }
3372 
3373     return OnScriptCompileComplete(mCurrentScriptProto->GetScriptObject(), rv);
3374 }
3375 
3376 NS_IMETHODIMP
OnScriptCompileComplete(JSScript * aScript,nsresult aStatus)3377 XULDocument::OnScriptCompileComplete(JSScript* aScript, nsresult aStatus)
3378 {
3379     // When compiling off thread the script will not have been attached to the
3380     // script proto yet.
3381     if (aScript && !mCurrentScriptProto->HasScriptObject())
3382         mCurrentScriptProto->Set(aScript);
3383 
3384     // Allow load events to be fired once off thread compilation finishes.
3385     if (mOffThreadCompiling) {
3386         mOffThreadCompiling = false;
3387         UnblockOnload(false);
3388     }
3389 
3390     // After compilation finishes the script's characters are no longer needed.
3391     if (mOffThreadCompileStringBuf) {
3392       js_free(mOffThreadCompileStringBuf);
3393       mOffThreadCompileStringBuf = nullptr;
3394       mOffThreadCompileStringLength = 0;
3395     }
3396 
3397     // Clear mCurrentScriptProto now, but save it first for use below in
3398     // the execute code, and in the while loop that resumes walks of other
3399     // documents that raced to load this script.
3400     nsXULPrototypeScript* scriptProto = mCurrentScriptProto;
3401     mCurrentScriptProto = nullptr;
3402 
3403     // Clear the prototype's loading flag before executing the script or
3404     // resuming document walks, in case any of those control flows starts a
3405     // new script load.
3406     scriptProto->mSrcLoading = false;
3407 
3408     nsresult rv = aStatus;
3409     if (NS_SUCCEEDED(rv)) {
3410         rv = ExecuteScript(scriptProto);
3411 
3412         // If the XUL cache is enabled, save the script object there in
3413         // case different XUL documents source the same script.
3414         //
3415         // But don't save the script in the cache unless the master XUL
3416         // document URL is a chrome: URL.  It is valid for a URL such as
3417         // about:config to translate into a master document URL, whose
3418         // prototype document nodes -- including prototype scripts that
3419         // hold GC roots protecting their mJSObject pointers -- are not
3420         // cached in the XUL prototype cache.  See StartDocumentLoad,
3421         // the fillXULCache logic.
3422         //
3423         // A document such as about:config is free to load a script via
3424         // a URL such as chrome://global/content/config.js, and we must
3425         // not cache that script object without a prototype cache entry
3426         // containing a companion nsXULPrototypeScript node that owns a
3427         // GC root protecting the script object.  Otherwise, the script
3428         // cache entry will dangle once the uncached prototype document
3429         // is released when its owning XULDocument is unloaded.
3430         //
3431         // (See http://bugzilla.mozilla.org/show_bug.cgi?id=98207 for
3432         // the true crime story.)
3433         bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
3434 
3435         if (useXULCache && IsChromeURI(mDocumentURI) && scriptProto->HasScriptObject()) {
3436             JS::Rooted<JSScript*> script(RootingCx(), scriptProto->GetScriptObject());
3437             nsXULPrototypeCache::GetInstance()->PutScript(
3438                                scriptProto->mSrcURI, script);
3439         }
3440 
3441         if (mIsWritingFastLoad && mCurrentPrototype != mMasterPrototype) {
3442             // If we are loading an overlay script, try to serialize
3443             // it to the FastLoad file here.  Master scripts will be
3444             // serialized when the master prototype document gets
3445             // written, at the bottom of ResumeWalk.  That way, master
3446             // out-of-line scripts are serialized in the same order that
3447             // they'll be read, in the FastLoad file, which reduces the
3448             // number of seeks that dump the underlying stream's buffer.
3449             //
3450             // Ignore the return value, as we don't need to propagate
3451             // a failure to write to the FastLoad file, because this
3452             // method aborts that whole process on error.
3453             scriptProto->SerializeOutOfLine(nullptr, mCurrentPrototype);
3454         }
3455         // ignore any evaluation errors
3456     }
3457 
3458     rv = ResumeWalk();
3459 
3460     // Load a pointer to the prototype-script's list of XULDocuments who
3461     // raced to load the same script
3462     XULDocument** docp = &scriptProto->mSrcLoadWaiters;
3463 
3464     // Resume walking other documents that waited for this one's load, first
3465     // executing the script we just compiled, in each doc's script context
3466     XULDocument* doc;
3467     while ((doc = *docp) != nullptr) {
3468         NS_ASSERTION(doc->mCurrentScriptProto == scriptProto,
3469                      "waiting for wrong script to load?");
3470         doc->mCurrentScriptProto = nullptr;
3471 
3472         // Unlink doc from scriptProto's list before executing and resuming
3473         *docp = doc->mNextSrcLoadWaiter;
3474         doc->mNextSrcLoadWaiter = nullptr;
3475 
3476         // Execute only if we loaded and compiled successfully, then resume
3477         if (NS_SUCCEEDED(aStatus) && scriptProto->HasScriptObject()) {
3478             doc->ExecuteScript(scriptProto);
3479         }
3480         doc->ResumeWalk();
3481         NS_RELEASE(doc);
3482     }
3483 
3484     return rv;
3485 }
3486 
3487 nsresult
ExecuteScript(nsXULPrototypeScript * aScript)3488 XULDocument::ExecuteScript(nsXULPrototypeScript *aScript)
3489 {
3490     NS_PRECONDITION(aScript != nullptr, "null ptr");
3491     NS_ENSURE_TRUE(aScript, NS_ERROR_NULL_POINTER);
3492     NS_ENSURE_TRUE(mScriptGlobalObject, NS_ERROR_NOT_INITIALIZED);
3493 
3494     nsresult rv;
3495     rv = mScriptGlobalObject->EnsureScriptEnvironment();
3496     NS_ENSURE_SUCCESS(rv, rv);
3497 
3498     // Execute the precompiled script with the given version
3499     nsAutoMicroTask mt;
3500 
3501     // We're about to run script via JS::CloneAndExecuteScript, so we need an
3502     // AutoEntryScript. This is Gecko specific and not in any spec.
3503     AutoEntryScript aes(mScriptGlobalObject, "precompiled XUL <script> element");
3504     JSContext* cx = aes.cx();
3505 
3506     JS::Rooted<JSScript*> scriptObject(cx, aScript->GetScriptObject());
3507     NS_ENSURE_TRUE(scriptObject, NS_ERROR_UNEXPECTED);
3508 
3509     JS::Rooted<JSObject*> baseGlobal(cx, JS::CurrentGlobalOrNull(cx));
3510     NS_ENSURE_TRUE(xpc::Scriptability::Get(baseGlobal).Allowed(), NS_OK);
3511 
3512     JSAddonId* addonId = mCurrentPrototype ? MapURIToAddonID(mCurrentPrototype->GetURI()) : nullptr;
3513     JS::Rooted<JSObject*> global(cx, xpc::GetAddonScope(cx, baseGlobal, addonId));
3514     NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
3515 
3516     JS::ExposeObjectToActiveJS(global);
3517     JSAutoCompartment ac(cx, global);
3518 
3519     // The script is in the compilation scope. Clone it into the target scope
3520     // and execute it. On failure, ~AutoScriptEntry will handle exceptions, so
3521     // there is no need to manually check the return value.
3522     JS::RootedValue rval(cx);
3523     JS::CloneAndExecuteScript(cx, scriptObject, &rval);
3524 
3525     return NS_OK;
3526 }
3527 
3528 
3529 nsresult
CreateElementFromPrototype(nsXULPrototypeElement * aPrototype,Element ** aResult,bool aIsRoot)3530 XULDocument::CreateElementFromPrototype(nsXULPrototypeElement* aPrototype,
3531                                         Element** aResult,
3532                                         bool aIsRoot)
3533 {
3534     // Create a content model element from a prototype element.
3535     NS_PRECONDITION(aPrototype != nullptr, "null ptr");
3536     if (! aPrototype)
3537         return NS_ERROR_NULL_POINTER;
3538 
3539     *aResult = nullptr;
3540     nsresult rv = NS_OK;
3541 
3542     if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) {
3543         MOZ_LOG(gXULLog, LogLevel::Debug,
3544                ("xul: creating <%s> from prototype",
3545                 NS_ConvertUTF16toUTF8(aPrototype->mNodeInfo->QualifiedName()).get()));
3546     }
3547 
3548     RefPtr<Element> result;
3549 
3550     if (aPrototype->mNodeInfo->NamespaceEquals(kNameSpaceID_XUL)) {
3551         // If it's a XUL element, it'll be lightweight until somebody
3552         // monkeys with it.
3553         rv = nsXULElement::Create(aPrototype, this, true, aIsRoot, getter_AddRefs(result));
3554         if (NS_FAILED(rv)) return rv;
3555     }
3556     else {
3557         // If it's not a XUL element, it's gonna be heavyweight no matter
3558         // what. So we need to copy everything out of the prototype
3559         // into the element.  Get a nodeinfo from our nodeinfo manager
3560         // for this node.
3561         RefPtr<mozilla::dom::NodeInfo> newNodeInfo;
3562         newNodeInfo = mNodeInfoManager->GetNodeInfo(aPrototype->mNodeInfo->NameAtom(),
3563                                                     aPrototype->mNodeInfo->GetPrefixAtom(),
3564                                                     aPrototype->mNodeInfo->NamespaceID(),
3565                                                     nsIDOMNode::ELEMENT_NODE);
3566         if (!newNodeInfo) return NS_ERROR_OUT_OF_MEMORY;
3567         RefPtr<mozilla::dom::NodeInfo> xtfNi = newNodeInfo;
3568         rv = NS_NewElement(getter_AddRefs(result), newNodeInfo.forget(),
3569                            NOT_FROM_PARSER);
3570         if (NS_FAILED(rv))
3571             return rv;
3572 
3573         rv = AddAttributes(aPrototype, result);
3574         if (NS_FAILED(rv)) return rv;
3575     }
3576 
3577     result.forget(aResult);
3578 
3579     return NS_OK;
3580 }
3581 
3582 nsresult
CreateOverlayElement(nsXULPrototypeElement * aPrototype,Element ** aResult)3583 XULDocument::CreateOverlayElement(nsXULPrototypeElement* aPrototype,
3584                                   Element** aResult)
3585 {
3586     nsresult rv;
3587 
3588     RefPtr<Element> element;
3589     rv = CreateElementFromPrototype(aPrototype, getter_AddRefs(element), false);
3590     if (NS_FAILED(rv)) return rv;
3591 
3592     OverlayForwardReference* fwdref =
3593         new OverlayForwardReference(this, element);
3594 
3595     // transferring ownership to ya...
3596     rv = AddForwardReference(fwdref);
3597     if (NS_FAILED(rv)) return rv;
3598 
3599     element.forget(aResult);
3600     return NS_OK;
3601 }
3602 
3603 nsresult
AddAttributes(nsXULPrototypeElement * aPrototype,nsIContent * aElement)3604 XULDocument::AddAttributes(nsXULPrototypeElement* aPrototype,
3605                            nsIContent* aElement)
3606 {
3607     nsresult rv;
3608 
3609     for (uint32_t i = 0; i < aPrototype->mNumAttributes; ++i) {
3610         nsXULPrototypeAttribute* protoattr = &(aPrototype->mAttributes[i]);
3611         nsAutoString  valueStr;
3612         protoattr->mValue.ToString(valueStr);
3613 
3614         rv = aElement->SetAttr(protoattr->mName.NamespaceID(),
3615                                protoattr->mName.LocalName(),
3616                                protoattr->mName.GetPrefix(),
3617                                valueStr,
3618                                false);
3619         if (NS_FAILED(rv)) return rv;
3620     }
3621 
3622     return NS_OK;
3623 }
3624 
3625 
3626 nsresult
CheckTemplateBuilderHookup(nsIContent * aElement,bool * aNeedsHookup)3627 XULDocument::CheckTemplateBuilderHookup(nsIContent* aElement,
3628                                         bool* aNeedsHookup)
3629 {
3630     // See if the element already has a `database' attribute. If it
3631     // does, then the template builder has already been created.
3632     //
3633     // XXX This approach will crash and burn (well, maybe not _that_
3634     // bad) if aElement is not a XUL element.
3635     //
3636     // XXXvarga Do we still want to support non XUL content?
3637     nsCOMPtr<nsIDOMXULElement> xulElement = do_QueryInterface(aElement);
3638     if (xulElement) {
3639         nsCOMPtr<nsIRDFCompositeDataSource> ds;
3640         xulElement->GetDatabase(getter_AddRefs(ds));
3641         if (ds) {
3642             *aNeedsHookup = false;
3643             return NS_OK;
3644         }
3645     }
3646 
3647     // Check aElement for a 'datasources' attribute, if it has
3648     // one a XUL template builder needs to be hooked up.
3649     *aNeedsHookup = aElement->HasAttr(kNameSpaceID_None,
3650                                       nsGkAtoms::datasources);
3651     return NS_OK;
3652 }
3653 
3654 /* static */ nsresult
CreateTemplateBuilder(nsIContent * aElement)3655 XULDocument::CreateTemplateBuilder(nsIContent* aElement)
3656 {
3657     // Check if need to construct a tree builder or content builder.
3658     bool isTreeBuilder = false;
3659 
3660     // return successful if the element is not is a document, as an inline
3661     // script could have removed it
3662     nsIDocument* document = aElement->GetUncomposedDoc();
3663     NS_ENSURE_TRUE(document, NS_OK);
3664 
3665     int32_t nameSpaceID;
3666     nsIAtom* baseTag = document->BindingManager()->
3667       ResolveTag(aElement, &nameSpaceID);
3668 
3669     if ((nameSpaceID == kNameSpaceID_XUL) && (baseTag == nsGkAtoms::tree)) {
3670         // By default, we build content for a tree and then we attach
3671         // the tree content view. However, if the `dont-build-content'
3672         // flag is set, then we we'll attach a tree builder which
3673         // directly implements the tree view.
3674 
3675         nsAutoString flags;
3676         aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::flags, flags);
3677         if (flags.Find(NS_LITERAL_STRING("dont-build-content")) >= 0) {
3678             isTreeBuilder = true;
3679         }
3680     }
3681 
3682     if (isTreeBuilder) {
3683         // Create and initialize a tree builder.
3684         nsCOMPtr<nsIXULTemplateBuilder> builder =
3685             do_CreateInstance("@mozilla.org/xul/xul-tree-builder;1");
3686 
3687         if (! builder)
3688             return NS_ERROR_FAILURE;
3689 
3690         builder->Init(aElement);
3691 
3692         // Create a <treechildren> if one isn't there already.
3693         // XXXvarga what about attributes?
3694         nsCOMPtr<nsIContent> bodyContent;
3695         nsXULContentUtils::FindChildByTag(aElement, kNameSpaceID_XUL,
3696                                           nsGkAtoms::treechildren,
3697                                           getter_AddRefs(bodyContent));
3698 
3699         if (! bodyContent) {
3700             bodyContent =
3701                 document->CreateElem(nsDependentAtomString(nsGkAtoms::treechildren),
3702                                      nullptr, kNameSpaceID_XUL);
3703 
3704             aElement->AppendChildTo(bodyContent, false);
3705         }
3706     }
3707     else {
3708         // Create and initialize a content builder.
3709         nsCOMPtr<nsIXULTemplateBuilder> builder
3710             = do_CreateInstance("@mozilla.org/xul/xul-template-builder;1");
3711 
3712         if (! builder)
3713             return NS_ERROR_FAILURE;
3714 
3715         builder->Init(aElement);
3716         builder->CreateContents(aElement, false);
3717     }
3718 
3719     return NS_OK;
3720 }
3721 
3722 
3723 nsresult
AddPrototypeSheets()3724 XULDocument::AddPrototypeSheets()
3725 {
3726     nsresult rv;
3727 
3728     const nsCOMArray<nsIURI>& sheets = mCurrentPrototype->GetStyleSheetReferences();
3729 
3730     for (int32_t i = 0; i < sheets.Count(); i++) {
3731         nsCOMPtr<nsIURI> uri = sheets[i];
3732 
3733         RefPtr<StyleSheet> incompleteSheet;
3734         rv = CSSLoader()->LoadSheet(uri,
3735                                     mCurrentPrototype->DocumentPrincipal(),
3736                                     EmptyCString(), this,
3737                                     &incompleteSheet);
3738 
3739         // XXXldb We need to prevent bogus sheets from being held in the
3740         // prototype's list, but until then, don't propagate the failure
3741         // from LoadSheet (and thus exit the loop).
3742         if (NS_SUCCEEDED(rv)) {
3743             ++mPendingSheets;
3744             if (!mOverlaySheets.AppendElement(incompleteSheet)) {
3745                 return NS_ERROR_OUT_OF_MEMORY;
3746             }
3747         }
3748     }
3749 
3750     return NS_OK;
3751 }
3752 
3753 
3754 //----------------------------------------------------------------------
3755 //
3756 // XULDocument::OverlayForwardReference
3757 //
3758 
3759 nsForwardReference::Result
Resolve()3760 XULDocument::OverlayForwardReference::Resolve()
3761 {
3762     // Resolve a forward reference from an overlay element; attempt to
3763     // hook it up into the main document.
3764     nsresult rv;
3765     nsCOMPtr<nsIContent> target;
3766 
3767     nsIPresShell *shell = mDocument->GetShell();
3768     bool notify = shell && shell->DidInitialize();
3769 
3770     nsAutoString id;
3771     mOverlay->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
3772     if (id.IsEmpty()) {
3773         // mOverlay is a direct child of <overlay> and has no id.
3774         // Insert it under the root element in the base document.
3775         Element* root = mDocument->GetRootElement();
3776         if (!root) {
3777             return eResolve_Error;
3778         }
3779 
3780         rv = mDocument->InsertElement(root, mOverlay, notify);
3781         if (NS_FAILED(rv)) return eResolve_Error;
3782 
3783         target = mOverlay;
3784     }
3785     else {
3786         // The hook-up element has an id, try to match it with an element
3787         // with the same id in the base document.
3788         target = mDocument->GetElementById(id);
3789 
3790         // If we can't find the element in the document, defer the hookup
3791         // until later.
3792         if (!target)
3793             return eResolve_Later;
3794 
3795         rv = Merge(target, mOverlay, notify);
3796         if (NS_FAILED(rv)) return eResolve_Error;
3797     }
3798 
3799     // Check if 'target' is still in our document --- it might not be!
3800     if (!notify && target->GetUncomposedDoc() == mDocument) {
3801         // Add child and any descendants to the element map
3802         // XXX this is bogus, the content in 'target' might already be
3803         // in the document
3804         rv = mDocument->AddSubtreeToDocument(target);
3805         if (NS_FAILED(rv)) return eResolve_Error;
3806     }
3807 
3808     if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) {
3809         nsAutoCString idC;
3810         idC.AssignWithConversion(id);
3811         MOZ_LOG(gXULLog, LogLevel::Debug,
3812                ("xul: overlay resolved '%s'",
3813                 idC.get()));
3814     }
3815 
3816     mResolved = true;
3817     return eResolve_Succeeded;
3818 }
3819 
3820 
3821 
3822 nsresult
Merge(nsIContent * aTargetNode,nsIContent * aOverlayNode,bool aNotify)3823 XULDocument::OverlayForwardReference::Merge(nsIContent* aTargetNode,
3824                                             nsIContent* aOverlayNode,
3825                                             bool aNotify)
3826 {
3827     // This function is given:
3828     // aTargetNode:  the node in the document whose 'id' attribute
3829     //               matches a toplevel node in our overlay.
3830     // aOverlayNode: the node in the overlay document that matches
3831     //               a node in the actual document.
3832     // aNotify:      whether or not content manipulation methods should
3833     //               use the aNotify parameter. After the initial
3834     //               reflow (i.e. in the dynamic overlay merge case),
3835     //               we want all the content manipulation methods we
3836     //               call to notify so that frames are constructed
3837     //               etc. Otherwise do not, since that's during initial
3838     //               document construction before StartLayout has been
3839     //               called which will do everything for us.
3840     //
3841     // This function merges the tree from the overlay into the tree in
3842     // the document, overwriting attributes and appending child content
3843     // nodes appropriately. (See XUL overlay reference for details)
3844 
3845     nsresult rv;
3846 
3847     // Merge attributes from the overlay content node to that of the
3848     // actual document.
3849     uint32_t i;
3850     const nsAttrName* name;
3851     for (i = 0; (name = aOverlayNode->GetAttrNameAt(i)); ++i) {
3852         // We don't want to swap IDs, they should be the same.
3853         if (name->Equals(nsGkAtoms::id))
3854             continue;
3855 
3856         // In certain cases merging command or observes is unsafe, so don't.
3857         if (!aNotify) {
3858             if (aTargetNode->NodeInfo()->Equals(nsGkAtoms::observes,
3859                                                 kNameSpaceID_XUL))
3860                 continue;
3861 
3862             if (name->Equals(nsGkAtoms::observes) &&
3863                 aTargetNode->HasAttr(kNameSpaceID_None, nsGkAtoms::observes))
3864                 continue;
3865 
3866             if (name->Equals(nsGkAtoms::command) &&
3867                 aTargetNode->HasAttr(kNameSpaceID_None, nsGkAtoms::command) &&
3868                 !aTargetNode->NodeInfo()->Equals(nsGkAtoms::key,
3869                                                  kNameSpaceID_XUL) &&
3870                 !aTargetNode->NodeInfo()->Equals(nsGkAtoms::menuitem,
3871                                                  kNameSpaceID_XUL))
3872                 continue;
3873         }
3874 
3875         int32_t nameSpaceID = name->NamespaceID();
3876         nsIAtom* attr = name->LocalName();
3877         nsIAtom* prefix = name->GetPrefix();
3878 
3879         nsAutoString value;
3880         aOverlayNode->GetAttr(nameSpaceID, attr, value);
3881 
3882         // Element in the overlay has the 'removeelement' attribute set
3883         // so remove it from the actual document.
3884         if (attr == nsGkAtoms::removeelement &&
3885             value.EqualsLiteral("true")) {
3886 
3887             nsCOMPtr<nsINode> parent = aTargetNode->GetParentNode();
3888             if (!parent) return NS_ERROR_FAILURE;
3889             rv = RemoveElement(parent, aTargetNode);
3890             if (NS_FAILED(rv)) return rv;
3891 
3892             return NS_OK;
3893         }
3894 
3895         rv = aTargetNode->SetAttr(nameSpaceID, attr, prefix, value, aNotify);
3896         if (!NS_FAILED(rv) && !aNotify)
3897             rv = mDocument->BroadcastAttributeChangeFromOverlay(aTargetNode,
3898                                                                 nameSpaceID,
3899                                                                 attr, prefix,
3900                                                                 value);
3901         if (NS_FAILED(rv)) return rv;
3902     }
3903 
3904 
3905     // Walk our child nodes, looking for elements that have the 'id'
3906     // attribute set. If we find any, we must do a parent check in the
3907     // actual document to ensure that the structure matches that of
3908     // the actual document. If it does, we can call ourselves and attempt
3909     // to merge inside that subtree. If not, we just append the tree to
3910     // the parent like any other.
3911 
3912     uint32_t childCount = aOverlayNode->GetChildCount();
3913 
3914     // This must be a strong reference since it will be the only
3915     // reference to a content object during part of this loop.
3916     nsCOMPtr<nsIContent> currContent;
3917 
3918     for (i = 0; i < childCount; ++i) {
3919         currContent = aOverlayNode->GetFirstChild();
3920 
3921         nsIAtom *idAtom = currContent->GetID();
3922 
3923         nsIContent *elementInDocument = nullptr;
3924         if (idAtom) {
3925             nsDependentAtomString id(idAtom);
3926 
3927             if (!id.IsEmpty()) {
3928                 nsIDocument *doc = aTargetNode->GetUncomposedDoc();
3929                 //XXXsmaug should we use ShadowRoot::GetElementById()
3930                 //         if doc is null?
3931                 if (!doc) return NS_ERROR_FAILURE;
3932 
3933                 elementInDocument = doc->GetElementById(id);
3934             }
3935         }
3936 
3937         // The item has an 'id' attribute set, and we need to check with
3938         // the actual document to see if an item with this id exists at
3939         // this locale. If so, we want to merge the subtree under that
3940         // node. Otherwise, we just do an append as if the element had
3941         // no id attribute.
3942         if (elementInDocument) {
3943             // Given two parents, aTargetNode and aOverlayNode, we want
3944             // to call merge on currContent if we find an associated
3945             // node in the document with the same id as currContent that
3946             // also has aTargetNode as its parent.
3947 
3948             nsIContent *elementParent = elementInDocument->GetParent();
3949 
3950             nsIAtom *parentID = elementParent->GetID();
3951             if (parentID &&
3952                 aTargetNode->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id,
3953                                          nsDependentAtomString(parentID),
3954                                          eCaseMatters)) {
3955                 // The element matches. "Go Deep!"
3956                 rv = Merge(elementInDocument, currContent, aNotify);
3957                 if (NS_FAILED(rv)) return rv;
3958                 aOverlayNode->RemoveChildAt(0, false);
3959 
3960                 continue;
3961             }
3962         }
3963 
3964         aOverlayNode->RemoveChildAt(0, false);
3965 
3966         rv = InsertElement(aTargetNode, currContent, aNotify);
3967         if (NS_FAILED(rv)) return rv;
3968     }
3969 
3970     return NS_OK;
3971 }
3972 
3973 
3974 
~OverlayForwardReference()3975 XULDocument::OverlayForwardReference::~OverlayForwardReference()
3976 {
3977     if (MOZ_LOG_TEST(gXULLog, LogLevel::Warning) && !mResolved) {
3978         nsAutoString id;
3979         mOverlay->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
3980 
3981         nsAutoCString idC;
3982         idC.AssignWithConversion(id);
3983 
3984         nsIURI *protoURI = mDocument->mCurrentPrototype->GetURI();
3985 
3986         nsCOMPtr<nsIURI> docURI;
3987         mDocument->mChannel->GetOriginalURI(getter_AddRefs(docURI));
3988 
3989         MOZ_LOG(gXULLog, LogLevel::Warning,
3990                ("xul: %s overlay failed to resolve '%s' in %s",
3991                 protoURI->GetSpecOrDefault().get(), idC.get(),
3992                 docURI ? docURI->GetSpecOrDefault().get() : ""));
3993     }
3994 }
3995 
3996 
3997 //----------------------------------------------------------------------
3998 //
3999 // XULDocument::BroadcasterHookup
4000 //
4001 
4002 nsForwardReference::Result
Resolve()4003 XULDocument::BroadcasterHookup::Resolve()
4004 {
4005     nsresult rv;
4006 
4007     bool listener;
4008     rv = mDocument->CheckBroadcasterHookup(mObservesElement, &listener, &mResolved);
4009     if (NS_FAILED(rv)) return eResolve_Error;
4010 
4011     return mResolved ? eResolve_Succeeded : eResolve_Later;
4012 }
4013 
4014 
~BroadcasterHookup()4015 XULDocument::BroadcasterHookup::~BroadcasterHookup()
4016 {
4017     if (MOZ_LOG_TEST(gXULLog, LogLevel::Warning) && !mResolved) {
4018         // Tell the world we failed
4019 
4020         nsAutoString broadcasterID;
4021         nsAutoString attribute;
4022 
4023         if (mObservesElement->IsXULElement(nsGkAtoms::observes)) {
4024             mObservesElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, broadcasterID);
4025             mObservesElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, attribute);
4026         }
4027         else {
4028             mObservesElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, broadcasterID);
4029             attribute.Assign('*');
4030         }
4031 
4032         nsAutoCString attributeC,broadcasteridC;
4033         attributeC.AssignWithConversion(attribute);
4034         broadcasteridC.AssignWithConversion(broadcasterID);
4035         MOZ_LOG(gXULLog, LogLevel::Warning,
4036                ("xul: broadcaster hookup failed <%s attribute='%s'> to %s",
4037                 nsAtomCString(mObservesElement->NodeInfo()->NameAtom()).get(),
4038                 attributeC.get(),
4039                 broadcasteridC.get()));
4040     }
4041 }
4042 
4043 
4044 //----------------------------------------------------------------------
4045 //
4046 // XULDocument::TemplateBuilderHookup
4047 //
4048 
4049 nsForwardReference::Result
Resolve()4050 XULDocument::TemplateBuilderHookup::Resolve()
4051 {
4052     bool needsHookup;
4053     nsresult rv = CheckTemplateBuilderHookup(mElement, &needsHookup);
4054     if (NS_FAILED(rv))
4055         return eResolve_Error;
4056 
4057     if (needsHookup) {
4058         rv = CreateTemplateBuilder(mElement);
4059         if (NS_FAILED(rv))
4060             return eResolve_Error;
4061     }
4062 
4063     return eResolve_Succeeded;
4064 }
4065 
4066 
4067 //----------------------------------------------------------------------
4068 
4069 nsresult
BroadcastAttributeChangeFromOverlay(nsIContent * aNode,int32_t aNameSpaceID,nsIAtom * aAttribute,nsIAtom * aPrefix,const nsAString & aValue)4070 XULDocument::BroadcastAttributeChangeFromOverlay(nsIContent* aNode,
4071                                                  int32_t aNameSpaceID,
4072                                                  nsIAtom* aAttribute,
4073                                                  nsIAtom* aPrefix,
4074                                                  const nsAString& aValue)
4075 {
4076     nsresult rv = NS_OK;
4077 
4078     if (!mBroadcasterMap || !CanBroadcast(aNameSpaceID, aAttribute))
4079         return rv;
4080 
4081     if (!aNode->IsElement())
4082         return rv;
4083 
4084     auto entry = static_cast<BroadcasterMapEntry*>
4085                             (mBroadcasterMap->Search(aNode->AsElement()));
4086     if (!entry)
4087         return rv;
4088 
4089     // We've got listeners: push the value.
4090     for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
4091         BroadcastListener* bl = entry->mListeners[i];
4092 
4093         if ((bl->mAttribute != aAttribute) &&
4094             (bl->mAttribute != nsGkAtoms::_asterisk))
4095             continue;
4096 
4097         nsCOMPtr<nsIContent> l = do_QueryReferent(bl->mListener);
4098         if (l) {
4099             rv = l->SetAttr(aNameSpaceID, aAttribute,
4100                             aPrefix, aValue, false);
4101             if (NS_FAILED(rv)) return rv;
4102         }
4103     }
4104     return rv;
4105 }
4106 
4107 nsresult
FindBroadcaster(Element * aElement,Element ** aListener,nsString & aBroadcasterID,nsString & aAttribute,Element ** aBroadcaster)4108 XULDocument::FindBroadcaster(Element* aElement,
4109                              Element** aListener,
4110                              nsString& aBroadcasterID,
4111                              nsString& aAttribute,
4112                              Element** aBroadcaster)
4113 {
4114     mozilla::dom::NodeInfo *ni = aElement->NodeInfo();
4115     *aListener = nullptr;
4116     *aBroadcaster = nullptr;
4117 
4118     if (ni->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
4119         // It's an <observes> element, which means that the actual
4120         // listener is the _parent_ node. This element should have an
4121         // 'element' attribute that specifies the ID of the
4122         // broadcaster element, and an 'attribute' element, which
4123         // specifies the name of the attribute to observe.
4124         nsIContent* parent = aElement->GetParent();
4125         if (!parent) {
4126              // <observes> is the root element
4127             return NS_FINDBROADCASTER_NOT_FOUND;
4128         }
4129 
4130         // If we're still parented by an 'overlay' tag, then we haven't
4131         // made it into the real document yet. Defer hookup.
4132         if (parent->NodeInfo()->Equals(nsGkAtoms::overlay,
4133                                        kNameSpaceID_XUL)) {
4134             return NS_FINDBROADCASTER_AWAIT_OVERLAYS;
4135         }
4136 
4137         *aListener = parent->IsElement() ? parent->AsElement() : nullptr;
4138         NS_IF_ADDREF(*aListener);
4139 
4140         aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, aBroadcasterID);
4141         if (aBroadcasterID.IsEmpty()) {
4142             return NS_FINDBROADCASTER_NOT_FOUND;
4143         }
4144         aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, aAttribute);
4145     }
4146     else {
4147         // It's a generic element, which means that we'll use the
4148         // value of the 'observes' attribute to determine the ID of
4149         // the broadcaster element, and we'll watch _all_ of its
4150         // values.
4151         aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, aBroadcasterID);
4152 
4153         // Bail if there's no aBroadcasterID
4154         if (aBroadcasterID.IsEmpty()) {
4155             // Try the command attribute next.
4156             aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::command, aBroadcasterID);
4157             if (!aBroadcasterID.IsEmpty()) {
4158                 // We've got something in the command attribute.  We
4159                 // only treat this as a normal broadcaster if we are
4160                 // not a menuitem or a key.
4161 
4162                 if (ni->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
4163                     ni->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) {
4164                 return NS_FINDBROADCASTER_NOT_FOUND;
4165               }
4166             }
4167             else {
4168               return NS_FINDBROADCASTER_NOT_FOUND;
4169             }
4170         }
4171 
4172         *aListener = aElement;
4173         NS_ADDREF(*aListener);
4174 
4175         aAttribute.Assign('*');
4176     }
4177 
4178     // Make sure we got a valid listener.
4179     NS_ENSURE_TRUE(*aListener, NS_ERROR_UNEXPECTED);
4180 
4181     // Try to find the broadcaster element in the document.
4182     *aBroadcaster = GetElementById(aBroadcasterID);
4183 
4184     // If we can't find the broadcaster, then we'll need to defer the
4185     // hookup. We may need to resolve some of the other overlays
4186     // first.
4187     if (! *aBroadcaster) {
4188         return NS_FINDBROADCASTER_AWAIT_OVERLAYS;
4189     }
4190 
4191     NS_ADDREF(*aBroadcaster);
4192 
4193     return NS_FINDBROADCASTER_FOUND;
4194 }
4195 
4196 nsresult
CheckBroadcasterHookup(Element * aElement,bool * aNeedsHookup,bool * aDidResolve)4197 XULDocument::CheckBroadcasterHookup(Element* aElement,
4198                                     bool* aNeedsHookup,
4199                                     bool* aDidResolve)
4200 {
4201     // Resolve a broadcaster hookup. Look at the element that we're
4202     // trying to resolve: it could be an '<observes>' element, or just
4203     // a vanilla element with an 'observes' attribute on it.
4204     nsresult rv;
4205 
4206     *aDidResolve = false;
4207 
4208     nsCOMPtr<Element> listener;
4209     nsAutoString broadcasterID;
4210     nsAutoString attribute;
4211     nsCOMPtr<Element> broadcaster;
4212 
4213     rv = FindBroadcaster(aElement, getter_AddRefs(listener),
4214                          broadcasterID, attribute, getter_AddRefs(broadcaster));
4215     switch (rv) {
4216         case NS_FINDBROADCASTER_NOT_FOUND:
4217             *aNeedsHookup = false;
4218             return NS_OK;
4219         case NS_FINDBROADCASTER_AWAIT_OVERLAYS:
4220             *aNeedsHookup = true;
4221             return NS_OK;
4222         case NS_FINDBROADCASTER_FOUND:
4223             break;
4224         default:
4225             return rv;
4226     }
4227 
4228     NS_ENSURE_ARG(broadcaster && listener);
4229     ErrorResult domRv;
4230     AddBroadcastListenerFor(*broadcaster, *listener, attribute, domRv);
4231     if (domRv.Failed()) {
4232         return domRv.StealNSResult();
4233     }
4234 
4235     // Tell the world we succeeded
4236     if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) {
4237         nsCOMPtr<nsIContent> content =
4238             do_QueryInterface(listener);
4239 
4240         NS_ASSERTION(content != nullptr, "not an nsIContent");
4241         if (! content)
4242             return rv;
4243 
4244         nsAutoCString attributeC,broadcasteridC;
4245         attributeC.AssignWithConversion(attribute);
4246         broadcasteridC.AssignWithConversion(broadcasterID);
4247         MOZ_LOG(gXULLog, LogLevel::Debug,
4248                ("xul: broadcaster hookup <%s attribute='%s'> to %s",
4249                 nsAtomCString(content->NodeInfo()->NameAtom()).get(),
4250                 attributeC.get(),
4251                 broadcasteridC.get()));
4252     }
4253 
4254     *aNeedsHookup = false;
4255     *aDidResolve = true;
4256     return NS_OK;
4257 }
4258 
4259 nsresult
InsertElement(nsINode * aParent,nsIContent * aChild,bool aNotify)4260 XULDocument::InsertElement(nsINode* aParent, nsIContent* aChild,
4261                            bool aNotify)
4262 {
4263     // Insert aChild appropriately into aParent, accounting for a
4264     // 'pos' attribute set on aChild.
4265 
4266     nsAutoString posStr;
4267     bool wasInserted = false;
4268 
4269     // insert after an element of a given id
4270     aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::insertafter, posStr);
4271     bool isInsertAfter = true;
4272 
4273     if (posStr.IsEmpty()) {
4274         aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::insertbefore, posStr);
4275         isInsertAfter = false;
4276     }
4277 
4278     if (!posStr.IsEmpty()) {
4279         nsIDocument *document = aParent->OwnerDoc();
4280 
4281         nsIContent *content = nullptr;
4282 
4283         char* str = ToNewCString(posStr);
4284         char* rest;
4285         char* token = nsCRT::strtok(str, ", ", &rest);
4286 
4287         while (token) {
4288             content = document->GetElementById(NS_ConvertASCIItoUTF16(token));
4289             if (content)
4290                 break;
4291 
4292             token = nsCRT::strtok(rest, ", ", &rest);
4293         }
4294         free(str);
4295 
4296         if (content) {
4297             int32_t pos = aParent->IndexOf(content);
4298 
4299             if (pos != -1) {
4300                 pos = isInsertAfter ? pos + 1 : pos;
4301                 nsresult rv = aParent->InsertChildAt(aChild, pos, aNotify);
4302                 if (NS_FAILED(rv))
4303                     return rv;
4304 
4305                 wasInserted = true;
4306             }
4307         }
4308     }
4309 
4310     if (!wasInserted) {
4311 
4312         aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::position, posStr);
4313         if (!posStr.IsEmpty()) {
4314             nsresult rv;
4315             // Positions are one-indexed.
4316             int32_t pos = posStr.ToInteger(&rv);
4317             // Note: if the insertion index (which is |pos - 1|) would be less
4318             // than 0 or greater than the number of children aParent has, then
4319             // don't insert, since the position is bogus.  Just skip on to
4320             // appending.
4321             if (NS_SUCCEEDED(rv) && pos > 0 &&
4322                 uint32_t(pos - 1) <= aParent->GetChildCount()) {
4323                 rv = aParent->InsertChildAt(aChild, pos - 1, aNotify);
4324                 if (NS_SUCCEEDED(rv))
4325                     wasInserted = true;
4326                 // If the insertion fails, then we should still
4327                 // attempt an append.  Thus, rather than returning rv
4328                 // immediately, we fall through to the final
4329                 // "catch-all" case that just does an AppendChildTo.
4330             }
4331         }
4332     }
4333 
4334     if (!wasInserted) {
4335         return aParent->AppendChildTo(aChild, aNotify);
4336     }
4337     return NS_OK;
4338 }
4339 
4340 nsresult
RemoveElement(nsINode * aParent,nsINode * aChild)4341 XULDocument::RemoveElement(nsINode* aParent, nsINode* aChild)
4342 {
4343     int32_t nodeOffset = aParent->IndexOf(aChild);
4344 
4345     aParent->RemoveChildAt(nodeOffset, true);
4346     return NS_OK;
4347 }
4348 
4349 //----------------------------------------------------------------------
4350 //
4351 // CachedChromeStreamListener
4352 //
4353 
CachedChromeStreamListener(XULDocument * aDocument,bool aProtoLoaded)4354 XULDocument::CachedChromeStreamListener::CachedChromeStreamListener(XULDocument* aDocument, bool aProtoLoaded)
4355     : mDocument(aDocument),
4356       mProtoLoaded(aProtoLoaded)
4357 {
4358 }
4359 
4360 
~CachedChromeStreamListener()4361 XULDocument::CachedChromeStreamListener::~CachedChromeStreamListener()
4362 {
4363 }
4364 
4365 
NS_IMPL_ISUPPORTS(XULDocument::CachedChromeStreamListener,nsIRequestObserver,nsIStreamListener)4366 NS_IMPL_ISUPPORTS(XULDocument::CachedChromeStreamListener,
4367                   nsIRequestObserver, nsIStreamListener)
4368 
4369 NS_IMETHODIMP
4370 XULDocument::CachedChromeStreamListener::OnStartRequest(nsIRequest *request,
4371                                                         nsISupports* acontext)
4372 {
4373     return NS_ERROR_PARSED_DATA_CACHED;
4374 }
4375 
4376 
4377 NS_IMETHODIMP
OnStopRequest(nsIRequest * request,nsISupports * aContext,nsresult aStatus)4378 XULDocument::CachedChromeStreamListener::OnStopRequest(nsIRequest *request,
4379                                                        nsISupports* aContext,
4380                                                        nsresult aStatus)
4381 {
4382     if (! mProtoLoaded)
4383         return NS_OK;
4384 
4385     return mDocument->OnPrototypeLoadDone(true);
4386 }
4387 
4388 
4389 NS_IMETHODIMP
OnDataAvailable(nsIRequest * request,nsISupports * aContext,nsIInputStream * aInStr,uint64_t aSourceOffset,uint32_t aCount)4390 XULDocument::CachedChromeStreamListener::OnDataAvailable(nsIRequest *request,
4391                                                          nsISupports* aContext,
4392                                                          nsIInputStream* aInStr,
4393                                                          uint64_t aSourceOffset,
4394                                                          uint32_t aCount)
4395 {
4396     NS_NOTREACHED("CachedChromeStream doesn't receive data");
4397     return NS_ERROR_UNEXPECTED;
4398 }
4399 
4400 //----------------------------------------------------------------------
4401 //
4402 // ParserObserver
4403 //
4404 
ParserObserver(XULDocument * aDocument,nsXULPrototypeDocument * aPrototype)4405 XULDocument::ParserObserver::ParserObserver(XULDocument* aDocument,
4406                                             nsXULPrototypeDocument* aPrototype)
4407     : mDocument(aDocument), mPrototype(aPrototype)
4408 {
4409 }
4410 
~ParserObserver()4411 XULDocument::ParserObserver::~ParserObserver()
4412 {
4413 }
4414 
NS_IMPL_ISUPPORTS(XULDocument::ParserObserver,nsIRequestObserver)4415 NS_IMPL_ISUPPORTS(XULDocument::ParserObserver, nsIRequestObserver)
4416 
4417 NS_IMETHODIMP
4418 XULDocument::ParserObserver::OnStartRequest(nsIRequest *request,
4419                                             nsISupports* aContext)
4420 {
4421     // Guard against buggy channels calling OnStartRequest multiple times.
4422     if (mPrototype) {
4423         nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
4424         nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
4425         if (channel && secMan) {
4426             nsCOMPtr<nsIPrincipal> principal;
4427             secMan->GetChannelResultPrincipal(channel, getter_AddRefs(principal));
4428 
4429             // Failure there is ok -- it'll just set a (safe) null principal
4430             mPrototype->SetDocumentPrincipal(principal);
4431         }
4432 
4433         // Make sure to avoid cycles
4434         mPrototype = nullptr;
4435     }
4436 
4437     return NS_OK;
4438 }
4439 
4440 NS_IMETHODIMP
OnStopRequest(nsIRequest * request,nsISupports * aContext,nsresult aStatus)4441 XULDocument::ParserObserver::OnStopRequest(nsIRequest *request,
4442                                            nsISupports* aContext,
4443                                            nsresult aStatus)
4444 {
4445     nsresult rv = NS_OK;
4446 
4447     if (NS_FAILED(aStatus)) {
4448         // If an overlay load fails, we need to nudge the prototype
4449         // walk along.
4450         nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
4451         if (aChannel) {
4452             nsCOMPtr<nsIURI> uri;
4453             aChannel->GetOriginalURI(getter_AddRefs(uri));
4454             if (uri) {
4455                 mDocument->ReportMissingOverlay(uri);
4456             }
4457         }
4458 
4459         rv = mDocument->ResumeWalk();
4460     }
4461 
4462     // Drop the reference to the document to break cycle between the
4463     // document, the parser, the content sink, and the parser
4464     // observer.
4465     mDocument = nullptr;
4466 
4467     return rv;
4468 }
4469 
4470 already_AddRefed<nsPIWindowRoot>
GetWindowRoot()4471 XULDocument::GetWindowRoot()
4472 {
4473   if (!mDocumentContainer) {
4474     return nullptr;
4475   }
4476 
4477     nsCOMPtr<nsPIDOMWindowOuter> piWin = mDocumentContainer->GetWindow();
4478     return piWin ? piWin->GetTopWindowRoot() : nullptr;
4479 }
4480 
4481 bool
IsDocumentRightToLeft()4482 XULDocument::IsDocumentRightToLeft()
4483 {
4484     // setting the localedir attribute on the root element forces a
4485     // specific direction for the document.
4486     Element* element = GetRootElement();
4487     if (element) {
4488         static nsIContent::AttrValuesArray strings[] =
4489             {&nsGkAtoms::ltr, &nsGkAtoms::rtl, nullptr};
4490         switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
4491                                          strings, eCaseMatters)) {
4492             case 0: return false;
4493             case 1: return true;
4494             default: break; // otherwise, not a valid value, so fall through
4495         }
4496     }
4497 
4498     // otherwise, get the locale from the chrome registry and
4499     // look up the intl.uidirection.<locale> preference
4500     nsCOMPtr<nsIXULChromeRegistry> reg =
4501         mozilla::services::GetXULChromeRegistryService();
4502     if (!reg)
4503         return false;
4504 
4505     nsAutoCString package;
4506     bool isChrome;
4507     if (NS_SUCCEEDED(mDocumentURI->SchemeIs("chrome", &isChrome)) &&
4508         isChrome) {
4509         mDocumentURI->GetHostPort(package);
4510     }
4511     else {
4512         // use the 'global' package for about and resource uris.
4513         // otherwise, just default to left-to-right.
4514         bool isAbout, isResource;
4515         if (NS_SUCCEEDED(mDocumentURI->SchemeIs("about", &isAbout)) &&
4516             isAbout) {
4517             package.AssignLiteral("global");
4518         }
4519         else if (NS_SUCCEEDED(mDocumentURI->SchemeIs("resource", &isResource)) &&
4520             isResource) {
4521             package.AssignLiteral("global");
4522         }
4523         else {
4524             return false;
4525         }
4526     }
4527 
4528     bool isRTL = false;
4529     reg->IsLocaleRTL(package, &isRTL);
4530     return isRTL;
4531 }
4532 
4533 void
ResetDocumentDirection()4534 XULDocument::ResetDocumentDirection()
4535 {
4536     DocumentStatesChanged(NS_DOCUMENT_STATE_RTL_LOCALE);
4537 }
4538 
4539 void
DirectionChanged(const char * aPrefName,void * aData)4540 XULDocument::DirectionChanged(const char* aPrefName, void* aData)
4541 {
4542   // Reset the direction and restyle the document if necessary.
4543   XULDocument* doc = (XULDocument *)aData;
4544   if (doc) {
4545       doc->ResetDocumentDirection();
4546   }
4547 }
4548 
4549 int
GetDocumentLWTheme()4550 XULDocument::GetDocumentLWTheme()
4551 {
4552     if (mDocLWTheme == Doc_Theme_Uninitialized) {
4553         mDocLWTheme = Doc_Theme_None; // No lightweight theme by default
4554 
4555         Element* element = GetRootElement();
4556         nsAutoString hasLWTheme;
4557         if (element &&
4558             element->GetAttr(kNameSpaceID_None, nsGkAtoms::lwtheme, hasLWTheme) &&
4559             !(hasLWTheme.IsEmpty()) &&
4560             hasLWTheme.EqualsLiteral("true")) {
4561             mDocLWTheme = Doc_Theme_Neutral;
4562             nsAutoString lwTheme;
4563             element->GetAttr(kNameSpaceID_None, nsGkAtoms::lwthemetextcolor, lwTheme);
4564             if (!(lwTheme.IsEmpty())) {
4565                 if (lwTheme.EqualsLiteral("dark"))
4566                     mDocLWTheme = Doc_Theme_Dark;
4567                 else if (lwTheme.EqualsLiteral("bright"))
4568                     mDocLWTheme = Doc_Theme_Bright;
4569             }
4570         }
4571     }
4572     return mDocLWTheme;
4573 }
4574 
4575 NS_IMETHODIMP
GetBoxObjectFor(nsIDOMElement * aElement,nsIBoxObject ** aResult)4576 XULDocument::GetBoxObjectFor(nsIDOMElement* aElement, nsIBoxObject** aResult)
4577 {
4578     ErrorResult rv;
4579     nsCOMPtr<Element> el = do_QueryInterface(aElement);
4580     *aResult = GetBoxObjectFor(el, rv).take();
4581     return rv.StealNSResult();
4582 }
4583 
4584 JSObject*
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)4585 XULDocument::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
4586 {
4587   return XULDocumentBinding::Wrap(aCx, this, aGivenProto);
4588 }
4589 
4590 } // namespace dom
4591 } // namespace mozilla
4592