dom/xul/nsXULElement.cpp
author Neil Deakin <neil@mozilla.com>
Mon, 29 Oct 2018 07:00:00 +0200
changeset 446935 2b13a09333e79d452555cff461b645e214639b4b
parent 446882 38844846c6ae6773afb8b9254bf3dbfec437d1ec
child 448947 6f3709b3878117466168c40affa7bca0b60cf75b
permissions -rw-r--r--
Bug 1502704, overlay scrollbars don't fade out properly as no QI to nsStyledElement is available r=peterv

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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 "nsIDocument.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,
                                  nsIDocument* 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");

    nsIDocument* 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

    nsCOMPtr<nsISupports> iface =
      CustomElementRegistry::CallGetCustomInterface(this, aIID);
    if (iface) {
      iface->QueryInterface(aIID, aInstancePtr);
      if (*aInstancePtr) {
        return NS_OK;
      }
    }

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?
    nsIDocument* 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 = do_QueryObject(this);
  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<nsIDocument> 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(do_QueryInterface(content));
              if (controlItem) {
                bool disabled;
                controlItem->GetDisabled(&disabled);
                if (!disabled) {
                  nsCOMPtr<nsIDOMXULSelectControlElement> selectControl;
                  controlItem->GetControl(getter_AddRefs(selectControl));
                  elementToFocus = do_QueryInterface(selectControl);
                }
              }
            } 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(nsIDocument* aDocument)
    : mozilla::Runnable("XULInContentErrorReporter")
    , mDocument(aDocument)
  {
  }

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

private:
  nsCOMPtr<nsIDocument> 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(nsIDocument* aDocument,
                         nsIContent* aParent,
                         nsIContent* aBindingParent)
{
  if (!aBindingParent &&
      aDocument &&
      !aDocument->IsLoadedAsInteractiveData() &&
      !aDocument->AllowXULXBL() &&
      !aDocument->HasWarnedAbout(nsIDocument::eImportXULIntoContent)) {
    nsContentUtils::AddScriptRunner(new XULInContentErrorReporter(aDocument));
  }

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

  nsIDocument* 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();
    }

    nsIDocument* 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
    //
    nsIDocument* 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);
        }

        nsIDocument* 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);
                }
            }

            nsIDocument* 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();
                }
            }

            nsIDocument* 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();
                }
            }
        }
        nsIDocument* 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<nsIDocument> 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<nsIDocument> 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<nsIDocument> 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)
{
    nsIDocument* 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()
{
    nsIDocument* 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(nsIDocument* 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() == nsIDocument::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);
}

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)) {
        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,
                              nsIDocument* 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;
}