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