Bug 1856663 - Add more chunks for Android mochitest-plain. r=jmaher,taskgraph-reviewe...
[gecko.git] / dom / prototype / PrototypeDocumentContentSink.cpp
blob29195c2d8992bea023c16ce2dc41cbb9f7a755c5
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/. */
7 #include "nsCOMPtr.h"
8 #include "mozilla/dom/PrototypeDocumentContentSink.h"
9 #include "nsIParser.h"
10 #include "mozilla/dom/Document.h"
11 #include "mozilla/dom/URL.h"
12 #include "nsIContent.h"
13 #include "nsIURI.h"
14 #include "nsNetUtil.h"
15 #include "nsHTMLParts.h"
16 #include "nsCRT.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"
25 #include "prtime.h"
26 #include "mozilla/Logging.h"
27 #include "nsRect.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"
36 #include "nsError.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);
82 it.forget(aResult);
83 return NS_OK;
86 namespace mozilla::dom {
88 PrototypeDocumentContentSink::PrototypeDocumentContentSink()
89 : mNextSrcLoadWaiter(nullptr),
90 mCurrentScriptProto(nullptr),
91 mOffThreadCompiling(false),
92 mStillWalking(false),
93 mPendingSheets(0) {}
95 PrototypeDocumentContentSink::~PrototypeDocumentContentSink() {
96 NS_ASSERTION(
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");
107 mDocument = aDoc;
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();
119 return NS_OK;
122 NS_IMPL_CYCLE_COLLECTION(PrototypeDocumentContentSink, mParser, mDocumentURI,
123 mDocument, mScriptLoader, mContextStack,
124 mCurrentPrototype)
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)
132 NS_INTERFACE_MAP_END
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) {
144 if (mDocument) {
145 mDocument->SetDocumentCharacterSet(aEncoding);
149 nsISupports* PrototypeDocumentContentSink::GetTarget() {
150 return ToSupports(mDocument);
153 bool PrototypeDocumentContentSink::IsScriptExecuting() {
154 return !!mScriptLoader->GetCurrentScript();
157 NS_IMETHODIMP
158 PrototypeDocumentContentSink::SetParser(nsParserBase* aParser) {
159 MOZ_ASSERT(aParser, "Should have a parser here!");
160 mParser = aParser;
161 return NS_OK;
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,
194 uint32_t aFlags) {
195 aFlags |= CycleCollectionEdgeNameArrayFlag;
196 Entry* current = mTop;
197 while (current) {
198 CycleCollectionNoteChild(aCallback, current->mElement, aName, aFlags);
199 current = current->mNext;
203 void PrototypeDocumentContentSink::ContextStack::Clear() {
204 while (mTop) {
205 Entry* doomed = mTop;
206 mTop = mTop->mNext;
207 NS_IF_RELEASE(doomed->mElement);
208 delete doomed;
210 mDepth = 0;
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);
219 entry->mIndex = 0;
221 entry->mNext = mTop;
222 mTop = entry;
224 ++mDepth;
225 return NS_OK;
228 nsresult PrototypeDocumentContentSink::ContextStack::Pop() {
229 if (mDepth == 0) return NS_ERROR_UNEXPECTED;
231 Entry* doomed = mTop;
232 mTop = mTop->mNext;
233 --mDepth;
235 NS_IF_RELEASE(doomed->mElement);
236 delete doomed;
237 return NS_OK;
240 nsresult PrototypeDocumentContentSink::ContextStack::Peek(
241 nsXULPrototypeElement** aPrototype, nsIContent** aElement,
242 int32_t* aIndex) {
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;
250 return NS_OK;
253 nsresult PrototypeDocumentContentSink::ContextStack::SetTopIndex(
254 int32_t aIndex) {
255 if (mDepth == 0) return NS_ERROR_UNEXPECTED;
257 mTop->mIndex = aIndex;
258 return NS_OK;
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);
274 rv = ResumeWalk();
276 return rv;
279 nsresult PrototypeDocumentContentSink::PrepareToWalk() {
280 MOZ_ASSERT(mCurrentPrototype);
281 nsresult rv;
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();
292 if (!proto) {
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()));
304 return NS_OK;
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,
315 nodeToInsertBefore);
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;
326 ErrorResult error;
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;
350 return NS_OK;
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);
362 nsresult rv;
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);
368 } else {
369 // No special processing, just add the PI to the document.
370 ErrorResult error;
371 aParent->InsertChildBefore(node->AsContent(),
372 aBeforeThis ? aBeforeThis->AsContent() : nullptr,
373 false, error);
374 rv = error.StealNSResult();
377 return rv;
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());
388 ErrorResult rv;
389 aParent->InsertChildBefore(
390 aPINode, aBeforeThis ? aBeforeThis->AsContent() : nullptr, false, rv);
391 if (rv.Failed()) {
392 return rv.StealNSResult();
395 // load the stylesheet if necessary, passing ourselves as
396 // nsICSSObserver
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();
405 return NS_OK;
408 auto update = result.unwrap();
409 if (update.ShouldBlock()) {
410 ++mPendingSheets;
413 return NS_OK;
416 void PrototypeDocumentContentSink::CloseElement(Element* aElement,
417 bool aHadChildren) {
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()) {
427 ++mPendingSheets;
429 return;
432 if (!aHadChildren) {
433 return;
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();
453 if (NS_FAILED(rv)) {
454 nsContentUtils::ReportToConsoleNonLocalized(
455 u"Failed to load document from prototype document."_ns,
456 nsIScriptError::errorFlag, "Prototype Document"_ns, mDocument,
457 mDocumentURI);
459 return rv;
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
468 // constructed.
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.
473 nsresult rv;
474 nsCOMPtr<nsIURI> docURI =
475 mCurrentPrototype ? mCurrentPrototype->GetURI() : nullptr;
477 while (true) {
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()) {
494 if (element) {
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.
500 mContextStack.Pop();
501 continue;
504 nodeToPushTo = element;
505 // For template elements append the content to the template's document
506 // fragment.
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),
523 nodeToPushTo));
525 if (auto* linkStyle = LinkStyle::FromNode(*child)) {
526 linkStyle->DisableUpdates();
529 // ...and append it to the content model.
530 ErrorResult error;
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;
547 } else {
548 // If there are no children, close the element immediately.
549 CloseElement(child, /* aHadChildren = */ false);
551 } break;
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.
562 bool blocked;
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()) {
568 // An inline script
569 rv = ExecuteScript(scriptproto);
570 if (NS_FAILED(rv)) return rv;
572 } break;
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);
582 ErrorResult error;
583 nodeToPushTo->AppendChildTo(text, false, error);
584 if (error.Failed()) {
585 return error.StealNSResult();
587 } break;
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();
605 if (parent) {
606 // an inline script could have removed the root element
607 rv = CreateAndInsertPI(piProto, parent, nullptr);
608 NS_ENSURE_SUCCESS(rv, rv);
610 } break;
612 default:
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.
620 break;
623 mStillWalking = false;
624 return MaybeDoneWalking();
627 void PrototypeDocumentContentSink::InitialTranslationCompleted() {
628 MaybeDoneWalking();
631 nsresult PrototypeDocumentContentSink::MaybeDoneWalking() {
632 if (mPendingSheets > 0 || mStillWalking) {
633 return NS_OK;
636 if (mDocument->HasPendingInitialTranslation()) {
637 mDocument->OnParsingCompleted();
638 return NS_OK;
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");
649 if (mDocument) {
650 MOZ_ASSERT(mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING,
651 "Bad readyState");
652 mDocument->SetReadyStateInternal(Document::READYSTATE_INTERACTIVE);
653 mDocument->NotifyPossibleTitleChange(false);
655 nsContentUtils::DispatchEventOnlyToChrome(mDocument, mDocument,
656 u"MozBeforeInitialXULLayout"_ns,
657 CanBubble::eYes, Cancelable::eNo);
660 if (mScriptLoader) {
661 mScriptLoader->ParsingComplete(false);
662 mScriptLoader->DeferCheckpointReached();
665 StartLayout();
667 if (IsChromeURI(mDocumentURI) &&
668 nsXULPrototypeCache::GetInstance()->IsEnabled()) {
669 bool isCachedOnDisk;
670 nsXULPrototypeCache::GetInstance()->HasPrototype(mDocumentURI,
671 &isCachedOnDisk);
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());
686 doc->EndLoad();
688 return NS_OK;
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();
699 if (NS_FAILED(rv)) {
700 return;
705 NS_IMETHODIMP
706 PrototypeDocumentContentSink::StyleSheetLoaded(StyleSheet* aSheet,
707 bool aWasDeferred,
708 nsresult aStatus) {
709 if (!aWasDeferred) {
710 // Don't care about when alternate sheets finish loading
711 MOZ_ASSERT(mPendingSheets > 0, "Unexpected StyleSheetLoaded notification");
713 --mPendingSheets;
715 return MaybeDoneWalking();
718 return NS_OK;
721 nsresult PrototypeDocumentContentSink::LoadScript(
722 nsXULPrototypeScript* aScriptProto, bool* aBlock) {
723 // Load a transcluded script
724 nsresult rv;
726 bool isChromeDoc = IsChromeURI(mDocumentURI);
728 if (isChromeDoc && aScriptProto->HasStencil()) {
729 rv = ExecuteScript(aScriptProto);
731 // Ignore return value from execution, and don't block
732 *aBlock = false;
733 return NS_OK;
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);
744 if (newStencil) {
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
754 *aBlock = false;
755 return NS_OK;
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;
773 NS_ADDREF_THIS();
774 } else {
775 nsCOMPtr<nsILoadGroup> group =
776 mDocument
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,
784 this, // aObserver
785 mDocument, // aRequestingContext
786 nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
787 nsIContentPolicy::TYPE_INTERNAL_SCRIPT, group);
789 if (NS_FAILED(rv)) {
790 mCurrentScriptProto = nullptr;
791 return rv;
794 aScriptProto->mSrcLoading = true;
797 // Block until OnStreamComplete resumes us.
798 *aBlock = true;
799 return NS_OK;
802 NS_IMETHODIMP
803 PrototypeDocumentContentSink::OnStreamComplete(nsIStreamLoader* aLoader,
804 nsISupports* context,
805 nsresult aStatus,
806 uint32_t stringLen,
807 const uint8_t* string) {
808 nsCOMPtr<nsIRequest> request;
809 aLoader->GetRequest(getter_AddRefs(request));
810 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
812 #ifdef DEBUG
813 // print a load error on bad status
814 if (NS_FAILED(aStatus)) {
815 if (channel) {
816 nsCOMPtr<nsIURI> uri;
817 channel->GetURI(getter_AddRefs(uri));
818 if (uri) {
819 printf("Failed to load %s\n", uri->GetSpecOrDefault().get());
823 #endif
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
835 return NS_OK;
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();
864 return NS_OK;
869 return OnScriptCompileComplete(mCurrentScriptProto->GetStencil(), rv);
872 NS_IMETHODIMP
873 PrototypeDocumentContentSink::OnScriptCompileComplete(JS::Stencil* aStencil,
874 nsresult aStatus) {
875 // The mCurrentScriptProto may have been cleared out by another
876 // PrototypeDocumentContentSink.
877 if (!mCurrentScriptProto) {
878 return NS_OK;
881 // When compiling off thread the script will not have been attached to the
882 // script proto yet.
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
901 // new script load.
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
938 rv = ResumeWalk();
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.
960 bool block = false;
961 doc->LoadScript(scriptProto, &block);
962 NS_RELEASE(doc);
963 return rv;
966 // Execute only if we loaded and compiled successfully, then resume
967 if (NS_SUCCEEDED(aStatus) && scriptProto->HasStencil()) {
968 doc->ExecuteScript(scriptProto);
970 doc->ResumeWalk();
971 NS_RELEASE(doc);
974 return rv;
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;
984 scriptGlobalObject =
985 mDocument->GetScriptHandlingObject(aHasHadScriptHandlingObject);
987 NS_ENSURE_TRUE(scriptGlobalObject, NS_ERROR_NOT_INITIALIZED);
989 nsresult rv;
990 rv = scriptGlobalObject->EnsureScriptEnvironment();
991 NS_ENSURE_SUCCESS(rv, rv);
993 // Execute the precompiled script with the given version
994 nsAutoMicroTask mt;
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);
1013 return NS_OK;
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;
1022 *aResult = nullptr;
1023 nsresult rv = NS_OK;
1025 if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
1026 MOZ_LOG(
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
1038 // monkeys with it.
1039 rv = nsXULElement::CreateFromPrototype(aPrototype, doc, true, isRoot,
1040 getter_AddRefs(result));
1041 if (NS_FAILED(rv)) return rv;
1042 } else {
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
1046 // for this node.
1047 RefPtr<NodeInfo> newNodeInfo = doc->NodeInfoManager()->GetNodeInfo(
1048 aPrototype->mNodeInfo->NameAtom(),
1049 aPrototype->mNodeInfo->GetPrefixAtom(),
1050 aPrototype->mNodeInfo->NamespaceID(), nsINode::ELEMENT_NODE);
1051 if (!newNodeInfo) {
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);
1061 } else {
1062 rv = NS_NewElement(getter_AddRefs(result), newNodeInfo.forget(),
1063 NOT_FROM_PARSER);
1065 if (NS_FAILED(rv)) return rv;
1067 rv = AddAttributes(aPrototype, result);
1068 if (NS_FAILED(rv)) return rv;
1070 if (isScript) {
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);
1095 return NS_OK;
1098 nsresult PrototypeDocumentContentSink::AddAttributes(
1099 nsXULPrototypeElement* aPrototype, Element* aElement) {
1100 nsresult rv;
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;
1113 return NS_OK;
1116 } // namespace mozilla::dom