dom/xul/nsXULElement.cpp
author Dave Townsend <dtownsend@oxymoronical.com>
Sun, 13 Jan 2019 17:44:29 -0800
changeset 453676 edca8877b0505cd1c31beaf6d907ca32e022aa52
parent 453079 9663d8100bdb65dda241c578c5279075a6d07c3a
child 453855 b0ae73c11374d34f061ef138a63fd758358b5949
permissions -rw-r--r--
Backing out Bug 1518799 (changeset eace4709948c) because the target page is not yet available. a=backout

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsCOMPtr.h"
#include "nsDOMCID.h"
#include "nsError.h"
#include "nsDOMString.h"
#include "nsAtom.h"
#include "nsIBaseWindow.h"
#include "nsIDOMEventListener.h"
#include "nsIDOMXULCommandDispatcher.h"
#include "nsIDOMXULSelectCntrlItemEl.h"
#include "mozilla/dom/Document.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/EventStates.h"
#include "mozilla/DeclarationBlock.h"
#include "js/CompilationAndEvaluation.h"
#include "js/SourceText.h"
#include "nsFocusManager.h"
#include "nsHTMLStyleSheet.h"
#include "nsNameSpaceManager.h"
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.h"
#include "nsIPresShell.h"
#include "nsIPrincipal.h"
#include "nsIScriptContext.h"
#include "nsIScriptError.h"
#include "nsIScriptSecurityManager.h"
#include "nsIServiceManager.h"
#include "nsIURL.h"
#include "nsViewManager.h"
#include "nsIWidget.h"
#include "nsLayoutCID.h"
#include "nsContentCID.h"
#include "mozilla/dom/Event.h"
#include "nsStyleConsts.h"
#include "nsString.h"
#include "nsXULControllers.h"
#include "nsIBoxObject.h"
#include "nsPIBoxObject.h"
#include "XULDocument.h"
#include "nsXULPopupListener.h"
#include "nsContentUtils.h"
#include "nsContentList.h"
#include "mozilla/InternalMutationEvent.h"
#include "mozilla/MouseEvents.h"
#include "nsPIDOMWindow.h"
#include "nsJSPrincipals.h"
#include "nsDOMAttributeMap.h"
#include "nsGkAtoms.h"
#include "nsNodeUtils.h"
#include "nsFrameLoader.h"
#include "mozilla/Logging.h"
#include "nsIControllers.h"
#include "nsAttrValueOrString.h"
#include "nsAttrValueInlines.h"
#include "mozilla/Attributes.h"
#include "nsIController.h"
#include "nsQueryObject.h"
#include <algorithm>
#include "nsIDOMChromeWindow.h"

#include "nsReadableUtils.h"
#include "nsIFrame.h"
#include "nsNodeInfoManager.h"
#include "nsXBLBinding.h"
#include "nsXULTooltipListener.h"
#include "mozilla/EventDispatcher.h"
#include "mozAutoDocUpdate.h"
#include "nsCCUncollectableMarker.h"
#include "nsICSSDeclaration.h"
#include "nsLayoutUtils.h"
#include "XULFrameElement.h"
#include "XULMenuElement.h"
#include "XULPopupElement.h"
#include "XULScrollElement.h"

#include "mozilla/dom/XULElementBinding.h"
#include "mozilla/dom/BoxObject.h"
#include "mozilla/dom/XULBroadcastManager.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "mozilla/dom/MutationEventBinding.h"
#include "mozilla/dom/XULCommandEvent.h"

using namespace mozilla;
using namespace mozilla::dom;

#ifdef XUL_PROTOTYPE_ATTRIBUTE_METERING
uint32_t nsXULPrototypeAttribute::gNumElements;
uint32_t nsXULPrototypeAttribute::gNumAttributes;
uint32_t nsXULPrototypeAttribute::gNumCacheTests;
uint32_t nsXULPrototypeAttribute::gNumCacheHits;
uint32_t nsXULPrototypeAttribute::gNumCacheSets;
uint32_t nsXULPrototypeAttribute::gNumCacheFills;
#endif

#define NS_DISPATCH_XUL_COMMAND (1 << 0)

//----------------------------------------------------------------------
// nsXULElement
//

nsXULElement::nsXULElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
    : nsStyledElement(std::move(aNodeInfo)), mBindingParent(nullptr) {
  XUL_PROTOTYPE_ATTRIBUTE_METER(gNumElements);

  // We may be READWRITE by default; check.
  if (IsReadWriteTextElement()) {
    AddStatesSilently(NS_EVENT_STATE_MOZ_READWRITE);
    RemoveStatesSilently(NS_EVENT_STATE_MOZ_READONLY);
  }
}

nsXULElement::~nsXULElement() {}

void nsXULElement::MaybeUpdatePrivateLifetime() {
  if (AttrValueIs(kNameSpaceID_None, nsGkAtoms::windowtype,
                  NS_LITERAL_STRING("navigator:browser"), eCaseMatters)) {
    return;
  }

  nsPIDOMWindowOuter* win = OwnerDoc()->GetWindow();
  nsCOMPtr<nsIDocShell> docShell = win ? win->GetDocShell() : nullptr;
  if (docShell) {
    docShell->SetAffectPrivateSessionLifetime(false);
  }
}

/* static */
nsXULElement* NS_NewBasicXULElement(
    already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) {
  return new nsXULElement(std::move(aNodeInfo));
}

/* static */
nsXULElement* nsXULElement::Construct(
    already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) {
  RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo;
  if (nodeInfo->Equals(nsGkAtoms::label) ||
      nodeInfo->Equals(nsGkAtoms::description)) {
    return new XULTextElement(nodeInfo.forget());
  }

  if (nodeInfo->Equals(nsGkAtoms::menupopup) ||
      nodeInfo->Equals(nsGkAtoms::popup) ||
      nodeInfo->Equals(nsGkAtoms::panel)) {
    return NS_NewXULPopupElement(nodeInfo.forget());
  }

  if (nodeInfo->Equals(nsGkAtoms::tooltip)) {
    return NS_NewXULTooltipElement(nodeInfo.forget());
  }

  if (nodeInfo->Equals(nsGkAtoms::iframe) ||
      nodeInfo->Equals(nsGkAtoms::browser) ||
      nodeInfo->Equals(nsGkAtoms::editor)) {
    return new XULFrameElement(nodeInfo.forget());
  }

  if (nodeInfo->Equals(nsGkAtoms::menu) ||
      nodeInfo->Equals(nsGkAtoms::menulist)) {
    return new XULMenuElement(nodeInfo.forget());
  }

  if (nodeInfo->Equals(nsGkAtoms::scrollbox)) {
    return new XULScrollElement(nodeInfo.forget());
  }

  return NS_NewBasicXULElement(nodeInfo.forget());
}

/* static */
already_AddRefed<nsXULElement> nsXULElement::CreateFromPrototype(
    nsXULPrototypeElement* aPrototype, mozilla::dom::NodeInfo* aNodeInfo,
    bool aIsScriptable, bool aIsRoot) {
  RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
  nsCOMPtr<Element> baseElement;
  NS_NewXULElement(getter_AddRefs(baseElement), ni.forget(),
                   dom::FROM_PARSER_NETWORK, aPrototype->mIsAtom);

  if (baseElement) {
    nsXULElement* element = FromNode(baseElement);

    if (aPrototype->mHasIdAttribute) {
      element->SetHasID();
    }
    if (aPrototype->mHasClassAttribute) {
      element->SetMayHaveClass();
    }
    if (aPrototype->mHasStyleAttribute) {
      element->SetMayHaveStyle();
    }

    element->MakeHeavyweight(aPrototype);
    if (aIsScriptable) {
      // Check each attribute on the prototype to see if we need to do
      // any additional processing and hookup that would otherwise be
      // done 'automagically' by SetAttr().
      for (uint32_t i = 0; i < aPrototype->mNumAttributes; ++i) {
        element->AddListenerFor(aPrototype->mAttributes[i].mName);
      }
    }

    if (aIsRoot && aPrototype->mNodeInfo->Equals(nsGkAtoms::window)) {
      for (uint32_t i = 0; i < aPrototype->mNumAttributes; ++i) {
        if (aPrototype->mAttributes[i].mName.Equals(nsGkAtoms::windowtype)) {
          element->MaybeUpdatePrivateLifetime();
        }
      }
    }

    return baseElement.forget().downcast<nsXULElement>();
  }

  return nullptr;
}

nsresult nsXULElement::CreateFromPrototype(nsXULPrototypeElement* aPrototype,
                                           Document* aDocument,
                                           bool aIsScriptable, bool aIsRoot,
                                           Element** aResult) {
  // Create an nsXULElement from a prototype
  MOZ_ASSERT(aPrototype != nullptr, "null ptr");
  if (!aPrototype) return NS_ERROR_NULL_POINTER;

  MOZ_ASSERT(aResult != nullptr, "null ptr");
  if (!aResult) return NS_ERROR_NULL_POINTER;

  RefPtr<mozilla::dom::NodeInfo> nodeInfo;
  if (aDocument) {
    mozilla::dom::NodeInfo* ni = aPrototype->mNodeInfo;
    nodeInfo = aDocument->NodeInfoManager()->GetNodeInfo(
        ni->NameAtom(), ni->GetPrefixAtom(), ni->NamespaceID(), ELEMENT_NODE);
  } else {
    nodeInfo = aPrototype->mNodeInfo;
  }

  RefPtr<nsXULElement> element =
      CreateFromPrototype(aPrototype, nodeInfo, aIsScriptable, aIsRoot);
  element.forget(aResult);

  return NS_OK;
}

nsresult NS_NewXULElement(Element** aResult,
                          already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
                          FromParser aFromParser, nsAtom* aIsAtom,
                          mozilla::dom::CustomElementDefinition* aDefinition) {
  RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo;

  MOZ_ASSERT(nodeInfo, "need nodeinfo for non-proto Create");

  NS_ASSERTION(
      nodeInfo->NamespaceEquals(kNameSpaceID_XUL),
      "Trying to create XUL elements that don't have the XUL namespace");

  Document* doc = nodeInfo->GetDocument();
  if (doc && !doc->AllowXULXBL()) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  return nsContentUtils::NewXULOrHTMLElement(aResult, nodeInfo, aFromParser,
                                             aIsAtom, aDefinition);
}

void NS_TrustedNewXULElement(
    Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) {
  RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
  MOZ_ASSERT(ni, "need nodeinfo for non-proto Create");

  // Create an nsXULElement with the specified namespace and tag.
  NS_ADDREF(*aResult = nsXULElement::Construct(ni.forget()));
}

//----------------------------------------------------------------------
// nsISupports interface

NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULElement)

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsXULElement, nsStyledElement)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBindingParent);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsXULElement, nsStyledElement)
  // Why aren't we unlinking the prototype?
  tmp->ClearHasID();
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mBindingParent);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_ADDREF_INHERITED(nsXULElement, nsStyledElement)
NS_IMPL_RELEASE_INHERITED(nsXULElement, nsStyledElement)

NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsXULElement)
  NS_ELEMENT_INTERFACE_TABLE_TO_MAP_SEGUE
NS_INTERFACE_MAP_END_INHERITING(nsStyledElement)

//----------------------------------------------------------------------
// nsINode interface

nsresult nsXULElement::Clone(mozilla::dom::NodeInfo* aNodeInfo,
                             nsINode** aResult) const {
  *aResult = nullptr;

  RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
  RefPtr<nsXULElement> element = Construct(ni.forget());

  nsresult rv = element->mAttrs.EnsureCapacityToClone(mAttrs);
  NS_ENSURE_SUCCESS(rv, rv);

  // Note that we're _not_ copying mControllers.

  uint32_t count = mAttrs.AttrCount();
  rv = NS_OK;
  for (uint32_t i = 0; i < count; ++i) {
    const nsAttrName* originalName = mAttrs.AttrNameAt(i);
    const nsAttrValue* originalValue = mAttrs.AttrAt(i);
    nsAttrValue attrValue;

    // Style rules need to be cloned.
    if (originalValue->Type() == nsAttrValue::eCSSDeclaration) {
      DeclarationBlock* decl = originalValue->GetCSSDeclarationValue();
      RefPtr<DeclarationBlock> declClone = decl->Clone();

      nsString stringValue;
      originalValue->ToString(stringValue);

      attrValue.SetTo(declClone.forget(), &stringValue);
    } else {
      attrValue.SetTo(*originalValue);
    }

    bool oldValueSet;
    if (originalName->IsAtom()) {
      rv = element->mAttrs.SetAndSwapAttr(originalName->Atom(), attrValue,
                                          &oldValueSet);
    } else {
      rv = element->mAttrs.SetAndSwapAttr(originalName->NodeInfo(), attrValue,
                                          &oldValueSet);
    }
    NS_ENSURE_SUCCESS(rv, rv);
    element->AddListenerFor(*originalName);
    if (originalName->Equals(nsGkAtoms::id) &&
        !originalValue->IsEmptyString()) {
      element->SetHasID();
    }
    if (originalName->Equals(nsGkAtoms::_class)) {
      element->SetMayHaveClass();
    }
    if (originalName->Equals(nsGkAtoms::style)) {
      element->SetMayHaveStyle();
    }
  }

  element.forget(aResult);
  return rv;
}

//----------------------------------------------------------------------

EventListenerManager* nsXULElement::GetEventListenerManagerForAttr(
    nsAtom* aAttrName, bool* aDefer) {
  // XXXbz sXBL/XBL2 issue: should we instead use GetComposedDoc()
  // here, override BindToTree for those classes and munge event
  // listeners there?
  Document* doc = OwnerDoc();

  nsPIDOMWindowInner* window;
  Element* root = doc->GetRootElement();
  if ((!root || root == this) && (window = doc->GetInnerWindow())) {
    nsCOMPtr<EventTarget> piTarget = do_QueryInterface(window);

    *aDefer = false;
    return piTarget->GetOrCreateListenerManager();
  }

  return nsStyledElement::GetEventListenerManagerForAttr(aAttrName, aDefer);
}

// returns true if the element is not a list
static bool IsNonList(mozilla::dom::NodeInfo* aNodeInfo) {
  return !aNodeInfo->Equals(nsGkAtoms::tree) &&
         !aNodeInfo->Equals(nsGkAtoms::richlistbox);
}

bool nsXULElement::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) {
  /*
   * Returns true if an element may be focused, and false otherwise. The inout
   * argument aTabIndex will be set to the tab order index to be used; -1 for
   * elements that should not be part of the tab order and a greater value to
   * indicate its tab order.
   *
   * Confusingly, the supplied value for the aTabIndex argument may indicate
   * whether the element may be focused as a result of the -moz-user-focus
   * property, where -1 means no and 0 means yes.
   *
   * For controls, the element cannot be focused and is not part of the tab
   * order if it is disabled.
   *
   * -moz-user-focus is overridden if a tabindex (even -1) is specified.
   *
   * Specifically, the behaviour for all XUL elements is as follows:
   *  *aTabIndex = -1  no tabindex     Not focusable or tabbable
   *  *aTabIndex = -1  tabindex="-1"   Focusable but not tabbable
   *  *aTabIndex = -1  tabindex=">=0"  Focusable and tabbable
   *  *aTabIndex >= 0  no tabindex     Focusable and tabbable
   *  *aTabIndex >= 0  tabindex="-1"   Focusable but not tabbable
   *  *aTabIndex >= 0  tabindex=">=0"  Focusable and tabbable
   *
   * If aTabIndex is null, then the tabindex is not computed, and
   * true is returned for non-disabled controls and false otherwise.
   */

  // elements are not focusable by default
  bool shouldFocus = false;

#ifdef XP_MACOSX
  // on Mac, mouse interactions only focus the element if it's a list,
  // or if it's a remote target, since the remote target must handle
  // the focus.
  if (aWithMouse && IsNonList(mNodeInfo) &&
      !EventStateManager::IsRemoteTarget(this)) {
    return false;
  }
#endif

  nsCOMPtr<nsIDOMXULControlElement> xulControl = AsXULControl();
  if (xulControl) {
    // a disabled element cannot be focused and is not part of the tab order
    bool disabled;
    xulControl->GetDisabled(&disabled);
    if (disabled) {
      if (aTabIndex) *aTabIndex = -1;
      return false;
    }
    shouldFocus = true;
  }

  if (aTabIndex) {
    if (HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) {
      // The tabindex attribute was specified, so the element becomes
      // focusable.
      shouldFocus = true;
      *aTabIndex = TabIndex();
    } else {
      // otherwise, if there is no tabindex attribute, just use the value of
      // *aTabIndex to indicate focusability. Reset any supplied tabindex to 0.
      shouldFocus = *aTabIndex >= 0;
      if (shouldFocus) {
        *aTabIndex = 0;
      }
    }

    if (xulControl && shouldFocus && sTabFocusModelAppliesToXUL &&
        !(sTabFocusModel & eTabFocus_formElementsMask)) {
      // By default, the tab focus model doesn't apply to xul element on any
      // system but OS X. on OS X we're following it for UI elements (XUL) as
      // sTabFocusModel is based on "Full Keyboard Access" system setting (see
      // mac/nsILookAndFeel). both textboxes and list elements (i.e. trees and
      // list) should always be focusable (textboxes are handled as html:input)
      // For compatibility, we only do this for controls, otherwise elements
      // like <browser> cannot take this focus.
      if (IsNonList(mNodeInfo)) {
        *aTabIndex = -1;
      }
    }
  }

  return shouldFocus;
}

bool nsXULElement::HasMenu() {
  nsMenuFrame* menu = do_QueryFrame(GetPrimaryFrame());
  return menu != nullptr;
}

void nsXULElement::OpenMenu(bool aOpenFlag) {
  nsMenuFrame* menu = do_QueryFrame(GetPrimaryFrame(FlushType::Frames));

  nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
  if (pm) {
    if (aOpenFlag) {
      // Nothing will happen if this element isn't a menu.
      pm->ShowMenu(this, false, false);
    } else if (menu) {
      nsMenuPopupFrame* popupFrame = menu->GetPopup();
      if (popupFrame) {
        pm->HidePopup(popupFrame->GetContent(), false, true, false, false);
      }
    }
  }
}

bool nsXULElement::PerformAccesskey(bool aKeyCausesActivation,
                                    bool aIsTrustedEvent) {
  RefPtr<Element> content(this);

  if (IsXULElement(nsGkAtoms::label)) {
    nsAutoString control;
    GetAttr(kNameSpaceID_None, nsGkAtoms::control, control);
    if (control.IsEmpty()) {
      return false;
    }

    // XXXsmaug Should we use ShadowRoot::GetElementById in case
    //         content is in Shadow DOM?
    nsCOMPtr<Document> document = content->GetUncomposedDoc();
    if (!document) {
      return false;
    }

    content = document->GetElementById(control);
    if (!content) {
      return false;
    }
  }

  nsIFrame* frame = content->GetPrimaryFrame();
  if (!frame || !frame->IsVisibleConsideringAncestors()) {
    return false;
  }

  bool focused = false;
  nsXULElement* elm = FromNode(content);
  if (elm) {
    // Define behavior for each type of XUL element.
    if (!content->IsXULElement(nsGkAtoms::toolbarbutton)) {
      nsIFocusManager* fm = nsFocusManager::GetFocusManager();
      if (fm) {
        nsCOMPtr<Element> elementToFocus;
        // for radio buttons, focus the radiogroup instead
        if (content->IsXULElement(nsGkAtoms::radio)) {
          nsCOMPtr<nsIDOMXULSelectControlItemElement> controlItem =
              content->AsXULSelectControlItem();
          if (controlItem) {
            bool disabled;
            controlItem->GetDisabled(&disabled);
            if (!disabled) {
              controlItem->GetControl(getter_AddRefs(elementToFocus));
            }
          }
        } else {
          elementToFocus = content;
        }
        if (elementToFocus) {
          fm->SetFocus(elementToFocus, nsIFocusManager::FLAG_BYKEY);

          // Return true if the element became focused.
          nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow();
          focused = (window && window->GetFocusedElement());
        }
      }
    }
    if (aKeyCausesActivation &&
        !content->IsAnyOfXULElements(nsGkAtoms::textbox, nsGkAtoms::menulist)) {
      elm->ClickWithInputSource(MouseEvent_Binding::MOZ_SOURCE_KEYBOARD,
                                aIsTrustedEvent);
    }
  } else {
    return content->PerformAccesskey(aKeyCausesActivation, aIsTrustedEvent);
  }

  return focused;
}

//----------------------------------------------------------------------

void nsXULElement::AddListenerFor(const nsAttrName& aName) {
  // If appropriate, add a popup listener and/or compile the event
  // handler. Called when we change the element's document, create a
  // new element, change an attribute's value, etc.
  // Eventlistenener-attributes are always in the null namespace
  if (aName.IsAtom()) {
    nsAtom* attr = aName.Atom();
    MaybeAddPopupListener(attr);
    if (nsContentUtils::IsEventAttributeName(attr, EventNameType_XUL)) {
      nsAutoString value;
      GetAttr(kNameSpaceID_None, attr, value);
      SetEventHandler(attr, value, true);
    }
  }
}

void nsXULElement::MaybeAddPopupListener(nsAtom* aLocalName) {
  // If appropriate, add a popup listener. Called when we change the
  // element's document, create a new element, change an attribute's
  // value, etc.
  if (aLocalName == nsGkAtoms::menu || aLocalName == nsGkAtoms::contextmenu ||
      // XXXdwh popup and context are deprecated
      aLocalName == nsGkAtoms::popup || aLocalName == nsGkAtoms::context) {
    AddPopupListener(aLocalName);
  }
}

//----------------------------------------------------------------------
//
// nsIContent interface
//
void nsXULElement::UpdateEditableState(bool aNotify) {
  // Don't call through to Element here because the things
  // it does don't work for cases when we're an editable control.
  nsIContent* parent = GetParent();

  SetEditableFlag(parent && parent->HasFlag(NODE_IS_EDITABLE));
  UpdateState(aNotify);
}

class XULInContentErrorReporter : public Runnable {
 public:
  explicit XULInContentErrorReporter(Document* aDocument)
      : mozilla::Runnable("XULInContentErrorReporter"), mDocument(aDocument) {}

  NS_IMETHOD Run() override {
    mDocument->WarnOnceAbout(Document::eImportXULIntoContent, false);
    return NS_OK;
  }

 private:
  nsCOMPtr<Document> mDocument;
};

static bool NeedTooltipSupport(const nsXULElement& aXULElement) {
  if (aXULElement.NodeInfo()->Equals(nsGkAtoms::treechildren)) {
    // treechildren always get tooltip support, since cropped tree cells show
    // their full text in a tooltip.
    return true;
  }

  return aXULElement.GetBoolAttr(nsGkAtoms::tooltip) ||
         aXULElement.GetBoolAttr(nsGkAtoms::tooltiptext);
}

nsresult nsXULElement::BindToTree(Document* aDocument, nsIContent* aParent,
                                  nsIContent* aBindingParent) {
  if (!aBindingParent && aDocument && !aDocument->IsLoadedAsInteractiveData() &&
      !aDocument->AllowXULXBL() &&
      !aDocument->HasWarnedAbout(Document::eImportXULIntoContent)) {
    nsContentUtils::AddScriptRunner(new XULInContentErrorReporter(aDocument));
  }

  nsresult rv = nsStyledElement::BindToTree(aDocument, aParent, aBindingParent);
  NS_ENSURE_SUCCESS(rv, rv);

  Document* doc = GetComposedDoc();
#ifdef DEBUG
  if (doc && !doc->AllowXULXBL() && !doc->IsUnstyledDocument()) {
    // To save CPU cycles and memory, non-XUL documents only load the user
    // agent style sheet rules for a minimal set of XUL elements such as
    // 'scrollbar' that may be created implicitly for their content (those
    // rules being in minimal-xul.css).
    //
    // This assertion makes sure no other XUL element is used in a non-XUL
    // document.
    nsAtom* tag = NodeInfo()->NameAtom();
    MOZ_ASSERT(
        // scrollbar parts
        tag == nsGkAtoms::scrollbar || tag == nsGkAtoms::scrollbarbutton ||
            tag == nsGkAtoms::scrollcorner || tag == nsGkAtoms::slider ||
            tag == nsGkAtoms::thumb ||
            // other
            tag == nsGkAtoms::datetimebox || tag == nsGkAtoms::resizer ||
            tag == nsGkAtoms::label || tag == nsGkAtoms::videocontrols,
        "Unexpected XUL element in non-XUL doc");
  }
#endif

  if (doc && NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
    // Create our XUL key listener and hook it up.
    nsXBLService::AttachGlobalKeyHandler(this);
  }

  if (doc && NeedTooltipSupport(*this)) {
    AddTooltipSupport();
  }

  if (doc && XULBroadcastManager::MayNeedListener(*this)) {
    if (!doc->HasXULBroadcastManager()) {
      doc->InitializeXULBroadcastManager();
    }
    XULBroadcastManager* broadcastManager = doc->GetXULBroadcastManager();
    broadcastManager->AddListener(this);
  }

  return rv;
}

void nsXULElement::UnbindFromTree(bool aDeep, bool aNullParent) {
  if (NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
    nsXBLService::DetachGlobalKeyHandler(this);
  }

  if (NeedTooltipSupport(*this)) {
    RemoveTooltipSupport();
  }

  Document* doc = GetComposedDoc();
  if (doc && doc->HasXULBroadcastManager() &&
      XULBroadcastManager::MayNeedListener(*this)) {
    RefPtr<XULBroadcastManager> broadcastManager =
        doc->GetXULBroadcastManager();
    broadcastManager->RemoveListener(this);
  }

  // mControllers can own objects that are implemented
  // in JavaScript (such as some implementations of
  // nsIControllers.  These objects prevent their global
  // object's script object from being garbage collected,
  // which means JS continues to hold an owning reference
  // to the nsGlobalWindow, which owns the document,
  // which owns this content.  That's a cycle, so we break
  // it here.  (It might be better to break this by releasing
  // mDocument in nsGlobalWindow::SetDocShell, but I'm not
  // sure whether that would fix all possible cycles through
  // mControllers.)
  nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
  if (slots) {
    slots->mControllers = nullptr;
  }

  nsStyledElement::UnbindFromTree(aDeep, aNullParent);
}

void nsXULElement::UnregisterAccessKey(const nsAString& aOldValue) {
  // If someone changes the accesskey, unregister the old one
  //
  Document* doc = GetComposedDoc();
  if (doc && !aOldValue.IsEmpty()) {
    nsIPresShell* shell = doc->GetShell();

    if (shell) {
      Element* element = this;

      // find out what type of content node this is
      if (mNodeInfo->Equals(nsGkAtoms::label)) {
        // For anonymous labels the unregistering must
        // occur on the binding parent control.
        // XXXldb: And what if the binding parent is null?
        nsIContent* bindingParent = GetBindingParent();
        element = bindingParent ? bindingParent->AsElement() : nullptr;
      }

      if (element) {
        shell->GetPresContext()->EventStateManager()->UnregisterAccessKey(
            element, aOldValue.First());
      }
    }
  }
}

nsresult nsXULElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
                                     const nsAttrValueOrString* aValue,
                                     bool aNotify) {
  if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::accesskey &&
      IsInUncomposedDoc()) {
    nsAutoString oldValue;
    if (GetAttr(aNamespaceID, aName, oldValue)) {
      UnregisterAccessKey(oldValue);
    }
  } else if (aNamespaceID == kNameSpaceID_None &&
             (aName == nsGkAtoms::command || aName == nsGkAtoms::observes) &&
             IsInUncomposedDoc()) {
    //         XXX sXBL/XBL2 issue! Owner or current document?
    // XXX Why does this not also remove broadcast listeners if the
    // "element" attribute was changed on an <observer>?
    nsAutoString oldValue;
    GetAttr(kNameSpaceID_None, nsGkAtoms::observes, oldValue);
    if (oldValue.IsEmpty()) {
      GetAttr(kNameSpaceID_None, nsGkAtoms::command, oldValue);
    }

    Document* doc = GetUncomposedDoc();
    if (!oldValue.IsEmpty() && doc->HasXULBroadcastManager()) {
      RefPtr<XULBroadcastManager> broadcastManager =
          doc->GetXULBroadcastManager();
      broadcastManager->RemoveListener(this);
    }
  } else if (aNamespaceID == kNameSpaceID_None && aValue &&
             mNodeInfo->Equals(nsGkAtoms::window) &&
             aName == nsGkAtoms::chromemargin) {
    nsAttrValue attrValue;
    // Make sure the margin format is valid first
    if (!attrValue.ParseIntMarginValue(aValue->String())) {
      return NS_ERROR_INVALID_ARG;
    }
  } else if (aNamespaceID == kNameSpaceID_None &&
             aName == nsGkAtoms::usercontextid) {
    nsAutoString oldValue;
    bool hasAttribute =
        GetAttr(kNameSpaceID_None, nsGkAtoms::usercontextid, oldValue);
    if (hasAttribute && (!aValue || !aValue->String().Equals(oldValue))) {
      MOZ_ASSERT(false, "Changing usercontextid is not allowed.");
      return NS_ERROR_INVALID_ARG;
    }
  }

  return nsStyledElement::BeforeSetAttr(aNamespaceID, aName, aValue, aNotify);
}

nsresult nsXULElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
                                    const nsAttrValue* aValue,
                                    const nsAttrValue* aOldValue,
                                    nsIPrincipal* aSubjectPrincipal,
                                    bool aNotify) {
  if (aNamespaceID == kNameSpaceID_None) {
    if (aValue) {
      // Add popup and event listeners. We can't call AddListenerFor since
      // the attribute isn't set yet.
      MaybeAddPopupListener(aName);
      if (nsContentUtils::IsEventAttributeName(aName, EventNameType_XUL)) {
        if (aValue->Type() == nsAttrValue::eString) {
          SetEventHandler(aName, aValue->GetStringValue(), true);
        } else {
          nsAutoString body;
          aValue->ToString(body);
          SetEventHandler(aName, body, true);
        }
      }

      Document* document = GetUncomposedDoc();

      // Hide chrome if needed
      if (mNodeInfo->Equals(nsGkAtoms::window)) {
        if (aName == nsGkAtoms::hidechrome) {
          HideWindowChrome(
              aValue->Equals(NS_LITERAL_STRING("true"), eCaseMatters));
        } else if (aName == nsGkAtoms::chromemargin) {
          SetChromeMargins(aValue);
        } else if (aName == nsGkAtoms::windowtype && document &&
                   document->GetRootElement() == this) {
          MaybeUpdatePrivateLifetime();
        }
      }
      // title and drawintitlebar are settable on
      // any root node (windows, dialogs, etc)
      if (document && document->GetRootElement() == this) {
        if (aName == nsGkAtoms::title) {
          document->NotifyPossibleTitleChange(false);
        } else if (aName == nsGkAtoms::drawintitlebar) {
          SetDrawsInTitlebar(
              aValue->Equals(NS_LITERAL_STRING("true"), eCaseMatters));
        } else if (aName == nsGkAtoms::drawtitle) {
          SetDrawsTitle(
              aValue->Equals(NS_LITERAL_STRING("true"), eCaseMatters));
        } else if (aName == nsGkAtoms::localedir) {
          // if the localedir changed on the root element, reset the document
          // direction
          if (document->IsXULDocument()) {
            document->AsXULDocument()->ResetDocumentDirection();
          }
        } else if (aName == nsGkAtoms::lwtheme ||
                   aName == nsGkAtoms::lwthemetextcolor) {
          // if the lwtheme changed, make sure to reset the document lwtheme
          // cache
          document->ResetDocumentLWTheme();
          UpdateBrightTitlebarForeground(document);
        } else if (aName == nsGkAtoms::brighttitlebarforeground) {
          UpdateBrightTitlebarForeground(document);
        }
      }
    } else {
      if (mNodeInfo->Equals(nsGkAtoms::window)) {
        if (aName == nsGkAtoms::hidechrome) {
          HideWindowChrome(false);
        } else if (aName == nsGkAtoms::chromemargin) {
          ResetChromeMargins();
        }
      }

      Document* doc = GetUncomposedDoc();
      if (doc && doc->GetRootElement() == this) {
        if (aName == nsGkAtoms::localedir) {
          // if the localedir changed on the root element, reset the document
          // direction
          if (doc->IsXULDocument()) {
            doc->AsXULDocument()->ResetDocumentDirection();
          }
        } else if ((aName == nsGkAtoms::lwtheme ||
                    aName == nsGkAtoms::lwthemetextcolor)) {
          // if the lwtheme changed, make sure to restyle appropriately
          doc->ResetDocumentLWTheme();
          UpdateBrightTitlebarForeground(doc);
        } else if (aName == nsGkAtoms::brighttitlebarforeground) {
          UpdateBrightTitlebarForeground(doc);
        } else if (aName == nsGkAtoms::drawintitlebar) {
          SetDrawsInTitlebar(false);
        } else if (aName == nsGkAtoms::drawtitle) {
          SetDrawsTitle(false);
        }
      }
    }

    if (aName == nsGkAtoms::tooltip || aName == nsGkAtoms::tooltiptext) {
      if (!!aValue != !!aOldValue && IsInComposedDoc() &&
          !NodeInfo()->Equals(nsGkAtoms::treechildren)) {
        if (aValue) {
          AddTooltipSupport();
        } else {
          RemoveTooltipSupport();
        }
      }
    }
    Document* doc = GetComposedDoc();
    if (doc && doc->HasXULBroadcastManager()) {
      RefPtr<XULBroadcastManager> broadcastManager =
          doc->GetXULBroadcastManager();
      broadcastManager->AttributeChanged(this, aNamespaceID, aName);
    }
    if (doc && XULBroadcastManager::MayNeedListener(*this)) {
      if (!doc->HasXULBroadcastManager()) {
        doc->InitializeXULBroadcastManager();
      }
      XULBroadcastManager* broadcastManager = doc->GetXULBroadcastManager();
      broadcastManager->AddListener(this);
    }

    // XXX need to check if they're changing an event handler: if
    // so, then we need to unhook the old one.  Or something.
  }

  return nsStyledElement::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue,
                                       aSubjectPrincipal, aNotify);
}

void nsXULElement::AddTooltipSupport() {
  nsXULTooltipListener* listener = nsXULTooltipListener::GetInstance();
  if (!listener) {
    return;
  }

  listener->AddTooltipSupport(this);
}

void nsXULElement::RemoveTooltipSupport() {
  nsXULTooltipListener* listener = nsXULTooltipListener::GetInstance();
  if (!listener) {
    return;
  }

  listener->RemoveTooltipSupport(this);
}

bool nsXULElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
                                  const nsAString& aValue,
                                  nsIPrincipal* aMaybeScriptedPrincipal,
                                  nsAttrValue& aResult) {
  if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::tabindex) {
    return aResult.ParseIntValue(aValue);
  }

  // Parse into a nsAttrValue
  if (!nsStyledElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                       aMaybeScriptedPrincipal, aResult)) {
    // Fall back to parsing as atom for short values
    aResult.ParseStringOrAtom(aValue);
  }

  return true;
}

void nsXULElement::DestroyContent() {
  nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
  if (slots) {
    slots->mControllers = nullptr;
  }

  nsStyledElement::DestroyContent();
}

#ifdef DEBUG
void nsXULElement::List(FILE* out, int32_t aIndent) const {
  nsCString prefix("XUL");
  if (HasSlots()) {
    prefix.Append('*');
  }
  prefix.Append(' ');

  nsStyledElement::List(out, aIndent, prefix);
}
#endif

bool nsXULElement::IsEventStoppedFromAnonymousScrollbar(EventMessage aMessage) {
  return (IsRootOfNativeAnonymousSubtree() &&
          IsAnyOfXULElements(nsGkAtoms::scrollbar, nsGkAtoms::scrollcorner) &&
          (aMessage == eMouseClick || aMessage == eMouseDoubleClick ||
           aMessage == eXULCommand || aMessage == eContextMenu ||
           aMessage == eDragStart || aMessage == eMouseAuxClick));
}

nsresult nsXULElement::DispatchXULCommand(const EventChainVisitor& aVisitor,
                                          nsAutoString& aCommand) {
  // XXX sXBL/XBL2 issue! Owner or current document?
  nsCOMPtr<Document> doc = GetUncomposedDoc();
  NS_ENSURE_STATE(doc);
  RefPtr<Element> commandElt = doc->GetElementById(aCommand);
  if (commandElt) {
    // Create a new command event to dispatch to the element
    // pointed to by the command attribute. The new event's
    // sourceEvent will be the original command event that we're
    // handling.
    RefPtr<Event> event = aVisitor.mDOMEvent;
    uint16_t inputSource = MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
    while (event) {
      NS_ENSURE_STATE(event->GetOriginalTarget() != commandElt);
      RefPtr<XULCommandEvent> commandEvent = event->AsXULCommandEvent();
      if (commandEvent) {
        event = commandEvent->GetSourceEvent();
        inputSource = commandEvent->InputSource();
      } else {
        event = nullptr;
      }
    }
    WidgetInputEvent* orig = aVisitor.mEvent->AsInputEvent();
    nsContentUtils::DispatchXULCommand(
        commandElt, orig->IsTrusted(), aVisitor.mDOMEvent, nullptr,
        orig->IsControl(), orig->IsAlt(), orig->IsShift(), orig->IsMeta(),
        inputSource);
  } else {
    NS_WARNING("A XUL element is attached to a command that doesn't exist!\n");
  }
  return NS_OK;
}

void nsXULElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
  aVisitor.mForceContentDispatch = true;  // FIXME! Bug 329119
  if (IsEventStoppedFromAnonymousScrollbar(aVisitor.mEvent->mMessage)) {
    // Don't propagate these events from native anonymous scrollbar.
    aVisitor.mCanHandle = true;
    aVisitor.SetParentTarget(nullptr, false);
    return;
  }
  if (aVisitor.mEvent->mMessage == eXULCommand &&
      aVisitor.mEvent->mClass == eInputEventClass &&
      aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) &&
      !IsXULElement(nsGkAtoms::command)) {
    // Check that we really have an xul command event. That will be handled
    // in a special way.
    // See if we have a command elt.  If so, we execute on the command
    // instead of on our content element.
    nsAutoString command;
    if (aVisitor.mDOMEvent && aVisitor.mDOMEvent->AsXULCommandEvent() &&
        GetAttr(kNameSpaceID_None, nsGkAtoms::command, command) &&
        !command.IsEmpty()) {
      // Stop building the event target chain for the original event.
      // We don't want it to propagate to any DOM nodes.
      aVisitor.mCanHandle = false;
      aVisitor.mAutomaticChromeDispatch = false;
      // Dispatch XUL command in PreHandleEvent to prevent it breaks event
      // target chain creation
      aVisitor.mWantsPreHandleEvent = true;
      aVisitor.mItemFlags |= NS_DISPATCH_XUL_COMMAND;
      return;
    }
  }

  nsStyledElement::GetEventTargetParent(aVisitor);
}

nsresult nsXULElement::PreHandleEvent(EventChainVisitor& aVisitor) {
  if (aVisitor.mItemFlags & NS_DISPATCH_XUL_COMMAND) {
    nsAutoString command;
    GetAttr(kNameSpaceID_None, nsGkAtoms::command, command);
    MOZ_ASSERT(!command.IsEmpty());
    return DispatchXULCommand(aVisitor, command);
  }
  return nsStyledElement::PreHandleEvent(aVisitor);
}

//----------------------------------------------------------------------
// Implementation methods

nsChangeHint nsXULElement::GetAttributeChangeHint(const nsAtom* aAttribute,
                                                  int32_t aModType) const {
  nsChangeHint retval(nsChangeHint(0));

  if (aAttribute == nsGkAtoms::value &&
      (aModType == MutationEvent_Binding::REMOVAL ||
       aModType == MutationEvent_Binding::ADDITION)) {
    if (IsAnyOfXULElements(nsGkAtoms::label, nsGkAtoms::description))
      // Label and description dynamically morph between a normal
      // block and a cropping single-line XUL text frame.  If the
      // value attribute is being added or removed, then we need to
      // return a hint of frame change.  (See bugzilla bug 95475 for
      // details.)
      retval = nsChangeHint_ReconstructFrame;
  } else {
    // if left or top changes we reflow. This will happen in xul
    // containers that manage positioned children such as a stack.
    if (nsGkAtoms::left == aAttribute || nsGkAtoms::top == aAttribute ||
        nsGkAtoms::right == aAttribute || nsGkAtoms::bottom == aAttribute ||
        nsGkAtoms::start == aAttribute || nsGkAtoms::end == aAttribute)
      retval = NS_STYLE_HINT_REFLOW;
  }

  return retval;
}

NS_IMETHODIMP_(bool)
nsXULElement::IsAttributeMapped(const nsAtom* aAttribute) const {
  return false;
}

nsIControllers* nsXULElement::GetControllers(ErrorResult& rv) {
  if (!Controllers()) {
    nsExtendedDOMSlots* slots = ExtendedDOMSlots();

    slots->mControllers = new nsXULControllers();
  }

  return Controllers();
}

already_AddRefed<BoxObject> nsXULElement::GetBoxObject(ErrorResult& rv) {
  // XXX sXBL/XBL2 issue! Owner or current document?
  return OwnerDoc()->GetBoxObjectFor(this, rv);
}

void nsXULElement::Click(CallerType aCallerType) {
  ClickWithInputSource(MouseEvent_Binding::MOZ_SOURCE_UNKNOWN,
                       aCallerType == CallerType::System);
}

void nsXULElement::ClickWithInputSource(uint16_t aInputSource,
                                        bool aIsTrustedEvent) {
  if (BoolAttrIsTrue(nsGkAtoms::disabled)) return;

  nsCOMPtr<Document> doc = GetComposedDoc();  // Strong just in case
  if (doc) {
    RefPtr<nsPresContext> context = doc->GetPresContext();
    if (context) {
      // strong ref to PresContext so events don't destroy it

      WidgetMouseEvent eventDown(aIsTrustedEvent, eMouseDown, nullptr,
                                 WidgetMouseEvent::eReal);
      WidgetMouseEvent eventUp(aIsTrustedEvent, eMouseUp, nullptr,
                               WidgetMouseEvent::eReal);
      WidgetMouseEvent eventClick(aIsTrustedEvent, eMouseClick, nullptr,
                                  WidgetMouseEvent::eReal);
      eventDown.inputSource = eventUp.inputSource = eventClick.inputSource =
          aInputSource;

      // send mouse down
      nsEventStatus status = nsEventStatus_eIgnore;
      EventDispatcher::Dispatch(static_cast<nsIContent*>(this), context,
                                &eventDown, nullptr, &status);

      // send mouse up
      status = nsEventStatus_eIgnore;  // reset status
      EventDispatcher::Dispatch(static_cast<nsIContent*>(this), context,
                                &eventUp, nullptr, &status);

      // send mouse click
      status = nsEventStatus_eIgnore;  // reset status
      EventDispatcher::Dispatch(static_cast<nsIContent*>(this), context,
                                &eventClick, nullptr, &status);

      // If the click has been prevented, lets skip the command call
      // this is how a physical click works
      if (status == nsEventStatus_eConsumeNoDefault) {
        return;
      }
    }
  }

  // oncommand is fired when an element is clicked...
  DoCommand();
}

void nsXULElement::DoCommand() {
  nsCOMPtr<Document> doc = GetComposedDoc();  // strong just in case
  if (doc) {
    nsContentUtils::DispatchXULCommand(this, true);
  }
}

bool nsXULElement::IsNodeOfType(uint32_t aFlags) const { return false; }

nsresult nsXULElement::AddPopupListener(nsAtom* aName) {
  // Add a popup listener to the element
  bool isContext =
      (aName == nsGkAtoms::context || aName == nsGkAtoms::contextmenu);
  uint32_t listenerFlag = isContext ? XUL_ELEMENT_HAS_CONTENTMENU_LISTENER
                                    : XUL_ELEMENT_HAS_POPUP_LISTENER;

  if (HasFlag(listenerFlag)) {
    return NS_OK;
  }

  nsCOMPtr<nsIDOMEventListener> listener =
      new nsXULPopupListener(this, isContext);

  // Add the popup as a listener on this element.
  EventListenerManager* manager = GetOrCreateListenerManager();
  SetFlags(listenerFlag);

  if (isContext) {
    manager->AddEventListenerByType(listener, NS_LITERAL_STRING("contextmenu"),
                                    TrustedEventsAtSystemGroupBubble());
  } else {
    manager->AddEventListenerByType(listener, NS_LITERAL_STRING("mousedown"),
                                    TrustedEventsAtSystemGroupBubble());
  }
  return NS_OK;
}

EventStates nsXULElement::IntrinsicState() const {
  EventStates state = nsStyledElement::IntrinsicState();

  if (IsReadWriteTextElement()) {
    state |= NS_EVENT_STATE_MOZ_READWRITE;
    state &= ~NS_EVENT_STATE_MOZ_READONLY;
  }

  return state;
}

//----------------------------------------------------------------------

nsresult nsXULElement::MakeHeavyweight(nsXULPrototypeElement* aPrototype) {
  if (!aPrototype) {
    return NS_OK;
  }

  uint32_t i;
  nsresult rv;
  for (i = 0; i < aPrototype->mNumAttributes; ++i) {
    nsXULPrototypeAttribute* protoattr = &aPrototype->mAttributes[i];
    nsAttrValue attrValue;

    // Style rules need to be cloned.
    if (protoattr->mValue.Type() == nsAttrValue::eCSSDeclaration) {
      DeclarationBlock* decl = protoattr->mValue.GetCSSDeclarationValue();
      RefPtr<DeclarationBlock> declClone = decl->Clone();

      nsString stringValue;
      protoattr->mValue.ToString(stringValue);

      attrValue.SetTo(declClone.forget(), &stringValue);
    } else {
      attrValue.SetTo(protoattr->mValue);
    }

    bool oldValueSet;
    // XXX we might wanna have a SetAndTakeAttr that takes an nsAttrName
    if (protoattr->mName.IsAtom()) {
      rv = mAttrs.SetAndSwapAttr(protoattr->mName.Atom(), attrValue,
                                 &oldValueSet);
    } else {
      rv = mAttrs.SetAndSwapAttr(protoattr->mName.NodeInfo(), attrValue,
                                 &oldValueSet);
    }
    NS_ENSURE_SUCCESS(rv, rv);
  }
  return NS_OK;
}

nsresult nsXULElement::HideWindowChrome(bool aShouldHide) {
  Document* doc = GetUncomposedDoc();
  if (!doc || doc->GetRootElement() != this) return NS_ERROR_UNEXPECTED;

  // only top level chrome documents can hide the window chrome
  if (!doc->IsRootDisplayDocument()) return NS_OK;

  nsPresContext* presContext = doc->GetPresContext();

  if (presContext && presContext->IsChrome()) {
    nsIFrame* frame = GetPrimaryFrame();

    if (frame) {
      nsView* view = frame->GetClosestView();

      if (view) {
        nsIWidget* w = view->GetWidget();
        NS_ENSURE_STATE(w);
        w->HideWindowChrome(aShouldHide);
      }
    }
  }

  return NS_OK;
}

nsIWidget* nsXULElement::GetWindowWidget() {
  Document* doc = GetComposedDoc();

  // only top level chrome documents can set the titlebar color
  if (doc && doc->IsRootDisplayDocument()) {
    nsCOMPtr<nsISupports> container = doc->GetContainer();
    nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container);
    if (baseWindow) {
      nsCOMPtr<nsIWidget> mainWidget;
      baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
      return mainWidget;
    }
  }
  return nullptr;
}

class SetDrawInTitleBarEvent : public Runnable {
 public:
  SetDrawInTitleBarEvent(nsIWidget* aWidget, bool aState)
      : mozilla::Runnable("SetDrawInTitleBarEvent"),
        mWidget(aWidget),
        mState(aState) {}

  NS_IMETHOD Run() override {
    NS_ASSERTION(mWidget,
                 "You shouldn't call this runnable with a null widget!");

    mWidget->SetDrawsInTitlebar(mState);
    return NS_OK;
  }

 private:
  nsCOMPtr<nsIWidget> mWidget;
  bool mState;
};

void nsXULElement::SetDrawsInTitlebar(bool aState) {
  nsIWidget* mainWidget = GetWindowWidget();
  if (mainWidget) {
    nsContentUtils::AddScriptRunner(
        new SetDrawInTitleBarEvent(mainWidget, aState));
  }
}

void nsXULElement::SetDrawsTitle(bool aState) {
  nsIWidget* mainWidget = GetWindowWidget();
  if (mainWidget) {
    // We can do this synchronously because SetDrawsTitle doesn't have any
    // synchronous effects apart from a harmless invalidation.
    mainWidget->SetDrawsTitle(aState);
  }
}

void nsXULElement::UpdateBrightTitlebarForeground(Document* aDoc) {
  nsIWidget* mainWidget = GetWindowWidget();
  if (mainWidget) {
    // We can do this synchronously because SetBrightTitlebarForeground doesn't
    // have any synchronous effects apart from a harmless invalidation.
    mainWidget->SetUseBrightTitlebarForeground(
        aDoc->GetDocumentLWTheme() == Document::Doc_Theme_Bright ||
        aDoc->GetRootElement()->AttrValueIs(
            kNameSpaceID_None, nsGkAtoms::brighttitlebarforeground,
            NS_LITERAL_STRING("true"), eCaseMatters));
  }
}

class MarginSetter : public Runnable {
 public:
  explicit MarginSetter(nsIWidget* aWidget)
      : mozilla::Runnable("MarginSetter"),
        mWidget(aWidget),
        mMargin(-1, -1, -1, -1) {}
  MarginSetter(nsIWidget* aWidget, const LayoutDeviceIntMargin& aMargin)
      : mozilla::Runnable("MarginSetter"), mWidget(aWidget), mMargin(aMargin) {}

  NS_IMETHOD Run() override {
    // SetNonClientMargins can dispatch native events, hence doing
    // it off a script runner.
    mWidget->SetNonClientMargins(mMargin);
    return NS_OK;
  }

 private:
  nsCOMPtr<nsIWidget> mWidget;
  LayoutDeviceIntMargin mMargin;
};

void nsXULElement::SetChromeMargins(const nsAttrValue* aValue) {
  if (!aValue) return;

  nsIWidget* mainWidget = GetWindowWidget();
  if (!mainWidget) return;

  // top, right, bottom, left - see nsAttrValue
  nsIntMargin margins;
  bool gotMargins = false;

  if (aValue->Type() == nsAttrValue::eIntMarginValue) {
    gotMargins = aValue->GetIntMarginValue(margins);
  } else {
    nsAutoString tmp;
    aValue->ToString(tmp);
    gotMargins = nsContentUtils::ParseIntMarginValue(tmp, margins);
  }
  if (gotMargins) {
    nsContentUtils::AddScriptRunner(new MarginSetter(
        mainWidget, LayoutDeviceIntMargin::FromUnknownMargin(margins)));
  }
}

void nsXULElement::ResetChromeMargins() {
  nsIWidget* mainWidget = GetWindowWidget();
  if (!mainWidget) return;
  // See nsIWidget
  nsContentUtils::AddScriptRunner(new MarginSetter(mainWidget));
}

bool nsXULElement::BoolAttrIsTrue(nsAtom* aName) const {
  const nsAttrValue* attr = GetAttrInfo(kNameSpaceID_None, aName).mValue;

  return attr && attr->Type() == nsAttrValue::eAtom &&
         attr->GetAtomValue() == nsGkAtoms::_true;
}

void nsXULElement::RecompileScriptEventListeners() {
  int32_t i, count = mAttrs.AttrCount();
  for (i = 0; i < count; ++i) {
    const nsAttrName* name = mAttrs.AttrNameAt(i);

    // Eventlistenener-attributes are always in the null namespace
    if (!name->IsAtom()) {
      continue;
    }

    nsAtom* attr = name->Atom();
    if (!nsContentUtils::IsEventAttributeName(attr, EventNameType_XUL)) {
      continue;
    }

    nsAutoString value;
    GetAttr(kNameSpaceID_None, attr, value);
    SetEventHandler(attr, value, true);
  }
}

bool nsXULElement::IsEventAttributeNameInternal(nsAtom* aName) {
  return nsContentUtils::IsEventAttributeName(aName, EventNameType_XUL);
}

JSObject* nsXULElement::WrapNode(JSContext* aCx,
                                 JS::Handle<JSObject*> aGivenProto) {
  return dom::XULElement_Binding::Wrap(aCx, this, aGivenProto);
}

bool nsXULElement::IsInteractiveHTMLContent(bool aIgnoreTabindex) const {
  return IsXULElement(nsGkAtoms::menupopup) ||
         Element::IsInteractiveHTMLContent(aIgnoreTabindex);
}

NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULPrototypeNode)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULPrototypeNode)
  if (tmp->mType == nsXULPrototypeNode::eType_Element) {
    static_cast<nsXULPrototypeElement*>(tmp)->Unlink();
  } else if (tmp->mType == nsXULPrototypeNode::eType_Script) {
    static_cast<nsXULPrototypeScript*>(tmp)->UnlinkJSObjects();
  }
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULPrototypeNode)
  if (tmp->mType == nsXULPrototypeNode::eType_Element) {
    nsXULPrototypeElement* elem = static_cast<nsXULPrototypeElement*>(tmp);
    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mNodeInfo");
    cb.NoteNativeChild(elem->mNodeInfo,
                       NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo));
    uint32_t i;
    for (i = 0; i < elem->mNumAttributes; ++i) {
      const nsAttrName& name = elem->mAttributes[i].mName;
      if (!name.IsAtom()) {
        NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
                                           "mAttributes[i].mName.NodeInfo()");
        cb.NoteNativeChild(name.NodeInfo(),
                           NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo));
      }
    }
    ImplCycleCollectionTraverse(cb, elem->mChildren, "mChildren");
  }
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXULPrototypeNode)
  if (tmp->mType == nsXULPrototypeNode::eType_Script) {
    nsXULPrototypeScript* script = static_cast<nsXULPrototypeScript*>(tmp);
    script->Trace(aCallbacks, aClosure);
  }
NS_IMPL_CYCLE_COLLECTION_TRACE_END

NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsXULPrototypeNode, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsXULPrototypeNode, Release)

//----------------------------------------------------------------------
//
// nsXULPrototypeAttribute
//

nsXULPrototypeAttribute::~nsXULPrototypeAttribute() {
  MOZ_COUNT_DTOR(nsXULPrototypeAttribute);
}

//----------------------------------------------------------------------
//
// nsXULPrototypeElement
//

nsresult nsXULPrototypeElement::Serialize(
    nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
    const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
  nsresult rv;

  // Write basic prototype data
  rv = aStream->Write32(mType);

  // Write Node Info
  int32_t index = aNodeInfos->IndexOf(mNodeInfo);
  NS_ASSERTION(index >= 0, "unknown mozilla::dom::NodeInfo index");
  nsresult tmp = aStream->Write32(index);
  if (NS_FAILED(tmp)) {
    rv = tmp;
  }

  // Write Attributes
  tmp = aStream->Write32(mNumAttributes);
  if (NS_FAILED(tmp)) {
    rv = tmp;
  }

  nsAutoString attributeValue;
  uint32_t i;
  for (i = 0; i < mNumAttributes; ++i) {
    RefPtr<mozilla::dom::NodeInfo> ni;
    if (mAttributes[i].mName.IsAtom()) {
      ni = mNodeInfo->NodeInfoManager()->GetNodeInfo(
          mAttributes[i].mName.Atom(), nullptr, kNameSpaceID_None,
          nsINode::ATTRIBUTE_NODE);
      NS_ASSERTION(ni, "the nodeinfo should already exist");
    } else {
      ni = mAttributes[i].mName.NodeInfo();
    }

    index = aNodeInfos->IndexOf(ni);
    NS_ASSERTION(index >= 0, "unknown mozilla::dom::NodeInfo index");
    tmp = aStream->Write32(index);
    if (NS_FAILED(tmp)) {
      rv = tmp;
    }

    mAttributes[i].mValue.ToString(attributeValue);
    tmp = aStream->WriteWStringZ(attributeValue.get());
    if (NS_FAILED(tmp)) {
      rv = tmp;
    }
  }

  // Now write children
  tmp = aStream->Write32(uint32_t(mChildren.Length()));
  if (NS_FAILED(tmp)) {
    rv = tmp;
  }
  for (i = 0; i < mChildren.Length(); i++) {
    nsXULPrototypeNode* child = mChildren[i].get();
    switch (child->mType) {
      case eType_Element:
      case eType_Text:
      case eType_PI:
        tmp = child->Serialize(aStream, aProtoDoc, aNodeInfos);
        if (NS_FAILED(tmp)) {
          rv = tmp;
        }
        break;
      case eType_Script:
        tmp = aStream->Write32(child->mType);
        if (NS_FAILED(tmp)) {
          rv = tmp;
        }
        nsXULPrototypeScript* script =
            static_cast<nsXULPrototypeScript*>(child);

        tmp = aStream->Write8(script->mOutOfLine);
        if (NS_FAILED(tmp)) {
          rv = tmp;
        }
        if (!script->mOutOfLine) {
          tmp = script->Serialize(aStream, aProtoDoc, aNodeInfos);
          if (NS_FAILED(tmp)) {
            rv = tmp;
          }
        } else {
          tmp = aStream->WriteCompoundObject(script->mSrcURI,
                                             NS_GET_IID(nsIURI), true);
          if (NS_FAILED(tmp)) {
            rv = tmp;
          }

          if (script->HasScriptObject()) {
            // This may return NS_OK without muxing script->mSrcURI's
            // data into the cache file, in the case where that
            // muxed document is already there (written by a prior
            // session, or by an earlier cache episode during this
            // session).
            tmp = script->SerializeOutOfLine(aStream, aProtoDoc);
            if (NS_FAILED(tmp)) {
              rv = tmp;
            }
          }
        }
        break;
    }
  }

  return rv;
}

nsresult nsXULPrototypeElement::Deserialize(
    nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
    nsIURI* aDocumentURI,
    const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
  MOZ_ASSERT(aNodeInfos, "missing nodeinfo array");

  // Read Node Info
  uint32_t number = 0;
  nsresult rv = aStream->Read32(&number);
  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
  mNodeInfo = aNodeInfos->SafeElementAt(number, nullptr);
  if (!mNodeInfo) {
    return NS_ERROR_UNEXPECTED;
  }

  // Read Attributes
  rv = aStream->Read32(&number);
  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
  mNumAttributes = int32_t(number);

  if (mNumAttributes > 0) {
    mAttributes = new (fallible) nsXULPrototypeAttribute[mNumAttributes];
    if (!mAttributes) {
      return NS_ERROR_OUT_OF_MEMORY;
    }

    nsAutoString attributeValue;
    for (uint32_t i = 0; i < mNumAttributes; ++i) {
      rv = aStream->Read32(&number);
      if (NS_WARN_IF(NS_FAILED(rv))) return rv;
      mozilla::dom::NodeInfo* ni = aNodeInfos->SafeElementAt(number, nullptr);
      if (!ni) {
        return NS_ERROR_UNEXPECTED;
      }

      mAttributes[i].mName.SetTo(ni);

      rv = aStream->ReadString(attributeValue);
      if (NS_WARN_IF(NS_FAILED(rv))) return rv;
      rv = SetAttrAt(i, attributeValue, aDocumentURI);
      if (NS_WARN_IF(NS_FAILED(rv))) return rv;
    }
  }

  rv = aStream->Read32(&number);
  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
  uint32_t numChildren = int32_t(number);

  if (numChildren > 0) {
    if (!mChildren.SetCapacity(numChildren, fallible)) {
      return NS_ERROR_OUT_OF_MEMORY;
    }

    for (uint32_t i = 0; i < numChildren; i++) {
      rv = aStream->Read32(&number);
      if (NS_WARN_IF(NS_FAILED(rv))) return rv;
      Type childType = (Type)number;

      RefPtr<nsXULPrototypeNode> child;

      switch (childType) {
        case eType_Element:
          child = new nsXULPrototypeElement();
          rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, aNodeInfos);
          if (NS_WARN_IF(NS_FAILED(rv))) return rv;
          break;
        case eType_Text:
          child = new nsXULPrototypeText();
          rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, aNodeInfos);
          if (NS_WARN_IF(NS_FAILED(rv))) return rv;
          break;
        case eType_PI:
          child = new nsXULPrototypePI();
          rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, aNodeInfos);
          if (NS_WARN_IF(NS_FAILED(rv))) return rv;
          break;
        case eType_Script: {
          // language version/options obtained during deserialization.
          RefPtr<nsXULPrototypeScript> script = new nsXULPrototypeScript(0);

          rv = aStream->ReadBoolean(&script->mOutOfLine);
          if (NS_WARN_IF(NS_FAILED(rv))) return rv;
          if (!script->mOutOfLine) {
            rv = script->Deserialize(aStream, aProtoDoc, aDocumentURI,
                                     aNodeInfos);
            if (NS_WARN_IF(NS_FAILED(rv))) return rv;
          } else {
            nsCOMPtr<nsISupports> supports;
            rv = aStream->ReadObject(true, getter_AddRefs(supports));
            if (NS_WARN_IF(NS_FAILED(rv))) return rv;
            script->mSrcURI = do_QueryInterface(supports);

            rv = script->DeserializeOutOfLine(aStream, aProtoDoc);
            if (NS_WARN_IF(NS_FAILED(rv))) return rv;
          }

          child = script.forget();
          break;
        }
        default:
          MOZ_ASSERT(false, "Unexpected child type!");
          return NS_ERROR_UNEXPECTED;
      }

      MOZ_ASSERT(child, "Don't append null to mChildren");
      MOZ_ASSERT(child->mType == childType);
      mChildren.AppendElement(child);

      // Oh dear. Something failed during the deserialization.
      // We don't know what.  But likely consequences of failed
      // deserializations included calls to |AbortCaching| which
      // shuts down the cache and closes our streams.
      // If that happens, next time through this loop, we die a messy
      // death. So, let's just fail now, and propagate that failure
      // upward so that the ChromeProtocolHandler knows it can't use
      // a cached chrome channel for this.
      if (NS_WARN_IF(NS_FAILED(rv))) return rv;
    }
  }

  return rv;
}

nsresult nsXULPrototypeElement::SetAttrAt(uint32_t aPos,
                                          const nsAString& aValue,
                                          nsIURI* aDocumentURI) {
  MOZ_ASSERT(aPos < mNumAttributes, "out-of-bounds");

  // WARNING!!
  // This code is largely duplicated in nsXULElement::SetAttr.
  // Any changes should be made to both functions.

  if (!mNodeInfo->NamespaceEquals(kNameSpaceID_XUL)) {
    if (mNodeInfo->NamespaceEquals(kNameSpaceID_XHTML) &&
        mAttributes[aPos].mName.Equals(nsGkAtoms::is)) {
      // We still care about the is attribute set on HTML elements.
      mAttributes[aPos].mValue.ParseAtom(aValue);
      mIsAtom = mAttributes[aPos].mValue.GetAtomValue();

      return NS_OK;
    }

    mAttributes[aPos].mValue.ParseStringOrAtom(aValue);

    return NS_OK;
  }

  if (mAttributes[aPos].mName.Equals(nsGkAtoms::id) && !aValue.IsEmpty()) {
    mHasIdAttribute = true;
    // Store id as atom.
    // id="" means that the element has no id. Not that it has
    // emptystring as id.
    mAttributes[aPos].mValue.ParseAtom(aValue);

    return NS_OK;
  } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::is)) {
    // Store is as atom.
    mAttributes[aPos].mValue.ParseAtom(aValue);
    mIsAtom = mAttributes[aPos].mValue.GetAtomValue();

    return NS_OK;
  } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::_class)) {
    mHasClassAttribute = true;
    // Compute the element's class list
    mAttributes[aPos].mValue.ParseAtomArray(aValue);

    return NS_OK;
  } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::style)) {
    mHasStyleAttribute = true;
    // Parse the element's 'style' attribute

    // This is basically duplicating what nsINode::NodePrincipal() does
    nsIPrincipal* principal = mNodeInfo->NodeInfoManager()->DocumentPrincipal();
    // XXX Get correct Base URI (need GetBaseURI on *prototype* element)
    // TODO: If we implement Content Security Policy for chrome documents
    // as has been discussed, the CSP should be checked here to see if
    // inline styles are allowed to be applied.
    // XXX No specific specs talk about xul and referrer policy, pass Unset
    RefPtr<URLExtraData> data = new URLExtraData(
        aDocumentURI, aDocumentURI, principal, mozilla::net::RP_Unset);
    RefPtr<DeclarationBlock> declaration = DeclarationBlock::FromCssText(
        aValue, data, eCompatibility_FullStandards, nullptr);
    if (declaration) {
      mAttributes[aPos].mValue.SetTo(declaration.forget(), &aValue);

      return NS_OK;
    }
    // Don't abort if parsing failed, it could just be malformed css.
  } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::tabindex)) {
    mAttributes[aPos].mValue.ParseIntValue(aValue);

    return NS_OK;
  }

  mAttributes[aPos].mValue.ParseStringOrAtom(aValue);

  return NS_OK;
}

void nsXULPrototypeElement::Unlink() {
  mNumAttributes = 0;
  delete[] mAttributes;
  mAttributes = nullptr;
  mChildren.Clear();
}

void nsXULPrototypeElement::TraceAllScripts(JSTracer* aTrc) {
  for (uint32_t i = 0; i < mChildren.Length(); ++i) {
    nsXULPrototypeNode* child = mChildren[i];
    if (child->mType == nsXULPrototypeNode::eType_Element) {
      static_cast<nsXULPrototypeElement*>(child)->TraceAllScripts(aTrc);
    } else if (child->mType == nsXULPrototypeNode::eType_Script) {
      static_cast<nsXULPrototypeScript*>(child)->TraceScriptObject(aTrc);
    }
  }
}

//----------------------------------------------------------------------
//
// nsXULPrototypeScript
//

nsXULPrototypeScript::nsXULPrototypeScript(uint32_t aLineNo)
    : nsXULPrototypeNode(eType_Script),
      mLineNo(aLineNo),
      mSrcLoading(false),
      mOutOfLine(true),
      mSrcLoadWaiters(nullptr),
      mScriptObject(nullptr) {}

nsXULPrototypeScript::~nsXULPrototypeScript() { UnlinkJSObjects(); }

nsresult nsXULPrototypeScript::Serialize(
    nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
    const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
  NS_ENSURE_TRUE(aProtoDoc, NS_ERROR_UNEXPECTED);

  AutoJSAPI jsapi;
  if (!jsapi.Init(xpc::CompilationScope())) {
    return NS_ERROR_UNEXPECTED;
  }

  NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mScriptObject,
               "script source still loading when serializing?!");
  if (!mScriptObject) return NS_ERROR_FAILURE;

  // Write basic prototype data
  nsresult rv;
  rv = aStream->Write32(mLineNo);
  if (NS_FAILED(rv)) return rv;
  rv = aStream->Write32(0);  // See bug 1418294.
  if (NS_FAILED(rv)) return rv;

  JSContext* cx = jsapi.cx();
  JS::Rooted<JSScript*> script(cx, mScriptObject);
  MOZ_ASSERT(xpc::CompilationScope() == JS::CurrentGlobalOrNull(cx));
  return nsContentUtils::XPConnect()->WriteScript(aStream, cx, script);
}

nsresult nsXULPrototypeScript::SerializeOutOfLine(
    nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc) {
  nsresult rv = NS_ERROR_NOT_IMPLEMENTED;

  bool isChrome = false;
  if (NS_FAILED(mSrcURI->SchemeIs("chrome", &isChrome)) || !isChrome)
    // Don't cache scripts that don't come from chrome uris.
    return rv;

  nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
  if (!cache) return NS_ERROR_OUT_OF_MEMORY;

  NS_ASSERTION(cache->IsEnabled(),
               "writing to the cache file, but the XUL cache is off?");
  bool exists;
  cache->HasData(mSrcURI, &exists);

  /* return will be NS_OK from GetAsciiSpec.
   * that makes no sense.
   * nor does returning NS_OK from HasMuxedDocument.
   * XXX return something meaningful.
   */
  if (exists) return NS_OK;

  nsCOMPtr<nsIObjectOutputStream> oos;
  rv = cache->GetOutputStream(mSrcURI, getter_AddRefs(oos));
  NS_ENSURE_SUCCESS(rv, rv);

  nsresult tmp = Serialize(oos, aProtoDoc, nullptr);
  if (NS_FAILED(tmp)) {
    rv = tmp;
  }
  tmp = cache->FinishOutputStream(mSrcURI);
  if (NS_FAILED(tmp)) {
    rv = tmp;
  }

  if (NS_FAILED(rv)) cache->AbortCaching();
  return rv;
}

nsresult nsXULPrototypeScript::Deserialize(
    nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
    nsIURI* aDocumentURI,
    const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
  nsresult rv;
  NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mScriptObject,
               "prototype script not well-initialized when deserializing?!");

  // Read basic prototype data
  rv = aStream->Read32(&mLineNo);
  if (NS_FAILED(rv)) return rv;
  uint32_t dummy;
  rv = aStream->Read32(&dummy);  // See bug 1418294.
  if (NS_FAILED(rv)) return rv;

  AutoJSAPI jsapi;
  if (!jsapi.Init(xpc::CompilationScope())) {
    return NS_ERROR_UNEXPECTED;
  }
  JSContext* cx = jsapi.cx();

  JS::Rooted<JSScript*> newScriptObject(cx);
  rv = nsContentUtils::XPConnect()->ReadScript(aStream, cx,
                                               newScriptObject.address());
  NS_ENSURE_SUCCESS(rv, rv);
  Set(newScriptObject);
  return NS_OK;
}

nsresult nsXULPrototypeScript::DeserializeOutOfLine(
    nsIObjectInputStream* aInput, nsXULPrototypeDocument* aProtoDoc) {
  // Keep track of failure via rv, so we can
  // AbortCaching if things look bad.
  nsresult rv = NS_OK;
  nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();

  nsCOMPtr<nsIObjectInputStream> objectInput = aInput;
  if (cache) {
    bool useXULCache = true;
    if (mSrcURI) {
      // NB: we must check the XUL script cache early, to avoid
      // multiple deserialization attempts for a given script.
      // Note that XULDocument::LoadScript
      // checks the XUL script cache too, in order to handle the
      // serialization case.
      //
      // We need do this only for <script src='strres.js'> and the
      // like, i.e., out-of-line scripts that are included by several
      // different XUL documents stored in the cache file.
      useXULCache = cache->IsEnabled();

      if (useXULCache) {
        JSScript* newScriptObject = cache->GetScript(mSrcURI);
        if (newScriptObject) Set(newScriptObject);
      }
    }

    if (!mScriptObject) {
      if (mSrcURI) {
        rv = cache->GetInputStream(mSrcURI, getter_AddRefs(objectInput));
      }
      // If !mSrcURI, we have an inline script. We shouldn't have
      // to do anything else in that case, I think.

      // We do reflect errors into rv, but our caller may want to
      // ignore our return value, because mScriptObject will be null
      // after any error, and that suffices to cause the script to
      // be reloaded (from the src= URI, if any) and recompiled.
      // We're better off slow-loading than bailing out due to a
      // error.
      if (NS_SUCCEEDED(rv))
        rv = Deserialize(objectInput, aProtoDoc, nullptr, nullptr);

      if (NS_SUCCEEDED(rv)) {
        if (useXULCache && mSrcURI) {
          bool isChrome = false;
          mSrcURI->SchemeIs("chrome", &isChrome);
          if (isChrome) {
            JS::Rooted<JSScript*> script(RootingCx(), GetScriptObject());
            cache->PutScript(mSrcURI, script);
          }
        }
        cache->FinishInputStream(mSrcURI);
      } else {
        // If mSrcURI is not in the cache,
        // rv will be NS_ERROR_NOT_AVAILABLE and we'll try to
        // update the cache file to hold a serialization of
        // this script, once it has finished loading.
        if (rv != NS_ERROR_NOT_AVAILABLE) cache->AbortCaching();
      }
    }
  }
  return rv;
}

class NotifyOffThreadScriptCompletedRunnable : public Runnable {
  // An array of all outstanding script receivers. All reference counting of
  // these objects happens on the main thread. When we return to the main
  // thread from script compilation we make sure our receiver is still in
  // this array (still alive) before proceeding. This array is cleared during
  // shutdown, potentially before all outstanding script compilations have
  // finished. We do not need to worry about pointer replay here, because
  // a) we should not be starting script compilation after clearing this
  // array and b) in all other cases the receiver will still be alive.
  static StaticAutoPtr<nsTArray<nsCOMPtr<nsIOffThreadScriptReceiver>>>
      sReceivers;
  static bool sSetupClearOnShutdown;

  nsIOffThreadScriptReceiver* mReceiver;
  JS::OffThreadToken* mToken;

 public:
  NotifyOffThreadScriptCompletedRunnable(nsIOffThreadScriptReceiver* aReceiver,
                                         JS::OffThreadToken* aToken)
      : mozilla::Runnable("NotifyOffThreadScriptCompletedRunnable"),
        mReceiver(aReceiver),
        mToken(aToken) {}

  static void NoteReceiver(nsIOffThreadScriptReceiver* aReceiver) {
    if (!sSetupClearOnShutdown) {
      ClearOnShutdown(&sReceivers);
      sSetupClearOnShutdown = true;
      sReceivers = new nsTArray<nsCOMPtr<nsIOffThreadScriptReceiver>>();
    }

    // If we ever crash here, it's because we tried to lazy compile script
    // too late in shutdown.
    sReceivers->AppendElement(aReceiver);
  }

  NS_DECL_NSIRUNNABLE
};

StaticAutoPtr<nsTArray<nsCOMPtr<nsIOffThreadScriptReceiver>>>
    NotifyOffThreadScriptCompletedRunnable::sReceivers;
bool NotifyOffThreadScriptCompletedRunnable::sSetupClearOnShutdown = false;

NS_IMETHODIMP
NotifyOffThreadScriptCompletedRunnable::Run() {
  MOZ_ASSERT(NS_IsMainThread());

  JS::Rooted<JSScript*> script(RootingCx());
  {
    AutoJSAPI jsapi;
    if (!jsapi.Init(xpc::CompilationScope())) {
      // Now what?  I guess we just leak... this should probably never
      // happen.
      return NS_ERROR_UNEXPECTED;
    }
    JSContext* cx = jsapi.cx();
    script = JS::FinishOffThreadScript(cx, mToken);
  }

  if (!sReceivers) {
    // We've already shut down.
    return NS_OK;
  }

  auto index = sReceivers->IndexOf(mReceiver);
  MOZ_RELEASE_ASSERT(index != sReceivers->NoIndex);
  nsCOMPtr<nsIOffThreadScriptReceiver> receiver = (*sReceivers)[index].forget();
  sReceivers->RemoveElementAt(index);

  return receiver->OnScriptCompileComplete(script,
                                           script ? NS_OK : NS_ERROR_FAILURE);
}

static void OffThreadScriptReceiverCallback(JS::OffThreadToken* aToken,
                                            void* aCallbackData) {
  // Be careful not to adjust the refcount on the receiver, as this callback
  // may be invoked off the main thread.
  nsIOffThreadScriptReceiver* aReceiver =
      static_cast<nsIOffThreadScriptReceiver*>(aCallbackData);
  RefPtr<NotifyOffThreadScriptCompletedRunnable> notify =
      new NotifyOffThreadScriptCompletedRunnable(aReceiver, aToken);
  NS_DispatchToMainThread(notify);
}

nsresult nsXULPrototypeScript::Compile(
    const char16_t* aText, size_t aTextLength, JS::SourceOwnership aOwnership,
    nsIURI* aURI, uint32_t aLineNo, Document* aDocument,
    nsIOffThreadScriptReceiver* aOffThreadReceiver /* = nullptr */) {
  // We'll compile the script in the compilation scope.
  AutoJSAPI jsapi;
  if (!jsapi.Init(xpc::CompilationScope())) {
    if (aOwnership == JS::SourceOwnership::TakeOwnership) {
      // In this early-exit case -- before the |srcBuf.init| call will
      // own |aText| -- we must relinquish ownership manually.
      js_free(const_cast<char16_t*>(aText));
    }

    return NS_ERROR_UNEXPECTED;
  }
  JSContext* cx = jsapi.cx();

  JS::SourceText<char16_t> srcBuf;
  if (NS_WARN_IF(!srcBuf.init(cx, aText, aTextLength, aOwnership))) {
    return NS_ERROR_FAILURE;
  }

  nsAutoCString urlspec;
  nsresult rv = aURI->GetSpec(urlspec);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Ok, compile it to create a prototype script object!
  JS::CompileOptions options(cx);
  options.setIntroductionType("scriptElement")
      .setFileAndLine(urlspec.get(), aLineNo);
  // If the script was inline, tell the JS parser to save source for
  // Function.prototype.toSource(). If it's out of line, we retrieve the
  // source from the files on demand.
  options.setSourceIsLazy(mOutOfLine);
  JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
  if (scope) {
    JS::ExposeObjectToActiveJS(scope);
  }

  if (aOffThreadReceiver && JS::CanCompileOffThread(cx, options, aTextLength)) {
    if (!JS::CompileOffThread(cx, options, srcBuf,
                              OffThreadScriptReceiverCallback,
                              static_cast<void*>(aOffThreadReceiver))) {
      return NS_ERROR_OUT_OF_MEMORY;
    }
    NotifyOffThreadScriptCompletedRunnable::NoteReceiver(aOffThreadReceiver);
  } else {
    JS::Rooted<JSScript*> script(cx);
    if (!JS::Compile(cx, options, srcBuf, &script))
      return NS_ERROR_OUT_OF_MEMORY;
    Set(script);
  }
  return NS_OK;
}

void nsXULPrototypeScript::UnlinkJSObjects() {
  if (mScriptObject) {
    mScriptObject = nullptr;
    mozilla::DropJSObjects(this);
  }
}

void nsXULPrototypeScript::Set(JSScript* aObject) {
  MOZ_ASSERT(!mScriptObject, "Leaking script object.");
  if (!aObject) {
    mScriptObject = nullptr;
    return;
  }

  mScriptObject = aObject;
  mozilla::HoldJSObjects(this);
}

//----------------------------------------------------------------------
//
// nsXULPrototypeText
//

nsresult nsXULPrototypeText::Serialize(
    nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
    const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
  nsresult rv;

  // Write basic prototype data
  rv = aStream->Write32(mType);

  nsresult tmp = aStream->WriteWStringZ(mValue.get());
  if (NS_FAILED(tmp)) {
    rv = tmp;
  }

  return rv;
}

nsresult nsXULPrototypeText::Deserialize(
    nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
    nsIURI* aDocumentURI,
    const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
  nsresult rv = aStream->ReadString(mValue);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

//----------------------------------------------------------------------
//
// nsXULPrototypePI
//

nsresult nsXULPrototypePI::Serialize(
    nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
    const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
  nsresult rv;

  // Write basic prototype data
  rv = aStream->Write32(mType);

  nsresult tmp = aStream->WriteWStringZ(mTarget.get());
  if (NS_FAILED(tmp)) {
    rv = tmp;
  }
  tmp = aStream->WriteWStringZ(mData.get());
  if (NS_FAILED(tmp)) {
    rv = tmp;
  }

  return rv;
}

nsresult nsXULPrototypePI::Deserialize(
    nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
    nsIURI* aDocumentURI,
    const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
  nsresult rv;

  rv = aStream->ReadString(mTarget);
  if (NS_FAILED(rv)) return rv;
  rv = aStream->ReadString(mData);
  if (NS_FAILED(rv)) return rv;

  return rv;
}