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/. */
6 
7 #include "nsCOMPtr.h"
8 #include "mozilla/dom/PrototypeDocumentContentSink.h"
9 #include "nsIParser.h"
10 #include "mozilla/dom/Document.h"
11 #include "nsIContent.h"
12 #include "nsIURI.h"
13 #include "nsNetUtil.h"
14 #include "nsHTMLParts.h"
15 #include "nsCRT.h"
16 #include "mozilla/StyleSheetInlines.h"
17 #include "mozilla/css/Loader.h"
18 #include "nsGkAtoms.h"
19 #include "nsContentUtils.h"
20 #include "nsDocElementCreatedNotificationRunner.h"
21 #include "nsIScriptContext.h"
22 #include "nsNameSpaceManager.h"
23 #include "nsIScriptError.h"
24 #include "prtime.h"
25 #include "mozilla/Logging.h"
26 #include "nsRect.h"
27 #include "nsIScriptElement.h"
28 #include "nsReadableUtils.h"
29 #include "nsUnicharUtils.h"
30 #include "nsIChannel.h"
31 #include "nsNodeInfoManager.h"
32 #include "nsContentCreatorFunctions.h"
33 #include "nsIContentPolicy.h"
34 #include "nsContentPolicyUtils.h"
35 #include "nsError.h"
36 #include "nsIScriptGlobalObject.h"
37 #include "mozAutoDocUpdate.h"
38 #include "nsMimeTypes.h"
39 #include "nsHtml5SVGLoadDispatcher.h"
40 #include "nsTextNode.h"
41 #include "mozilla/dom/AutoEntryScript.h"
42 #include "mozilla/dom/CDATASection.h"
43 #include "mozilla/dom/Comment.h"
44 #include "mozilla/dom/DocumentType.h"
45 #include "mozilla/dom/Element.h"
46 #include "mozilla/dom/HTMLTemplateElement.h"
47 #include "mozilla/dom/ProcessingInstruction.h"
48 #include "mozilla/dom/XMLStylesheetProcessingInstruction.h"
49 #include "mozilla/dom/ScriptLoader.h"
50 #include "mozilla/LoadInfo.h"
51 #include "mozilla/PresShell.h"
52 #include "mozilla/ProfilerLabels.h"
53 #include "mozilla/RefPtr.h"
54 
55 #include "nsXULPrototypeCache.h"
56 #include "nsXULElement.h"
57 #include "mozilla/CycleCollectedJSContext.h"
58 #include "js/CompilationAndEvaluation.h"
59 #include "js/experimental/JSStencil.h"
60 
61 using namespace mozilla;
62 using namespace mozilla::dom;
63 
64 LazyLogModule PrototypeDocumentContentSink::gLog("PrototypeDocument");
65 
NS_NewPrototypeDocumentContentSink(nsIContentSink ** aResult,Document * aDoc,nsIURI * aURI,nsISupports * aContainer,nsIChannel * aChannel)66 nsresult NS_NewPrototypeDocumentContentSink(nsIContentSink** aResult,
67                                             Document* aDoc, nsIURI* aURI,
68                                             nsISupports* aContainer,
69                                             nsIChannel* aChannel) {
70   MOZ_ASSERT(nullptr != aResult, "null ptr");
71   if (nullptr == aResult) {
72     return NS_ERROR_NULL_POINTER;
73   }
74   RefPtr<PrototypeDocumentContentSink> it = new PrototypeDocumentContentSink();
75 
76   nsresult rv = it->Init(aDoc, aURI, aContainer, aChannel);
77   NS_ENSURE_SUCCESS(rv, rv);
78 
79   it.forget(aResult);
80   return NS_OK;
81 }
82 
83 namespace mozilla::dom {
84 
PrototypeDocumentContentSink()85 PrototypeDocumentContentSink::PrototypeDocumentContentSink()
86     : mNextSrcLoadWaiter(nullptr),
87       mCurrentScriptProto(nullptr),
88       mOffThreadCompiling(false),
89       mOffThreadCompileStringBuf(nullptr),
90       mOffThreadCompileStringLength(0),
91       mStillWalking(false),
92       mPendingSheets(0) {}
93 
~PrototypeDocumentContentSink()94 PrototypeDocumentContentSink::~PrototypeDocumentContentSink() {
95   NS_ASSERTION(
96       mNextSrcLoadWaiter == nullptr,
97       "unreferenced document still waiting for script source to load?");
98 
99   if (mOffThreadCompileStringBuf) {
100     js_free(mOffThreadCompileStringBuf);
101   }
102 }
103 
Init(Document * aDoc,nsIURI * aURI,nsISupports * aContainer,nsIChannel * aChannel)104 nsresult PrototypeDocumentContentSink::Init(Document* aDoc, nsIURI* aURI,
105                                             nsISupports* aContainer,
106                                             nsIChannel* aChannel) {
107   MOZ_ASSERT(aDoc, "null ptr");
108   MOZ_ASSERT(aURI, "null ptr");
109 
110   mDocument = aDoc;
111 
112   mDocument->SetDelayFrameLoaderInitialization(true);
113   mDocument->SetMayStartLayout(false);
114 
115   // Get the URI.  this should match the uri used for the OnNewURI call in
116   // nsDocShell::CreateContentViewer.
117   nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(mDocumentURI));
118   NS_ENSURE_SUCCESS(rv, rv);
119 
120   mScriptLoader = mDocument->ScriptLoader();
121 
122   return NS_OK;
123 }
124 
NS_IMPL_CYCLE_COLLECTION(PrototypeDocumentContentSink,mParser,mDocumentURI,mDocument,mScriptLoader,mContextStack,mCurrentPrototype)125 NS_IMPL_CYCLE_COLLECTION(PrototypeDocumentContentSink, mParser, mDocumentURI,
126                          mDocument, mScriptLoader, mContextStack,
127                          mCurrentPrototype)
128 
129 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PrototypeDocumentContentSink)
130   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentSink)
131   NS_INTERFACE_MAP_ENTRY(nsIContentSink)
132   NS_INTERFACE_MAP_ENTRY(nsIStreamLoaderObserver)
133   NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
134   NS_INTERFACE_MAP_ENTRY(nsIOffThreadScriptReceiver)
135 NS_INTERFACE_MAP_END
136 
137 NS_IMPL_CYCLE_COLLECTING_ADDREF(PrototypeDocumentContentSink)
138 NS_IMPL_CYCLE_COLLECTING_RELEASE(PrototypeDocumentContentSink)
139 
140 //----------------------------------------------------------------------
141 //
142 // nsIContentSink interface
143 //
144 
145 void PrototypeDocumentContentSink::SetDocumentCharset(
146     NotNull<const Encoding*> aEncoding) {
147   if (mDocument) {
148     mDocument->SetDocumentCharacterSet(aEncoding);
149   }
150 }
151 
GetTarget()152 nsISupports* PrototypeDocumentContentSink::GetTarget() {
153   return ToSupports(mDocument);
154 }
155 
IsScriptExecuting()156 bool PrototypeDocumentContentSink::IsScriptExecuting() {
157   return !!mScriptLoader->GetCurrentScript();
158 }
159 
160 NS_IMETHODIMP
SetParser(nsParserBase * aParser)161 PrototypeDocumentContentSink::SetParser(nsParserBase* aParser) {
162   MOZ_ASSERT(aParser, "Should have a parser here!");
163   mParser = aParser;
164   return NS_OK;
165 }
166 
GetParser()167 nsIParser* PrototypeDocumentContentSink::GetParser() {
168   return static_cast<nsIParser*>(mParser.get());
169 }
170 
ContinueInterruptedParsingIfEnabled()171 void PrototypeDocumentContentSink::ContinueInterruptedParsingIfEnabled() {
172   if (mParser && mParser->IsParserEnabled()) {
173     GetParser()->ContinueInterruptedParsing();
174   }
175 }
176 
ContinueInterruptedParsingAsync()177 void PrototypeDocumentContentSink::ContinueInterruptedParsingAsync() {
178   nsCOMPtr<nsIRunnable> ev = NewRunnableMethod(
179       "PrototypeDocumentContentSink::ContinueInterruptedParsingIfEnabled", this,
180       &PrototypeDocumentContentSink::ContinueInterruptedParsingIfEnabled);
181 
182   mDocument->Dispatch(mozilla::TaskCategory::Other, ev.forget());
183 }
184 
185 //----------------------------------------------------------------------
186 //
187 // PrototypeDocumentContentSink::ContextStack
188 //
189 
ContextStack()190 PrototypeDocumentContentSink::ContextStack::ContextStack()
191     : mTop(nullptr), mDepth(0) {}
192 
~ContextStack()193 PrototypeDocumentContentSink::ContextStack::~ContextStack() { Clear(); }
194 
Traverse(nsCycleCollectionTraversalCallback & aCallback,const char * aName,uint32_t aFlags)195 void PrototypeDocumentContentSink::ContextStack::Traverse(
196     nsCycleCollectionTraversalCallback& aCallback, const char* aName,
197     uint32_t aFlags) {
198   aFlags |= CycleCollectionEdgeNameArrayFlag;
199   Entry* current = mTop;
200   while (current) {
201     CycleCollectionNoteChild(aCallback, current->mElement, aName, aFlags);
202     current = current->mNext;
203   }
204 }
205 
Clear()206 void PrototypeDocumentContentSink::ContextStack::Clear() {
207   while (mTop) {
208     Entry* doomed = mTop;
209     mTop = mTop->mNext;
210     NS_IF_RELEASE(doomed->mElement);
211     delete doomed;
212   }
213   mDepth = 0;
214 }
215 
Push(nsXULPrototypeElement * aPrototype,nsIContent * aElement)216 nsresult PrototypeDocumentContentSink::ContextStack::Push(
217     nsXULPrototypeElement* aPrototype, nsIContent* aElement) {
218   Entry* entry = new Entry;
219   entry->mPrototype = aPrototype;
220   entry->mElement = aElement;
221   NS_IF_ADDREF(entry->mElement);
222   entry->mIndex = 0;
223 
224   entry->mNext = mTop;
225   mTop = entry;
226 
227   ++mDepth;
228   return NS_OK;
229 }
230 
Pop()231 nsresult PrototypeDocumentContentSink::ContextStack::Pop() {
232   if (mDepth == 0) return NS_ERROR_UNEXPECTED;
233 
234   Entry* doomed = mTop;
235   mTop = mTop->mNext;
236   --mDepth;
237 
238   NS_IF_RELEASE(doomed->mElement);
239   delete doomed;
240   return NS_OK;
241 }
242 
Peek(nsXULPrototypeElement ** aPrototype,nsIContent ** aElement,int32_t * aIndex)243 nsresult PrototypeDocumentContentSink::ContextStack::Peek(
244     nsXULPrototypeElement** aPrototype, nsIContent** aElement,
245     int32_t* aIndex) {
246   if (mDepth == 0) return NS_ERROR_UNEXPECTED;
247 
248   *aPrototype = mTop->mPrototype;
249   *aElement = mTop->mElement;
250   NS_IF_ADDREF(*aElement);
251   *aIndex = mTop->mIndex;
252 
253   return NS_OK;
254 }
255 
SetTopIndex(int32_t aIndex)256 nsresult PrototypeDocumentContentSink::ContextStack::SetTopIndex(
257     int32_t aIndex) {
258   if (mDepth == 0) return NS_ERROR_UNEXPECTED;
259 
260   mTop->mIndex = aIndex;
261   return NS_OK;
262 }
263 
264 //----------------------------------------------------------------------
265 //
266 // Content model walking routines
267 //
268 
OnPrototypeLoadDone(nsXULPrototypeDocument * aPrototype)269 nsresult PrototypeDocumentContentSink::OnPrototypeLoadDone(
270     nsXULPrototypeDocument* aPrototype) {
271   mCurrentPrototype = aPrototype;
272   mDocument->SetPrototypeDocument(aPrototype);
273 
274   nsresult rv = PrepareToWalk();
275   NS_ENSURE_SUCCESS(rv, rv);
276 
277   rv = ResumeWalk();
278 
279   return rv;
280 }
281 
PrepareToWalk()282 nsresult PrototypeDocumentContentSink::PrepareToWalk() {
283   MOZ_ASSERT(mCurrentPrototype);
284   nsresult rv;
285 
286   mStillWalking = true;
287 
288   // Notify document that the load is beginning
289   mDocument->BeginLoad();
290 
291   // Get the prototype's root element and initialize the context
292   // stack for the prototype walk.
293   nsXULPrototypeElement* proto = mCurrentPrototype->GetRootElement();
294 
295   if (!proto) {
296     if (MOZ_LOG_TEST(gLog, LogLevel::Error)) {
297       nsCOMPtr<nsIURI> url = mCurrentPrototype->GetURI();
298 
299       nsAutoCString urlspec;
300       rv = url->GetSpec(urlspec);
301       if (NS_FAILED(rv)) return rv;
302 
303       MOZ_LOG(gLog, LogLevel::Error,
304               ("prototype: error parsing '%s'", urlspec.get()));
305     }
306 
307     return NS_OK;
308   }
309 
310   nsINode* nodeToInsertBefore = mDocument->GetFirstChild();
311 
312   const nsTArray<RefPtr<nsXULPrototypePI> >& processingInstructions =
313       mCurrentPrototype->GetProcessingInstructions();
314 
315   uint32_t total = processingInstructions.Length();
316   for (uint32_t i = 0; i < total; ++i) {
317     rv = CreateAndInsertPI(processingInstructions[i], mDocument,
318                            nodeToInsertBefore);
319     if (NS_FAILED(rv)) return rv;
320   }
321 
322   // Do one-time initialization.
323   RefPtr<Element> root;
324 
325   // Add the root element
326   rv = CreateElementFromPrototype(proto, getter_AddRefs(root), nullptr);
327   if (NS_FAILED(rv)) return rv;
328 
329   ErrorResult error;
330   mDocument->AppendChildTo(root, false, error);
331   if (error.Failed()) {
332     return error.StealNSResult();
333   }
334 
335   // TODO(emilio): Should this really notify? We don't notify of appends anyhow,
336   // and we just appended the root so no styles can possibly depend on it.
337   mDocument->UpdateDocumentStates(NS_DOCUMENT_STATE_RTL_LOCALE, true);
338 
339   nsContentUtils::AddScriptRunner(
340       new nsDocElementCreatedNotificationRunner(mDocument));
341 
342   // There'd better not be anything on the context stack at this
343   // point! This is the basis case for our "induction" in
344   // ResumeWalk(), below, which'll assume that there's always a
345   // content element on the context stack if we're in the document.
346   NS_ASSERTION(mContextStack.Depth() == 0,
347                "something's on the context stack already");
348   if (mContextStack.Depth() != 0) return NS_ERROR_UNEXPECTED;
349 
350   rv = mContextStack.Push(proto, root);
351   if (NS_FAILED(rv)) return rv;
352 
353   return NS_OK;
354 }
355 
CreateAndInsertPI(const nsXULPrototypePI * aProtoPI,nsINode * aParent,nsINode * aBeforeThis)356 nsresult PrototypeDocumentContentSink::CreateAndInsertPI(
357     const nsXULPrototypePI* aProtoPI, nsINode* aParent, nsINode* aBeforeThis) {
358   MOZ_ASSERT(aProtoPI, "null ptr");
359   MOZ_ASSERT(aParent, "null ptr");
360 
361   RefPtr<ProcessingInstruction> node =
362       NS_NewXMLProcessingInstruction(aParent->OwnerDoc()->NodeInfoManager(),
363                                      aProtoPI->mTarget, aProtoPI->mData);
364 
365   nsresult rv;
366   if (aProtoPI->mTarget.EqualsLiteral("xml-stylesheet")) {
367     MOZ_ASSERT(LinkStyle::FromNode(*node),
368                "XML Stylesheet node does not implement LinkStyle!");
369     auto* pi = static_cast<XMLStylesheetProcessingInstruction*>(node.get());
370     rv = InsertXMLStylesheetPI(aProtoPI, aParent, aBeforeThis, pi);
371   } else {
372     // No special processing, just add the PI to the document.
373     ErrorResult error;
374     aParent->InsertChildBefore(node->AsContent(),
375                                aBeforeThis ? aBeforeThis->AsContent() : nullptr,
376                                false, error);
377     rv = error.StealNSResult();
378   }
379 
380   return rv;
381 }
382 
InsertXMLStylesheetPI(const nsXULPrototypePI * aProtoPI,nsINode * aParent,nsINode * aBeforeThis,XMLStylesheetProcessingInstruction * aPINode)383 nsresult PrototypeDocumentContentSink::InsertXMLStylesheetPI(
384     const nsXULPrototypePI* aProtoPI, nsINode* aParent, nsINode* aBeforeThis,
385     XMLStylesheetProcessingInstruction* aPINode) {
386   // We want to be notified when the style sheet finishes loading, so
387   // disable style sheet loading for now.
388   aPINode->SetEnableUpdates(false);
389   aPINode->OverrideBaseURI(mCurrentPrototype->GetURI());
390 
391   ErrorResult rv;
392   aParent->InsertChildBefore(
393       aPINode, aBeforeThis ? aBeforeThis->AsContent() : nullptr, false, rv);
394   if (rv.Failed()) {
395     return rv.StealNSResult();
396   }
397 
398   aPINode->SetEnableUpdates(true);
399 
400   // load the stylesheet if necessary, passing ourselves as
401   // nsICSSObserver
402   auto result = aPINode->UpdateStyleSheet(this);
403   if (result.isErr()) {
404     // Ignore errors from UpdateStyleSheet; we don't want failure to
405     // do that to break the XUL document load.  But do propagate out
406     // NS_ERROR_OUT_OF_MEMORY.
407     if (result.unwrapErr() == NS_ERROR_OUT_OF_MEMORY) {
408       return result.unwrapErr();
409     }
410     return NS_OK;
411   }
412 
413   auto update = result.unwrap();
414   if (update.ShouldBlock()) {
415     ++mPendingSheets;
416   }
417 
418   return NS_OK;
419 }
420 
CloseElement(Element * aElement,bool aHadChildren)421 void PrototypeDocumentContentSink::CloseElement(Element* aElement,
422                                                 bool aHadChildren) {
423   if (nsIContent::RequiresDoneAddingChildren(
424           aElement->NodeInfo()->NamespaceID(),
425           aElement->NodeInfo()->NameAtom())) {
426     aElement->DoneAddingChildren(false);
427   }
428 
429   if (!aHadChildren) {
430     return;
431   }
432 
433   // See bug 370111 and bug 1495946. We don't cache inline styles nor module
434   // scripts in the prototype cache, and we don't notify on node insertion, so
435   // we need to do this for the stylesheet / script to be properly processed.
436   // This kinda sucks, but notifying was a pretty sizeable perf regression so...
437   if (aElement->IsHTMLElement(nsGkAtoms::script) ||
438       aElement->IsSVGElement(nsGkAtoms::script)) {
439     nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aElement);
440     MOZ_ASSERT(sele, "Node didn't QI to script.");
441     if (sele->GetScriptIsModule()) {
442       DebugOnly<bool> block = sele->AttemptToExecute();
443       MOZ_ASSERT(!block, "<script type=module> shouldn't block the parser");
444     }
445   }
446 
447   if (aElement->IsHTMLElement(nsGkAtoms::style) ||
448       aElement->IsSVGElement(nsGkAtoms::style)) {
449     auto* linkStyle = LinkStyle::FromNode(*aElement);
450     NS_ASSERTION(linkStyle,
451                  "<html:style> doesn't implement "
452                  "nsIStyleSheetLinkingElement?");
453     Unused << linkStyle->UpdateStyleSheet(nullptr);
454   }
455 }
456 
ResumeWalk()457 nsresult PrototypeDocumentContentSink::ResumeWalk() {
458   nsresult rv = ResumeWalkInternal();
459   if (NS_FAILED(rv)) {
460     nsContentUtils::ReportToConsoleNonLocalized(
461         u"Failed to load document from prototype document."_ns,
462         nsIScriptError::errorFlag, "Prototype Document"_ns, mDocument,
463         mDocumentURI);
464   }
465   return rv;
466 }
467 
ResumeWalkInternal()468 nsresult PrototypeDocumentContentSink::ResumeWalkInternal() {
469   MOZ_ASSERT(mStillWalking);
470   // Walk the prototype and build the delegate content model. The
471   // walk is performed in a top-down, left-to-right fashion. That
472   // is, a parent is built before any of its children; a node is
473   // only built after all of its siblings to the left are fully
474   // constructed.
475   //
476   // It is interruptable so that transcluded documents (e.g.,
477   // <html:script src="..." />) can be properly re-loaded if the
478   // cached copy of the document becomes stale.
479   nsresult rv;
480   nsCOMPtr<nsIURI> docURI =
481       mCurrentPrototype ? mCurrentPrototype->GetURI() : nullptr;
482 
483   while (1) {
484     // Begin (or resume) walking the current prototype.
485 
486     while (mContextStack.Depth() > 0) {
487       // Look at the top of the stack to determine what we're
488       // currently working on.
489       // This will always be a node already constructed and
490       // inserted to the actual document.
491       nsXULPrototypeElement* proto;
492       nsCOMPtr<nsIContent> element;
493       nsCOMPtr<nsIContent> nodeToPushTo;
494       int32_t indx;  // all children of proto before indx (not
495                      // inclusive) have already been constructed
496       rv = mContextStack.Peek(&proto, getter_AddRefs(element), &indx);
497       if (NS_FAILED(rv)) return rv;
498 
499       if (indx >= (int32_t)proto->mChildren.Length()) {
500         if (element) {
501           // We've processed all of the prototype's children.
502           CloseElement(element->AsElement(), /* aHadChildren = */ true);
503         }
504         // Now pop the context stack back up to the parent
505         // element and continue the prototype walk.
506         mContextStack.Pop();
507         continue;
508       }
509 
510       nodeToPushTo = element;
511       // For template elements append the content to the template's document
512       // fragment.
513       if (auto* templateElement = HTMLTemplateElement::FromNode(element)) {
514         nodeToPushTo = templateElement->Content();
515       }
516 
517       // Grab the next child, and advance the current context stack
518       // to the next sibling to our right.
519       nsXULPrototypeNode* childproto = proto->mChildren[indx];
520       mContextStack.SetTopIndex(++indx);
521 
522       switch (childproto->mType) {
523         case nsXULPrototypeNode::eType_Element: {
524           // An 'element', which may contain more content.
525           auto* protoele = static_cast<nsXULPrototypeElement*>(childproto);
526 
527           RefPtr<Element> child;
528 
529           rv = CreateElementFromPrototype(protoele, getter_AddRefs(child),
530                                           nodeToPushTo);
531           if (NS_FAILED(rv)) return rv;
532 
533           // ...and append it to the content model.
534           ErrorResult error;
535           nodeToPushTo->AppendChildTo(child, false, error);
536           if (error.Failed()) {
537             return error.StealNSResult();
538           }
539 
540           if (nsIContent::RequiresDoneCreatingElement(
541                   protoele->mNodeInfo->NamespaceID(),
542                   protoele->mNodeInfo->NameAtom())) {
543             child->DoneCreatingElement();
544           }
545 
546           // If it has children, push the element onto the context
547           // stack and begin to process them.
548           if (protoele->mChildren.Length() > 0) {
549             rv = mContextStack.Push(protoele, child);
550             if (NS_FAILED(rv)) return rv;
551           } else {
552             // If there are no children, close the element immediately.
553             CloseElement(child, /* aHadChildren = */ false);
554           }
555         } break;
556 
557         case nsXULPrototypeNode::eType_Script: {
558           // A script reference. Execute the script immediately;
559           // this may have side effects in the content model.
560           auto* scriptproto = static_cast<nsXULPrototypeScript*>(childproto);
561           if (scriptproto->mSrcURI) {
562             // A transcluded script reference; this may
563             // "block" our prototype walk if the script isn't
564             // cached, or the cached copy of the script is
565             // stale and must be reloaded.
566             bool blocked;
567             rv = LoadScript(scriptproto, &blocked);
568             // If the script cannot be loaded, just keep going!
569 
570             if (NS_SUCCEEDED(rv) && blocked) return NS_OK;
571           } else if (scriptproto->HasStencil()) {
572             // An inline script
573             rv = ExecuteScript(scriptproto);
574             if (NS_FAILED(rv)) return rv;
575           }
576         } break;
577 
578         case nsXULPrototypeNode::eType_Text: {
579           nsNodeInfoManager* nim = nodeToPushTo->NodeInfo()->NodeInfoManager();
580           // A simple text node.
581           RefPtr<nsTextNode> text = new (nim) nsTextNode(nim);
582 
583           auto* textproto = static_cast<nsXULPrototypeText*>(childproto);
584           text->SetText(textproto->mValue, false);
585 
586           ErrorResult error;
587           nodeToPushTo->AppendChildTo(text, false, error);
588           if (error.Failed()) {
589             return error.StealNSResult();
590           }
591         } break;
592 
593         case nsXULPrototypeNode::eType_PI: {
594           auto* piProto = static_cast<nsXULPrototypePI*>(childproto);
595 
596           // <?xml-stylesheet?> doesn't have an effect
597           // outside the prolog, like it used to. Issue a warning.
598 
599           if (piProto->mTarget.EqualsLiteral("xml-stylesheet")) {
600             AutoTArray<nsString, 1> params = {piProto->mTarget};
601 
602             nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
603                                             "XUL Document"_ns, nullptr,
604                                             nsContentUtils::eXUL_PROPERTIES,
605                                             "PINotInProlog", params, docURI);
606           }
607 
608           nsIContent* parent = element.get();
609           if (parent) {
610             // an inline script could have removed the root element
611             rv = CreateAndInsertPI(piProto, parent, nullptr);
612             NS_ENSURE_SUCCESS(rv, rv);
613           }
614         } break;
615 
616         default:
617           MOZ_ASSERT_UNREACHABLE("Unexpected nsXULPrototypeNode::Type");
618       }
619     }
620 
621     // Once we get here, the context stack will have been
622     // depleted. That means that the entire prototype has been
623     // walked and content has been constructed.
624     break;
625   }
626 
627   mStillWalking = false;
628   return MaybeDoneWalking();
629 }
630 
InitialTranslationCompleted()631 void PrototypeDocumentContentSink::InitialTranslationCompleted() {
632   MaybeDoneWalking();
633 }
634 
MaybeDoneWalking()635 nsresult PrototypeDocumentContentSink::MaybeDoneWalking() {
636   if (mPendingSheets > 0 || mStillWalking) {
637     return NS_OK;
638   }
639 
640   if (mDocument->HasPendingInitialTranslation()) {
641     mDocument->OnParsingCompleted();
642     return NS_OK;
643   }
644 
645   return DoneWalking();
646 }
647 
DoneWalking()648 nsresult PrototypeDocumentContentSink::DoneWalking() {
649   MOZ_ASSERT(mPendingSheets == 0, "there are sheets to be loaded");
650   MOZ_ASSERT(!mStillWalking, "walk not done");
651   MOZ_ASSERT(!mDocument->HasPendingInitialTranslation(), "translation pending");
652 
653   if (mDocument) {
654     MOZ_ASSERT(mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING,
655                "Bad readyState");
656     mDocument->SetReadyStateInternal(Document::READYSTATE_INTERACTIVE);
657     mDocument->NotifyPossibleTitleChange(false);
658 
659     nsContentUtils::DispatchEventOnlyToChrome(mDocument, ToSupports(mDocument),
660                                               u"MozBeforeInitialXULLayout"_ns,
661                                               CanBubble::eYes, Cancelable::eNo);
662   }
663 
664   if (mScriptLoader) {
665     mScriptLoader->ParsingComplete(false);
666     mScriptLoader->DeferCheckpointReached();
667   }
668 
669   StartLayout();
670 
671   if (IsChromeURI(mDocumentURI) &&
672       nsXULPrototypeCache::GetInstance()->IsEnabled()) {
673     bool isCachedOnDisk;
674     nsXULPrototypeCache::GetInstance()->HasData(mDocumentURI, &isCachedOnDisk);
675     if (!isCachedOnDisk) {
676       nsXULPrototypeCache::GetInstance()->WritePrototype(mCurrentPrototype);
677     }
678   }
679 
680   mDocument->SetDelayFrameLoaderInitialization(false);
681   mDocument->MaybeInitializeFinalizeFrameLoaders();
682 
683   // If the document we are loading has a reference or it is a
684   // frameset document, disable the scroll bars on the views.
685 
686   mDocument->SetScrollToRef(mDocument->GetDocumentURI());
687 
688   mDocument->EndLoad();
689 
690   return NS_OK;
691 }
692 
StartLayout()693 void PrototypeDocumentContentSink::StartLayout() {
694   AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
695       "PrototypeDocumentContentSink::StartLayout", LAYOUT,
696       mDocumentURI->GetSpecOrDefault());
697   mDocument->SetMayStartLayout(true);
698   RefPtr<PresShell> presShell = mDocument->GetPresShell();
699   if (presShell && !presShell->DidInitialize()) {
700     nsresult rv = presShell->Initialize();
701     if (NS_FAILED(rv)) {
702       return;
703     }
704   }
705 }
706 
707 NS_IMETHODIMP
StyleSheetLoaded(StyleSheet * aSheet,bool aWasDeferred,nsresult aStatus)708 PrototypeDocumentContentSink::StyleSheetLoaded(StyleSheet* aSheet,
709                                                bool aWasDeferred,
710                                                nsresult aStatus) {
711   if (!aWasDeferred) {
712     // Don't care about when alternate sheets finish loading
713     MOZ_ASSERT(mPendingSheets > 0, "Unexpected StyleSheetLoaded notification");
714 
715     --mPendingSheets;
716 
717     return MaybeDoneWalking();
718   }
719 
720   return NS_OK;
721 }
722 
LoadScript(nsXULPrototypeScript * aScriptProto,bool * aBlock)723 nsresult PrototypeDocumentContentSink::LoadScript(
724     nsXULPrototypeScript* aScriptProto, bool* aBlock) {
725   // Load a transcluded script
726   nsresult rv;
727 
728   bool isChromeDoc = IsChromeURI(mDocumentURI);
729 
730   if (isChromeDoc && aScriptProto->HasStencil()) {
731     rv = ExecuteScript(aScriptProto);
732 
733     // Ignore return value from execution, and don't block
734     *aBlock = false;
735     return NS_OK;
736   }
737 
738   // Try the XUL script cache, in case two XUL documents source the same
739   // .js file (e.g., strres.js from navigator.xul and utilityOverlay.xul).
740   // XXXbe the cache relies on aScriptProto's GC root!
741   bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
742 
743   if (isChromeDoc && useXULCache) {
744     RefPtr<JS::Stencil> newStencil =
745         nsXULPrototypeCache::GetInstance()->GetStencil(aScriptProto->mSrcURI);
746     if (newStencil) {
747       // The script language for a proto must remain constant - we
748       // can't just change it for this unexpected language.
749       aScriptProto->Set(newStencil);
750     }
751 
752     if (aScriptProto->HasStencil()) {
753       rv = ExecuteScript(aScriptProto);
754 
755       // Ignore return value from execution, and don't block
756       *aBlock = false;
757       return NS_OK;
758     }
759   }
760 
761   // Release stencil from FastLoad since we decided against using them
762   aScriptProto->Set(nullptr);
763 
764   // Set the current script prototype so that OnStreamComplete can report
765   // the right file if there are errors in the script.
766   NS_ASSERTION(!mCurrentScriptProto,
767                "still loading a script when starting another load?");
768   mCurrentScriptProto = aScriptProto;
769 
770   if (isChromeDoc && aScriptProto->mSrcLoading) {
771     // Another document load has started, which is still in progress.
772     // Remember to ResumeWalk this document when the load completes.
773     mNextSrcLoadWaiter = aScriptProto->mSrcLoadWaiters;
774     aScriptProto->mSrcLoadWaiters = this;
775     NS_ADDREF_THIS();
776   } else {
777     nsCOMPtr<nsILoadGroup> group =
778         mDocument
779             ->GetDocumentLoadGroup();  // found in
780                                        // mozilla::dom::Document::SetScriptGlobalObject
781 
782     // Note: the loader will keep itself alive while it's loading.
783     nsCOMPtr<nsIStreamLoader> loader;
784     rv = NS_NewStreamLoader(
785         getter_AddRefs(loader), aScriptProto->mSrcURI,
786         this,       // aObserver
787         mDocument,  // aRequestingContext
788         nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
789         nsIContentPolicy::TYPE_INTERNAL_SCRIPT, group);
790 
791     if (NS_FAILED(rv)) {
792       mCurrentScriptProto = nullptr;
793       return rv;
794     }
795 
796     aScriptProto->mSrcLoading = true;
797   }
798 
799   // Block until OnStreamComplete resumes us.
800   *aBlock = true;
801   return NS_OK;
802 }
803 
804 NS_IMETHODIMP
OnStreamComplete(nsIStreamLoader * aLoader,nsISupports * context,nsresult aStatus,uint32_t stringLen,const uint8_t * string)805 PrototypeDocumentContentSink::OnStreamComplete(nsIStreamLoader* aLoader,
806                                                nsISupports* context,
807                                                nsresult aStatus,
808                                                uint32_t stringLen,
809                                                const uint8_t* string) {
810   nsCOMPtr<nsIRequest> request;
811   aLoader->GetRequest(getter_AddRefs(request));
812   nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
813 
814 #ifdef DEBUG
815   // print a load error on bad status
816   if (NS_FAILED(aStatus)) {
817     if (channel) {
818       nsCOMPtr<nsIURI> uri;
819       channel->GetURI(getter_AddRefs(uri));
820       if (uri) {
821         printf("Failed to load %s\n", uri->GetSpecOrDefault().get());
822       }
823     }
824   }
825 #endif
826 
827   // This is the completion routine that will be called when a
828   // transcluded script completes. Compile and execute the script
829   // if the load was successful, then continue building content
830   // from the prototype.
831   nsresult rv = aStatus;
832 
833   NS_ASSERTION(mCurrentScriptProto && mCurrentScriptProto->mSrcLoading,
834                "script source not loading on unichar stream complete?");
835   if (!mCurrentScriptProto) {
836     // XXX Wallpaper for bug 270042
837     return NS_OK;
838   }
839 
840   if (NS_SUCCEEDED(aStatus)) {
841     // If the including document is a FastLoad document, and we're
842     // compiling an out-of-line script (one with src=...), then we must
843     // be writing a new FastLoad file.  If we were reading this script
844     // from the FastLoad file, XULContentSinkImpl::OpenScript (over in
845     // nsXULContentSink.cpp) would have already deserialized a non-null
846     // script->mStencil, causing control flow at the top of LoadScript
847     // not to reach here.
848     nsCOMPtr<nsIURI> uri = mCurrentScriptProto->mSrcURI;
849 
850     // XXX should also check nsIHttpChannel::requestSucceeded
851 
852     MOZ_ASSERT(!mOffThreadCompiling && (mOffThreadCompileStringLength == 0 &&
853                                         !mOffThreadCompileStringBuf),
854                "PrototypeDocument can't load multiple scripts at once");
855 
856     rv = ScriptLoader::ConvertToUTF16(channel, string, stringLen, u""_ns,
857                                       mDocument, mOffThreadCompileStringBuf,
858                                       mOffThreadCompileStringLength);
859     if (NS_SUCCEEDED(rv)) {
860       // Pass ownership of the buffer, carefully emptying the existing
861       // fields in the process.  Note that the |Compile| function called
862       // below always takes ownership of the buffer.
863       char16_t* units = nullptr;
864       size_t unitsLength = 0;
865 
866       std::swap(units, mOffThreadCompileStringBuf);
867       std::swap(unitsLength, mOffThreadCompileStringLength);
868 
869       rv = mCurrentScriptProto->Compile(units, unitsLength,
870                                         JS::SourceOwnership::TakeOwnership, uri,
871                                         1, mDocument, this);
872       if (NS_SUCCEEDED(rv) && !mCurrentScriptProto->HasStencil()) {
873         mOffThreadCompiling = true;
874         mDocument->BlockOnload();
875         return NS_OK;
876       }
877     }
878   }
879 
880   return OnScriptCompileComplete(mCurrentScriptProto->GetStencil(), rv);
881 }
882 
883 NS_IMETHODIMP
OnScriptCompileComplete(JS::Stencil * aStencil,nsresult aStatus)884 PrototypeDocumentContentSink::OnScriptCompileComplete(JS::Stencil* aStencil,
885                                                       nsresult aStatus) {
886   // The mCurrentScriptProto may have been cleared out by another
887   // PrototypeDocumentContentSink.
888   if (!mCurrentScriptProto) {
889     return NS_OK;
890   }
891 
892   // When compiling off thread the script will not have been attached to the
893   // script proto yet.
894   if (aStencil && !mCurrentScriptProto->HasStencil()) {
895     mCurrentScriptProto->Set(aStencil);
896   }
897 
898   // Allow load events to be fired once off thread compilation finishes.
899   if (mOffThreadCompiling) {
900     mOffThreadCompiling = false;
901     mDocument->UnblockOnload(false);
902   }
903 
904   // After compilation finishes the script's characters are no longer needed.
905   if (mOffThreadCompileStringBuf) {
906     js_free(mOffThreadCompileStringBuf);
907     mOffThreadCompileStringBuf = nullptr;
908     mOffThreadCompileStringLength = 0;
909   }
910 
911   // Clear mCurrentScriptProto now, but save it first for use below in
912   // the execute code, and in the while loop that resumes walks of other
913   // documents that raced to load this script.
914   nsXULPrototypeScript* scriptProto = mCurrentScriptProto;
915   mCurrentScriptProto = nullptr;
916 
917   // Clear the prototype's loading flag before executing the script or
918   // resuming document walks, in case any of those control flows starts a
919   // new script load.
920   scriptProto->mSrcLoading = false;
921 
922   nsresult rv = aStatus;
923   if (NS_SUCCEEDED(rv)) {
924     rv = ExecuteScript(scriptProto);
925 
926     // If the XUL cache is enabled, save the script object there in
927     // case different XUL documents source the same script.
928     //
929     // But don't save the script in the cache unless the master XUL
930     // document URL is a chrome: URL.  It is valid for a URL such as
931     // about:config to translate into a master document URL, whose
932     // prototype document nodes -- including prototype scripts that
933     // hold GC roots protecting their mJSObject pointers -- are not
934     // cached in the XUL prototype cache.  See StartDocumentLoad,
935     // the fillXULCache logic.
936     //
937     // A document such as about:config is free to load a script via
938     // a URL such as chrome://global/content/config.js, and we must
939     // not cache that script object without a prototype cache entry
940     // containing a companion nsXULPrototypeScript node that owns a
941     // GC root protecting the script object.  Otherwise, the script
942     // cache entry will dangle once the uncached prototype document
943     // is released when its owning document is unloaded.
944     //
945     // (See http://bugzilla.mozilla.org/show_bug.cgi?id=98207 for
946     // the true crime story.)
947     bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
948 
949     if (useXULCache && IsChromeURI(mDocumentURI) && scriptProto->HasStencil()) {
950       nsXULPrototypeCache::GetInstance()->PutStencil(scriptProto->mSrcURI,
951                                                      scriptProto->GetStencil());
952     }
953     // ignore any evaluation errors
954   }
955 
956   rv = ResumeWalk();
957 
958   // Load a pointer to the prototype-script's list of documents who
959   // raced to load the same script
960   PrototypeDocumentContentSink** docp = &scriptProto->mSrcLoadWaiters;
961 
962   // Resume walking other documents that waited for this one's load, first
963   // executing the script we just compiled, in each doc's script context
964   PrototypeDocumentContentSink* doc;
965   while ((doc = *docp) != nullptr) {
966     NS_ASSERTION(doc->mCurrentScriptProto == scriptProto,
967                  "waiting for wrong script to load?");
968     doc->mCurrentScriptProto = nullptr;
969 
970     // Unlink doc from scriptProto's list before executing and resuming
971     *docp = doc->mNextSrcLoadWaiter;
972     doc->mNextSrcLoadWaiter = nullptr;
973 
974     if (aStatus == NS_BINDING_ABORTED && !scriptProto->HasStencil()) {
975       // If the previous doc load was aborted, we want to try loading
976       // again for the next doc. Otherwise, one abort would lead to all
977       // subsequent waiting docs to abort as well.
978       bool block = false;
979       doc->LoadScript(scriptProto, &block);
980       NS_RELEASE(doc);
981       return rv;
982     }
983 
984     // Execute only if we loaded and compiled successfully, then resume
985     if (NS_SUCCEEDED(aStatus) && scriptProto->HasStencil()) {
986       doc->ExecuteScript(scriptProto);
987     }
988     doc->ResumeWalk();
989     NS_RELEASE(doc);
990   }
991 
992   return rv;
993 }
994 
ExecuteScript(nsXULPrototypeScript * aScript)995 nsresult PrototypeDocumentContentSink::ExecuteScript(
996     nsXULPrototypeScript* aScript) {
997   MOZ_ASSERT(aScript != nullptr, "null ptr");
998   NS_ENSURE_TRUE(aScript, NS_ERROR_NULL_POINTER);
999 
1000   nsIScriptGlobalObject* scriptGlobalObject;
1001   bool aHasHadScriptHandlingObject;
1002   scriptGlobalObject =
1003       mDocument->GetScriptHandlingObject(aHasHadScriptHandlingObject);
1004 
1005   NS_ENSURE_TRUE(scriptGlobalObject, NS_ERROR_NOT_INITIALIZED);
1006 
1007   nsresult rv;
1008   rv = scriptGlobalObject->EnsureScriptEnvironment();
1009   NS_ENSURE_SUCCESS(rv, rv);
1010 
1011   // Execute the precompiled script with the given version
1012   nsAutoMicroTask mt;
1013 
1014   // We're about to run script via JS_ExecuteScript, so we need an
1015   // AutoEntryScript. This is Gecko specific and not in any spec.
1016   AutoEntryScript aes(scriptGlobalObject, "precompiled XUL <script> element");
1017   JSContext* cx = aes.cx();
1018 
1019   JS::Rooted<JSScript*> scriptObject(cx);
1020   rv = aScript->InstantiateScript(cx, &scriptObject);
1021   NS_ENSURE_SUCCESS(rv, rv);
1022 
1023   JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
1024   NS_ENSURE_TRUE(xpc::Scriptability::Get(global).Allowed(), NS_OK);
1025 
1026   // On failure, ~AutoScriptEntry will handle exceptions, so
1027   // there is no need to manually check the return value.
1028   JS::RootedValue rval(cx);
1029   Unused << JS_ExecuteScript(cx, scriptObject, &rval);
1030 
1031   return NS_OK;
1032 }
1033 
CreateElementFromPrototype(nsXULPrototypeElement * aPrototype,Element ** aResult,nsIContent * aParent)1034 nsresult PrototypeDocumentContentSink::CreateElementFromPrototype(
1035     nsXULPrototypeElement* aPrototype, Element** aResult, nsIContent* aParent) {
1036   // Create a content model element from a prototype element.
1037   MOZ_ASSERT(aPrototype, "null ptr");
1038   if (!aPrototype) return NS_ERROR_NULL_POINTER;
1039 
1040   *aResult = nullptr;
1041   nsresult rv = NS_OK;
1042 
1043   if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
1044     MOZ_LOG(
1045         gLog, LogLevel::Debug,
1046         ("prototype: creating <%s> from prototype",
1047          NS_ConvertUTF16toUTF8(aPrototype->mNodeInfo->QualifiedName()).get()));
1048   }
1049 
1050   RefPtr<Element> result;
1051 
1052   Document* doc = aParent ? aParent->OwnerDoc() : mDocument.get();
1053   if (aPrototype->mNodeInfo->NamespaceEquals(kNameSpaceID_XUL)) {
1054     const bool isRoot = !aParent;
1055     // If it's a XUL element, it'll be lightweight until somebody
1056     // monkeys with it.
1057     rv = nsXULElement::CreateFromPrototype(aPrototype, doc, true, isRoot,
1058                                            getter_AddRefs(result));
1059     if (NS_FAILED(rv)) return rv;
1060   } else {
1061     // If it's not a XUL element, it's gonna be heavyweight no matter
1062     // what. So we need to copy everything out of the prototype
1063     // into the element.  Get a nodeinfo from our nodeinfo manager
1064     // for this node.
1065     RefPtr<NodeInfo> newNodeInfo = doc->NodeInfoManager()->GetNodeInfo(
1066         aPrototype->mNodeInfo->NameAtom(),
1067         aPrototype->mNodeInfo->GetPrefixAtom(),
1068         aPrototype->mNodeInfo->NamespaceID(), nsINode::ELEMENT_NODE);
1069     if (!newNodeInfo) {
1070       return NS_ERROR_OUT_OF_MEMORY;
1071     }
1072     const bool isScript =
1073         newNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_XHTML) ||
1074         newNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_SVG);
1075     if (aPrototype->mIsAtom &&
1076         newNodeInfo->NamespaceID() == kNameSpaceID_XHTML) {
1077       rv = NS_NewHTMLElement(getter_AddRefs(result), newNodeInfo.forget(),
1078                              NOT_FROM_PARSER, aPrototype->mIsAtom);
1079     } else {
1080       rv = NS_NewElement(getter_AddRefs(result), newNodeInfo.forget(),
1081                          NOT_FROM_PARSER);
1082     }
1083     if (NS_FAILED(rv)) return rv;
1084 
1085     rv = AddAttributes(aPrototype, result);
1086     if (NS_FAILED(rv)) return rv;
1087 
1088     if (isScript) {
1089       nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(result);
1090       MOZ_ASSERT(sele, "Node didn't QI to script.");
1091 
1092       sele->FreezeExecutionAttrs(doc);
1093       // Script loading is handled by the this content sink, so prevent the
1094       // script from loading when it is bound to the document.
1095       //
1096       // NOTE(emilio): This is only done for non-module scripts, because we
1097       // don't support caching modules properly yet, see the comment in
1098       // XULContentSinkImpl::OpenScript. For non-inline scripts, this is enough,
1099       // since we can start the load when the node is inserted. Non-inline
1100       // scripts need another special-case in CloseElement.
1101       if (!sele->GetScriptIsModule()) {
1102         sele->PreventExecution();
1103       }
1104     }
1105   }
1106 
1107   // FIXME(bug 1627474): Is this right if this is inside an <html:template>?
1108   if (result->HasAttr(kNameSpaceID_None, nsGkAtoms::datal10nid)) {
1109     mDocument->mL10nProtoElements.InsertOrUpdate(result, RefPtr{aPrototype});
1110     result->SetElementCreatedFromPrototypeAndHasUnmodifiedL10n();
1111   }
1112   result.forget(aResult);
1113   return NS_OK;
1114 }
1115 
AddAttributes(nsXULPrototypeElement * aPrototype,Element * aElement)1116 nsresult PrototypeDocumentContentSink::AddAttributes(
1117     nsXULPrototypeElement* aPrototype, Element* aElement) {
1118   nsresult rv;
1119 
1120   for (size_t i = 0; i < aPrototype->mAttributes.Length(); ++i) {
1121     nsXULPrototypeAttribute* protoattr = &(aPrototype->mAttributes[i]);
1122     nsAutoString valueStr;
1123     protoattr->mValue.ToString(valueStr);
1124 
1125     rv = aElement->SetAttr(protoattr->mName.NamespaceID(),
1126                            protoattr->mName.LocalName(),
1127                            protoattr->mName.GetPrefix(), valueStr, false);
1128     if (NS_FAILED(rv)) return rv;
1129   }
1130 
1131   return NS_OK;
1132 }
1133 
1134 }  // namespace mozilla::dom
1135