content/xul/document/src/XULDocument.cpp

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:6fcde34c8dd2
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim: set ts=4 sw=4 et tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 /*
8
9 An implementation for the XUL document. This implementation serves
10 as the basis for generating an NGLayout content model.
11
12 Notes
13 -----
14
15 1. We do some monkey business in the document observer methods to`
16 keep the element map in sync for HTML elements. Why don't we just
17 do it for _all_ elements? Well, in the case of XUL elements,
18 which may be lazily created during frame construction, the
19 document observer methods will never be called because we'll be
20 adding the XUL nodes into the content model "quietly".
21
22 */
23
24 #include "mozilla/ArrayUtils.h"
25
26 // Note the ALPHABETICAL ORDERING
27 #include "XULDocument.h"
28
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"
94
95 using namespace mozilla;
96 using namespace mozilla::dom;
97
98 //----------------------------------------------------------------------
99 //
100 // CIDs
101 //
102
103 static NS_DEFINE_CID(kParserCID, NS_PARSER_CID);
104
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 }
114
115 //----------------------------------------------------------------------
116 //
117 // Miscellaneous Constants
118 //
119
120 const nsForwardReference::Phase nsForwardReference::kPasses[] = {
121 nsForwardReference::eConstruction,
122 nsForwardReference::eHookup,
123 nsForwardReference::eDone
124 };
125
126 const uint32_t kMaxAttrNameLength = 512;
127 const uint32_t kMaxAttributeLength = 4096;
128
129 //----------------------------------------------------------------------
130 //
131 // Statics
132 //
133
134 int32_t XULDocument::gRefCnt = 0;
135
136 nsIRDFService* XULDocument::gRDFService;
137 nsIRDFResource* XULDocument::kNC_persist;
138 nsIRDFResource* XULDocument::kNC_attribute;
139 nsIRDFResource* XULDocument::kNC_value;
140
141 PRLogModuleInfo* XULDocument::gXULLog;
142
143 //----------------------------------------------------------------------
144
145 struct BroadcasterMapEntry : public PLDHashEntryHdr {
146 Element* mBroadcaster; // [WEAK]
147 nsSmallVoidArray mListeners; // [OWNING] of BroadcastListener objects
148 };
149
150 struct BroadcastListener {
151 nsWeakPtr mListener;
152 nsCOMPtr<nsIAtom> mAttribute;
153 };
154
155 Element*
156 nsRefMapEntry::GetFirstElement()
157 {
158 return static_cast<Element*>(mRefContentList.SafeElementAt(0));
159 }
160
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 }
168
169 bool
170 nsRefMapEntry::AddElement(Element* aElement)
171 {
172 if (mRefContentList.IndexOf(aElement) >= 0)
173 return true;
174 return mRefContentList.AppendElement(aElement);
175 }
176
177 bool
178 nsRefMapEntry::RemoveElement(Element* aElement)
179 {
180 mRefContentList.RemoveElement(aElement);
181 return mRefContentList.Count() == 0;
182 }
183
184 //----------------------------------------------------------------------
185 //
186 // ctors & dtors
187 //
188
189 namespace mozilla {
190 namespace dom {
191
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.
200
201 // Override the default in nsDocument
202 mCharacterSet.AssignLiteral("UTF-8");
203
204 mDefaultElementType = kNameSpaceID_XUL;
205 mIsXUL = true;
206
207 mDelayFrameLoaderInitialization = true;
208
209 mAllowXULXBL = eTriTrue;
210 }
211
212 XULDocument::~XULDocument()
213 {
214 NS_ASSERTION(mNextSrcLoadWaiter == nullptr,
215 "unreferenced document still waiting for script source to load?");
216
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();
223
224 // Destroy our broadcaster map.
225 if (mBroadcasterMap) {
226 PL_DHashTableDestroy(mBroadcasterMap);
227 }
228
229 if (mLocalStore) {
230 nsCOMPtr<nsIRDFRemoteDataSource> remote =
231 do_QueryInterface(mLocalStore);
232 if (remote)
233 remote->Flush();
234 }
235
236 delete mTemplateBuilderTable;
237
238 Preferences::UnregisterCallback(XULDocument::DirectionChanged,
239 "intl.uidirection.", this);
240
241 if (--gRefCnt == 0) {
242 NS_IF_RELEASE(gRDFService);
243
244 NS_IF_RELEASE(kNC_persist);
245 NS_IF_RELEASE(kNC_attribute);
246 NS_IF_RELEASE(kNC_value);
247 }
248
249 if (mOffThreadCompileStringBuf) {
250 js_free(mOffThreadCompileStringBuf);
251 }
252 }
253
254 } // namespace dom
255 } // namespace mozilla
256
257 nsresult
258 NS_NewXULDocument(nsIXULDocument** result)
259 {
260 NS_PRECONDITION(result != nullptr, "null ptr");
261 if (! result)
262 return NS_ERROR_NULL_POINTER;
263
264 XULDocument* doc = new XULDocument();
265 if (! doc)
266 return NS_ERROR_OUT_OF_MEMORY;
267
268 NS_ADDREF(doc);
269
270 nsresult rv;
271 if (NS_FAILED(rv = doc->Init())) {
272 NS_RELEASE(doc);
273 return rv;
274 }
275
276 *result = doc;
277 return NS_OK;
278 }
279
280
281 namespace mozilla {
282 namespace dom {
283
284 //----------------------------------------------------------------------
285 //
286 // nsISupports interface
287 //
288
289 static PLDHashOperator
290 TraverseTemplateBuilders(nsISupports* aKey, nsIXULTemplateBuilder* aData,
291 void* aContext)
292 {
293 nsCycleCollectionTraversalCallback *cb =
294 static_cast<nsCycleCollectionTraversalCallback*>(aContext);
295
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);
300
301 return PL_DHASH_NEXT;
302 }
303
304 static PLDHashOperator
305 TraverseObservers(nsIURI* aKey, nsIObserver* aData, void* aContext)
306 {
307 nsCycleCollectionTraversalCallback *cb =
308 static_cast<nsCycleCollectionTraversalCallback*>(aContext);
309
310 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mOverlayLoadObservers/mPendingOverlayLoadNotifications value");
311 cb->NoteXPCOMChild(aData);
312
313 return PL_DHASH_NEXT;
314 }
315
316 NS_IMPL_CYCLE_COLLECTION_CLASS(XULDocument)
317
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?
323
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);
328
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)
334
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
342
343 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULDocument, XMLDocument)
344 delete tmp->mTemplateBuilderTable;
345 tmp->mTemplateBuilderTable = nullptr;
346
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
350
351 NS_IMPL_ADDREF_INHERITED(XULDocument, XMLDocument)
352 NS_IMPL_RELEASE_INHERITED(XULDocument, XMLDocument)
353
354
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)
361
362
363 //----------------------------------------------------------------------
364 //
365 // nsIDocument interface
366 //
367
368 void
369 XULDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup)
370 {
371 NS_NOTREACHED("Reset");
372 }
373
374 void
375 XULDocument::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
376 nsIPrincipal* aPrincipal)
377 {
378 NS_NOTREACHED("ResetToURI");
379 }
380
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 }
389
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)) {
401
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);
419
420 mChannel = aChannel;
421
422 mHaveInputEncoding = true;
423
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);
428
429 ResetStylesheetsToURI(mDocumentURI);
430
431 RetrieveRelevantHeaders(aChannel);
432
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;
438
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.
454
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;
464
465 mMasterPrototype = mCurrentPrototype = proto;
466
467 // Set up the right principal on ourselves.
468 SetPrincipal(proto->DocumentPrincipal());
469
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));
481
482
483 // It's just a vanilla document load. Create a parser to deal
484 // with the stream n' stuff.
485
486 nsCOMPtr<nsIParser> parser;
487 rv = PrepareToLoad(aContainer, aCommand, aChannel, aLoadGroup,
488 getter_AddRefs(parser));
489 if (NS_FAILED(rv)) return rv;
490
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;
495
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;
499
500 *aDocListener = listener;
501
502 parser->Parse(mDocumentURI);
503
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 }
512
513 NS_IF_ADDREF(*aDocListener);
514 return NS_OK;
515 }
516
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;
525
526 nsresult rv;
527
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.
530
531 nsCOMPtr<nsIURI> uri = mCurrentPrototype->GetURI();
532 bool isChrome = IsChromeURI(uri);
533
534 // Remember if the XUL cache is on
535 bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
536
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 }
545
546 if (IsOverlayAllowed(uri)) {
547 nsCOMPtr<nsIXULOverlayProvider> reg =
548 mozilla::services::GetXULOverlayProviderService();
549
550 if (reg) {
551 nsCOMPtr<nsISimpleEnumerator> overlays;
552 rv = reg->GetStyleOverlays(uri, getter_AddRefs(overlays));
553 if (NS_FAILED(rv)) return;
554
555 bool moreSheets;
556 nsCOMPtr<nsISupports> next;
557 nsCOMPtr<nsIURI> sheetURI;
558
559 while (NS_SUCCEEDED(rv = overlays->HasMoreElements(&moreSheets)) &&
560 moreSheets) {
561 overlays->GetNext(getter_AddRefs(next));
562
563 sheetURI = do_QueryInterface(next);
564 if (!sheetURI) {
565 NS_ERROR("Chrome registry handed me a non-nsIURI object!");
566 continue;
567 }
568
569 if (IsChromeURI(sheetURI)) {
570 mCurrentPrototype->AddStyleSheetReference(sheetURI);
571 }
572 }
573 }
574
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 }
583
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 }
596
597 NS_IMETHODIMP
598 XULDocument::OnPrototypeLoadDone(bool aResumeWalk)
599 {
600 nsresult rv;
601
602 // Add the style overlays from chrome registry, if any.
603 rv = AddPrototypeSheets();
604 if (NS_FAILED(rv)) return rv;
605
606 rv = PrepareToWalk();
607 NS_ASSERTION(NS_SUCCEEDED(rv), "unable to prepare for walk");
608 if (NS_FAILED(rv)) return rv;
609
610 if (aResumeWalk) {
611 rv = ResumeWalk();
612 }
613 return rv;
614 }
615
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 }
630
631 return false;
632 }
633
634 return true;
635 }
636
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 }
645
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 }
650
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 }
667
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 };
679
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;
693
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();
701
702 // _Don't_ push the |id|, |ref|, or |persist| attribute's value!
703 if (! CanBroadcast(nameSpaceID, name))
704 continue;
705
706 attributes.AppendElement(nsAttrNameInfo(nameSpaceID, name,
707 attrName->GetPrefix()));
708 }
709
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 }
719
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);
733
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 }
740
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 }
750
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 }
763
764 void
765 XULDocument::AddBroadcastListenerFor(Element& aBroadcaster, Element& aListener,
766 const nsAString& aAttr, ErrorResult& aRv)
767 {
768 nsresult rv =
769 nsContentUtils::CheckSameOrigin(this, &aBroadcaster);
770
771 if (NS_FAILED(rv)) {
772 aRv.Throw(rv);
773 return;
774 }
775
776 rv = nsContentUtils::CheckSameOrigin(this, &aListener);
777
778 if (NS_FAILED(rv)) {
779 aRv.Throw(rv);
780 return;
781 }
782
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 };
793
794 if (! mBroadcasterMap) {
795 mBroadcasterMap =
796 PL_NewDHashTable(&gOps, nullptr, sizeof(BroadcasterMapEntry),
797 PL_DHASH_MIN_SIZE);
798
799 if (! mBroadcasterMap) {
800 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
801 return;
802 }
803 }
804
805 BroadcasterMapEntry* entry =
806 static_cast<BroadcasterMapEntry*>
807 (PL_DHashTableOperate(mBroadcasterMap, &aBroadcaster,
808 PL_DHASH_LOOKUP));
809
810 if (PL_DHASH_ENTRY_IS_FREE(entry)) {
811 entry =
812 static_cast<BroadcasterMapEntry*>
813 (PL_DHashTableOperate(mBroadcasterMap, &aBroadcaster,
814 PL_DHASH_ADD));
815
816 if (! entry) {
817 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
818 return;
819 }
820
821 entry->mBroadcaster = &aBroadcaster;
822
823 // N.B. placement new to construct the nsSmallVoidArray object
824 // in-place
825 new (&entry->mListeners) nsSmallVoidArray();
826 }
827
828 // Only add the listener if it's not there already!
829 nsCOMPtr<nsIAtom> attr = do_GetAtom(aAttr);
830
831 BroadcastListener* bl;
832 for (int32_t i = entry->mListeners.Count() - 1; i >= 0; --i) {
833 bl = static_cast<BroadcastListener*>(entry->mListeners[i]);
834
835 nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
836
837 if (blListener == &aListener && bl->mAttribute == attr)
838 return;
839 }
840
841 bl = new BroadcastListener;
842
843 bl->mListener = do_GetWeakReference(&aListener);
844 bl->mAttribute = attr;
845
846 entry->mListeners.AppendElement(bl);
847
848 SynchronizeBroadcastListener(&aBroadcaster, &aListener, aAttr);
849 }
850
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 }
862
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;
872
873 BroadcasterMapEntry* entry =
874 static_cast<BroadcasterMapEntry*>
875 (PL_DHashTableOperate(mBroadcasterMap, &aBroadcaster,
876 PL_DHASH_LOOKUP));
877
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]);
883
884 nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
885
886 if (blListener == &aListener && bl->mAttribute == attr) {
887 entry->mListeners.RemoveElementAt(i);
888 delete bl;
889
890 if (entry->mListeners.Count() == 0)
891 PL_DHashTableOperate(mBroadcasterMap, &aBroadcaster,
892 PL_DHASH_REMOVE);
893
894 break;
895 }
896 }
897 }
898 }
899
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.
908
909 for (nsIContent* child = aListener->GetFirstChild();
910 child;
911 child = child->GetNextSibling()) {
912
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;
919
920 // Is this the element that was listening to us?
921 nsAutoString listeningToID;
922 child->GetAttr(kNameSpaceID_None, nsGkAtoms::element, listeningToID);
923
924 nsAutoString broadcasterID;
925 aBroadcaster->GetAttr(kNameSpaceID_None, nsGkAtoms::id, broadcasterID);
926
927 if (listeningToID != broadcasterID)
928 continue;
929
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);
935
936 if (!aAttr->Equals(listeningToAttribute) &&
937 !listeningToAttribute.EqualsLiteral("*")) {
938 continue;
939 }
940
941 // This is the right <observes> element. Execute the
942 // |onbroadcast| event handler
943 WidgetEvent event(true, NS_XUL_BROADCAST);
944
945 nsCOMPtr<nsIPresShell> shell = GetShell();
946 if (shell) {
947 nsRefPtr<nsPresContext> aPresContext = shell->GetPresContext();
948
949 // Handle the DOM event
950 nsEventStatus status = nsEventStatus_eIgnore;
951 EventDispatcher::Dispatch(child, aPresContext, &event, nullptr,
952 &status);
953 }
954 }
955
956 return NS_OK;
957 }
958
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!");
966
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 }
976
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");
983
984 // Might not need this, but be safe for now.
985 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
986
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 }
993
994 nsresult rv;
995
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));
1003
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);
1008
1009 int32_t i;
1010 for (i = entry->mListeners.Count() - 1; i >= 0; --i) {
1011 BroadcastListener* bl =
1012 static_cast<BroadcastListener*>(entry->mListeners[i]);
1013
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);
1035
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 }
1046
1047 mDelayedAttrChangeBroadcasts.AppendElement(delayedUpdate);
1048 }
1049 }
1050 }
1051 }
1052 }
1053
1054 // checks for modifications in broadcasters
1055 bool listener, resolved;
1056 CheckBroadcasterHookup(aElement, &listener, &resolved);
1057
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 }
1071
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");
1079
1080 // Might not need this, but be safe for now.
1081 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
1082
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 }
1090
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");
1098
1099 // Might not need this, but be safe for now.
1100 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
1101
1102 AddSubtreeToDocument(aChild);
1103 }
1104
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");
1113
1114 // Might not need this, but be safe for now.
1115 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
1116
1117 RemoveSubtreeFromDocument(aChild);
1118 }
1119
1120 //----------------------------------------------------------------------
1121 //
1122 // nsIXULDocument interface
1123 //
1124
1125 void
1126 XULDocument::GetElementsForID(const nsAString& aID,
1127 nsCOMArray<nsIContent>& aElements)
1128 {
1129 aElements.Clear();
1130
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 }
1140
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 }
1154
1155 return NS_OK;
1156 }
1157
1158 nsresult
1159 XULDocument::ResolveForwardReferences()
1160 {
1161 if (mResolutionPhase == nsForwardReference::eDone)
1162 return NS_OK;
1163
1164 NS_ASSERTION(mResolutionPhase == nsForwardReference::eStart,
1165 "nested ResolveForwardReferences()");
1166
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.
1172
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();
1179
1180 for (uint32_t i = 0; i < mForwardReferences.Length(); ++i) {
1181 nsForwardReference* fwdref = mForwardReferences[i];
1182
1183 if (fwdref->GetPhase() == *pass) {
1184 nsForwardReference::Result result = fwdref->Resolve();
1185
1186 switch (result) {
1187 case nsForwardReference::eResolve_Succeeded:
1188 case nsForwardReference::eResolve_Error:
1189 mForwardReferences.RemoveElementAt(i);
1190
1191 // fixup because we removed from list
1192 --i;
1193 break;
1194
1195 case nsForwardReference::eResolve_Later:
1196 // do nothing. we'll try again later
1197 ;
1198 }
1199
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 }
1209
1210 ++pass;
1211 }
1212
1213 mForwardReferences.Clear();
1214 return NS_OK;
1215 }
1216
1217 //----------------------------------------------------------------------
1218 //
1219 // nsIDOMDocument interface
1220 //
1221
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 }
1230
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);
1244
1245 return list.forget();
1246 }
1247
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 }
1259
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);
1268
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 }
1279
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 }
1289
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;
1298
1299 Element* element = nsDocument::GetElementById(aID);
1300 if (!element)
1301 return NS_OK;
1302
1303 nsCOMPtr<nsIAtom> tag;
1304 int32_t nameSpaceID;
1305
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);
1316
1317 if (NS_FAILED(rv)) {
1318 // There was an invalid character or it was malformed.
1319 return NS_ERROR_INVALID_ARG;
1320 }
1321
1322 if (colon) {
1323 // We don't really handle namespace qualifiers in attribute names.
1324 return NS_ERROR_NOT_IMPLEMENTED;
1325 }
1326
1327 tag = do_GetAtom(aAttr);
1328 NS_ENSURE_TRUE(tag, NS_ERROR_OUT_OF_MEMORY);
1329
1330 nameSpaceID = kNameSpaceID_None;
1331 }
1332
1333 rv = Persist(element, nameSpaceID, tag);
1334 if (NS_FAILED(rv)) return rv;
1335
1336 return NS_OK;
1337 }
1338
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;
1346
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;
1352
1353 nsresult rv;
1354
1355 nsCOMPtr<nsIRDFResource> element;
1356 rv = nsXULContentUtils::GetElementResource(aElement, getter_AddRefs(element));
1357 if (NS_FAILED(rv)) return rv;
1358
1359 // No ID, so nothing to persist.
1360 if (! element)
1361 return NS_OK;
1362
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 }
1373
1374 nsCOMPtr<nsIRDFResource> attr;
1375 rv = gRDFService->GetResource(attrstr,
1376 getter_AddRefs(attr));
1377 if (NS_FAILED(rv)) return rv;
1378
1379 // Turn the value into a literal
1380 nsAutoString valuestr;
1381 aElement->GetAttr(kNameSpaceID_None, aAttribute, valuestr);
1382
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 }
1390
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;
1395
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;
1407
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 }
1418
1419 if (NS_FAILED(rv)) return rv;
1420
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;
1427
1428 nsCOMPtr<nsIRDFResource> doc;
1429 rv = gRDFService->GetResource(docurl, getter_AddRefs(doc));
1430 if (NS_FAILED(rv)) return rv;
1431
1432 bool hasAssertion;
1433 rv = mLocalStore->HasAssertion(doc, kNC_persist, element, true, &hasAssertion);
1434 if (NS_FAILED(rv)) return rv;
1435
1436 if (! hasAssertion) {
1437 rv = mLocalStore->Assert(doc, kNC_persist, element, true);
1438 if (NS_FAILED(rv)) return rv;
1439 }
1440 }
1441
1442 return NS_OK;
1443 }
1444
1445
1446 nsresult
1447 XULDocument::GetViewportSize(int32_t* aWidth,
1448 int32_t* aHeight)
1449 {
1450 *aWidth = *aHeight = 0;
1451
1452 FlushPendingNotifications(Flush_Layout);
1453
1454 nsIPresShell *shell = GetShell();
1455 NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
1456
1457 nsIFrame* frame = shell->GetRootFrame();
1458 NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
1459
1460 nsSize size = frame->GetSize();
1461
1462 *aWidth = nsPresContext::AppUnitsToIntCSSPixels(size.width);
1463 *aHeight = nsPresContext::AppUnitsToIntCSSPixels(size.height);
1464
1465 return NS_OK;
1466 }
1467
1468 NS_IMETHODIMP
1469 XULDocument::GetWidth(int32_t* aWidth)
1470 {
1471 NS_ENSURE_ARG_POINTER(aWidth);
1472
1473 int32_t height;
1474 return GetViewportSize(aWidth, &height);
1475 }
1476
1477 int32_t
1478 XULDocument::GetWidth(ErrorResult& aRv)
1479 {
1480 int32_t width;
1481 aRv = GetWidth(&width);
1482 return width;
1483 }
1484
1485 NS_IMETHODIMP
1486 XULDocument::GetHeight(int32_t* aHeight)
1487 {
1488 NS_ENSURE_ARG_POINTER(aHeight);
1489
1490 int32_t width;
1491 return GetViewportSize(&width, aHeight);
1492 }
1493
1494 int32_t
1495 XULDocument::GetHeight(ErrorResult& aRv)
1496 {
1497 int32_t height;
1498 aRv = GetHeight(&height);
1499 return height;
1500 }
1501
1502 JSObject*
1503 GetScopeObjectOfNode(nsIDOMNode* node)
1504 {
1505 MOZ_ASSERT(node, "Must not be called with null.");
1506
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?");
1517
1518 nsIDocument* doc = inode->OwnerDoc();
1519 MOZ_ASSERT(inode, "This should never happen.");
1520
1521 nsIGlobalObject* global = doc->GetScopeObject();
1522 return global ? global->GetGlobalJSObject() : nullptr;
1523 }
1524
1525 //----------------------------------------------------------------------
1526 //
1527 // nsIDOMXULDocument interface
1528 //
1529
1530 NS_IMETHODIMP
1531 XULDocument::GetPopupNode(nsIDOMNode** aNode)
1532 {
1533 *aNode = nullptr;
1534
1535 nsCOMPtr<nsIDOMNode> node;
1536 nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
1537 if (rootWin)
1538 node = rootWin->GetPopupNode(); // addref happens here
1539
1540 if (!node) {
1541 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1542 if (pm) {
1543 node = pm->GetLastTriggerPopupNode(this);
1544 }
1545 }
1546
1547 if (node && nsContentUtils::CanCallerAccess(node)
1548 && GetScopeObjectOfNode(node)) {
1549 node.swap(*aNode);
1550 }
1551
1552 return NS_OK;
1553 }
1554
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 }
1564
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 }
1573
1574 nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
1575 if (rootWin)
1576 rootWin->SetPopupNode(aNode); // addref happens here
1577
1578 return NS_OK;
1579 }
1580
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 }
1588
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;
1596
1597 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1598 if (!pm)
1599 return NS_ERROR_FAILURE;
1600
1601 int32_t offset;
1602 pm->GetMouseLocation(aRangeParent, &offset);
1603
1604 if (*aRangeParent && !nsContentUtils::CanCallerAccess(*aRangeParent)) {
1605 NS_RELEASE(*aRangeParent);
1606 return NS_ERROR_DOM_SECURITY_ERR;
1607 }
1608
1609 return NS_OK;
1610 }
1611
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 }
1620
1621
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 }
1631
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 }
1640
1641 int32_t offset;
1642 nsCOMPtr<nsIDOMNode> parent;
1643 pm->GetMouseLocation(getter_AddRefs(parent), &offset);
1644
1645 if (parent && !nsContentUtils::CanCallerAccess(parent)) {
1646 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
1647 return 0;
1648 }
1649 return offset;
1650 }
1651
1652 NS_IMETHODIMP
1653 XULDocument::GetTooltipNode(nsIDOMNode** aNode)
1654 {
1655 *aNode = nullptr;
1656
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 }
1663
1664 return NS_OK;
1665 }
1666
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 }
1676
1677 NS_IMETHODIMP
1678 XULDocument::SetTooltipNode(nsIDOMNode* aNode)
1679 {
1680 // do nothing
1681 return NS_OK;
1682 }
1683
1684
1685 NS_IMETHODIMP
1686 XULDocument::GetCommandDispatcher(nsIDOMXULCommandDispatcher** aTracker)
1687 {
1688 *aTracker = mCommandDispatcher;
1689 NS_IF_ADDREF(*aTracker);
1690 return NS_OK;
1691 }
1692
1693 Element*
1694 XULDocument::GetElementById(const nsAString& aId)
1695 {
1696 if (!CheckGetElementByIdArg(aId))
1697 return nullptr;
1698
1699 nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aId);
1700 if (entry) {
1701 Element* element = entry->GetIdElement();
1702 if (element)
1703 return element;
1704 }
1705
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 }
1714
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;
1721
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;
1733
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 }
1742
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;
1748
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;
1755
1756 rv = AddForwardReference(hookup);
1757 if (NS_FAILED(rv)) return rv;
1758 }
1759
1760 return NS_OK;
1761 }
1762
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 }
1771
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;
1777
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;
1788
1789 rv = AddForwardReference(hookup);
1790 if (NS_FAILED(rv))
1791 return rv;
1792 }
1793 }
1794
1795 return NS_OK;
1796 }
1797
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 }
1806
1807 Element* aElement = aContent->AsElement();
1808
1809 // Do pre-order addition magic
1810 nsresult rv = AddElementToDocumentPre(aElement);
1811 if (NS_FAILED(rv)) return rv;
1812
1813 // Recurse to children
1814 for (nsIContent* child = aElement->GetLastChild();
1815 child;
1816 child = child->GetPreviousSibling()) {
1817
1818 rv = AddSubtreeToDocument(child);
1819 if (NS_FAILED(rv))
1820 return rv;
1821 }
1822
1823 // Do post-order addition magic
1824 return AddElementToDocumentPost(aElement);
1825 }
1826
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 }
1834
1835 Element* aElement = aContent->AsElement();
1836
1837 // Do a bunch of cleanup to remove an element from the XUL
1838 // document.
1839 nsresult rv;
1840
1841 if (aElement->NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
1842 nsXBLService::DetachGlobalKeyHandler(aElement);
1843 }
1844
1845 // 1. Remove any children from the document.
1846 for (nsIContent* child = aElement->GetLastChild();
1847 child;
1848 child = child->GetPreviousSibling()) {
1849
1850 rv = RemoveSubtreeFromDocument(child);
1851 if (NS_FAILED(rv))
1852 return rv;
1853 }
1854
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 }
1865
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;
1874
1875 rv = mCommandDispatcher->RemoveCommandUpdater(domelement);
1876 if (NS_FAILED(rv)) return rv;
1877 }
1878
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 }
1888
1889 return NS_OK;
1890 }
1891
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 }
1902
1903 if (aBuilder) {
1904 mTemplateBuilderTable->Put(aContent, aBuilder);
1905 }
1906 else {
1907 mTemplateBuilderTable->Remove(aContent);
1908 }
1909
1910 return NS_OK;
1911 }
1912
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;
1922
1923 return NS_OK;
1924 }
1925
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 }
1934
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 }
1949
1950 return NS_OK;
1951 }
1952
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 }
1968
1969 //----------------------------------------------------------------------
1970 //
1971 // nsIDOMNode interface
1972 //
1973
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 }
1981
1982
1983 //----------------------------------------------------------------------
1984 //
1985 // Implementation methods
1986 //
1987
1988 nsresult
1989 XULDocument::Init()
1990 {
1991 nsresult rv = XMLDocument::Init();
1992 NS_ENSURE_SUCCESS(rv, rv);
1993
1994 // Create our command dispatcher and hook it up.
1995 mCommandDispatcher = new nsXULCommandDispatcher(this);
1996 NS_ENSURE_TRUE(mCommandDispatcher, NS_ERROR_OUT_OF_MEMORY);
1997
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);
2002
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;
2009
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);
2016
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 }
2026
2027 Preferences::RegisterCallback(XULDocument::DirectionChanged,
2028 "intl.uidirection.", this);
2029
2030 #ifdef PR_LOGGING
2031 if (! gXULLog)
2032 gXULLog = PR_NewLogModule("XULDocument");
2033 #endif
2034
2035 return NS_OK;
2036 }
2037
2038
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;
2050
2051 nsCOMPtr<nsIDocShell> docShell = cx->GetDocShell();
2052 NS_ASSERTION(docShell != nullptr, "container is not a docshell");
2053 if (! docShell)
2054 return NS_ERROR_UNEXPECTED;
2055
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 }
2061
2062 return NS_OK;
2063 }
2064
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 }
2081
2082 // Qualified name match. This takes more work.
2083
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 }
2095
2096 if (nameMatch) {
2097 return attrValue->EqualsLiteral("*") ||
2098 aContent->AttrValueIs(name->NamespaceID(), name->LocalName(),
2099 *attrValue, eCaseMatters);
2100 }
2101 }
2102
2103 return false;
2104 }
2105
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 }
2119
2120
2121 nsresult
2122 XULDocument::PrepareToLoadPrototype(nsIURI* aURI, const char* aCommand,
2123 nsIPrincipal* aDocumentPrincipal,
2124 nsIParser** aResult)
2125 {
2126 nsresult rv;
2127
2128 // Create a new prototype document.
2129 rv = NS_NewXULPrototypeDocument(getter_AddRefs(mCurrentPrototype));
2130 if (NS_FAILED(rv)) return rv;
2131
2132 rv = mCurrentPrototype->InitPrincipal(aURI, aDocumentPrincipal);
2133 if (NS_FAILED(rv)) {
2134 mCurrentPrototype = nullptr;
2135 return rv;
2136 }
2137
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 }
2144
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;
2149
2150 rv = sink->Init(this, mCurrentPrototype);
2151 NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to initialize datasource sink");
2152 if (NS_FAILED(rv)) return rv;
2153
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;
2157
2158 parser->SetCommand(nsCRT::strcmp(aCommand, "view-source") ? eViewNormal :
2159 eViewSource);
2160
2161 parser->SetDocumentCharset(NS_LITERAL_CSTRING("UTF-8"),
2162 kCharsetFromDocTypeDefault);
2163 parser->SetContentSink(sink); // grabs a reference to the parser
2164
2165 *aResult = parser;
2166 NS_ADDREF(*aResult);
2167 return NS_OK;
2168 }
2169
2170
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;
2177
2178 // Add all of the 'persisted' attributes into the content
2179 // model.
2180 if (!mLocalStore)
2181 return NS_OK;
2182
2183 mApplyingPersistedAttrs = true;
2184 ApplyPersistentAttributesInternal();
2185 mApplyingPersistedAttrs = false;
2186
2187 // After we've applied persistence once, we should only reapply
2188 // it to nodes created by overlays
2189 mRestrictPersistence = true;
2190 mPersistenceIds.Clear();
2191
2192 return NS_OK;
2193 }
2194
2195
2196 nsresult
2197 XULDocument::ApplyPersistentAttributesInternal()
2198 {
2199 nsCOMArray<nsIContent> elements;
2200
2201 nsAutoCString docurl;
2202 mDocumentURI->GetSpec(docurl);
2203
2204 nsCOMPtr<nsIRDFResource> doc;
2205 gRDFService->GetResource(docurl, getter_AddRefs(doc));
2206
2207 nsCOMPtr<nsISimpleEnumerator> persisted;
2208 mLocalStore->GetTargets(doc, kNC_persist, true, getter_AddRefs(persisted));
2209
2210 while (1) {
2211 bool hasmore = false;
2212 persisted->HasMoreElements(&hasmore);
2213 if (! hasmore)
2214 break;
2215
2216 nsCOMPtr<nsISupports> isupports;
2217 persisted->GetNext(getter_AddRefs(isupports));
2218
2219 nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(isupports);
2220 if (! resource) {
2221 NS_WARNING("expected element to be a resource");
2222 continue;
2223 }
2224
2225 const char *uri;
2226 resource->GetValueConst(&uri);
2227 if (! uri)
2228 continue;
2229
2230 nsAutoString id;
2231 nsXULContentUtils::MakeElementID(this, nsDependentCString(uri), id);
2232
2233 if (id.IsEmpty())
2234 continue;
2235
2236 if (mRestrictPersistence && !mPersistenceIds.Contains(id))
2237 continue;
2238
2239 // This will clear the array if there are no elements.
2240 GetElementsForID(id, elements);
2241
2242 if (!elements.Count())
2243 continue;
2244
2245 ApplyPersistentAttributesToElements(resource, elements);
2246 }
2247
2248 return NS_OK;
2249 }
2250
2251
2252 nsresult
2253 XULDocument::ApplyPersistentAttributesToElements(nsIRDFResource* aResource,
2254 nsCOMArray<nsIContent>& aElements)
2255 {
2256 nsresult rv;
2257
2258 nsCOMPtr<nsISimpleEnumerator> attrs;
2259 rv = mLocalStore->ArcLabelsOut(aResource, getter_AddRefs(attrs));
2260 if (NS_FAILED(rv)) return rv;
2261
2262 while (1) {
2263 bool hasmore;
2264 rv = attrs->HasMoreElements(&hasmore);
2265 if (NS_FAILED(rv)) return rv;
2266
2267 if (! hasmore)
2268 break;
2269
2270 nsCOMPtr<nsISupports> isupports;
2271 rv = attrs->GetNext(getter_AddRefs(isupports));
2272 if (NS_FAILED(rv)) return rv;
2273
2274 nsCOMPtr<nsIRDFResource> property = do_QueryInterface(isupports);
2275 if (! property) {
2276 NS_WARNING("expected a resource");
2277 continue;
2278 }
2279
2280 const char* attrname;
2281 rv = property->GetValueConst(&attrname);
2282 if (NS_FAILED(rv)) return rv;
2283
2284 nsCOMPtr<nsIAtom> attr = do_GetAtom(attrname);
2285 if (! attr)
2286 return NS_ERROR_OUT_OF_MEMORY;
2287
2288 // XXX could hang namespace off here, as well...
2289
2290 nsCOMPtr<nsIRDFNode> node;
2291 rv = mLocalStore->GetTarget(aResource, property, true,
2292 getter_AddRefs(node));
2293 if (NS_FAILED(rv)) return rv;
2294
2295 nsCOMPtr<nsIRDFLiteral> literal = do_QueryInterface(node);
2296 if (! literal) {
2297 NS_WARNING("expected a literal");
2298 continue;
2299 }
2300
2301 const char16_t* value;
2302 rv = literal->GetValueConst(&value);
2303 if (NS_FAILED(rv)) return rv;
2304
2305 nsDependentString wrapper(value);
2306
2307 uint32_t cnt = aElements.Count();
2308
2309 for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
2310 nsCOMPtr<nsIContent> element = aElements.SafeObjectAt(i);
2311 if (!element)
2312 continue;
2313
2314 rv = element->SetAttr(/* XXX */ kNameSpaceID_None,
2315 attr,
2316 wrapper,
2317 true);
2318 }
2319 }
2320
2321 return NS_OK;
2322 }
2323
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 }
2332
2333 //----------------------------------------------------------------------
2334 //
2335 // XULDocument::ContextStack
2336 //
2337
2338 XULDocument::ContextStack::ContextStack()
2339 : mTop(nullptr), mDepth(0)
2340 {
2341 }
2342
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 }
2352
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;
2360
2361 entry->mPrototype = aPrototype;
2362 entry->mElement = aElement;
2363 NS_IF_ADDREF(entry->mElement);
2364 entry->mIndex = 0;
2365
2366 entry->mNext = mTop;
2367 mTop = entry;
2368
2369 ++mDepth;
2370 return NS_OK;
2371 }
2372
2373 nsresult
2374 XULDocument::ContextStack::Pop()
2375 {
2376 if (mDepth == 0)
2377 return NS_ERROR_UNEXPECTED;
2378
2379 Entry* doomed = mTop;
2380 mTop = mTop->mNext;
2381 --mDepth;
2382
2383 NS_IF_RELEASE(doomed->mElement);
2384 delete doomed;
2385 return NS_OK;
2386 }
2387
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;
2395
2396 *aPrototype = mTop->mPrototype;
2397 *aElement = mTop->mElement;
2398 NS_IF_ADDREF(*aElement);
2399 *aIndex = mTop->mIndex;
2400
2401 return NS_OK;
2402 }
2403
2404
2405 nsresult
2406 XULDocument::ContextStack::SetTopIndex(int32_t aIndex)
2407 {
2408 if (mDepth == 0)
2409 return NS_ERROR_UNEXPECTED;
2410
2411 mTop->mIndex = aIndex;
2412 return NS_OK;
2413 }
2414
2415
2416 //----------------------------------------------------------------------
2417 //
2418 // Content model walking routines
2419 //
2420
2421 nsresult
2422 XULDocument::PrepareToWalk()
2423 {
2424 // Prepare to walk the mCurrentPrototype
2425 nsresult rv;
2426
2427 // Keep an owning reference to the prototype document so that its
2428 // elements aren't yanked from beneath us.
2429 mPrototypes.AppendElement(mCurrentPrototype);
2430
2431 // Get the prototype's root element and initialize the context
2432 // stack for the prototype walk.
2433 nsXULPrototypeElement* proto = mCurrentPrototype->GetRootElement();
2434
2435 if (! proto) {
2436 #ifdef PR_LOGGING
2437 if (PR_LOG_TEST(gXULLog, PR_LOG_ERROR)) {
2438 nsCOMPtr<nsIURI> url = mCurrentPrototype->GetURI();
2439
2440 nsAutoCString urlspec;
2441 rv = url->GetSpec(urlspec);
2442 if (NS_FAILED(rv)) return rv;
2443
2444 PR_LOG(gXULLog, PR_LOG_ERROR,
2445 ("xul: error parsing '%s'", urlspec.get()));
2446 }
2447 #endif
2448
2449 return NS_OK;
2450 }
2451
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 }
2459
2460 const nsTArray<nsRefPtr<nsXULPrototypePI> >& processingInstructions =
2461 mCurrentPrototype->GetProcessingInstructions();
2462
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 }
2469
2470 // Now check the chrome registry for any additional overlays.
2471 rv = AddChromeOverlays();
2472 if (NS_FAILED(rv)) return rv;
2473
2474 // Do one-time initialization if we're preparing to walk the
2475 // master document's prototype.
2476 nsRefPtr<Element> root;
2477
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;
2482
2483 rv = AppendChildTo(root, false);
2484 if (NS_FAILED(rv)) return rv;
2485
2486 rv = AddElementToRefMap(root);
2487 if (NS_FAILED(rv)) return rv;
2488
2489 // Block onload until we've finished building the complete
2490 // document content model.
2491 BlockOnload();
2492 }
2493
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;
2504
2505 rv = mContextStack.Push(proto, root);
2506 if (NS_FAILED(rv)) return rv;
2507
2508 return NS_OK;
2509 }
2510
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");
2517
2518 nsRefPtr<ProcessingInstruction> node =
2519 NS_NewXMLProcessingInstruction(mNodeInfoManager, aProtoPI->mTarget,
2520 aProtoPI->mData);
2521
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 }
2531
2532 return rv;
2533 }
2534
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!");
2544
2545 nsresult rv;
2546
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());
2552
2553 rv = aParent->InsertChildAt(aPINode, aIndex, false);
2554 if (NS_FAILED(rv)) return rv;
2555
2556 ssle->SetEnableUpdates(true);
2557
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 }
2566
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 }
2573
2574 return NS_OK;
2575 }
2576
2577 nsresult
2578 XULDocument::InsertXULOverlayPI(const nsXULPrototypePI* aProtoPI,
2579 nsINode* aParent,
2580 uint32_t aIndex,
2581 nsIContent* aPINode)
2582 {
2583 nsresult rv;
2584
2585 rv = aParent->InsertChildAt(aPINode, aIndex, false);
2586 if (NS_FAILED(rv)) return rv;
2587
2588 // xul-overlay PI is special only in prolog
2589 if (!nsContentUtils::InProlog(aPINode)) {
2590 return NS_OK;
2591 }
2592
2593 nsAutoString href;
2594 nsContentUtils::GetPseudoAttributeValue(aProtoPI->mData,
2595 nsGkAtoms::href,
2596 href);
2597
2598 // If there was no href, we can't do anything with this PI
2599 if (href.IsEmpty()) {
2600 return NS_OK;
2601 }
2602
2603 // Add the overlay to our list of overlays that need to be processed.
2604 nsCOMPtr<nsIURI> uri;
2605
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 }
2622
2623 return rv;
2624 }
2625
2626 nsresult
2627 XULDocument::AddChromeOverlays()
2628 {
2629 nsresult rv;
2630
2631 nsCOMPtr<nsIURI> docUri = mCurrentPrototype->GetURI();
2632
2633 /* overlays only apply to chrome or about URIs */
2634 if (!IsOverlayAllowed(docUri)) return NS_OK;
2635
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);
2641
2642 nsCOMPtr<nsISimpleEnumerator> overlays;
2643 rv = chromeReg->GetXULOverlays(docUri, getter_AddRefs(overlays));
2644 NS_ENSURE_SUCCESS(rv, rv);
2645
2646 bool moreOverlays;
2647 nsCOMPtr<nsISupports> next;
2648 nsCOMPtr<nsIURI> uri;
2649
2650 while (NS_SUCCEEDED(rv = overlays->HasMoreElements(&moreOverlays)) &&
2651 moreOverlays) {
2652
2653 rv = overlays->GetNext(getter_AddRefs(next));
2654 if (NS_FAILED(rv) || !next) break;
2655
2656 uri = do_QueryInterface(next);
2657 if (!uri) {
2658 NS_ERROR("Chrome registry handed me a non-nsIURI object!");
2659 continue;
2660 }
2661
2662 // Same comment as in XULDocument::InsertXULOverlayPI
2663 mUnloadedOverlays.InsertElementAt(0, uri);
2664 }
2665
2666 return rv;
2667 }
2668
2669 NS_IMETHODIMP
2670 XULDocument::LoadOverlay(const nsAString& aURL, nsIObserver* aObserver)
2671 {
2672 nsresult rv;
2673
2674 nsCOMPtr<nsIURI> uri;
2675 rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr);
2676 if (NS_FAILED(rv)) return rv;
2677
2678 if (aObserver) {
2679 nsIObserver* obs = nullptr;
2680 if (!mOverlayLoadObservers) {
2681 mOverlayLoadObservers = new nsInterfaceHashtable<nsURIHashKey,nsIObserver>;
2682 }
2683 obs = mOverlayLoadObservers->GetWeak(uri);
2684
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 }
2698
2699 nsresult
2700 XULDocument::LoadOverlayInternal(nsIURI* aURI, bool aIsDynamic,
2701 bool* aShouldReturn,
2702 bool* aFailureFromContent)
2703 {
2704 nsresult rv;
2705
2706 *aShouldReturn = false;
2707 *aFailureFromContent = false;
2708
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 = "";
2720
2721 PR_LOG(gXULLog, PR_LOG_DEBUG,
2722 ("xul: %s loading overlay %s", parentDoc.get(), urlspec.get()));
2723 }
2724 #endif
2725
2726 if (aIsDynamic)
2727 mResolutionPhase = nsForwardReference::eStart;
2728
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.
2732
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 }
2742
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;
2750
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.
2766
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;
2772
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 }
2781
2782 PR_LOG(gXULLog, PR_LOG_DEBUG, ("xul: overlay was cached"));
2783
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"));
2792
2793 if (mIsGoingAway) {
2794 PR_LOG(gXULLog, PR_LOG_DEBUG, ("xul: ...and document already destroyed"));
2795 return NS_ERROR_NOT_AVAILABLE;
2796 }
2797
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;
2804
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;
2809
2810 nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(parser);
2811 if (! listener)
2812 return NS_ERROR_UNEXPECTED;
2813
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;
2821
2822 NS_ADDREF(parserObserver);
2823 parser->Parse(aURI, parserObserver);
2824 NS_RELEASE(parserObserver);
2825
2826 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
2827 nsCOMPtr<nsIChannel> channel;
2828 rv = NS_NewChannel(getter_AddRefs(channel), aURI, nullptr, group);
2829
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());
2836
2837 rv = channel->AsyncOpen(listener, nullptr);
2838 }
2839
2840 if (NS_FAILED(rv)) {
2841 // Abandon this prototype
2842 mCurrentPrototype = nullptr;
2843
2844 // The parser won't get an OnStartRequest and
2845 // OnStopRequest, so it needs a Terminate.
2846 parser->Terminate();
2847
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);
2852
2853 // XXX the error could indicate an internal error as well...
2854 *aFailureFromContent = true;
2855 return rv;
2856 }
2857
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 }
2868
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 }
2878
2879 static PLDHashOperator
2880 FirePendingMergeNotification(nsIURI* aKey, nsCOMPtr<nsIObserver>& aObserver, void* aClosure)
2881 {
2882 aObserver->Observe(aKey, "xul-overlay-merged", EmptyString().get());
2883
2884 typedef nsInterfaceHashtable<nsURIHashKey,nsIObserver> table;
2885 table* observers = static_cast<table*>(aClosure);
2886 if (observers) {
2887 observers->Remove(aKey);
2888 }
2889
2890 return PL_DHASH_REMOVE;
2891 }
2892
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;
2908
2909 while (1) {
2910 // Begin (or resume) walking the current prototype.
2911
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;
2923
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());
2933
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 }
2956
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);
2961
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);
2969
2970 NS_ASSERTION(element || processingOverlayHookupNodes,
2971 "no element on context stack");
2972
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);
2978
2979 nsRefPtr<Element> child;
2980
2981 if (!processingOverlayHookupNodes) {
2982 rv = CreateElementFromPrototype(protoele,
2983 getter_AddRefs(child),
2984 false);
2985 if (NS_FAILED(rv)) return rv;
2986
2987 // ...and append it to the content model.
2988 rv = element->AppendChildTo(child, false);
2989 if (NS_FAILED(rv)) return rv;
2990
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 }
2999
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 }
3016
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;
3033
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);
3039
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!
3048
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;
3059
3060 case nsXULPrototypeNode::eType_Text: {
3061 // A simple text node.
3062
3063 if (!processingOverlayHookupNodes) {
3064 // This does mean that text nodes that are direct children
3065 // of <overlay> get ignored.
3066
3067 nsRefPtr<nsTextNode> text =
3068 new nsTextNode(mNodeInfoManager);
3069
3070 nsXULPrototypeText* textproto =
3071 static_cast<nsXULPrototypeText*>(childproto);
3072 text->SetText(textproto->mValue, false);
3073
3074 rv = element->AppendChildTo(text, false);
3075 NS_ENSURE_SUCCESS(rv, rv);
3076 }
3077 }
3078 break;
3079
3080 case nsXULPrototypeNode::eType_PI: {
3081 nsXULPrototypePI* piProto =
3082 static_cast<nsXULPrototypePI*>(childproto);
3083
3084 // <?xul-overlay?> and <?xml-stylesheet?> don't have effect
3085 // outside the prolog, like they used to. Issue a warning.
3086
3087 if (piProto->mTarget.EqualsLiteral("xml-stylesheet") ||
3088 piProto->mTarget.EqualsLiteral("xul-overlay")) {
3089
3090 const char16_t* params[] = { piProto->mTarget.get() };
3091
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 }
3100
3101 nsIContent* parent = processingOverlayHookupNodes ?
3102 GetRootElement() : element.get();
3103
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;
3112
3113 default:
3114 NS_NOTREACHED("Unexpected nsXULPrototypeNode::Type value");
3115 }
3116 }
3117
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.
3121
3122 // If we're not already, mark us as now processing overlays.
3123 mState = eState_Overlay;
3124
3125 // If there are no overlay URIs, then we're done.
3126 uint32_t count = mUnloadedOverlays.Length();
3127 if (! count)
3128 break;
3129
3130 nsCOMPtr<nsIURI> uri = mUnloadedOverlays[count-1];
3131 mUnloadedOverlays.RemoveElementAt(count - 1);
3132
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 }
3159
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;
3164
3165 ApplyPersistentAttributes();
3166
3167 mStillWalking = false;
3168 if (mPendingSheets == 0) {
3169 rv = DoneWalking();
3170 }
3171 return rv;
3172 }
3173
3174 nsresult
3175 XULDocument::DoneWalking()
3176 {
3177 NS_PRECONDITION(mPendingSheets == 0, "there are sheets to be loaded");
3178 NS_PRECONDITION(!mStillWalking, "walk not done");
3179
3180 // XXXldb This is where we should really be setting the chromehidden
3181 // attribute.
3182
3183 uint32_t count = mOverlaySheets.Length();
3184 for (uint32_t i = 0; i < count; ++i) {
3185 AddStyleSheet(mOverlaySheets[i]);
3186 }
3187 mOverlaySheets.Clear();
3188
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;
3198
3199 NotifyPossibleTitleChange(false);
3200
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 }
3218
3219 StartLayout();
3220
3221 if (mIsWritingFastLoad && IsChromeURI(mDocumentURI))
3222 nsXULPrototypeCache::GetInstance()->WritePrototype(mMasterPrototype);
3223
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 }
3232
3233 NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
3234
3235 // DispatchContentLoadedEvents undoes the onload-blocking we
3236 // did in PrepareToWalk().
3237 DispatchContentLoadedEvents();
3238
3239 mInitialLayoutComplete = true;
3240
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!
3275
3276 if (!mPendingOverlayLoadNotifications) {
3277 mPendingOverlayLoadNotifications =
3278 new nsInterfaceHashtable<nsURIHashKey,nsIObserver>;
3279 }
3280
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 }
3290
3291 return NS_OK;
3292 }
3293
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
3301
3302 NS_ASSERTION(mPendingSheets > 0,
3303 "Unexpected StyleSheetLoaded notification");
3304
3305 --mPendingSheets;
3306
3307 if (!mStillWalking && mPendingSheets == 0) {
3308 return DoneWalking();
3309 }
3310 }
3311
3312 return NS_OK;
3313 }
3314
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 }
3352
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 }
3368
3369 void
3370 XULDocument::EndUpdate(nsUpdateType aUpdateType)
3371 {
3372 XMLDocument::EndUpdate(aUpdateType);
3373
3374 MaybeBroadcast();
3375 }
3376
3377 void
3378 XULDocument::ReportMissingOverlay(nsIURI* aURI)
3379 {
3380 NS_PRECONDITION(aURI, "Must have a URI");
3381
3382 nsAutoCString spec;
3383 aURI->GetSpec(spec);
3384
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 }
3393
3394 nsresult
3395 XULDocument::LoadScript(nsXULPrototypeScript* aScriptProto, bool* aBlock)
3396 {
3397 // Load a transcluded script
3398 nsresult rv;
3399
3400 bool isChromeDoc = IsChromeURI(mDocumentURI);
3401
3402 if (isChromeDoc && aScriptProto->GetScriptObject()) {
3403 rv = ExecuteScript(aScriptProto);
3404
3405 // Ignore return value from execution, and don't block
3406 *aBlock = false;
3407 return NS_OK;
3408 }
3409
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();
3414
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 }
3424
3425 if (aScriptProto->GetScriptObject()) {
3426 rv = ExecuteScript(aScriptProto);
3427
3428 // Ignore return value from execution, and don't block
3429 *aBlock = false;
3430 return NS_OK;
3431 }
3432 }
3433
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 }
3445
3446 // Release script objects from FastLoad since we decided against using them
3447 aScriptProto->UnlinkJSObjects();
3448
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;
3454
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);
3464
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 }
3473
3474 aScriptProto->mSrcLoading = true;
3475 }
3476
3477 // Block until OnStreamComplete resumes us.
3478 *aBlock = true;
3479 return NS_OK;
3480 }
3481
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);
3492
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
3507
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;
3513
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 }
3520
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;
3530
3531 // XXX should also check nsIHttpChannel::requestSucceeded
3532
3533 MOZ_ASSERT(!mOffThreadCompiling && (mOffThreadCompileStringLength == 0 &&
3534 !mOffThreadCompileStringBuf),
3535 "XULDocument can't load multiple scripts at once");
3536
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;
3551
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 }
3572
3573 return OnScriptCompileComplete(mCurrentScriptProto->GetScriptObject(), rv);
3574 }
3575
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);
3583
3584 // Allow load events to be fired once off thread compilation finishes.
3585 if (mOffThreadCompiling) {
3586 mOffThreadCompiling = false;
3587 UnblockOnload(false);
3588 }
3589
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 }
3596
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;
3602
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;
3607
3608 nsresult rv = aStatus;
3609 if (NS_SUCCEEDED(rv)) {
3610 rv = ExecuteScript(scriptProto);
3611
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();
3634
3635 if (useXULCache && IsChromeURI(mDocumentURI) && scriptProto->GetScriptObject()) {
3636 nsXULPrototypeCache::GetInstance()->PutScript(
3637 scriptProto->mSrcURI,
3638 scriptProto->GetScriptObject());
3639 }
3640
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 }
3657
3658 rv = ResumeWalk();
3659
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;
3663
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;
3671
3672 // Unlink doc from scriptProto's list before executing and resuming
3673 *docp = doc->mNextSrcLoadWaiter;
3674 doc->mNextSrcLoadWaiter = nullptr;
3675
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 }
3683
3684 return rv;
3685 }
3686
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;
3694
3695 NS_ENSURE_TRUE(mScriptGlobalObject, NS_ERROR_NOT_INITIALIZED);
3696
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);
3707
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 }
3714
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);
3721
3722 nsresult rv;
3723 rv = mScriptGlobalObject->EnsureScriptEnvironment();
3724 NS_ENSURE_SUCCESS(rv, rv);
3725
3726 nsCOMPtr<nsIScriptContext> context =
3727 mScriptGlobalObject->GetScriptContext();
3728 // failure getting a script context is fatal.
3729 NS_ENSURE_TRUE(context != nullptr, NS_ERROR_UNEXPECTED);
3730
3731 if (aScript->GetScriptObject())
3732 rv = ExecuteScript(context, aScript->GetScriptObject());
3733 else
3734 rv = NS_ERROR_UNEXPECTED;
3735 return rv;
3736 }
3737
3738
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;
3748
3749 *aResult = nullptr;
3750 nsresult rv = NS_OK;
3751
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
3759
3760 nsRefPtr<Element> result;
3761
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;
3784
3785 rv = AddAttributes(aPrototype, result);
3786 if (NS_FAILED(rv)) return rv;
3787 }
3788
3789 result.swap(*aResult);
3790
3791 return NS_OK;
3792 }
3793
3794 nsresult
3795 XULDocument::CreateOverlayElement(nsXULPrototypeElement* aPrototype,
3796 Element** aResult)
3797 {
3798 nsresult rv;
3799
3800 nsRefPtr<Element> element;
3801 rv = CreateElementFromPrototype(aPrototype, getter_AddRefs(element), false);
3802 if (NS_FAILED(rv)) return rv;
3803
3804 OverlayForwardReference* fwdref =
3805 new OverlayForwardReference(this, element);
3806 if (! fwdref)
3807 return NS_ERROR_OUT_OF_MEMORY;
3808
3809 // transferring ownership to ya...
3810 rv = AddForwardReference(fwdref);
3811 if (NS_FAILED(rv)) return rv;
3812
3813 NS_ADDREF(*aResult = element);
3814 return NS_OK;
3815 }
3816
3817 nsresult
3818 XULDocument::AddAttributes(nsXULPrototypeElement* aPrototype,
3819 nsIContent* aElement)
3820 {
3821 nsresult rv;
3822
3823 for (uint32_t i = 0; i < aPrototype->mNumAttributes; ++i) {
3824 nsXULPrototypeAttribute* protoattr = &(aPrototype->mAttributes[i]);
3825 nsAutoString valueStr;
3826 protoattr->mValue.ToString(valueStr);
3827
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 }
3835
3836 return NS_OK;
3837 }
3838
3839
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 }
3860
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 }
3867
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;
3873
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);
3878
3879 int32_t nameSpaceID;
3880 nsIAtom* baseTag = document->BindingManager()->
3881 ResolveTag(aElement, &nameSpaceID);
3882
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.
3888
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 }
3895
3896 if (isTreeBuilder) {
3897 // Create and initialize a tree builder.
3898 nsCOMPtr<nsIXULTemplateBuilder> builder =
3899 do_CreateInstance("@mozilla.org/xul/xul-tree-builder;1");
3900
3901 if (! builder)
3902 return NS_ERROR_FAILURE;
3903
3904 builder->Init(aElement);
3905
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));
3912
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);
3919
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");
3927
3928 if (! builder)
3929 return NS_ERROR_FAILURE;
3930
3931 builder->Init(aElement);
3932 builder->CreateContents(aElement, false);
3933 }
3934
3935 return NS_OK;
3936 }
3937
3938
3939 nsresult
3940 XULDocument::AddPrototypeSheets()
3941 {
3942 nsresult rv;
3943
3944 const nsCOMArray<nsIURI>& sheets = mCurrentPrototype->GetStyleSheetReferences();
3945
3946 for (int32_t i = 0; i < sheets.Count(); i++) {
3947 nsCOMPtr<nsIURI> uri = sheets[i];
3948
3949 nsRefPtr<nsCSSStyleSheet> incompleteSheet;
3950 rv = CSSLoader()->LoadSheet(uri,
3951 mCurrentPrototype->DocumentPrincipal(),
3952 EmptyCString(), this,
3953 getter_AddRefs(incompleteSheet));
3954
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 }
3965
3966 return NS_OK;
3967 }
3968
3969
3970 //----------------------------------------------------------------------
3971 //
3972 // XULDocument::OverlayForwardReference
3973 //
3974
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;
3982
3983 nsIPresShell *shell = mDocument->GetShell();
3984 bool notify = shell && shell->DidInitialize();
3985
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 }
3995
3996 rv = mDocument->InsertElement(root, mOverlay, notify);
3997 if (NS_FAILED(rv)) return eResolve_Error;
3998
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);
4005
4006 // If we can't find the element in the document, defer the hookup
4007 // until later.
4008 if (!target)
4009 return eResolve_Later;
4010
4011 rv = Merge(target, mOverlay, notify);
4012 if (NS_FAILED(rv)) return eResolve_Error;
4013 }
4014
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 }
4023
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
4033
4034 mResolved = true;
4035 return eResolve_Succeeded;
4036 }
4037
4038
4039
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)
4062
4063 nsresult rv;
4064
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;
4073
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;
4079
4080 if (name->Equals(nsGkAtoms::observes) &&
4081 aTargetNode->HasAttr(kNameSpaceID_None, nsGkAtoms::observes))
4082 continue;
4083
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 }
4092
4093 int32_t nameSpaceID = name->NamespaceID();
4094 nsIAtom* attr = name->LocalName();
4095 nsIAtom* prefix = name->GetPrefix();
4096
4097 nsAutoString value;
4098 aOverlayNode->GetAttr(nameSpaceID, attr, value);
4099
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")) {
4104
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;
4109
4110 return NS_OK;
4111 }
4112
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 }
4121
4122
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.
4129
4130 uint32_t childCount = aOverlayNode->GetChildCount();
4131
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;
4135
4136 for (i = 0; i < childCount; ++i) {
4137 currContent = aOverlayNode->GetFirstChild();
4138
4139 nsIAtom *idAtom = currContent->GetID();
4140
4141 nsIContent *elementInDocument = nullptr;
4142 if (idAtom) {
4143 nsDependentAtomString id(idAtom);
4144
4145 if (!id.IsEmpty()) {
4146 nsIDocument *doc = aTargetNode->GetDocument();
4147 if (!doc) return NS_ERROR_FAILURE;
4148
4149 elementInDocument = doc->GetElementById(id);
4150 }
4151 }
4152
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.
4163
4164 nsIContent *elementParent = elementInDocument->GetParent();
4165
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);
4175
4176 continue;
4177 }
4178 }
4179
4180 aOverlayNode->RemoveChildAt(0, false);
4181
4182 rv = InsertElement(aTargetNode, currContent, aNotify);
4183 if (NS_FAILED(rv)) return rv;
4184 }
4185
4186 return NS_OK;
4187 }
4188
4189
4190
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);
4197
4198 nsAutoCString idC;
4199 idC.AssignWithConversion(id);
4200
4201 nsIURI *protoURI = mDocument->mCurrentPrototype->GetURI();
4202 nsAutoCString urlspec;
4203 protoURI->GetSpec(urlspec);
4204
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 }
4216
4217
4218 //----------------------------------------------------------------------
4219 //
4220 // XULDocument::BroadcasterHookup
4221 //
4222
4223 nsForwardReference::Result
4224 XULDocument::BroadcasterHookup::Resolve()
4225 {
4226 nsresult rv;
4227
4228 bool listener;
4229 rv = mDocument->CheckBroadcasterHookup(mObservesElement, &listener, &mResolved);
4230 if (NS_FAILED(rv)) return eResolve_Error;
4231
4232 return mResolved ? eResolve_Succeeded : eResolve_Later;
4233 }
4234
4235
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();
4242
4243 nsAutoString broadcasterID;
4244 nsAutoString attribute;
4245
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 }
4254
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 }
4266
4267
4268 //----------------------------------------------------------------------
4269 //
4270 // XULDocument::TemplateBuilderHookup
4271 //
4272
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;
4280
4281 if (needsHookup) {
4282 rv = CreateTemplateBuilder(mElement);
4283 if (NS_FAILED(rv))
4284 return eResolve_Error;
4285 }
4286
4287 return eResolve_Succeeded;
4288 }
4289
4290
4291 //----------------------------------------------------------------------
4292
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;
4301
4302 if (!mBroadcasterMap || !CanBroadcast(aNameSpaceID, aAttribute))
4303 return rv;
4304
4305 if (!aNode->IsElement())
4306 return rv;
4307
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;
4312
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]);
4318
4319 if ((bl->mAttribute != aAttribute) &&
4320 (bl->mAttribute != nsGkAtoms::_asterix))
4321 continue;
4322
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 }
4332
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;
4343
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 }
4355
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 }
4362
4363 *aListener = parent->IsElement() ? parent->AsElement() : nullptr;
4364 NS_IF_ADDREF(*aListener);
4365
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);
4378
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.
4387
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 }
4397
4398 *aListener = aElement;
4399 NS_ADDREF(*aListener);
4400
4401 aAttribute.AssignLiteral("*");
4402 }
4403
4404 // Make sure we got a valid listener.
4405 NS_ENSURE_TRUE(*aListener, NS_ERROR_UNEXPECTED);
4406
4407 // Try to find the broadcaster element in the document.
4408 *aBroadcaster = GetElementById(aBroadcasterID);
4409
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 }
4416
4417 NS_ADDREF(*aBroadcaster);
4418
4419 return NS_FINDBROADCASTER_FOUND;
4420 }
4421
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;
4431
4432 *aDidResolve = false;
4433
4434 nsCOMPtr<Element> listener;
4435 nsAutoString broadcasterID;
4436 nsAutoString attribute;
4437 nsCOMPtr<Element> broadcaster;
4438
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 }
4453
4454 NS_ENSURE_ARG(broadcaster && listener);
4455 ErrorResult domRv;
4456 AddBroadcastListenerFor(*broadcaster, *listener, attribute, domRv);
4457 if (domRv.Failed()) {
4458 return domRv.ErrorCode();
4459 }
4460
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);
4466
4467 NS_ASSERTION(content != nullptr, "not an nsIContent");
4468 if (! content)
4469 return rv;
4470
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
4481
4482 *aNeedsHookup = false;
4483 *aDidResolve = true;
4484 return NS_OK;
4485 }
4486
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.
4493
4494 nsAutoString posStr;
4495 bool wasInserted = false;
4496
4497 // insert after an element of a given id
4498 aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::insertafter, posStr);
4499 bool isInsertAfter = true;
4500
4501 if (posStr.IsEmpty()) {
4502 aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::insertbefore, posStr);
4503 isInsertAfter = false;
4504 }
4505
4506 if (!posStr.IsEmpty()) {
4507 nsIDocument *document = aParent->OwnerDoc();
4508
4509 nsIContent *content = nullptr;
4510
4511 char* str = ToNewCString(posStr);
4512 char* rest;
4513 char* token = nsCRT::strtok(str, ", ", &rest);
4514
4515 while (token) {
4516 content = document->GetElementById(NS_ConvertASCIItoUTF16(token));
4517 if (content)
4518 break;
4519
4520 token = nsCRT::strtok(rest, ", ", &rest);
4521 }
4522 nsMemory::Free(str);
4523
4524 if (content) {
4525 int32_t pos = aParent->IndexOf(content);
4526
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;
4532
4533 wasInserted = true;
4534 }
4535 }
4536 }
4537
4538 if (!wasInserted) {
4539
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 }
4561
4562 if (!wasInserted) {
4563 return aParent->AppendChildTo(aChild, aNotify);
4564 }
4565 return NS_OK;
4566 }
4567
4568 nsresult
4569 XULDocument::RemoveElement(nsINode* aParent, nsINode* aChild)
4570 {
4571 int32_t nodeOffset = aParent->IndexOf(aChild);
4572
4573 aParent->RemoveChildAt(nodeOffset, true);
4574 return NS_OK;
4575 }
4576
4577 //----------------------------------------------------------------------
4578 //
4579 // CachedChromeStreamListener
4580 //
4581
4582 XULDocument::CachedChromeStreamListener::CachedChromeStreamListener(XULDocument* aDocument, bool aProtoLoaded)
4583 : mDocument(aDocument),
4584 mProtoLoaded(aProtoLoaded)
4585 {
4586 NS_ADDREF(mDocument);
4587 }
4588
4589
4590 XULDocument::CachedChromeStreamListener::~CachedChromeStreamListener()
4591 {
4592 NS_RELEASE(mDocument);
4593 }
4594
4595
4596 NS_IMPL_ISUPPORTS(XULDocument::CachedChromeStreamListener,
4597 nsIRequestObserver, nsIStreamListener)
4598
4599 NS_IMETHODIMP
4600 XULDocument::CachedChromeStreamListener::OnStartRequest(nsIRequest *request,
4601 nsISupports* acontext)
4602 {
4603 return NS_ERROR_PARSED_DATA_CACHED;
4604 }
4605
4606
4607 NS_IMETHODIMP
4608 XULDocument::CachedChromeStreamListener::OnStopRequest(nsIRequest *request,
4609 nsISupports* aContext,
4610 nsresult aStatus)
4611 {
4612 if (! mProtoLoaded)
4613 return NS_OK;
4614
4615 return mDocument->OnPrototypeLoadDone(true);
4616 }
4617
4618
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 }
4629
4630 //----------------------------------------------------------------------
4631 //
4632 // ParserObserver
4633 //
4634
4635 XULDocument::ParserObserver::ParserObserver(XULDocument* aDocument,
4636 nsXULPrototypeDocument* aPrototype)
4637 : mDocument(aDocument), mPrototype(aPrototype)
4638 {
4639 }
4640
4641 XULDocument::ParserObserver::~ParserObserver()
4642 {
4643 }
4644
4645 NS_IMPL_ISUPPORTS(XULDocument::ParserObserver, nsIRequestObserver)
4646
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));
4658
4659 // Failure there is ok -- it'll just set a (safe) null principal
4660 mPrototype->SetDocumentPrincipal(principal);
4661 }
4662
4663 // Make sure to avoid cycles
4664 mPrototype = nullptr;
4665 }
4666
4667 return NS_OK;
4668 }
4669
4670 NS_IMETHODIMP
4671 XULDocument::ParserObserver::OnStopRequest(nsIRequest *request,
4672 nsISupports* aContext,
4673 nsresult aStatus)
4674 {
4675 nsresult rv = NS_OK;
4676
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 }
4688
4689 rv = mDocument->ResumeWalk();
4690 }
4691
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;
4696
4697 return rv;
4698 }
4699
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 }
4708
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 }
4725
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;
4732
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 }
4755
4756 bool isRTL = false;
4757 reg->IsLocaleRTL(package, &isRTL);
4758 return isRTL;
4759 }
4760
4761 void
4762 XULDocument::ResetDocumentDirection()
4763 {
4764 DocumentStatesChanged(NS_DOCUMENT_STATE_RTL_LOCALE);
4765 }
4766
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 }
4776
4777 int
4778 XULDocument::GetDocumentLWTheme()
4779 {
4780 if (mDocLWTheme == Doc_Theme_Uninitialized) {
4781 mDocLWTheme = Doc_Theme_None; // No lightweight theme by default
4782
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 }
4802
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 }
4811
4812 JSObject*
4813 XULDocument::WrapNode(JSContext *aCx)
4814 {
4815 return XULDocumentBinding::Wrap(aCx, this);
4816 }
4817
4818 } // namespace dom
4819 } // namespace mozilla

mercurial