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