1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 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 https://mozilla.org/MPL/2.0/. */
8 #include "mozilla/dom/PrototypeDocumentContentSink.h"
10 #include "mozilla/dom/Document.h"
11 #include "mozilla/dom/URL.h"
12 #include "nsIContent.h"
14 #include "nsNetUtil.h"
15 #include "nsHTMLParts.h"
17 #include "mozilla/StyleSheetInlines.h"
18 #include "mozilla/css/Loader.h"
19 #include "nsGkAtoms.h"
20 #include "nsContentUtils.h"
21 #include "nsDocElementCreatedNotificationRunner.h"
22 #include "nsIScriptContext.h"
23 #include "nsNameSpaceManager.h"
24 #include "nsIScriptError.h"
26 #include "mozilla/Logging.h"
28 #include "nsIScriptElement.h"
29 #include "nsReadableUtils.h"
30 #include "nsUnicharUtils.h"
31 #include "nsIChannel.h"
32 #include "nsNodeInfoManager.h"
33 #include "nsContentCreatorFunctions.h"
34 #include "nsIContentPolicy.h"
35 #include "nsContentPolicyUtils.h"
37 #include "nsIScriptGlobalObject.h"
38 #include "mozAutoDocUpdate.h"
39 #include "nsMimeTypes.h"
40 #include "nsHtml5SVGLoadDispatcher.h"
41 #include "nsTextNode.h"
42 #include "mozilla/dom/AutoEntryScript.h"
43 #include "mozilla/dom/CDATASection.h"
44 #include "mozilla/dom/Comment.h"
45 #include "mozilla/dom/DocumentType.h"
46 #include "mozilla/dom/Element.h"
47 #include "mozilla/dom/HTMLTemplateElement.h"
48 #include "mozilla/dom/ProcessingInstruction.h"
49 #include "mozilla/dom/XMLStylesheetProcessingInstruction.h"
50 #include "mozilla/dom/ScriptLoader.h"
51 #include "mozilla/LoadInfo.h"
52 #include "mozilla/PresShell.h"
53 #include "mozilla/ProfilerLabels.h"
54 #include "mozilla/RefPtr.h"
55 #include "mozilla/Try.h"
57 #include "nsXULPrototypeCache.h"
58 #include "nsXULElement.h"
59 #include "mozilla/CycleCollectedJSContext.h"
60 #include "js/CompilationAndEvaluation.h"
61 #include "js/experimental/JSStencil.h"
62 #include "js/Utility.h" // JS::FreePolicy
64 using namespace mozilla
;
65 using namespace mozilla::dom
;
67 LazyLogModule
PrototypeDocumentContentSink::gLog("PrototypeDocument");
69 nsresult
NS_NewPrototypeDocumentContentSink(nsIContentSink
** aResult
,
70 Document
* aDoc
, nsIURI
* aURI
,
71 nsISupports
* aContainer
,
72 nsIChannel
* aChannel
) {
73 MOZ_ASSERT(nullptr != aResult
, "null ptr");
74 if (nullptr == aResult
) {
75 return NS_ERROR_NULL_POINTER
;
77 RefPtr
<PrototypeDocumentContentSink
> it
= new PrototypeDocumentContentSink();
79 nsresult rv
= it
->Init(aDoc
, aURI
, aContainer
, aChannel
);
80 NS_ENSURE_SUCCESS(rv
, rv
);
86 namespace mozilla::dom
{
88 PrototypeDocumentContentSink::PrototypeDocumentContentSink()
89 : mNextSrcLoadWaiter(nullptr),
90 mCurrentScriptProto(nullptr),
91 mOffThreadCompiling(false),
95 PrototypeDocumentContentSink::~PrototypeDocumentContentSink() {
97 mNextSrcLoadWaiter
== nullptr,
98 "unreferenced document still waiting for script source to load?");
101 nsresult
PrototypeDocumentContentSink::Init(Document
* aDoc
, nsIURI
* aURI
,
102 nsISupports
* aContainer
,
103 nsIChannel
* aChannel
) {
104 MOZ_ASSERT(aDoc
, "null ptr");
105 MOZ_ASSERT(aURI
, "null ptr");
109 mDocument
->SetDelayFrameLoaderInitialization(true);
110 mDocument
->SetMayStartLayout(false);
112 // Get the URI. this should match the uri used for the OnNewURI call in
113 // nsDocShell::CreateContentViewer.
114 nsresult rv
= NS_GetFinalChannelURI(aChannel
, getter_AddRefs(mDocumentURI
));
115 NS_ENSURE_SUCCESS(rv
, rv
);
117 mScriptLoader
= mDocument
->ScriptLoader();
122 NS_IMPL_CYCLE_COLLECTION(PrototypeDocumentContentSink
, mParser
, mDocumentURI
,
123 mDocument
, mScriptLoader
, mContextStack
,
126 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PrototypeDocumentContentSink
)
127 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIContentSink
)
128 NS_INTERFACE_MAP_ENTRY(nsIContentSink
)
129 NS_INTERFACE_MAP_ENTRY(nsIStreamLoaderObserver
)
130 NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver
)
131 NS_INTERFACE_MAP_ENTRY(nsIOffThreadScriptReceiver
)
134 NS_IMPL_CYCLE_COLLECTING_ADDREF(PrototypeDocumentContentSink
)
135 NS_IMPL_CYCLE_COLLECTING_RELEASE(PrototypeDocumentContentSink
)
137 //----------------------------------------------------------------------
139 // nsIContentSink interface
142 void PrototypeDocumentContentSink::SetDocumentCharset(
143 NotNull
<const Encoding
*> aEncoding
) {
145 mDocument
->SetDocumentCharacterSet(aEncoding
);
149 nsISupports
* PrototypeDocumentContentSink::GetTarget() {
150 return ToSupports(mDocument
);
153 bool PrototypeDocumentContentSink::IsScriptExecuting() {
154 return !!mScriptLoader
->GetCurrentScript();
158 PrototypeDocumentContentSink::SetParser(nsParserBase
* aParser
) {
159 MOZ_ASSERT(aParser
, "Should have a parser here!");
164 nsIParser
* PrototypeDocumentContentSink::GetParser() {
165 return static_cast<nsIParser
*>(mParser
.get());
168 void PrototypeDocumentContentSink::ContinueInterruptedParsingIfEnabled() {
169 if (mParser
&& mParser
->IsParserEnabled()) {
170 GetParser()->ContinueInterruptedParsing();
174 void PrototypeDocumentContentSink::ContinueInterruptedParsingAsync() {
175 nsCOMPtr
<nsIRunnable
> ev
= NewRunnableMethod(
176 "PrototypeDocumentContentSink::ContinueInterruptedParsingIfEnabled", this,
177 &PrototypeDocumentContentSink::ContinueInterruptedParsingIfEnabled
);
179 mDocument
->Dispatch(mozilla::TaskCategory::Other
, ev
.forget());
182 //----------------------------------------------------------------------
184 // PrototypeDocumentContentSink::ContextStack
187 PrototypeDocumentContentSink::ContextStack::ContextStack()
188 : mTop(nullptr), mDepth(0) {}
190 PrototypeDocumentContentSink::ContextStack::~ContextStack() { Clear(); }
192 void PrototypeDocumentContentSink::ContextStack::Traverse(
193 nsCycleCollectionTraversalCallback
& aCallback
, const char* aName
,
195 aFlags
|= CycleCollectionEdgeNameArrayFlag
;
196 Entry
* current
= mTop
;
198 CycleCollectionNoteChild(aCallback
, current
->mElement
, aName
, aFlags
);
199 current
= current
->mNext
;
203 void PrototypeDocumentContentSink::ContextStack::Clear() {
205 Entry
* doomed
= mTop
;
207 NS_IF_RELEASE(doomed
->mElement
);
213 nsresult
PrototypeDocumentContentSink::ContextStack::Push(
214 nsXULPrototypeElement
* aPrototype
, nsIContent
* aElement
) {
215 Entry
* entry
= new Entry
;
216 entry
->mPrototype
= aPrototype
;
217 entry
->mElement
= aElement
;
218 NS_IF_ADDREF(entry
->mElement
);
228 nsresult
PrototypeDocumentContentSink::ContextStack::Pop() {
229 if (mDepth
== 0) return NS_ERROR_UNEXPECTED
;
231 Entry
* doomed
= mTop
;
235 NS_IF_RELEASE(doomed
->mElement
);
240 nsresult
PrototypeDocumentContentSink::ContextStack::Peek(
241 nsXULPrototypeElement
** aPrototype
, nsIContent
** aElement
,
243 if (mDepth
== 0) return NS_ERROR_UNEXPECTED
;
245 *aPrototype
= mTop
->mPrototype
;
246 *aElement
= mTop
->mElement
;
247 NS_IF_ADDREF(*aElement
);
248 *aIndex
= mTop
->mIndex
;
253 nsresult
PrototypeDocumentContentSink::ContextStack::SetTopIndex(
255 if (mDepth
== 0) return NS_ERROR_UNEXPECTED
;
257 mTop
->mIndex
= aIndex
;
261 //----------------------------------------------------------------------
263 // Content model walking routines
266 nsresult
PrototypeDocumentContentSink::OnPrototypeLoadDone(
267 nsXULPrototypeDocument
* aPrototype
) {
268 mCurrentPrototype
= aPrototype
;
269 mDocument
->SetPrototypeDocument(aPrototype
);
271 nsresult rv
= PrepareToWalk();
272 NS_ENSURE_SUCCESS(rv
, rv
);
279 nsresult
PrototypeDocumentContentSink::PrepareToWalk() {
280 MOZ_ASSERT(mCurrentPrototype
);
283 mStillWalking
= true;
285 // Notify document that the load is beginning
286 mDocument
->BeginLoad();
288 // Get the prototype's root element and initialize the context
289 // stack for the prototype walk.
290 nsXULPrototypeElement
* proto
= mCurrentPrototype
->GetRootElement();
293 if (MOZ_LOG_TEST(gLog
, LogLevel::Error
)) {
294 nsCOMPtr
<nsIURI
> url
= mCurrentPrototype
->GetURI();
296 nsAutoCString urlspec
;
297 rv
= url
->GetSpec(urlspec
);
298 if (NS_FAILED(rv
)) return rv
;
300 MOZ_LOG(gLog
, LogLevel::Error
,
301 ("prototype: error parsing '%s'", urlspec
.get()));
307 nsINode
* nodeToInsertBefore
= mDocument
->GetFirstChild();
309 const nsTArray
<RefPtr
<nsXULPrototypePI
> >& processingInstructions
=
310 mCurrentPrototype
->GetProcessingInstructions();
312 uint32_t total
= processingInstructions
.Length();
313 for (uint32_t i
= 0; i
< total
; ++i
) {
314 rv
= CreateAndInsertPI(processingInstructions
[i
], mDocument
,
316 if (NS_FAILED(rv
)) return rv
;
319 // Do one-time initialization.
320 RefPtr
<Element
> root
;
322 // Add the root element
323 rv
= CreateElementFromPrototype(proto
, getter_AddRefs(root
), nullptr);
324 if (NS_FAILED(rv
)) return rv
;
327 mDocument
->AppendChildTo(root
, false, error
);
328 if (error
.Failed()) {
329 return error
.StealNSResult();
332 // TODO(emilio): Should this really notify? We don't notify of appends anyhow,
333 // and we just appended the root so no styles can possibly depend on it.
334 mDocument
->UpdateDocumentStates(DocumentState::RTL_LOCALE
, true);
336 nsContentUtils::AddScriptRunner(
337 new nsDocElementCreatedNotificationRunner(mDocument
));
339 // There'd better not be anything on the context stack at this
340 // point! This is the basis case for our "induction" in
341 // ResumeWalk(), below, which'll assume that there's always a
342 // content element on the context stack if we're in the document.
343 NS_ASSERTION(mContextStack
.Depth() == 0,
344 "something's on the context stack already");
345 if (mContextStack
.Depth() != 0) return NS_ERROR_UNEXPECTED
;
347 rv
= mContextStack
.Push(proto
, root
);
348 if (NS_FAILED(rv
)) return rv
;
353 nsresult
PrototypeDocumentContentSink::CreateAndInsertPI(
354 const nsXULPrototypePI
* aProtoPI
, nsINode
* aParent
, nsINode
* aBeforeThis
) {
355 MOZ_ASSERT(aProtoPI
, "null ptr");
356 MOZ_ASSERT(aParent
, "null ptr");
358 RefPtr
<ProcessingInstruction
> node
=
359 NS_NewXMLProcessingInstruction(aParent
->OwnerDoc()->NodeInfoManager(),
360 aProtoPI
->mTarget
, aProtoPI
->mData
);
363 if (aProtoPI
->mTarget
.EqualsLiteral("xml-stylesheet")) {
364 MOZ_ASSERT(LinkStyle::FromNode(*node
),
365 "XML Stylesheet node does not implement LinkStyle!");
366 auto* pi
= static_cast<XMLStylesheetProcessingInstruction
*>(node
.get());
367 rv
= InsertXMLStylesheetPI(aProtoPI
, aParent
, aBeforeThis
, pi
);
369 // No special processing, just add the PI to the document.
371 aParent
->InsertChildBefore(node
->AsContent(),
372 aBeforeThis
? aBeforeThis
->AsContent() : nullptr,
374 rv
= error
.StealNSResult();
380 nsresult
PrototypeDocumentContentSink::InsertXMLStylesheetPI(
381 const nsXULPrototypePI
* aProtoPI
, nsINode
* aParent
, nsINode
* aBeforeThis
,
382 XMLStylesheetProcessingInstruction
* aPINode
) {
383 // We want to be notified when the style sheet finishes loading, so
384 // disable style sheet loading for now.
385 aPINode
->DisableUpdates();
386 aPINode
->OverrideBaseURI(mCurrentPrototype
->GetURI());
389 aParent
->InsertChildBefore(
390 aPINode
, aBeforeThis
? aBeforeThis
->AsContent() : nullptr, false, rv
);
392 return rv
.StealNSResult();
395 // load the stylesheet if necessary, passing ourselves as
397 auto result
= aPINode
->EnableUpdatesAndUpdateStyleSheet(this);
398 if (result
.isErr()) {
399 // Ignore errors from UpdateStyleSheet; we don't want failure to
400 // do that to break the XUL document load. But do propagate out
401 // NS_ERROR_OUT_OF_MEMORY.
402 if (result
.unwrapErr() == NS_ERROR_OUT_OF_MEMORY
) {
403 return result
.unwrapErr();
408 auto update
= result
.unwrap();
409 if (update
.ShouldBlock()) {
416 void PrototypeDocumentContentSink::CloseElement(Element
* aElement
,
418 if (nsIContent::RequiresDoneAddingChildren(
419 aElement
->NodeInfo()->NamespaceID(),
420 aElement
->NodeInfo()->NameAtom())) {
421 aElement
->DoneAddingChildren(false);
424 if (auto* linkStyle
= LinkStyle::FromNode(*aElement
)) {
425 auto result
= linkStyle
->EnableUpdatesAndUpdateStyleSheet(this);
426 if (result
.isOk() && result
.unwrap().ShouldBlock()) {
436 // See bug 370111 and bug 1495946. We don't cache inline styles nor module
437 // scripts in the prototype cache, and we don't notify on node insertion, so
438 // we need to do this for the stylesheet / script to be properly processed.
439 // This kinda sucks, but notifying was a pretty sizeable perf regression so...
440 if (aElement
->IsHTMLElement(nsGkAtoms::script
) ||
441 aElement
->IsSVGElement(nsGkAtoms::script
)) {
442 nsCOMPtr
<nsIScriptElement
> sele
= do_QueryInterface(aElement
);
443 MOZ_ASSERT(sele
, "Node didn't QI to script.");
444 if (sele
->GetScriptIsModule()) {
445 DebugOnly
<bool> block
= sele
->AttemptToExecute();
446 MOZ_ASSERT(!block
, "<script type=module> shouldn't block the parser");
451 nsresult
PrototypeDocumentContentSink::ResumeWalk() {
452 nsresult rv
= ResumeWalkInternal();
454 nsContentUtils::ReportToConsoleNonLocalized(
455 u
"Failed to load document from prototype document."_ns
,
456 nsIScriptError::errorFlag
, "Prototype Document"_ns
, mDocument
,
462 nsresult
PrototypeDocumentContentSink::ResumeWalkInternal() {
463 MOZ_ASSERT(mStillWalking
);
464 // Walk the prototype and build the delegate content model. The
465 // walk is performed in a top-down, left-to-right fashion. That
466 // is, a parent is built before any of its children; a node is
467 // only built after all of its siblings to the left are fully
470 // It is interruptable so that transcluded documents (e.g.,
471 // <html:script src="..." />) can be properly re-loaded if the
472 // cached copy of the document becomes stale.
474 nsCOMPtr
<nsIURI
> docURI
=
475 mCurrentPrototype
? mCurrentPrototype
->GetURI() : nullptr;
478 // Begin (or resume) walking the current prototype.
480 while (mContextStack
.Depth() > 0) {
481 // Look at the top of the stack to determine what we're
482 // currently working on.
483 // This will always be a node already constructed and
484 // inserted to the actual document.
485 nsXULPrototypeElement
* proto
;
486 nsCOMPtr
<nsIContent
> element
;
487 nsCOMPtr
<nsIContent
> nodeToPushTo
;
488 int32_t indx
; // all children of proto before indx (not
489 // inclusive) have already been constructed
490 rv
= mContextStack
.Peek(&proto
, getter_AddRefs(element
), &indx
);
491 if (NS_FAILED(rv
)) return rv
;
493 if (indx
>= (int32_t)proto
->mChildren
.Length()) {
495 // We've processed all of the prototype's children.
496 CloseElement(element
->AsElement(), /* aHadChildren = */ true);
498 // Now pop the context stack back up to the parent
499 // element and continue the prototype walk.
504 nodeToPushTo
= element
;
505 // For template elements append the content to the template's document
507 if (auto* templateElement
= HTMLTemplateElement::FromNode(element
)) {
508 nodeToPushTo
= templateElement
->Content();
511 // Grab the next child, and advance the current context stack
512 // to the next sibling to our right.
513 nsXULPrototypeNode
* childproto
= proto
->mChildren
[indx
];
514 mContextStack
.SetTopIndex(++indx
);
516 switch (childproto
->mType
) {
517 case nsXULPrototypeNode::eType_Element
: {
518 // An 'element', which may contain more content.
519 auto* protoele
= static_cast<nsXULPrototypeElement
*>(childproto
);
521 RefPtr
<Element
> child
;
522 MOZ_TRY(CreateElementFromPrototype(protoele
, getter_AddRefs(child
),
525 if (auto* linkStyle
= LinkStyle::FromNode(*child
)) {
526 linkStyle
->DisableUpdates();
529 // ...and append it to the content model.
531 nodeToPushTo
->AppendChildTo(child
, false, error
);
532 if (error
.Failed()) {
533 return error
.StealNSResult();
536 if (nsIContent::RequiresDoneCreatingElement(
537 protoele
->mNodeInfo
->NamespaceID(),
538 protoele
->mNodeInfo
->NameAtom())) {
539 child
->DoneCreatingElement();
542 // If it has children, push the element onto the context
543 // stack and begin to process them.
544 if (protoele
->mChildren
.Length() > 0) {
545 rv
= mContextStack
.Push(protoele
, child
);
546 if (NS_FAILED(rv
)) return rv
;
548 // If there are no children, close the element immediately.
549 CloseElement(child
, /* aHadChildren = */ false);
553 case nsXULPrototypeNode::eType_Script
: {
554 // A script reference. Execute the script immediately;
555 // this may have side effects in the content model.
556 auto* scriptproto
= static_cast<nsXULPrototypeScript
*>(childproto
);
557 if (scriptproto
->mSrcURI
) {
558 // A transcluded script reference; this may
559 // "block" our prototype walk if the script isn't
560 // cached, or the cached copy of the script is
561 // stale and must be reloaded.
563 rv
= LoadScript(scriptproto
, &blocked
);
564 // If the script cannot be loaded, just keep going!
566 if (NS_SUCCEEDED(rv
) && blocked
) return NS_OK
;
567 } else if (scriptproto
->HasStencil()) {
569 rv
= ExecuteScript(scriptproto
);
570 if (NS_FAILED(rv
)) return rv
;
574 case nsXULPrototypeNode::eType_Text
: {
575 nsNodeInfoManager
* nim
= nodeToPushTo
->NodeInfo()->NodeInfoManager();
576 // A simple text node.
577 RefPtr
<nsTextNode
> text
= new (nim
) nsTextNode(nim
);
579 auto* textproto
= static_cast<nsXULPrototypeText
*>(childproto
);
580 text
->SetText(textproto
->mValue
, false);
583 nodeToPushTo
->AppendChildTo(text
, false, error
);
584 if (error
.Failed()) {
585 return error
.StealNSResult();
589 case nsXULPrototypeNode::eType_PI
: {
590 auto* piProto
= static_cast<nsXULPrototypePI
*>(childproto
);
592 // <?xml-stylesheet?> doesn't have an effect
593 // outside the prolog, like it used to. Issue a warning.
595 if (piProto
->mTarget
.EqualsLiteral("xml-stylesheet")) {
596 AutoTArray
<nsString
, 1> params
= {piProto
->mTarget
};
598 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag
,
599 "XUL Document"_ns
, nullptr,
600 nsContentUtils::eXUL_PROPERTIES
,
601 "PINotInProlog", params
, docURI
);
604 nsIContent
* parent
= element
.get();
606 // an inline script could have removed the root element
607 rv
= CreateAndInsertPI(piProto
, parent
, nullptr);
608 NS_ENSURE_SUCCESS(rv
, rv
);
613 MOZ_ASSERT_UNREACHABLE("Unexpected nsXULPrototypeNode::Type");
617 // Once we get here, the context stack will have been
618 // depleted. That means that the entire prototype has been
619 // walked and content has been constructed.
623 mStillWalking
= false;
624 return MaybeDoneWalking();
627 void PrototypeDocumentContentSink::InitialTranslationCompleted() {
631 nsresult
PrototypeDocumentContentSink::MaybeDoneWalking() {
632 if (mPendingSheets
> 0 || mStillWalking
) {
636 if (mDocument
->HasPendingInitialTranslation()) {
637 mDocument
->OnParsingCompleted();
641 return DoneWalking();
644 nsresult
PrototypeDocumentContentSink::DoneWalking() {
645 MOZ_ASSERT(mPendingSheets
== 0, "there are sheets to be loaded");
646 MOZ_ASSERT(!mStillWalking
, "walk not done");
647 MOZ_ASSERT(!mDocument
->HasPendingInitialTranslation(), "translation pending");
650 MOZ_ASSERT(mDocument
->GetReadyStateEnum() == Document::READYSTATE_LOADING
,
652 mDocument
->SetReadyStateInternal(Document::READYSTATE_INTERACTIVE
);
653 mDocument
->NotifyPossibleTitleChange(false);
655 nsContentUtils::DispatchEventOnlyToChrome(mDocument
, mDocument
,
656 u
"MozBeforeInitialXULLayout"_ns
,
657 CanBubble::eYes
, Cancelable::eNo
);
661 mScriptLoader
->ParsingComplete(false);
662 mScriptLoader
->DeferCheckpointReached();
667 if (IsChromeURI(mDocumentURI
) &&
668 nsXULPrototypeCache::GetInstance()->IsEnabled()) {
670 nsXULPrototypeCache::GetInstance()->HasPrototype(mDocumentURI
,
672 if (!isCachedOnDisk
) {
673 nsXULPrototypeCache::GetInstance()->WritePrototype(mCurrentPrototype
);
677 mDocument
->SetDelayFrameLoaderInitialization(false);
678 RefPtr
<Document
> doc
= mDocument
;
679 doc
->MaybeInitializeFinalizeFrameLoaders();
681 // If the document we are loading has a reference or it is a
682 // frameset document, disable the scroll bars on the views.
684 doc
->SetScrollToRef(mDocument
->GetDocumentURI());
691 void PrototypeDocumentContentSink::StartLayout() {
692 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
693 "PrototypeDocumentContentSink::StartLayout", LAYOUT
,
694 mDocumentURI
->GetSpecOrDefault());
695 mDocument
->SetMayStartLayout(true);
696 RefPtr
<PresShell
> presShell
= mDocument
->GetPresShell();
697 if (presShell
&& !presShell
->DidInitialize()) {
698 nsresult rv
= presShell
->Initialize();
706 PrototypeDocumentContentSink::StyleSheetLoaded(StyleSheet
* aSheet
,
710 // Don't care about when alternate sheets finish loading
711 MOZ_ASSERT(mPendingSheets
> 0, "Unexpected StyleSheetLoaded notification");
715 return MaybeDoneWalking();
721 nsresult
PrototypeDocumentContentSink::LoadScript(
722 nsXULPrototypeScript
* aScriptProto
, bool* aBlock
) {
723 // Load a transcluded script
726 bool isChromeDoc
= IsChromeURI(mDocumentURI
);
728 if (isChromeDoc
&& aScriptProto
->HasStencil()) {
729 rv
= ExecuteScript(aScriptProto
);
731 // Ignore return value from execution, and don't block
736 // Try the XUL script cache, in case two XUL documents source the same
737 // .js file (e.g., strres.js from navigator.xul and utilityOverlay.xul).
738 // XXXbe the cache relies on aScriptProto's GC root!
739 bool useXULCache
= nsXULPrototypeCache::GetInstance()->IsEnabled();
741 if (isChromeDoc
&& useXULCache
) {
742 RefPtr
<JS::Stencil
> newStencil
=
743 nsXULPrototypeCache::GetInstance()->GetStencil(aScriptProto
->mSrcURI
);
745 // The script language for a proto must remain constant - we
746 // can't just change it for this unexpected language.
747 aScriptProto
->Set(newStencil
);
750 if (aScriptProto
->HasStencil()) {
751 rv
= ExecuteScript(aScriptProto
);
753 // Ignore return value from execution, and don't block
759 // Release stencil from FastLoad since we decided against using them
760 aScriptProto
->Set(nullptr);
762 // Set the current script prototype so that OnStreamComplete can report
763 // the right file if there are errors in the script.
764 NS_ASSERTION(!mCurrentScriptProto
,
765 "still loading a script when starting another load?");
766 mCurrentScriptProto
= aScriptProto
;
768 if (isChromeDoc
&& aScriptProto
->mSrcLoading
) {
769 // Another document load has started, which is still in progress.
770 // Remember to ResumeWalk this document when the load completes.
771 mNextSrcLoadWaiter
= aScriptProto
->mSrcLoadWaiters
;
772 aScriptProto
->mSrcLoadWaiters
= this;
775 nsCOMPtr
<nsILoadGroup
> group
=
777 ->GetDocumentLoadGroup(); // found in
778 // mozilla::dom::Document::SetScriptGlobalObject
780 // Note: the loader will keep itself alive while it's loading.
781 nsCOMPtr
<nsIStreamLoader
> loader
;
782 rv
= NS_NewStreamLoader(
783 getter_AddRefs(loader
), aScriptProto
->mSrcURI
,
785 mDocument
, // aRequestingContext
786 nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT
,
787 nsIContentPolicy::TYPE_INTERNAL_SCRIPT
, group
);
790 mCurrentScriptProto
= nullptr;
794 aScriptProto
->mSrcLoading
= true;
797 // Block until OnStreamComplete resumes us.
803 PrototypeDocumentContentSink::OnStreamComplete(nsIStreamLoader
* aLoader
,
804 nsISupports
* context
,
807 const uint8_t* string
) {
808 nsCOMPtr
<nsIRequest
> request
;
809 aLoader
->GetRequest(getter_AddRefs(request
));
810 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(request
);
813 // print a load error on bad status
814 if (NS_FAILED(aStatus
)) {
816 nsCOMPtr
<nsIURI
> uri
;
817 channel
->GetURI(getter_AddRefs(uri
));
819 printf("Failed to load %s\n", uri
->GetSpecOrDefault().get());
825 // This is the completion routine that will be called when a
826 // transcluded script completes. Compile and execute the script
827 // if the load was successful, then continue building content
828 // from the prototype.
829 nsresult rv
= aStatus
;
831 NS_ASSERTION(mCurrentScriptProto
&& mCurrentScriptProto
->mSrcLoading
,
832 "script source not loading on unichar stream complete?");
833 if (!mCurrentScriptProto
) {
834 // XXX Wallpaper for bug 270042
838 if (NS_SUCCEEDED(aStatus
)) {
839 // If the including document is a FastLoad document, and we're
840 // compiling an out-of-line script (one with src=...), then we must
841 // be writing a new FastLoad file. If we were reading this script
842 // from the FastLoad file, XULContentSinkImpl::OpenScript (over in
843 // nsXULContentSink.cpp) would have already deserialized a non-null
844 // script->mStencil, causing control flow at the top of LoadScript
845 // not to reach here.
846 nsCOMPtr
<nsIURI
> uri
= mCurrentScriptProto
->mSrcURI
;
848 // XXX should also check nsIHttpChannel::requestSucceeded
850 MOZ_ASSERT(!mOffThreadCompiling
,
851 "PrototypeDocument can't load multiple scripts at once");
853 UniquePtr
<Utf8Unit
[], JS::FreePolicy
> units
;
854 size_t unitsLength
= 0;
856 rv
= ScriptLoader::ConvertToUTF8(channel
, string
, stringLen
, u
""_ns
,
857 mDocument
, units
, unitsLength
);
858 if (NS_SUCCEEDED(rv
)) {
859 rv
= mCurrentScriptProto
->CompileMaybeOffThread(
860 std::move(units
), unitsLength
, uri
, 1, mDocument
, this);
861 if (NS_SUCCEEDED(rv
) && !mCurrentScriptProto
->HasStencil()) {
862 mOffThreadCompiling
= true;
863 mDocument
->BlockOnload();
869 return OnScriptCompileComplete(mCurrentScriptProto
->GetStencil(), rv
);
873 PrototypeDocumentContentSink::OnScriptCompileComplete(JS::Stencil
* aStencil
,
875 // The mCurrentScriptProto may have been cleared out by another
876 // PrototypeDocumentContentSink.
877 if (!mCurrentScriptProto
) {
881 // When compiling off thread the script will not have been attached to the
883 if (aStencil
&& !mCurrentScriptProto
->HasStencil()) {
884 mCurrentScriptProto
->Set(aStencil
);
887 // Allow load events to be fired once off thread compilation finishes.
888 if (mOffThreadCompiling
) {
889 mOffThreadCompiling
= false;
890 mDocument
->UnblockOnload(false);
893 // Clear mCurrentScriptProto now, but save it first for use below in
894 // the execute code, and in the while loop that resumes walks of other
895 // documents that raced to load this script.
896 nsXULPrototypeScript
* scriptProto
= mCurrentScriptProto
;
897 mCurrentScriptProto
= nullptr;
899 // Clear the prototype's loading flag before executing the script or
900 // resuming document walks, in case any of those control flows starts a
902 scriptProto
->mSrcLoading
= false;
904 nsresult rv
= aStatus
;
905 if (NS_SUCCEEDED(rv
)) {
906 rv
= ExecuteScript(scriptProto
);
908 // If the XUL cache is enabled, save the script object there in
909 // case different XUL documents source the same script.
911 // But don't save the script in the cache unless the master XUL
912 // document URL is a chrome: URL. It is valid for a URL such as
913 // about:config to translate into a master document URL, whose
914 // prototype document nodes -- including prototype scripts that
915 // hold GC roots protecting their mJSObject pointers -- are not
916 // cached in the XUL prototype cache. See StartDocumentLoad,
917 // the fillXULCache logic.
919 // A document such as about:config is free to load a script via
920 // a URL such as chrome://global/content/config.js, and we must
921 // not cache that script object without a prototype cache entry
922 // containing a companion nsXULPrototypeScript node that owns a
923 // GC root protecting the script object. Otherwise, the script
924 // cache entry will dangle once the uncached prototype document
925 // is released when its owning document is unloaded.
927 // (See http://bugzilla.mozilla.org/show_bug.cgi?id=98207 for
928 // the true crime story.)
929 bool useXULCache
= nsXULPrototypeCache::GetInstance()->IsEnabled();
931 if (useXULCache
&& IsChromeURI(mDocumentURI
) && scriptProto
->HasStencil()) {
932 nsXULPrototypeCache::GetInstance()->PutStencil(scriptProto
->mSrcURI
,
933 scriptProto
->GetStencil());
935 // ignore any evaluation errors
940 // Load a pointer to the prototype-script's list of documents who
941 // raced to load the same script
942 PrototypeDocumentContentSink
** docp
= &scriptProto
->mSrcLoadWaiters
;
944 // Resume walking other documents that waited for this one's load, first
945 // executing the script we just compiled, in each doc's script context
946 PrototypeDocumentContentSink
* doc
;
947 while ((doc
= *docp
) != nullptr) {
948 NS_ASSERTION(doc
->mCurrentScriptProto
== scriptProto
,
949 "waiting for wrong script to load?");
950 doc
->mCurrentScriptProto
= nullptr;
952 // Unlink doc from scriptProto's list before executing and resuming
953 *docp
= doc
->mNextSrcLoadWaiter
;
954 doc
->mNextSrcLoadWaiter
= nullptr;
956 if (aStatus
== NS_BINDING_ABORTED
&& !scriptProto
->HasStencil()) {
957 // If the previous doc load was aborted, we want to try loading
958 // again for the next doc. Otherwise, one abort would lead to all
959 // subsequent waiting docs to abort as well.
961 doc
->LoadScript(scriptProto
, &block
);
966 // Execute only if we loaded and compiled successfully, then resume
967 if (NS_SUCCEEDED(aStatus
) && scriptProto
->HasStencil()) {
968 doc
->ExecuteScript(scriptProto
);
977 nsresult
PrototypeDocumentContentSink::ExecuteScript(
978 nsXULPrototypeScript
* aScript
) {
979 MOZ_ASSERT(aScript
!= nullptr, "null ptr");
980 NS_ENSURE_TRUE(aScript
, NS_ERROR_NULL_POINTER
);
982 nsIScriptGlobalObject
* scriptGlobalObject
;
983 bool aHasHadScriptHandlingObject
;
985 mDocument
->GetScriptHandlingObject(aHasHadScriptHandlingObject
);
987 NS_ENSURE_TRUE(scriptGlobalObject
, NS_ERROR_NOT_INITIALIZED
);
990 rv
= scriptGlobalObject
->EnsureScriptEnvironment();
991 NS_ENSURE_SUCCESS(rv
, rv
);
993 // Execute the precompiled script with the given version
996 // We're about to run script via JS_ExecuteScript, so we need an
997 // AutoEntryScript. This is Gecko specific and not in any spec.
998 AutoEntryScript
aes(scriptGlobalObject
, "precompiled XUL <script> element");
999 JSContext
* cx
= aes
.cx();
1001 JS::Rooted
<JSScript
*> scriptObject(cx
);
1002 rv
= aScript
->InstantiateScript(cx
, &scriptObject
);
1003 NS_ENSURE_SUCCESS(rv
, rv
);
1005 JS::Rooted
<JSObject
*> global(cx
, JS::CurrentGlobalOrNull(cx
));
1006 NS_ENSURE_TRUE(xpc::Scriptability::Get(global
).Allowed(), NS_OK
);
1008 // On failure, ~AutoScriptEntry will handle exceptions, so
1009 // there is no need to manually check the return value.
1010 JS::Rooted
<JS::Value
> rval(cx
);
1011 Unused
<< JS_ExecuteScript(cx
, scriptObject
, &rval
);
1016 nsresult
PrototypeDocumentContentSink::CreateElementFromPrototype(
1017 nsXULPrototypeElement
* aPrototype
, Element
** aResult
, nsIContent
* aParent
) {
1018 // Create a content model element from a prototype element.
1019 MOZ_ASSERT(aPrototype
, "null ptr");
1020 if (!aPrototype
) return NS_ERROR_NULL_POINTER
;
1023 nsresult rv
= NS_OK
;
1025 if (MOZ_LOG_TEST(gLog
, LogLevel::Debug
)) {
1027 gLog
, LogLevel::Debug
,
1028 ("prototype: creating <%s> from prototype",
1029 NS_ConvertUTF16toUTF8(aPrototype
->mNodeInfo
->QualifiedName()).get()));
1032 RefPtr
<Element
> result
;
1034 Document
* doc
= aParent
? aParent
->OwnerDoc() : mDocument
.get();
1035 if (aPrototype
->mNodeInfo
->NamespaceEquals(kNameSpaceID_XUL
)) {
1036 const bool isRoot
= !aParent
;
1037 // If it's a XUL element, it'll be lightweight until somebody
1039 rv
= nsXULElement::CreateFromPrototype(aPrototype
, doc
, true, isRoot
,
1040 getter_AddRefs(result
));
1041 if (NS_FAILED(rv
)) return rv
;
1043 // If it's not a XUL element, it's gonna be heavyweight no matter
1044 // what. So we need to copy everything out of the prototype
1045 // into the element. Get a nodeinfo from our nodeinfo manager
1047 RefPtr
<NodeInfo
> newNodeInfo
= doc
->NodeInfoManager()->GetNodeInfo(
1048 aPrototype
->mNodeInfo
->NameAtom(),
1049 aPrototype
->mNodeInfo
->GetPrefixAtom(),
1050 aPrototype
->mNodeInfo
->NamespaceID(), nsINode::ELEMENT_NODE
);
1052 return NS_ERROR_OUT_OF_MEMORY
;
1054 const bool isScript
=
1055 newNodeInfo
->Equals(nsGkAtoms::script
, kNameSpaceID_XHTML
) ||
1056 newNodeInfo
->Equals(nsGkAtoms::script
, kNameSpaceID_SVG
);
1057 if (aPrototype
->mIsAtom
&&
1058 newNodeInfo
->NamespaceID() == kNameSpaceID_XHTML
) {
1059 rv
= NS_NewHTMLElement(getter_AddRefs(result
), newNodeInfo
.forget(),
1060 NOT_FROM_PARSER
, aPrototype
->mIsAtom
);
1062 rv
= NS_NewElement(getter_AddRefs(result
), newNodeInfo
.forget(),
1065 if (NS_FAILED(rv
)) return rv
;
1067 rv
= AddAttributes(aPrototype
, result
);
1068 if (NS_FAILED(rv
)) return rv
;
1071 nsCOMPtr
<nsIScriptElement
> sele
= do_QueryInterface(result
);
1072 MOZ_ASSERT(sele
, "Node didn't QI to script.");
1074 sele
->FreezeExecutionAttrs(doc
);
1075 // Script loading is handled by the this content sink, so prevent the
1076 // script from loading when it is bound to the document.
1078 // NOTE(emilio): This is only done for non-module scripts, because we
1079 // don't support caching modules properly yet, see the comment in
1080 // XULContentSinkImpl::OpenScript. For non-inline scripts, this is enough,
1081 // since we can start the load when the node is inserted. Non-inline
1082 // scripts need another special-case in CloseElement.
1083 if (!sele
->GetScriptIsModule()) {
1084 sele
->PreventExecution();
1089 // FIXME(bug 1627474): Is this right if this is inside an <html:template>?
1090 if (result
->HasAttr(nsGkAtoms::datal10nid
)) {
1091 mDocument
->mL10nProtoElements
.InsertOrUpdate(result
, RefPtr
{aPrototype
});
1092 result
->SetElementCreatedFromPrototypeAndHasUnmodifiedL10n();
1094 result
.forget(aResult
);
1098 nsresult
PrototypeDocumentContentSink::AddAttributes(
1099 nsXULPrototypeElement
* aPrototype
, Element
* aElement
) {
1102 for (size_t i
= 0; i
< aPrototype
->mAttributes
.Length(); ++i
) {
1103 nsXULPrototypeAttribute
* protoattr
= &(aPrototype
->mAttributes
[i
]);
1104 nsAutoString valueStr
;
1105 protoattr
->mValue
.ToString(valueStr
);
1107 rv
= aElement
->SetAttr(protoattr
->mName
.NamespaceID(),
1108 protoattr
->mName
.LocalName(),
1109 protoattr
->mName
.GetPrefix(), valueStr
, false);
1110 if (NS_FAILED(rv
)) return rv
;
1116 } // namespace mozilla::dom