tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }