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