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