nsXULElement.cpp (68338B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "nsXULElement.h" 7 8 #include <new> 9 #include <utility> 10 11 #include "AttrArray.h" 12 #include "MainThreadUtils.h" 13 #include "ReferrerInfo.h" 14 #include "Units.h" 15 #include "XULButtonElement.h" 16 #include "XULFrameElement.h" 17 #include "XULMenuBarElement.h" 18 #include "XULMenuElement.h" 19 #include "XULPopupElement.h" 20 #include "XULResizerElement.h" 21 #include "XULTextElement.h" 22 #include "XULTooltipElement.h" 23 #include "XULTreeElement.h" 24 #include "js/CompilationAndEvaluation.h" 25 #include "js/CompileOptions.h" // JS::CompileOptions, JS::OwningCompileOptions, , JS::ReadOnlyCompileOptions, JS::ReadOnlyDecodeOptions, JS::DecodeOptions 26 #include "js/SourceText.h" 27 #include "js/Transcoding.h" 28 #include "js/Utility.h" 29 #include "js/experimental/CompileScript.h" // JS::NewFrontendContext, JS::DestroyFrontendContext, JS::SetNativeStackQuota, JS::ThreadStackQuotaForSize, JS::CompileGlobalScriptToStencil, JS::CompilationStorage 30 #include "js/experimental/JSStencil.h" // JS::Stencil, JS::FrontendContext 31 #include "jsapi.h" 32 #include "mozilla/ArrayIterator.h" 33 #include "mozilla/Assertions.h" 34 #include "mozilla/ClearOnShutdown.h" 35 #include "mozilla/DeclarationBlock.h" 36 #include "mozilla/EventDispatcher.h" 37 #include "mozilla/EventListenerManager.h" 38 #include "mozilla/EventQueue.h" 39 #include "mozilla/EventStateManager.h" 40 #include "mozilla/FlushType.h" 41 #include "mozilla/FocusModel.h" 42 #include "mozilla/GlobalKeyListener.h" 43 #include "mozilla/HoldDropJSObjects.h" 44 #include "mozilla/Maybe.h" 45 #include "mozilla/MouseEvents.h" 46 #include "mozilla/OwningNonNull.h" 47 #include "mozilla/PresShell.h" 48 #include "mozilla/RefPtr.h" 49 #include "mozilla/ScopeExit.h" 50 #include "mozilla/ShutdownPhase.h" 51 #include "mozilla/StaticAnalysisFunctions.h" 52 #include "mozilla/StaticPrefs_dom.h" 53 #include "mozilla/StaticPrefs_javascript.h" 54 #include "mozilla/StaticPtr.h" 55 #include "mozilla/TaskController.h" 56 #include "mozilla/URLExtraData.h" 57 #include "mozilla/UniquePtr.h" 58 #include "mozilla/dom/BindContext.h" 59 #include "mozilla/dom/BorrowedAttrInfo.h" 60 #include "mozilla/dom/CSSRuleBinding.h" 61 #include "mozilla/dom/Document.h" 62 #include "mozilla/dom/DocumentInlines.h" 63 #include "mozilla/dom/Element.h" 64 #include "mozilla/dom/Event.h" 65 #include "mozilla/dom/EventTarget.h" 66 #include "mozilla/dom/FragmentOrElement.h" 67 #include "mozilla/dom/FromParser.h" 68 #include "mozilla/dom/MouseEventBinding.h" 69 #include "mozilla/dom/NodeInfo.h" 70 #include "mozilla/dom/ReferrerPolicyBinding.h" 71 #include "mozilla/dom/ScriptSettings.h" 72 #include "mozilla/dom/XULBroadcastManager.h" 73 #include "mozilla/dom/XULCommandEvent.h" 74 #include "mozilla/dom/XULElementBinding.h" 75 #include "mozilla/dom/nsCSPUtils.h" 76 #include "mozilla/fallible.h" 77 #include "nsAtom.h" 78 #include "nsAttrValueInlines.h" 79 #include "nsCOMPtr.h" 80 #include "nsCaseTreatment.h" 81 #include "nsChangeHint.h" 82 #include "nsCompatibility.h" 83 #include "nsContentCreatorFunctions.h" 84 #include "nsContentUtils.h" 85 #include "nsCycleCollectionNoteChild.h" 86 #include "nsCycleCollectionTraversalCallback.h" 87 #include "nsDebug.h" 88 #include "nsError.h" 89 #include "nsFocusManager.h" 90 #include "nsGkAtoms.h" 91 #include "nsIContent.h" 92 #include "nsIContentSecurityPolicy.h" 93 #include "nsIControllers.h" 94 #include "nsID.h" 95 #include "nsIDOMEventListener.h" 96 #include "nsIDOMXULControlElement.h" 97 #include "nsIDOMXULSelectCntrlItemEl.h" 98 #include "nsIDocShell.h" 99 #include "nsIFocusManager.h" 100 #include "nsIFrame.h" 101 #include "nsIObjectInputStream.h" 102 #include "nsIObjectOutputStream.h" 103 #include "nsIRunnable.h" 104 #include "nsIScriptContext.h" 105 #include "nsISupportsUtils.h" 106 #include "nsIURI.h" 107 #include "nsIXPConnect.h" 108 #include "nsMenuPopupFrame.h" 109 #include "nsNodeInfoManager.h" 110 #include "nsPIDOMWindow.h" 111 #include "nsPIDOMWindowInlines.h" 112 #include "nsPresContext.h" 113 #include "nsQueryFrame.h" 114 #include "nsString.h" 115 #include "nsStyledElement.h" 116 #include "nsThreadUtils.h" 117 #include "nsXULControllers.h" 118 #include "nsXULPopupListener.h" 119 #include "nsXULPopupManager.h" 120 #include "nsXULPrototypeCache.h" 121 #include "nsXULTooltipListener.h" 122 #include "xpcpublic.h" 123 124 using namespace mozilla; 125 using namespace mozilla::dom; 126 127 #ifdef XUL_PROTOTYPE_ATTRIBUTE_METERING 128 uint32_t nsXULPrototypeAttribute::gNumElements; 129 uint32_t nsXULPrototypeAttribute::gNumAttributes; 130 uint32_t nsXULPrototypeAttribute::gNumCacheTests; 131 uint32_t nsXULPrototypeAttribute::gNumCacheHits; 132 uint32_t nsXULPrototypeAttribute::gNumCacheSets; 133 uint32_t nsXULPrototypeAttribute::gNumCacheFills; 134 #endif 135 136 #define NS_DISPATCH_XUL_COMMAND (1 << 0) 137 138 //---------------------------------------------------------------------- 139 // nsXULElement 140 // 141 142 nsXULElement::nsXULElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) 143 : nsStyledElement(std::move(aNodeInfo)) { 144 XUL_PROTOTYPE_ATTRIBUTE_METER(gNumElements); 145 } 146 147 nsXULElement::~nsXULElement() = default; 148 149 /* static */ 150 nsXULElement* NS_NewBasicXULElement( 151 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) { 152 RefPtr<mozilla::dom::NodeInfo> nodeInfo(std::move(aNodeInfo)); 153 auto* nim = nodeInfo->NodeInfoManager(); 154 return new (nim) nsXULElement(nodeInfo.forget()); 155 } 156 157 /* static */ 158 nsXULElement* nsXULElement::Construct( 159 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) { 160 // NOTE: If you add elements here, you probably also want to change 161 // mozilla::dom::binding_detail::HTMLConstructor in BindingUtils.cpp to take 162 // them into account, otherwise you'll start getting "Illegal constructor" 163 // exceptions in chrome code. 164 RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo; 165 if (nodeInfo->Equals(nsGkAtoms::resizer)) { 166 return NS_NewXULResizerElement(nodeInfo.forget()); 167 } 168 169 if (nodeInfo->Equals(nsGkAtoms::label) || 170 nodeInfo->Equals(nsGkAtoms::description)) { 171 auto* nim = nodeInfo->NodeInfoManager(); 172 return new (nim) XULTextElement(nodeInfo.forget()); 173 } 174 175 if (nodeInfo->Equals(nsGkAtoms::menupopup) || 176 nodeInfo->Equals(nsGkAtoms::panel)) { 177 return NS_NewXULPopupElement(nodeInfo.forget()); 178 } 179 180 if (nodeInfo->Equals(nsGkAtoms::tooltip)) { 181 return NS_NewXULTooltipElement(nodeInfo.forget()); 182 } 183 184 if (nodeInfo->Equals(nsGkAtoms::iframe) || 185 nodeInfo->Equals(nsGkAtoms::browser) || 186 nodeInfo->Equals(nsGkAtoms::editor)) { 187 auto* nim = nodeInfo->NodeInfoManager(); 188 return new (nim) XULFrameElement(nodeInfo.forget()); 189 } 190 191 if (nodeInfo->Equals(nsGkAtoms::menubar)) { 192 auto* nim = nodeInfo->NodeInfoManager(); 193 return new (nim) XULMenuBarElement(nodeInfo.forget()); 194 } 195 196 if (nodeInfo->Equals(nsGkAtoms::menu) || 197 nodeInfo->Equals(nsGkAtoms::menulist)) { 198 auto* nim = nodeInfo->NodeInfoManager(); 199 return new (nim) XULMenuElement(nodeInfo.forget()); 200 } 201 202 if (nodeInfo->Equals(nsGkAtoms::tree)) { 203 auto* nim = nodeInfo->NodeInfoManager(); 204 return new (nim) XULTreeElement(nodeInfo.forget()); 205 } 206 207 if (nodeInfo->Equals(nsGkAtoms::checkbox) || 208 nodeInfo->Equals(nsGkAtoms::radio) || 209 nodeInfo->Equals(nsGkAtoms::thumb) || 210 nodeInfo->Equals(nsGkAtoms::button) || 211 nodeInfo->Equals(nsGkAtoms::menuitem) || 212 nodeInfo->Equals(nsGkAtoms::toolbarbutton) || 213 nodeInfo->Equals(nsGkAtoms::toolbarpaletteitem) || 214 nodeInfo->Equals(nsGkAtoms::scrollbarbutton)) { 215 auto* nim = nodeInfo->NodeInfoManager(); 216 return new (nim) XULButtonElement(nodeInfo.forget()); 217 } 218 219 return NS_NewBasicXULElement(nodeInfo.forget()); 220 } 221 222 /* static */ 223 already_AddRefed<Element> nsXULElement::CreateFromPrototype( 224 nsXULPrototypeElement* aPrototype, Document* aDocument, bool aIsRoot) { 225 mozilla::dom::NodeInfo* ni = aPrototype->mNodeInfo; 226 RefPtr<mozilla::dom::NodeInfo> nodeInfo = 227 aDocument->NodeInfoManager()->GetNodeInfo( 228 ni->NameAtom(), ni->GetPrefixAtom(), ni->NamespaceID(), ELEMENT_NODE); 229 230 nsCOMPtr<Element> baseElement; 231 NS_NewXULElement(getter_AddRefs(baseElement), nodeInfo.forget(), 232 dom::FROM_PARSER_NETWORK, aPrototype->mIsAtom); 233 if (!baseElement) { 234 return nullptr; 235 } 236 237 nsXULElement* element = FromNode(baseElement); 238 element->MakeHeavyweight(aPrototype); 239 return baseElement.forget(); 240 } 241 242 nsresult NS_NewXULElement(Element** aResult, 243 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, 244 FromParser aFromParser, nsAtom* aIsAtom, 245 mozilla::dom::CustomElementDefinition* aDefinition) { 246 RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo; 247 248 MOZ_ASSERT(nodeInfo, "need nodeinfo for non-proto Create"); 249 250 NS_ASSERTION( 251 nodeInfo->NamespaceEquals(kNameSpaceID_XUL), 252 "Trying to create XUL elements that don't have the XUL namespace"); 253 254 Document* doc = nodeInfo->GetDocument(); 255 if (doc && !doc->AllowXULXBL()) { 256 return NS_ERROR_NOT_AVAILABLE; 257 } 258 259 return nsContentUtils::NewXULOrHTMLElement(aResult, nodeInfo, aFromParser, 260 aIsAtom, aDefinition); 261 } 262 263 void NS_TrustedNewXULElement( 264 Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) { 265 RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo; 266 MOZ_ASSERT(ni, "need nodeinfo for non-proto Create"); 267 268 // Create an nsXULElement with the specified namespace and tag. 269 NS_ADDREF(*aResult = nsXULElement::Construct(ni.forget())); 270 } 271 272 //---------------------------------------------------------------------- 273 // nsISupports interface 274 275 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsXULElement, nsStyledElement) 276 277 NS_IMPL_ADDREF_INHERITED(nsXULElement, nsStyledElement) 278 NS_IMPL_RELEASE_INHERITED(nsXULElement, nsStyledElement) 279 280 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsXULElement) 281 NS_ELEMENT_INTERFACE_TABLE_TO_MAP_SEGUE 282 NS_INTERFACE_MAP_END_INHERITING(nsStyledElement) 283 284 //---------------------------------------------------------------------- 285 // nsINode interface 286 287 nsresult nsXULElement::Clone(mozilla::dom::NodeInfo* aNodeInfo, 288 nsINode** aResult) const { 289 *aResult = nullptr; 290 291 RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo; 292 RefPtr<nsXULElement> element = Construct(ni.forget()); 293 294 nsresult rv = const_cast<nsXULElement*>(this)->CopyInnerTo( 295 element, ReparseAttributes::No); 296 NS_ENSURE_SUCCESS(rv, rv); 297 298 // Note that we're _not_ copying mControllers. 299 300 element.forget(aResult); 301 return rv; 302 } 303 304 //---------------------------------------------------------------------- 305 306 EventListenerManager* nsXULElement::GetEventListenerManagerForAttr( 307 nsAtom* aAttrName, bool* aDefer) { 308 // XXXbz sXBL/XBL2 issue: should we instead use GetComposedDoc() 309 // here, override BindToTree for those classes and munge event 310 // listeners there? 311 Document* doc = OwnerDoc(); 312 313 nsPIDOMWindowInner* window; 314 Element* root = doc->GetRootElement(); 315 if ((!root || root == this) && (window = doc->GetInnerWindow())) { 316 nsCOMPtr<EventTarget> piTarget = do_QueryInterface(window); 317 318 *aDefer = false; 319 return piTarget->GetOrCreateListenerManager(); 320 } 321 322 return nsStyledElement::GetEventListenerManagerForAttr(aAttrName, aDefer); 323 } 324 325 // returns true if the element is not a list 326 static bool IsNonList(mozilla::dom::NodeInfo* aNodeInfo) { 327 return !aNodeInfo->Equals(nsGkAtoms::tree) && 328 !aNodeInfo->Equals(nsGkAtoms::richlistbox); 329 } 330 331 nsXULElement::XULFocusability nsXULElement::GetXULFocusability( 332 IsFocusableFlags aFlags) { 333 #ifdef XP_MACOSX 334 // On Mac, mouse interactions only focus the element if it's a list, 335 // or if it's a remote target, since the remote target must handle 336 // the focus. 337 if ((aFlags & IsFocusableFlags::WithMouse) && IsNonList(mNodeInfo) && 338 !EventStateManager::IsTopLevelRemoteTarget(this)) { 339 return XULFocusability::NeverFocusable(); 340 } 341 #endif 342 343 XULFocusability result; 344 nsCOMPtr<nsIDOMXULControlElement> xulControl = AsXULControl(); 345 if (xulControl) { 346 // A disabled element cannot be focused and is not part of the tab order 347 bool disabled; 348 xulControl->GetDisabled(&disabled); 349 if (disabled) { 350 return XULFocusability::NeverFocusable(); 351 } 352 result.mDefaultFocusable = true; 353 } 354 if (Maybe<int32_t> attrVal = GetTabIndexAttrValue()) { 355 // The tabindex attribute was specified, so the element becomes 356 // focusable. 357 result.mDefaultFocusable = true; 358 result.mForcedFocusable.emplace(true); 359 result.mForcedTabIndexIfFocusable.emplace(attrVal.value()); 360 } 361 if (xulControl && FocusModel::AppliesToXUL() && 362 !FocusModel::IsTabFocusable(TabFocusableType::FormElements) && 363 IsNonList(mNodeInfo)) { 364 // By default, the tab focus model doesn't apply to xul element on any 365 // system but OS X. For compatibility, we only do this for controls, 366 // otherwise elements like <browser> cannot take this focus. 367 result.mForcedTabIndexIfFocusable = Some(-1); 368 } 369 return result; 370 } 371 372 // XUL elements are not focusable unless explicitly opted-into it with 373 // -moz-user-focus: normal, or the tabindex attribute. 374 Focusable nsXULElement::IsFocusableWithoutStyle(IsFocusableFlags aFlags) { 375 const auto focusability = GetXULFocusability(aFlags); 376 const bool focusable = focusability.mDefaultFocusable; 377 return {focusable, 378 focusable ? focusability.mForcedTabIndexIfFocusable.valueOr(-1) : -1}; 379 } 380 381 bool nsXULElement::HasMenu() { 382 if (auto* button = XULButtonElement::FromNode(this)) { 383 return button->IsMenu(); 384 } 385 return false; 386 } 387 388 void nsXULElement::OpenMenu(bool aOpenFlag) { 389 // Flush frames first. It's not clear why this is needed, see bug 1704670. 390 if (Document* doc = GetComposedDoc()) { 391 doc->FlushPendingNotifications(FlushType::Frames); 392 } 393 394 nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 395 if (!pm) { 396 return; 397 } 398 399 if (aOpenFlag) { 400 // Nothing will happen if this element isn't a menu. 401 pm->ShowMenu(this, false); 402 } else { 403 // Nothing will happen if this element isn't a menu. 404 pm->HideMenu(this); 405 } 406 } 407 408 Result<bool, nsresult> nsXULElement::PerformAccesskey(bool aKeyCausesActivation, 409 bool aIsTrustedEvent) { 410 if (IsXULElement(nsGkAtoms::label)) { 411 nsAutoString control; 412 GetAttr(nsGkAtoms::control, control); 413 if (control.IsEmpty()) { 414 return Err(NS_ERROR_UNEXPECTED); 415 } 416 417 // XXXsmaug Should we use ShadowRoot::GetElementById in case 418 // element is in Shadow DOM? 419 RefPtr<Document> document = GetUncomposedDoc(); 420 if (!document) { 421 return Err(NS_ERROR_UNEXPECTED); 422 } 423 424 RefPtr<Element> element = document->GetElementById(control); 425 if (!element) { 426 return Err(NS_ERROR_UNEXPECTED); 427 } 428 429 // XXXedgar, This is mainly for HTMLElement which doesn't do visible 430 // check in PerformAccesskey. We probably should always do visible 431 // check on HTMLElement even if the PerformAccesskey is not redirected from 432 // label XULelement per spec. 433 nsIFrame* frame = element->GetPrimaryFrame(); 434 if (!frame || !frame->IsVisibleConsideringAncestors()) { 435 return Err(NS_ERROR_UNEXPECTED); 436 } 437 438 return element->PerformAccesskey(aKeyCausesActivation, aIsTrustedEvent); 439 } 440 441 nsIFrame* frame = GetPrimaryFrame(); 442 if (!frame || !frame->IsVisibleConsideringAncestors()) { 443 return Err(NS_ERROR_UNEXPECTED); 444 } 445 446 bool focused = false; 447 // Define behavior for each type of XUL element. 448 if (!IsXULElement(nsGkAtoms::toolbarbutton)) { 449 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { 450 RefPtr<Element> elementToFocus = this; 451 // for radio buttons, focus the radiogroup instead 452 if (IsXULElement(nsGkAtoms::radio)) { 453 if (nsCOMPtr<nsIDOMXULSelectControlItemElement> controlItem = 454 AsXULSelectControlItem()) { 455 bool disabled; 456 controlItem->GetDisabled(&disabled); 457 if (!disabled) { 458 controlItem->GetControl(getter_AddRefs(elementToFocus)); 459 } 460 } 461 } 462 463 if (elementToFocus) { 464 fm->SetFocus(elementToFocus, nsIFocusManager::FLAG_BYKEY); 465 466 // Return true if the element became focused. 467 nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow(); 468 focused = (window && window->GetFocusedElement() == elementToFocus); 469 } 470 } 471 } 472 473 if (aKeyCausesActivation && !IsXULElement(nsGkAtoms::menulist)) { 474 ClickWithInputSource(MouseEvent_Binding::MOZ_SOURCE_KEYBOARD, 475 aIsTrustedEvent); 476 return focused; 477 } 478 479 // If the accesskey won't cause the activation and the focus isn't changed, 480 // either. Return error so EventStateManager would try to find next element 481 // to handle the accesskey. 482 return focused ? Result<bool, nsresult>{focused} : Err(NS_ERROR_ABORT); 483 } 484 485 //---------------------------------------------------------------------- 486 487 void nsXULElement::AddListenerForAttributeIfNeeded(nsAtom* aLocalName) { 488 // If appropriate, add a popup listener and/or compile the event 489 // handler. Called when we change the element's document, create a 490 // new element, change an attribute's value, etc. 491 // Eventlistenener-attributes are always in the null namespace. 492 if (aLocalName == nsGkAtoms::menu || aLocalName == nsGkAtoms::contextmenu || 493 // XXXdwh popup and context are deprecated 494 aLocalName == nsGkAtoms::popup || aLocalName == nsGkAtoms::context) { 495 AddPopupListener(aLocalName); 496 } 497 if (nsContentUtils::IsEventAttributeName(aLocalName, EventNameType_XUL)) { 498 nsAutoString value; 499 GetAttr(aLocalName, value); 500 SetEventHandler(aLocalName, value, true); 501 } 502 } 503 504 class XULInContentErrorReporter : public Runnable { 505 public: 506 explicit XULInContentErrorReporter(Document& aDocument) 507 : mozilla::Runnable("XULInContentErrorReporter"), mDocument(aDocument) {} 508 509 NS_IMETHOD Run() override { 510 mDocument->WarnOnceAbout(DeprecatedOperations::eImportXULIntoContent, 511 false); 512 return NS_OK; 513 } 514 515 private: 516 OwningNonNull<Document> mDocument; 517 }; 518 519 static bool NeedTooltipSupport(const nsXULElement& aXULElement) { 520 if (aXULElement.NodeInfo()->Equals(nsGkAtoms::treechildren)) { 521 // treechildren always get tooltip support, since cropped tree cells show 522 // their full text in a tooltip. 523 return true; 524 } 525 526 return aXULElement.GetBoolAttr(nsGkAtoms::tooltip) || 527 aXULElement.GetBoolAttr(nsGkAtoms::tooltiptext); 528 } 529 530 nsresult nsXULElement::BindToTree(BindContext& aContext, nsINode& aParent) { 531 nsresult rv = nsStyledElement::BindToTree(aContext, aParent); 532 NS_ENSURE_SUCCESS(rv, rv); 533 534 if (!IsInComposedDoc()) { 535 return rv; 536 } 537 538 Document& doc = aContext.OwnerDoc(); 539 if (!IsInNativeAnonymousSubtree() && !doc.AllowXULXBL() && 540 !doc.HasWarnedAbout(DeprecatedOperations::eImportXULIntoContent)) { 541 nsContentUtils::AddScriptRunner(new XULInContentErrorReporter(doc)); 542 } 543 544 #ifdef DEBUG 545 if (!doc.AllowXULXBL() && !doc.IsLoadedAsData()) { 546 // To save CPU cycles and memory, we don't load xul.css for other elements 547 // except scrollbars. 548 // 549 // This assertion makes sure no other XUL element is used in a non-XUL 550 // document. 551 nsAtom* tag = NodeInfo()->NameAtom(); 552 MOZ_ASSERT(tag == nsGkAtoms::scrollbar || 553 tag == nsGkAtoms::scrollbarbutton || 554 tag == nsGkAtoms::scrollcorner || tag == nsGkAtoms::slider || 555 tag == nsGkAtoms::thumb || tag == nsGkAtoms::resizer, 556 "Unexpected XUL element in non-XUL doc"); 557 } 558 #endif 559 560 if (NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) { 561 // Create our XUL key listener and hook it up. 562 XULKeySetGlobalKeyListener::AttachKeyHandler(this); 563 } 564 565 RegUnRegAccessKey(true); 566 567 if (NeedTooltipSupport(*this)) { 568 AddTooltipSupport(); 569 } 570 571 if (XULBroadcastManager::MayNeedListener(*this)) { 572 if (!doc.HasXULBroadcastManager()) { 573 doc.InitializeXULBroadcastManager(); 574 } 575 XULBroadcastManager* broadcastManager = doc.GetXULBroadcastManager(); 576 broadcastManager->AddListener(this); 577 } 578 return rv; 579 } 580 581 void nsXULElement::UnbindFromTree(UnbindContext& aContext) { 582 if (NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) { 583 XULKeySetGlobalKeyListener::DetachKeyHandler(this); 584 } 585 586 RegUnRegAccessKey(false); 587 588 if (NeedTooltipSupport(*this)) { 589 RemoveTooltipSupport(); 590 } 591 592 Document* doc = GetComposedDoc(); 593 if (doc && doc->HasXULBroadcastManager() && 594 XULBroadcastManager::MayNeedListener(*this)) { 595 RefPtr<XULBroadcastManager> broadcastManager = 596 doc->GetXULBroadcastManager(); 597 broadcastManager->RemoveListener(this); 598 } 599 600 // mControllers can own objects that are implemented 601 // in JavaScript (such as some implementations of 602 // nsIControllers. These objects prevent their global 603 // object's script object from being garbage collected, 604 // which means JS continues to hold an owning reference 605 // to the nsGlobalWindow, which owns the document, 606 // which owns this content. That's a cycle, so we break 607 // it here. (It might be better to break this by releasing 608 // mDocument in nsGlobalWindow::SetDocShell, but I'm not 609 // sure whether that would fix all possible cycles through 610 // mControllers.) 611 nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); 612 if (slots) { 613 slots->mControllers = nullptr; 614 } 615 616 nsStyledElement::UnbindFromTree(aContext); 617 } 618 619 void nsXULElement::DoneAddingChildren(bool aHaveNotified) { 620 if (IsXULElement(nsGkAtoms::linkset)) { 621 Document* doc = GetComposedDoc(); 622 if (doc) { 623 doc->OnL10nResourceContainerParsed(); 624 } 625 } 626 } 627 628 void nsXULElement::RegUnRegAccessKey(bool aDoReg) { 629 // Don't try to register for unsupported elements 630 if (!SupportsAccessKey()) { 631 return; 632 } 633 634 nsStyledElement::RegUnRegAccessKey(aDoReg); 635 } 636 637 bool nsXULElement::SupportsAccessKey() const { 638 if (NodeInfo()->Equals(nsGkAtoms::label) && HasAttr(nsGkAtoms::control)) { 639 return true; 640 } 641 642 // XXX(ntim): check if description[value] or description[accesskey] are 643 // actually used, remove `value` from {Before/After}SetAttr if not the case 644 if (NodeInfo()->Equals(nsGkAtoms::description) && HasAttr(nsGkAtoms::value) && 645 HasAttr(nsGkAtoms::control)) { 646 return true; 647 } 648 649 return IsAnyOfXULElements(nsGkAtoms::button, nsGkAtoms::toolbarbutton, 650 nsGkAtoms::checkbox, nsGkAtoms::tab, 651 nsGkAtoms::radio); 652 } 653 654 void nsXULElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName, 655 const nsAttrValue* aValue, bool aNotify) { 656 if (aNamespaceID == kNameSpaceID_None) { 657 if (aName == nsGkAtoms::accesskey || aName == nsGkAtoms::control || 658 aName == nsGkAtoms::value) { 659 RegUnRegAccessKey(false); 660 } else if ((aName == nsGkAtoms::command || aName == nsGkAtoms::observes) && 661 IsInUncomposedDoc()) { 662 // XXX sXBL/XBL2 issue! Owner or current document? 663 // XXX Why does this not also remove broadcast listeners if the 664 // "element" attribute was changed on an <observer>? 665 nsAutoString oldValue; 666 GetAttr(nsGkAtoms::observes, oldValue); 667 if (oldValue.IsEmpty()) { 668 GetAttr(nsGkAtoms::command, oldValue); 669 } 670 Document* doc = GetUncomposedDoc(); 671 if (!oldValue.IsEmpty() && doc->HasXULBroadcastManager()) { 672 RefPtr<XULBroadcastManager> broadcastManager = 673 doc->GetXULBroadcastManager(); 674 broadcastManager->RemoveListener(this); 675 } 676 #ifdef DEBUG 677 } else if (aName == nsGkAtoms::usercontextid) { 678 const nsAttrValue* oldValue = GetParsedAttr(aName); 679 if (oldValue && (!aValue || !aValue->Equals(*oldValue))) { 680 MOZ_ASSERT(false, 681 "Changing usercontextid doesn't really work properly."); 682 } 683 #endif 684 } 685 } 686 687 return nsStyledElement::BeforeSetAttr(aNamespaceID, aName, aValue, aNotify); 688 } 689 690 void nsXULElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, 691 const nsAttrValue* aValue, 692 const nsAttrValue* aOldValue, 693 nsIPrincipal* aSubjectPrincipal, bool aNotify) { 694 if (aNamespaceID == kNameSpaceID_None) { 695 if (aValue) { 696 AddListenerForAttributeIfNeeded(aName); 697 } 698 699 if (aName == nsGkAtoms::accesskey || aName == nsGkAtoms::control || 700 aName == nsGkAtoms::value) { 701 RegUnRegAccessKey(true); 702 } else if (aName == nsGkAtoms::tooltip || aName == nsGkAtoms::tooltiptext) { 703 if (!!aValue != !!aOldValue && IsInComposedDoc() && 704 !NodeInfo()->Equals(nsGkAtoms::treechildren)) { 705 if (aValue) { 706 AddTooltipSupport(); 707 } else { 708 RemoveTooltipSupport(); 709 } 710 } 711 } 712 Document* doc = GetComposedDoc(); 713 if (doc && doc->HasXULBroadcastManager()) { 714 RefPtr<XULBroadcastManager> broadcastManager = 715 doc->GetXULBroadcastManager(); 716 broadcastManager->AttributeChanged(this, aNamespaceID, aName); 717 } 718 if (doc && XULBroadcastManager::MayNeedListener(*this)) { 719 if (!doc->HasXULBroadcastManager()) { 720 doc->InitializeXULBroadcastManager(); 721 } 722 XULBroadcastManager* broadcastManager = doc->GetXULBroadcastManager(); 723 broadcastManager->AddListener(this); 724 } 725 726 // XXX need to check if they're changing an event handler: if 727 // so, then we need to unhook the old one. Or something. 728 } 729 730 return nsStyledElement::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue, 731 aSubjectPrincipal, aNotify); 732 } 733 734 void nsXULElement::AddTooltipSupport() { 735 nsXULTooltipListener* listener = nsXULTooltipListener::GetInstance(); 736 if (!listener) { 737 return; 738 } 739 740 listener->AddTooltipSupport(this); 741 } 742 743 void nsXULElement::RemoveTooltipSupport() { 744 nsXULTooltipListener* listener = nsXULTooltipListener::GetInstance(); 745 if (!listener) { 746 return; 747 } 748 749 listener->RemoveTooltipSupport(this); 750 } 751 752 bool nsXULElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, 753 const nsAString& aValue, 754 nsIPrincipal* aMaybeScriptedPrincipal, 755 nsAttrValue& aResult) { 756 if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::tabindex) { 757 return aResult.ParseIntValue(aValue); 758 } 759 760 // Parse into a nsAttrValue 761 if (!nsStyledElement::ParseAttribute(aNamespaceID, aAttribute, aValue, 762 aMaybeScriptedPrincipal, aResult)) { 763 // Fall back to parsing as atom for short values 764 aResult.ParseStringOrAtom(aValue); 765 } 766 767 return true; 768 } 769 770 void nsXULElement::DestroyContent() { 771 nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); 772 if (slots) { 773 slots->mControllers = nullptr; 774 } 775 776 nsStyledElement::DestroyContent(); 777 } 778 779 #ifdef MOZ_DOM_LIST 780 void nsXULElement::List(FILE* out, int32_t aIndent) const { 781 nsCString prefix("XUL"); 782 if (HasSlots()) { 783 prefix.Append('*'); 784 } 785 prefix.Append(' '); 786 787 nsStyledElement::List(out, aIndent, prefix); 788 } 789 #endif 790 791 bool nsXULElement::IsEventStoppedFromAnonymousScrollbar(EventMessage aMessage) { 792 return (IsRootOfNativeAnonymousSubtree() && 793 IsAnyOfXULElements(nsGkAtoms::scrollbar, nsGkAtoms::scrollcorner) && 794 (aMessage == ePointerClick || aMessage == eMouseDoubleClick || 795 aMessage == eCommand || aMessage == eContextMenu || 796 aMessage == eDragStart || aMessage == ePointerAuxClick)); 797 } 798 799 nsresult nsXULElement::DispatchXULCommand(const EventChainVisitor& aVisitor, 800 nsAutoString& aCommand) { 801 // XXX sXBL/XBL2 issue! Owner or current document? 802 nsCOMPtr<Document> doc = GetUncomposedDoc(); 803 NS_ENSURE_STATE(doc); 804 RefPtr<Element> commandElt = doc->GetElementById(aCommand); 805 if (commandElt) { 806 // Create a new command event to dispatch to the element 807 // pointed to by the command attribute. The new event's 808 // sourceEvent will be the original command event that we're 809 // handling. 810 RefPtr<Event> event = aVisitor.mDOMEvent; 811 uint16_t inputSource = MouseEvent_Binding::MOZ_SOURCE_UNKNOWN; 812 int16_t button = 0; 813 while (event) { 814 NS_ENSURE_STATE(event->GetOriginalTarget() != commandElt); 815 RefPtr<XULCommandEvent> commandEvent = event->AsXULCommandEvent(); 816 if (commandEvent) { 817 event = commandEvent->GetSourceEvent(); 818 inputSource = commandEvent->InputSource(); 819 button = commandEvent->Button(); 820 } else { 821 event = nullptr; 822 } 823 } 824 WidgetInputEvent* orig = aVisitor.mEvent->AsInputEvent(); 825 nsContentUtils::DispatchXULCommand( 826 commandElt, orig->IsTrusted(), MOZ_KnownLive(aVisitor.mDOMEvent), 827 nullptr, orig->IsControl(), orig->IsAlt(), orig->IsShift(), 828 orig->IsMeta(), inputSource, button); 829 } else { 830 NS_WARNING("A XUL element is attached to a command that doesn't exist!\n"); 831 } 832 return NS_OK; 833 } 834 835 void nsXULElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { 836 aVisitor.mForceContentDispatch = true; // FIXME! Bug 329119 837 if (IsEventStoppedFromAnonymousScrollbar(aVisitor.mEvent->mMessage)) { 838 // Don't propagate these events from native anonymous scrollbar. 839 aVisitor.mCanHandle = true; 840 aVisitor.SetParentTarget(nullptr, false); 841 return; 842 } 843 if (aVisitor.mEvent->mMessage == eUnidentifiedEvent && 844 aVisitor.mEvent->mSpecifiedEventType == nsGkAtoms::oncommand && 845 aVisitor.mEvent->mClass == eInputEventClass && 846 aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) && 847 !IsXULElement(nsGkAtoms::command)) { 848 // Check that we really have an xul command event. That will be handled 849 // in a special way. 850 // See if we have a command elt. If so, we execute on the command 851 // instead of on our content element. 852 if (aVisitor.mDOMEvent && aVisitor.mDOMEvent->AsXULCommandEvent() && 853 HasNonEmptyAttr(nsGkAtoms::command)) { 854 // Stop building the event target chain for the original event. 855 // We don't want it to propagate to any DOM nodes. 856 aVisitor.mCanHandle = false; 857 aVisitor.mAutomaticChromeDispatch = false; 858 // Dispatch XUL command in PreHandleEvent to prevent it breaks event 859 // target chain creation 860 aVisitor.mWantsPreHandleEvent = true; 861 aVisitor.mItemFlags |= NS_DISPATCH_XUL_COMMAND; 862 return; 863 } 864 } 865 866 nsStyledElement::GetEventTargetParent(aVisitor); 867 } 868 869 nsresult nsXULElement::PreHandleEvent(EventChainVisitor& aVisitor) { 870 if (aVisitor.mItemFlags & NS_DISPATCH_XUL_COMMAND) { 871 nsAutoString command; 872 GetAttr(nsGkAtoms::command, command); 873 MOZ_ASSERT(!command.IsEmpty()); 874 return DispatchXULCommand(aVisitor, command); 875 } 876 return nsStyledElement::PreHandleEvent(aVisitor); 877 } 878 879 //---------------------------------------------------------------------- 880 // Implementation methods 881 882 NS_IMETHODIMP_(bool) 883 nsXULElement::IsAttributeMapped(const nsAtom* aAttribute) const { 884 return false; 885 } 886 887 nsIControllers* nsXULElement::EnsureControllers() { 888 auto* slots = ExtendedDOMSlots(); 889 if (!slots->mControllers) { 890 slots->mControllers = new nsXULControllers(); 891 } 892 return slots->mControllers; 893 } 894 895 void nsXULElement::Click(CallerType aCallerType) { 896 ClickWithInputSource(MouseEvent_Binding::MOZ_SOURCE_UNKNOWN, 897 aCallerType == CallerType::System); 898 } 899 900 void nsXULElement::ClickWithInputSource(uint16_t aInputSource, 901 bool aIsTrustedEvent) { 902 if (State().HasState(ElementState::DISABLED)) { 903 return; 904 } 905 906 nsCOMPtr<Document> doc = GetComposedDoc(); // Strong just in case 907 if (doc) { 908 RefPtr<nsPresContext> context = doc->GetPresContext(); 909 if (context) { 910 // strong ref to PresContext so events don't destroy it 911 912 WidgetMouseEvent eventDown(aIsTrustedEvent, eMouseDown, nullptr, 913 WidgetMouseEvent::eReal); 914 WidgetMouseEvent eventUp(aIsTrustedEvent, eMouseUp, nullptr, 915 WidgetMouseEvent::eReal); 916 // This helps to avoid commands being dispatched from 917 // XULButtonElement::PostHandleEventForMenu. 918 eventUp.mFlags.mMultipleActionsPrevented = true; 919 WidgetPointerEvent eventClick(aIsTrustedEvent, ePointerClick, nullptr); 920 eventDown.mInputSource = eventUp.mInputSource = eventClick.mInputSource = 921 aInputSource; 922 switch (aInputSource) { 923 case MouseEvent_Binding::MOZ_SOURCE_MOUSE: 924 MOZ_ASSERT(eventClick.pointerId == 0 || eventClick.pointerId == 1, 925 "pointerId for the primary mouse pointer must be 0 or 1"); 926 break; 927 case MouseEvent_Binding::MOZ_SOURCE_KEYBOARD: 928 case MouseEvent_Binding::MOZ_SOURCE_UNKNOWN: 929 // pointerId definition in Pointer Events: 930 // > The pointerId value of -1 MUST be reserved and used to indicate 931 // > events that were generated by something other than a pointing 932 // > device. 933 eventDown.pointerId = eventUp.pointerId = eventClick.pointerId = -1; 934 break; 935 } 936 937 // send mouse down 938 nsEventStatus status = nsEventStatus_eIgnore; 939 EventDispatcher::Dispatch(this, context, &eventDown, nullptr, &status); 940 941 // send mouse up 942 status = nsEventStatus_eIgnore; // reset status 943 EventDispatcher::Dispatch(this, context, &eventUp, nullptr, &status); 944 945 // send mouse click 946 status = nsEventStatus_eIgnore; // reset status 947 EventDispatcher::Dispatch(this, context, &eventClick, nullptr, &status); 948 949 // If the click has been prevented, lets skip the command call 950 // this is how a physical click works 951 if (status == nsEventStatus_eConsumeNoDefault) { 952 return; 953 } 954 } 955 } 956 957 // oncommand is fired when an element is clicked... 958 DoCommand(); 959 } 960 961 void nsXULElement::DoCommand() { 962 nsCOMPtr<Document> doc = GetComposedDoc(); // strong just in case 963 if (doc) { 964 RefPtr<nsXULElement> self = this; 965 nsContentUtils::DispatchXULCommand(self, true); 966 } 967 } 968 969 nsresult nsXULElement::AddPopupListener(nsAtom* aName) { 970 // Add a popup listener to the element 971 bool isContext = 972 (aName == nsGkAtoms::context || aName == nsGkAtoms::contextmenu); 973 uint32_t listenerFlag = isContext ? XUL_ELEMENT_HAS_CONTENTMENU_LISTENER 974 : XUL_ELEMENT_HAS_POPUP_LISTENER; 975 976 if (HasFlag(listenerFlag)) { 977 return NS_OK; 978 } 979 980 nsCOMPtr<nsIDOMEventListener> listener = 981 new nsXULPopupListener(this, isContext); 982 983 // Add the popup as a listener on this element. 984 EventListenerManager* manager = GetOrCreateListenerManager(); 985 SetFlags(listenerFlag); 986 987 if (isContext) { 988 manager->AddEventListenerByType(listener, u"contextmenu"_ns, 989 TrustedEventsAtSystemGroupBubble()); 990 } else { 991 manager->AddEventListenerByType(listener, u"mousedown"_ns, 992 TrustedEventsAtSystemGroupBubble()); 993 } 994 return NS_OK; 995 } 996 997 //---------------------------------------------------------------------- 998 999 nsresult nsXULElement::MakeHeavyweight(nsXULPrototypeElement* aPrototype) { 1000 MOZ_ASSERT(aPrototype); 1001 for (const auto& protoattr : aPrototype->mAttributes) { 1002 nsAttrValue value(protoattr.mValue); 1003 MOZ_TRY(SetParsedAttr( 1004 protoattr.mName.NamespaceID(), protoattr.mName.LocalName(), 1005 protoattr.mName.GetPrefix(), value, /* aNotify = */ false)); 1006 } 1007 return NS_OK; 1008 } 1009 1010 bool nsXULElement::IsEventAttributeNameInternal(nsAtom* aName) { 1011 return nsContentUtils::IsEventAttributeName(aName, EventNameType_XUL); 1012 } 1013 1014 JSObject* nsXULElement::WrapNode(JSContext* aCx, 1015 JS::Handle<JSObject*> aGivenProto) { 1016 return dom::XULElement_Binding::Wrap(aCx, this, aGivenProto); 1017 } 1018 1019 bool nsXULElement::IsInteractiveHTMLContent() const { 1020 return IsXULElement(nsGkAtoms::menupopup) || 1021 Element::IsInteractiveHTMLContent(); 1022 } 1023 1024 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULPrototypeNode) 1025 1026 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULPrototypeNode) 1027 if (tmp->mType == nsXULPrototypeNode::eType_Element) { 1028 static_cast<nsXULPrototypeElement*>(tmp)->Unlink(); 1029 } 1030 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1031 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULPrototypeNode) 1032 if (tmp->mType == nsXULPrototypeNode::eType_Element) { 1033 nsXULPrototypeElement* elem = static_cast<nsXULPrototypeElement*>(tmp); 1034 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mNodeInfo"); 1035 cb.NoteNativeChild(elem->mNodeInfo, 1036 NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo)); 1037 size_t i; 1038 for (i = 0; i < elem->mAttributes.Length(); ++i) { 1039 const nsAttrName& name = elem->mAttributes[i].mName; 1040 if (!name.IsAtom()) { 1041 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, 1042 "mAttributes[i].mName.NodeInfo()"); 1043 cb.NoteNativeChild(name.NodeInfo(), 1044 NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo)); 1045 } 1046 } 1047 ImplCycleCollectionTraverse(cb, elem->mChildren, "mChildren"); 1048 } 1049 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1050 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXULPrototypeNode) 1051 NS_IMPL_CYCLE_COLLECTION_TRACE_END 1052 1053 //---------------------------------------------------------------------- 1054 // 1055 // nsXULPrototypeAttribute 1056 // 1057 1058 nsXULPrototypeAttribute::~nsXULPrototypeAttribute() { 1059 MOZ_COUNT_DTOR(nsXULPrototypeAttribute); 1060 } 1061 1062 //---------------------------------------------------------------------- 1063 // 1064 // nsXULPrototypeElement 1065 // 1066 1067 nsresult nsXULPrototypeElement::Serialize( 1068 nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc, 1069 const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) { 1070 nsresult rv; 1071 1072 // Write basic prototype data 1073 rv = aStream->Write32(mType); 1074 1075 // Write Node Info 1076 int32_t index = aNodeInfos->IndexOf(mNodeInfo); 1077 NS_ASSERTION(index >= 0, "unknown mozilla::dom::NodeInfo index"); 1078 nsresult tmp = aStream->Write32(index); 1079 if (NS_FAILED(tmp)) { 1080 rv = tmp; 1081 } 1082 1083 // Write Attributes 1084 tmp = aStream->Write32(mAttributes.Length()); 1085 if (NS_FAILED(tmp)) { 1086 rv = tmp; 1087 } 1088 1089 nsAutoString attributeValue; 1090 size_t i; 1091 for (i = 0; i < mAttributes.Length(); ++i) { 1092 RefPtr<mozilla::dom::NodeInfo> ni; 1093 if (mAttributes[i].mName.IsAtom()) { 1094 ni = mNodeInfo->NodeInfoManager()->GetNodeInfo( 1095 mAttributes[i].mName.Atom(), nullptr, kNameSpaceID_None, 1096 nsINode::ATTRIBUTE_NODE); 1097 NS_ASSERTION(ni, "the nodeinfo should already exist"); 1098 } else { 1099 ni = mAttributes[i].mName.NodeInfo(); 1100 } 1101 1102 index = aNodeInfos->IndexOf(ni); 1103 NS_ASSERTION(index >= 0, "unknown mozilla::dom::NodeInfo index"); 1104 tmp = aStream->Write32(index); 1105 if (NS_FAILED(tmp)) { 1106 rv = tmp; 1107 } 1108 1109 mAttributes[i].mValue.ToString(attributeValue); 1110 tmp = aStream->WriteWStringZ(attributeValue.get()); 1111 if (NS_FAILED(tmp)) { 1112 rv = tmp; 1113 } 1114 } 1115 1116 // Now write children 1117 tmp = aStream->Write32(uint32_t(mChildren.Length())); 1118 if (NS_FAILED(tmp)) { 1119 rv = tmp; 1120 } 1121 for (i = 0; i < mChildren.Length(); i++) { 1122 nsXULPrototypeNode* child = mChildren[i].get(); 1123 switch (child->mType) { 1124 case eType_Element: 1125 case eType_Text: 1126 case eType_PI: 1127 tmp = child->Serialize(aStream, aProtoDoc, aNodeInfos); 1128 if (NS_FAILED(tmp)) { 1129 rv = tmp; 1130 } 1131 break; 1132 case eType_Script: 1133 tmp = aStream->Write32(child->mType); 1134 if (NS_FAILED(tmp)) { 1135 rv = tmp; 1136 } 1137 nsXULPrototypeScript* script = 1138 static_cast<nsXULPrototypeScript*>(child); 1139 1140 tmp = aStream->Write8(script->mOutOfLine); 1141 if (NS_FAILED(tmp)) { 1142 rv = tmp; 1143 } 1144 if (!script->mOutOfLine) { 1145 tmp = script->Serialize(aStream, aProtoDoc, aNodeInfos); 1146 if (NS_FAILED(tmp)) { 1147 rv = tmp; 1148 } 1149 } else { 1150 tmp = aStream->WriteCompoundObject(script->mSrcURI, 1151 NS_GET_IID(nsIURI), true); 1152 if (NS_FAILED(tmp)) { 1153 rv = tmp; 1154 } 1155 1156 if (script->HasStencil()) { 1157 // This may return NS_OK without muxing script->mSrcURI's 1158 // data into the cache file, in the case where that 1159 // muxed document is already there (written by a prior 1160 // session, or by an earlier cache episode during this 1161 // session). 1162 tmp = script->SerializeOutOfLine(aStream, aProtoDoc); 1163 if (NS_FAILED(tmp)) { 1164 rv = tmp; 1165 } 1166 } 1167 } 1168 break; 1169 } 1170 } 1171 1172 return rv; 1173 } 1174 1175 nsresult nsXULPrototypeElement::Deserialize( 1176 nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc, 1177 nsIURI* aDocumentURI, 1178 const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) { 1179 MOZ_ASSERT(aNodeInfos, "missing nodeinfo array"); 1180 1181 // Read Node Info 1182 uint32_t number = 0; 1183 nsresult rv = aStream->Read32(&number); 1184 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 1185 mNodeInfo = aNodeInfos->SafeElementAt(number, nullptr); 1186 if (!mNodeInfo) { 1187 return NS_ERROR_UNEXPECTED; 1188 } 1189 1190 if (mNodeInfo->Equals(nsGkAtoms::parsererror) && 1191 mNodeInfo->NamespaceEquals( 1192 nsDependentAtomString(nsGkAtoms::nsuri_parsererror))) { 1193 return NS_ERROR_UNEXPECTED; 1194 } 1195 1196 // Read Attributes 1197 rv = aStream->Read32(&number); 1198 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 1199 int32_t attributes = int32_t(number); 1200 1201 if (attributes > 0) { 1202 mAttributes.AppendElements(attributes); 1203 1204 nsAutoString attributeValue; 1205 for (size_t i = 0; i < mAttributes.Length(); ++i) { 1206 rv = aStream->Read32(&number); 1207 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 1208 mozilla::dom::NodeInfo* ni = aNodeInfos->SafeElementAt(number, nullptr); 1209 if (!ni) { 1210 return NS_ERROR_UNEXPECTED; 1211 } 1212 1213 mAttributes[i].mName.SetTo(ni); 1214 1215 rv = aStream->ReadString(attributeValue); 1216 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 1217 rv = SetAttrAt(i, attributeValue, aDocumentURI); 1218 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 1219 } 1220 } 1221 1222 rv = aStream->Read32(&number); 1223 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 1224 uint32_t numChildren = int32_t(number); 1225 1226 if (numChildren > 0) { 1227 if (!mChildren.SetCapacity(numChildren, fallible)) { 1228 return NS_ERROR_OUT_OF_MEMORY; 1229 } 1230 1231 for (uint32_t i = 0; i < numChildren; i++) { 1232 rv = aStream->Read32(&number); 1233 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 1234 Type childType = (Type)number; 1235 1236 RefPtr<nsXULPrototypeNode> child; 1237 1238 switch (childType) { 1239 case eType_Element: 1240 child = new nsXULPrototypeElement(); 1241 rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, aNodeInfos); 1242 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 1243 break; 1244 case eType_Text: 1245 child = new nsXULPrototypeText(); 1246 rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, aNodeInfos); 1247 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 1248 break; 1249 case eType_PI: 1250 child = new nsXULPrototypePI(); 1251 rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, aNodeInfos); 1252 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 1253 break; 1254 case eType_Script: { 1255 // language version/options obtained during deserialization. 1256 RefPtr<nsXULPrototypeScript> script = new nsXULPrototypeScript(0); 1257 1258 rv = aStream->ReadBoolean(&script->mOutOfLine); 1259 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 1260 if (!script->mOutOfLine) { 1261 rv = script->Deserialize(aStream, aProtoDoc, aDocumentURI, 1262 aNodeInfos); 1263 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 1264 } else { 1265 nsCOMPtr<nsISupports> supports; 1266 rv = aStream->ReadObject(true, getter_AddRefs(supports)); 1267 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 1268 script->mSrcURI = do_QueryInterface(supports); 1269 1270 rv = script->DeserializeOutOfLine(aStream, aProtoDoc); 1271 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 1272 } 1273 1274 child = std::move(script); 1275 break; 1276 } 1277 default: 1278 MOZ_ASSERT(false, "Unexpected child type!"); 1279 return NS_ERROR_UNEXPECTED; 1280 } 1281 1282 MOZ_ASSERT(child, "Don't append null to mChildren"); 1283 MOZ_ASSERT(child->mType == childType); 1284 mChildren.AppendElement(child); 1285 1286 // Oh dear. Something failed during the deserialization. 1287 // We don't know what. But likely consequences of failed 1288 // deserializations included calls to |AbortCaching| which 1289 // shuts down the cache and closes our streams. 1290 // If that happens, next time through this loop, we die a messy 1291 // death. So, let's just fail now, and propagate that failure 1292 // upward so that the ChromeProtocolHandler knows it can't use 1293 // a cached chrome channel for this. 1294 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 1295 } 1296 } 1297 1298 return rv; 1299 } 1300 1301 nsresult nsXULPrototypeElement::SetAttrAt(uint32_t aPos, 1302 const nsAString& aValue, 1303 nsIURI* aDocumentURI) { 1304 MOZ_ASSERT(aPos < mAttributes.Length(), "out-of-bounds"); 1305 1306 // WARNING!! 1307 // This code is largely duplicated in nsXULElement::SetAttr. 1308 // Any changes should be made to both functions. 1309 1310 if (!mNodeInfo->NamespaceEquals(kNameSpaceID_XUL)) { 1311 if (mNodeInfo->NamespaceEquals(kNameSpaceID_XHTML) && 1312 mAttributes[aPos].mName.Equals(nsGkAtoms::is)) { 1313 // We still care about the is attribute set on HTML elements. 1314 mAttributes[aPos].mValue.ParseAtom(aValue); 1315 mIsAtom = mAttributes[aPos].mValue.GetAtomValue(); 1316 1317 return NS_OK; 1318 } 1319 1320 mAttributes[aPos].mValue.ParseStringOrAtom(aValue); 1321 1322 return NS_OK; 1323 } 1324 1325 if (mAttributes[aPos].mName.Equals(nsGkAtoms::id) && !aValue.IsEmpty()) { 1326 // Store id as atom. 1327 // id="" means that the element has no id. Not that it has 1328 // emptystring as id. 1329 mAttributes[aPos].mValue.ParseAtom(aValue); 1330 1331 return NS_OK; 1332 } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::aria_activedescendant)) { 1333 mAttributes[aPos].mValue.ParseAtom(aValue); 1334 1335 return NS_OK; 1336 } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::is)) { 1337 // Store is as atom. 1338 mAttributes[aPos].mValue.ParseAtom(aValue); 1339 mIsAtom = mAttributes[aPos].mValue.GetAtomValue(); 1340 1341 return NS_OK; 1342 } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::_class)) { 1343 // Compute the element's class list 1344 mAttributes[aPos].mValue.ParseAtomArray(aValue); 1345 1346 return NS_OK; 1347 } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::style)) { 1348 // Parse the element's 'style' attribute 1349 1350 // This is basically duplicating what nsINode::NodePrincipal() does 1351 nsIPrincipal* principal = mNodeInfo->NodeInfoManager()->DocumentPrincipal(); 1352 // XXX Get correct Base URI (need GetBaseURI on *prototype* element) 1353 // TODO: If we implement Content Security Policy for chrome documents 1354 // as has been discussed, the CSP should be checked here to see if 1355 // inline styles are allowed to be applied. 1356 // XXX No specific specs talk about xul and referrer policy, pass Unset 1357 auto referrerInfo = 1358 MakeRefPtr<ReferrerInfo>(aDocumentURI, ReferrerPolicy::_empty); 1359 auto data = MakeRefPtr<URLExtraData>(aDocumentURI, referrerInfo, principal); 1360 RefPtr<DeclarationBlock> declaration = DeclarationBlock::FromCssText( 1361 aValue, data, eCompatibility_FullStandards, nullptr, 1362 StyleCssRuleType::Style); 1363 if (declaration) { 1364 declaration->SetImmutable(); 1365 mAttributes[aPos].mValue.SetTo(declaration.forget(), &aValue); 1366 1367 return NS_OK; 1368 } 1369 // Don't abort if parsing failed, it could just be malformed css. 1370 } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::tabindex)) { 1371 mAttributes[aPos].mValue.ParseIntValue(aValue); 1372 1373 return NS_OK; 1374 } 1375 1376 mAttributes[aPos].mValue.ParseStringOrAtom(aValue); 1377 1378 return NS_OK; 1379 } 1380 1381 void nsXULPrototypeElement::Unlink() { 1382 mAttributes.Clear(); 1383 mChildren.Clear(); 1384 } 1385 1386 //---------------------------------------------------------------------- 1387 // 1388 // nsXULPrototypeScript 1389 // 1390 1391 nsXULPrototypeScript::nsXULPrototypeScript(uint32_t aLineNo) 1392 : nsXULPrototypeNode(eType_Script), 1393 mLineNo(aLineNo), 1394 mSrcLoading(false), 1395 mOutOfLine(true), 1396 mSrcLoadWaiters(nullptr), 1397 mStencil(nullptr) {} 1398 1399 static nsresult WriteStencil(nsIObjectOutputStream* aStream, 1400 JS::Stencil* aStencil) { 1401 JS::FrontendContext* fc = JS::NewFrontendContext(); 1402 if (!fc) { 1403 return NS_ERROR_OUT_OF_MEMORY; 1404 } 1405 1406 JS::TranscodeBuffer buffer; 1407 JS::TranscodeResult code; 1408 code = JS::EncodeStencil(fc, aStencil, buffer); 1409 1410 JS::DestroyFrontendContext(fc); 1411 1412 if (code != JS::TranscodeResult::Ok) { 1413 if (code == JS::TranscodeResult::Throw) { 1414 return NS_ERROR_OUT_OF_MEMORY; 1415 } 1416 1417 MOZ_ASSERT(IsTranscodeFailureResult(code)); 1418 return NS_ERROR_FAILURE; 1419 } 1420 1421 size_t size = buffer.length(); 1422 if (size > UINT32_MAX) { 1423 return NS_ERROR_FAILURE; 1424 } 1425 nsresult rv = aStream->Write32(size); 1426 if (NS_SUCCEEDED(rv)) { 1427 // Ideally we could just pass "buffer" here. See bug 1566574. 1428 rv = aStream->WriteBytes(Span(buffer.begin(), size)); 1429 } 1430 1431 return rv; 1432 } 1433 1434 static nsresult ReadStencil(nsIObjectInputStream* aStream, JSContext* aCx, 1435 const JS::ReadOnlyDecodeOptions& aOptions, 1436 JS::Stencil** aStencilOut) { 1437 // We don't serialize mutedError-ness of scripts, which is fine as long as 1438 // we only serialize system and XUL-y things. We can detect this by checking 1439 // where the caller wants us to deserialize. 1440 // 1441 // CompilationScope() could theoretically GC, so get that out of the way 1442 // before comparing to the cx global. 1443 JSObject* loaderGlobal = xpc::CompilationScope(); 1444 MOZ_RELEASE_ASSERT(nsContentUtils::IsSystemCaller(aCx) || 1445 JS::CurrentGlobalOrNull(aCx) == loaderGlobal); 1446 1447 uint32_t size; 1448 nsresult rv = aStream->Read32(&size); 1449 if (NS_FAILED(rv)) { 1450 return rv; 1451 } 1452 1453 char* data; 1454 rv = aStream->ReadBytes(size, &data); 1455 if (NS_FAILED(rv)) { 1456 return rv; 1457 } 1458 1459 // The decoded stencil shouldn't borrow from the XDR buffer. 1460 MOZ_ASSERT(!aOptions.borrowBuffer); 1461 auto cleanupData = MakeScopeExit([&]() { free(data); }); 1462 1463 JS::TranscodeRange range(reinterpret_cast<uint8_t*>(data), size); 1464 1465 { 1466 JS::TranscodeResult code; 1467 RefPtr<JS::Stencil> stencil; 1468 code = JS::DecodeStencil(aCx, aOptions, range, getter_AddRefs(stencil)); 1469 if (code != JS::TranscodeResult::Ok) { 1470 if (code == JS::TranscodeResult::Throw) { 1471 JS_ClearPendingException(aCx); 1472 return NS_ERROR_OUT_OF_MEMORY; 1473 } 1474 1475 MOZ_ASSERT(IsTranscodeFailureResult(code)); 1476 return NS_ERROR_FAILURE; 1477 } 1478 1479 stencil.forget(aStencilOut); 1480 } 1481 1482 return rv; 1483 } 1484 1485 void nsXULPrototypeScript::FillCompileOptions(JS::CompileOptions& aOptions, 1486 const char* aFilename, 1487 uint32_t aLineNo) { 1488 // NOTE: This method shouldn't change any field which also exists in 1489 // JS::InstantiateOptions. If such field is added, 1490 // nsXULPrototypeScript::InstantiateScript should also call this method. 1491 1492 // If the script was inline, tell the JS parser to save source for 1493 // Function.prototype.toSource(). If it's out of line, we retrieve the 1494 // source from the files on demand. 1495 aOptions.setSourceIsLazy(mOutOfLine); 1496 1497 aOptions.setIntroductionType(mOutOfLine ? "srcScript" : "inlineScript") 1498 .setFileAndLine(aFilename, mOutOfLine ? 1 : aLineNo); 1499 } 1500 1501 nsresult nsXULPrototypeScript::Serialize( 1502 nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc, 1503 const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) { 1504 NS_ENSURE_TRUE(aProtoDoc, NS_ERROR_UNEXPECTED); 1505 1506 NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mStencil, 1507 "script source still loading when serializing?!"); 1508 if (!mStencil) return NS_ERROR_FAILURE; 1509 1510 // Write basic prototype data 1511 nsresult rv; 1512 rv = aStream->Write32(mLineNo); 1513 if (NS_FAILED(rv)) return rv; 1514 1515 return WriteStencil(aStream, mStencil); 1516 } 1517 1518 nsresult nsXULPrototypeScript::SerializeOutOfLine( 1519 nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc) { 1520 if (!mSrcURI->SchemeIs("chrome")) 1521 // Don't cache scripts that don't come from chrome uris. 1522 return NS_ERROR_NOT_IMPLEMENTED; 1523 1524 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance(); 1525 if (!cache) return NS_ERROR_OUT_OF_MEMORY; 1526 1527 NS_ASSERTION(cache->IsEnabled(), 1528 "writing to the cache file, but the XUL cache is off?"); 1529 bool exists; 1530 cache->HasScript(mSrcURI, &exists); 1531 1532 /* return will be NS_OK from GetAsciiSpec. 1533 * that makes no sense. 1534 * nor does returning NS_OK from HasMuxedDocument. 1535 * XXX return something meaningful. 1536 */ 1537 if (exists) return NS_OK; 1538 1539 nsCOMPtr<nsIObjectOutputStream> oos; 1540 nsresult rv = cache->GetScriptOutputStream(mSrcURI, getter_AddRefs(oos)); 1541 NS_ENSURE_SUCCESS(rv, rv); 1542 1543 nsresult tmp = Serialize(oos, aProtoDoc, nullptr); 1544 if (NS_FAILED(tmp)) { 1545 rv = tmp; 1546 } 1547 tmp = cache->FinishScriptOutputStream(mSrcURI); 1548 if (NS_FAILED(tmp)) { 1549 rv = tmp; 1550 } 1551 1552 if (NS_FAILED(rv)) cache->AbortCaching(); 1553 return rv; 1554 } 1555 1556 nsresult nsXULPrototypeScript::Deserialize( 1557 nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc, 1558 nsIURI* aDocumentURI, 1559 const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) { 1560 nsresult rv; 1561 NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mStencil, 1562 "prototype script not well-initialized when deserializing?!"); 1563 1564 // Read basic prototype data 1565 rv = aStream->Read32(&mLineNo); 1566 if (NS_FAILED(rv)) return rv; 1567 1568 AutoJSAPI jsapi; 1569 if (!jsapi.Init(xpc::CompilationScope())) { 1570 return NS_ERROR_UNEXPECTED; 1571 } 1572 JSContext* cx = jsapi.cx(); 1573 1574 JS::DecodeOptions options; 1575 RefPtr<JS::Stencil> newStencil; 1576 rv = ReadStencil(aStream, cx, options, getter_AddRefs(newStencil)); 1577 NS_ENSURE_SUCCESS(rv, rv); 1578 Set(newStencil); 1579 return NS_OK; 1580 } 1581 1582 nsresult nsXULPrototypeScript::DeserializeOutOfLine( 1583 nsIObjectInputStream* aInput, nsXULPrototypeDocument* aProtoDoc) { 1584 // Keep track of failure via rv, so we can 1585 // AbortCaching if things look bad. 1586 nsresult rv = NS_OK; 1587 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance(); 1588 1589 nsCOMPtr<nsIObjectInputStream> objectInput = aInput; 1590 if (cache) { 1591 bool useXULCache = true; 1592 if (mSrcURI) { 1593 // NB: we must check the XUL script cache early, to avoid 1594 // multiple deserialization attempts for a given script. 1595 // Note that PrototypeDocumentContentSink::LoadScript 1596 // checks the XUL script cache too, in order to handle the 1597 // serialization case. 1598 // 1599 // We need do this only for <script src='strres.js'> and the 1600 // like, i.e., out-of-line scripts that are included by several 1601 // different XUL documents stored in the cache file. 1602 useXULCache = cache->IsEnabled(); 1603 1604 if (useXULCache) { 1605 RefPtr<JS::Stencil> newStencil = cache->GetStencil(mSrcURI); 1606 if (newStencil) { 1607 Set(newStencil); 1608 } 1609 } 1610 } 1611 1612 if (!mStencil) { 1613 if (mSrcURI) { 1614 rv = cache->GetScriptInputStream(mSrcURI, getter_AddRefs(objectInput)); 1615 } 1616 // If !mSrcURI, we have an inline script. We shouldn't have 1617 // to do anything else in that case, I think. 1618 1619 // We do reflect errors into rv, but our caller may want to 1620 // ignore our return value, because mStencil will be null 1621 // after any error, and that suffices to cause the script to 1622 // be reloaded (from the src= URI, if any) and recompiled. 1623 // We're better off slow-loading than bailing out due to a 1624 // error. 1625 if (NS_SUCCEEDED(rv)) 1626 rv = Deserialize(objectInput, aProtoDoc, nullptr, nullptr); 1627 1628 if (NS_SUCCEEDED(rv)) { 1629 if (useXULCache && mSrcURI && mSrcURI->SchemeIs("chrome")) { 1630 cache->PutStencil(mSrcURI, GetStencil()); 1631 } 1632 cache->FinishScriptInputStream(mSrcURI); 1633 } else { 1634 // If mSrcURI is not in the cache, 1635 // rv will be NS_ERROR_NOT_AVAILABLE and we'll try to 1636 // update the cache file to hold a serialization of 1637 // this script, once it has finished loading. 1638 if (rv != NS_ERROR_NOT_AVAILABLE) cache->AbortCaching(); 1639 } 1640 } 1641 } 1642 return rv; 1643 } 1644 1645 #ifdef DEBUG 1646 static void CheckErrorsAndWarnings(JS::FrontendContext* aFc, 1647 const JS::ReadOnlyCompileOptions& aOptions) { 1648 if (JS::HadFrontendErrors(aFc)) { 1649 const JSErrorReport* report = JS::GetFrontendErrorReport(aFc, aOptions); 1650 if (report) { 1651 const char* message = "<unknown>"; 1652 const char* filename = "<unknown>"; 1653 1654 if (report->message().c_str()) { 1655 message = report->message().c_str(); 1656 } 1657 if (report->filename.c_str()) { 1658 filename = report->filename.c_str(); 1659 } 1660 1661 NS_WARNING( 1662 nsPrintfCString( 1663 "Had compilation error in ScriptCompileTask: %s at %s:%u:%u", 1664 message, filename, report->lineno, 1665 report->column.oneOriginValue()) 1666 .get()); 1667 } 1668 1669 if (JS::HadFrontendOverRecursed(aFc)) { 1670 NS_WARNING("Had over recursed in ScriptCompileTask"); 1671 } 1672 1673 if (JS::HadFrontendOutOfMemory(aFc)) { 1674 NS_WARNING("Had out of memory in ScriptCompileTask"); 1675 } 1676 1677 if (JS::HadFrontendAllocationOverflow(aFc)) { 1678 NS_WARNING("Had allocation overflow in ScriptCompileTask"); 1679 } 1680 } 1681 1682 size_t count = JS::GetFrontendWarningCount(aFc); 1683 for (size_t i = 0; i < count; i++) { 1684 const JSErrorReport* report = JS::GetFrontendWarningAt(aFc, i, aOptions); 1685 1686 const char* message = "<unknown>"; 1687 const char* filename = "<unknown>"; 1688 1689 if (report->message().c_str()) { 1690 message = report->message().c_str(); 1691 } 1692 if (report->filename.c_str()) { 1693 filename = report->filename.c_str(); 1694 } 1695 1696 NS_WARNING( 1697 nsPrintfCString( 1698 "Had compilation warning in ScriptCompileTask: %s at %s:%u:%u", 1699 message, filename, report->lineno, report->column.oneOriginValue()) 1700 .get()); 1701 } 1702 } 1703 #endif 1704 1705 class ScriptCompileTask final : public Task { 1706 public: 1707 explicit ScriptCompileTask(UniquePtr<Utf8Unit[], JS::FreePolicy>&& aText, 1708 size_t aTextLength) 1709 : Task(Kind::OffMainThreadOnly, EventQueuePriority::Normal), 1710 mOptions(JS::OwningCompileOptions::ForFrontendContext()), 1711 mText(std::move(aText)), 1712 mTextLength(aTextLength) {} 1713 1714 ~ScriptCompileTask() { 1715 if (mFrontendContext) { 1716 JS::DestroyFrontendContext(mFrontendContext); 1717 } 1718 } 1719 1720 nsresult Init(JS::CompileOptions& aOptions) { 1721 mFrontendContext = JS::NewFrontendContext(); 1722 if (!mFrontendContext) { 1723 return NS_ERROR_FAILURE; 1724 } 1725 1726 if (!mOptions.copy(mFrontendContext, aOptions)) { 1727 return NS_ERROR_FAILURE; 1728 } 1729 1730 return NS_OK; 1731 } 1732 1733 private: 1734 void Compile() { 1735 // NOTE: The stack limit must be set from the same thread that compiles. 1736 size_t stackSize = TaskController::GetThreadStackSize(); 1737 JS::SetNativeStackQuota(mFrontendContext, 1738 JS::ThreadStackQuotaForSize(stackSize)); 1739 1740 JS::SourceText<Utf8Unit> srcBuf; 1741 if (NS_WARN_IF(!srcBuf.init(mFrontendContext, mText.get(), mTextLength, 1742 JS::SourceOwnership::Borrowed))) { 1743 return; 1744 } 1745 1746 mStencil = 1747 JS::CompileGlobalScriptToStencil(mFrontendContext, mOptions, srcBuf); 1748 #ifdef DEBUG 1749 // Chrome-privileged code shouldn't have any compilation error. 1750 CheckErrorsAndWarnings(mFrontendContext, mOptions); 1751 MOZ_ASSERT(mStencil); 1752 #endif 1753 } 1754 1755 public: 1756 TaskResult Run() override { 1757 Compile(); 1758 return TaskResult::Complete; 1759 } 1760 1761 already_AddRefed<JS::Stencil> StealStencil() { return mStencil.forget(); } 1762 1763 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY 1764 bool GetName(nsACString& aName) override { 1765 aName.AssignLiteral("ScriptCompileTask"); 1766 return true; 1767 } 1768 #endif 1769 1770 private: 1771 // Owning-pointer for the context associated with the script compilation. 1772 // 1773 // The context is allocated on main thread in Init method, and is freed on 1774 // any thread in the destructor. 1775 JS::FrontendContext* mFrontendContext = nullptr; 1776 1777 JS::OwningCompileOptions mOptions; 1778 1779 RefPtr<JS::Stencil> mStencil; 1780 1781 // The source text for this compilation. 1782 UniquePtr<Utf8Unit[], JS::FreePolicy> mText; 1783 size_t mTextLength; 1784 }; 1785 1786 class NotifyOffThreadScriptCompletedTask : public Task { 1787 public: 1788 NotifyOffThreadScriptCompletedTask(nsIOffThreadScriptReceiver* aReceiver, 1789 ScriptCompileTask* aCompileTask) 1790 : Task(Kind::MainThreadOnly, EventQueuePriority::Normal), 1791 mReceiver(aReceiver), 1792 mCompileTask(aCompileTask) {} 1793 1794 TaskResult Run() override { 1795 MOZ_ASSERT(NS_IsMainThread()); 1796 1797 if (PastShutdownPhase(ShutdownPhase::XPCOMShutdownFinal)) { 1798 return TaskResult::Complete; 1799 } 1800 1801 RefPtr<JS::Stencil> stencil = mCompileTask->StealStencil(); 1802 mCompileTask = nullptr; 1803 1804 (void)mReceiver->OnScriptCompileComplete( 1805 stencil, stencil ? NS_OK : NS_ERROR_FAILURE); 1806 1807 return TaskResult::Complete; 1808 } 1809 1810 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY 1811 bool GetName(nsACString& aName) override { 1812 aName.AssignLiteral("NotifyOffThreadScriptCompletedTask"); 1813 return true; 1814 } 1815 #endif 1816 1817 private: 1818 // NOTE: 1819 // This field is main-thread only, and this task shouldn't be freed off 1820 // main thread. 1821 // 1822 // This is guaranteed by not having off-thread tasks which depends on this 1823 // task, or any other pointer from off-thread task to this task, because 1824 // otherwise the off-thread task's mDependencies can be the last reference, 1825 // which results in freeing this task off main thread. 1826 // 1827 // If such task is added, this field must be moved to separate storage. 1828 nsCOMPtr<nsIOffThreadScriptReceiver> mReceiver; 1829 1830 RefPtr<ScriptCompileTask> mCompileTask; 1831 }; 1832 1833 nsresult StartOffThreadCompile(JS::CompileOptions& aOptions, 1834 UniquePtr<Utf8Unit[], JS::FreePolicy>&& aText, 1835 size_t aTextLength, 1836 nsIOffThreadScriptReceiver* aOffThreadReceiver) { 1837 RefPtr<ScriptCompileTask> compileTask = 1838 new ScriptCompileTask(std::move(aText), aTextLength); 1839 1840 RefPtr<NotifyOffThreadScriptCompletedTask> notifyTask = 1841 new NotifyOffThreadScriptCompletedTask(aOffThreadReceiver, compileTask); 1842 1843 nsresult rv = compileTask->Init(aOptions); 1844 NS_ENSURE_SUCCESS(rv, rv); 1845 1846 notifyTask->AddDependency(compileTask.get()); 1847 1848 TaskController::Get()->AddTask(compileTask.forget()); 1849 TaskController::Get()->AddTask(notifyTask.forget()); 1850 1851 return NS_OK; 1852 } 1853 1854 nsresult nsXULPrototypeScript::Compile(const char16_t* aText, 1855 size_t aTextLength, nsIURI* aURI, 1856 uint32_t aLineNo, Document* aDocument) { 1857 AutoJSAPI jsapi; 1858 if (!jsapi.Init(xpc::CompilationScope())) { 1859 return NS_ERROR_UNEXPECTED; 1860 } 1861 JSContext* cx = jsapi.cx(); 1862 1863 JS::SourceText<char16_t> srcBuf; 1864 if (NS_WARN_IF(!srcBuf.init(cx, aText, aTextLength, 1865 JS::SourceOwnership::Borrowed))) { 1866 return NS_ERROR_FAILURE; 1867 } 1868 1869 nsAutoCString urlspec; 1870 nsresult rv = aURI->GetSpec(urlspec); 1871 if (NS_WARN_IF(NS_FAILED(rv))) { 1872 return rv; 1873 } 1874 1875 JS::CompileOptions options(cx); 1876 FillCompileOptions(options, urlspec.get(), aLineNo); 1877 1878 RefPtr<JS::Stencil> stencil = 1879 JS::CompileGlobalScriptToStencil(cx, options, srcBuf); 1880 if (!stencil) { 1881 return NS_ERROR_OUT_OF_MEMORY; 1882 } 1883 Set(stencil); 1884 return NS_OK; 1885 } 1886 1887 nsresult nsXULPrototypeScript::CompileMaybeOffThread( 1888 mozilla::UniquePtr<mozilla::Utf8Unit[], JS::FreePolicy>&& aText, 1889 size_t aTextLength, nsIURI* aURI, uint32_t aLineNo, Document* aDocument, 1890 nsIOffThreadScriptReceiver* aOffThreadReceiver) { 1891 MOZ_ASSERT(aOffThreadReceiver); 1892 1893 nsAutoCString urlspec; 1894 nsresult rv = aURI->GetSpec(urlspec); 1895 if (NS_WARN_IF(NS_FAILED(rv))) { 1896 return rv; 1897 } 1898 1899 AutoJSAPI jsapi; 1900 if (!jsapi.Init(xpc::CompilationScope())) { 1901 return NS_ERROR_UNEXPECTED; 1902 } 1903 JSContext* cx = jsapi.cx(); 1904 1905 JS::CompileOptions options(cx); 1906 FillCompileOptions(options, urlspec.get(), aLineNo); 1907 1908 // TODO: This uses the same heuristics and the same threshold as the 1909 // JS::CanDecodeOffThread API, but the heuristics needs to be updated 1910 // to reflect the change regarding the Stencil API, and also the thread 1911 // management on the consumer side (bug 1840831). 1912 static constexpr size_t OffThreadMinimumTextLength = 5 * 1000; 1913 1914 if (StaticPrefs::javascript_options_parallel_parsing() && 1915 aTextLength >= OffThreadMinimumTextLength) { 1916 rv = StartOffThreadCompile(options, std::move(aText), aTextLength, 1917 aOffThreadReceiver); 1918 if (NS_WARN_IF(NS_FAILED(rv))) { 1919 return rv; 1920 } 1921 } else { 1922 JS::SourceText<Utf8Unit> srcBuf; 1923 if (NS_WARN_IF(!srcBuf.init(cx, aText.get(), aTextLength, 1924 JS::SourceOwnership::Borrowed))) { 1925 return NS_ERROR_FAILURE; 1926 } 1927 1928 RefPtr<JS::Stencil> stencil = 1929 JS::CompileGlobalScriptToStencil(cx, options, srcBuf); 1930 if (!stencil) { 1931 return NS_ERROR_OUT_OF_MEMORY; 1932 } 1933 Set(stencil); 1934 } 1935 return NS_OK; 1936 } 1937 1938 nsresult nsXULPrototypeScript::InstantiateScript( 1939 JSContext* aCx, JS::MutableHandle<JSScript*> aScript) { 1940 MOZ_ASSERT(mStencil); 1941 1942 JS::CompileOptions options(aCx); 1943 JS::InstantiateOptions instantiateOptions(options); 1944 aScript.set(JS::InstantiateGlobalStencil(aCx, instantiateOptions, mStencil)); 1945 if (!aScript) { 1946 JS_ClearPendingException(aCx); 1947 return NS_ERROR_OUT_OF_MEMORY; 1948 } 1949 1950 return NS_OK; 1951 } 1952 1953 void nsXULPrototypeScript::Set(JS::Stencil* aStencil) { mStencil = aStencil; } 1954 1955 void nsXULPrototypeScript::AddSizeOfExcludingThis(nsWindowSizes& aSizes, 1956 size_t* aNodeSize) const { 1957 // It is okay to include the size of mSrcURI here even though it might have 1958 // strong references from elsewhere because the URI was created for this 1959 // object, in XULContentSinkImpl::OpenScript() or 1960 // nsXULPrototypeElement::Deserialize(). Only objects that created their own 1961 // URI will call nsIURI::SizeOfIncludingThis(). 1962 if (mSrcURI) { 1963 *aNodeSize += mSrcURI->SizeOfIncludingThis(aSizes.mState.mMallocSizeOf); 1964 } 1965 } 1966 1967 //---------------------------------------------------------------------- 1968 // 1969 // nsXULPrototypeText 1970 // 1971 1972 nsresult nsXULPrototypeText::Serialize( 1973 nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc, 1974 const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) { 1975 nsresult rv; 1976 1977 // Write basic prototype data 1978 rv = aStream->Write32(mType); 1979 1980 nsresult tmp = aStream->WriteWStringZ(mValue.get()); 1981 if (NS_FAILED(tmp)) { 1982 rv = tmp; 1983 } 1984 1985 return rv; 1986 } 1987 1988 nsresult nsXULPrototypeText::Deserialize( 1989 nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc, 1990 nsIURI* aDocumentURI, 1991 const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) { 1992 nsresult rv = aStream->ReadString(mValue); 1993 if (NS_WARN_IF(NS_FAILED(rv))) { 1994 return rv; 1995 } 1996 return NS_OK; 1997 } 1998 1999 //---------------------------------------------------------------------- 2000 // 2001 // nsXULPrototypePI 2002 // 2003 2004 nsresult nsXULPrototypePI::Serialize( 2005 nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc, 2006 const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) { 2007 nsresult rv; 2008 2009 // Write basic prototype data 2010 rv = aStream->Write32(mType); 2011 2012 nsresult tmp = aStream->WriteWStringZ(mTarget.get()); 2013 if (NS_FAILED(tmp)) { 2014 rv = tmp; 2015 } 2016 tmp = aStream->WriteWStringZ(mData.get()); 2017 if (NS_FAILED(tmp)) { 2018 rv = tmp; 2019 } 2020 2021 return rv; 2022 } 2023 2024 nsresult nsXULPrototypePI::Deserialize( 2025 nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc, 2026 nsIURI* aDocumentURI, 2027 const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) { 2028 nsresult rv; 2029 2030 rv = aStream->ReadString(mTarget); 2031 if (NS_FAILED(rv)) return rv; 2032 rv = aStream->ReadString(mData); 2033 if (NS_FAILED(rv)) return rv; 2034 2035 return rv; 2036 }