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