summaryrefslogtreecommitdiffstats
path: root/dom/xul
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/xul
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/xul')
-rw-r--r--dom/xul/ChromeObserver.cpp262
-rw-r--r--dom/xul/ChromeObserver.h46
-rw-r--r--dom/xul/XULBroadcastManager.cpp596
-rw-r--r--dom/xul/XULBroadcastManager.h90
-rw-r--r--dom/xul/XULFrameElement.cpp203
-rw-r--r--dom/xul/XULFrameElement.h86
-rw-r--r--dom/xul/XULMenuElement.cpp100
-rw-r--r--dom/xul/XULMenuElement.h37
-rw-r--r--dom/xul/XULPersist.cpp322
-rw-r--r--dom/xul/XULPersist.h54
-rw-r--r--dom/xul/XULPopupElement.cpp243
-rw-r--r--dom/xul/XULPopupElement.h101
-rw-r--r--dom/xul/XULTextElement.cpp25
-rw-r--r--dom/xul/XULTextElement.h45
-rw-r--r--dom/xul/XULTooltipElement.cpp105
-rw-r--r--dom/xul/XULTooltipElement.h39
-rw-r--r--dom/xul/XULTreeElement.cpp413
-rw-r--r--dom/xul/XULTreeElement.h129
-rw-r--r--dom/xul/crashtests/107518-1.xml52
-rw-r--r--dom/xul/crashtests/253479-1.xhtml6
-rw-r--r--dom/xul/crashtests/253479-2.xhtml4
-rw-r--r--dom/xul/crashtests/326204-1.xhtml0
-rw-r--r--dom/xul/crashtests/326644-1-inner.xhtml34
-rw-r--r--dom/xul/crashtests/326644-1.html9
-rw-r--r--dom/xul/crashtests/326875-1.xhtml27
-rw-r--r--dom/xul/crashtests/329982-1.xhtml42
-rw-r--r--dom/xul/crashtests/336096-1.xhtml41
-rw-r--r--dom/xul/crashtests/344215-1.xhtml7
-rw-r--r--dom/xul/crashtests/354611-1.html20
-rw-r--r--dom/xul/crashtests/360078-1.xhtml42
-rw-r--r--dom/xul/crashtests/360078-1xbl.xml3
-rw-r--r--dom/xul/crashtests/363791-1.xhtml44
-rw-r--r--dom/xul/crashtests/384877-1-inner.xhtml12
-rw-r--r--dom/xul/crashtests/384877-1.html9
-rw-r--r--dom/xul/crashtests/386914-1-inner.xhtml10
-rw-r--r--dom/xul/crashtests/386914-1.html9
-rw-r--r--dom/xul/crashtests/425821-1.xhtml15
-rw-r--r--dom/xul/crashtests/428951-1.xhtml21
-rw-r--r--dom/xul/crashtests/431906-1-inner.xhtml19
-rw-r--r--dom/xul/crashtests/431906-1.html9
-rw-r--r--dom/xul/crashtests/461917-1.xhtml6
-rw-r--r--dom/xul/crashtests/crashtests.list17
-rw-r--r--dom/xul/moz.build89
-rw-r--r--dom/xul/nsIBrowserController.idl22
-rw-r--r--dom/xul/nsIController.idl39
-rw-r--r--dom/xul/nsIControllers.idl34
-rw-r--r--dom/xul/nsXULCommandDispatcher.cpp433
-rw-r--r--dom/xul/nsXULCommandDispatcher.h79
-rw-r--r--dom/xul/nsXULContentSink.cpp884
-rw-r--r--dom/xul/nsXULContentSink.h144
-rw-r--r--dom/xul/nsXULContentUtils.cpp73
-rw-r--r--dom/xul/nsXULContentUtils.h45
-rw-r--r--dom/xul/nsXULControllers.cpp214
-rw-r--r--dom/xul/nsXULControllers.h51
-rw-r--r--dom/xul/nsXULElement.cpp1974
-rw-r--r--dom/xul/nsXULElement.h592
-rw-r--r--dom/xul/nsXULPopupListener.cpp355
-rw-r--r--dom/xul/nsXULPopupListener.h67
-rw-r--r--dom/xul/nsXULPrototypeCache.cpp533
-rw-r--r--dom/xul/nsXULPrototypeCache.h128
-rw-r--r--dom/xul/nsXULPrototypeDocument.cpp535
-rw-r--r--dom/xul/nsXULPrototypeDocument.h130
-rw-r--r--dom/xul/nsXULSortService.cpp397
-rw-r--r--dom/xul/nsXULSortService.h42
-rw-r--r--dom/xul/test/.eslintrc.js5
-rw-r--r--dom/xul/test/398289-resource.xhtml46
-rw-r--r--dom/xul/test/chrome.ini26
-rw-r--r--dom/xul/test/file_bug236853.rdf14
-rw-r--r--dom/xul/test/mochitest.ini6
-rw-r--r--dom/xul/test/test_bug1070049_throw_from_script.xhtml40
-rw-r--r--dom/xul/test/test_bug1290965.xhtml38
-rw-r--r--dom/xul/test/test_bug1686822.xhtml35
-rw-r--r--dom/xul/test/test_bug199692.xhtml102
-rw-r--r--dom/xul/test/test_bug311681.xhtml106
-rw-r--r--dom/xul/test/test_bug391002.xhtml41
-rw-r--r--dom/xul/test/test_bug398289.html40
-rw-r--r--dom/xul/test/test_bug403868.xhtml83
-rw-r--r--dom/xul/test/test_bug418216.xhtml46
-rw-r--r--dom/xul/test/test_bug445177.xhtml66
-rw-r--r--dom/xul/test/test_bug449457.xhtml25
-rw-r--r--dom/xul/test/test_bug468176.xhtml84
-rw-r--r--dom/xul/test/test_bug486990.xhtml155
-rw-r--r--dom/xul/test/test_bug583948.xhtml41
-rw-r--r--dom/xul/test/test_bug749367.xhtml39
-rw-r--r--dom/xul/test/test_bug757137.xhtml37
-rw-r--r--dom/xul/test/test_bug775972.xhtml35
-rw-r--r--dom/xul/test/test_disable_scroll_frame_plain.html19
-rw-r--r--dom/xul/test/test_html_template.xhtml18
-rw-r--r--dom/xul/test/test_import_xul_to_content.xhtml69
-rw-r--r--dom/xul/test/test_xul_tabindex_focus.xhtml44
-rw-r--r--dom/xul/test/window_bug1686822.xhtml7
-rw-r--r--dom/xul/test/window_bug583948.xhtml8
-rw-r--r--dom/xul/test/window_bug757137.xhtml6
93 files changed, 11495 insertions, 0 deletions
diff --git a/dom/xul/ChromeObserver.cpp b/dom/xul/ChromeObserver.cpp
new file mode 100644
index 0000000000..37eeb261f6
--- /dev/null
+++ b/dom/xul/ChromeObserver.cpp
@@ -0,0 +1,262 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#include "ChromeObserver.h"
+
+#include "nsIBaseWindow.h"
+#include "nsIWidget.h"
+#include "nsIFrame.h"
+
+#include "nsContentUtils.h"
+#include "nsView.h"
+#include "nsPresContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/MutationEventBinding.h"
+#include "nsXULElement.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS(ChromeObserver, nsIMutationObserver)
+
+ChromeObserver::ChromeObserver(Document* aDocument)
+ : nsStubMutationObserver(), mDocument(aDocument) {}
+
+ChromeObserver::~ChromeObserver() = default;
+
+void ChromeObserver::Init() {
+ mDocument->AddMutationObserver(this);
+ Element* rootElement = mDocument->GetRootElement();
+ if (!rootElement) {
+ return;
+ }
+ nsAutoScriptBlocker scriptBlocker;
+ uint32_t attributeCount = rootElement->GetAttrCount();
+ for (uint32_t i = 0; i < attributeCount; i++) {
+ BorrowedAttrInfo info = rootElement->GetAttrInfoAt(i);
+ const nsAttrName* name = info.mName;
+ if (name->LocalName() == nsGkAtoms::chromemargin) {
+ // Some linux windows managers have an issue when the chrome margin is
+ // applied while the browser is loading (bug 1598848). For now, skip
+ // applying this attribute when initializing.
+ continue;
+ }
+ AttributeChanged(rootElement, name->NamespaceID(), name->LocalName(),
+ MutationEvent_Binding::ADDITION, nullptr);
+ }
+}
+
+nsIWidget* ChromeObserver::GetWindowWidget() {
+ // only top level chrome documents can set the titlebar color
+ if (mDocument && mDocument->IsRootDisplayDocument()) {
+ nsCOMPtr<nsISupports> container = mDocument->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 ChromeObserver::SetDrawsInTitlebar(bool aState) {
+ nsIWidget* mainWidget = GetWindowWidget();
+ if (mainWidget) {
+ nsContentUtils::AddScriptRunner(
+ new SetDrawInTitleBarEvent(mainWidget, aState));
+ }
+}
+
+void ChromeObserver::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 ChromeObserver::UpdateBrightTitlebarForeground() {
+ 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(
+ mDocument->GetDocumentLWTheme() == Document::Doc_Theme_Bright ||
+ mDocument->GetRootElement()->AttrValueIs(
+ kNameSpaceID_None, nsGkAtoms::brighttitlebarforeground, u"true"_ns,
+ 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 ChromeObserver::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 ChromeObserver::AttributeChanged(dom::Element* aElement,
+ int32_t aNamespaceID, nsAtom* aName,
+ int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ // We only care about changes to the root element.
+ if (!mDocument || aElement != mDocument->GetRootElement()) {
+ return;
+ }
+
+ const nsAttrValue* value = aElement->GetParsedAttr(aName, aNamespaceID);
+ if (value) {
+ // Hide chrome if needed
+ if (aName == nsGkAtoms::hidechrome) {
+ HideWindowChrome(value->Equals(u"true"_ns, eCaseMatters));
+ } else if (aName == nsGkAtoms::chromemargin) {
+ SetChromeMargins(value);
+ } else if (aName == nsGkAtoms::windowtype && aElement->IsXULElement()) {
+ RefPtr<nsXULElement> xulElement = nsXULElement::FromNodeOrNull(aElement);
+ xulElement->MaybeUpdatePrivateLifetime();
+ }
+ // title and drawintitlebar are settable on
+ // any root node (windows, dialogs, etc)
+ else if (aName == nsGkAtoms::title) {
+ mDocument->NotifyPossibleTitleChange(false);
+ } else if (aName == nsGkAtoms::drawintitlebar) {
+ SetDrawsInTitlebar(value->Equals(u"true"_ns, eCaseMatters));
+ } else if (aName == nsGkAtoms::drawtitle) {
+ SetDrawsTitle(value->Equals(u"true"_ns, eCaseMatters));
+ } else if (aName == nsGkAtoms::localedir) {
+ // if the localedir changed on the root element, reset the document
+ // direction
+ mDocument->ResetDocumentDirection();
+ } else if (aName == nsGkAtoms::lwtheme ||
+ aName == nsGkAtoms::lwthemetextcolor) {
+ // if the lwtheme changed, make sure to reset the document lwtheme
+ // cache
+ mDocument->ResetDocumentLWTheme();
+ UpdateBrightTitlebarForeground();
+ } else if (aName == nsGkAtoms::brighttitlebarforeground) {
+ UpdateBrightTitlebarForeground();
+ }
+ } else {
+ if (aName == nsGkAtoms::hidechrome) {
+ HideWindowChrome(false);
+ } else if (aName == nsGkAtoms::chromemargin) {
+ ResetChromeMargins();
+ } else if (aName == nsGkAtoms::localedir) {
+ // if the localedir changed on the root element, reset the document
+ // direction
+ mDocument->ResetDocumentDirection();
+ } else if ((aName == nsGkAtoms::lwtheme ||
+ aName == nsGkAtoms::lwthemetextcolor)) {
+ // if the lwtheme changed, make sure to restyle appropriately
+ mDocument->ResetDocumentLWTheme();
+ UpdateBrightTitlebarForeground();
+ } else if (aName == nsGkAtoms::brighttitlebarforeground) {
+ UpdateBrightTitlebarForeground();
+ } else if (aName == nsGkAtoms::drawintitlebar) {
+ SetDrawsInTitlebar(false);
+ } else if (aName == nsGkAtoms::drawtitle) {
+ SetDrawsTitle(false);
+ }
+ }
+}
+
+void ChromeObserver::NodeWillBeDestroyed(const nsINode* aNode) {
+ mDocument = nullptr;
+}
+
+void ChromeObserver::ResetChromeMargins() {
+ nsIWidget* mainWidget = GetWindowWidget();
+ if (!mainWidget) return;
+ // See nsIWidget
+ nsContentUtils::AddScriptRunner(new MarginSetter(mainWidget));
+}
+
+nsresult ChromeObserver::HideWindowChrome(bool aShouldHide) {
+ // only top level chrome documents can hide the window chrome
+ if (!mDocument->IsRootDisplayDocument()) return NS_OK;
+
+ nsPresContext* presContext = mDocument->GetPresContext();
+
+ if (presContext && presContext->IsChrome()) {
+ nsIFrame* frame = mDocument->GetDocumentElement()->GetPrimaryFrame();
+
+ if (frame) {
+ nsView* view = frame->GetClosestView();
+
+ if (view) {
+ nsIWidget* w = view->GetWidget();
+ NS_ENSURE_STATE(w);
+ w->HideWindowChrome(aShouldHide);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/xul/ChromeObserver.h b/dom/xul/ChromeObserver.h
new file mode 100644
index 0000000000..d974e7e35e
--- /dev/null
+++ b/dom/xul/ChromeObserver.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_ChromeObserver_h
+#define mozilla_dom_ChromeObserver_h
+
+#include "nsStubMutationObserver.h"
+
+class nsIWidget;
+
+namespace mozilla {
+namespace dom {
+class Document;
+
+class ChromeObserver final : public nsStubMutationObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit ChromeObserver(Document* aDocument);
+ void Init();
+
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+ protected:
+ nsIWidget* GetWindowWidget();
+ void SetDrawsInTitlebar(bool aState);
+ void SetDrawsTitle(bool aState);
+ void UpdateBrightTitlebarForeground();
+ void SetChromeMargins(const nsAttrValue* aValue);
+ nsresult HideWindowChrome(bool aShouldHide);
+
+ private:
+ void ResetChromeMargins();
+ ~ChromeObserver();
+ // A weak pointer cleared when the element will be destroyed.
+ Document* MOZ_NON_OWNING_REF mDocument;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ChromeObserver_h
diff --git a/dom/xul/XULBroadcastManager.cpp b/dom/xul/XULBroadcastManager.cpp
new file mode 100644
index 0000000000..9246ad19be
--- /dev/null
+++ b/dom/xul/XULBroadcastManager.cpp
@@ -0,0 +1,596 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 et tw=80: */
+/* 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 "XULBroadcastManager.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/Logging.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "nsXULElement.h"
+
+struct BroadcastListener {
+ nsWeakPtr mListener;
+ RefPtr<nsAtom> mAttribute;
+};
+
+struct BroadcasterMapEntry : public PLDHashEntryHdr {
+ mozilla::dom::Element* mBroadcaster; // [WEAK]
+ nsTArray<BroadcastListener*>
+ mListeners; // [OWNING] of BroadcastListener objects
+};
+
+struct nsAttrNameInfo {
+ nsAttrNameInfo(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix)
+ : mNamespaceID(aNamespaceID), mName(aName), mPrefix(aPrefix) {}
+ nsAttrNameInfo(const nsAttrNameInfo& aOther) = delete;
+ nsAttrNameInfo(nsAttrNameInfo&& aOther) = default;
+
+ int32_t mNamespaceID;
+ RefPtr<nsAtom> mName;
+ RefPtr<nsAtom> mPrefix;
+};
+
+static void ClearBroadcasterMapEntry(PLDHashTable* aTable,
+ PLDHashEntryHdr* aEntry) {
+ BroadcasterMapEntry* entry = static_cast<BroadcasterMapEntry*>(aEntry);
+ for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+ delete entry->mListeners[i];
+ }
+ entry->mListeners.Clear();
+
+ // N.B. that we need to manually run the dtor because we
+ // constructed the nsTArray object in-place.
+ entry->mListeners.~nsTArray<BroadcastListener*>();
+}
+
+static bool CanBroadcast(int32_t aNameSpaceID, nsAtom* aAttribute) {
+ // Don't push changes to the |id|, |persist|, |command| or
+ // |observes| attribute.
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if ((aAttribute == nsGkAtoms::id) || (aAttribute == nsGkAtoms::persist) ||
+ (aAttribute == nsGkAtoms::command) ||
+ (aAttribute == nsGkAtoms::observes)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+namespace mozilla {
+namespace dom {
+static LazyLogModule sXULBroadCastManager("XULBroadcastManager");
+
+class XULBroadcastManager::nsDelayedBroadcastUpdate {
+ public:
+ nsDelayedBroadcastUpdate(Element* aBroadcaster, Element* aListener,
+ const nsAString& aAttr)
+ : mBroadcaster(aBroadcaster),
+ mListener(aListener),
+ mAttr(aAttr),
+ mSetAttr(false),
+ mNeedsAttrChange(false) {}
+
+ nsDelayedBroadcastUpdate(Element* aBroadcaster, Element* aListener,
+ nsAtom* aAttrName, const nsAString& aAttr,
+ bool aSetAttr, bool aNeedsAttrChange)
+ : mBroadcaster(aBroadcaster),
+ mListener(aListener),
+ mAttr(aAttr),
+ mAttrName(aAttrName),
+ mSetAttr(aSetAttr),
+ mNeedsAttrChange(aNeedsAttrChange) {}
+
+ nsDelayedBroadcastUpdate(const nsDelayedBroadcastUpdate& aOther) = delete;
+ nsDelayedBroadcastUpdate(nsDelayedBroadcastUpdate&& aOther) = default;
+
+ nsCOMPtr<Element> mBroadcaster;
+ nsCOMPtr<Element> mListener;
+ // Note if mAttrName isn't used, this is the name of the attr, otherwise
+ // this is the value of the attribute.
+ nsString mAttr;
+ RefPtr<nsAtom> mAttrName;
+ bool mSetAttr;
+ bool mNeedsAttrChange;
+
+ class Comparator {
+ public:
+ static bool Equals(const nsDelayedBroadcastUpdate& a,
+ const nsDelayedBroadcastUpdate& b) {
+ return a.mBroadcaster == b.mBroadcaster && a.mListener == b.mListener &&
+ a.mAttrName == b.mAttrName;
+ }
+ };
+};
+
+/* static */
+bool XULBroadcastManager::MayNeedListener(const Element& aElement) {
+ if (aElement.NodeInfo()->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
+ return true;
+ }
+ if (aElement.HasAttr(nsGkAtoms::observes)) {
+ return true;
+ }
+ if (aElement.HasAttr(nsGkAtoms::command) &&
+ !(aElement.NodeInfo()->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
+ aElement.NodeInfo()->Equals(nsGkAtoms::key, kNameSpaceID_XUL))) {
+ return true;
+ }
+ return false;
+}
+
+XULBroadcastManager::XULBroadcastManager(Document* aDocument)
+ : mDocument(aDocument),
+ mBroadcasterMap(nullptr),
+ mHandlingDelayedAttrChange(false),
+ mHandlingDelayedBroadcasters(false) {}
+
+XULBroadcastManager::~XULBroadcastManager() { delete mBroadcasterMap; }
+
+void XULBroadcastManager::DropDocumentReference(void) { mDocument = nullptr; }
+
+void XULBroadcastManager::SynchronizeBroadcastListener(Element* aBroadcaster,
+ Element* aListener,
+ const nsAString& aAttr) {
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ mDelayedBroadcasters.EmplaceBack(aBroadcaster, aListener, aAttr);
+ MaybeBroadcast();
+ return;
+ }
+ bool notify = mHandlingDelayedBroadcasters;
+
+ if (aAttr.EqualsLiteral("*")) {
+ uint32_t count = aBroadcaster->GetAttrCount();
+ nsTArray<nsAttrNameInfo> attributes(count);
+ for (uint32_t i = 0; i < count; ++i) {
+ const nsAttrName* attrName = aBroadcaster->GetAttrNameAt(i);
+ int32_t nameSpaceID = attrName->NamespaceID();
+ nsAtom* name = attrName->LocalName();
+
+ // _Don't_ push the |id|, |ref|, or |persist| attribute's value!
+ if (!CanBroadcast(nameSpaceID, name)) continue;
+
+ attributes.AppendElement(
+ nsAttrNameInfo(nameSpaceID, name, attrName->GetPrefix()));
+ }
+
+ count = attributes.Length();
+ while (count-- > 0) {
+ int32_t nameSpaceID = attributes[count].mNamespaceID;
+ nsAtom* name = attributes[count].mName;
+ nsAutoString value;
+ if (aBroadcaster->GetAttr(nameSpaceID, name, value)) {
+ aListener->SetAttr(nameSpaceID, name, attributes[count].mPrefix, value,
+ notify);
+ }
+
+#if 0
+ // XXX we don't fire the |onbroadcast| handler during
+ // initial hookup: doing so would potentially run the
+ // |onbroadcast| handler before the |onload| handler,
+ // which could define JS properties that mask XBL
+ // properties, etc.
+ ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
+#endif
+ }
+ } else {
+ // Find out if the attribute is even present at all.
+ RefPtr<nsAtom> name = NS_Atomize(aAttr);
+
+ nsAutoString value;
+ if (aBroadcaster->GetAttr(kNameSpaceID_None, name, value)) {
+ aListener->SetAttr(kNameSpaceID_None, name, value, notify);
+ } else {
+ aListener->UnsetAttr(kNameSpaceID_None, name, notify);
+ }
+
+#if 0
+ // XXX we don't fire the |onbroadcast| handler during initial
+ // hookup: doing so would potentially run the |onbroadcast|
+ // handler before the |onload| handler, which could define JS
+ // properties that mask XBL properties, etc.
+ ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
+#endif
+ }
+}
+
+void XULBroadcastManager::AddListenerFor(Element& aBroadcaster,
+ Element& aListener,
+ const nsAString& aAttr,
+ ErrorResult& aRv) {
+ if (!mDocument) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsresult rv = nsContentUtils::CheckSameOrigin(mDocument, &aBroadcaster);
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ rv = nsContentUtils::CheckSameOrigin(mDocument, &aListener);
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ static const PLDHashTableOps gOps = {
+ PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
+ PLDHashTable::MoveEntryStub, ClearBroadcasterMapEntry, nullptr};
+
+ if (!mBroadcasterMap) {
+ mBroadcasterMap = new PLDHashTable(&gOps, sizeof(BroadcasterMapEntry));
+ }
+
+ auto entry =
+ static_cast<BroadcasterMapEntry*>(mBroadcasterMap->Search(&aBroadcaster));
+ if (!entry) {
+ entry = static_cast<BroadcasterMapEntry*>(
+ mBroadcasterMap->Add(&aBroadcaster, fallible));
+
+ if (!entry) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ entry->mBroadcaster = &aBroadcaster;
+
+ // N.B. placement new to construct the nsTArray object in-place
+ new (&entry->mListeners) nsTArray<BroadcastListener*>();
+ }
+
+ // Only add the listener if it's not there already!
+ RefPtr<nsAtom> attr = NS_Atomize(aAttr);
+
+ for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+ BroadcastListener* bl = entry->mListeners[i];
+ nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
+
+ if (blListener == &aListener && bl->mAttribute == attr) return;
+ }
+
+ BroadcastListener* bl = new BroadcastListener;
+ bl->mListener = do_GetWeakReference(&aListener);
+ bl->mAttribute = attr;
+
+ entry->mListeners.AppendElement(bl);
+
+ SynchronizeBroadcastListener(&aBroadcaster, &aListener, aAttr);
+}
+
+void XULBroadcastManager::RemoveListenerFor(Element& aBroadcaster,
+ Element& aListener,
+ const nsAString& aAttr) {
+ // If we haven't added any broadcast listeners, then there sure
+ // aren't any to remove.
+ if (!mBroadcasterMap) return;
+
+ auto entry =
+ static_cast<BroadcasterMapEntry*>(mBroadcasterMap->Search(&aBroadcaster));
+ if (entry) {
+ RefPtr<nsAtom> attr = NS_Atomize(aAttr);
+ for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+ BroadcastListener* bl = entry->mListeners[i];
+ nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
+
+ if (blListener == &aListener && bl->mAttribute == attr) {
+ entry->mListeners.RemoveElementAt(i);
+ delete bl;
+
+ if (entry->mListeners.IsEmpty()) mBroadcasterMap->RemoveEntry(entry);
+
+ break;
+ }
+ }
+ }
+}
+
+nsresult XULBroadcastManager::ExecuteOnBroadcastHandlerFor(
+ Element* aBroadcaster, Element* aListener, nsAtom* aAttr) {
+ if (!mDocument) {
+ return NS_OK;
+ }
+ // Now we execute the onchange handler in the context of the
+ // observer. We need to find the observer in order to
+ // execute the handler.
+
+ for (nsIContent* child = aListener->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ // Look for an <observes> element beneath the listener. This
+ // ought to have an |element| attribute that refers to
+ // aBroadcaster, and an |attribute| element that tells us what
+ // attriubtes we're listening for.
+ if (!child->IsXULElement(nsGkAtoms::observes)) continue;
+
+ // Is this the element that was listening to us?
+ nsAutoString listeningToID;
+ child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::element,
+ listeningToID);
+
+ nsAutoString broadcasterID;
+ aBroadcaster->GetAttr(kNameSpaceID_None, nsGkAtoms::id, broadcasterID);
+
+ if (listeningToID != broadcasterID) continue;
+
+ // We are observing the broadcaster, but is this the right
+ // attribute?
+ nsAutoString listeningToAttribute;
+ child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute,
+ listeningToAttribute);
+
+ if (!aAttr->Equals(listeningToAttribute) &&
+ !listeningToAttribute.EqualsLiteral("*")) {
+ continue;
+ }
+
+ // This is the right <observes> element. Execute the
+ // |onbroadcast| event handler
+ WidgetEvent event(true, eXULBroadcast);
+
+ RefPtr<nsPresContext> presContext = mDocument->GetPresContext();
+ if (presContext) {
+ // Handle the DOM event
+ nsEventStatus status = nsEventStatus_eIgnore;
+ EventDispatcher::Dispatch(child, presContext, &event, nullptr, &status);
+ }
+ }
+
+ return NS_OK;
+}
+
+void XULBroadcastManager::AttributeChanged(Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute) {
+ if (!mDocument) {
+ return;
+ }
+ NS_ASSERTION(aElement->OwnerDoc() == mDocument, "unexpected doc");
+
+ // Synchronize broadcast listeners
+ if (mBroadcasterMap && CanBroadcast(aNameSpaceID, aAttribute)) {
+ auto entry =
+ static_cast<BroadcasterMapEntry*>(mBroadcasterMap->Search(aElement));
+
+ if (entry) {
+ // We've got listeners: push the value.
+ nsAutoString value;
+ bool attrSet = aElement->GetAttr(kNameSpaceID_None, aAttribute, value);
+
+ for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+ BroadcastListener* bl = entry->mListeners[i];
+ if ((bl->mAttribute == aAttribute) ||
+ (bl->mAttribute == nsGkAtoms::_asterisk)) {
+ nsCOMPtr<Element> listenerEl = do_QueryReferent(bl->mListener);
+ if (listenerEl) {
+ nsAutoString currentValue;
+ bool hasAttr = listenerEl->GetAttr(kNameSpaceID_None, aAttribute,
+ currentValue);
+ // We need to update listener only if we're
+ // (1) removing an existing attribute,
+ // (2) adding a new attribute or
+ // (3) changing the value of an attribute.
+ bool needsAttrChange =
+ attrSet != hasAttr || !value.Equals(currentValue);
+ nsDelayedBroadcastUpdate delayedUpdate(aElement, listenerEl,
+ aAttribute, value, attrSet,
+ needsAttrChange);
+
+ size_t index = mDelayedAttrChangeBroadcasts.IndexOf(
+ delayedUpdate, 0, nsDelayedBroadcastUpdate::Comparator());
+ if (index != mDelayedAttrChangeBroadcasts.NoIndex) {
+ if (mHandlingDelayedAttrChange) {
+ NS_WARNING("Broadcasting loop!");
+ continue;
+ }
+ mDelayedAttrChangeBroadcasts.RemoveElementAt(index);
+ }
+
+ mDelayedAttrChangeBroadcasts.AppendElement(
+ std::move(delayedUpdate));
+ }
+ }
+ }
+ }
+ }
+}
+
+void XULBroadcastManager::MaybeBroadcast() {
+ // Only broadcast when not in an update and when safe to run scripts.
+ if (mDocument && mDocument->UpdateNestingLevel() == 0 &&
+ (mDelayedAttrChangeBroadcasts.Length() ||
+ mDelayedBroadcasters.Length())) {
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ if (mDocument) {
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod("dom::XULBroadcastManager::MaybeBroadcast", this,
+ &XULBroadcastManager::MaybeBroadcast));
+ }
+ return;
+ }
+ if (!mHandlingDelayedAttrChange) {
+ mHandlingDelayedAttrChange = true;
+ for (uint32_t i = 0; i < mDelayedAttrChangeBroadcasts.Length(); ++i) {
+ nsAtom* attrName = mDelayedAttrChangeBroadcasts[i].mAttrName;
+ if (mDelayedAttrChangeBroadcasts[i].mNeedsAttrChange) {
+ nsCOMPtr<Element> listener =
+ mDelayedAttrChangeBroadcasts[i].mListener;
+ const nsString& value = mDelayedAttrChangeBroadcasts[i].mAttr;
+ if (mDelayedAttrChangeBroadcasts[i].mSetAttr) {
+ listener->SetAttr(kNameSpaceID_None, attrName, value, true);
+ } else {
+ listener->UnsetAttr(kNameSpaceID_None, attrName, true);
+ }
+ }
+ ExecuteOnBroadcastHandlerFor(
+ mDelayedAttrChangeBroadcasts[i].mBroadcaster,
+ mDelayedAttrChangeBroadcasts[i].mListener, attrName);
+ }
+ mDelayedAttrChangeBroadcasts.Clear();
+ mHandlingDelayedAttrChange = false;
+ }
+
+ uint32_t length = mDelayedBroadcasters.Length();
+ if (length) {
+ bool oldValue = mHandlingDelayedBroadcasters;
+ mHandlingDelayedBroadcasters = true;
+ nsTArray<nsDelayedBroadcastUpdate> delayedBroadcasters =
+ std::move(mDelayedBroadcasters);
+ for (uint32_t i = 0; i < length; ++i) {
+ SynchronizeBroadcastListener(delayedBroadcasters[i].mBroadcaster,
+ delayedBroadcasters[i].mListener,
+ delayedBroadcasters[i].mAttr);
+ }
+ mHandlingDelayedBroadcasters = oldValue;
+ }
+ }
+}
+
+nsresult XULBroadcastManager::FindBroadcaster(Element* aElement,
+ Element** aListener,
+ nsString& aBroadcasterID,
+ nsString& aAttribute,
+ Element** aBroadcaster) {
+ NodeInfo* ni = aElement->NodeInfo();
+ *aListener = nullptr;
+ *aBroadcaster = nullptr;
+
+ if (ni->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
+ // It's an <observes> element, which means that the actual
+ // listener is the _parent_ node. This element should have an
+ // 'element' attribute that specifies the ID of the
+ // broadcaster element, and an 'attribute' element, which
+ // specifies the name of the attribute to observe.
+ nsIContent* parent = aElement->GetParent();
+ if (!parent) {
+ // <observes> is the root element
+ return NS_FINDBROADCASTER_NOT_FOUND;
+ }
+
+ *aListener = Element::FromNode(parent);
+ NS_IF_ADDREF(*aListener);
+
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, aBroadcasterID);
+ if (aBroadcasterID.IsEmpty()) {
+ return NS_FINDBROADCASTER_NOT_FOUND;
+ }
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, aAttribute);
+ } else {
+ // It's a generic element, which means that we'll use the
+ // value of the 'observes' attribute to determine the ID of
+ // the broadcaster element, and we'll watch _all_ of its
+ // values.
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, aBroadcasterID);
+
+ // Bail if there's no aBroadcasterID
+ if (aBroadcasterID.IsEmpty()) {
+ // Try the command attribute next.
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::command, aBroadcasterID);
+ if (!aBroadcasterID.IsEmpty()) {
+ // We've got something in the command attribute. We
+ // only treat this as a normal broadcaster if we are
+ // not a menuitem or a key.
+
+ if (ni->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
+ ni->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) {
+ return NS_FINDBROADCASTER_NOT_FOUND;
+ }
+ } else {
+ return NS_FINDBROADCASTER_NOT_FOUND;
+ }
+ }
+
+ *aListener = aElement;
+ NS_ADDREF(*aListener);
+
+ aAttribute.Assign('*');
+ }
+
+ // Make sure we got a valid listener.
+ NS_ENSURE_TRUE(*aListener, NS_ERROR_UNEXPECTED);
+
+ // Try to find the broadcaster element in the document.
+ Document* doc = aElement->GetComposedDoc();
+ if (doc) {
+ *aBroadcaster = doc->GetElementById(aBroadcasterID);
+ }
+
+ // The broadcaster element is missing.
+ if (!*aBroadcaster) {
+ return NS_FINDBROADCASTER_NOT_FOUND;
+ }
+
+ NS_ADDREF(*aBroadcaster);
+
+ return NS_FINDBROADCASTER_FOUND;
+}
+
+nsresult XULBroadcastManager::UpdateListenerHookup(Element* aElement,
+ HookupAction aAction) {
+ // Resolve a broadcaster hookup. Look at the element that we're
+ // trying to resolve: it could be an '<observes>' element, or just
+ // a vanilla element with an 'observes' attribute on it.
+ nsresult rv;
+
+ nsCOMPtr<Element> listener;
+ nsAutoString broadcasterID;
+ nsAutoString attribute;
+ nsCOMPtr<Element> broadcaster;
+
+ rv = FindBroadcaster(aElement, getter_AddRefs(listener), broadcasterID,
+ attribute, getter_AddRefs(broadcaster));
+ switch (rv) {
+ case NS_FINDBROADCASTER_NOT_FOUND:
+ return NS_OK;
+ case NS_FINDBROADCASTER_FOUND:
+ break;
+ default:
+ return rv;
+ }
+
+ NS_ENSURE_ARG(broadcaster && listener);
+ if (aAction == eHookupAdd) {
+ ErrorResult domRv;
+ AddListenerFor(*broadcaster, *listener, attribute, domRv);
+ if (domRv.Failed()) {
+ return domRv.StealNSResult();
+ }
+ } else {
+ RemoveListenerFor(*broadcaster, *listener, attribute);
+ }
+
+ // Tell the world we succeeded
+ if (MOZ_LOG_TEST(sXULBroadCastManager, LogLevel::Debug)) {
+ nsCOMPtr<nsIContent> content = listener;
+ NS_ASSERTION(content != nullptr, "not an nsIContent");
+ if (!content) {
+ return rv;
+ }
+
+ nsAutoCString attributeC, broadcasteridC;
+ LossyCopyUTF16toASCII(attribute, attributeC);
+ LossyCopyUTF16toASCII(broadcasterID, broadcasteridC);
+ MOZ_LOG(sXULBroadCastManager, LogLevel::Debug,
+ ("xul: broadcaster hookup <%s attribute='%s'> to %s",
+ nsAtomCString(content->NodeInfo()->NameAtom()).get(),
+ attributeC.get(), broadcasteridC.get()));
+ }
+
+ return NS_OK;
+}
+
+nsresult XULBroadcastManager::AddListener(Element* aElement) {
+ return UpdateListenerHookup(aElement, eHookupAdd);
+}
+
+nsresult XULBroadcastManager::RemoveListener(Element* aElement) {
+ return UpdateListenerHookup(aElement, eHookupRemove);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/xul/XULBroadcastManager.h b/dom/xul/XULBroadcastManager.h
new file mode 100644
index 0000000000..c5fcbf3bf7
--- /dev/null
+++ b/dom/xul/XULBroadcastManager.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_XULBroadcastManager_h
+#define mozilla_dom_XULBroadcastManager_h
+
+#include "nsAtom.h"
+#include "nsTArray.h"
+
+class PLDHashTable;
+class nsXULElement;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class Document;
+class Element;
+
+class XULBroadcastManager final {
+ public:
+ explicit XULBroadcastManager(Document* aDocument);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(XULBroadcastManager)
+
+ /**
+ * Checks whether an element uses any of the special broadcaster attributes
+ * or is an observes element. This mimics the logic in FindBroadcaster, but
+ * is intended to be a lighter weight check and doesn't actually guarantee
+ * that the element will need a listener.
+ */
+ static bool MayNeedListener(const Element& aElement);
+
+ nsresult AddListener(Element* aElement);
+ nsresult RemoveListener(Element* aElement);
+ void AttributeChanged(Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute);
+ void MaybeBroadcast();
+ void DropDocumentReference(); // notification that doc is going away
+ protected:
+ enum HookupAction { eHookupAdd = 0, eHookupRemove };
+
+ nsresult UpdateListenerHookup(Element* aElement, HookupAction aAction);
+
+ void RemoveListenerFor(Element& aBroadcaster, Element& aListener,
+ const nsAString& aAttr);
+ void AddListenerFor(Element& aBroadcaster, Element& aListener,
+ const nsAString& aAttr, ErrorResult& aRv);
+
+ nsresult ExecuteOnBroadcastHandlerFor(Element* aBroadcaster,
+ Element* aListener, nsAtom* aAttr);
+ // The out params of FindBroadcaster only have values that make sense when
+ // the method returns NS_FINDBROADCASTER_FOUND. In all other cases, the
+ // values of the out params should not be relied on (though *aListener and
+ // *aBroadcaster do need to be released if non-null, of course).
+ nsresult FindBroadcaster(Element* aElement, Element** aListener,
+ nsString& aBroadcasterID, nsString& aAttribute,
+ Element** aBroadcaster);
+
+ void SynchronizeBroadcastListener(Element* aBroadcaster, Element* aListener,
+ const nsAString& aAttr);
+
+ // This reference is nulled by the Document in it's destructor through
+ // DropDocumentReference().
+ Document* MOZ_NON_OWNING_REF mDocument;
+
+ /**
+ * A map from a broadcaster element to a list of listener elements.
+ */
+ PLDHashTable* mBroadcasterMap;
+
+ class nsDelayedBroadcastUpdate;
+ nsTArray<nsDelayedBroadcastUpdate> mDelayedBroadcasters;
+ nsTArray<nsDelayedBroadcastUpdate> mDelayedAttrChangeBroadcasts;
+ bool mHandlingDelayedAttrChange;
+ bool mHandlingDelayedBroadcasters;
+
+ private:
+ ~XULBroadcastManager();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_XULBroadcastManager_h
diff --git a/dom/xul/XULFrameElement.cpp b/dom/xul/XULFrameElement.cpp
new file mode 100644
index 0000000000..47ae2b85a9
--- /dev/null
+++ b/dom/xul/XULFrameElement.cpp
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsIBrowser.h"
+#include "nsIContent.h"
+#include "nsIOpenWindowInfo.h"
+#include "nsFrameLoader.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/dom/HTMLIFrameElement.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "mozilla/dom/XULFrameElement.h"
+#include "mozilla/dom/XULFrameElementBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(XULFrameElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XULFrameElement, nsXULElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameLoader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpenWindowInfo)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULFrameElement, nsXULElement)
+ if (tmp->mFrameLoader) {
+ tmp->mFrameLoader->Destroy();
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOpenWindowInfo)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(XULFrameElement, nsXULElement,
+ nsFrameLoaderOwner)
+
+JSObject* XULFrameElement::WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return XULFrameElement_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsDocShell* XULFrameElement::GetDocShell() {
+ RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+ return frameLoader ? frameLoader->GetDocShell(IgnoreErrors()) : nullptr;
+}
+
+already_AddRefed<nsIWebNavigation> XULFrameElement::GetWebNavigation() {
+ nsCOMPtr<nsIDocShell> docShell = GetDocShell();
+ nsCOMPtr<nsIWebNavigation> webnav = do_QueryInterface(docShell);
+ return webnav.forget();
+}
+
+Nullable<WindowProxyHolder> XULFrameElement::GetContentWindow() {
+ RefPtr<nsDocShell> docShell = GetDocShell();
+ if (docShell) {
+ return docShell->GetWindowProxy();
+ }
+
+ return nullptr;
+}
+
+Document* XULFrameElement::GetContentDocument() {
+ nsCOMPtr<nsIDocShell> docShell = GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsPIDOMWindowOuter> win = docShell->GetWindow();
+ if (win) {
+ return win->GetDoc();
+ }
+ }
+ return nullptr;
+}
+
+uint64_t XULFrameElement::BrowserId() {
+ if (mFrameLoader) {
+ if (auto* bc = mFrameLoader->GetExtantBrowsingContext()) {
+ return bc->GetBrowserId();
+ }
+ }
+ return 0;
+}
+
+nsIOpenWindowInfo* XULFrameElement::GetOpenWindowInfo() const {
+ return mOpenWindowInfo;
+}
+
+void XULFrameElement::SetOpenWindowInfo(nsIOpenWindowInfo* aInfo) {
+ mOpenWindowInfo = aInfo;
+}
+
+void XULFrameElement::LoadSrc() {
+ if (!IsInUncomposedDoc() || !OwnerDoc()->GetRootElement()) {
+ return;
+ }
+ RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+ if (!frameLoader) {
+ // We may have had a nsIOpenWindowInfo set on us by browser chrome, due to
+ // being used as the target for a `window.open` call. Fetch that information
+ // if it's available, and clear it out so we don't read it again.
+ nsCOMPtr<nsIOpenWindowInfo> openWindowInfo = mOpenWindowInfo.forget();
+
+ // false as the networkCreated parameter so that xul:iframe/browser/editor
+ // session history handling works like dynamic html:iframes. Usually xul
+ // elements are used in chrome, which doesn't have session history at all.
+ mFrameLoader = nsFrameLoader::Create(this, false, openWindowInfo);
+ if (NS_WARN_IF(!mFrameLoader)) {
+ return;
+ }
+
+ (new AsyncEventDispatcher(this, u"XULFrameLoaderCreated"_ns,
+ CanBubble::eYes))
+ ->RunDOMEventWhenSafe();
+ }
+
+ mFrameLoader->LoadFrame(false);
+}
+
+void XULFrameElement::SwapFrameLoaders(HTMLIFrameElement& aOtherLoaderOwner,
+ ErrorResult& rv) {
+ aOtherLoaderOwner.SwapFrameLoaders(this, rv);
+}
+
+void XULFrameElement::SwapFrameLoaders(XULFrameElement& aOtherLoaderOwner,
+ ErrorResult& rv) {
+ if (&aOtherLoaderOwner == this) {
+ // nothing to do
+ return;
+ }
+
+ aOtherLoaderOwner.SwapFrameLoaders(this, rv);
+}
+
+void XULFrameElement::SwapFrameLoaders(nsFrameLoaderOwner* aOtherLoaderOwner,
+ mozilla::ErrorResult& rv) {
+ if (RefPtr<Document> doc = GetComposedDoc()) {
+ // SwapWithOtherLoader relies on frames being up-to-date.
+ doc->FlushPendingNotifications(FlushType::Frames);
+ }
+
+ RefPtr<nsFrameLoader> loader = GetFrameLoader();
+ RefPtr<nsFrameLoader> otherLoader = aOtherLoaderOwner->GetFrameLoader();
+ if (!loader || !otherLoader) {
+ rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return;
+ }
+
+ rv = loader->SwapWithOtherLoader(otherLoader, this, aOtherLoaderOwner);
+}
+
+nsresult XULFrameElement::BindToTree(BindContext& aContext, nsINode& aParent) {
+ nsresult rv = nsXULElement::BindToTree(aContext, aParent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (IsInUncomposedDoc()) {
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "Missing a script blocker!");
+ // We're in a document now. Kick off the frame load.
+ LoadSrc();
+ }
+
+ return NS_OK;
+}
+
+void XULFrameElement::UnbindFromTree(bool aNullParent) {
+ if (RefPtr<nsFrameLoader> frameLoader = GetFrameLoader()) {
+ frameLoader->Destroy();
+ }
+ mFrameLoader = nullptr;
+
+ nsXULElement::UnbindFromTree(aNullParent);
+}
+
+void XULFrameElement::DestroyContent() {
+ RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+ if (frameLoader) {
+ frameLoader->Destroy();
+ }
+ mFrameLoader = nullptr;
+
+ nsXULElement::DestroyContent();
+}
+
+nsresult XULFrameElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue,
+ const nsAttrValue* aOldValue,
+ nsIPrincipal* aSubjectPrincipal,
+ bool aNotify) {
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aName == nsGkAtoms::src && aValue) {
+ LoadSrc();
+ } else if (aName == nsGkAtoms::disablefullscreen && mFrameLoader) {
+ if (auto* bc = mFrameLoader->GetExtantBrowsingContext()) {
+ MOZ_ALWAYS_SUCCEEDS(bc->SetFullscreenAllowedByOwner(!aValue));
+ }
+ }
+ }
+
+ return nsXULElement::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue,
+ aSubjectPrincipal, aNotify);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/xul/XULFrameElement.h b/dom/xul/XULFrameElement.h
new file mode 100644
index 0000000000..72247c7f06
--- /dev/null
+++ b/dom/xul/XULFrameElement.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef XULFrameElement_h__
+#define XULFrameElement_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "js/TypeDecls.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIOpenWindowInfo.h"
+#include "nsWrapperCache.h"
+#include "nsString.h"
+#include "nsXULElement.h"
+#include "nsFrameLoaderOwner.h"
+
+class nsIWebNavigation;
+class nsFrameLoader;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class BrowsingContext;
+
+class XULFrameElement final : public nsXULElement, public nsFrameLoaderOwner {
+ public:
+ explicit XULFrameElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsXULElement(std::move(aNodeInfo)) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULFrameElement, nsXULElement)
+
+ // XULFrameElement.webidl
+ nsDocShell* GetDocShell();
+ already_AddRefed<nsIWebNavigation> GetWebNavigation();
+ Nullable<WindowProxyHolder> GetContentWindow();
+ Document* GetContentDocument();
+ uint64_t BrowserId();
+ nsIOpenWindowInfo* GetOpenWindowInfo() const;
+ void SetOpenWindowInfo(nsIOpenWindowInfo* aInfo);
+
+ void SwapFrameLoaders(mozilla::dom::HTMLIFrameElement& aOtherLoaderOwner,
+ mozilla::ErrorResult& rv);
+ void SwapFrameLoaders(XULFrameElement& aOtherLoaderOwner,
+ mozilla::ErrorResult& rv);
+ void SwapFrameLoaders(nsFrameLoaderOwner* aOtherLoaderOwner,
+ mozilla::ErrorResult& rv);
+
+ // nsIContent
+ virtual nsresult BindToTree(BindContext&, nsINode& aParent) override;
+ virtual void UnbindFromTree(bool aNullParent) override;
+ virtual void DestroyContent() override;
+
+ virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue,
+ const nsAttrValue* aOldValue,
+ nsIPrincipal* aSubjectPrincipal,
+ bool aNotify) override;
+
+ NS_IMPL_FROMNODE_HELPER(XULFrameElement,
+ IsAnyOfXULElements(nsGkAtoms::iframe,
+ nsGkAtoms::browser,
+ nsGkAtoms::editor))
+
+ protected:
+ virtual ~XULFrameElement() = default;
+
+ JSObject* WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void LoadSrc();
+
+ private:
+ nsCOMPtr<nsIOpenWindowInfo> mOpenWindowInfo;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // XULFrameElement_h
diff --git a/dom/xul/XULMenuElement.cpp b/dom/xul/XULMenuElement.cpp
new file mode 100644
index 0000000000..65bd6c13c4
--- /dev/null
+++ b/dom/xul/XULMenuElement.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/KeyboardEvent.h"
+#include "mozilla/dom/KeyboardEventBinding.h"
+#include "mozilla/dom/Element.h"
+#include "nsIFrame.h"
+#include "nsMenuBarFrame.h"
+#include "nsMenuBarListener.h"
+#include "nsMenuFrame.h"
+#include "nsMenuPopupFrame.h"
+#include "mozilla/dom/XULMenuElement.h"
+#include "mozilla/dom/XULMenuElementBinding.h"
+#include "nsXULPopupManager.h"
+
+namespace mozilla {
+namespace dom {
+
+JSObject* XULMenuElement::WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return XULMenuElement_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<Element> XULMenuElement::GetActiveChild() {
+ nsMenuFrame* menu = do_QueryFrame(GetPrimaryFrame(FlushType::Frames));
+ if (menu) {
+ RefPtr<Element> el;
+ menu->GetActiveChild(getter_AddRefs(el));
+ return el.forget();
+ }
+ return nullptr;
+}
+
+void XULMenuElement::SetActiveChild(Element* arg) {
+ nsMenuFrame* menu = do_QueryFrame(GetPrimaryFrame(FlushType::Frames));
+ if (menu) {
+ menu->SetActiveChild(arg);
+ }
+}
+
+bool XULMenuElement::HandleKeyPress(KeyboardEvent& keyEvent) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!pm) {
+ return false;
+ }
+
+ // if event has already been handled, bail
+ if (keyEvent.DefaultPrevented()) {
+ return false;
+ }
+
+ if (nsMenuBarListener::IsAccessKeyPressed(&keyEvent)) return false;
+
+ nsMenuFrame* menu = do_QueryFrame(GetPrimaryFrame(FlushType::Frames));
+ if (!menu) {
+ return false;
+ }
+
+ nsMenuPopupFrame* popupFrame = menu->GetPopup();
+ if (!popupFrame) {
+ return false;
+ }
+
+ uint32_t keyCode = keyEvent.KeyCode();
+ switch (keyCode) {
+ case KeyboardEvent_Binding::DOM_VK_UP:
+ case KeyboardEvent_Binding::DOM_VK_DOWN:
+ case KeyboardEvent_Binding::DOM_VK_HOME:
+ case KeyboardEvent_Binding::DOM_VK_END: {
+ nsNavigationDirection theDirection;
+ theDirection = NS_DIRECTION_FROM_KEY_CODE(popupFrame, keyCode);
+ return pm->HandleKeyboardNavigationInPopup(popupFrame, theDirection);
+ }
+ default:
+ return pm->HandleShortcutNavigation(&keyEvent, popupFrame);
+ }
+}
+
+bool XULMenuElement::OpenedWithKey() {
+ nsMenuFrame* menuframe = do_QueryFrame(GetPrimaryFrame(FlushType::Frames));
+ if (!menuframe) {
+ return false;
+ }
+
+ nsIFrame* frame = menuframe->GetParent();
+ while (frame) {
+ nsMenuBarFrame* menubar = do_QueryFrame(frame);
+ if (menubar) {
+ return menubar->IsActiveByKeyboard();
+ }
+ frame = frame->GetParent();
+ }
+ return false;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/xul/XULMenuElement.h b/dom/xul/XULMenuElement.h
new file mode 100644
index 0000000000..5fead7e18f
--- /dev/null
+++ b/dom/xul/XULMenuElement.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_XULMenuElement_h
+#define mozilla_dom_XULMenuElement_h
+
+#include "nsXULElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class KeyboardEvent;
+
+class XULMenuElement final : public nsXULElement {
+ public:
+ explicit XULMenuElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsXULElement(std::move(aNodeInfo)) {}
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Element> GetActiveChild();
+ MOZ_CAN_RUN_SCRIPT void SetActiveChild(Element* arg);
+ MOZ_CAN_RUN_SCRIPT bool HandleKeyPress(KeyboardEvent& keyEvent);
+ MOZ_CAN_RUN_SCRIPT bool OpenedWithKey();
+
+ private:
+ virtual ~XULMenuElement() = default;
+ JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ nsIFrame* GetFrame();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // XULMenuElement_h
diff --git a/dom/xul/XULPersist.cpp b/dom/xul/XULPersist.cpp
new file mode 100644
index 0000000000..e119b79d7d
--- /dev/null
+++ b/dom/xul/XULPersist.cpp
@@ -0,0 +1,322 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#include "XULPersist.h"
+
+#ifdef MOZ_NEW_XULSTORE
+# include "mozilla/XULStore.h"
+#else
+# include "nsIXULStore.h"
+# include "nsIStringEnumerator.h"
+#endif
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "nsContentUtils.h"
+#include "nsIAppWindow.h"
+
+namespace mozilla {
+namespace dom {
+
+static bool IsRootElement(Element* aElement) {
+ return aElement->OwnerDoc()->GetRootElement() == aElement;
+}
+
+static bool ShouldPersistAttribute(Element* aElement, nsAtom* aAttribute) {
+ if (IsRootElement(aElement)) {
+ // This is not an element of the top document, its owner is
+ // not an AppWindow. Persist it.
+ if (aElement->OwnerDoc()->GetInProcessParentDocument()) {
+ return true;
+ }
+ // The following attributes of xul:window should be handled in
+ // AppWindow::SavePersistentAttributes instead of here.
+ if (aAttribute == nsGkAtoms::screenX || aAttribute == nsGkAtoms::screenY ||
+ aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height ||
+ aAttribute == nsGkAtoms::sizemode) {
+ return false;
+ }
+ }
+ return true;
+}
+
+NS_IMPL_ISUPPORTS(XULPersist, nsIDocumentObserver)
+
+XULPersist::XULPersist(Document* aDocument)
+ : nsStubDocumentObserver(), mDocument(aDocument) {}
+
+XULPersist::~XULPersist() = default;
+
+void XULPersist::Init() {
+ ApplyPersistentAttributes();
+ mDocument->AddObserver(this);
+}
+
+void XULPersist::DropDocumentReference() {
+ mDocument->RemoveObserver(this);
+ mDocument = nullptr;
+}
+
+void XULPersist::AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ NS_ASSERTION(aElement->OwnerDoc() == mDocument, "unexpected doc");
+
+ // See if there is anything we need to persist in the localstore.
+ //
+ // XXX Namespace handling broken :-(
+ nsAutoString persist;
+ // Persistence of attributes of xul:window is handled in AppWindow.
+ if (aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist) &&
+ ShouldPersistAttribute(aElement, aAttribute) && !persist.IsEmpty() &&
+ // XXXldb This should check that it's a token, not just a substring.
+ persist.Find(nsDependentAtomString(aAttribute)) >= 0) {
+ // Might not need this, but be safe for now.
+ nsCOMPtr<nsIDocumentObserver> kungFuDeathGrip(this);
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod<Element*, int32_t, nsAtom*>(
+ "dom::XULPersist::Persist", this, &XULPersist::Persist, aElement,
+ kNameSpaceID_None, aAttribute));
+ }
+}
+
+void XULPersist::Persist(Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute) {
+ if (!mDocument) {
+ return;
+ }
+ // For non-chrome documents, persistance is simply broken
+ if (!mDocument->NodePrincipal()->IsSystemPrincipal()) {
+ return;
+ }
+
+#ifndef MOZ_NEW_XULSTORE
+ if (!mLocalStore) {
+ mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
+ if (NS_WARN_IF(!mLocalStore)) {
+ return;
+ }
+ }
+#endif
+
+ nsAutoString id;
+
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
+ nsAtomString attrstr(aAttribute);
+
+ nsAutoString valuestr;
+ aElement->GetAttr(kNameSpaceID_None, aAttribute, valuestr);
+
+ nsAutoCString utf8uri;
+ nsresult rv = mDocument->GetDocumentURI()->GetSpec(utf8uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ NS_ConvertUTF8toUTF16 uri(utf8uri);
+
+ bool hasAttr;
+#ifdef MOZ_NEW_XULSTORE
+ rv = XULStore::HasValue(uri, id, attrstr, hasAttr);
+#else
+ rv = mLocalStore->HasValue(uri, id, attrstr, &hasAttr);
+#endif
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ if (hasAttr && valuestr.IsEmpty()) {
+#ifdef MOZ_NEW_XULSTORE
+ rv = XULStore::RemoveValue(uri, id, attrstr);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "value removed");
+#else
+ mLocalStore->RemoveValue(uri, id, attrstr);
+#endif
+ return;
+ }
+
+ // Persisting attributes to top level windows is handled by AppWindow.
+ if (IsRootElement(aElement)) {
+ if (nsCOMPtr<nsIAppWindow> win =
+ mDocument->GetAppWindowIfToplevelChrome()) {
+ return;
+ }
+ }
+
+#ifdef MOZ_NEW_XULSTORE
+ rv = XULStore::SetValue(uri, id, attrstr, valuestr);
+#else
+ mLocalStore->SetValue(uri, id, attrstr, valuestr);
+#endif
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "value set");
+}
+
+nsresult XULPersist::ApplyPersistentAttributes() {
+ if (!mDocument) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ // For non-chrome documents, persistance is simply broken
+ if (!mDocument->NodePrincipal()->IsSystemPrincipal()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Add all of the 'persisted' attributes into the content
+ // model.
+#ifndef MOZ_NEW_XULSTORE
+ if (!mLocalStore) {
+ mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
+ if (NS_WARN_IF(!mLocalStore)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ }
+#endif
+
+ ApplyPersistentAttributesInternal();
+
+ return NS_OK;
+}
+
+nsresult XULPersist::ApplyPersistentAttributesInternal() {
+ nsCOMArray<Element> elements;
+
+ nsAutoCString utf8uri;
+ nsresult rv = mDocument->GetDocumentURI()->GetSpec(utf8uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ NS_ConvertUTF8toUTF16 uri(utf8uri);
+
+ // Get a list of element IDs for which persisted values are available
+#ifdef MOZ_NEW_XULSTORE
+ UniquePtr<XULStoreIterator> ids;
+ rv = XULStore::GetIDs(uri, ids);
+#else
+ nsCOMPtr<nsIStringEnumerator> ids;
+ rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids));
+#endif
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#ifdef MOZ_NEW_XULSTORE
+ while (ids->HasMore()) {
+ nsAutoString id;
+ rv = ids->GetNext(&id);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+#else
+ while (1) {
+ bool hasmore = false;
+ ids->HasMore(&hasmore);
+ if (!hasmore) {
+ break;
+ }
+
+ nsAutoString id;
+ ids->GetNext(id);
+#endif
+
+ // We want to hold strong refs to the elements while applying
+ // persistent attributes, just in case.
+ const nsTArray<Element*>* allElements = mDocument->GetAllElementsForId(id);
+ if (!allElements) {
+ continue;
+ }
+ elements.Clear();
+ elements.SetCapacity(allElements->Length());
+ for (Element* element : *allElements) {
+ elements.AppendObject(element);
+ }
+
+ rv = ApplyPersistentAttributesToElements(id, elements);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult XULPersist::ApplyPersistentAttributesToElements(
+ const nsAString& aID, nsCOMArray<Element>& aElements) {
+ nsAutoCString utf8uri;
+ nsresult rv = mDocument->GetDocumentURI()->GetSpec(utf8uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ NS_ConvertUTF8toUTF16 uri(utf8uri);
+
+ // Get a list of attributes for which persisted values are available
+#ifdef MOZ_NEW_XULSTORE
+ UniquePtr<XULStoreIterator> attrs;
+ rv = XULStore::GetAttrs(uri, aID, attrs);
+#else
+ nsCOMPtr<nsIStringEnumerator> attrs;
+ rv = mLocalStore->GetAttributeEnumerator(uri, aID, getter_AddRefs(attrs));
+#endif
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#ifdef MOZ_NEW_XULSTORE
+ while (attrs->HasMore()) {
+ nsAutoString attrstr;
+ rv = attrs->GetNext(&attrstr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoString value;
+ rv = XULStore::GetValue(uri, aID, attrstr, value);
+#else
+ while (1) {
+ bool hasmore = PR_FALSE;
+ attrs->HasMore(&hasmore);
+ if (!hasmore) {
+ break;
+ }
+
+ nsAutoString attrstr;
+ attrs->GetNext(attrstr);
+
+ nsAutoString value;
+ rv = mLocalStore->GetValue(uri, aID, attrstr, value);
+#endif
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<nsAtom> attr = NS_Atomize(attrstr);
+ if (NS_WARN_IF(!attr)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t cnt = aElements.Length();
+ for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
+ Element* element = aElements.SafeElementAt(i);
+ if (!element) {
+ continue;
+ }
+
+ // Applying persistent attributes to top level windows is handled
+ // by AppWindow.
+ if (IsRootElement(element)) {
+ if (nsCOMPtr<nsIAppWindow> win =
+ mDocument->GetAppWindowIfToplevelChrome()) {
+ continue;
+ }
+ }
+
+ Unused << element->SetAttr(kNameSpaceID_None, attr, value, true);
+ }
+ }
+
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/xul/XULPersist.h b/dom/xul/XULPersist.h
new file mode 100644
index 0000000000..d6cd20eae3
--- /dev/null
+++ b/dom/xul/XULPersist.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_XULPersist_h
+#define mozilla_dom_XULPersist_h
+
+#include "nsStubDocumentObserver.h"
+
+#ifndef MOZ_NEW_XULSTORE
+class nsIXULStore;
+#endif
+
+template <typename T>
+class nsCOMArray;
+
+namespace mozilla {
+namespace dom {
+
+class XULPersist final : public nsStubDocumentObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit XULPersist(Document* aDocument);
+ void Init();
+ void DropDocumentReference();
+
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+
+ protected:
+ void Persist(mozilla::dom::Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute);
+
+ private:
+ ~XULPersist();
+ nsresult ApplyPersistentAttributes();
+ nsresult ApplyPersistentAttributesInternal();
+ nsresult ApplyPersistentAttributesToElements(const nsAString& aID,
+ nsCOMArray<Element>& aElements);
+
+#ifndef MOZ_NEW_XULSTORE
+ nsCOMPtr<nsIXULStore> mLocalStore;
+#endif
+
+ // A weak pointer to our document. Nulled out by DropDocumentReference.
+ Document* MOZ_NON_OWNING_REF mDocument;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_XULPersist_h
diff --git a/dom/xul/XULPopupElement.cpp b/dom/xul/XULPopupElement.cpp
new file mode 100644
index 0000000000..42ddd23fc8
--- /dev/null
+++ b/dom/xul/XULPopupElement.cpp
@@ -0,0 +1,243 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsIContent.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsMenuPopupFrame.h"
+#include "nsView.h"
+#include "mozilla/AppUnits.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/XULPopupElement.h"
+#include "mozilla/dom/XULPopupElementBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+nsXULElement* NS_NewXULPopupElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) {
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo);
+ auto* nim = nodeInfo->NodeInfoManager();
+ return new (nim) XULPopupElement(nodeInfo.forget());
+}
+
+JSObject* XULPopupElement::WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return XULPopupElement_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void XULPopupElement::OpenPopup(Element* aAnchorElement,
+ const StringOrOpenPopupOptions& aOptions,
+ int32_t aXPos, int32_t aYPos,
+ bool aIsContextMenu, bool aAttributesOverride,
+ Event* aTriggerEvent) {
+ nsAutoString position;
+ if (aOptions.IsOpenPopupOptions()) {
+ const OpenPopupOptions& options = aOptions.GetAsOpenPopupOptions();
+ position = options.mPosition;
+ aXPos = options.mX;
+ aYPos = options.mY;
+ aIsContextMenu = options.mIsContextMenu;
+ aAttributesOverride = options.mAttributesOverride;
+ aTriggerEvent = options.mTriggerEvent;
+ } else {
+ position = aOptions.GetAsString();
+ }
+
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ // As a special case for popups that are menus when no anchor or position
+ // are specified, open the popup with ShowMenu instead of ShowPopup so that
+ // the popup is aligned with the menu.
+ if (!aAnchorElement && position.IsEmpty() && GetPrimaryFrame()) {
+ nsMenuFrame* menu = do_QueryFrame(GetPrimaryFrame()->GetParent());
+ if (menu) {
+ pm->ShowMenu(menu->GetContent(), false, false);
+ return;
+ }
+ }
+
+ pm->ShowPopup(this, aAnchorElement, position, aXPos, aYPos, aIsContextMenu,
+ aAttributesOverride, false, aTriggerEvent);
+ }
+}
+
+void XULPopupElement::OpenPopupAtScreen(int32_t aXPos, int32_t aYPos,
+ bool aIsContextMenu,
+ Event* aTriggerEvent) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ pm->ShowPopupAtScreen(this, aXPos, aYPos, aIsContextMenu, aTriggerEvent);
+ }
+}
+
+void XULPopupElement::OpenPopupAtScreenRect(const nsAString& aPosition,
+ int32_t aXPos, int32_t aYPos,
+ int32_t aWidth, int32_t aHeight,
+ bool aIsContextMenu,
+ bool aAttributesOverride,
+ Event* aTriggerEvent) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ pm->ShowPopupAtScreenRect(
+ this, aPosition, nsIntRect(aXPos, aYPos, aWidth, aHeight),
+ aIsContextMenu, aAttributesOverride, aTriggerEvent);
+ }
+}
+
+void XULPopupElement::HidePopup(bool aCancel) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ pm->HidePopup(this, false, true, false, aCancel);
+ }
+}
+
+void XULPopupElement::MoveTo(int32_t aLeft, int32_t aTop) {
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetPrimaryFrame());
+ if (menuPopupFrame) {
+ menuPopupFrame->MoveTo(CSSIntPoint(aLeft, aTop), true);
+ }
+}
+
+void XULPopupElement::MoveToAnchor(Element* aAnchorElement,
+ const nsAString& aPosition, int32_t aXPos,
+ int32_t aYPos, bool aAttributesOverride) {
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetPrimaryFrame());
+ if (menuPopupFrame && menuPopupFrame->IsVisible()) {
+ menuPopupFrame->MoveToAnchor(aAnchorElement, aPosition, aXPos, aYPos,
+ aAttributesOverride);
+ }
+}
+
+void XULPopupElement::SizeTo(int32_t aWidth, int32_t aHeight) {
+ nsAutoString width, height;
+ width.AppendInt(aWidth);
+ height.AppendInt(aHeight);
+
+ nsCOMPtr<nsIContent> kungFuDeathGrip = this; // keep a reference
+
+ // We only want to pass aNotify=true to SetAttr once, but must make sure
+ // we pass it when a value is being changed. Thus, we check if the height
+ // is the same and if so, pass true when setting the width.
+ bool heightSame =
+ AttrValueIs(kNameSpaceID_None, nsGkAtoms::height, height, eCaseMatters);
+
+ SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, heightSame);
+ SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true);
+
+ // If the popup is open, force a reposition of the popup after resizing it
+ // with notifications set to true so that the popuppositioned event is fired.
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetPrimaryFrame());
+ if (menuPopupFrame && menuPopupFrame->PopupState() == ePopupShown) {
+ menuPopupFrame->SetPopupPosition(nullptr, false, false);
+ }
+}
+
+bool XULPopupElement::AutoPosition() {
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetPrimaryFrame());
+ if (menuPopupFrame) {
+ return menuPopupFrame->GetAutoPosition();
+ }
+ return true;
+}
+
+void XULPopupElement::SetAutoPosition(bool aShouldAutoPosition) {
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetPrimaryFrame());
+ if (menuPopupFrame) {
+ menuPopupFrame->SetAutoPosition(aShouldAutoPosition);
+ }
+}
+
+void XULPopupElement::GetState(nsString& aState) {
+ // set this here in case there's no frame for the popup
+ aState.AssignLiteral("closed");
+
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetPrimaryFrame());
+ if (menuPopupFrame) {
+ switch (menuPopupFrame->PopupState()) {
+ case ePopupShown:
+ aState.AssignLiteral("open");
+ break;
+ case ePopupShowing:
+ case ePopupPositioning:
+ case ePopupOpening:
+ case ePopupVisible:
+ aState.AssignLiteral("showing");
+ break;
+ case ePopupHiding:
+ case ePopupInvisible:
+ aState.AssignLiteral("hiding");
+ break;
+ case ePopupClosed:
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Bad popup state");
+ break;
+ }
+ }
+}
+
+nsINode* XULPopupElement::GetTriggerNode() const {
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetPrimaryFrame());
+ return nsMenuPopupFrame::GetTriggerContent(menuPopupFrame);
+}
+
+bool XULPopupElement::IsAnchored() const {
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetPrimaryFrame());
+ if (!menuPopupFrame) {
+ return false;
+ }
+
+ return menuPopupFrame->IsAnchored();
+}
+
+// FIXME(emilio): should probably be renamed to GetAnchorElement?
+Element* XULPopupElement::GetAnchorNode() const {
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetPrimaryFrame());
+ if (!menuPopupFrame) {
+ return nullptr;
+ }
+
+ return Element::FromNodeOrNull(menuPopupFrame->GetAnchor());
+}
+
+already_AddRefed<DOMRect> XULPopupElement::GetOuterScreenRect() {
+ RefPtr<DOMRect> rect = new DOMRect(ToSupports(this));
+
+ // Return an empty rectangle if the popup is not open.
+ nsMenuPopupFrame* menuPopupFrame =
+ do_QueryFrame(GetPrimaryFrame(FlushType::Frames));
+ if (!menuPopupFrame || !menuPopupFrame->IsOpen()) {
+ return rect.forget();
+ }
+
+ nsView* view = menuPopupFrame->GetView();
+ if (view) {
+ nsIWidget* widget = view->GetWidget();
+ if (widget) {
+ LayoutDeviceIntRect screenRect = widget->GetScreenBounds();
+
+ int32_t pp = menuPopupFrame->PresContext()->AppUnitsPerDevPixel();
+ rect->SetLayoutRect(LayoutDeviceIntRect::ToAppUnits(screenRect, pp));
+ }
+ }
+ return rect.forget();
+}
+
+void XULPopupElement::SetConstraintRect(dom::DOMRectReadOnly& aRect) {
+ nsMenuPopupFrame* menuPopupFrame =
+ do_QueryFrame(GetPrimaryFrame(FlushType::Frames));
+ if (menuPopupFrame) {
+ menuPopupFrame->SetOverrideConstraintRect(LayoutDeviceIntRect::Truncate(
+ aRect.Left(), aRect.Top(), aRect.Width(), aRect.Height()));
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/xul/XULPopupElement.h b/dom/xul/XULPopupElement.h
new file mode 100644
index 0000000000..825a69be40
--- /dev/null
+++ b/dom/xul/XULPopupElement.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef XULPopupElement_h__
+#define XULPopupElement_h__
+
+#include "mozilla/Attributes.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsString.h"
+#include "nsXULElement.h"
+
+struct JSContext;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class DOMRect;
+class Element;
+class Event;
+class StringOrOpenPopupOptions;
+
+nsXULElement* NS_NewXULPopupElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+class XULPopupElement : public nsXULElement {
+ private:
+ nsIFrame* GetFrame(bool aFlushLayout);
+
+ public:
+ explicit XULPopupElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsXULElement(std::move(aNodeInfo)) {}
+
+ void GetLabel(DOMString& aValue) const {
+ GetXULAttr(nsGkAtoms::label, aValue);
+ }
+ void SetLabel(const nsAString& aValue, ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::label, aValue, rv);
+ }
+
+ void GetPosition(DOMString& aValue) const {
+ GetXULAttr(nsGkAtoms::position, aValue);
+ }
+ void SetPosition(const nsAString& aValue, ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::position, aValue, rv);
+ }
+
+ bool AutoPosition();
+
+ void SetAutoPosition(bool aShouldAutoPosition);
+
+ void OpenPopup(Element* aAnchorElement,
+ const StringOrOpenPopupOptions& aOptions, int32_t aXPos,
+ int32_t aYPos, bool aIsContextMenu, bool aAttributesOverride,
+ Event* aTriggerEvent);
+
+ void OpenPopupAtScreen(int32_t aXPos, int32_t aYPos, bool aIsContextMenu,
+ Event* aTriggerEvent);
+
+ void OpenPopupAtScreenRect(const nsAString& aPosition, int32_t aXPos,
+ int32_t aYPos, int32_t aWidth, int32_t aHeight,
+ bool aIsContextMenu, bool aAttributesOverride,
+ Event* aTriggerEvent);
+
+ void HidePopup(bool aCancel);
+
+ void GetState(nsString& aState);
+
+ nsINode* GetTriggerNode() const;
+
+ bool IsAnchored() const;
+
+ Element* GetAnchorNode() const;
+
+ already_AddRefed<DOMRect> GetOuterScreenRect();
+
+ void MoveTo(int32_t aLeft, int32_t aTop);
+
+ void MoveToAnchor(Element* aAnchorElement, const nsAString& aPosition,
+ int32_t aXPos, int32_t aYPos, bool aAttributesOverride);
+
+ void SizeTo(int32_t aWidth, int32_t aHeight);
+
+ void SetConstraintRect(DOMRectReadOnly& aRect);
+
+ protected:
+ virtual ~XULPopupElement() = default;
+
+ JSObject* WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // XULPopupElement_h
diff --git a/dom/xul/XULTextElement.cpp b/dom/xul/XULTextElement.cpp
new file mode 100644
index 0000000000..1b6561c72b
--- /dev/null
+++ b/dom/xul/XULTextElement.cpp
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/Element.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+#include "nsPresContext.h"
+#include "nsIScrollableFrame.h"
+#include "mozilla/dom/XULTextElement.h"
+#include "mozilla/dom/XULTextElementBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+JSObject* XULTextElement::WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return XULTextElement_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/xul/XULTextElement.h b/dom/xul/XULTextElement.h
new file mode 100644
index 0000000000..d445e187c7
--- /dev/null
+++ b/dom/xul/XULTextElement.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef XULTextElement_h__
+#define XULTextElement_h__
+
+#include "nsXULElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class XULTextElement final : public nsXULElement {
+ public:
+ explicit XULTextElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsXULElement(std::move(aNodeInfo)) {}
+
+ bool Disabled() { return GetXULBoolAttr(nsGkAtoms::disabled); }
+ MOZ_CAN_RUN_SCRIPT void SetDisabled(bool aValue) {
+ SetXULBoolAttr(nsGkAtoms::disabled, aValue);
+ }
+ void GetValue(DOMString& aValue) const {
+ GetXULAttr(nsGkAtoms::value, aValue);
+ }
+ MOZ_CAN_RUN_SCRIPT void SetValue(const nsAString& aValue) {
+ SetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue, true);
+ }
+ void GetAccessKey(DOMString& aValue) const {
+ GetXULAttr(nsGkAtoms::accesskey, aValue);
+ }
+ MOZ_CAN_RUN_SCRIPT void SetAccessKey(const nsAString& aValue) {
+ SetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, aValue, true);
+ }
+
+ protected:
+ virtual ~XULTextElement() = default;
+ JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // XULTextElement_h
diff --git a/dom/xul/XULTooltipElement.cpp b/dom/xul/XULTooltipElement.cpp
new file mode 100644
index 0000000000..dc5df4b3a3
--- /dev/null
+++ b/dom/xul/XULTooltipElement.cpp
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/Event.h"
+#include "mozilla/dom/XULTooltipElement.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/EventDispatcher.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentUtils.h"
+#include "nsCTooltipTextProvider.h"
+#include "nsITooltipTextProvider.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+nsXULElement* NS_NewXULTooltipElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) {
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo);
+ auto* nim = nodeInfo->NodeInfoManager();
+ RefPtr<XULTooltipElement> tooltip =
+ new (nim) XULTooltipElement(nodeInfo.forget());
+ NS_ENSURE_SUCCESS(tooltip->Init(), nullptr);
+ return tooltip;
+}
+
+nsresult XULTooltipElement::Init() {
+ // Create the default child label node that will contain the text of the
+ // tooltip.
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = mNodeInfo->NodeInfoManager()->GetNodeInfo(
+ nsGkAtoms::description, nullptr, kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
+ nsCOMPtr<Element> description;
+ nsresult rv = NS_NewXULElement(getter_AddRefs(description), nodeInfo.forget(),
+ dom::NOT_FROM_PARSER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ description->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
+ u"tooltip-label"_ns, false);
+ description->SetAttr(kNameSpaceID_None, nsGkAtoms::flex, u"true"_ns, false);
+ ErrorResult error;
+ AppendChild(*description, error);
+
+ return error.StealNSResult();
+}
+
+nsresult XULTooltipElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
+ const nsAttrValue* aValue,
+ const nsAttrValue* aOldValue,
+ nsIPrincipal* aSubjectPrincipal,
+ bool aNotify) {
+ if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::label) {
+ // When the label attribute of this node changes propagate the text down
+ // into child description element.
+ nsCOMPtr<nsIContent> description = GetFirstChild();
+ if (description && description->IsXULElement(nsGkAtoms::description)) {
+ nsAutoString value;
+ if (aValue) {
+ aValue->ToString(value);
+ }
+ nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
+ "XULTooltipElement::AfterSetAttr", [description, value]() {
+ Element* descriptionElement = description->AsElement();
+ descriptionElement->SetTextContent(value, IgnoreErrors());
+ }));
+ }
+ }
+ return nsXULElement::AfterSetAttr(aNameSpaceID, aName, aValue, aOldValue,
+ aSubjectPrincipal, aNotify);
+}
+
+nsresult XULTooltipElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
+ if (aVisitor.mEvent->mMessage == eXULPopupShowing &&
+ aVisitor.mEvent->IsTrusted() && !aVisitor.mEvent->DefaultPrevented() &&
+ AttrValueIs(kNameSpaceID_None, nsGkAtoms::page, nsGkAtoms::_true,
+ eCaseMatters)) {
+ // When the tooltip node has the "page" attribute set to "true" the
+ // tooltip text provider is used to find the tooltip text from page where
+ // mouse is hovering over.
+ nsCOMPtr<nsITooltipTextProvider> textProvider =
+ do_GetService(NS_DEFAULTTOOLTIPTEXTPROVIDER_CONTRACTID);
+ nsString text;
+ nsString direction;
+ bool shouldChange = false;
+ if (textProvider) {
+ textProvider->GetNodeText(GetTriggerNode(), getter_Copies(text),
+ getter_Copies(direction), &shouldChange);
+ }
+ if (shouldChange) {
+ SetAttr(kNameSpaceID_None, nsGkAtoms::label, text, true);
+ SetAttr(kNameSpaceID_None, nsGkAtoms::direction, direction, true);
+ } else {
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ aVisitor.mEvent->PreventDefault();
+ }
+ }
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/xul/XULTooltipElement.h b/dom/xul/XULTooltipElement.h
new file mode 100644
index 0000000000..7dffebffe0
--- /dev/null
+++ b/dom/xul/XULTooltipElement.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef XULTooltipElement_h__
+#define XULTooltipElement_h__
+
+#include "XULPopupElement.h"
+
+namespace mozilla {
+namespace dom {
+
+nsXULElement* NS_NewXULTooltipElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+class XULTooltipElement final : public XULPopupElement {
+ public:
+ explicit XULTooltipElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : XULPopupElement(std::move(aNodeInfo)) {}
+ nsresult Init();
+
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
+ const nsAttrValue* aValue,
+ const nsAttrValue* aOldValue,
+ nsIPrincipal* aSubjectPrincipal,
+ bool aNotify) override;
+ virtual nsresult PostHandleEvent(EventChainPostVisitor& aVisitor) override;
+
+ protected:
+ virtual ~XULTooltipElement() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // XULPopupElement_h
diff --git a/dom/xul/XULTreeElement.cpp b/dom/xul/XULTreeElement.cpp
new file mode 100644
index 0000000000..7e86190d6d
--- /dev/null
+++ b/dom/xul/XULTreeElement.cpp
@@ -0,0 +1,413 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsTreeContentView.h"
+#include "nsITreeSelection.h"
+#include "ChildIterator.h"
+#include "nsError.h"
+#include "nsTreeBodyFrame.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/XULTreeElement.h"
+#include "mozilla/dom/XULTreeElementBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(XULTreeElement, nsXULElement)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTreeElement, nsXULElement, mView)
+
+JSObject* XULTreeElement::WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return XULTreeElement_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void XULTreeElement::UnbindFromTree(bool aNullParent) {
+ // Drop the view's ref to us.
+ if (mView) {
+ nsCOMPtr<nsITreeSelection> sel;
+ mView->GetSelection(getter_AddRefs(sel));
+ if (sel) {
+ sel->SetTree(nullptr);
+ }
+ mView->SetTree(nullptr); // Break the circular ref between the view and us.
+ }
+ mView = nullptr;
+
+ nsXULElement::UnbindFromTree(aNullParent);
+}
+
+void XULTreeElement::DestroyContent() {
+ // Drop the view's ref to us.
+ if (mView) {
+ nsCOMPtr<nsITreeSelection> sel;
+ mView->GetSelection(getter_AddRefs(sel));
+ if (sel) {
+ sel->SetTree(nullptr);
+ }
+ mView->SetTree(nullptr); // Break the circular ref between the view and us.
+ }
+ mView = nullptr;
+
+ nsXULElement::DestroyContent();
+}
+
+static nsIContent* FindBodyElement(nsIContent* aParent) {
+ mozilla::dom::FlattenedChildIterator iter(aParent);
+ for (nsIContent* content = iter.GetNextChild(); content;
+ content = iter.GetNextChild()) {
+ mozilla::dom::NodeInfo* ni = content->NodeInfo();
+ if (ni->Equals(nsGkAtoms::treechildren, kNameSpaceID_XUL)) {
+ return content;
+ } else if (ni->Equals(nsGkAtoms::tree, kNameSpaceID_XUL)) {
+ // There are nesting tree elements. Only the innermost should
+ // find the treechilren.
+ return nullptr;
+ } else if (content->IsElement() &&
+ !ni->Equals(nsGkAtoms::_template, kNameSpaceID_XUL)) {
+ nsIContent* result = FindBodyElement(content);
+ if (result) return result;
+ }
+ }
+
+ return nullptr;
+}
+
+nsTreeBodyFrame* XULTreeElement::GetTreeBodyFrame(FlushType aFlushType) {
+ MOZ_ASSERT(aFlushType == FlushType::Frames ||
+ aFlushType == FlushType::Layout || aFlushType == FlushType::None);
+ nsCOMPtr<nsIContent> kungFuDeathGrip = this; // keep a reference
+
+ // Make sure our frames are up to date, and layout as needed. We
+ // have to do this before checking for our cached mTreeBody, since
+ // it might go away on style flush, and in any case if aFlushLayout
+ // is true we need to make sure to flush no matter what.
+ if (aFlushType != FlushType::None) {
+ if (RefPtr<Document> doc = GetComposedDoc()) {
+ doc->FlushPendingNotifications(aFlushType);
+ }
+ }
+
+ if (mTreeBody) {
+ // Have one cached already.
+ return mTreeBody;
+ }
+
+ if (nsCOMPtr<nsIContent> tree = FindBodyElement(this)) {
+ mTreeBody = do_QueryFrame(tree->GetPrimaryFrame());
+ }
+
+ return mTreeBody;
+}
+
+already_AddRefed<nsITreeView> XULTreeElement::GetView(FlushType aFlushType) {
+ if (!mTreeBody) {
+ if (!GetTreeBodyFrame(aFlushType)) {
+ return nullptr;
+ }
+
+ if (mView) {
+ nsCOMPtr<nsITreeView> view;
+ // Our new frame needs to initialise itself
+ mTreeBody->GetView(getter_AddRefs(view));
+ return view.forget();
+ }
+ }
+ if (!mView) {
+ // No tree builder, create a tree content view.
+ if (NS_FAILED(NS_NewTreeContentView(getter_AddRefs(mView)))) {
+ return nullptr;
+ }
+
+ // Initialise the frame and view
+ mTreeBody->SetView(mView);
+ }
+
+ return do_AddRef(mView);
+}
+
+void XULTreeElement::SetView(nsITreeView* aView, CallerType aCallerType,
+ ErrorResult& aRv) {
+ if (aCallerType != CallerType::System) {
+ // Don't trust views coming from random places.
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ mView = aView;
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ body->SetView(aView);
+ }
+}
+
+bool XULTreeElement::Focused() {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ return body->GetFocused();
+ }
+ return false;
+}
+
+void XULTreeElement::SetFocused(bool aFocused) {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ body->SetFocused(aFocused);
+ }
+}
+
+already_AddRefed<Element> XULTreeElement::GetTreeBody() {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ nsCOMPtr<Element> element;
+ body->GetTreeBody(getter_AddRefs(element));
+ return element.forget();
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<nsTreeColumns> XULTreeElement::GetColumns(
+ FlushType aFlushType) {
+ if (nsTreeBodyFrame* body = GetTreeBodyFrame(aFlushType)) {
+ return body->Columns();
+ }
+ return nullptr;
+}
+
+int32_t XULTreeElement::RowHeight() {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ return body->RowHeight();
+ }
+ return 0;
+}
+
+int32_t XULTreeElement::RowWidth() {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ return body->RowWidth();
+ }
+ return 0;
+}
+
+int32_t XULTreeElement::GetFirstVisibleRow() {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ return body->FirstVisibleRow();
+ }
+ return 0;
+}
+
+int32_t XULTreeElement::GetLastVisibleRow() {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ return body->LastVisibleRow();
+ }
+ return 0;
+}
+
+int32_t XULTreeElement::HorizontalPosition() {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ return body->GetHorizontalPosition();
+ }
+ return 0;
+}
+
+int32_t XULTreeElement::GetPageLength() {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ return body->PageLength();
+ }
+ return 0;
+}
+
+void XULTreeElement::EnsureRowIsVisible(int32_t aRow) {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ body->EnsureRowIsVisible(aRow);
+ }
+}
+
+void XULTreeElement::EnsureCellIsVisible(int32_t aRow, nsTreeColumn* aCol,
+ ErrorResult& aRv) {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ nsresult rv = body->EnsureCellIsVisible(aRow, aCol);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+ }
+}
+
+void XULTreeElement::ScrollToRow(int32_t aRow) {
+ nsTreeBodyFrame* body = GetTreeBodyFrame(FlushType::Layout);
+ if (!body) {
+ return;
+ }
+
+ body->ScrollToRow(aRow);
+}
+
+void XULTreeElement::ScrollByLines(int32_t aNumLines) {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (!body) {
+ return;
+ }
+ body->ScrollByLines(aNumLines);
+}
+
+void XULTreeElement::ScrollByPages(int32_t aNumPages) {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ body->ScrollByPages(aNumPages);
+ }
+}
+
+void XULTreeElement::Invalidate() {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ body->Invalidate();
+ }
+}
+
+void XULTreeElement::InvalidateColumn(nsTreeColumn* aCol) {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ body->InvalidateColumn(aCol);
+ }
+}
+
+void XULTreeElement::InvalidateRow(int32_t aIndex) {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ body->InvalidateRow(aIndex);
+ }
+}
+
+void XULTreeElement::InvalidateCell(int32_t aRow, nsTreeColumn* aCol) {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ body->InvalidateCell(aRow, aCol);
+ }
+}
+
+void XULTreeElement::InvalidateRange(int32_t aStart, int32_t aEnd) {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ body->InvalidateRange(aStart, aEnd);
+ }
+}
+
+int32_t XULTreeElement::GetRowAt(int32_t x, int32_t y) {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (!body) {
+ return 0;
+ }
+ return body->GetRowAt(x, y);
+}
+
+void XULTreeElement::GetCellAt(int32_t aX, int32_t aY, TreeCellInfo& aRetVal,
+ ErrorResult& aRv) {
+ aRetVal.mRow = 0;
+ aRetVal.mCol = nullptr;
+
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ nsAutoCString element;
+ body->GetCellAt(aX, aY, &aRetVal.mRow, getter_AddRefs(aRetVal.mCol),
+ element);
+ CopyUTF8toUTF16(element, aRetVal.mChildElt);
+ }
+}
+
+nsIntRect XULTreeElement::GetCoordsForCellItem(int32_t aRow, nsTreeColumn* aCol,
+ const nsAString& aElement,
+ nsresult& rv) {
+ rv = NS_OK;
+ nsIntRect rect;
+
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ NS_ConvertUTF16toUTF8 element(aElement);
+ if (body) {
+ rv = body->GetCoordsForCellItem(aRow, aCol, element, &rect.x, &rect.y,
+ &rect.width, &rect.height);
+ }
+
+ return rect;
+}
+
+already_AddRefed<DOMRect> XULTreeElement::GetCoordsForCellItem(
+ int32_t aRow, nsTreeColumn& aCol, const nsAString& aElement,
+ ErrorResult& aRv) {
+ nsresult rv;
+ nsIntRect rect = GetCoordsForCellItem(aRow, &aCol, aElement, rv);
+ aRv = rv;
+
+ RefPtr<DOMRect> domRect =
+ new DOMRect(this, rect.x, rect.y, rect.width, rect.height);
+ return domRect.forget();
+}
+
+bool XULTreeElement::IsCellCropped(int32_t aRow, nsTreeColumn* aCol,
+ ErrorResult& aRv) {
+ bool cropped = false;
+
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ aRv = body->IsCellCropped(aRow, aCol, &cropped);
+ }
+
+ return cropped;
+}
+
+void XULTreeElement::RowCountChanged(int32_t aIndex, int32_t aDelta) {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ body->RowCountChanged(aIndex, aDelta);
+ }
+}
+
+void XULTreeElement::BeginUpdateBatch() {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ body->BeginUpdateBatch();
+ }
+}
+
+void XULTreeElement::EndUpdateBatch() {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ body->EndUpdateBatch();
+ }
+}
+
+void XULTreeElement::ClearStyleAndImageCaches() {
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ body->ClearStyleAndImageCaches();
+ }
+}
+
+void XULTreeElement::RemoveImageCacheEntry(int32_t aRowIndex,
+ nsTreeColumn& aCol,
+ ErrorResult& aRv) {
+ if (NS_WARN_IF(aRowIndex < 0)) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+ nsTreeBodyFrame* body = GetTreeBodyFrame();
+ if (body) {
+ body->RemoveImageCacheEntry(aRowIndex, &aCol);
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/xul/XULTreeElement.h b/dom/xul/XULTreeElement.h
new file mode 100644
index 0000000000..381ae88f41
--- /dev/null
+++ b/dom/xul/XULTreeElement.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef XULTreeElement_h__
+#define XULTreeElement_h__
+
+#include "mozilla/Attributes.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsString.h"
+#include "nsXULElement.h"
+#include "nsITreeView.h"
+
+class nsTreeBodyFrame;
+class nsTreeColumn;
+class nsTreeColumns;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+struct TreeCellInfo;
+class DOMRect;
+enum class CallerType : uint32_t;
+
+class XULTreeElement final : public nsXULElement {
+ public:
+ explicit XULTreeElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsXULElement(std::move(aNodeInfo)),
+ mCachedFirstVisibleRow(0),
+ mTreeBody(nullptr) {}
+
+ NS_IMPL_FROMNODE_WITH_TAG(XULTreeElement, kNameSpaceID_XUL, tree)
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULTreeElement, nsXULElement)
+
+ nsTreeBodyFrame* GetTreeBodyFrame(FlushType = FlushType::Frames);
+ nsTreeBodyFrame* GetCachedTreeBodyFrame() { return mTreeBody; }
+
+ already_AddRefed<nsTreeColumns> GetColumns(FlushType = FlushType::Frames);
+
+ already_AddRefed<nsITreeView> GetView(CallerType /* unused */) {
+ return GetView();
+ }
+ already_AddRefed<nsITreeView> GetView(FlushType = FlushType::Frames);
+
+ void SetView(nsITreeView* arg, CallerType aCallerType, ErrorResult& aRv);
+
+ bool Focused();
+
+ already_AddRefed<Element> GetTreeBody();
+
+ int32_t RowHeight();
+
+ int32_t RowWidth();
+
+ int32_t HorizontalPosition();
+
+ void EnsureCellIsVisible(int32_t row, nsTreeColumn* col, ErrorResult& aRv);
+
+ void ScrollToRow(int32_t aRow);
+
+ void ScrollByLines(int32_t aNumLines);
+
+ void ScrollByPages(int32_t aNumPages);
+
+ int32_t GetFirstVisibleRow();
+
+ int32_t GetLastVisibleRow();
+
+ int32_t GetPageLength();
+
+ int32_t GetRowAt(int32_t x, int32_t y);
+
+ void GetCellAt(int32_t x, int32_t y, TreeCellInfo& aRetVal, ErrorResult& aRv);
+
+ nsIntRect GetCoordsForCellItem(int32_t aRow, nsTreeColumn* aCol,
+ const nsAString& aElement, nsresult& rv);
+ already_AddRefed<DOMRect> GetCoordsForCellItem(int32_t row, nsTreeColumn& col,
+ const nsAString& element,
+ ErrorResult& aRv);
+
+ bool IsCellCropped(int32_t row, nsTreeColumn* col, ErrorResult& aRv);
+
+ void RemoveImageCacheEntry(int32_t row, nsTreeColumn& col, ErrorResult& aRv);
+
+ void SetFocused(bool aFocused);
+ void EnsureRowIsVisible(int32_t index);
+ void Invalidate(void);
+ void InvalidateColumn(nsTreeColumn* col);
+ void InvalidateRow(int32_t index);
+ void InvalidateCell(int32_t row, nsTreeColumn* col);
+ void InvalidateRange(int32_t startIndex, int32_t endIndex);
+ void RowCountChanged(int32_t index, int32_t count);
+ void BeginUpdateBatch(void);
+ void EndUpdateBatch(void);
+ void ClearStyleAndImageCaches(void);
+
+ virtual void UnbindFromTree(bool aNullParent) override;
+ virtual void DestroyContent() override;
+
+ void BodyDestroyed(int32_t aFirstVisibleRow) {
+ mTreeBody = nullptr;
+ mCachedFirstVisibleRow = aFirstVisibleRow;
+ }
+
+ int32_t GetCachedTopVisibleRow() { return mCachedFirstVisibleRow; }
+
+ protected:
+ int32_t mCachedFirstVisibleRow;
+
+ nsTreeBodyFrame* mTreeBody;
+ nsCOMPtr<nsITreeView> mView;
+
+ virtual ~XULTreeElement() = default;
+
+ JSObject* WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/xul/crashtests/107518-1.xml b/dom/xul/crashtests/107518-1.xml
new file mode 100644
index 0000000000..58e652624c
--- /dev/null
+++ b/dom/xul/crashtests/107518-1.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<!DOCTYPE window>
+
+<window
+ id = "xulnote-main-window"
+ xmlns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html = "http://www.w3.org/1999/xhtml"
+ onload = "init();"
+>
+ <scrollbox>
+ <vbox style="background-color:white;">
+ <label value="hahaha 0"/>
+ <label value="hahaha 1"/>
+ <label value="hahaha 2"/>
+ <label value="hahaha 3"/>
+ <label value="hahaha 4"/>
+ <label value="hahaha 5"/>
+ <label value="hahaha 6"/>
+ <label value="hahaha 7"/>
+ <label value="hahaha 8"/>
+ <label value="hahaha 9"/>
+ <label value="hahaha 10"/>
+ <label value="hahaha 11"/>
+ <label value="hahaha 12"/>
+ <label value="hahaha 13"/>
+ <label value="hahaha 14"/>
+ <label value="hahaha 15"/>
+ <label value="hahaha 16"/>
+ <label value="hahaha 17"/>
+ <label value="hahaha 18"/>
+ <label value="hahaha 19"/>
+ </vbox>
+<scrollbar
+ id="identifier"
+ align="horizontal"
+ curpos="20"
+ maxpos="100"
+ increment="1"
+ pageincrement="10"/>
+
+ </scrollbox>
+
+ <script type="application/x-javascript">
+ <![CDATA[
+ function init()
+ {
+ }
+ ]]>
+ </script>
+
+</window>
diff --git a/dom/xul/crashtests/253479-1.xhtml b/dom/xul/crashtests/253479-1.xhtml
new file mode 100644
index 0000000000..5860d2200d
--- /dev/null
+++ b/dom/xul/crashtests/253479-1.xhtml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+>
+<html:div style="position:fixed;"/>
+</window> \ No newline at end of file
diff --git a/dom/xul/crashtests/253479-2.xhtml b/dom/xul/crashtests/253479-2.xhtml
new file mode 100644
index 0000000000..43a8c17b84
--- /dev/null
+++ b/dom/xul/crashtests/253479-2.xhtml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <x style="position:fixed; display: block;"/>
+</window>
diff --git a/dom/xul/crashtests/326204-1.xhtml b/dom/xul/crashtests/326204-1.xhtml
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/xul/crashtests/326204-1.xhtml
diff --git a/dom/xul/crashtests/326644-1-inner.xhtml b/dom/xul/crashtests/326644-1-inner.xhtml
new file mode 100644
index 0000000000..63ed9b7212
--- /dev/null
+++ b/dom/xul/crashtests/326644-1-inner.xhtml
@@ -0,0 +1,34 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Testcase bug 326644 - Crash when changing enumerated properties of objects in xul">
+
+<html:script><![CDATA[
+var timers=0;
+function doe(aObj, aNested, aCurrentTimer){
+var temp =0;
+for (var i in aObj) {
+try {
+if (typeof aObj[i] == 'object') {
+ if (aNested >= 19 || aObj[i] == window.location)
+ continue;
+ setTimeout(doe,500, aObj[i], ++aNested, timers);
+ timers++;
+}
+}
+catch(e){}
+try {
+ //if (temp == 68 && aNested == 21 && aCurrentTimer >= 116) {
+ // alert(i + '-'+ aObj[i]);
+ // return;
+ // }
+ aObj[i]= i;
+ temp+=1;
+}
+catch (e) {
+
+}
+}
+}
+var s=document.getElementsByTagName('window')[0];
+setTimeout(doe,100, s, 0);
+]]></html:script>
+</window>
diff --git a/dom/xul/crashtests/326644-1.html b/dom/xul/crashtests/326644-1.html
new file mode 100644
index 0000000000..6f1d88a5eb
--- /dev/null
+++ b/dom/xul/crashtests/326644-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="326644-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/dom/xul/crashtests/326875-1.xhtml b/dom/xul/crashtests/326875-1.xhtml
new file mode 100644
index 0000000000..9dca9a0d53
--- /dev/null
+++ b/dom/xul/crashtests/326875-1.xhtml
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+
+
+<script>
+
+
+function init() {
+
+ var m = document.getElementById("m");
+ m.parentNode.removeChild(m);
+ m.controllers;
+};
+
+
+window.addEventListener("load", init, false);
+
+</script>
+
+
+
+<hbox id="m" />
+
+</window>
diff --git a/dom/xul/crashtests/329982-1.xhtml b/dom/xul/crashtests/329982-1.xhtml
new file mode 100644
index 0000000000..43f374a57e
--- /dev/null
+++ b/dom/xul/crashtests/329982-1.xhtml
@@ -0,0 +1,42 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script>
+
+function init()
+{
+ var A = document.getElementById("z");
+ var B = A.nextSibling;
+ var C = B.nextSibling;
+ var P = A.parentNode;
+
+ document.addEventListener("DOMNodeRemoved", fizzy, false);
+ P.removeChild(B);
+ document.removeEventListener("DOMNodeRemoved", fizzy, false);
+
+ function fizzy()
+ {
+ document.removeEventListener("DOMNodeRemoved", fizzy, false); // avoid recursion
+ P.removeChild(A);
+ }
+
+ document.documentElement.appendChild(C);
+}
+
+
+window.addEventListener("load", init, false);
+
+</script>
+
+</head>
+
+<body>
+
+<hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<menupopup y="x"> <menuitem id="z"/> <menuitem/> <menuitem/> </menupopup>
+
+</hbox>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/xul/crashtests/336096-1.xhtml b/dom/xul/crashtests/336096-1.xhtml
new file mode 100644
index 0000000000..e15691d88d
--- /dev/null
+++ b/dom/xul/crashtests/336096-1.xhtml
@@ -0,0 +1,41 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+<![CDATA[
+
+function init()
+{
+ var targetWindow = window.frames[0];
+ var targetDocument = targetWindow.document;
+
+ targetDocument.body.appendChild(document.getElementById('rootish'));
+ targetDocument.designMode = 'on';
+
+ var r = targetDocument.createRange();
+ r.setStart(targetDocument.getElementById("start"), 0);
+ r.setEnd (targetDocument.getElementById("end"), 0);
+ targetWindow.getSelection().addRange(r);
+
+ targetDocument.execCommand('bold', false, null);
+}
+
+]]>
+</script>
+</head>
+
+<body onload="setTimeout(init, 200);">
+
+<iframe src="data:text/html," style="width: 95%; height: 500px;"></iframe>
+
+<div id="rootish">
+ <div id="start"></div>
+ <hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <hbox/>
+ <vbox id="end">
+ <hbox/>
+ </vbox>
+ </hbox>
+</div>
+
+</body>
+</html>
diff --git a/dom/xul/crashtests/344215-1.xhtml b/dom/xul/crashtests/344215-1.xhtml
new file mode 100644
index 0000000000..6443c22d6c
--- /dev/null
+++ b/dom/xul/crashtests/344215-1.xhtml
@@ -0,0 +1,7 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<observes/>
+
+<description>You should not see any assertions in a debug build.</description>
+
+</window> \ No newline at end of file
diff --git a/dom/xul/crashtests/354611-1.html b/dom/xul/crashtests/354611-1.html
new file mode 100644
index 0000000000..fe25de3660
--- /dev/null
+++ b/dom/xul/crashtests/354611-1.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<script>
+
+var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+function boom()
+{
+ var z = document.createElementNS(XUL_NS, "window");
+ document.body.appendChild(z);
+ z.setAttribute("hidechrome", "true");
+}
+
+</script>
+
+<body onload="boom();">
+
+</body>
+
+</html> \ No newline at end of file
diff --git a/dom/xul/crashtests/360078-1.xhtml b/dom/xul/crashtests/360078-1.xhtml
new file mode 100644
index 0000000000..a29087014d
--- /dev/null
+++ b/dom/xul/crashtests/360078-1.xhtml
@@ -0,0 +1,42 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+
+<style>
+<![CDATA[
+#baz { -moz-binding: url(360078-1xbl.xml#foo); }
+]]>
+</style>
+
+<script>
+<![CDATA[
+
+function stuff()
+{
+ var baz = document.getElementById("baz");
+ var count = 0;
+
+ setTimeout(step, 30);
+
+ function step()
+ {
+ ++count;
+ if (count < 15) {
+ baz.cloneNode(true);
+ setTimeout(step, 30);
+ }
+ else {
+ document.documentElement.removeAttribute("class");
+ }
+ }
+
+}
+
+]]>
+</script>
+</head>
+<body onload="stuff()">
+
+<hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="baz"></hbox>
+
+</body>
+</html>
diff --git a/dom/xul/crashtests/360078-1xbl.xml b/dom/xul/crashtests/360078-1xbl.xml
new file mode 100644
index 0000000000..c0f428c056
--- /dev/null
+++ b/dom/xul/crashtests/360078-1xbl.xml
@@ -0,0 +1,3 @@
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo"><content>
+<a xmlns="http://www.w3.org/1999/xhtml" href="http://www.mozilla.org/" id="mlink">Foo<children xmlns="http://www.mozilla.org/xbl"/></a>
+</content></binding></bindings>
diff --git a/dom/xul/crashtests/363791-1.xhtml b/dom/xul/crashtests/363791-1.xhtml
new file mode 100644
index 0000000000..0786960f23
--- /dev/null
+++ b/dom/xul/crashtests/363791-1.xhtml
@@ -0,0 +1,44 @@
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="setTimeout(boom, 30);" class="reftest-wait">
+
+<script>
+
+function boom()
+{
+ var tree = document.getElementById("tree");
+ var treecols = document.getElementById("treecols");
+ var treechildren = document.getElementById("treechildren");
+
+ tree.appendChild(treechildren); // no real change
+
+ function boom2() {
+ treecols.parentNode.removeChild(treecols);
+ document.documentElement.removeAttribute("class");
+ }
+
+ setTimeout(boom2, 30);
+}
+
+</script>
+
+
+<tree rows="6" id="tree">
+
+ <treecols id="treecols">
+ <treecol id="firstname" label="First Name"/>
+ </treecols>
+
+ <treechildren id="treechildren">
+ <treeitem>
+ <treerow>
+ <treecell label="Bob"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+
+</tree>
+
+
+</window>
diff --git a/dom/xul/crashtests/384877-1-inner.xhtml b/dom/xul/crashtests/384877-1-inner.xhtml
new file mode 100644
index 0000000000..bd75aa87a7
--- /dev/null
+++ b/dom/xul/crashtests/384877-1-inner.xhtml
@@ -0,0 +1,12 @@
+<menupopup xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="d" popup="d">
+<script>
+function doe() {
+var d = document.getElementById('d');
+if (d.openPopup) {
+ d.openPopup(document.documentElement, 'before_start', 0, 0, false, false);
+// alert(d.state);
+}
+
+setTimeout(doe, 200);
+</script>
+</menupopup>
diff --git a/dom/xul/crashtests/384877-1.html b/dom/xul/crashtests/384877-1.html
new file mode 100644
index 0000000000..b4ecd1b5f8
--- /dev/null
+++ b/dom/xul/crashtests/384877-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="384877-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/dom/xul/crashtests/386914-1-inner.xhtml b/dom/xul/crashtests/386914-1-inner.xhtml
new file mode 100644
index 0000000000..909889d8b5
--- /dev/null
+++ b/dom/xul/crashtests/386914-1-inner.xhtml
@@ -0,0 +1,10 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="a">
+
+<box id="b" observes="a"/>
+
+<html:script xmlns:html="http://www.w3.org/1999/xhtml">
+document.getElementById('b').addEventListener('DOMAttrModified', function(e) {document.removeChild(document.documentElement);}, true);
+setTimeout(function() {document.getElementById('a').setAttribute('tabindex', '1') ;}, 100);
+</html:script>
+
+</window> \ No newline at end of file
diff --git a/dom/xul/crashtests/386914-1.html b/dom/xul/crashtests/386914-1.html
new file mode 100644
index 0000000000..2bb1224ab7
--- /dev/null
+++ b/dom/xul/crashtests/386914-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="386914-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/dom/xul/crashtests/425821-1.xhtml b/dom/xul/crashtests/425821-1.xhtml
new file mode 100644
index 0000000000..9764e64aad
--- /dev/null
+++ b/dom/xul/crashtests/425821-1.xhtml
@@ -0,0 +1,15 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();">
+<script type="text/javascript">
+
+function boom()
+{
+ var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var wiz = document.createElementNS(XUL_NS, "wizard");
+ var btn = document.createElementNS(XUL_NS, "hbox");
+ btn.setAttribute("anonid", "Buttons");
+ wiz.appendChild(btn);
+ wiz.cloneNode(true);
+}
+
+</script>
+</window>
diff --git a/dom/xul/crashtests/428951-1.xhtml b/dom/xul/crashtests/428951-1.xhtml
new file mode 100644
index 0000000000..41dd353f3d
--- /dev/null
+++ b/dom/xul/crashtests/428951-1.xhtml
@@ -0,0 +1,21 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:mathml="http://www.w3.org/1998/Math/MathML">
+<box>
+ <box style="background: initial;" id="f">
+ <box style="margin-top: -9999999px;"/>
+ </box>
+ <mathml:divergence>
+ <box/>
+ </mathml:divergence>
+ <mathml:moment command="f"/>
+</box>
+
+<script id="script" xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+function init() {
+ var f = document.getElementsByTagName('mathml:divergence')[0];
+ window.addEventListener('DOMAttrModified',function() { f.remove();}, true);
+ var x=document.getElementsByTagName('mathml:moment')[0];
+ x.remove();
+}
+window.addEventListener("load", init, false);
+]]></script>
+</window>
diff --git a/dom/xul/crashtests/431906-1-inner.xhtml b/dom/xul/crashtests/431906-1-inner.xhtml
new file mode 100644
index 0000000000..367f621b2f
--- /dev/null
+++ b/dom/xul/crashtests/431906-1-inner.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<colgroup id="a" command="a">
+<box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<box id="a" command="a"/>
+<box id="a" command="a"/>
+</box>
+</colgroup>
+<script><![CDATA[
+function doe(){
+document.getElementsByTagName('*')[1].setAttribute('id','');
+document.getElementsByTagName('*')[1].setAttribute('xmlns', '');
+document.getElementsByTagName('*')[3].setAttribute('id','');
+
+document.getElementsByTagName('*')[4].removeAttribute('xmlns');
+document.getElementsByTagName('*')[4].setAttribute('width', '1px');
+}
+setTimeout(doe,100);
+]]></script>
+</html> \ No newline at end of file
diff --git a/dom/xul/crashtests/431906-1.html b/dom/xul/crashtests/431906-1.html
new file mode 100644
index 0000000000..a8570b1bc0
--- /dev/null
+++ b/dom/xul/crashtests/431906-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="431906-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/dom/xul/crashtests/461917-1.xhtml b/dom/xul/crashtests/461917-1.xhtml
new file mode 100644
index 0000000000..15792f6f0f
--- /dev/null
+++ b/dom/xul/crashtests/461917-1.xhtml
@@ -0,0 +1,6 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head></head>
+<body>
+<tabs xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onselect="#"><wizard/></tabs>
+</body>
+</html>
diff --git a/dom/xul/crashtests/crashtests.list b/dom/xul/crashtests/crashtests.list
new file mode 100644
index 0000000000..c28f885848
--- /dev/null
+++ b/dom/xul/crashtests/crashtests.list
@@ -0,0 +1,17 @@
+load 107518-1.xml
+load chrome://reftest/content/crashtests/dom/xul/crashtests/253479-1.xhtml
+load chrome://reftest/content/crashtests/dom/xul/crashtests/253479-2.xhtml
+load chrome://reftest/content/crashtests/dom/xul/crashtests/326204-1.xhtml
+load 326644-1.html
+load chrome://reftest/content/crashtests/dom/xul/crashtests/326875-1.xhtml
+load 329982-1.xhtml
+load 336096-1.xhtml
+load chrome://reftest/content/crashtests/dom/xul/crashtests/344215-1.xhtml
+load 354611-1.html
+load chrome://reftest/content/crashtests/dom/xul/crashtests/363791-1.xhtml
+load 384877-1.html
+load 386914-1.html
+load chrome://reftest/content/crashtests/dom/xul/crashtests/425821-1.xhtml
+load chrome://reftest/content/crashtests/dom/xul/crashtests/428951-1.xhtml
+load 431906-1.html
+load 461917-1.xhtml
diff --git a/dom/xul/moz.build b/dom/xul/moz.build
new file mode 100644
index 0000000000..155fece389
--- /dev/null
+++ b/dom/xul/moz.build
@@ -0,0 +1,89 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "XUL")
+
+if CONFIG["MOZ_BUILD_APP"] == "browser":
+ DEFINES["MOZ_BREAK_XUL_OVERLAYS"] = True
+
+MOCHITEST_CHROME_MANIFESTS += ["test/chrome.ini"]
+MOCHITEST_MANIFESTS += ["test/mochitest.ini"]
+
+if CONFIG["MOZ_XUL"]:
+ EXPORTS += [
+ "nsXULCommandDispatcher.h",
+ "nsXULElement.h",
+ "nsXULPrototypeDocument.h",
+ "nsXULSortService.h",
+ ]
+
+ EXPORTS.mozilla.dom += [
+ "XULBroadcastManager.h",
+ "XULFrameElement.h",
+ "XULMenuElement.h",
+ "XULPersist.h",
+ "XULPopupElement.h",
+ "XULTextElement.h",
+ "XULTooltipElement.h",
+ "XULTreeElement.h",
+ ]
+
+ UNIFIED_SOURCES += [
+ "nsXULCommandDispatcher.cpp",
+ "nsXULContentSink.cpp",
+ "nsXULContentUtils.cpp",
+ "nsXULElement.cpp",
+ "nsXULPopupListener.cpp",
+ "nsXULPrototypeCache.cpp",
+ "nsXULPrototypeDocument.cpp",
+ "nsXULSortService.cpp",
+ "XULBroadcastManager.cpp",
+ "XULFrameElement.cpp",
+ "XULMenuElement.cpp",
+ "XULPersist.cpp",
+ "XULPopupElement.cpp",
+ "XULTextElement.cpp",
+ "XULTooltipElement.cpp",
+ "XULTreeElement.cpp",
+ ]
+
+XPIDL_SOURCES += [
+ "nsIBrowserController.idl",
+ "nsIController.idl",
+ "nsIControllers.idl",
+]
+
+XPIDL_MODULE = "xul"
+
+EXPORTS.mozilla.dom += [
+ "ChromeObserver.h",
+]
+
+
+UNIFIED_SOURCES += [
+ "ChromeObserver.cpp",
+ "nsXULControllers.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/docshell/base",
+ "/dom/base",
+ "/dom/html",
+ "/dom/xml",
+ "/layout/base",
+ "/layout/generic",
+ "/layout/style",
+ "/layout/xul",
+ "/layout/xul/tree",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/dom/xul/nsIBrowserController.idl b/dom/xul/nsIBrowserController.idl
new file mode 100644
index 0000000000..e976a97c6f
--- /dev/null
+++ b/dom/xul/nsIBrowserController.idl
@@ -0,0 +1,22 @@
+/* -*- 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 "nsISupports.idl"
+
+/**
+ * This interface is used to accompany the nsIController for a
+ * <browser> element. It is used to update the commands in the
+ * parent process when the set of child command have changed.
+ */
+[scriptable, uuid(5bb3d56b-e733-4a2c-8a53-058123df65e2)]
+interface nsIBrowserController : nsISupports
+{
+ // Update the commands for a given action in the parent process.
+ void enableDisableCommands(in AString action,
+ in Array<ACString> enabledCommands,
+ in Array<ACString> disabledCommands);
+};
+
+
diff --git a/dom/xul/nsIController.idl b/dom/xul/nsIController.idl
new file mode 100644
index 0000000000..c3249c5749
--- /dev/null
+++ b/dom/xul/nsIController.idl
@@ -0,0 +1,39 @@
+/* -*- 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 "nsISupports.idl"
+
+[scriptable, uuid(D5B61B82-1DA4-11d3-BF87-00105A1B0627)]
+interface nsIController : nsISupports {
+ boolean isCommandEnabled(in string command);
+ boolean supportsCommand(in string command);
+
+ [can_run_script]
+ void doCommand(in string command);
+
+ void onEvent(in string eventName);
+};
+
+
+/*
+
+ Enhanced controller interface that allows for passing of parameters
+ to commands.
+
+*/
+
+interface nsICommandParams;
+
+[scriptable, uuid(EEC0B435-7F53-44FE-B00A-CF3EED65C01A)]
+interface nsICommandController : nsISupports
+{
+
+ void getCommandStateWithParams( in string command, in nsICommandParams aCommandParams);
+
+ [can_run_script]
+ void doCommandWithParams(in string command, in nsICommandParams aCommandParams);
+
+ Array<ACString> getSupportedCommands();
+};
diff --git a/dom/xul/nsIControllers.idl b/dom/xul/nsIControllers.idl
new file mode 100644
index 0000000000..a6235ba2b5
--- /dev/null
+++ b/dom/xul/nsIControllers.idl
@@ -0,0 +1,34 @@
+/* -*- 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 "nsISupports.idl"
+
+interface nsIController;
+interface nsIDOMXULCommandDispatcher;
+
+[scriptable, uuid(f36e3ec1-9197-4ad8-8d4c-d3b1927fd6df)]
+interface nsIControllers : nsISupports
+{
+ nsIController getControllerForCommand(in string command);
+
+ void insertControllerAt(in unsigned long index, in nsIController controller);
+ nsIController removeControllerAt(in unsigned long index);
+ nsIController getControllerAt(in unsigned long index);
+
+ void appendController(in nsIController controller);
+ void removeController(in nsIController controller);
+
+ /*
+ Return an ID for this controller which is unique to this
+ nsIControllers.
+ */
+ unsigned long getControllerId(in nsIController controller);
+ /*
+ Get the controller specified by the given ID.
+ */
+ nsIController getControllerById(in unsigned long controllerID);
+
+ unsigned long getControllerCount();
+};
diff --git a/dom/xul/nsXULCommandDispatcher.cpp b/dom/xul/nsXULCommandDispatcher.cpp
new file mode 100644
index 0000000000..75f4ceb043
--- /dev/null
+++ b/dom/xul/nsXULCommandDispatcher.cpp
@@ -0,0 +1,433 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=80: */
+/* 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/. */
+
+/*
+
+ This file provides the implementation for the XUL Command Dispatcher.
+
+ */
+
+#include "nsIContent.h"
+#include "nsFocusManager.h"
+#include "nsIControllers.h"
+#include "mozilla/dom/Document.h"
+#include "nsPresContext.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIWindowRoot.h"
+#include "nsXULCommandDispatcher.h"
+#include "mozilla/Logging.h"
+#include "nsContentUtils.h"
+#include "nsReadableUtils.h"
+#include "nsCRT.h"
+#include "nsError.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using mozilla::dom::Document;
+using mozilla::dom::Element;
+
+static LazyLogModule gCommandLog("nsXULCommandDispatcher");
+
+////////////////////////////////////////////////////////////////////////
+
+nsXULCommandDispatcher::nsXULCommandDispatcher(Document* aDocument)
+ : mDocument(aDocument), mUpdaters(nullptr), mLocked(false) {}
+
+nsXULCommandDispatcher::~nsXULCommandDispatcher() { Disconnect(); }
+
+// QueryInterface implementation for nsXULCommandDispatcher
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULCommandDispatcher)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMXULCommandDispatcher)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMXULCommandDispatcher)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULCommandDispatcher)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULCommandDispatcher)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULCommandDispatcher)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULCommandDispatcher)
+ tmp->Disconnect();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULCommandDispatcher)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
+ Updater* updater = tmp->mUpdaters;
+ while (updater) {
+ cb.NoteXPCOMChild(updater->mElement);
+ updater = updater->mNext;
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+void nsXULCommandDispatcher::Disconnect() {
+ while (mUpdaters) {
+ Updater* doomed = mUpdaters;
+ mUpdaters = mUpdaters->mNext;
+ delete doomed;
+ }
+ mDocument = nullptr;
+}
+
+already_AddRefed<nsPIWindowRoot> nsXULCommandDispatcher::GetWindowRoot() {
+ if (mDocument) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow()) {
+ return window->GetTopWindowRoot();
+ }
+ }
+
+ return nullptr;
+}
+
+Element* nsXULCommandDispatcher::GetRootFocusedContentAndWindow(
+ nsPIDOMWindowOuter** aWindow) {
+ *aWindow = nullptr;
+
+ if (!mDocument) {
+ return nullptr;
+ }
+
+ if (nsCOMPtr<nsPIDOMWindowOuter> win = mDocument->GetWindow()) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> rootWindow = win->GetPrivateRoot()) {
+ return nsFocusManager::GetFocusedDescendant(
+ rootWindow, nsFocusManager::eIncludeAllDescendants, aWindow);
+ }
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::GetFocusedElement(Element** aElement) {
+ *aElement = nullptr;
+
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ RefPtr<Element> focusedContent =
+ GetRootFocusedContentAndWindow(getter_AddRefs(focusedWindow));
+ if (focusedContent) {
+ // Make sure the caller can access the focused element.
+ if (!nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller()->Subsumes(
+ focusedContent->NodePrincipal())) {
+ // XXX This might want to return null, but we use that return value
+ // to mean "there is no focused element," so to be clear, throw an
+ // exception.
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ }
+
+ focusedContent.forget(aElement);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::GetFocusedWindow(mozIDOMWindowProxy** aWindow) {
+ *aWindow = nullptr;
+
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ GetRootFocusedContentAndWindow(getter_AddRefs(window));
+ if (!window) return NS_OK;
+
+ // Make sure the caller can access this window. The caller can access this
+ // window iff it can access the document.
+ nsCOMPtr<Document> doc = window->GetDoc();
+
+ // Note: If there is no document, then this window has been cleared and
+ // there's nothing left to protect, so let the window pass through.
+ if (doc && !nsContentUtils::CanCallerAccess(doc))
+ return NS_ERROR_DOM_SECURITY_ERR;
+
+ window.forget(aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::SetFocusedElement(Element* aElement) {
+ RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
+ NS_ENSURE_TRUE(fm, NS_ERROR_FAILURE);
+
+ if (aElement) {
+ return fm->SetFocus(aElement, 0);
+ }
+
+ // if aElement is null, clear the focus in the currently focused child window
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ GetRootFocusedContentAndWindow(getter_AddRefs(focusedWindow));
+ return fm->ClearFocus(focusedWindow);
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::SetFocusedWindow(mozIDOMWindowProxy* aWindow) {
+ NS_ENSURE_TRUE(aWindow, NS_OK); // do nothing if set to null
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
+ NS_ENSURE_TRUE(fm, NS_ERROR_FAILURE);
+
+ // get the containing frame for the window, and set it as focused. This will
+ // end up focusing whatever is currently focused inside the frame. Since
+ // setting the command dispatcher's focused window doesn't raise the window,
+ // setting it to a top-level window doesn't need to do anything.
+ RefPtr<Element> frameElement = window->GetFrameElementInternal();
+ if (frameElement) {
+ return fm->SetFocus(frameElement, 0);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::AdvanceFocus() {
+ return AdvanceFocusIntoSubtree(nullptr);
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::RewindFocus() {
+ nsCOMPtr<nsPIDOMWindowOuter> win;
+ GetRootFocusedContentAndWindow(getter_AddRefs(win));
+
+ RefPtr<Element> result;
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm)
+ return fm->MoveFocus(win, nullptr, nsIFocusManager::MOVEFOCUS_BACKWARD, 0,
+ getter_AddRefs(result));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::AdvanceFocusIntoSubtree(Element* aElt) {
+ nsCOMPtr<nsPIDOMWindowOuter> win;
+ GetRootFocusedContentAndWindow(getter_AddRefs(win));
+
+ RefPtr<Element> result;
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm)
+ return fm->MoveFocus(win, aElt, nsIFocusManager::MOVEFOCUS_FORWARD, 0,
+ getter_AddRefs(result));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::AddCommandUpdater(Element* aElement,
+ const nsAString& aEvents,
+ const nsAString& aTargets) {
+ MOZ_ASSERT(aElement != nullptr, "null ptr");
+ if (!aElement) return NS_ERROR_NULL_POINTER;
+
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_UNEXPECTED);
+
+ nsresult rv = nsContentUtils::CheckSameOrigin(mDocument, aElement);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Updater* updater = mUpdaters;
+ Updater** link = &mUpdaters;
+
+ while (updater) {
+ if (updater->mElement == aElement) {
+#ifdef DEBUG
+ if (MOZ_LOG_TEST(gCommandLog, LogLevel::Debug)) {
+ nsAutoCString eventsC, targetsC, aeventsC, atargetsC;
+ LossyCopyUTF16toASCII(updater->mEvents, eventsC);
+ LossyCopyUTF16toASCII(updater->mTargets, targetsC);
+ CopyUTF16toUTF8(aEvents, aeventsC);
+ CopyUTF16toUTF8(aTargets, atargetsC);
+ MOZ_LOG(gCommandLog, LogLevel::Debug,
+ ("xulcmd[%p] replace %p(events=%s targets=%s) with (events=%s "
+ "targets=%s)",
+ this, aElement, eventsC.get(), targetsC.get(), aeventsC.get(),
+ atargetsC.get()));
+ }
+#endif
+
+ // If the updater was already in the list, then replace
+ // (?) the 'events' and 'targets' filters with the new
+ // specification.
+ updater->mEvents = aEvents;
+ updater->mTargets = aTargets;
+ return NS_OK;
+ }
+
+ link = &(updater->mNext);
+ updater = updater->mNext;
+ }
+#ifdef DEBUG
+ if (MOZ_LOG_TEST(gCommandLog, LogLevel::Debug)) {
+ nsAutoCString aeventsC, atargetsC;
+ CopyUTF16toUTF8(aEvents, aeventsC);
+ CopyUTF16toUTF8(aTargets, atargetsC);
+
+ MOZ_LOG(gCommandLog, LogLevel::Debug,
+ ("xulcmd[%p] add %p(events=%s targets=%s)", this, aElement,
+ aeventsC.get(), atargetsC.get()));
+ }
+#endif
+
+ // If we get here, this is a new updater. Append it to the list.
+ *link = new Updater(aElement, aEvents, aTargets);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::RemoveCommandUpdater(Element* aElement) {
+ MOZ_ASSERT(aElement != nullptr, "null ptr");
+ if (!aElement) return NS_ERROR_NULL_POINTER;
+
+ Updater* updater = mUpdaters;
+ Updater** link = &mUpdaters;
+
+ while (updater) {
+ if (updater->mElement == aElement) {
+#ifdef DEBUG
+ if (MOZ_LOG_TEST(gCommandLog, LogLevel::Debug)) {
+ nsAutoCString eventsC, targetsC;
+ LossyCopyUTF16toASCII(updater->mEvents, eventsC);
+ LossyCopyUTF16toASCII(updater->mTargets, targetsC);
+ MOZ_LOG(gCommandLog, LogLevel::Debug,
+ ("xulcmd[%p] remove %p(events=%s targets=%s)", this, aElement,
+ eventsC.get(), targetsC.get()));
+ }
+#endif
+
+ *link = updater->mNext;
+ delete updater;
+ return NS_OK;
+ }
+
+ link = &(updater->mNext);
+ updater = updater->mNext;
+ }
+
+ // Hmm. Not found. Oh well.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::UpdateCommands(const nsAString& aEventName) {
+ if (mLocked) {
+ if (!mPendingUpdates.Contains(aEventName)) {
+ mPendingUpdates.AppendElement(aEventName);
+ }
+
+ return NS_OK;
+ }
+
+ nsAutoString id;
+ RefPtr<Element> element;
+ GetFocusedElement(getter_AddRefs(element));
+ if (element) {
+ element->GetAttr(nsGkAtoms::id, id);
+ }
+
+ nsCOMArray<nsIContent> updaters;
+
+ for (Updater* updater = mUpdaters; updater != nullptr;
+ updater = updater->mNext) {
+ // Skip any nodes that don't match our 'events' or 'targets'
+ // filters.
+ if (!Matches(updater->mEvents, aEventName)) continue;
+
+ if (!Matches(updater->mTargets, id)) continue;
+
+ nsIContent* content = updater->mElement;
+ NS_ASSERTION(content != nullptr, "mElement is null");
+ if (!content) return NS_ERROR_UNEXPECTED;
+
+ updaters.AppendObject(content);
+ }
+
+ for (int32_t u = 0; u < updaters.Count(); u++) {
+ nsIContent* content = updaters[u];
+
+#ifdef DEBUG
+ if (MOZ_LOG_TEST(gCommandLog, LogLevel::Debug)) {
+ nsAutoCString aeventnameC;
+ CopyUTF16toUTF8(aEventName, aeventnameC);
+ MOZ_LOG(
+ gCommandLog, LogLevel::Debug,
+ ("xulcmd[%p] update %p event=%s", this, content, aeventnameC.get()));
+ }
+#endif
+
+ WidgetEvent event(true, eXULCommandUpdate);
+ EventDispatcher::Dispatch(content, nullptr, &event);
+ }
+ return NS_OK;
+}
+
+bool nsXULCommandDispatcher::Matches(const nsString& aList,
+ const nsAString& aElement) {
+ if (aList.EqualsLiteral("*")) return true; // match _everything_!
+
+ int32_t indx = aList.Find(PromiseFlatString(aElement));
+ if (indx == -1) return false; // not in the list at all
+
+ // okay, now make sure it's not a substring snafu; e.g., 'ur'
+ // found inside of 'blur'.
+ if (indx > 0) {
+ char16_t ch = aList[indx - 1];
+ if (!nsCRT::IsAsciiSpace(ch) && ch != char16_t(',')) return false;
+ }
+
+ if (indx + aElement.Length() < aList.Length()) {
+ char16_t ch = aList[indx + aElement.Length()];
+ if (!nsCRT::IsAsciiSpace(ch) && ch != char16_t(',')) return false;
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::GetControllers(nsIControllers** aResult) {
+ nsCOMPtr<nsPIWindowRoot> root = GetWindowRoot();
+ NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
+
+ return root->GetControllers(false /* for any window */, aResult);
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::GetControllerForCommand(const char* aCommand,
+ nsIController** _retval) {
+ nsCOMPtr<nsPIWindowRoot> root = GetWindowRoot();
+ NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
+
+ return root->GetControllerForCommand(aCommand, false /* for any window */,
+ _retval);
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::Lock() {
+ // Since locking is used only as a performance optimization, we don't worry
+ // about nested lock calls. If that does happen, it just means we will unlock
+ // and process updates earlier.
+ mLocked = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::Unlock() {
+ if (mLocked) {
+ mLocked = false;
+
+ // Handle any pending updates one at a time. In the unlikely case where a
+ // lock is added during the update, break out.
+ while (!mLocked && mPendingUpdates.Length() > 0) {
+ nsString name = mPendingUpdates.ElementAt(0);
+ mPendingUpdates.RemoveElementAt(0);
+ UpdateCommands(name);
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/dom/xul/nsXULCommandDispatcher.h b/dom/xul/nsXULCommandDispatcher.h
new file mode 100644
index 0000000000..baf1826da9
--- /dev/null
+++ b/dom/xul/nsXULCommandDispatcher.h
@@ -0,0 +1,79 @@
+/* -*- 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/. */
+
+/*
+
+ This is the focus manager for XUL documents.
+
+*/
+
+#ifndef nsXULCommandDispatcher_h__
+#define nsXULCommandDispatcher_h__
+
+#include "nsCOMPtr.h"
+#include "nsIDOMXULCommandDispatcher.h"
+#include "nsWeakReference.h"
+#include "nsString.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/RefPtr.h"
+
+class nsPIWindowRoot;
+
+namespace mozilla {
+namespace dom {
+class Document;
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+class nsXULCommandDispatcher : public nsIDOMXULCommandDispatcher,
+ public nsSupportsWeakReference {
+ public:
+ explicit nsXULCommandDispatcher(mozilla::dom::Document* aDocument);
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsXULCommandDispatcher,
+ nsIDOMXULCommandDispatcher)
+
+ // nsIDOMXULCommandDispatcher interface
+ NS_DECL_NSIDOMXULCOMMANDDISPATCHER
+
+ void Disconnect();
+
+ protected:
+ virtual ~nsXULCommandDispatcher();
+
+ already_AddRefed<nsPIWindowRoot> GetWindowRoot();
+
+ mozilla::dom::Element* GetRootFocusedContentAndWindow(
+ nsPIDOMWindowOuter** aWindow);
+
+ RefPtr<mozilla::dom::Document> mDocument;
+
+ class Updater {
+ public:
+ Updater(mozilla::dom::Element* aElement, const nsAString& aEvents,
+ const nsAString& aTargets)
+ : mElement(aElement),
+ mEvents(aEvents),
+ mTargets(aTargets),
+ mNext(nullptr) {}
+
+ RefPtr<mozilla::dom::Element> mElement;
+ nsString mEvents;
+ nsString mTargets;
+ Updater* mNext;
+ };
+
+ Updater* mUpdaters;
+
+ bool Matches(const nsString& aList, const nsAString& aElement);
+
+ bool mLocked;
+ nsTArray<nsString> mPendingUpdates;
+};
+
+#endif // nsXULCommandDispatcher_h__
diff --git a/dom/xul/nsXULContentSink.cpp b/dom/xul/nsXULContentSink.cpp
new file mode 100644
index 0000000000..7a4d9bfd3c
--- /dev/null
+++ b/dom/xul/nsXULContentSink.cpp
@@ -0,0 +1,884 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/*
+ * An implementation for a Gecko-style content sink that knows how
+ * to build a content model (the "prototype" document) from XUL.
+ *
+ * For more information on XUL,
+ * see http://developer.mozilla.org/en/docs/XUL
+ */
+
+#include "nsXULContentSink.h"
+
+#include "jsfriendapi.h"
+
+#include "nsCOMPtr.h"
+#include "nsHTMLStyleSheet.h"
+#include "nsIContentSink.h"
+#include "mozilla/dom/Document.h"
+#include "nsIFormControl.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsNameSpaceManager.h"
+#include "nsParserBase.h"
+#include "nsViewManager.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsLayoutCID.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsXULElement.h"
+#include "mozilla/Logging.h"
+#include "nsCRT.h"
+
+#include "nsXULPrototypeDocument.h" // XXXbe temporary
+#include "mozilla/css/Loader.h"
+
+#include "nsUnicharUtils.h"
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+#include "nsAttrName.h"
+#include "nsXMLContentSink.h"
+#include "nsIScriptError.h"
+#include "nsContentTypeParser.h"
+
+static mozilla::LazyLogModule gContentSinkLog("nsXULContentSink");
+
+using namespace mozilla;
+using namespace mozilla::dom;
+//----------------------------------------------------------------------
+
+XULContentSinkImpl::ContextStack::ContextStack() : mTop(nullptr), mDepth(0) {}
+
+XULContentSinkImpl::ContextStack::~ContextStack() {
+ while (mTop) {
+ Entry* doomed = mTop;
+ mTop = mTop->mNext;
+ delete doomed;
+ }
+}
+
+void XULContentSinkImpl::ContextStack::Push(RefPtr<nsXULPrototypeNode>&& aNode,
+ State aState) {
+ mTop = new Entry(std::move(aNode), aState, mTop);
+ ++mDepth;
+}
+
+nsresult XULContentSinkImpl::ContextStack::Pop(State* aState) {
+ if (mDepth == 0) return NS_ERROR_UNEXPECTED;
+
+ Entry* entry = mTop;
+ mTop = mTop->mNext;
+ --mDepth;
+
+ *aState = entry->mState;
+ delete entry;
+
+ return NS_OK;
+}
+
+nsresult XULContentSinkImpl::ContextStack::GetTopNode(
+ RefPtr<nsXULPrototypeNode>& aNode) {
+ if (mDepth == 0) return NS_ERROR_UNEXPECTED;
+
+ aNode = mTop->mNode;
+ return NS_OK;
+}
+
+nsresult XULContentSinkImpl::ContextStack::GetTopChildren(
+ nsPrototypeArray** aChildren) {
+ if (mDepth == 0) return NS_ERROR_UNEXPECTED;
+
+ *aChildren = &(mTop->mChildren);
+ return NS_OK;
+}
+
+void XULContentSinkImpl::ContextStack::Clear() {
+ Entry* cur = mTop;
+ while (cur) {
+ // Release the root element (and its descendants).
+ Entry* next = cur->mNext;
+ delete cur;
+ cur = next;
+ }
+
+ mTop = nullptr;
+ mDepth = 0;
+}
+
+void XULContentSinkImpl::ContextStack::Traverse(
+ nsCycleCollectionTraversalCallback& aCb) {
+ nsCycleCollectionTraversalCallback& cb = aCb;
+ for (ContextStack::Entry* tmp = mTop; tmp; tmp = tmp->mNext) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNode)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildren)
+ }
+}
+
+//----------------------------------------------------------------------
+
+XULContentSinkImpl::XULContentSinkImpl()
+ : mText(nullptr),
+ mTextLength(0),
+ mTextSize(0),
+ mConstrainSize(true),
+ mState(eInProlog) {}
+
+XULContentSinkImpl::~XULContentSinkImpl() {
+ // The context stack _should_ be empty, unless something has gone wrong.
+ NS_ASSERTION(mContextStack.Depth() == 0, "Context stack not empty?");
+ mContextStack.Clear();
+
+ free(mText);
+}
+
+//----------------------------------------------------------------------
+// nsISupports interface
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(XULContentSinkImpl)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(XULContentSinkImpl)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNodeInfoManager)
+ tmp->mContextStack.Clear();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototype)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(XULContentSinkImpl)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfoManager)
+ tmp->mContextStack.Traverse(cb);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototype)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XULContentSinkImpl)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXMLContentSink)
+ NS_INTERFACE_MAP_ENTRY(nsIXMLContentSink)
+ NS_INTERFACE_MAP_ENTRY(nsIExpatSink)
+ NS_INTERFACE_MAP_ENTRY(nsIContentSink)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(XULContentSinkImpl)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(XULContentSinkImpl)
+
+//----------------------------------------------------------------------
+// nsIContentSink interface
+
+NS_IMETHODIMP
+XULContentSinkImpl::WillBuildModel(nsDTDMode aDTDMode) {
+#if FIXME
+ if (!mParentContentSink) {
+ // If we're _not_ an overlay, then notify the document that
+ // the load is beginning.
+ mDocument->BeginLoad();
+ }
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::DidBuildModel(bool aTerminated) {
+ nsCOMPtr<Document> doc = do_QueryReferent(mDocument);
+ if (doc) {
+ mPrototype->NotifyLoadDone();
+ mDocument = nullptr;
+ }
+
+ // Drop our reference to the parser to get rid of a circular
+ // reference.
+ mParser = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::WillInterrupt(void) {
+ // XXX Notify the docshell, if necessary
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::WillResume(void) {
+ // XXX Notify the docshell, if necessary
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::SetParser(nsParserBase* aParser) {
+ mParser = aParser;
+ return NS_OK;
+}
+
+void XULContentSinkImpl::SetDocumentCharset(
+ NotNull<const Encoding*> aEncoding) {
+ nsCOMPtr<Document> doc = do_QueryReferent(mDocument);
+ if (doc) {
+ doc->SetDocumentCharacterSet(aEncoding);
+ }
+}
+
+nsISupports* XULContentSinkImpl::GetTarget() {
+ nsCOMPtr<Document> doc = do_QueryReferent(mDocument);
+ return ToSupports(doc);
+}
+
+//----------------------------------------------------------------------
+
+nsresult XULContentSinkImpl::Init(Document* aDocument,
+ nsXULPrototypeDocument* aPrototype) {
+ MOZ_ASSERT(aDocument != nullptr, "null ptr");
+ if (!aDocument) return NS_ERROR_NULL_POINTER;
+
+ mDocument = do_GetWeakReference(aDocument);
+ mPrototype = aPrototype;
+
+ mDocumentURL = mPrototype->GetURI();
+ mNodeInfoManager = aPrototype->GetNodeInfoManager();
+ if (!mNodeInfoManager) return NS_ERROR_UNEXPECTED;
+
+ mState = eInProlog;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+//
+// Text buffering
+//
+
+bool XULContentSinkImpl::IsDataInBuffer(char16_t* buffer, int32_t length) {
+ for (int32_t i = 0; i < length; ++i) {
+ if (buffer[i] == ' ' || buffer[i] == '\t' || buffer[i] == '\n' ||
+ buffer[i] == '\r')
+ continue;
+
+ return true;
+ }
+ return false;
+}
+
+nsresult XULContentSinkImpl::FlushText(bool aCreateTextNode) {
+ nsresult rv;
+
+ do {
+ // Don't do anything if there's no text to create a node from, or
+ // if they've told us not to create a text node
+ if (!mTextLength) break;
+
+ if (!aCreateTextNode) break;
+
+ RefPtr<nsXULPrototypeNode> node;
+ rv = mContextStack.GetTopNode(node);
+ if (NS_FAILED(rv)) return rv;
+
+ bool stripWhitespace = false;
+ if (node->mType == nsXULPrototypeNode::eType_Element) {
+ mozilla::dom::NodeInfo* nodeInfo =
+ static_cast<nsXULPrototypeElement*>(node.get())->mNodeInfo;
+
+ if (nodeInfo->NamespaceEquals(kNameSpaceID_XUL))
+ stripWhitespace = !nodeInfo->Equals(nsGkAtoms::label) &&
+ !nodeInfo->Equals(nsGkAtoms::description);
+ }
+
+ // Don't bother if there's nothing but whitespace.
+ if (stripWhitespace && !IsDataInBuffer(mText, mTextLength)) break;
+
+ // Don't bother if we're not in XUL document body
+ if (mState != eInDocumentElement || mContextStack.Depth() == 0) break;
+
+ RefPtr<nsXULPrototypeText> text = new nsXULPrototypeText();
+ text->mValue.Assign(mText, mTextLength);
+ if (stripWhitespace) text->mValue.Trim(" \t\n\r");
+
+ // hook it up
+ nsPrototypeArray* children = nullptr;
+ rv = mContextStack.GetTopChildren(&children);
+ if (NS_FAILED(rv)) return rv;
+
+ children->AppendElement(text.forget());
+ } while (0);
+
+ // Reset our text buffer
+ mTextLength = 0;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+
+nsresult XULContentSinkImpl::NormalizeAttributeString(
+ const char16_t* aExpatName, nsAttrName& aName) {
+ int32_t nameSpaceID;
+ RefPtr<nsAtom> prefix, localName;
+ nsContentUtils::SplitExpatName(aExpatName, getter_AddRefs(prefix),
+ getter_AddRefs(localName), &nameSpaceID);
+
+ if (nameSpaceID == kNameSpaceID_None) {
+ aName.SetTo(localName);
+
+ return NS_OK;
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> ni;
+ ni = mNodeInfoManager->GetNodeInfo(localName, prefix, nameSpaceID,
+ nsINode::ATTRIBUTE_NODE);
+ aName.SetTo(ni);
+
+ return NS_OK;
+}
+
+/**** BEGIN NEW APIs ****/
+
+NS_IMETHODIMP
+XULContentSinkImpl::HandleStartElement(const char16_t* aName,
+ const char16_t** aAtts,
+ uint32_t aAttsCount,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber) {
+ // XXX Hopefully the parser will flag this before we get here. If
+ // we're in the epilog, there should be no new elements
+ MOZ_ASSERT(mState != eInEpilog, "tag in XUL doc epilog");
+ MOZ_ASSERT(aAttsCount % 2 == 0, "incorrect aAttsCount");
+
+ // Adjust aAttsCount so it's the actual number of attributes
+ aAttsCount /= 2;
+
+ if (mState == eInEpilog) return NS_ERROR_UNEXPECTED;
+
+ if (mState != eInScript) {
+ FlushText();
+ }
+
+ int32_t nameSpaceID;
+ RefPtr<nsAtom> prefix, localName;
+ nsContentUtils::SplitExpatName(aName, getter_AddRefs(prefix),
+ getter_AddRefs(localName), &nameSpaceID);
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = mNodeInfoManager->GetNodeInfo(localName, prefix, nameSpaceID,
+ nsINode::ELEMENT_NODE);
+
+ nsresult rv = NS_OK;
+ switch (mState) {
+ case eInProlog:
+ // We're the root document element
+ rv = OpenRoot(aAtts, aAttsCount, nodeInfo);
+ break;
+
+ case eInDocumentElement:
+ rv = OpenTag(aAtts, aAttsCount, aLineNumber, nodeInfo);
+ break;
+
+ case eInEpilog:
+ case eInScript:
+ MOZ_LOG(
+ gContentSinkLog, LogLevel::Warning,
+ ("xul: warning: unexpected tags in epilog at line %d", aLineNumber));
+ rv = NS_ERROR_UNEXPECTED; // XXX
+ break;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::HandleEndElement(const char16_t* aName) {
+ // Never EVER return anything but NS_OK or
+ // NS_ERROR_HTMLPARSER_BLOCK from this method. Doing so will blow
+ // the parser's little mind all over the planet.
+ nsresult rv;
+
+ RefPtr<nsXULPrototypeNode> node;
+ rv = mContextStack.GetTopNode(node);
+
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ switch (node->mType) {
+ case nsXULPrototypeNode::eType_Element: {
+ // Flush any text _now_, so that we'll get text nodes created
+ // before popping the stack.
+ FlushText();
+
+ // Pop the context stack and do prototype hookup.
+ nsPrototypeArray* children = nullptr;
+ rv = mContextStack.GetTopChildren(&children);
+ if (NS_FAILED(rv)) return rv;
+
+ nsXULPrototypeElement* element =
+ static_cast<nsXULPrototypeElement*>(node.get());
+
+ int32_t count = children->Length();
+ if (count) {
+ element->mChildren.SetCapacity(count);
+
+ for (int32_t i = 0; i < count; ++i)
+ element->mChildren.AppendElement(children->ElementAt(i));
+ }
+ } break;
+
+ case nsXULPrototypeNode::eType_Script: {
+ nsXULPrototypeScript* script =
+ static_cast<nsXULPrototypeScript*>(node.get());
+
+ // If given a src= attribute, we must ignore script tag content.
+ if (!script->mSrcURI && !script->HasScriptObject()) {
+ nsCOMPtr<Document> doc = do_QueryReferent(mDocument);
+
+ script->mOutOfLine = false;
+ if (doc) {
+ script->Compile(mText, mTextLength, JS::SourceOwnership::Borrowed,
+ mDocumentURL, script->mLineNo, doc);
+ }
+ }
+
+ FlushText(false);
+ } break;
+
+ default:
+ NS_ERROR("didn't expect that");
+ break;
+ }
+
+ rv = mContextStack.Pop(&mState);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "context stack corrupted");
+ if (NS_FAILED(rv)) return rv;
+
+ if (mContextStack.Depth() == 0) {
+ // The root element should -always- be an element, because
+ // it'll have been created via XULContentSinkImpl::OpenRoot().
+ NS_ASSERTION(node->mType == nsXULPrototypeNode::eType_Element,
+ "root is not an element");
+ if (node->mType != nsXULPrototypeNode::eType_Element)
+ return NS_ERROR_UNEXPECTED;
+
+ // Now that we're done parsing, set the prototype document's
+ // root element. This transfers ownership of the prototype
+ // element tree to the prototype document.
+ nsXULPrototypeElement* element =
+ static_cast<nsXULPrototypeElement*>(node.get());
+
+ mPrototype->SetRootElement(element);
+ mState = eInEpilog;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::HandleComment(const char16_t* aName) {
+ FlushText();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::HandleCDataSection(const char16_t* aData,
+ uint32_t aLength) {
+ FlushText();
+ return AddText(aData, aLength);
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::HandleDoctypeDecl(const nsAString& aSubset,
+ const nsAString& aName,
+ const nsAString& aSystemId,
+ const nsAString& aPublicId,
+ nsISupports* aCatalogData) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::HandleCharacterData(const char16_t* aData,
+ uint32_t aLength) {
+ if (aData && mState != eInProlog && mState != eInEpilog) {
+ return AddText(aData, aLength);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::HandleProcessingInstruction(const char16_t* aTarget,
+ const char16_t* aData) {
+ FlushText();
+
+ const nsDependentString target(aTarget);
+ const nsDependentString data(aData);
+
+ // Note: the created nsXULPrototypePI has mRefCnt == 1
+ RefPtr<nsXULPrototypePI> pi = new nsXULPrototypePI();
+ pi->mTarget = target;
+ pi->mData = data;
+
+ if (mState == eInProlog) {
+ // Note: passing in already addrefed pi
+ return mPrototype->AddProcessingInstruction(pi);
+ }
+
+ nsresult rv;
+ nsPrototypeArray* children = nullptr;
+ rv = mContextStack.GetTopChildren(&children);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ children->AppendElement(pi);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::HandleXMLDeclaration(const char16_t* aVersion,
+ const char16_t* aEncoding,
+ int32_t aStandalone) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::ReportError(const char16_t* aErrorText,
+ const char16_t* aSourceText,
+ nsIScriptError* aError, bool* _retval) {
+ MOZ_ASSERT(aError && aSourceText && aErrorText, "Check arguments!!!");
+
+ // The expat driver should report the error.
+ *_retval = true;
+
+ nsresult rv = NS_OK;
+
+ // make sure to empty the context stack so that
+ // <parsererror> could become the root element.
+ mContextStack.Clear();
+
+ mState = eInProlog;
+
+ // Clear any buffered-up text we have. It's enough to set the length to 0.
+ // The buffer itself is allocated when we're created and deleted in our
+ // destructor, so don't mess with it.
+ mTextLength = 0;
+
+ // return leaving the document empty if we're asked to not add a <parsererror>
+ // root node
+ nsCOMPtr<Document> idoc = do_QueryReferent(mDocument);
+ if (idoc && idoc->SuppressParserErrorElement()) {
+ return NS_OK;
+ };
+
+ const char16_t* noAtts[] = {0, 0};
+
+ constexpr auto errorNs =
+ u"http://www.mozilla.org/newlayout/xml/parsererror.xml"_ns;
+
+ nsAutoString parsererror(errorNs);
+ parsererror.Append((char16_t)0xFFFF);
+ parsererror.AppendLiteral("parsererror");
+
+ rv = HandleStartElement(parsererror.get(), noAtts, 0, 0, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = HandleCharacterData(aErrorText, NS_strlen(aErrorText));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString sourcetext(errorNs);
+ sourcetext.Append((char16_t)0xFFFF);
+ sourcetext.AppendLiteral("sourcetext");
+
+ rv = HandleStartElement(sourcetext.get(), noAtts, 0, 0, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = HandleCharacterData(aSourceText, NS_strlen(aSourceText));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = HandleEndElement(sourcetext.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = HandleEndElement(parsererror.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+nsresult XULContentSinkImpl::OpenRoot(const char16_t** aAttributes,
+ const uint32_t aAttrLen,
+ mozilla::dom::NodeInfo* aNodeInfo) {
+ NS_ASSERTION(mState == eInProlog, "how'd we get here?");
+ if (mState != eInProlog) return NS_ERROR_UNEXPECTED;
+
+ if (aNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_XHTML) ||
+ aNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_XUL)) {
+ MOZ_LOG(gContentSinkLog, LogLevel::Error,
+ ("xul: script tag not allowed as root content element"));
+
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Create the element
+ RefPtr<nsXULPrototypeElement> element = new nsXULPrototypeElement(aNodeInfo);
+
+ // Add the attributes
+ nsresult rv = AddAttributes(aAttributes, aAttrLen, element);
+ if (NS_FAILED(rv)) return rv;
+
+ // Push the element onto the context stack, so that child
+ // containers will hook up to us as their parent.
+ mContextStack.Push(std::move(element), mState);
+
+ mState = eInDocumentElement;
+ return NS_OK;
+}
+
+nsresult XULContentSinkImpl::OpenTag(const char16_t** aAttributes,
+ const uint32_t aAttrLen,
+ const uint32_t aLineNumber,
+ mozilla::dom::NodeInfo* aNodeInfo) {
+ // Create the element
+ RefPtr<nsXULPrototypeElement> element = new nsXULPrototypeElement(aNodeInfo);
+
+ // Link this element to its parent.
+ nsPrototypeArray* children = nullptr;
+ nsresult rv = mContextStack.GetTopChildren(&children);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Add the attributes
+ rv = AddAttributes(aAttributes, aAttrLen, element);
+ if (NS_FAILED(rv)) return rv;
+
+ children->AppendElement(element);
+
+ if (aNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_XHTML) ||
+ aNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_XUL)) {
+ // Do scripty things now
+ rv = OpenScript(aAttributes, aLineNumber);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(mState == eInScript || mState == eInDocumentElement,
+ "Unexpected state");
+ if (mState == eInScript) {
+ // OpenScript has pushed the nsPrototypeScriptElement onto the
+ // stack, so we're done.
+ return NS_OK;
+ }
+ }
+
+ // Push the element onto the context stack, so that child
+ // containers will hook up to us as their parent.
+ mContextStack.Push(std::move(element), mState);
+
+ mState = eInDocumentElement;
+ return NS_OK;
+}
+
+nsresult XULContentSinkImpl::OpenScript(const char16_t** aAttributes,
+ const uint32_t aLineNumber) {
+ bool isJavaScript = true;
+ nsresult rv;
+
+ // Look for SRC attribute and look for a LANGUAGE attribute
+ nsAutoString src;
+ while (*aAttributes) {
+ const nsDependentString key(aAttributes[0]);
+ if (key.EqualsLiteral("src")) {
+ src.Assign(aAttributes[1]);
+ } else if (key.EqualsLiteral("type")) {
+ nsDependentString str(aAttributes[1]);
+ nsContentTypeParser parser(str);
+ nsAutoString mimeType;
+ rv = parser.GetType(mimeType);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_INVALID_ARG) {
+ // Fail immediately rather than checking if later things
+ // are okay.
+ return NS_OK;
+ }
+ // We do want the warning here
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // NOTE(emilio): Module scripts don't pass this test, aren't cached yet.
+ // If they become cached, then we need to tweak
+ // PrototypeDocumentContentSink and remove the special cases there.
+ if (nsContentUtils::IsJavascriptMIMEType(mimeType)) {
+ isJavaScript = true;
+
+ // Get the version string, and ensure that JavaScript supports it.
+ nsAutoString versionName;
+ rv = parser.GetParameter("version", versionName);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsContentUtils::ReportToConsoleNonLocalized(
+ u"Versioned JavaScripts are no longer supported. "
+ "Please remove the version parameter."_ns,
+ nsIScriptError::errorFlag, "XUL Document"_ns, nullptr,
+ mDocumentURL, u""_ns, aLineNumber);
+ isJavaScript = false;
+ } else if (rv != NS_ERROR_INVALID_ARG) {
+ return rv;
+ }
+ } else {
+ isJavaScript = false;
+ }
+ } else if (key.EqualsLiteral("language")) {
+ // Language is deprecated, and the impl in ScriptLoader ignores the
+ // various version strings anyway. So we make no attempt to support
+ // languages other than JS for language=
+ nsAutoString lang(aAttributes[1]);
+ if (nsContentUtils::IsJavaScriptLanguage(lang)) {
+ isJavaScript = true;
+ }
+ }
+ aAttributes += 2;
+ }
+
+ // Don't process scripts that aren't JavaScript.
+ if (!isJavaScript) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<Document> doc(do_QueryReferent(mDocument));
+ nsCOMPtr<nsIScriptGlobalObject> globalObject;
+ if (doc) globalObject = do_QueryInterface(doc->GetWindow());
+ RefPtr<nsXULPrototypeScript> script = new nsXULPrototypeScript(aLineNumber);
+
+ // If there is a SRC attribute...
+ if (!src.IsEmpty()) {
+ // Use the SRC attribute value to load the URL
+ rv = NS_NewURI(getter_AddRefs(script->mSrcURI), src, nullptr, mDocumentURL);
+
+ // Check if this document is allowed to load a script from this source
+ // NOTE: if we ever allow scripts added via the DOM to run, we need to
+ // add a CheckLoadURI call for that as well.
+ if (NS_SUCCEEDED(rv)) {
+ if (!mSecMan)
+ mSecMan = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<Document> doc = do_QueryReferent(mDocument, &rv);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = mSecMan->CheckLoadURIWithPrincipal(
+ doc->NodePrincipal(), script->mSrcURI,
+ nsIScriptSecurityManager::ALLOW_CHROME, doc->InnerWindowID());
+ }
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Attempt to deserialize an out-of-line script from the FastLoad
+ // file right away. Otherwise we'll end up reloading the script and
+ // corrupting the FastLoad file trying to serialize it, in the case
+ // where it's already there.
+ script->DeserializeOutOfLine(nullptr, mPrototype);
+ }
+
+ nsPrototypeArray* children = nullptr;
+ rv = mContextStack.GetTopChildren(&children);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ children->AppendElement(script);
+
+ mConstrainSize = false;
+
+ mContextStack.Push(script, mState);
+ mState = eInScript;
+
+ return NS_OK;
+}
+
+nsresult XULContentSinkImpl::AddAttributes(const char16_t** aAttributes,
+ const uint32_t aAttrLen,
+ nsXULPrototypeElement* aElement) {
+ // Add tag attributes to the element
+ nsresult rv;
+
+ // Create storage for the attributes
+ nsXULPrototypeAttribute* attrs = nullptr;
+ if (aAttrLen > 0) {
+ attrs = aElement->mAttributes.AppendElements(aAttrLen);
+ }
+
+ // Copy the attributes into the prototype
+ uint32_t i;
+ for (i = 0; i < aAttrLen; ++i) {
+ rv = NormalizeAttributeString(aAttributes[i * 2], attrs[i].mName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aElement->SetAttrAt(i, nsDependentString(aAttributes[i * 2 + 1]),
+ mDocumentURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (MOZ_LOG_TEST(gContentSinkLog, LogLevel::Debug)) {
+ nsAutoString extraWhiteSpace;
+ int32_t cnt = mContextStack.Depth();
+ while (--cnt >= 0) extraWhiteSpace.AppendLiteral(" ");
+ nsAutoString qnameC, valueC;
+ qnameC.Assign(aAttributes[0]);
+ valueC.Assign(aAttributes[1]);
+ MOZ_LOG(gContentSinkLog, LogLevel::Debug,
+ ("xul: %.5d. %s %s=%s",
+ -1, // XXX pass in line number
+ NS_ConvertUTF16toUTF8(extraWhiteSpace).get(),
+ NS_ConvertUTF16toUTF8(qnameC).get(),
+ NS_ConvertUTF16toUTF8(valueC).get()));
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult XULContentSinkImpl::AddText(const char16_t* aText, int32_t aLength) {
+ // Create buffer when we first need it
+ if (0 == mTextSize) {
+ mText = (char16_t*)malloc(sizeof(char16_t) * 4096);
+ if (nullptr == mText) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mTextSize = 4096;
+ }
+
+ // Copy data from string into our buffer; flush buffer when it fills up
+ int32_t offset = 0;
+ while (0 != aLength) {
+ int32_t amount = mTextSize - mTextLength;
+ if (amount > aLength) {
+ amount = aLength;
+ }
+ if (0 == amount) {
+ if (mConstrainSize) {
+ nsresult rv = FlushText();
+ if (NS_OK != rv) {
+ return rv;
+ }
+ } else {
+ CheckedInt32 size = mTextSize;
+ size += aLength;
+ if (!size.isValid()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mTextSize = size.value();
+
+ mText = (char16_t*)realloc(mText, sizeof(char16_t) * mTextSize);
+ if (nullptr == mText) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+ memcpy(&mText[mTextLength], aText + offset, sizeof(char16_t) * amount);
+
+ mTextLength += amount;
+ offset += amount;
+ aLength -= amount;
+ }
+
+ return NS_OK;
+}
diff --git a/dom/xul/nsXULContentSink.h b/dom/xul/nsXULContentSink.h
new file mode 100644
index 0000000000..948abc94cb
--- /dev/null
+++ b/dom/xul/nsXULContentSink.h
@@ -0,0 +1,144 @@
+/* -*- 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/. */
+
+#ifndef nsXULContentSink_h__
+#define nsXULContentSink_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIExpatSink.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIXMLContentSink.h"
+#include "nsNodeInfoManager.h"
+#include "nsXULElement.h"
+#include "nsIDTD.h"
+
+class nsIScriptSecurityManager;
+class nsAttrName;
+class nsXULPrototypeDocument;
+class nsXULPrototypeElement;
+class nsXULPrototypeNode;
+
+class XULContentSinkImpl final : public nsIXMLContentSink, public nsIExpatSink {
+ public:
+ XULContentSinkImpl();
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIEXPATSINK
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(XULContentSinkImpl,
+ nsIXMLContentSink)
+
+ // nsIContentSink
+ NS_IMETHOD WillParse(void) override { return NS_OK; }
+ NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode) override;
+ NS_IMETHOD DidBuildModel(bool aTerminated) override;
+ NS_IMETHOD WillInterrupt(void) override;
+ NS_IMETHOD WillResume(void) override;
+ NS_IMETHOD SetParser(nsParserBase* aParser) override;
+ virtual void FlushPendingNotifications(mozilla::FlushType aType) override {}
+ virtual void SetDocumentCharset(NotNull<const Encoding*> aEncoding) override;
+ virtual nsISupports* GetTarget() override;
+
+ /**
+ * Initialize the content sink, giving it a document with which to communicate
+ * with the outside world, and an nsXULPrototypeDocument to build.
+ */
+ nsresult Init(mozilla::dom::Document* aDocument,
+ nsXULPrototypeDocument* aPrototype);
+
+ protected:
+ virtual ~XULContentSinkImpl();
+
+ // pseudo-constants
+ char16_t* mText;
+ int32_t mTextLength;
+ int32_t mTextSize;
+ bool mConstrainSize;
+
+ nsresult AddAttributes(const char16_t** aAttributes, const uint32_t aAttrLen,
+ nsXULPrototypeElement* aElement);
+
+ nsresult OpenRoot(const char16_t** aAttributes, const uint32_t aAttrLen,
+ mozilla::dom::NodeInfo* aNodeInfo);
+
+ nsresult OpenTag(const char16_t** aAttributes, const uint32_t aAttrLen,
+ const uint32_t aLineNumber,
+ mozilla::dom::NodeInfo* aNodeInfo);
+
+ // If OpenScript returns NS_OK and after it returns our state is eInScript,
+ // that means that we created a prototype script and stuck it on
+ // mContextStack. If NS_OK is returned but the state is still
+ // eInDocumentElement then we didn't create a prototype script (e.g. the
+ // script had an unknown type), and the caller should create a prototype
+ // element.
+ nsresult OpenScript(const char16_t** aAttributes, const uint32_t aLineNumber);
+
+ static bool IsDataInBuffer(char16_t* aBuffer, int32_t aLength);
+
+ // Text management
+ nsresult FlushText(bool aCreateTextNode = true);
+ nsresult AddText(const char16_t* aText, int32_t aLength);
+
+ RefPtr<nsNodeInfoManager> mNodeInfoManager;
+
+ nsresult NormalizeAttributeString(const char16_t* aExpatName,
+ nsAttrName& aName);
+
+ public:
+ enum State { eInProlog, eInDocumentElement, eInScript, eInEpilog };
+
+ protected:
+ State mState;
+
+ // content stack management
+ class ContextStack {
+ protected:
+ struct Entry {
+ RefPtr<nsXULPrototypeNode> mNode;
+ // a LOT of nodes have children; preallocate for 8
+ nsPrototypeArray mChildren;
+ State mState;
+ Entry* mNext;
+ Entry(RefPtr<nsXULPrototypeNode>&& aNode, State aState, Entry* aNext)
+ : mNode(std::move(aNode)),
+ mChildren(8),
+ mState(aState),
+ mNext(aNext) {}
+ };
+
+ Entry* mTop;
+ int32_t mDepth;
+
+ public:
+ ContextStack();
+ ~ContextStack();
+
+ int32_t Depth() { return mDepth; }
+
+ void Push(RefPtr<nsXULPrototypeNode>&& aNode, State aState);
+ nsresult Pop(State* aState);
+
+ nsresult GetTopNode(RefPtr<nsXULPrototypeNode>& aNode);
+ nsresult GetTopChildren(nsPrototypeArray** aChildren);
+
+ void Clear();
+
+ void Traverse(nsCycleCollectionTraversalCallback& aCallback);
+ };
+
+ friend class ContextStack;
+ ContextStack mContextStack;
+
+ nsWeakPtr mDocument; // [OWNER]
+ nsCOMPtr<nsIURI> mDocumentURL; // [OWNER]
+
+ RefPtr<nsXULPrototypeDocument> mPrototype; // [OWNER]
+
+ RefPtr<nsParserBase> mParser;
+ nsCOMPtr<nsIScriptSecurityManager> mSecMan;
+};
+
+#endif /* nsXULContentSink_h__ */
diff --git a/dom/xul/nsXULContentUtils.cpp b/dom/xul/nsXULContentUtils.cpp
new file mode 100644
index 0000000000..0bad4ab3b6
--- /dev/null
+++ b/dom/xul/nsXULContentUtils.cpp
@@ -0,0 +1,73 @@
+/* -*- 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/. */
+
+/*
+
+ A package of routines shared by the XUL content code.
+
+ */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsCollationCID.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIContent.h"
+#include "nsICollation.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "nsXULContentUtils.h"
+#include "nsLayoutCID.h"
+#include "nsString.h"
+#include "nsGkAtoms.h"
+
+using namespace mozilla;
+
+//------------------------------------------------------------------------
+
+nsICollation* nsXULContentUtils::gCollation;
+
+//------------------------------------------------------------------------
+// Constructors n' stuff
+//
+
+nsresult nsXULContentUtils::Finish() {
+ NS_IF_RELEASE(gCollation);
+
+ return NS_OK;
+}
+
+nsICollation* nsXULContentUtils::GetCollation() {
+ if (!gCollation) {
+ nsCOMPtr<nsICollationFactory> colFactory =
+ do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
+ if (colFactory) {
+ DebugOnly<nsresult> rv = colFactory->CreateCollation(&gCollation);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "couldn't create collation instance");
+ } else
+ NS_ERROR("couldn't create instance of collation factory");
+ }
+
+ return gCollation;
+}
+
+//------------------------------------------------------------------------
+//
+
+nsresult nsXULContentUtils::FindChildByTag(nsIContent* aElement,
+ int32_t aNameSpaceID, nsAtom* aTag,
+ mozilla::dom::Element** aResult) {
+ for (nsIContent* child = aElement->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsElement() && child->NodeInfo()->Equals(aTag, aNameSpaceID)) {
+ NS_ADDREF(*aResult = child->AsElement());
+ return NS_OK;
+ }
+ }
+
+ *aResult = nullptr;
+ return NS_RDF_NO_VALUE; // not found
+}
diff --git a/dom/xul/nsXULContentUtils.h b/dom/xul/nsXULContentUtils.h
new file mode 100644
index 0000000000..9f96811d72
--- /dev/null
+++ b/dom/xul/nsXULContentUtils.h
@@ -0,0 +1,45 @@
+/* -*- 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/. */
+
+/*
+
+ A package of routines shared by the XUL content code.
+
+ */
+
+#ifndef nsXULContentUtils_h__
+#define nsXULContentUtils_h__
+
+#include "nsISupports.h"
+
+class nsAtom;
+class nsICollation;
+class nsIContent;
+
+namespace mozilla {
+namespace dom {
+class Element;
+}
+} // namespace mozilla
+
+class nsXULContentUtils {
+ protected:
+ static nsICollation* gCollation;
+
+ static bool gDisableXULCache;
+
+ static int DisableXULCacheChangedCallback(const char* aPrefName,
+ void* aClosure);
+
+ public:
+ static nsresult Finish();
+
+ static nsresult FindChildByTag(nsIContent* aElement, int32_t aNameSpaceID,
+ nsAtom* aTag, mozilla::dom::Element** aResult);
+
+ static nsICollation* GetCollation();
+};
+
+#endif // nsXULContentUtils_h__
diff --git a/dom/xul/nsXULControllers.cpp b/dom/xul/nsXULControllers.cpp
new file mode 100644
index 0000000000..3e96fd6e41
--- /dev/null
+++ b/dom/xul/nsXULControllers.cpp
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+/*
+
+ This file provides the implementation for the XUL "controllers"
+ object.
+
+*/
+
+#include "nsString.h"
+
+#include "nsIControllers.h"
+#include "nsXULControllers.h"
+#include "nsIController.h"
+#include "mozilla/RefPtr.h"
+
+//----------------------------------------------------------------------
+
+nsXULControllerData::nsXULControllerData(uint32_t inControllerID,
+ nsIController* inController)
+ : mControllerID(inControllerID), mController(inController) {}
+
+nsresult nsXULControllerData::GetController(nsIController** outController) {
+ NS_IF_ADDREF(*outController = mController);
+ return NS_OK;
+}
+
+nsXULControllers::nsXULControllers() : mCurControllerID(0) {}
+
+nsXULControllers::~nsXULControllers(void) { DeleteControllers(); }
+
+void nsXULControllers::DeleteControllers() {
+ uint32_t count = mControllers.Length();
+ for (uint32_t i = 0; i < count; i++) {
+ nsXULControllerData* controllerData = mControllers.ElementAt(i);
+ delete controllerData; // releases the nsIController
+ }
+
+ mControllers.Clear();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULControllers)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULControllers)
+ tmp->DeleteControllers();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULControllers)
+ {
+ uint32_t i, count = tmp->mControllers.Length();
+ for (i = 0; i < count; ++i) {
+ nsXULControllerData* controllerData = tmp->mControllers[i];
+ if (controllerData) {
+ cb.NoteXPCOMChild(controllerData->mController);
+ }
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULControllers)
+ NS_INTERFACE_MAP_ENTRY(nsIControllers)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIControllers)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULControllers)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULControllers)
+
+NS_IMETHODIMP
+nsXULControllers::GetControllerForCommand(const char* aCommand,
+ nsIController** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ uint32_t count = mControllers.Length();
+ for (uint32_t i = 0; i < count; i++) {
+ nsXULControllerData* controllerData = mControllers.ElementAt(i);
+ if (controllerData) {
+ nsCOMPtr<nsIController> controller;
+ controllerData->GetController(getter_AddRefs(controller));
+ if (controller) {
+ bool supportsCommand;
+ controller->SupportsCommand(aCommand, &supportsCommand);
+ if (supportsCommand) {
+ controller.forget(_retval);
+ return NS_OK;
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULControllers::InsertControllerAt(uint32_t aIndex,
+ nsIController* controller) {
+ nsXULControllerData* controllerData =
+ new nsXULControllerData(++mCurControllerID, controller);
+#ifdef DEBUG
+ nsXULControllerData** inserted =
+#endif
+ mControllers.InsertElementAt(aIndex, controllerData);
+ NS_ASSERTION(inserted != nullptr, "Insertion of controller failed");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULControllers::RemoveControllerAt(uint32_t aIndex, nsIController** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ nsXULControllerData* controllerData = mControllers.SafeElementAt(aIndex);
+ if (!controllerData) return NS_ERROR_FAILURE;
+
+ mControllers.RemoveElementAt(aIndex);
+
+ controllerData->GetController(_retval);
+ delete controllerData;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULControllers::GetControllerAt(uint32_t aIndex, nsIController** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ nsXULControllerData* controllerData = mControllers.SafeElementAt(aIndex);
+ if (!controllerData) return NS_ERROR_FAILURE;
+
+ return controllerData->GetController(_retval); // does the addref
+}
+
+NS_IMETHODIMP
+nsXULControllers::AppendController(nsIController* controller) {
+ // This assigns controller IDs starting at 1 so we can use 0 to test if an ID
+ // was obtained
+ nsXULControllerData* controllerData =
+ new nsXULControllerData(++mCurControllerID, controller);
+
+#ifdef DEBUG
+ nsXULControllerData** appended =
+#endif
+ mControllers.AppendElement(controllerData);
+ NS_ASSERTION(appended != nullptr, "Appending controller failed");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULControllers::RemoveController(nsIController* controller) {
+ // first get the identity pointer
+ nsCOMPtr<nsISupports> controllerSup(do_QueryInterface(controller));
+ // then find it
+ uint32_t count = mControllers.Length();
+ for (uint32_t i = 0; i < count; i++) {
+ nsXULControllerData* controllerData = mControllers.ElementAt(i);
+ if (controllerData) {
+ nsCOMPtr<nsIController> thisController;
+ controllerData->GetController(getter_AddRefs(thisController));
+ nsCOMPtr<nsISupports> thisControllerSup(
+ do_QueryInterface(thisController)); // get identity
+ if (thisControllerSup == controllerSup) {
+ mControllers.RemoveElementAt(i);
+ delete controllerData;
+ return NS_OK;
+ }
+ }
+ }
+ return NS_ERROR_FAILURE; // right thing to return if no controller found?
+}
+
+NS_IMETHODIMP
+nsXULControllers::GetControllerId(nsIController* controller,
+ uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ uint32_t count = mControllers.Length();
+ for (uint32_t i = 0; i < count; i++) {
+ nsXULControllerData* controllerData = mControllers.ElementAt(i);
+ if (controllerData) {
+ nsCOMPtr<nsIController> thisController;
+ controllerData->GetController(getter_AddRefs(thisController));
+ if (thisController.get() == controller) {
+ *_retval = controllerData->GetControllerID();
+ return NS_OK;
+ }
+ }
+ }
+ return NS_ERROR_FAILURE; // none found
+}
+
+NS_IMETHODIMP
+nsXULControllers::GetControllerById(uint32_t controllerID,
+ nsIController** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ uint32_t count = mControllers.Length();
+ for (uint32_t i = 0; i < count; i++) {
+ nsXULControllerData* controllerData = mControllers.ElementAt(i);
+ if (controllerData && controllerData->GetControllerID() == controllerID) {
+ return controllerData->GetController(_retval);
+ }
+ }
+ return NS_ERROR_FAILURE; // none found
+}
+
+NS_IMETHODIMP
+nsXULControllers::GetControllerCount(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mControllers.Length();
+ return NS_OK;
+}
diff --git a/dom/xul/nsXULControllers.h b/dom/xul/nsXULControllers.h
new file mode 100644
index 0000000000..495cf64e71
--- /dev/null
+++ b/dom/xul/nsXULControllers.h
@@ -0,0 +1,51 @@
+/* -*- 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/. */
+
+/*
+
+ The XUL "controllers" object.
+
+*/
+
+#ifndef nsXULControllers_h__
+#define nsXULControllers_h__
+
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsIControllers.h"
+#include "nsCycleCollectionParticipant.h"
+
+/* non-XPCOM class for holding controllers and their IDs */
+class nsXULControllerData final {
+ public:
+ nsXULControllerData(uint32_t inControllerID, nsIController* inController);
+ ~nsXULControllerData() = default;
+
+ uint32_t GetControllerID() { return mControllerID; }
+
+ nsresult GetController(nsIController** outController);
+
+ uint32_t mControllerID;
+ nsCOMPtr<nsIController> mController;
+};
+
+class nsXULControllers final : public nsIControllers {
+ public:
+ nsXULControllers();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsXULControllers, nsIControllers)
+ NS_DECL_NSICONTROLLERS
+
+ protected:
+ virtual ~nsXULControllers(void);
+
+ void DeleteControllers();
+
+ nsTArray<nsXULControllerData*> mControllers;
+ uint32_t mCurControllerID;
+};
+
+#endif // nsXULControllers_h__
diff --git a/dom/xul/nsXULElement.cpp b/dom/xul/nsXULElement.cpp
new file mode 100644
index 0000000000..725f09125d
--- /dev/null
+++ b/dom/xul/nsXULElement.cpp
@@ -0,0 +1,1974 @@
+/* -*- 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 "nsXULElement.h"
+
+#include <new>
+#include <utility>
+#include "AttrArray.h"
+#include "MainThreadUtils.h"
+#include "ReferrerInfo.h"
+#include "Units.h"
+#include "XULFrameElement.h"
+#include "XULMenuElement.h"
+#include "XULPopupElement.h"
+#include "XULTextElement.h"
+#include "XULTooltipElement.h"
+#include "XULTreeElement.h"
+#include "js/CompilationAndEvaluation.h"
+#include "js/CompileOptions.h"
+#include "js/OffThreadScriptCompilation.h"
+#include "js/SourceText.h"
+#include "js/Utility.h"
+#include "jsapi.h"
+#include "mozilla/ArrayIterator.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/FlushType.h"
+#include "mozilla/GlobalKeyListener.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticAnalysisFunctions.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/URLExtraData.h"
+#include "mozilla/dom/BindContext.h"
+#include "mozilla/dom/BorrowedAttrInfo.h"
+#include "mozilla/dom/CSSRuleBinding.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/FragmentOrElement.h"
+#include "mozilla/dom/FromParser.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/dom/MutationEventBinding.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/ReferrerPolicyBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/XULBroadcastManager.h"
+#include "mozilla/dom/XULCommandEvent.h"
+#include "mozilla/dom/XULElementBinding.h"
+#include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/fallible.h"
+#include "nsAtom.h"
+#include "nsAttrValueInlines.h"
+#include "nsAttrValueOrString.h"
+#include "nsCaseTreatment.h"
+#include "nsChangeHint.h"
+#include "nsCOMPtr.h"
+#include "nsCompatibility.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionNoteChild.h"
+#include "nsCycleCollectionTraversalCallback.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsFocusManager.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIControllers.h"
+#include "nsID.h"
+#include "nsIDOMEventListener.h"
+#include "nsIDOMXULControlElement.h"
+#include "nsIDOMXULSelectCntrlItemEl.h"
+#include "nsIDocShell.h"
+#include "nsIFocusManager.h"
+#include "nsIFrame.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIRunnable.h"
+#include "nsIScriptContext.h"
+#include "nsISupportsUtils.h"
+#include "nsIURI.h"
+#include "nsIXPConnect.h"
+#include "nsMenuFrame.h"
+#include "nsMenuPopupFrame.h"
+#include "nsNodeInfoManager.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIDOMWindowInlines.h"
+#include "nsPresContext.h"
+#include "nsQueryFrame.h"
+#include "nsString.h"
+#include "nsStyledElement.h"
+#include "nsThreadUtils.h"
+#include "nsXULControllers.h"
+#include "nsXULPopupListener.h"
+#include "nsXULPopupManager.h"
+#include "nsXULPrototypeCache.h"
+#include "nsXULTooltipListener.h"
+#include "xpcpublic.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)) {
+ XUL_PROTOTYPE_ATTRIBUTE_METER(gNumElements);
+}
+
+nsXULElement::~nsXULElement() = default;
+
+void nsXULElement::MaybeUpdatePrivateLifetime() {
+ if (AttrValueIs(kNameSpaceID_None, nsGkAtoms::windowtype,
+ u"navigator:browser"_ns, eCaseMatters) ||
+ AttrValueIs(kNameSpaceID_None, nsGkAtoms::windowtype,
+ u"navigator:geckoview"_ns, 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) {
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo(std::move(aNodeInfo));
+ auto* nim = nodeInfo->NodeInfoManager();
+ return new (nim) nsXULElement(nodeInfo.forget());
+}
+
+/* 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)) {
+ auto* nim = nodeInfo->NodeInfoManager();
+ return new (nim) 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)) {
+ auto* nim = nodeInfo->NodeInfoManager();
+ return new (nim) XULFrameElement(nodeInfo.forget());
+ }
+
+ if (nodeInfo->Equals(nsGkAtoms::menu) ||
+ nodeInfo->Equals(nsGkAtoms::menulist)) {
+ auto* nim = nodeInfo->NodeInfoManager();
+ return new (nim) XULMenuElement(nodeInfo.forget());
+ }
+
+ if (nodeInfo->Equals(nsGkAtoms::tree)) {
+ auto* nim = nodeInfo->NodeInfoManager();
+ return new (nim) XULTreeElement(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 (const auto& attribute : aPrototype->mAttributes) {
+ element->AddListenerForAttributeIfNeeded(attribute.mName);
+ }
+ }
+
+ if (aIsRoot && aPrototype->mNodeInfo->Equals(nsGkAtoms::window)) {
+ for (size_t i = 0; i < aPrototype->mAttributes.Length(); ++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_INHERITED(nsXULElement, nsStyledElement)
+
+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 = const_cast<nsXULElement*>(this)->CopyInnerTo(
+ element, ReparseAttributes::No);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Note that we're _not_ copying mControllers.
+
+ 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::IsTopLevelRemoteTarget(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) {
+ Maybe<int32_t> attrVal = GetTabIndexAttrValue();
+ if (attrVal.isSome()) {
+ // The tabindex attribute was specified, so the element becomes
+ // focusable.
+ shouldFocus = true;
+ *aTabIndex = attrVal.value();
+ } 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;
+}
+
+int32_t nsXULElement::ScreenX() {
+ nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
+ return frame ? frame->GetScreenRect().x : 0;
+}
+
+int32_t nsXULElement::ScreenY() {
+ nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
+ return frame ? frame->GetScreenRect().y : 0;
+}
+
+bool nsXULElement::HasMenu() {
+ nsMenuFrame* menu = do_QueryFrame(GetPrimaryFrame(FlushType::Frames));
+ return !!menu;
+}
+
+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)) {
+ if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
+ 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->IsXULElement(nsGkAtoms::menulist)) {
+ elm->ClickWithInputSource(MouseEvent_Binding::MOZ_SOURCE_KEYBOARD,
+ aIsTrustedEvent);
+ }
+ } else {
+ return content->PerformAccesskey(aKeyCausesActivation, aIsTrustedEvent);
+ }
+
+ return focused;
+}
+
+//----------------------------------------------------------------------
+
+void nsXULElement::AddListenerForAttributeIfNeeded(nsAtom* aLocalName) {
+ // 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 (aLocalName == nsGkAtoms::menu || aLocalName == nsGkAtoms::contextmenu ||
+ // XXXdwh popup and context are deprecated
+ aLocalName == nsGkAtoms::popup || aLocalName == nsGkAtoms::context) {
+ AddPopupListener(aLocalName);
+ }
+ if (nsContentUtils::IsEventAttributeName(aLocalName, EventNameType_XUL)) {
+ nsAutoString value;
+ GetAttr(kNameSpaceID_None, aLocalName, value);
+ SetEventHandler(aLocalName, value, true);
+ }
+}
+
+void nsXULElement::AddListenerForAttributeIfNeeded(const nsAttrName& aName) {
+ if (aName.IsAtom()) {
+ AddListenerForAttributeIfNeeded(aName.Atom());
+ }
+}
+
+//----------------------------------------------------------------------
+//
+// 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(DeprecatedOperations::eImportXULIntoContent,
+ false);
+ return NS_OK;
+ }
+
+ private:
+ OwningNonNull<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(BindContext& aContext, nsINode& aParent) {
+ nsresult rv = nsStyledElement::BindToTree(aContext, aParent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!IsInComposedDoc()) {
+ return rv;
+ }
+
+ Document& doc = aContext.OwnerDoc();
+ if (!IsInNativeAnonymousSubtree() && !doc.AllowXULXBL() &&
+ !doc.HasWarnedAbout(DeprecatedOperations::eImportXULIntoContent)) {
+ nsContentUtils::AddScriptRunner(new XULInContentErrorReporter(doc));
+ }
+
+#ifdef DEBUG
+ if (!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::resizer || tag == nsGkAtoms::label,
+ "Unexpected XUL element in non-XUL doc");
+ }
+#endif
+
+ // Within Bug 1492063 and its dependencies we started to apply a
+ // CSP to system privileged about pages. Since some about: pages
+ // are implemented in *.xul files we added this workaround to
+ // apply a CSP to them. To do so, we check the introduced custom
+ // attribute 'csp' on the root element.
+ if (doc.GetRootElement() == this) {
+ nsAutoString cspPolicyStr;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::csp, cspPolicyStr);
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIContentSecurityPolicy> docCSP = doc.GetCsp();
+ uint32_t policyCount = 0;
+ if (docCSP) {
+ docCSP->GetPolicyCount(&policyCount);
+ }
+ MOZ_ASSERT(policyCount == 0, "how come we already have a policy?");
+ }
+#endif
+
+ CSP_ApplyMetaCSPToDoc(doc, cspPolicyStr);
+ }
+
+ if (NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
+ // Create our XUL key listener and hook it up.
+ XULKeySetGlobalKeyListener::AttachKeyHandler(this);
+ }
+
+ if (NeedTooltipSupport(*this)) {
+ AddTooltipSupport();
+ }
+
+ if (XULBroadcastManager::MayNeedListener(*this)) {
+ if (!doc.HasXULBroadcastManager()) {
+ doc.InitializeXULBroadcastManager();
+ }
+ XULBroadcastManager* broadcastManager = doc.GetXULBroadcastManager();
+ broadcastManager->AddListener(this);
+ }
+ return rv;
+}
+
+void nsXULElement::UnbindFromTree(bool aNullParent) {
+ if (NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
+ XULKeySetGlobalKeyListener::DetachKeyHandler(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(aNullParent);
+}
+
+void nsXULElement::DoneAddingChildren(bool aHaveNotified) {
+ if (IsXULElement(nsGkAtoms::linkset)) {
+ Document* doc = GetComposedDoc();
+ if (doc) {
+ doc->OnL10nResourceContainerParsed();
+ }
+ }
+}
+
+void nsXULElement::UnregisterAccessKey(const nsAString& aOldValue) {
+ // If someone changes the accesskey, unregister the old one
+ //
+ Document* doc = GetComposedDoc();
+ if (doc && !aOldValue.IsEmpty()) {
+ if (PresShell* presShell = doc->GetPresShell()) {
+ presShell->GetPresContext()->EventStateManager()->UnregisterAccessKey(
+ this, 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) {
+ AddListenerForAttributeIfNeeded(aName);
+ }
+
+ 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(), MOZ_KnownLive(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.
+ if (aVisitor.mDOMEvent && aVisitor.mDOMEvent->AsXULCommandEvent() &&
+ HasNonEmptyAttr(nsGkAtoms::command)) {
+ // 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 {
+ if (aAttribute == nsGkAtoms::value &&
+ (aModType == MutationEvent_Binding::REMOVAL ||
+ aModType == MutationEvent_Binding::ADDITION) &&
+ 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.)
+ return nsChangeHint_ReconstructFrame;
+ }
+
+ if (aAttribute == nsGkAtoms::type &&
+ IsAnyOfXULElements(nsGkAtoms::toolbarbutton, nsGkAtoms::button)) {
+ // type=menu switches from a button frame to a menu frame.
+ return nsChangeHint_ReconstructFrame;
+ }
+
+ return nsChangeHint(0);
+}
+
+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();
+}
+
+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.mInputSource = eventUp.mInputSource = eventClick.mInputSource =
+ 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) {
+ RefPtr<nsXULElement> self = this;
+ nsContentUtils::DispatchXULCommand(self, 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, u"contextmenu"_ns,
+ TrustedEventsAtSystemGroupBubble());
+ } else {
+ manager->AddEventListenerByType(listener, u"mousedown"_ns,
+ TrustedEventsAtSystemGroupBubble());
+ }
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+
+nsresult nsXULElement::MakeHeavyweight(nsXULPrototypeElement* aPrototype) {
+ if (!aPrototype) {
+ return NS_OK;
+ }
+
+ size_t i;
+ nsresult rv;
+ for (i = 0; i < aPrototype->mAttributes.Length(); ++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;
+}
+
+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() const {
+ return IsXULElement(nsGkAtoms::menupopup) ||
+ Element::IsInteractiveHTMLContent();
+}
+
+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));
+ size_t i;
+ for (i = 0; i < elem->mAttributes.Length(); ++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(mAttributes.Length());
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ nsAutoString attributeValue;
+ size_t i;
+ for (i = 0; i < mAttributes.Length(); ++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;
+ int32_t attributes = int32_t(number);
+
+ if (attributes > 0) {
+ mAttributes.AppendElements(attributes);
+
+ nsAutoString attributeValue;
+ for (size_t i = 0; i < mAttributes.Length(); ++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 = std::move(script);
+ 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 < mAttributes.Length(), "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
+ auto referrerInfo =
+ MakeRefPtr<ReferrerInfo>(aDocumentURI, ReferrerPolicy::_empty);
+ auto data = MakeRefPtr<URLExtraData>(aDocumentURI, referrerInfo, principal);
+ RefPtr<DeclarationBlock> declaration = DeclarationBlock::FromCssText(
+ aValue, data, eCompatibility_FullStandards, nullptr,
+ CSSRule_Binding::STYLE_RULE);
+ 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() {
+ mAttributes.Clear();
+ 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) {
+ if (!mSrcURI->SchemeIs("chrome"))
+ // Don't cache scripts that don't come from chrome uris.
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ 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;
+ nsresult 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;
+}
+
+void nsXULPrototypeScript::FillCompileOptions(JS::CompileOptions& options) {
+ // 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);
+}
+
+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::CompileOptions options(cx);
+ FillCompileOptions(options);
+
+ JS::Rooted<JSScript*> newScriptObject(cx);
+ rv = nsContentUtils::XPConnect()->ReadScript(aStream, cx, options,
+ 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 PrototypeDocumentContentSink::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 && mSrcURI->SchemeIs("chrome")) {
+ 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 =
+ std::move((*sReceivers)[index]);
+ 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);
+ FillCompileOptions(options);
+ options.setIntroductionType(mOutOfLine ? "srcScript" : "inlineScript")
+ .setFileAndLine(urlspec.get(), mOutOfLine ? 1 : aLineNo);
+
+ JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
+
+ 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, JS::Compile(cx, options, srcBuf));
+ if (!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;
+}
diff --git a/dom/xul/nsXULElement.h b/dom/xul/nsXULElement.h
new file mode 100644
index 0000000000..ec1e5fc09a
--- /dev/null
+++ b/dom/xul/nsXULElement.h
@@ -0,0 +1,592 @@
+/* -*- 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/. */
+
+/*
+
+ The base XUL element class and associates.
+
+*/
+
+#ifndef nsXULElement_h__
+#define nsXULElement_h__
+
+#include <stdint.h>
+#include <stdio.h>
+#include "ErrorList.h"
+#include "js/RootingAPI.h"
+#include "js/SourceText.h"
+#include "js/TracingAPI.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/DOMString.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/FragmentOrElement.h"
+#include "mozilla/dom/FromParser.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsAtom.h"
+#include "nsAttrName.h"
+#include "nsAttrValue.h"
+#include "nsCOMPtr.h"
+#include "nsCaseTreatment.h"
+#include "nsChangeHint.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsINode.h"
+#include "nsISupports.h"
+#include "nsLiteralString.h"
+#include "nsString.h"
+#include "nsStyledElement.h"
+#include "nsTArray.h"
+#include "nsTLiteralString.h"
+#include "nscore.h"
+
+class JSObject;
+class JSScript;
+class nsAttrValueOrString;
+class nsIControllers;
+class nsIObjectInputStream;
+class nsIObjectOutputStream;
+class nsIOffThreadScriptReceiver;
+class nsIPrincipal;
+class nsIURI;
+class nsXULPrototypeDocument;
+class nsXULPrototypeNode;
+struct JSContext;
+
+typedef nsTArray<RefPtr<nsXULPrototypeNode>> nsPrototypeArray;
+
+namespace JS {
+class CompileOptions;
+}
+
+namespace mozilla {
+class ErrorResult;
+class EventChainPreVisitor;
+class EventListenerManager;
+namespace css {
+class StyleRule;
+} // namespace css
+namespace dom {
+class Document;
+class HTMLIFrameElement;
+class PrototypeDocumentContentSink;
+enum class CallerType : uint32_t;
+} // namespace dom
+} // namespace mozilla
+
+////////////////////////////////////////////////////////////////////////
+
+#ifdef XUL_PROTOTYPE_ATTRIBUTE_METERING
+# define XUL_PROTOTYPE_ATTRIBUTE_METER(counter) \
+ (nsXULPrototypeAttribute::counter++)
+#else
+# define XUL_PROTOTYPE_ATTRIBUTE_METER(counter) ((void)0)
+#endif
+
+/**
+
+ A prototype attribute for an nsXULPrototypeElement.
+
+ */
+
+class nsXULPrototypeAttribute {
+ public:
+ nsXULPrototypeAttribute()
+ : mName(nsGkAtoms::id) // XXX this is a hack, but names have to have a
+ // value
+ {
+ XUL_PROTOTYPE_ATTRIBUTE_METER(gNumAttributes);
+ MOZ_COUNT_CTOR(nsXULPrototypeAttribute);
+ }
+
+ ~nsXULPrototypeAttribute();
+
+ nsAttrName mName;
+ nsAttrValue mValue;
+
+#ifdef XUL_PROTOTYPE_ATTRIBUTE_METERING
+ static uint32_t gNumElements;
+ static uint32_t gNumAttributes;
+ static uint32_t gNumCacheTests;
+ static uint32_t gNumCacheHits;
+ static uint32_t gNumCacheSets;
+ static uint32_t gNumCacheFills;
+#endif /* !XUL_PROTOTYPE_ATTRIBUTE_METERING */
+};
+
+/**
+
+ A prototype content model element that holds the "primordial" values
+ that have been parsed from the original XUL document.
+
+ */
+
+class nsXULPrototypeNode {
+ public:
+ enum Type { eType_Element, eType_Script, eType_Text, eType_PI };
+
+ Type mType;
+
+ virtual nsresult Serialize(
+ nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) = 0;
+ virtual nsresult Deserialize(
+ nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
+ nsIURI* aDocumentURI,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) = 0;
+
+ /**
+ * The prototype document must call ReleaseSubtree when it is going
+ * away. This makes the parents through the tree stop owning their
+ * children, whether or not the parent's reference count is zero.
+ * Individual elements may still own individual prototypes, but
+ * those prototypes no longer remember their children to allow them
+ * to be constructed.
+ */
+ virtual void ReleaseSubtree() {}
+
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(nsXULPrototypeNode)
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsXULPrototypeNode)
+
+ protected:
+ explicit nsXULPrototypeNode(Type aType) : mType(aType) {}
+ virtual ~nsXULPrototypeNode() = default;
+};
+
+class nsXULPrototypeElement : public nsXULPrototypeNode {
+ public:
+ explicit nsXULPrototypeElement(mozilla::dom::NodeInfo* aNodeInfo = nullptr)
+ : nsXULPrototypeNode(eType_Element),
+ mNodeInfo(aNodeInfo),
+ mHasIdAttribute(false),
+ mHasClassAttribute(false),
+ mHasStyleAttribute(false),
+ mIsAtom(nullptr) {}
+
+ private:
+ virtual ~nsXULPrototypeElement() { Unlink(); }
+
+ public:
+ virtual void ReleaseSubtree() override {
+ for (int32_t i = mChildren.Length() - 1; i >= 0; i--) {
+ if (mChildren[i].get()) mChildren[i]->ReleaseSubtree();
+ }
+ mChildren.Clear();
+ nsXULPrototypeNode::ReleaseSubtree();
+ }
+
+ virtual nsresult Serialize(
+ nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) override;
+ virtual nsresult Deserialize(
+ nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
+ nsIURI* aDocumentURI,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) override;
+
+ nsresult SetAttrAt(uint32_t aPos, const nsAString& aValue,
+ nsIURI* aDocumentURI);
+
+ void Unlink();
+
+ // Trace all scripts held by this element and its children.
+ void TraceAllScripts(JSTracer* aTrc);
+
+ nsPrototypeArray mChildren;
+
+ RefPtr<mozilla::dom::NodeInfo> mNodeInfo;
+
+ uint32_t mHasIdAttribute : 1;
+ uint32_t mHasClassAttribute : 1;
+ uint32_t mHasStyleAttribute : 1;
+ nsTArray<nsXULPrototypeAttribute> mAttributes; // [OWNER]
+ RefPtr<nsAtom> mIsAtom;
+};
+
+class nsXULPrototypeScript : public nsXULPrototypeNode {
+ public:
+ explicit nsXULPrototypeScript(uint32_t aLineNo);
+
+ private:
+ virtual ~nsXULPrototypeScript();
+
+ void FillCompileOptions(JS::CompileOptions& options);
+
+ public:
+ virtual nsresult Serialize(
+ nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) override;
+ nsresult SerializeOutOfLine(nsIObjectOutputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc);
+ virtual nsresult Deserialize(
+ nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
+ nsIURI* aDocumentURI,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) override;
+ nsresult DeserializeOutOfLine(nsIObjectInputStream* aInput,
+ nsXULPrototypeDocument* aProtoDoc);
+
+ nsresult Compile(const char16_t* aText, size_t aTextLength,
+ JS::SourceOwnership aOwnership, nsIURI* aURI,
+ uint32_t aLineNo, mozilla::dom::Document* aDocument,
+ nsIOffThreadScriptReceiver* aOffThreadReceiver = nullptr);
+
+ void UnlinkJSObjects();
+
+ void Set(JSScript* aObject);
+
+ bool HasScriptObject() {
+ // Conversion to bool doesn't trigger mScriptObject's read barrier.
+ return mScriptObject;
+ }
+
+ JSScript* GetScriptObject() { return mScriptObject; }
+
+ void TraceScriptObject(JSTracer* aTrc) {
+ JS::TraceEdge(aTrc, &mScriptObject, "active window XUL prototype script");
+ }
+
+ void Trace(const TraceCallbacks& aCallbacks, void* aClosure) {
+ if (mScriptObject) {
+ aCallbacks.Trace(&mScriptObject, "mScriptObject", aClosure);
+ }
+ }
+
+ nsCOMPtr<nsIURI> mSrcURI;
+ uint32_t mLineNo;
+ bool mSrcLoading;
+ bool mOutOfLine;
+ mozilla::dom::PrototypeDocumentContentSink*
+ mSrcLoadWaiters; // [OWNER] but not COMPtr
+ private:
+ JS::Heap<JSScript*> mScriptObject;
+};
+
+class nsXULPrototypeText : public nsXULPrototypeNode {
+ public:
+ nsXULPrototypeText() : nsXULPrototypeNode(eType_Text) {}
+
+ private:
+ virtual ~nsXULPrototypeText() = default;
+
+ public:
+ virtual nsresult Serialize(
+ nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) override;
+ virtual nsresult Deserialize(
+ nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
+ nsIURI* aDocumentURI,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) override;
+
+ nsString mValue;
+};
+
+class nsXULPrototypePI : public nsXULPrototypeNode {
+ public:
+ nsXULPrototypePI() : nsXULPrototypeNode(eType_PI) {}
+
+ private:
+ virtual ~nsXULPrototypePI() = default;
+
+ public:
+ virtual nsresult Serialize(
+ nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) override;
+ virtual nsresult Deserialize(
+ nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
+ nsIURI* aDocumentURI,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) override;
+
+ nsString mTarget;
+ nsString mData;
+};
+
+////////////////////////////////////////////////////////////////////////
+
+/**
+
+ The XUL element.
+
+ */
+
+#define XUL_ELEMENT_FLAG_BIT(n_) \
+ NODE_FLAG_BIT(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + (n_))
+
+// XUL element specific bits
+enum {
+ XUL_ELEMENT_HAS_CONTENTMENU_LISTENER = XUL_ELEMENT_FLAG_BIT(0),
+ XUL_ELEMENT_HAS_POPUP_LISTENER = XUL_ELEMENT_FLAG_BIT(1)
+};
+
+ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 2);
+
+#undef XUL_ELEMENT_FLAG_BIT
+
+class nsXULElement : public nsStyledElement {
+ protected:
+ typedef mozilla::dom::Document Document;
+
+ // Use Construct to construct elements instead of this constructor.
+ explicit nsXULElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+ public:
+ using Element::Blur;
+ using Element::Focus;
+
+ static nsresult CreateFromPrototype(nsXULPrototypeElement* aPrototype,
+ Document* aDocument, bool aIsScriptable,
+ bool aIsRoot,
+ mozilla::dom::Element** aResult);
+
+ // This is the constructor for nsXULElements.
+ static nsXULElement* Construct(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+ NS_IMPL_FROMNODE(nsXULElement, kNameSpaceID_XUL)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsXULElement, nsStyledElement)
+
+ // nsINode
+ void GetEventTargetParent(mozilla::EventChainPreVisitor& aVisitor) override;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual nsresult PreHandleEvent(
+ mozilla::EventChainVisitor& aVisitor) override;
+ // nsIContent
+ virtual nsresult BindToTree(BindContext&, nsINode& aParent) override;
+ virtual void UnbindFromTree(bool aNullParent) override;
+ virtual void DestroyContent() override;
+ virtual void DoneAddingChildren(bool aHaveNotified) override;
+
+#ifdef DEBUG
+ virtual void List(FILE* out, int32_t aIndent) const override;
+ virtual void DumpContent(FILE* out, int32_t aIndent,
+ bool aDumpAll) const override {}
+#endif
+
+ MOZ_CAN_RUN_SCRIPT int32_t ScreenX();
+ MOZ_CAN_RUN_SCRIPT int32_t ScreenY();
+
+ MOZ_CAN_RUN_SCRIPT bool HasMenu();
+ MOZ_CAN_RUN_SCRIPT void OpenMenu(bool aOpenFlag);
+
+ MOZ_CAN_RUN_SCRIPT virtual bool PerformAccesskey(
+ bool aKeyCausesActivation, bool aIsTrustedEvent) override;
+ void ClickWithInputSource(uint16_t aInputSource, bool aIsTrustedEvent);
+
+ virtual bool IsNodeOfType(uint32_t aFlags) const override;
+ virtual bool IsFocusableInternal(int32_t* aTabIndex,
+ bool aWithMouse) override;
+
+ virtual nsChangeHint GetAttributeChangeHint(const nsAtom* aAttribute,
+ int32_t aModType) const override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo*,
+ nsINode** aResult) const override;
+
+ virtual void RecompileScriptEventListeners() override;
+
+ virtual bool IsEventAttributeNameInternal(nsAtom* aName) override;
+
+ typedef mozilla::dom::DOMString DOMString;
+ void GetXULAttr(nsAtom* aName, DOMString& aResult) const {
+ GetAttr(kNameSpaceID_None, aName, aResult);
+ }
+ void SetXULAttr(nsAtom* aName, const nsAString& aValue,
+ mozilla::ErrorResult& aError) {
+ SetAttr(aName, aValue, aError);
+ }
+ bool GetXULBoolAttr(nsAtom* aName) const {
+ return AttrValueIs(kNameSpaceID_None, aName, u"true"_ns, eCaseMatters);
+ }
+ void SetXULBoolAttr(nsAtom* aName, bool aValue) {
+ if (aValue) {
+ SetAttr(kNameSpaceID_None, aName, u"true"_ns, true);
+ } else {
+ UnsetAttr(kNameSpaceID_None, aName, true);
+ }
+ }
+
+ // WebIDL API
+ void GetFlex(DOMString& aValue) const { GetXULAttr(nsGkAtoms::flex, aValue); }
+ void SetFlex(const nsAString& aValue, mozilla::ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::flex, aValue, rv);
+ }
+ bool Hidden() const { return BoolAttrIsTrue(nsGkAtoms::hidden); }
+ void SetHidden(bool aHidden) { SetXULBoolAttr(nsGkAtoms::hidden, aHidden); }
+ bool Collapsed() const { return BoolAttrIsTrue(nsGkAtoms::collapsed); }
+ void SetCollapsed(bool aCollapsed) {
+ SetXULBoolAttr(nsGkAtoms::collapsed, aCollapsed);
+ }
+ void GetObserves(DOMString& aValue) const {
+ GetXULAttr(nsGkAtoms::observes, aValue);
+ }
+ void SetObserves(const nsAString& aValue, mozilla::ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::observes, aValue, rv);
+ }
+ void GetMenu(DOMString& aValue) const { GetXULAttr(nsGkAtoms::menu, aValue); }
+ void SetMenu(const nsAString& aValue, mozilla::ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::menu, aValue, rv);
+ }
+ void GetContextMenu(DOMString& aValue) {
+ GetXULAttr(nsGkAtoms::contextmenu, aValue);
+ }
+ void SetContextMenu(const nsAString& aValue, mozilla::ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::contextmenu, aValue, rv);
+ }
+ void GetTooltip(DOMString& aValue) const {
+ GetXULAttr(nsGkAtoms::tooltip, aValue);
+ }
+ void SetTooltip(const nsAString& aValue, mozilla::ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::tooltip, aValue, rv);
+ }
+ void GetWidth(DOMString& aValue) const {
+ GetXULAttr(nsGkAtoms::width, aValue);
+ }
+ void SetWidth(const nsAString& aValue, mozilla::ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::width, aValue, rv);
+ }
+ void GetHeight(DOMString& aValue) { GetXULAttr(nsGkAtoms::height, aValue); }
+ void SetHeight(const nsAString& aValue, mozilla::ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::height, aValue, rv);
+ }
+ void GetMinWidth(DOMString& aValue) const {
+ GetXULAttr(nsGkAtoms::minwidth, aValue);
+ }
+ void SetMinWidth(const nsAString& aValue, mozilla::ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::minwidth, aValue, rv);
+ }
+ void GetMinHeight(DOMString& aValue) const {
+ GetXULAttr(nsGkAtoms::minheight, aValue);
+ }
+ void SetMinHeight(const nsAString& aValue, mozilla::ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::minheight, aValue, rv);
+ }
+ void GetMaxWidth(DOMString& aValue) const {
+ GetXULAttr(nsGkAtoms::maxwidth, aValue);
+ }
+ void SetMaxWidth(const nsAString& aValue, mozilla::ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::maxwidth, aValue, rv);
+ }
+ void GetMaxHeight(DOMString& aValue) const {
+ GetXULAttr(nsGkAtoms::maxheight, aValue);
+ }
+ void SetMaxHeight(const nsAString& aValue, mozilla::ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::maxheight, aValue, rv);
+ }
+ void GetTooltipText(DOMString& aValue) const {
+ GetXULAttr(nsGkAtoms::tooltiptext, aValue);
+ }
+ void SetTooltipText(const nsAString& aValue, mozilla::ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::tooltiptext, aValue, rv);
+ }
+ void GetSrc(DOMString& aValue) const { GetXULAttr(nsGkAtoms::src, aValue); }
+ void SetSrc(const nsAString& aValue, mozilla::ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::src, aValue, rv);
+ }
+ bool AllowEvents() const { return BoolAttrIsTrue(nsGkAtoms::allowevents); }
+ void SetAllowEvents(bool aAllowEvents) {
+ SetXULBoolAttr(nsGkAtoms::allowevents, aAllowEvents);
+ }
+ nsIControllers* GetControllers(mozilla::ErrorResult& rv);
+ void Click(mozilla::dom::CallerType aCallerType);
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void DoCommand();
+ // Style() inherited from nsStyledElement
+
+ nsINode* GetScopeChainParent() const override {
+ // For XUL, the parent is the parent element, if any
+ Element* parent = GetParentElement();
+ return parent ? parent : nsStyledElement::GetScopeChainParent();
+ }
+
+ bool IsInteractiveHTMLContent() const override;
+
+ void MaybeUpdatePrivateLifetime();
+
+ protected:
+ ~nsXULElement();
+
+ // This can be removed if EnsureContentsGenerated dies.
+ friend class nsNSElementTearoff;
+
+ // Implementation methods
+ nsresult EnsureContentsGenerated(void) const;
+
+ nsresult AddPopupListener(nsAtom* aName);
+
+ /**
+ * Abandon our prototype linkage, and copy all attributes locally
+ */
+ nsresult MakeHeavyweight(nsXULPrototypeElement* aPrototype);
+
+ virtual nsresult BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValueOrString* aValue,
+ bool aNotify) override;
+ virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue,
+ const nsAttrValue* aOldValue,
+ nsIPrincipal* aSubjectPrincipal,
+ bool aNotify) override;
+
+ virtual void UpdateEditableState(bool aNotify) override;
+
+ virtual bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
+ const nsAString& aValue,
+ nsIPrincipal* aMaybeScriptedPrincipal,
+ nsAttrValue& aResult) override;
+
+ virtual mozilla::EventListenerManager* GetEventListenerManagerForAttr(
+ nsAtom* aAttrName, bool* aDefer) override;
+
+ /**
+ * Add a listener for the specified attribute, if appropriate.
+ */
+ void AddListenerForAttributeIfNeeded(const nsAttrName& aName);
+ void AddListenerForAttributeIfNeeded(nsAtom* aLocalName);
+
+ protected:
+ void AddTooltipSupport();
+ void RemoveTooltipSupport();
+
+ // Internal accessor. This shadows the 'Slots', and returns
+ // appropriate value.
+ nsIControllers* Controllers() {
+ nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
+ return slots ? slots->mControllers.get() : nullptr;
+ }
+
+ void UnregisterAccessKey(const nsAString& aOldValue);
+ bool BoolAttrIsTrue(nsAtom* aName) const;
+
+ friend nsXULElement* NS_NewBasicXULElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+ friend nsresult NS_NewXULElement(mozilla::dom::Element** aResult,
+ mozilla::dom::NodeInfo* aNodeInfo,
+ mozilla::dom::FromParser aFromParser,
+ const nsAString* aIs);
+ friend void NS_TrustedNewXULElement(mozilla::dom::Element** aResult,
+ mozilla::dom::NodeInfo* aNodeInfo);
+
+ static already_AddRefed<nsXULElement> CreateFromPrototype(
+ nsXULPrototypeElement* aPrototype, mozilla::dom::NodeInfo* aNodeInfo,
+ bool aIsScriptable, bool aIsRoot);
+
+ virtual JSObject* WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ bool IsEventStoppedFromAnonymousScrollbar(mozilla::EventMessage aMessage);
+
+ MOZ_CAN_RUN_SCRIPT
+ nsresult DispatchXULCommand(const mozilla::EventChainVisitor& aVisitor,
+ nsAutoString& aCommand);
+};
+
+#endif // nsXULElement_h__
diff --git a/dom/xul/nsXULPopupListener.cpp b/dom/xul/nsXULPopupListener.cpp
new file mode 100644
index 0000000000..1092dd0450
--- /dev/null
+++ b/dom/xul/nsXULPopupListener.cpp
@@ -0,0 +1,355 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+/*
+ This file provides the implementation for xul popup listener which
+ tracks xul popups and context menus
+ */
+
+#include "nsXULPopupListener.h"
+#include "nsCOMPtr.h"
+#include "nsGkAtoms.h"
+#include "nsContentCID.h"
+#include "nsContentUtils.h"
+#include "nsXULPopupManager.h"
+#include "nsIScriptContext.h"
+#include "mozilla/dom/Document.h"
+#include "nsServiceManagerUtils.h"
+#include "nsLayoutUtils.h"
+#include "mozilla/ReflowInput.h"
+#include "nsIObjectLoadingContent.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h" // for Event
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/FragmentOrElement.h"
+#include "mozilla/dom/MouseEvent.h"
+#include "mozilla/dom/MouseEventBinding.h"
+
+// for event firing in context menus
+#include "nsPresContext.h"
+#include "nsFocusManager.h"
+#include "nsPIDOMWindow.h"
+#include "nsViewManager.h"
+#include "nsError.h"
+#include "nsMenuFrame.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// on win32 and os/2, context menus come up on mouse up. On other platforms,
+// they appear on mouse down. Certain bits of code care about this difference.
+#if defined(XP_WIN)
+# define NS_CONTEXT_MENU_IS_MOUSEUP 1
+#endif
+
+nsXULPopupListener::nsXULPopupListener(mozilla::dom::Element* aElement,
+ bool aIsContext)
+ : mElement(aElement), mPopupContent(nullptr), mIsContext(aIsContext) {}
+
+nsXULPopupListener::~nsXULPopupListener(void) { ClosePopup(); }
+
+NS_IMPL_CYCLE_COLLECTION(nsXULPopupListener, mElement, mPopupContent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULPopupListener)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULPopupListener)
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsXULPopupListener)
+ // If the owner, mElement, can be skipped, so can we.
+ if (tmp->mElement) {
+ return mozilla::dom::FragmentOrElement::CanSkip(tmp->mElement, true);
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsXULPopupListener)
+ if (tmp->mElement) {
+ return mozilla::dom::FragmentOrElement::CanSkipInCC(tmp->mElement);
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsXULPopupListener)
+ if (tmp->mElement) {
+ return mozilla::dom::FragmentOrElement::CanSkipThis(tmp->mElement);
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULPopupListener)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+////////////////////////////////////////////////////////////////
+// nsIDOMEventListener
+
+nsresult nsXULPopupListener::HandleEvent(Event* aEvent) {
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+
+ if (!((eventType.EqualsLiteral("mousedown") && !mIsContext) ||
+ (eventType.EqualsLiteral("contextmenu") && mIsContext)))
+ return NS_OK;
+
+ MouseEvent* mouseEvent = aEvent->AsMouseEvent();
+ if (!mouseEvent) {
+ // non-ui event passed in. bad things.
+ return NS_OK;
+ }
+
+ // Get the node that was clicked on.
+ EventTarget* target = mouseEvent->GetTarget();
+ nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
+ if (!targetContent) {
+ return NS_OK;
+ }
+
+ {
+ EventTarget* originalTarget = mouseEvent->GetOriginalTarget();
+ nsCOMPtr<nsIContent> content = do_QueryInterface(originalTarget);
+ if (content && EventStateManager::IsTopLevelRemoteTarget(content)) {
+ return NS_OK;
+ }
+ }
+
+ bool preventDefault = mouseEvent->DefaultPrevented();
+ if (preventDefault && mIsContext) {
+ // Someone called preventDefault on a context menu.
+ // Let's make sure they are allowed to do so.
+ bool eventEnabled =
+ Preferences::GetBool("dom.event.contextmenu.enabled", true);
+ if (!eventEnabled) {
+ // If the target node is for plug-in, we should not open XUL context
+ // menu on windowless plug-ins.
+ nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(targetContent);
+ uint32_t type;
+ if (olc && NS_SUCCEEDED(olc->GetDisplayedType(&type)) &&
+ type == nsIObjectLoadingContent::TYPE_PLUGIN) {
+ return NS_OK;
+ }
+
+ // The user wants his contextmenus. Let's make sure that this is a
+ // website and not chrome since there could be places in chrome which
+ // don't want contextmenus.
+ if (!targetContent->NodePrincipal()->IsSystemPrincipal()) {
+ // This isn't chrome. Cancel the preventDefault() and
+ // let the event go forth.
+ preventDefault = false;
+ }
+ }
+ }
+
+ if (preventDefault) {
+ // someone called preventDefault. bail.
+ return NS_OK;
+ }
+
+ // prevent popups on menu and menuitems as they handle their own popups
+ // This was added for bug 96920.
+ // If a menu item child was clicked on that leads to a popup needing
+ // to show, we know (guaranteed) that we're dealing with a menu or
+ // submenu of an already-showing popup. We don't need to do anything at all.
+ if (!mIsContext &&
+ targetContent->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menuitem)) {
+ return NS_OK;
+ }
+
+ if (mIsContext) {
+#ifndef NS_CONTEXT_MENU_IS_MOUSEUP
+ uint16_t inputSource = mouseEvent->MozInputSource();
+ bool isTouch = inputSource == MouseEvent_Binding::MOZ_SOURCE_TOUCH;
+ // If the context menu launches on mousedown,
+ // we have to fire focus on the content we clicked on
+ FireFocusOnTargetContent(targetContent, isTouch);
+#endif
+ } else {
+ // Only open popups when the left mouse button is down.
+ if (mouseEvent->Button() != 0) {
+ return NS_OK;
+ }
+ }
+
+ // Open the popup. LaunchPopup will call StopPropagation and PreventDefault
+ // in the right situations.
+ LaunchPopup(mouseEvent);
+
+ return NS_OK;
+}
+
+#ifndef NS_CONTEXT_MENU_IS_MOUSEUP
+nsresult nsXULPopupListener::FireFocusOnTargetContent(
+ nsIContent* aTargetContent, bool aIsTouch) {
+ nsCOMPtr<Document> doc = aTargetContent->OwnerDoc();
+
+ // strong reference to keep this from going away between events
+ // XXXbz between what events? We don't use this local at all!
+ RefPtr<nsPresContext> context = doc->GetPresContext();
+ if (!context) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIFrame* targetFrame = aTargetContent->GetPrimaryFrame();
+ if (!targetFrame) return NS_ERROR_FAILURE;
+
+ const nsStyleUI* ui = targetFrame->StyleUI();
+ bool suppressBlur = (ui->mUserFocus == StyleUserFocus::Ignore);
+
+ RefPtr<Element> newFocusElement;
+
+ nsIFrame* currFrame = targetFrame;
+ // Look for the nearest enclosing focusable frame.
+ while (currFrame) {
+ if (currFrame->IsFocusable(/* aWithMouse = */ true) &&
+ currFrame->GetContent()->IsElement()) {
+ newFocusElement = currFrame->GetContent()->AsElement();
+ break;
+ }
+ currFrame = currFrame->GetParent();
+ }
+
+ if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
+ if (newFocusElement) {
+ uint32_t focusFlags =
+ nsIFocusManager::FLAG_BYMOUSE | nsIFocusManager::FLAG_NOSCROLL;
+ if (aIsTouch) {
+ focusFlags |= nsIFocusManager::FLAG_BYTOUCH;
+ }
+ fm->SetFocus(newFocusElement, focusFlags);
+ } else if (!suppressBlur) {
+ nsPIDOMWindowOuter* window = doc->GetWindow();
+ fm->ClearFocus(window);
+ }
+ }
+
+ EventStateManager* esm = context->EventStateManager();
+ esm->SetContentState(newFocusElement, NS_EVENT_STATE_ACTIVE);
+
+ return NS_OK;
+}
+#endif
+
+// ClosePopup
+//
+// Do everything needed to shut down the popup.
+//
+// NOTE: This routine is safe to call even if the popup is already closed.
+//
+void nsXULPopupListener::ClosePopup() {
+ if (mPopupContent) {
+ // this is called when the listener is going away, so make sure that the
+ // popup is hidden. Use asynchronous hiding just to be safe so we don't
+ // fire events during destruction.
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) pm->HidePopup(mPopupContent, false, true, true, false);
+ mPopupContent = nullptr; // release the popup
+ }
+} // ClosePopup
+
+static already_AddRefed<Element> GetImmediateChild(nsIContent* aContent,
+ nsAtom* aTag) {
+ for (nsIContent* child = aContent->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsXULElement(aTag)) {
+ RefPtr<Element> ret = child->AsElement();
+ return ret.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+//
+// LaunchPopup
+//
+// Given the element on which the event was triggered and the mouse locations in
+// Client and widget coordinates, popup a new window showing the appropriate
+// content.
+//
+// aTargetContent is the target of the mouse event aEvent that triggered the
+// popup. mElement is the element that the popup menu is attached to.
+// aTargetContent may be equal to mElement or it may be a descendant.
+//
+// This looks for an attribute on |mElement| of the appropriate popup type
+// (popup, context) and uses that attribute's value as an ID for
+// the popup content in the document.
+//
+nsresult nsXULPopupListener::LaunchPopup(MouseEvent* aEvent) {
+ nsresult rv = NS_OK;
+
+ nsAutoString identifier;
+ nsAtom* type = mIsContext ? nsGkAtoms::context : nsGkAtoms::popup;
+ bool hasPopupAttr = mElement->GetAttr(kNameSpaceID_None, type, identifier);
+
+ if (identifier.IsEmpty()) {
+ hasPopupAttr =
+ mElement->GetAttr(kNameSpaceID_None,
+ mIsContext ? nsGkAtoms::contextmenu : nsGkAtoms::menu,
+ identifier) ||
+ hasPopupAttr;
+ }
+
+ if (hasPopupAttr) {
+ aEvent->StopPropagation();
+ aEvent->PreventDefault();
+ }
+
+ if (identifier.IsEmpty()) return rv;
+
+ // Try to find the popup content and the document.
+ nsCOMPtr<Document> document = mElement->GetComposedDoc();
+ if (!document) {
+ NS_WARNING("No document!");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Handle the _child case for popups and context menus
+ RefPtr<Element> popup;
+ if (identifier.EqualsLiteral("_child")) {
+ popup = GetImmediateChild(mElement, nsGkAtoms::menupopup);
+ } else if (!mElement->IsInUncomposedDoc() ||
+ !(popup = document->GetElementById(identifier))) {
+ // XXXsmaug Should we try to use ShadowRoot::GetElementById in case
+ // mElement is in shadow DOM?
+ //
+ // Use getElementById to obtain the popup content and gracefully fail if
+ // we didn't find any popup content in the document.
+ NS_WARNING("GetElementById had some kind of spasm.");
+ return rv;
+ }
+
+ // return if no popup was found or the popup is the element itself.
+ if (!popup || popup == mElement) return NS_OK;
+
+ // Submenus can't be used as context menus or popups, bug 288763.
+ // Similar code also in nsXULTooltipListener::GetTooltipFor.
+ nsIContent* parent = popup->GetParent();
+ if (parent) {
+ nsMenuFrame* menu = do_QueryFrame(parent->GetPrimaryFrame());
+ if (menu) return NS_OK;
+ }
+
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!pm) return NS_OK;
+
+ // For left-clicks, if the popup has an position attribute, or both the
+ // popupanchor and popupalign attributes are used, anchor the popup to the
+ // element, otherwise just open it at the screen position where the mouse
+ // was clicked. Context menus always open at the mouse position.
+ mPopupContent = popup;
+ if (!mIsContext &&
+ (mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::position) ||
+ (mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popupanchor) &&
+ mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popupalign)))) {
+ pm->ShowPopup(mPopupContent, mElement, u""_ns, 0, 0, false, true, false,
+ aEvent);
+ } else {
+ int32_t xPos = aEvent->ScreenX(CallerType::System);
+ int32_t yPos = aEvent->ScreenY(CallerType::System);
+
+ pm->ShowPopupAtScreen(mPopupContent, xPos, yPos, mIsContext, aEvent);
+ }
+
+ return NS_OK;
+}
diff --git a/dom/xul/nsXULPopupListener.h b/dom/xul/nsXULPopupListener.h
new file mode 100644
index 0000000000..14f6c16c68
--- /dev/null
+++ b/dom/xul/nsXULPopupListener.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+/**
+ * This is the popup listener implementation for popup menus and context menus.
+ */
+
+#ifndef nsXULPopupListener_h___
+#define nsXULPopupListener_h___
+
+#include "nsCOMPtr.h"
+
+#include "nsIDOMEventListener.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsIContent;
+
+namespace mozilla {
+namespace dom {
+class Element;
+class MouseEvent;
+} // namespace dom
+} // namespace mozilla
+
+class nsXULPopupListener : public nsIDOMEventListener {
+ public:
+ // aElement is the element that the popup is attached to. If aIsContext is
+ // false, the popup opens on left click on aElement or a descendant. If
+ // aIsContext is true, the popup is a context menu which opens on a
+ // context menu event.
+ nsXULPopupListener(mozilla::dom::Element* aElement, bool aIsContext);
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS(nsXULPopupListener)
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ protected:
+ virtual ~nsXULPopupListener(void);
+
+ // open the popup. aEvent is the event that triggered the popup such as
+ // a mouse click and aTargetContent is the target of this event.
+ virtual nsresult LaunchPopup(mozilla::dom::MouseEvent* aEvent);
+
+ // close the popup when the listener goes away
+ virtual void ClosePopup();
+
+ private:
+#ifndef NS_CONTEXT_MENU_IS_MOUSEUP
+ // When a context menu is opened, focus the target of the contextmenu event.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
+ FireFocusOnTargetContent(nsIContent* aTargetContent, bool aIsTouch);
+#endif
+
+ // |mElement| is the node to which this listener is attached.
+ RefPtr<mozilla::dom::Element> mElement;
+
+ // The popup that is getting shown on top of mElement.
+ RefPtr<mozilla::dom::Element> mPopupContent;
+
+ // true if a context popup
+ bool mIsContext;
+};
+
+#endif // nsXULPopupListener_h___
diff --git a/dom/xul/nsXULPrototypeCache.cpp b/dom/xul/nsXULPrototypeCache.cpp
new file mode 100644
index 0000000000..2e91cfd24b
--- /dev/null
+++ b/dom/xul/nsXULPrototypeCache.cpp
@@ -0,0 +1,533 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsXULPrototypeCache.h"
+
+#include "plstr.h"
+#include "nsXULPrototypeDocument.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+
+#include "nsIFile.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIObserverService.h"
+#include "nsIStorageStream.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+
+#include "js/TracingAPI.h"
+
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/scache/StartupCache.h"
+#include "mozilla/scache/StartupCacheUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/intl/LocaleService.h"
+
+using namespace mozilla;
+using namespace mozilla::scache;
+using mozilla::intl::LocaleService;
+
+#define XUL_CACHE_DISABLED_DEFAULT false
+
+static bool gDisableXULCache =
+ XUL_CACHE_DISABLED_DEFAULT; // enabled by default
+static const char kDisableXULCachePref[] = "nglayout.debug.disable_xul_cache";
+static const char kXULCacheInfoKey[] = "nsXULPrototypeCache.startupCache";
+static const char kXULCachePrefix[] = "xulcache";
+
+//----------------------------------------------------------------------
+
+static void UpdategDisableXULCache() {
+ // Get the value of "nglayout.debug.disable_xul_cache" preference
+ gDisableXULCache =
+ Preferences::GetBool(kDisableXULCachePref, XUL_CACHE_DISABLED_DEFAULT);
+
+ // Sets the flag if the XUL cache is disabled
+ if (gDisableXULCache) {
+ Telemetry::Accumulate(Telemetry::XUL_CACHE_DISABLED, true);
+ }
+}
+
+static void DisableXULCacheChangedCallback(const char* aPref, void* aClosure) {
+ bool wasEnabled = !gDisableXULCache;
+ UpdategDisableXULCache();
+
+ if (wasEnabled && gDisableXULCache) {
+ nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
+ if (cache) {
+ // AbortCaching() calls Flush() for us.
+ cache->AbortCaching();
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+
+nsXULPrototypeCache* nsXULPrototypeCache::sInstance = nullptr;
+
+nsXULPrototypeCache::nsXULPrototypeCache() = default;
+
+nsXULPrototypeCache::~nsXULPrototypeCache() { FlushScripts(); }
+
+NS_IMPL_ISUPPORTS(nsXULPrototypeCache, nsIObserver)
+
+/* static */
+nsXULPrototypeCache* nsXULPrototypeCache::GetInstance() {
+ if (!sInstance) {
+ NS_ADDREF(sInstance = new nsXULPrototypeCache());
+
+ UpdategDisableXULCache();
+
+ Preferences::RegisterCallback(DisableXULCacheChangedCallback,
+ kDisableXULCachePref);
+
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ if (obsSvc) {
+ nsXULPrototypeCache* p = sInstance;
+ obsSvc->AddObserver(p, "chrome-flush-caches", false);
+ obsSvc->AddObserver(p, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ obsSvc->AddObserver(p, "startupcache-invalidate", false);
+ }
+ }
+ return sInstance;
+}
+
+//----------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsXULPrototypeCache::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "chrome-flush-caches") ||
+ !strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ Flush();
+ } else if (!strcmp(aTopic, "startupcache-invalidate")) {
+ AbortCaching();
+ } else {
+ NS_WARNING("Unexpected observer topic.");
+ }
+ return NS_OK;
+}
+
+nsXULPrototypeDocument* nsXULPrototypeCache::GetPrototype(nsIURI* aURI) {
+ if (!aURI) return nullptr;
+
+ nsCOMPtr<nsIURI> uriWithoutRef;
+ NS_GetURIWithoutRef(aURI, getter_AddRefs(uriWithoutRef));
+
+ nsXULPrototypeDocument* protoDoc = mPrototypeTable.GetWeak(uriWithoutRef);
+ if (protoDoc) {
+ return protoDoc;
+ }
+
+ nsresult rv = BeginCaching(aURI);
+ if (NS_FAILED(rv)) return nullptr;
+
+ // No prototype in XUL memory cache. Spin up the cache Service.
+ nsCOMPtr<nsIObjectInputStream> ois;
+ rv = GetInputStream(aURI, getter_AddRefs(ois));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ RefPtr<nsXULPrototypeDocument> newProto;
+ rv = NS_NewXULPrototypeDocument(getter_AddRefs(newProto));
+ if (NS_FAILED(rv)) return nullptr;
+
+ rv = newProto->Read(ois);
+ if (NS_SUCCEEDED(rv)) {
+ rv = PutPrototype(newProto);
+ } else {
+ newProto = nullptr;
+ }
+
+ mInputStreamTable.Remove(aURI);
+ return newProto;
+}
+
+nsresult nsXULPrototypeCache::PutPrototype(nsXULPrototypeDocument* aDocument) {
+ if (!aDocument->GetURI()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ NS_GetURIWithoutRef(aDocument->GetURI(), getter_AddRefs(uri));
+
+ // Put() releases any old value
+ mPrototypeTable.Put(uri, RefPtr{aDocument});
+
+ return NS_OK;
+}
+
+mozilla::StyleSheet* nsXULPrototypeCache::GetStyleSheet(nsIURI* aURI) {
+ return mStyleSheetTable.GetWeak(aURI);
+}
+
+nsresult nsXULPrototypeCache::PutStyleSheet(RefPtr<StyleSheet>&& aStyleSheet) {
+ nsIURI* uri = aStyleSheet->GetSheetURI();
+ mStyleSheetTable.Put(uri, std::move(aStyleSheet));
+ return NS_OK;
+}
+
+JSScript* nsXULPrototypeCache::GetScript(nsIURI* aURI) {
+ return mScriptTable.Get(aURI);
+}
+
+nsresult nsXULPrototypeCache::PutScript(nsIURI* aURI,
+ JS::Handle<JSScript*> aScriptObject) {
+ MOZ_ASSERT(aScriptObject, "Need a non-NULL script");
+
+#ifdef DEBUG_BUG_392650
+ if (mScriptTable.Get(aURI)) {
+ nsAutoCString scriptName;
+ aURI->GetSpec(scriptName);
+ nsAutoCString message("Loaded script ");
+ message += scriptName;
+ message += " twice (bug 392650)";
+ NS_WARNING(message.get());
+ }
+#endif
+
+ mScriptTable.Put(aURI, aScriptObject);
+
+ return NS_OK;
+}
+
+void nsXULPrototypeCache::FlushScripts() { mScriptTable.Clear(); }
+
+void nsXULPrototypeCache::Flush() {
+ mPrototypeTable.Clear();
+ mScriptTable.Clear();
+ mStyleSheetTable.Clear();
+}
+
+bool nsXULPrototypeCache::IsEnabled() { return !gDisableXULCache; }
+
+void nsXULPrototypeCache::AbortCaching() {
+#ifdef DEBUG_brendan
+ NS_BREAK();
+#endif
+
+ // Flush the XUL cache for good measure, in case we cached a bogus/downrev
+ // script, somehow.
+ Flush();
+
+ // Clear the cache set
+ mStartupCacheURITable.Clear();
+}
+
+nsresult nsXULPrototypeCache::WritePrototype(
+ nsXULPrototypeDocument* aPrototypeDocument) {
+ nsresult rv = NS_OK, rv2 = NS_OK;
+
+ if (!StartupCache::GetSingleton()) return NS_OK;
+
+ nsCOMPtr<nsIURI> protoURI = aPrototypeDocument->GetURI();
+
+ nsCOMPtr<nsIObjectOutputStream> oos;
+ rv = GetOutputStream(protoURI, getter_AddRefs(oos));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aPrototypeDocument->Write(oos);
+ NS_ENSURE_SUCCESS(rv, rv);
+ FinishOutputStream(protoURI);
+ return NS_FAILED(rv) ? rv : rv2;
+}
+
+nsresult nsXULPrototypeCache::GetInputStream(nsIURI* uri,
+ nsIObjectInputStream** stream) {
+ nsAutoCString spec(kXULCachePrefix);
+ nsresult rv = PathifyURI(uri, spec);
+ if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE;
+
+ const char* buf;
+ uint32_t len;
+ nsCOMPtr<nsIObjectInputStream> ois;
+ StartupCache* sc = StartupCache::GetSingleton();
+ if (!sc) return NS_ERROR_NOT_AVAILABLE;
+
+ rv = sc->GetBuffer(spec.get(), &buf, &len);
+ if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE;
+
+ rv = NewObjectInputStreamFromBuffer(buf, len, getter_AddRefs(ois));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInputStreamTable.Put(uri, ois);
+
+ ois.forget(stream);
+ return NS_OK;
+}
+
+nsresult nsXULPrototypeCache::FinishInputStream(nsIURI* uri) {
+ mInputStreamTable.Remove(uri);
+ return NS_OK;
+}
+
+nsresult nsXULPrototypeCache::GetOutputStream(nsIURI* uri,
+ nsIObjectOutputStream** stream) {
+ nsresult rv;
+ nsCOMPtr<nsIObjectOutputStream> objectOutput;
+ nsCOMPtr<nsIStorageStream> storageStream;
+ bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream));
+ if (found) {
+ // Setting an output stream here causes crashes on Windows. The previous
+ // version of this code always returned NS_ERROR_OUT_OF_MEMORY here,
+ // because it used a mistyped contract ID to create its object stream.
+ return NS_ERROR_NOT_IMPLEMENTED;
+#if 0
+ nsCOMPtr<nsIOutputStream> outputStream
+ = do_QueryInterface(storageStream);
+ objectOutput = NS_NewObjectOutputStream(outputStream);
+#endif
+ } else {
+ rv = NewObjectOutputWrappedStorageStream(
+ getter_AddRefs(objectOutput), getter_AddRefs(storageStream), false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mOutputStreamTable.Put(uri, storageStream);
+ }
+ objectOutput.forget(stream);
+ return NS_OK;
+}
+
+nsresult nsXULPrototypeCache::FinishOutputStream(nsIURI* uri) {
+ nsresult rv;
+ StartupCache* sc = StartupCache::GetSingleton();
+ if (!sc) return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIStorageStream> storageStream;
+ bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream));
+ if (!found) return NS_ERROR_UNEXPECTED;
+ nsCOMPtr<nsIOutputStream> outputStream = do_QueryInterface(storageStream);
+ outputStream->Close();
+
+ UniquePtr<char[]> buf;
+ uint32_t len;
+ rv = NewBufferFromStorageStream(storageStream, &buf, &len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mStartupCacheURITable.GetEntry(uri)) {
+ nsAutoCString spec(kXULCachePrefix);
+ rv = PathifyURI(uri, spec);
+ if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE;
+ rv = sc->PutBuffer(spec.get(), std::move(buf), len);
+ if (NS_SUCCEEDED(rv)) {
+ mOutputStreamTable.Remove(uri);
+ mStartupCacheURITable.PutEntry(uri);
+ }
+ }
+
+ return rv;
+}
+
+// We have data if we're in the middle of writing it or we already
+// have it in the cache.
+nsresult nsXULPrototypeCache::HasData(nsIURI* uri, bool* exists) {
+ if (mOutputStreamTable.Get(uri, nullptr)) {
+ *exists = true;
+ return NS_OK;
+ }
+ nsAutoCString spec(kXULCachePrefix);
+ nsresult rv = PathifyURI(uri, spec);
+ if (NS_FAILED(rv)) {
+ *exists = false;
+ return NS_OK;
+ }
+ UniquePtr<char[]> buf;
+ StartupCache* sc = StartupCache::GetSingleton();
+ if (sc) {
+ *exists = sc->HasEntry(spec.get());
+ } else {
+ *exists = false;
+ }
+ return NS_OK;
+}
+
+nsresult nsXULPrototypeCache::BeginCaching(nsIURI* aURI) {
+ nsresult rv, tmp;
+
+ nsAutoCString path;
+ aURI->GetPathQueryRef(path);
+ if (!(StringEndsWith(path, ".xul"_ns) || StringEndsWith(path, ".xhtml"_ns))) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ StartupCache* startupCache = StartupCache::GetSingleton();
+ if (!startupCache) return NS_ERROR_FAILURE;
+
+ if (gDisableXULCache) return NS_ERROR_NOT_AVAILABLE;
+
+ // Get the chrome directory to validate against the one stored in the
+ // cache file, or to store there if we're generating a new file.
+ nsCOMPtr<nsIFile> chromeDir;
+ rv = NS_GetSpecialDirectory(NS_APP_CHROME_DIR, getter_AddRefs(chromeDir));
+ if (NS_FAILED(rv)) return rv;
+ nsAutoCString chromePath;
+ rv = chromeDir->GetPersistentDescriptor(chromePath);
+ if (NS_FAILED(rv)) return rv;
+
+ // XXXbe we assume the first package's locale is the same as the locale of
+ // all subsequent packages of cached chrome URIs....
+ nsAutoCString package;
+ rv = aURI->GetHost(package);
+ if (NS_FAILED(rv)) return rv;
+ nsAutoCString locale;
+ LocaleService::GetInstance()->GetAppLocaleAsBCP47(locale);
+
+ nsAutoCString fileChromePath, fileLocale;
+
+ const char* buf = nullptr;
+ uint32_t len, amtRead;
+ nsCOMPtr<nsIObjectInputStream> objectInput;
+
+ rv = startupCache->GetBuffer(kXULCacheInfoKey, &buf, &len);
+ if (NS_SUCCEEDED(rv))
+ rv = NewObjectInputStreamFromBuffer(buf, len, getter_AddRefs(objectInput));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = objectInput->ReadCString(fileLocale);
+ tmp = objectInput->ReadCString(fileChromePath);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ if (NS_FAILED(rv) ||
+ (!fileChromePath.Equals(chromePath) || !fileLocale.Equals(locale))) {
+ // Our cache won't be valid in this case, we'll need to rewrite.
+ // XXX This blows away work that other consumers (like
+ // mozJSComponentLoader) have done, need more fine-grained control.
+ startupCache->InvalidateCache();
+ mStartupCacheURITable.Clear();
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ } else if (rv != NS_ERROR_NOT_AVAILABLE)
+ // NS_ERROR_NOT_AVAILABLE is normal, usually if there's no cachefile.
+ return rv;
+
+ if (NS_FAILED(rv)) {
+ // Either the cache entry was invalid or it didn't exist, so write it now.
+ nsCOMPtr<nsIObjectOutputStream> objectOutput;
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIStorageStream> storageStream;
+ rv = NewObjectOutputWrappedStorageStream(
+ getter_AddRefs(objectOutput), getter_AddRefs(storageStream), false);
+ if (NS_SUCCEEDED(rv)) {
+ rv = objectOutput->WriteStringZ(locale.get());
+ tmp = objectOutput->WriteStringZ(chromePath.get());
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = objectOutput->Close();
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = storageStream->NewInputStream(0, getter_AddRefs(inputStream));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ uint64_t len64;
+ rv = inputStream->Available(&len64);
+ if (NS_SUCCEEDED(rv)) {
+ if (len64 <= UINT32_MAX)
+ len = (uint32_t)len64;
+ else
+ rv = NS_ERROR_FILE_TOO_BIG;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ auto putBuf = MakeUnique<char[]>(len);
+ rv = inputStream->Read(putBuf.get(), len, &amtRead);
+ if (NS_SUCCEEDED(rv) && len == amtRead)
+ rv = startupCache->PutBuffer(kXULCacheInfoKey, std::move(putBuf), len);
+ else {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ // Failed again, just bail.
+ if (NS_FAILED(rv)) {
+ startupCache->InvalidateCache();
+ mStartupCacheURITable.Clear();
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsXULPrototypeCache::MarkInCCGeneration(uint32_t aGeneration) {
+ for (auto iter = mPrototypeTable.Iter(); !iter.Done(); iter.Next()) {
+ iter.Data()->MarkInCCGeneration(aGeneration);
+ }
+}
+
+void nsXULPrototypeCache::MarkInGC(JSTracer* aTrc) {
+ for (auto iter = mScriptTable.Iter(); !iter.Done(); iter.Next()) {
+ JS::Heap<JSScript*>& script = iter.Data();
+ JS::TraceEdge(aTrc, &script, "nsXULPrototypeCache script");
+ }
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(CacheMallocSizeOf)
+
+static void ReportSize(const nsCString& aPath, size_t aAmount,
+ const nsCString& aDescription,
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData) {
+ nsAutoCString path("explicit/xul-prototype-cache/");
+ path += aPath;
+ aHandleReport->Callback(""_ns, path, nsIMemoryReporter::KIND_HEAP,
+ nsIMemoryReporter::UNITS_BYTES, aAmount, aDescription,
+ aData);
+}
+
+/* static */
+void nsXULPrototypeCache::CollectMemoryReports(
+ nsIHandleReportCallback* aHandleReport, nsISupports* aData) {
+ if (!sInstance) {
+ return;
+ }
+
+ MallocSizeOf mallocSizeOf = CacheMallocSizeOf;
+ size_t other = mallocSizeOf(sInstance);
+
+#define REPORT_SIZE(_path, _amount, _desc) \
+ ReportSize(_path, _amount, nsLiteralCString(_desc), aHandleReport, aData)
+
+ other += sInstance->mPrototypeTable.ShallowSizeOfExcludingThis(mallocSizeOf);
+ // TODO Report content in mPrototypeTable?
+
+ other += sInstance->mStyleSheetTable.ShallowSizeOfExcludingThis(mallocSizeOf);
+ for (auto iter = sInstance->mStyleSheetTable.ConstIter(); !iter.Done();
+ iter.Next()) {
+ // NOTE: If Loader::DoSheetComplete() is ever modified to stop clongin
+ // sheets before inserting into this cache, we will need to stop using
+ // SizeOfIncludingThis()
+ other += iter.Data()->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ other += sInstance->mScriptTable.ShallowSizeOfExcludingThis(mallocSizeOf);
+ // TODO Report content inside mScriptTable?
+
+ other +=
+ sInstance->mStartupCacheURITable.ShallowSizeOfExcludingThis(mallocSizeOf);
+
+ other +=
+ sInstance->mOutputStreamTable.ShallowSizeOfExcludingThis(mallocSizeOf);
+ other +=
+ sInstance->mInputStreamTable.ShallowSizeOfExcludingThis(mallocSizeOf);
+
+ REPORT_SIZE("other"_ns, other,
+ "Memory used by "
+ "the instance and tables of the XUL prototype cache.");
+
+#undef REPORT_SIZE
+}
diff --git a/dom/xul/nsXULPrototypeCache.h b/dom/xul/nsXULPrototypeCache.h
new file mode 100644
index 0000000000..7c987ccf74
--- /dev/null
+++ b/dom/xul/nsXULPrototypeCache.h
@@ -0,0 +1,128 @@
+/* -*- 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/. */
+
+#ifndef nsXULPrototypeCache_h__
+#define nsXULPrototypeCache_h__
+
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsJSThingHashtable.h"
+#include "nsInterfaceHashtable.h"
+#include "nsRefPtrHashtable.h"
+#include "nsURIHashKey.h"
+#include "nsXULPrototypeDocument.h"
+#include "nsIStorageStream.h"
+
+#include "mozilla/scache/StartupCache.h"
+
+class nsIHandleReportCallback;
+namespace mozilla {
+class StyleSheet;
+} // namespace mozilla
+
+/**
+ * The XUL prototype cache can be used to store and retrieve shared data for
+ * XUL documents, style sheets, XBL, and scripts.
+ *
+ * The cache has two levels:
+ * 1. In-memory hashtables
+ * 2. The on-disk cache file.
+ */
+class nsXULPrototypeCache : public nsIObserver {
+ public:
+ // nsISupports
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ bool IsCached(nsIURI* aURI) { return GetPrototype(aURI) != nullptr; }
+ void AbortCaching();
+
+ /**
+ * Whether the prototype cache is enabled.
+ */
+ bool IsEnabled();
+
+ /**
+ * Flush the cache; remove all XUL prototype documents, style
+ * sheets, and scripts.
+ */
+ void Flush();
+
+ // The following methods are used to put and retrive various items into and
+ // from the cache.
+
+ nsXULPrototypeDocument* GetPrototype(nsIURI* aURI);
+ nsresult PutPrototype(nsXULPrototypeDocument* aDocument);
+
+ JSScript* GetScript(nsIURI* aURI);
+ nsresult PutScript(nsIURI* aURI, JS::Handle<JSScript*> aScriptObject);
+
+ /**
+ * Get a style sheet by URI. If the style sheet is not in the cache,
+ * returns nullptr.
+ */
+ mozilla::StyleSheet* GetStyleSheet(nsIURI* aURI);
+
+ /**
+ * Store a style sheet in the cache. The key, style sheet's URI is obtained
+ * from the style sheet itself.
+ */
+ nsresult PutStyleSheet(RefPtr<mozilla::StyleSheet>&& aStyleSheet);
+
+ /**
+ * Write the XUL prototype document to a cache file. The proto must be
+ * fully loaded.
+ */
+ nsresult WritePrototype(nsXULPrototypeDocument* aPrototypeDocument);
+
+ /**
+ * This interface allows partial reads and writes from the buffers in the
+ * startupCache.
+ */
+ nsresult GetInputStream(nsIURI* aURI, nsIObjectInputStream** objectInput);
+ nsresult FinishInputStream(nsIURI* aURI);
+ nsresult GetOutputStream(nsIURI* aURI, nsIObjectOutputStream** objectOutput);
+ nsresult FinishOutputStream(nsIURI* aURI);
+ nsresult HasData(nsIURI* aURI, bool* exists);
+
+ static nsXULPrototypeCache* GetInstance();
+ static nsXULPrototypeCache* MaybeGetInstance() { return sInstance; }
+
+ static void ReleaseGlobals() { NS_IF_RELEASE(sInstance); }
+
+ void MarkInCCGeneration(uint32_t aGeneration);
+ void MarkInGC(JSTracer* aTrc);
+ void FlushScripts();
+
+ static void CollectMemoryReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData);
+
+ protected:
+ friend nsresult NS_NewXULPrototypeCache(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult);
+
+ nsXULPrototypeCache();
+ virtual ~nsXULPrototypeCache();
+
+ static nsXULPrototypeCache* sInstance;
+
+ using StyleSheetTable = nsRefPtrHashtable<nsURIHashKey, mozilla::StyleSheet>;
+
+ nsRefPtrHashtable<nsURIHashKey, nsXULPrototypeDocument>
+ mPrototypeTable; // owns the prototypes
+ StyleSheetTable mStyleSheetTable;
+ nsJSThingHashtable<nsURIHashKey, JSScript*> mScriptTable;
+
+ // URIs already written to the startup cache, to prevent double-caching.
+ nsTHashtable<nsURIHashKey> mStartupCacheURITable;
+
+ nsInterfaceHashtable<nsURIHashKey, nsIStorageStream> mOutputStreamTable;
+ nsInterfaceHashtable<nsURIHashKey, nsIObjectInputStream> mInputStreamTable;
+
+ // Bootstrap caching service
+ nsresult BeginCaching(nsIURI* aDocumentURI);
+};
+
+#endif // nsXULPrototypeCache_h__
diff --git a/dom/xul/nsXULPrototypeDocument.cpp b/dom/xul/nsXULPrototypeDocument.cpp
new file mode 100644
index 0000000000..03ca13665f
--- /dev/null
+++ b/dom/xul/nsXULPrototypeDocument.cpp
@@ -0,0 +1,535 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsXULPrototypeDocument.h"
+
+#include "nsXULElement.h"
+#include "nsAString.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIPrincipal.h"
+#include "nsJSPrincipals.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIURI.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "nsString.h"
+#include "nsDOMCID.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentUtils.h"
+#include "nsCCUncollectableMarker.h"
+#include "xpcpublic.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "nsXULPrototypeCache.h"
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/dom/CustomElementRegistry.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Text.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using mozilla::dom::DestroyProtoAndIfaceCache;
+
+uint32_t nsXULPrototypeDocument::gRefCnt;
+
+//----------------------------------------------------------------------
+//
+// ctors, dtors, n' stuff
+//
+
+nsXULPrototypeDocument::nsXULPrototypeDocument()
+ : mRoot(nullptr),
+ mLoaded(false),
+ mCCGeneration(0),
+ mGCNumber(0),
+ mWasL10nCached(false) {
+ ++gRefCnt;
+}
+
+nsresult nsXULPrototypeDocument::Init() {
+ mNodeInfoManager = new nsNodeInfoManager();
+ return mNodeInfoManager->Init(nullptr);
+}
+
+nsXULPrototypeDocument::~nsXULPrototypeDocument() {
+ if (mRoot) mRoot->ReleaseSubtree();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULPrototypeDocument)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULPrototypeDocument)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeWaiters)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULPrototypeDocument)
+ if (nsCCUncollectableMarker::InGeneration(cb, tmp->mCCGeneration)) {
+ return NS_SUCCESS_INTERRUPTED_TRAVERSE;
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfoManager)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULPrototypeDocument)
+ NS_INTERFACE_MAP_ENTRY(nsISerializable)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULPrototypeDocument)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULPrototypeDocument)
+
+NS_IMETHODIMP
+NS_NewXULPrototypeDocument(nsXULPrototypeDocument** aResult) {
+ *aResult = nullptr;
+ RefPtr<nsXULPrototypeDocument> doc = new nsXULPrototypeDocument();
+
+ nsresult rv = doc->Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ doc.forget(aResult);
+ return rv;
+}
+
+//----------------------------------------------------------------------
+//
+// nsISerializable methods
+//
+
+NS_IMETHODIMP
+nsXULPrototypeDocument::Read(nsIObjectInputStream* aStream) {
+ nsCOMPtr<nsISupports> supports;
+ nsresult rv = aStream->ReadObject(true, getter_AddRefs(supports));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mURI = do_QueryInterface(supports);
+
+ // nsIPrincipal mNodeInfoManager->mPrincipal
+ nsAutoCString JSON;
+ rv = aStream->ReadCString(JSON);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::FromJSON(JSON);
+
+ // Better safe than sorry....
+ mNodeInfoManager->SetDocumentPrincipal(principal);
+
+ rv = aStream->ReadBoolean(&mWasL10nCached);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mRoot = new nsXULPrototypeElement();
+
+ // mozilla::dom::NodeInfo table
+ nsTArray<RefPtr<mozilla::dom::NodeInfo>> nodeInfos;
+
+ uint32_t count, i;
+ rv = aStream->Read32(&count);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsAutoString namespaceURI, prefixStr, localName;
+ bool prefixIsNull;
+ RefPtr<nsAtom> prefix;
+ for (i = 0; i < count; ++i) {
+ rv = aStream->ReadString(namespaceURI);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = aStream->ReadBoolean(&prefixIsNull);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (prefixIsNull) {
+ prefix = nullptr;
+ } else {
+ rv = aStream->ReadString(prefixStr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ prefix = NS_Atomize(prefixStr);
+ }
+ rv = aStream->ReadString(localName);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ // Using UINT16_MAX here as we don't know which nodeinfos will be
+ // used for attributes and which for elements. And that doesn't really
+ // matter.
+ rv = mNodeInfoManager->GetNodeInfo(localName, prefix, namespaceURI,
+ UINT16_MAX, getter_AddRefs(nodeInfo));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nodeInfos.AppendElement(nodeInfo);
+ }
+
+ // Document contents
+ uint32_t type;
+ while (NS_SUCCEEDED(rv)) {
+ rv = aStream->Read32(&type);
+ if (NS_FAILED(rv)) {
+ return rv;
+ break;
+ }
+
+ if ((nsXULPrototypeNode::Type)type == nsXULPrototypeNode::eType_PI) {
+ RefPtr<nsXULPrototypePI> pi = new nsXULPrototypePI();
+
+ rv = pi->Deserialize(aStream, this, mURI, &nodeInfos);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = AddProcessingInstruction(pi);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else if ((nsXULPrototypeNode::Type)type ==
+ nsXULPrototypeNode::eType_Element) {
+ rv = mRoot->Deserialize(aStream, this, mURI, &nodeInfos);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ break;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected prototype node type");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NotifyLoadDone();
+}
+
+static nsresult GetNodeInfos(nsXULPrototypeElement* aPrototype,
+ nsTArray<RefPtr<mozilla::dom::NodeInfo>>& aArray) {
+ if (aArray.IndexOf(aPrototype->mNodeInfo) == aArray.NoIndex) {
+ aArray.AppendElement(aPrototype->mNodeInfo);
+ }
+
+ // Search attributes
+ size_t i;
+ for (i = 0; i < aPrototype->mAttributes.Length(); ++i) {
+ RefPtr<mozilla::dom::NodeInfo> ni;
+ nsAttrName* name = &aPrototype->mAttributes[i].mName;
+ if (name->IsAtom()) {
+ ni = aPrototype->mNodeInfo->NodeInfoManager()->GetNodeInfo(
+ name->Atom(), nullptr, kNameSpaceID_None, nsINode::ATTRIBUTE_NODE);
+ } else {
+ ni = name->NodeInfo();
+ }
+
+ if (aArray.IndexOf(ni) == aArray.NoIndex) {
+ aArray.AppendElement(ni);
+ }
+ }
+
+ // Search children
+ for (i = 0; i < aPrototype->mChildren.Length(); ++i) {
+ nsXULPrototypeNode* child = aPrototype->mChildren[i];
+ if (child->mType == nsXULPrototypeNode::eType_Element) {
+ nsresult rv =
+ GetNodeInfos(static_cast<nsXULPrototypeElement*>(child), aArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULPrototypeDocument::Write(nsIObjectOutputStream* aStream) {
+ nsresult rv;
+
+ rv = aStream->WriteCompoundObject(mURI, NS_GET_IID(nsIURI), true);
+
+ // nsIPrincipal mNodeInfoManager->mPrincipal
+ nsAutoCString JSON;
+ mozilla::BasePrincipal::Cast(mNodeInfoManager->DocumentPrincipal())
+ ->ToJSON(JSON);
+ nsresult tmp = aStream->WriteStringZ(JSON.get());
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+#ifdef DEBUG
+ // XXX Worrisome if we're caching things without system principal.
+ if (!mNodeInfoManager->DocumentPrincipal()->IsSystemPrincipal()) {
+ NS_WARNING("Serializing document without system principal");
+ }
+#endif
+
+ tmp = aStream->WriteBoolean(mWasL10nCached);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ // mozilla::dom::NodeInfo table
+ nsTArray<RefPtr<mozilla::dom::NodeInfo>> nodeInfos;
+ if (mRoot) {
+ tmp = GetNodeInfos(mRoot, nodeInfos);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+
+ uint32_t nodeInfoCount = nodeInfos.Length();
+ tmp = aStream->Write32(nodeInfoCount);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ uint32_t i;
+ for (i = 0; i < nodeInfoCount; ++i) {
+ mozilla::dom::NodeInfo* nodeInfo = nodeInfos[i];
+ NS_ENSURE_TRUE(nodeInfo, NS_ERROR_FAILURE);
+
+ nsAutoString namespaceURI;
+ nodeInfo->GetNamespaceURI(namespaceURI);
+ tmp = aStream->WriteWStringZ(namespaceURI.get());
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ nsAutoString prefix;
+ nodeInfo->GetPrefix(prefix);
+ bool nullPrefix = DOMStringIsNull(prefix);
+ tmp = aStream->WriteBoolean(nullPrefix);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ if (!nullPrefix) {
+ tmp = aStream->WriteWStringZ(prefix.get());
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+
+ nsAutoString localName;
+ nodeInfo->GetName(localName);
+ tmp = aStream->WriteWStringZ(localName.get());
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+
+ // Now serialize the document contents
+ uint32_t count = mProcessingInstructions.Length();
+ for (i = 0; i < count; ++i) {
+ nsXULPrototypePI* pi = mProcessingInstructions[i];
+ tmp = pi->Serialize(aStream, this, &nodeInfos);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+
+ if (mRoot) {
+ tmp = mRoot->Serialize(aStream, this, &nodeInfos);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+
+ return rv;
+}
+
+//----------------------------------------------------------------------
+//
+
+nsresult nsXULPrototypeDocument::InitPrincipal(nsIURI* aURI,
+ nsIPrincipal* aPrincipal) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ mURI = aURI;
+ mNodeInfoManager->SetDocumentPrincipal(aPrincipal);
+ return NS_OK;
+}
+
+nsIURI* nsXULPrototypeDocument::GetURI() {
+ NS_ASSERTION(mURI, "null URI");
+ return mURI;
+}
+
+nsXULPrototypeElement* nsXULPrototypeDocument::GetRootElement() {
+ return mRoot;
+}
+
+void nsXULPrototypeDocument::SetRootElement(nsXULPrototypeElement* aElement) {
+ mRoot = aElement;
+}
+
+nsresult nsXULPrototypeDocument::AddProcessingInstruction(
+ nsXULPrototypePI* aPI) {
+ MOZ_ASSERT(aPI, "null ptr");
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier, or change the return type to void.
+ mProcessingInstructions.AppendElement(aPI);
+ return NS_OK;
+}
+
+const nsTArray<RefPtr<nsXULPrototypePI>>&
+nsXULPrototypeDocument::GetProcessingInstructions() const {
+ return mProcessingInstructions;
+}
+
+nsIPrincipal* nsXULPrototypeDocument::DocumentPrincipal() {
+ MOZ_ASSERT(mNodeInfoManager, "missing nodeInfoManager");
+ return mNodeInfoManager->DocumentPrincipal();
+}
+
+void nsXULPrototypeDocument::SetDocumentPrincipal(nsIPrincipal* aPrincipal) {
+ mNodeInfoManager->SetDocumentPrincipal(aPrincipal);
+}
+
+void nsXULPrototypeDocument::MarkInCCGeneration(uint32_t aCCGeneration) {
+ mCCGeneration = aCCGeneration;
+}
+
+nsNodeInfoManager* nsXULPrototypeDocument::GetNodeInfoManager() {
+ return mNodeInfoManager;
+}
+
+nsresult nsXULPrototypeDocument::AwaitLoadDone(Callback&& aCallback,
+ bool* aResult) {
+ nsresult rv = NS_OK;
+
+ *aResult = mLoaded;
+
+ if (!mLoaded) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier, or change the return type to void.
+ mPrototypeWaiters.AppendElement(std::move(aCallback));
+ }
+
+ return rv;
+}
+
+nsresult nsXULPrototypeDocument::NotifyLoadDone() {
+ // Call back to each XUL document that raced to start the same
+ // prototype document load, lost the race, but hit the XUL
+ // prototype cache because the winner filled the cache with
+ // the not-yet-loaded prototype object.
+
+ mLoaded = true;
+
+ for (uint32_t i = mPrototypeWaiters.Length(); i > 0;) {
+ --i;
+ mPrototypeWaiters[i]();
+ }
+ mPrototypeWaiters.Clear();
+
+ return NS_OK;
+}
+
+void nsXULPrototypeDocument::TraceProtos(JSTracer* aTrc) {
+ // Only trace the protos once per GC if we are marking.
+ if (aTrc->isMarkingTracer()) {
+ uint32_t currentGCNumber = aTrc->gcNumberForMarking();
+ if (mGCNumber == currentGCNumber) {
+ return;
+ }
+ mGCNumber = currentGCNumber;
+ }
+
+ if (mRoot) {
+ mRoot->TraceAllScripts(aTrc);
+ }
+}
+
+void nsXULPrototypeDocument::SetIsL10nCached(bool aIsCached) {
+ mWasL10nCached = aIsCached;
+}
+
+void nsXULPrototypeDocument::RebuildPrototypeFromElement(
+ nsXULPrototypeElement* aPrototype, Element* aElement, bool aDeep) {
+ aPrototype->mHasIdAttribute = aElement->HasID();
+ aPrototype->mHasClassAttribute = aElement->MayHaveClass();
+ aPrototype->mHasStyleAttribute = aElement->MayHaveStyle();
+ NodeInfo* oldNodeInfo = aElement->NodeInfo();
+ RefPtr<NodeInfo> newNodeInfo = mNodeInfoManager->GetNodeInfo(
+ oldNodeInfo->NameAtom(), oldNodeInfo->GetPrefixAtom(),
+ oldNodeInfo->NamespaceID(), nsINode::ELEMENT_NODE);
+ aPrototype->mNodeInfo = newNodeInfo;
+
+ // First replace the prototype attributes with the new ones from this element.
+ aPrototype->mAttributes.Clear();
+
+ uint32_t count = aElement->GetAttrCount();
+ nsXULPrototypeAttribute* protoAttr =
+ aPrototype->mAttributes.AppendElements(count);
+ for (uint32_t index = 0; index < count; index++) {
+ BorrowedAttrInfo attr = aElement->GetAttrInfoAt(index);
+
+ if (attr.mName->IsAtom()) {
+ protoAttr->mName.SetTo(attr.mName->Atom());
+ } else {
+ NodeInfo* oldNodeInfo = attr.mName->NodeInfo();
+ RefPtr<NodeInfo> newNodeInfo = mNodeInfoManager->GetNodeInfo(
+ oldNodeInfo->NameAtom(), oldNodeInfo->GetPrefixAtom(),
+ oldNodeInfo->NamespaceID(), nsINode::ATTRIBUTE_NODE);
+ protoAttr->mName.SetTo(newNodeInfo);
+ }
+ protoAttr->mValue.SetTo(*attr.mValue);
+
+ protoAttr++;
+ }
+
+ // Make sure the mIsAtom is correct in case this prototype element has been
+ // completely rebuilt.
+ CustomElementData* ceData = aElement->GetCustomElementData();
+ nsAtom* isAtom = ceData ? ceData->GetIs(aElement) : nullptr;
+ aPrototype->mIsAtom = isAtom;
+
+ if (aDeep) {
+ // We have to rebuild the prototype children from this element.
+ // First release the tree under this element.
+ aPrototype->ReleaseSubtree();
+
+ RefPtr<nsXULPrototypeNode>* children =
+ aPrototype->mChildren.AppendElements(aElement->GetChildCount());
+ for (nsIContent* child = aElement->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsElement()) {
+ Element* element = child->AsElement();
+ RefPtr<nsXULPrototypeElement> elemProto = new nsXULPrototypeElement;
+ RebuildPrototypeFromElement(elemProto, element, true);
+ *children = elemProto;
+ } else if (child->IsText()) {
+ Text* text = child->AsText();
+ RefPtr<nsXULPrototypeText> textProto = new nsXULPrototypeText();
+ text->AppendTextTo(textProto->mValue);
+ *children = textProto;
+ } else {
+ MOZ_ASSERT(false, "We handle only elements and text nodes here.");
+ }
+
+ children++;
+ }
+ }
+}
+
+void nsXULPrototypeDocument::RebuildL10nPrototype(Element* aElement,
+ bool aDeep) {
+ if (mWasL10nCached) {
+ return;
+ }
+
+ Document* doc = aElement->OwnerDoc();
+
+ nsAutoString id;
+ MOZ_RELEASE_ASSERT(aElement->GetAttr(nsGkAtoms::datal10nid, id));
+
+ if (!doc) {
+ return;
+ }
+
+ RefPtr<nsXULPrototypeElement> proto = doc->mL10nProtoElements.Get(aElement);
+ MOZ_RELEASE_ASSERT(proto);
+ RebuildPrototypeFromElement(proto, aElement, aDeep);
+}
diff --git a/dom/xul/nsXULPrototypeDocument.h b/dom/xul/nsXULPrototypeDocument.h
new file mode 100644
index 0000000000..3db185403e
--- /dev/null
+++ b/dom/xul/nsXULPrototypeDocument.h
@@ -0,0 +1,130 @@
+/* -*- 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/. */
+
+#ifndef nsXULPrototypeDocument_h__
+#define nsXULPrototypeDocument_h__
+
+#include "js/TracingAPI.h"
+#include "mozilla/Attributes.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsISerializable.h"
+#include "nsCycleCollectionParticipant.h"
+#include <functional>
+
+class nsAtom;
+class nsIPrincipal;
+class nsIURI;
+class nsNodeInfoManager;
+class nsXULPrototypeElement;
+class nsXULPrototypePI;
+
+namespace mozilla::dom {
+class Element;
+}
+
+/**
+ * A "prototype" document that stores shared document information
+ * for the XUL cache.
+ * Among other things, stores the tree of nsXULPrototype*
+ * objects, from which the real DOM tree is built later in
+ * PrototypeDocumentContentSink::ResumeWalk.
+ */
+class nsXULPrototypeDocument final : public nsISerializable {
+ public:
+ typedef std::function<void()> Callback;
+
+ // nsISupports interface
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ // nsISerializable interface
+ NS_DECL_NSISERIALIZABLE
+
+ nsresult InitPrincipal(nsIURI* aURI, nsIPrincipal* aPrincipal);
+ nsIURI* GetURI();
+
+ /**
+ * Get/set the root nsXULPrototypeElement of the document.
+ */
+ nsXULPrototypeElement* GetRootElement();
+ void SetRootElement(nsXULPrototypeElement* aElement);
+
+ /**
+ * Add a processing instruction to the prolog. Note that only
+ * PI nodes are currently stored in a XUL prototype document's
+ * prolog and that they're handled separately from the rest of
+ * prototype node tree.
+ *
+ * @param aPI an already adrefed PI proto to add. This method takes
+ * ownership of the passed PI.
+ */
+ nsresult AddProcessingInstruction(nsXULPrototypePI* aPI);
+ /**
+ * @note GetProcessingInstructions retains the ownership (the PI
+ * protos only get deleted when the proto document is deleted)
+ */
+ const nsTArray<RefPtr<nsXULPrototypePI> >& GetProcessingInstructions() const;
+
+ nsIPrincipal* DocumentPrincipal();
+ void SetDocumentPrincipal(nsIPrincipal* aPrincipal);
+
+ /**
+ * If current prototype document has not yet finished loading,
+ * appends aDocument to the list of documents to notify (via
+ * PrototypeDocumentContentSink::OnPrototypeLoadDone()) and
+ * sets aLoaded to false. Otherwise sets aLoaded to true.
+ */
+ nsresult AwaitLoadDone(Callback&& aCallback, bool* aResult);
+
+ /**
+ * Notifies each document registered via AwaitLoadDone on this
+ * prototype document that the prototype has finished loading.
+ * The notification is performed by calling
+ * PrototypeDocumentContentSink::OnPrototypeLoadDone on the
+ * registered documents.
+ */
+ nsresult NotifyLoadDone();
+
+ nsNodeInfoManager* GetNodeInfoManager();
+
+ void MarkInCCGeneration(uint32_t aCCGeneration);
+
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsXULPrototypeDocument)
+
+ void TraceProtos(JSTracer* aTrc);
+
+ bool WasL10nCached() { return mWasL10nCached; };
+
+ void SetIsL10nCached(bool aIsCached);
+ void RebuildPrototypeFromElement(nsXULPrototypeElement* aPrototype,
+ mozilla::dom::Element* aElement, bool aDeep);
+ void RebuildL10nPrototype(mozilla::dom::Element* aElement, bool aDeep);
+
+ protected:
+ nsCOMPtr<nsIURI> mURI;
+ RefPtr<nsXULPrototypeElement> mRoot;
+ nsTArray<RefPtr<nsXULPrototypePI> > mProcessingInstructions;
+
+ bool mLoaded;
+ nsTArray<Callback> mPrototypeWaiters;
+
+ RefPtr<nsNodeInfoManager> mNodeInfoManager;
+
+ uint32_t mCCGeneration;
+ uint32_t mGCNumber;
+
+ nsXULPrototypeDocument();
+ virtual ~nsXULPrototypeDocument();
+ nsresult Init();
+
+ friend NS_IMETHODIMP NS_NewXULPrototypeDocument(
+ nsXULPrototypeDocument** aResult);
+
+ static uint32_t gRefCnt;
+ bool mWasL10nCached;
+};
+
+#endif // nsXULPrototypeDocument_h__
diff --git a/dom/xul/nsXULSortService.cpp b/dom/xul/nsXULSortService.cpp
new file mode 100644
index 0000000000..22bb76ed3f
--- /dev/null
+++ b/dom/xul/nsXULSortService.cpp
@@ -0,0 +1,397 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+/*
+ This file provides the implementation for the sort service manager.
+ */
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+#include "nsXULContentUtils.h"
+#include "nsString.h"
+#include "nsQuickSort.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsXULSortService.h"
+#include "nsXULElement.h"
+#include "nsICollation.h"
+#include "nsTArray.h"
+#include "nsUnicharUtils.h"
+
+#include "mozilla/dom/Element.h"
+
+using mozilla::dom::Element;
+const unsigned long SORT_COMPARECASE = 0x0001;
+const unsigned long SORT_INTEGER = 0x0100;
+
+enum nsSortState_direction {
+ nsSortState_descending,
+ nsSortState_ascending,
+ nsSortState_natural
+};
+
+// the sort state holds info about the current sort
+struct MOZ_STACK_CLASS nsSortState final {
+ bool initialized;
+ MOZ_INIT_OUTSIDE_CTOR bool invertSort;
+
+ uint32_t sortHints;
+
+ MOZ_INIT_OUTSIDE_CTOR nsSortState_direction direction;
+ nsAutoString sort;
+ nsTArray<RefPtr<nsAtom>> sortKeys;
+
+ nsCOMPtr<nsIContent> lastContainer;
+ MOZ_INIT_OUTSIDE_CTOR bool lastWasFirst, lastWasLast;
+
+ nsSortState() : initialized(false), sortHints(0) {}
+};
+
+// information about a particular item to be sorted
+struct contentSortInfo {
+ nsCOMPtr<nsIContent> content;
+ nsCOMPtr<nsIContent> parent;
+ void swap(contentSortInfo& other) {
+ content.swap(other.content);
+ parent.swap(other.parent);
+ }
+};
+
+/**
+ * Set sortActive and sortDirection attributes on a tree column when a sort
+ * is done. The column to change is the one with a sort attribute that
+ * matches the sort key. The sort attributes are removed from the other
+ * columns.
+ */
+static void SetSortColumnHints(nsIContent* content,
+ const nsAString& sortResource,
+ const nsAString& sortDirection) {
+ // set sort info on current column. This ensures that the
+ // column header sort indicator is updated properly.
+ for (nsIContent* child = content->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsXULElement(nsGkAtoms::treecols)) {
+ SetSortColumnHints(child, sortResource, sortDirection);
+ } else if (child->IsXULElement(nsGkAtoms::treecol)) {
+ nsAutoString value;
+ child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, value);
+ if (value == sortResource) {
+ child->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::sortActive,
+ u"true"_ns, true);
+
+ child->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection,
+ sortDirection, true);
+ // Note: don't break out of loop; want to set/unset
+ // attribs on ALL sort columns
+ } else if (!value.IsEmpty()) {
+ child->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::sortActive,
+ true);
+ child->AsElement()->UnsetAttr(kNameSpaceID_None,
+ nsGkAtoms::sortDirection, true);
+ }
+ }
+ }
+}
+
+/**
+ * Set sort and sortDirection attributes when a sort is done.
+ */
+static void SetSortHints(Element* aElement, nsSortState* aSortState) {
+ // set sort and sortDirection attributes when is sort is done
+ aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::sort, aSortState->sort, true);
+
+ nsAutoString direction;
+ if (aSortState->direction == nsSortState_descending)
+ direction.AssignLiteral("descending");
+ else if (aSortState->direction == nsSortState_ascending)
+ direction.AssignLiteral("ascending");
+ aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, direction,
+ true);
+
+ // for trees, also set the sort info on the currently sorted column
+ if (aElement->IsXULElement(nsGkAtoms::tree)) {
+ if (aSortState->sortKeys.Length() >= 1) {
+ nsAutoString sortkey;
+ aSortState->sortKeys[0]->ToString(sortkey);
+ SetSortColumnHints(aElement, sortkey, direction);
+ }
+ }
+}
+
+/**
+ * Determine the list of items which need to be sorted. This is determined
+ * in the following way:
+ * - for elements that have a content builder, get its list of generated
+ * results
+ * - otherwise, for trees, get the child treeitems
+ * - otherwise, get the direct children
+ */
+static nsresult GetItemsToSort(nsIContent* aContainer, nsSortState* aSortState,
+ nsTArray<contentSortInfo>& aSortItems) {
+ // Get the children. For trees, get the treechildren element and
+ // use that as the parent
+ RefPtr<Element> treechildren;
+ if (aContainer->IsXULElement(nsGkAtoms::tree)) {
+ nsXULContentUtils::FindChildByTag(aContainer, kNameSpaceID_XUL,
+ nsGkAtoms::treechildren,
+ getter_AddRefs(treechildren));
+ if (!treechildren) return NS_OK;
+
+ aContainer = treechildren;
+ }
+
+ for (nsIContent* child = aContainer->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ contentSortInfo* cinfo = aSortItems.AppendElement();
+ if (!cinfo) return NS_ERROR_OUT_OF_MEMORY;
+
+ cinfo->content = child;
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Compares aLeft and aRight and returns < 0, 0, or > 0. The sort
+ * hints are checked for case matching and integer sorting.
+ */
+static int32_t CompareValues(const nsAString& aLeft, const nsAString& aRight,
+ uint32_t aSortHints) {
+ if (aSortHints & SORT_INTEGER) {
+ nsresult err;
+ int32_t leftint = PromiseFlatString(aLeft).ToInteger(&err);
+ if (NS_SUCCEEDED(err)) {
+ int32_t rightint = PromiseFlatString(aRight).ToInteger(&err);
+ if (NS_SUCCEEDED(err)) {
+ return leftint - rightint;
+ }
+ }
+ // if they aren't integers, just fall through and compare strings
+ }
+
+ if (aSortHints & SORT_COMPARECASE) {
+ return ::Compare(aLeft, aRight);
+ }
+
+ nsICollation* collation = nsXULContentUtils::GetCollation();
+ if (collation) {
+ int32_t result;
+ collation->CompareString(nsICollation::kCollationCaseInSensitive, aLeft,
+ aRight, &result);
+ return result;
+ }
+
+ return ::Compare(aLeft, aRight, nsCaseInsensitiveStringComparator);
+}
+
+static int testSortCallback(const void* data1, const void* data2,
+ void* privateData) {
+ /// Note: testSortCallback is a small C callback stub for NS_QuickSort
+ contentSortInfo* left = (contentSortInfo*)data1;
+ contentSortInfo* right = (contentSortInfo*)data2;
+ nsSortState* sortState = (nsSortState*)privateData;
+
+ int32_t sortOrder = 0;
+
+ int32_t length = sortState->sortKeys.Length();
+ for (int32_t t = 0; t < length; t++) {
+ // compare attributes. Ignore namespaces for now.
+ nsAutoString leftstr, rightstr;
+ if (left->content->IsElement()) {
+ left->content->AsElement()->GetAttr(kNameSpaceID_None,
+ sortState->sortKeys[t], leftstr);
+ }
+ if (right->content->IsElement()) {
+ right->content->AsElement()->GetAttr(kNameSpaceID_None,
+ sortState->sortKeys[t], rightstr);
+ }
+
+ sortOrder = CompareValues(leftstr, rightstr, sortState->sortHints);
+ }
+
+ if (sortState->direction == nsSortState_descending) sortOrder = -sortOrder;
+
+ return sortOrder;
+}
+
+/**
+ * Given a list of sortable items, reverse the list. This is done
+ * when simply changing the sort direction for the same key.
+ */
+static nsresult InvertSortInfo(nsTArray<contentSortInfo>& aData, int32_t aStart,
+ int32_t aNumItems) {
+ if (aNumItems > 1) {
+ // reverse the items in the array starting from aStart
+ int32_t upPoint = (aNumItems + 1) / 2 + aStart;
+ int32_t downPoint = (aNumItems - 2) / 2 + aStart;
+ int32_t half = aNumItems / 2;
+ while (half-- > 0) {
+ aData[downPoint--].swap(aData[upPoint++]);
+ }
+ }
+ return NS_OK;
+}
+
+/**
+ * Sort a container using the supplied sort state details.
+ */
+static nsresult SortContainer(nsIContent* aContainer, nsSortState* aSortState) {
+ nsTArray<contentSortInfo> items;
+ nsresult rv = GetItemsToSort(aContainer, aSortState, items);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numResults = items.Length();
+ if (!numResults) return NS_OK;
+
+ uint32_t i;
+
+ // if the items are just being inverted, that is, just switching between
+ // ascending and descending, just reverse the list.
+ if (aSortState->invertSort)
+ InvertSortInfo(items, 0, numResults);
+ else
+ NS_QuickSort((void*)items.Elements(), numResults, sizeof(contentSortInfo),
+ testSortCallback, (void*)aSortState);
+
+ // first remove the items from the old positions
+ for (i = 0; i < numResults; i++) {
+ nsIContent* child = items[i].content;
+ nsIContent* parent = child->GetParent();
+
+ if (parent) {
+ // remember the parent so that it can be reinserted back
+ // into the same parent. This is necessary as multiple rules
+ // may generate results which get placed in different locations.
+ items[i].parent = parent;
+ parent->RemoveChildNode(child, true);
+ }
+ }
+
+ // now add the items back in sorted order
+ for (i = 0; i < numResults; i++) {
+ nsIContent* child = items[i].content;
+ nsIContent* parent = items[i].parent;
+ if (parent) {
+ parent->AppendChildTo(child, true);
+
+ // if it's a container in a tree or menu, find its children,
+ // and sort those also
+ if (!child->IsElement() || !child->AsElement()->AttrValueIs(
+ kNameSpaceID_None, nsGkAtoms::container,
+ nsGkAtoms::_true, eCaseMatters))
+ continue;
+
+ for (nsIContent* grandchild = child->GetFirstChild(); grandchild;
+ grandchild = grandchild->GetNextSibling()) {
+ mozilla::dom::NodeInfo* ni = grandchild->NodeInfo();
+ nsAtom* localName = ni->NameAtom();
+ if (ni->NamespaceID() == kNameSpaceID_XUL &&
+ (localName == nsGkAtoms::treechildren ||
+ localName == nsGkAtoms::menupopup)) {
+ SortContainer(grandchild, aSortState);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Initialize sort information from attributes specified on the container,
+ * the sort key and sort direction.
+ *
+ * @param aRootElement the element that contains sort attributes
+ * @param aContainer the container to sort, usually equal to aRootElement
+ * @param aSortKey space separated list of sort keys
+ * @param aSortDirection direction to sort in
+ * @param aSortState structure filled in with sort data
+ */
+static nsresult InitializeSortState(Element* aRootElement, Element* aContainer,
+ const nsAString& aSortKey,
+ const nsAString& aSortHints,
+ nsSortState* aSortState) {
+ // used as an optimization for the content builder
+ if (aContainer != aSortState->lastContainer.get()) {
+ aSortState->lastContainer = aContainer;
+ aSortState->lastWasFirst = false;
+ aSortState->lastWasLast = false;
+ }
+
+ // The sort attribute is of the form: sort="key1 key2 ..."
+ nsAutoString sort(aSortKey);
+ aSortState->sortKeys.Clear();
+ nsWhitespaceTokenizer tokenizer(sort);
+ while (tokenizer.hasMoreTokens()) {
+ RefPtr<nsAtom> keyatom = NS_Atomize(tokenizer.nextToken());
+ NS_ENSURE_TRUE(keyatom, NS_ERROR_OUT_OF_MEMORY);
+ aSortState->sortKeys.AppendElement(keyatom);
+ }
+
+ aSortState->sort.Assign(sort);
+ aSortState->direction = nsSortState_natural;
+
+ bool noNaturalState = false;
+ nsWhitespaceTokenizer hintsTokenizer(aSortHints);
+ while (hintsTokenizer.hasMoreTokens()) {
+ const nsDependentSubstring& token(hintsTokenizer.nextToken());
+ if (token.EqualsLiteral("comparecase"))
+ aSortState->sortHints |= SORT_COMPARECASE;
+ else if (token.EqualsLiteral("integer"))
+ aSortState->sortHints |= SORT_INTEGER;
+ else if (token.EqualsLiteral("descending"))
+ aSortState->direction = nsSortState_descending;
+ else if (token.EqualsLiteral("ascending"))
+ aSortState->direction = nsSortState_ascending;
+ else if (token.EqualsLiteral("twostate"))
+ noNaturalState = true;
+ }
+
+ // if the twostate flag was set, the natural order is skipped and only
+ // ascending and descending are allowed
+ if (aSortState->direction == nsSortState_natural && noNaturalState) {
+ aSortState->direction = nsSortState_ascending;
+ }
+
+ // set up sort order info
+ aSortState->invertSort = false;
+
+ nsAutoString existingsort;
+ aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, existingsort);
+ nsAutoString existingsortDirection;
+ aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection,
+ existingsortDirection);
+
+ // if just switching direction, set the invertSort flag
+ if (sort.Equals(existingsort)) {
+ if (aSortState->direction == nsSortState_descending) {
+ if (existingsortDirection.EqualsLiteral("ascending"))
+ aSortState->invertSort = true;
+ } else if (aSortState->direction == nsSortState_ascending &&
+ existingsortDirection.EqualsLiteral("descending")) {
+ aSortState->invertSort = true;
+ }
+ }
+
+ aSortState->initialized = true;
+
+ return NS_OK;
+}
+
+nsresult mozilla::XULWidgetSort(Element* aNode, const nsAString& aSortKey,
+ const nsAString& aSortHints) {
+ nsSortState sortState;
+ nsresult rv =
+ InitializeSortState(aNode, aNode, aSortKey, aSortHints, &sortState);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // store sort info in attributes on content
+ SetSortHints(aNode, &sortState);
+ rv = SortContainer(aNode, &sortState);
+
+ return rv;
+}
diff --git a/dom/xul/nsXULSortService.h b/dom/xul/nsXULSortService.h
new file mode 100644
index 0000000000..c087cb572a
--- /dev/null
+++ b/dom/xul/nsXULSortService.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+/*
+ This sort service is used to sort content by attribute.
+ */
+
+#ifndef nsXULSortService_h
+#define nsXULSortService_h
+
+#include "nsAString.h"
+#include "nsError.h"
+
+namespace mozilla {
+
+namespace dom {
+class Element;
+} // namespace dom
+
+/**
+ * Sort the contents of the widget containing <code>aNode</code>
+ * using <code>aSortKey</code> as the comparison key, and
+ * <code>aSortDirection</code> as the direction.
+ *
+ * @param aNode A node in the XUL widget whose children are to be sorted.
+ * @param aSortKey The value to be used as the comparison key.
+ * @param aSortHints One or more hints as to how to sort:
+ *
+ * ascending: to sort the contents in ascending order
+ * descending: to sort the contents in descending order
+ * comparecase: perform case sensitive comparisons
+ * integer: treat values as integers, non-integers are compared as strings
+ * twostate: don't allow the natural (unordered state)
+ */
+nsresult XULWidgetSort(dom::Element* aNode, const nsAString& aSortKey,
+ const nsAString& aSortHints);
+
+} // namespace mozilla
+
+#endif // nsXULSortService_h
diff --git a/dom/xul/test/.eslintrc.js b/dom/xul/test/.eslintrc.js
new file mode 100644
index 0000000000..721e0938af
--- /dev/null
+++ b/dom/xul/test/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/mochitest-test", "plugin:mozilla/chrome-test"],
+};
diff --git a/dom/xul/test/398289-resource.xhtml b/dom/xul/test/398289-resource.xhtml
new file mode 100644
index 0000000000..23286fa461
--- /dev/null
+++ b/dom/xul/test/398289-resource.xhtml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<dialog buttonlabelaccept="OK"
+ buttonlabelcancel="Cancel">
+
+ <script>
+ document.addEventListener("dialogaccept", function() { alert('OK') });
+ document.addEventListener("dialogcancel", function() { alert('Cancel') });
+ </script>
+
+ <tabbox id="test" flex="1" persist="selectedIndex">
+
+ <tabs>
+ <tab label="One"/>
+ <tab label="Two"/>
+ </tabs>
+
+ <tabpanels flex="1">
+
+ <vbox flex="1">
+ <description>Text for tab ONE</description>
+ <description class="text-link"
+ onclick="window.open('https://bugzilla.mozilla.org/show_bug.cgi?id=398289');">(test case for bug 398289)</description>
+ <tree>
+ <treecols>
+ <treecol label="Header" flex="1"/>
+ </treecols>
+ </tree>
+ </vbox>
+
+ <vbox flex="1">
+ <description>Text for tab TWO</description>
+ <description>(When the document is reloaded, this content gets replaced by the one from the first tab.)</description>
+ </vbox>
+
+ </tabpanels>
+
+ </tabbox>
+
+ <box height="1000"/> <!-- Push dialog buttons out of sight so that the animated default button isn't part of the snapshot -->
+
+</dialog>
+</window>
diff --git a/dom/xul/test/chrome.ini b/dom/xul/test/chrome.ini
new file mode 100644
index 0000000000..69e5f9ea63
--- /dev/null
+++ b/dom/xul/test/chrome.ini
@@ -0,0 +1,26 @@
+[DEFAULT]
+support-files =
+ 398289-resource.xhtml
+ window_bug583948.xhtml
+ window_bug757137.xhtml
+
+[test_bug199692.xhtml]
+[test_bug311681.xhtml]
+[test_bug391002.xhtml]
+[test_bug398289.html]
+[test_bug403868.xhtml]
+[test_bug418216.xhtml]
+[test_bug445177.xhtml]
+[test_bug449457.xhtml]
+[test_bug468176.xhtml]
+[test_bug583948.xhtml]
+[test_bug757137.xhtml]
+[test_bug775972.xhtml]
+[test_bug1070049_throw_from_script.xhtml]
+[test_html_template.xhtml]
+[test_import_xul_to_content.xhtml]
+[test_bug1290965.xhtml]
+[test_bug749367.xhtml]
+[test_xul_tabindex_focus.xhtml]
+[test_bug1686822.xhtml]
+ support-files = window_bug1686822.xhtml
diff --git a/dom/xul/test/file_bug236853.rdf b/dom/xul/test/file_bug236853.rdf
new file mode 100644
index 0000000000..8d17bf6912
--- /dev/null
+++ b/dom/xul/test/file_bug236853.rdf
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:ex="http://www.ex.org/ex-rdf#">
+
+ <rdf:Description about="urn:root">
+ <ex:nodes>
+ <rdf:Bag about="http://www.ex.org/nodes">
+ <rdf:li>
+ <rdf:Description about="http://www.ex.org/nodes/A"/>
+ </rdf:li>
+ </rdf:Bag>
+ </ex:nodes>
+ </rdf:Description>
+</rdf:RDF>
diff --git a/dom/xul/test/mochitest.ini b/dom/xul/test/mochitest.ini
new file mode 100644
index 0000000000..acaecd9897
--- /dev/null
+++ b/dom/xul/test/mochitest.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+
+[test_bug486990.xhtml]
+skip-if = toolkit == 'android' #TIMED_OUT
+
+[test_disable_scroll_frame_plain.html]
diff --git a/dom/xul/test/test_bug1070049_throw_from_script.xhtml b/dom/xul/test/test_bug1070049_throw_from_script.xhtml
new file mode 100644
index 0000000000..b9c635efd6
--- /dev/null
+++ b/dom/xul/test/test_bug1070049_throw_from_script.xhtml
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1070049
+-->
+<window title="Mozilla Bug 1070049"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 1070049 **/
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ // Prevent the test from failing when the exception hits onerror.
+ SimpleTest.expectUncaughtException();
+
+ // Tell the test to expect exactly one console error with the given parameters,
+ // with SimpleTest.finish as a continuation function.
+ SimpleTest.monitorConsole(SimpleTest.finish, [{errorMessage: new RegExp('flimfniffle')}]);
+
+ // Schedule the console accounting (and continuation) to run next, right
+ // after we throw (below).
+ SimpleTest.executeSoon(SimpleTest.endMonitorConsole);
+
+ // Throw.
+ throw "flimfniffle";
+ });
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1070049"
+ target="_blank">Mozilla Bug 1070049</a>
+ </body>
+</window>
diff --git a/dom/xul/test/test_bug1290965.xhtml b/dom/xul/test/test_bug1290965.xhtml
new file mode 100644
index 0000000000..53860082e0
--- /dev/null
+++ b/dom/xul/test/test_bug1290965.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:h="http://www.w3.org/1999/xhtml">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <toolbarbutton oncommand="++countera;" id="a">A</toolbarbutton>
+ <toolbarbutton oncommand="++counterb;" id="b">B</toolbarbutton>
+ <script type="text/javascript">
+ <![CDATA[
+ let aEl = document.getElementById('a');
+ let bEl = document.getElementById('b');
+ let countera = 0;
+ let counterb = 0;
+
+ aEl.addEventListener('click', function (aEvent) {
+ aEvent.preventDefault();
+ let cmdEvent = document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent("command", true, true, window, 0,
+ aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey,
+ aEvent.metaKey, null, aEvent.mozInputSource);
+ aEvent.currentTarget.dispatchEvent(cmdEvent);
+ });
+
+ bEl.addEventListener('click', function (aEvent) {
+ let cmdEvent = document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent("command", true, true, window, 0,
+ aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey,
+ aEvent.metaKey, null, aEvent.mozInputSource);
+ aEvent.currentTarget.dispatchEvent(cmdEvent);
+ });
+
+ bEl.click();
+ aEl.click();
+
+ is(countera, 1, "Counter should be one as event fires once");
+ is(counterb, 2, "Counter should be two as event fires twice");
+ ]]>
+ </script>
+</window>
diff --git a/dom/xul/test/test_bug1686822.xhtml b/dom/xul/test/test_bug1686822.xhtml
new file mode 100644
index 0000000000..cdad585d00
--- /dev/null
+++ b/dom/xul/test/test_bug1686822.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <html:script><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ let chromeWindow = browsingContext.topChromeWindow;
+
+ let resolve;
+ let i = 0;
+ chromeWindow.moduleScriptRan = function() {
+ ok(true, "Module script executed: " + ++i)
+ resolve();
+ resolve = null;
+ }
+
+ function testOnce() {
+ let currentWin;
+ return new Promise(r => {
+ currentWin = chromeWindow.open("window_bug1686822.xhtml", "", "chrome");
+ resolve = r;
+ }).then(function() {
+ currentWin.close();
+ });
+ }
+
+ (async function() {
+ // The first and second loads are different so make sure we test both code paths.
+ await testOnce();
+ await testOnce();
+ delete chromeWindow.moduleScriptRan;
+ SimpleTest.finish();
+ }());
+ ]]></html:script>
+</window>
diff --git a/dom/xul/test/test_bug199692.xhtml b/dom/xul/test/test_bug199692.xhtml
new file mode 100644
index 0000000000..98982efd74
--- /dev/null
+++ b/dom/xul/test/test_bug199692.xhtml
@@ -0,0 +1,102 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=199692
+-->
+<window title="Test for Bug 199692"
+ id="test_bug199692.xhtml"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript">
+ <![CDATA[
+ customElements.define("test-xul-element", class CustomElement extends XULElement {
+ constructor() {
+ super();
+ const template = document.getElementById("template");
+ const shadowRoot = this.attachShadow({mode: "open"})
+ .appendChild(template.content.cloneNode(true));
+ }
+ });
+ ]]>
+ </script>
+<body id="body" xmlns="http://www.w3.org/1999/xhtml">
+<template id="template">
+ <xul:label id="anon-label" value="ANON"/>
+</template>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=199692">Mozilla Bug 199692</a>
+
+<vbox id="content" style="position: relative;"
+xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <xul:label id="non-anon-label" value="a textbox!:" control="textbox"/>
+ <html:textarea id="textbox" rows="4"/>
+ <xul:radiogroup style="outline: 2px solid orange;">
+ <xul:radio id="unselected-radio" label="Orange" style="outline: 2px solid red;"/>
+ <xul:radio id="selected-radio" label="Violet" selected="true"/>
+ <xul:radio id="disabled-radio" label="Yellow" disabled="true"/>
+ </xul:radiogroup>
+ <test-xul-element id="bound" style="border: 2px solid green;"></test-xul-element>
+</vbox>
+<pre id="test">
+ <script class="testbody" type="text/javascript">
+<![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ // Before onload, XUL docs have no root frame.
+ is(document.elementFromPoint(10,10), null,
+ "Calls to elementFromPoint before onload should return null");
+
+ var d = 10;
+ function middle(e) {
+ return { "x": e.getBoundingClientRect().x + e.getBoundingClientRect().width/2,
+ "y": e.getBoundingClientRect().y + e.getBoundingClientRect().height/2 };
+ }
+ function lower_right(e) {
+ return { "x": e.getBoundingClientRect().x + e.getBoundingClientRect().width - d,
+ "y": e.getBoundingClientRect().y + e.getBoundingClientRect().height - d };
+ }
+ function upper_left(e) {
+ return { "x": e.getBoundingClientRect().x + d,
+ "y": e.getBoundingClientRect().y + d };
+ }
+ function scrollbar_button(e) { // a bit down from upper right
+ return { "x": e.getBoundingClientRect().x + e.getBoundingClientRect().width - d,
+ "y": e.getBoundingClientRect().y + d + 15 };
+ }
+
+ function test(ptFunc, id, message) {
+ var pt = ptFunc($(id));
+ var e = document.elementFromPoint(pt.x, pt.y);
+ ok(e != null, message + " (returned null)");
+ is(e.id, id, message);
+ }
+
+ function do_test() {
+ // Avoid hardcoding x,y pixel values, to better deal with differing default
+ // font sizes or other layout defaults.
+
+ test(middle, 'textbox', "Point within textbox should return textbox element");
+ test(lower_right, 'textbox', "Point on textbox's scrollbar should return textbox element");
+ test(scrollbar_button, 'textbox', "Point on textbox's scrollbar button should return textbox element");
+ test(middle, 'non-anon-label', "Point on label should return label");
+ test(upper_left, 'bound', "Point on custom element content should return custom element");
+
+ SimpleTest.finish();
+ }
+ $("textbox").setAttribute("value",
+ "lorem ipsum dolor sit amet " +
+ "lorem ipsum dolor sit amet " +
+ "lorem ipsum dolor sit amet " +
+ "lorem ipsum dolor sit amet " +
+ "lorem ipsum dolor sit amet " +
+ "lorem ipsum dolor sit amet " +
+ "lorem ipsum dolor sit amet "); // force scrollbars to appear
+ addLoadEvent(do_test);
+]]>
+ </script>
+</pre>
+</body>
+</window>
diff --git a/dom/xul/test/test_bug311681.xhtml b/dom/xul/test/test_bug311681.xhtml
new file mode 100644
index 0000000000..87368280df
--- /dev/null
+++ b/dom/xul/test/test_bug311681.xhtml
@@ -0,0 +1,106 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=311681
+-->
+<window title="Mozilla Bug 311681"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=311681">Mozilla Bug 311681</a>
+<script class="testbody" type="text/javascript">
+<![CDATA[
+ // Setup script
+ SimpleTest.waitForExplicitFinish();
+
+ // Make sure to trigger the hashtable case by asking for enough elements
+ // by ID.
+ for (var i = 0; i < 256; ++i) {
+ var x = document.getElementById(i);
+ }
+
+ // save off the document.getElementById function, since getting it as a
+ // property off the document it causes a content flush.
+ var fun = document.getElementById;
+
+ // Slot for our initial element with id "content"
+ var testNode;
+
+ function getCont() {
+ return fun.call(document, "content");
+ }
+
+ function testClone() {
+ // Test to make sure that if we have multiple nodes with the same ID in
+ // a document we don't forget about one of them when the other is
+ // removed.
+ var newParent = $("display");
+ var node = testNode.cloneNode(true);
+ isnot(node, testNode, "Clone should be a different node");
+
+ newParent.appendChild(node);
+
+ // Check what getElementById returns, no flushing
+ is(getCont(), node, "Should be getting new node pre-flush 1")
+
+ // Trigger a layout flush, just in case.
+ var itemHeight = newParent.offsetHeight/10;
+
+ // Check what getElementById returns now.
+ is(getCont(), node, "Should be getting new node post-flush 1")
+
+ clear(newParent);
+
+ // Check what getElementById returns, no flushing
+ is(getCont(), testNode, "Should be getting orig node pre-flush 2");
+
+ // Trigger a layout flush, just in case.
+ var itemHeight = newParent.offsetHeight/10;
+
+ // Check what getElementById returns now.
+ is(getCont(), testNode, "Should be getting orig node post-flush 2");
+
+ node = testNode.cloneNode(true);
+ newParent.appendChild(node);
+ testNode.remove();
+
+ // Check what getElementById returns, no flushing
+ is(getCont(), node, "Should be getting clone pre-flush");
+
+ // Trigger a layout flush, just in case.
+ var itemHeight = newParent.offsetHeight/10;
+
+ // Check what getElementById returns now.
+ is(getCont(), node, "Should be getting clone post-flush");
+
+ }
+
+ function clear(node) {
+ while (node.hasChildNodes()) {
+ node.firstChild.remove();
+ }
+ }
+
+ addLoadEvent(testClone);
+ addLoadEvent(SimpleTest.finish);
+]]>
+</script>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <script class="testbody" type="text/javascript">
+ <![CDATA[
+ testNode = fun.call(document, "content");
+ // Needs incremental XML parser
+ isnot(testNode, null, "Should have node here");
+ ]]>
+ </script>
+</div>
+<pre id="test">
+</pre>
+</body>
+
+</window>
diff --git a/dom/xul/test/test_bug391002.xhtml b/dom/xul/test/test_bug391002.xhtml
new file mode 100644
index 0000000000..da8c8b4d35
--- /dev/null
+++ b/dom/xul/test/test_bug391002.xhtml
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=391002
+-->
+<window title="Mozilla Bug 391002"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=391002"
+ target="_blank">Mozilla Bug 391002</a>
+ </body>
+
+ <button id="btn1" command="cmd1"/>
+
+ <button id="btn2">
+ <observes id="observes" element="cmd1" attribute="label"/>
+ </button>
+
+ <commandset>
+ <command id="cmd1" label="cmd1"/>
+ <command id="cmd2" label="cmd2"/>
+ </commandset>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 391002 **/
+
+ $("btn1").setAttribute("command", "cmd2");
+ is($("btn1").getAttribute("label"), $("cmd2").getAttribute("label"))
+
+ $("observes").setAttribute("element", "cmd2");
+ is($("btn2").getAttribute("label"), $("cmd2").getAttribute("label"))
+
+ ]]></script>
+</window>
+
diff --git a/dom/xul/test/test_bug398289.html b/dom/xul/test/test_bug398289.html
new file mode 100644
index 0000000000..eae7f2d923
--- /dev/null
+++ b/dom/xul/test/test_bug398289.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html style="height: 100%">
+<head>
+ <title>Test for bug 398289</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body style="height: 100%" onload="setTimeout(onBodyLoad, 0);">
+ <iframe id="test" src="398289-resource.xhtml" width="100%" height="100%">
+ </iframe>
+
+ <script class="testbody" type="text/javascript">
+ var snap1, snap2;
+
+ SimpleTest.waitForExplicitFinish();
+
+ async function onBodyLoad() {
+ window.frames[0].document.getElementById("test").selectedIndex = 0;
+ window.frames[0].document.getElementById("test").selectedIndex = 1;
+
+ snap1 = await snapshotWindow(window);
+
+ document.getElementById("test").onload = onFrameLoad;
+ window.frames[0].location.reload();
+ }
+
+ async function onFrameLoad() {
+ snap2 = await snapshotWindow(window);
+
+ var equal, str1, str2;
+ [equal, str1, str2] = compareSnapshots(snap1, snap2, true);
+
+ ok(equal, "persistent attribute in tab box broken, expected: "+str1+" got: "+str2);
+
+ SimpleTest.finish();
+ }
+ </script>
+</body>
+</html>
diff --git a/dom/xul/test/test_bug403868.xhtml b/dom/xul/test/test_bug403868.xhtml
new file mode 100644
index 0000000000..c2427bb644
--- /dev/null
+++ b/dom/xul/test/test_bug403868.xhtml
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=403868
+-->
+<window title="Mozilla Bug 403868"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=403868"
+ target="_blank">Mozilla Bug 403868</a>
+ <div id="content" style="display: none"/>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 403868 **/
+function createSpan(id, insertionPoint) {
+ var s = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ s.id = id;
+ $("content").insertBefore(s, insertionPoint);
+ return s;
+}
+
+var s1a = createSpan("test1", null);
+is(document.getElementById("test1"), s1a,
+ "Only one span with id=test1 in the tree; should work!");
+
+var s2a = createSpan("test1", null);
+is(document.getElementById("test1"), s1a,
+ "Appending span with id=test1 doesn't change which one comes first");
+
+var s3a = createSpan("test1", s2a);
+is(document.getElementById("test1"), s1a,
+ "Inserting span with id=test1 not at the beginning; doesn't matter");
+
+var s4a = createSpan("test1", s1a);
+is(document.getElementById("test1"), s4a,
+ "Inserting span with id=test1 at the beginning changes which one is first");
+
+s4a.remove();
+is(document.getElementById("test1"), s1a,
+ "First-created span with id=test1 is first again");
+
+s1a.remove();
+is(document.getElementById("test1"), s3a,
+ "Third-created span with id=test1 is first now");
+
+// Start the id hashtable
+for (var i = 0; i < 256; ++i) {
+ document.getElementById("no-such-id-in-the-document" + i);
+}
+
+var s1b = createSpan("test2", null);
+is(document.getElementById("test2"), s1b,
+ "Only one span with id=test2 in the tree; should work!");
+
+var s2b = createSpan("test2", null);
+is(document.getElementById("test2"), s1b,
+ "Appending span with id=test2 doesn't change which one comes first");
+
+var s3b = createSpan("test2", s2b);
+is(document.getElementById("test2"), s1b,
+ "Inserting span with id=test2 not at the beginning; doesn't matter");
+
+var s4b = createSpan("test2", s1b);
+is(document.getElementById("test2"), s4b,
+ "Inserting span with id=test2 at the beginning changes which one is first");
+
+s4b.remove();
+is(document.getElementById("test2"), s1b,
+ "First-created span with id=test2 is first again");
+
+s1b.remove();
+is(document.getElementById("test2"), s3b,
+ "Third-created span with id=test2 is first now");
+
+ ]]></script>
+</window>
diff --git a/dom/xul/test/test_bug418216.xhtml b/dom/xul/test/test_bug418216.xhtml
new file mode 100644
index 0000000000..56e487faf2
--- /dev/null
+++ b/dom/xul/test/test_bug418216.xhtml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=418216
+-->
+<window title="Mozilla Bug 418216"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=418216"
+ target="_blank">Mozilla Bug 418216</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 418216 **/
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function onload() {
+ var element = $("item");
+ $("container").removeChild($("item"));
+ $("broadcaster").removeAttribute("disabled");
+ $("container").appendChild(element);
+ is($("item").hasAttribute("disabled"), $("broadcaster").hasAttribute("disabled"));
+ SimpleTest.finish();
+ }
+
+
+
+
+ ]]></script>
+
+<box id="container">
+<box id="item">
+ <observes element="broadcaster" attribute="disabled"/>
+</box>
+</box>
+
+<broadcasterset>
+ <broadcaster id="broadcaster" disabled="true"/>
+</broadcasterset>
+
+</window>
diff --git a/dom/xul/test/test_bug445177.xhtml b/dom/xul/test/test_bug445177.xhtml
new file mode 100644
index 0000000000..d188f19241
--- /dev/null
+++ b/dom/xul/test/test_bug445177.xhtml
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=445177
+-->
+<window title="Test for Bug 445177"
+ id="test_bug445177.xhtml"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body id="body" xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=445177">Mozilla Bug 445177</a>
+
+
+<xul:hbox id="b1" value="foo"/>
+<xul:hbox id="o1" observes="b1"/>
+
+<pre id="test">
+ <script class="testbody" type="text/javascript">
+<![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ function do_test() {
+ var b1 = document.getElementById("b1");
+ var o1 = document.getElementById("o1");
+
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (1)");
+
+ b1.setAttribute("value", "bar");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (2)");
+
+ b1.removeAttribute("value");
+ is(o1.hasAttribute("value"), b1.hasAttribute("value"), "Wrong value (3)");
+
+ o1.setAttribute("value", "foo");
+ isnot(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (4)");
+
+ b1.setAttribute("value", "foobar");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (5)");
+
+ //After removing listener, changes to broadcaster shouldn't have any effect.
+ o1.remove();
+ b1.setAttribute("value", "foo");
+ isnot(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (6)");
+
+ b1.parentNode.appendChild(o1);
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (7)");
+
+ o1.remove();
+ o1.removeAttribute("observes");
+ o1.removeAttribute("value");
+ b1.parentNode.appendChild(o1);
+ isnot(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (8)");
+
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(do_test);
+]]>
+ </script>
+</pre>
+</body>
+</window>
diff --git a/dom/xul/test/test_bug449457.xhtml b/dom/xul/test/test_bug449457.xhtml
new file mode 100644
index 0000000000..b166155775
--- /dev/null
+++ b/dom/xul/test/test_bug449457.xhtml
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=449457
+-->
+<window title="Mozilla Bug 449457"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=449457"
+ target="_blank">Mozilla Bug 449457</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 449457 **/
+ document.popupNode = document;
+ ok(true, "This is just a leak test");
+
+ ]]></script>
+</window>
diff --git a/dom/xul/test/test_bug468176.xhtml b/dom/xul/test/test_bug468176.xhtml
new file mode 100644
index 0000000000..f21c1bcde3
--- /dev/null
+++ b/dom/xul/test/test_bug468176.xhtml
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=468176
+-->
+<window title="Test for Bug 468176"
+ id="test_bug468176.xhtml"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body id="body" xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=468176">Mozilla Bug 468176</a>
+
+<xul:hbox id="b1" value="foo"/>
+
+<xul:hbox id="o1">
+ <xul:observes id="inner" element="b1" attribute="*"/>
+</xul:hbox>
+
+<pre id="test">
+ <script class="testbody" type="text/javascript">
+<![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ var broadcastCount = 0;
+ function b_listener(evt) {
+ ++broadcastCount;
+ }
+
+ function do_test() {
+ var b1 = document.getElementById("b1");
+ var o1 = document.getElementById("o1");
+ var inner = document.getElementById("inner");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (1)");
+
+ inner.addEventListener("broadcast", b_listener, true);
+ b1.setAttribute("value", "bar");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (2)");
+ is(broadcastCount, 1, "Wrong value (3)");
+
+ b1.removeAttribute("value");
+ is(o1.hasAttribute("value"), b1.hasAttribute("value"), "Wrong value (4)");
+ is(broadcastCount, 2, "Wrong value (5)");
+
+ o1.setAttribute("value", "foo");
+ isnot(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (6)");
+ is(broadcastCount, 2, "Wrong value (7)");
+
+ b1.setAttribute("value", "foobar");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (8)");
+ is(broadcastCount, 3, "Wrong value (9)");
+
+ b1.removeAttribute("value");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (10)");
+ is(broadcastCount, 4, "Wrong value (11)");
+
+ b1.removeAttribute("value");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (12)");
+ is(broadcastCount, 4, "Wrong value (13)");
+
+ o1.setAttribute("value", "bar");
+ b1.setAttribute("value", "bar"); // This should still dispatch 'broadcast'
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (14)");
+ is(broadcastCount, 5, "Wrong value (15)");
+
+ //After removing listener, changes to broadcaster shouldn't have any effect.
+ o1.remove();
+ b1.setAttribute("value", "foo");
+ isnot(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (16)");
+ is(broadcastCount, 5, "Wrong value (17)");
+
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(do_test);
+]]>
+ </script>
+</pre>
+</body>
+</window>
diff --git a/dom/xul/test/test_bug486990.xhtml b/dom/xul/test/test_bug486990.xhtml
new file mode 100644
index 0000000000..1ab586e4a0
--- /dev/null
+++ b/dom/xul/test/test_bug486990.xhtml
@@ -0,0 +1,155 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=486990
+-->
+<window title="Mozilla Bug 486990"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="setTimeout(runTests, 0);">
+ <script xmlns="http://www.w3.org/1999/xhtml" src="/tests/SimpleTest/SimpleTest.js"/>
+ <script xmlns="http://www.w3.org/1999/xhtml" src="/tests/SimpleTest/EventUtils.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=486990"
+ target="_blank">Mozilla Bug 486990</a>
+
+ </body>
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <select size="5" id="select">
+ <option>1</option>
+ <option>2</option>
+ <option>3</option>
+ <option>4</option>
+ <option>5</option>
+ <option>6</option>
+ <option>7</option>
+ <option>8</option>
+ <option>9</option>
+ <option>10</option>
+ </select>
+ </div>
+ <menupopup id="cm" onpopupshowing="popupShowing(event);">
+ <menuitem label="Mozilla" value="http://mozilla.org"/>
+ <menuitem label="Slashdot" value="http://slashdot.org"/>
+ <menuitem label="Sourceforge" value="http://sf.net"/>
+ <menuitem label="Freshmeat" value="http://freshmeat.net"/>
+ </menupopup>
+ <button label="test button" contextmenu="cm" id="testbutton"/>
+
+ <!-- test code goes here -->
+ <script xmlns="http://www.w3.org/1999/xhtml" type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 486990 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ var prevented = false;
+ var eventCount = 0;
+
+ function fooListener(evt) {
+ evt.preventDefault();
+ prevented = evt.defaultPrevented;
+ ++eventCount;
+ };
+
+ var clickCount = 0;
+ var mouseDownCount = 0;
+ var mouseUpCount = 0;
+ function clickListener(evt) {
+ ++clickCount;
+ }
+
+ function mouseDownListener(evt) {
+ ++mouseDownCount;
+ }
+
+ function mouseUpListener(evt) {
+ ++mouseUpCount;
+ }
+
+ var popupshowingcount = 0;
+
+ function popupShowing(evt) {
+ ++popupshowingcount;
+ evt.preventDefault();
+ }
+
+ function contextMenuStopper(evt) {
+ evt.stopPropagation();
+ }
+
+ function contextMenuPreventer(evt) {
+ evt.preventDefault();
+ }
+
+ var tb;
+ function runTests() {
+ document.addEventListener("foo", fooListener, true);
+ var e1 = document.createEvent("Event");
+ e1.initEvent("foo", true, true);
+ document.dispatchEvent(e1);
+ is(eventCount, 1, "Wrong event count");
+ ok(prevented, "Default handling should have been prevented.");
+
+ prevented = false;
+ var e2 = document.createEvent("Event");
+ e2.initEvent("foo", false, false);
+ document.dispatchEvent(e1);
+ is(eventCount, 2, "Wrong event count");
+ ok(prevented, "Default handling should have been prevented.");
+
+ tb = document.getElementById("testbutton");
+ dispatchTrustedContextMenuEvent(tb);
+ is(popupshowingcount, 1, "Should have got 'popupShowing' event!");
+
+ tb.addEventListener("contextmenu", contextMenuStopper, true);
+ dispatchTrustedContextMenuEvent(tb);
+ is(popupshowingcount, 2, "Should have got 'popupShowing' event!");
+
+ tb.addEventListener("contextmenu", contextMenuPreventer, true);
+ dispatchTrustedContextMenuEvent(tb);
+ is(popupshowingcount, 2, "Should not have got 'popupShowing' event!");
+
+ SpecialPowers.pushPrefEnv({"set": [["dom.event.contextmenu.enabled", false]]}, test2);
+ }
+
+ function test2() {
+ dispatchTrustedContextMenuEvent(tb);
+ is(popupshowingcount, 3, "Should have got 'popupShowing' event!");
+
+ SpecialPowers.pushPrefEnv({"set": [["dom.event.contextmenu.enabled", true]]}, test3);
+ }
+
+ function test3() {
+ dispatchTrustedContextMenuEvent(tb);
+ is(popupshowingcount, 3, "Should not have got 'popupshowing' event!");
+
+ var s = document.getElementById("select");
+ s.addEventListener("click", clickListener, true);
+ s.addEventListener("mousedown", mouseDownListener, true);
+ s.addEventListener("mouseup", mouseUpListener, true);
+
+ synthesizeMouse(s, 1, 10, {}, window);
+ is(clickCount, 1, "Should have got click event!");
+ is(mouseDownCount, 1, "Should have got mousedown event!");
+ is(mouseUpCount, 1, "Should have got mouseup event!");
+
+ // Dispatch to scrollbar.
+ synthesizeMouse(s, s.getBoundingClientRect().right - 3, 10, {}, window);
+ is(clickCount, 1, "Should not have got click event!");
+ is(mouseDownCount, 2, "Should have got mousedown event!");
+ is(mouseUpCount, 2, "Should have got mouseup event!");
+
+ SimpleTest.finish();
+ }
+
+ function dispatchTrustedContextMenuEvent(target) {
+ return sendMouseEvent({type:"contextmenu"}, target, window);
+ }
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/xul/test/test_bug583948.xhtml b/dom/xul/test/test_bug583948.xhtml
new file mode 100644
index 0000000000..2cab13a4ac
--- /dev/null
+++ b/dom/xul/test/test_bug583948.xhtml
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <div id="content" style="display: none"/>
+</body>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+let chromeWindow = window.browsingContext.topChromeWindow;
+
+var attempts = 0;
+
+chromeWindow.update = function () {
+ // without the crash fix, this usually crashes after 2 to 4 reloads
+ if (++attempts == 6) {
+ ok(true, "didn't crash after 6 attempts");
+ otherWindow.close();
+ SimpleTest.waitForFocus(function() {
+ chromeWindow.update = null;
+ SimpleTest.finish();
+ });
+ } else {
+ otherWindow.document.commandDispatcher.updateCommands('');
+ setTimeout(function() {
+ otherWindow.location.reload()
+ }, 0);
+ }
+}
+
+var otherWindow = chromeWindow.open("window_bug583948.xhtml", "_blank", "chrome");
+</script>
+
+</window>
diff --git a/dom/xul/test/test_bug749367.xhtml b/dom/xul/test/test_bug749367.xhtml
new file mode 100644
index 0000000000..3dcea24f1c
--- /dev/null
+++ b/dom/xul/test/test_bug749367.xhtml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=749367
+-->
+<window title="Mozilla Bug 749367"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="setTimeout(runTests, 0);">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=486990"
+ target="_blank">Mozilla Bug 486990</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="text/template">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ function runTests() {
+ ok(false, "Shouldn't execute");
+ SimpleTest.finish();
+ }
+ ]]>
+ </script>
+
+ <script type="text/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ function runTests() {
+ ok(true, "Should execute");
+ SimpleTest.finish();
+ }
+ ]]>
+ </script>
+
+</window>
diff --git a/dom/xul/test/test_bug757137.xhtml b/dom/xul/test/test_bug757137.xhtml
new file mode 100644
index 0000000000..e29123814a
--- /dev/null
+++ b/dom/xul/test/test_bug757137.xhtml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <div id="content" style="display: none"/>
+</body>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+// Force off out-of-process mozbrowser because we need to grab its
+// |window| synchronously from here. Out-of-process docshell creation
+// for mozbrowser haves entirely differently.
+SpecialPowers.pushPrefEnv({"set":[["dom.ipc.tabs.disabled", true]]}, startTest);
+
+function startTest() {
+ var otherWindow = window.browsingContext.topChromeWindow.open("window_bug757137.xhtml", "", "chrome");
+ ok(otherWindow.isChromeWindow, 'XUL window should be a ChromeWindow');
+
+ otherWindow.onload = function () {
+ var w = otherWindow.document.getElementById('f').contentWindow;
+ ok(w !== null, 'got the |window| for a mozbrowser iframe');
+ ok(!w.isChromeWindow, 'mozbrowser iframe should not be a ChromeWindow');
+
+ otherWindow.close();
+ SimpleTest.waitForFocus(SimpleTest.finish);
+ };
+}
+</script>
+
+</window>
diff --git a/dom/xul/test/test_bug775972.xhtml b/dom/xul/test/test_bug775972.xhtml
new file mode 100644
index 0000000000..571bf6a6c3
--- /dev/null
+++ b/dom/xul/test/test_bug775972.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=775972
+-->
+<window title="Mozilla Bug 775972"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test()">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=775972"
+ target="_blank">Mozilla Bug 775972</a>
+ </body>
+
+ <hbox id="container"><label value="test" id=""/></hbox>
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 775972 **/
+
+ function test() {
+ var c = document.getElementById("container");
+ var clone = c.cloneNode(true);
+ document.documentElement.appendChild(clone);
+ ok(true, "This shouldn't crash!");
+ }
+
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/xul/test/test_disable_scroll_frame_plain.html b/dom/xul/test/test_disable_scroll_frame_plain.html
new file mode 100644
index 0000000000..8104f55721
--- /dev/null
+++ b/dom/xul/test/test_disable_scroll_frame_plain.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html scrolling="false">
+<head>
+ <meta charset="utf-8">
+ <title>disable scroll frame exposed</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<div style="height: 300vh"></div>
+<script>
+ // Ensure that disabling the scroll frame isn't exposed to content.
+ ok(document.scrollingElement.scrollTopMax > 0, "Scrolling should still work.");
+</script>
+</body>
+</html>
diff --git a/dom/xul/test/test_html_template.xhtml b/dom/xul/test/test_html_template.xhtml
new file mode 100644
index 0000000000..7e6029486e
--- /dev/null
+++ b/dom/xul/test/test_html_template.xhtml
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<window title="HTML template in XUL"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <template id="template">Content<span>Content</span></template>
+<script type="application/javascript"><![CDATA[
+ add_task(async function test_template() {
+ let template = document.getElementById("template");
+ ok("content" in template, "Template has shadow root.");
+ is(template.childNodes.length, 0, "Template should have no children.");
+ is(template.content.childNodes.length, 2, "Template content should have two children.");
+ });
+]]></script>
+ </body>
+</window>
diff --git a/dom/xul/test/test_import_xul_to_content.xhtml b/dom/xul/test/test_import_xul_to_content.xhtml
new file mode 100644
index 0000000000..1fd02fabe7
--- /dev/null
+++ b/dom/xul/test/test_import_xul_to_content.xhtml
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<window title="Mozilla Importing XUL into Content"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1027299"
+ target="_blank">Mozilla Bug 1027299</a>
+ </body>
+
+ <browser id="browserelt" src="about:blank" type="content"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ SimpleTest.waitForExplicitFinish();
+
+ function expectWarning(expected, when, f) {
+ Services.console.reset();
+
+ f();
+
+ var sawWarning = false;
+ var msgs = Services.console.getMessageArray();
+ for (var i = 0; i < msgs.length; i++) {
+ var msg = msgs[i];
+ if (!msg || !(msg instanceof Ci.nsIScriptError)) {
+ continue;
+ }
+
+ if (msg.category.includes("DOM") && msg.errorMessage.includes("Importing XUL")) {
+ sawWarning = true;
+ }
+ }
+
+ ok(sawWarning == expected, "correct warning condition when " + when);
+ }
+
+ var browser = document.getElementById("browserelt");
+ browser.addEventListener("load", function() {
+ var doc = browser.contentDocument;
+
+ // We add a <video> element, which contains anonymous XUL content. This should not warn.
+ var video = doc.createElement("video");
+ expectWarning(false, "appending video", function() {
+ doc.documentElement.appendChild(video);
+ // Force a layout flush to make sure the anonymous content is added.
+ let dummy = doc.documentElement.offsetLeft;
+ });
+
+ // We add some XUL to a content document. This should generate a warning.
+ var elt = document.createXULElement("label");
+ var newElt = doc.importNode(elt, false);
+ expectWarning(true, "appending XUL", function() {
+ doc.documentElement.appendChild(newElt);
+ });
+
+ SimpleTest.finish();
+ });
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/xul/test/test_xul_tabindex_focus.xhtml b/dom/xul/test/test_xul_tabindex_focus.xhtml
new file mode 100644
index 0000000000..1363d9b1e1
--- /dev/null
+++ b/dom/xul/test/test_xul_tabindex_focus.xhtml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1128054
+-->
+<window title="Mozilla Bug 1128054"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<body>
+<!-- Test default focusability -->
+<label></label>
+<!-- Test tabindex=0 focusability -->
+<label tabindex="0"></label>
+<!-- Test tabindex=-1 focusability -->
+<label tabindex="-1"></label>
+<!-- Test tabindex=invalid focusability -->
+<label tabindex="invalid"></label>
+<!-- Tests code -->
+<script type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 1128054 **/
+
+add_task(function test_xul_tabindex_focus() {
+ for (let element of document.querySelectorAll("label")) {
+ let desc = "xul element";
+ let focusable = false;
+ if (element.hasAttribute("tabindex")) {
+ let attr = element.getAttribute("tabindex");
+ focusable = Number.isInteger(Number.parseInt(attr));
+ desc += ` with tabindex=${attr}`;
+ }
+
+ element.focus();
+ focusable ? is(document.activeElement, element, desc + " should focusable")
+ : isnot(document.activeElement, element, desc + " should not focusable");
+ }
+});
+
+]]>
+</script>
+</body>
+</window>
diff --git a/dom/xul/test/window_bug1686822.xhtml b/dom/xul/test/window_bug1686822.xhtml
new file mode 100644
index 0000000000..1a2cf25636
--- /dev/null
+++ b/dom/xul/test/window_bug1686822.xhtml
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+ <html:script type="module"><![CDATA[
+ window.opener.moduleScriptRan();
+ ]]></html:script>
+</window>
diff --git a/dom/xul/test/window_bug583948.xhtml b/dom/xul/test/window_bug583948.xhtml
new file mode 100644
index 0000000000..d0f6a26926
--- /dev/null
+++ b/dom/xul/test/window_bug583948.xhtml
@@ -0,0 +1,8 @@
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="opener.update()">
+
+<command oncommandupdate="document.removeChild(document.documentElement)" commandupdater="true"/>
+<box command="c"/>
+<iframe/>
+
+</window>
diff --git a/dom/xul/test/window_bug757137.xhtml b/dom/xul/test/window_bug757137.xhtml
new file mode 100644
index 0000000000..16f38ef20f
--- /dev/null
+++ b/dom/xul/test/window_bug757137.xhtml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+ <html:iframe id="f" mozbrowser="true"
+ src="data:text/html;charset=utf-8,%3C!DOCTYPE html>Hi" />
+</window>