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