summaryrefslogtreecommitdiffstats
path: root/dom/xul
diff options
context:
space:
mode:
Diffstat (limited to 'dom/xul')
-rw-r--r--dom/xul/ChromeObserver.cpp197
-rw-r--r--dom/xul/ChromeObserver.h42
-rw-r--r--dom/xul/MenuBarListener.cpp489
-rw-r--r--dom/xul/MenuBarListener.h78
-rw-r--r--dom/xul/XULBroadcastManager.cpp589
-rw-r--r--dom/xul/XULBroadcastManager.h91
-rw-r--r--dom/xul/XULButtonElement.cpp798
-rw-r--r--dom/xul/XULButtonElement.h123
-rw-r--r--dom/xul/XULFrameElement.cpp200
-rw-r--r--dom/xul/XULFrameElement.h84
-rw-r--r--dom/xul/XULMenuBarElement.cpp103
-rw-r--r--dom/xul/XULMenuBarElement.h59
-rw-r--r--dom/xul/XULMenuElement.cpp82
-rw-r--r--dom/xul/XULMenuElement.h37
-rw-r--r--dom/xul/XULMenuParentElement.cpp398
-rw-r--r--dom/xul/XULMenuParentElement.h80
-rw-r--r--dom/xul/XULPersist.cpp247
-rw-r--r--dom/xul/XULPersist.h57
-rw-r--r--dom/xul/XULPopupElement.cpp339
-rw-r--r--dom/xul/XULPopupElement.h116
-rw-r--r--dom/xul/XULResizerElement.cpp354
-rw-r--r--dom/xul/XULResizerElement.h62
-rw-r--r--dom/xul/XULTextElement.cpp49
-rw-r--r--dom/xul/XULTextElement.h50
-rw-r--r--dom/xul/XULTooltipElement.cpp104
-rw-r--r--dom/xul/XULTooltipElement.h35
-rw-r--r--dom/xul/XULTreeElement.cpp411
-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/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.build94
-rw-r--r--dom/xul/nsIBrowserController.idl20
-rw-r--r--dom/xul/nsIController.idl39
-rw-r--r--dom/xul/nsIControllers.idl34
-rw-r--r--dom/xul/nsXULCommandDispatcher.cpp435
-rw-r--r--dom/xul/nsXULCommandDispatcher.h82
-rw-r--r--dom/xul/nsXULContentSink.cpp861
-rw-r--r--dom/xul/nsXULContentSink.h144
-rw-r--r--dom/xul/nsXULContentUtils.cpp89
-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.cpp2121
-rw-r--r--dom/xul/nsXULElement.h568
-rw-r--r--dom/xul/nsXULPopupListener.cpp283
-rw-r--r--dom/xul/nsXULPopupListener.h59
-rw-r--r--dom/xul/nsXULPrototypeCache.cpp500
-rw-r--r--dom/xul/nsXULPrototypeCache.h164
-rw-r--r--dom/xul/nsXULPrototypeDocument.cpp511
-rw-r--r--dom/xul/nsXULPrototypeDocument.h127
-rw-r--r--dom/xul/nsXULSortService.cpp387
-rw-r--r--dom/xul/nsXULSortService.h42
-rw-r--r--dom/xul/test/398289-resource.xhtml49
-rw-r--r--dom/xul/test/chrome.toml42
-rw-r--r--dom/xul/test/file_bug236853.rdf14
-rw-r--r--dom/xul/test/mochitest.toml3
-rw-r--r--dom/xul/test/test_accesskey.xhtml57
-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.xhtml107
-rw-r--r--dom/xul/test/test_bug391002.xhtml41
-rw-r--r--dom/xul/test/test_bug398289.html41
-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_bug468176.xhtml84
-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_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.xhtml68
-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
98 files changed, 13855 insertions, 0 deletions
diff --git a/dom/xul/ChromeObserver.cpp b/dom/xul/ChromeObserver.cpp
new file mode 100644
index 0000000000..9ad66348d4
--- /dev/null
+++ b/dom/xul/ChromeObserver.cpp
@@ -0,0 +1,197 @@
+/* -*- 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::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;
+}
+
+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);
+ }
+}
+
+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
+ nsAutoString tmp;
+ aValue->ToString(tmp);
+ nsIntMargin margins;
+ if (nsContentUtils::ParseIntMarginValue(tmp, margins)) {
+ 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);
+ }
+ // title and drawintitlebar are settable on
+ // any root node (windows, dialogs, etc)
+ else if (aName == nsGkAtoms::title) {
+ mDocument->NotifyPossibleTitleChange(false);
+ } 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) {
+ // if the lwtheme changed, make sure to reset the document lwtheme
+ // cache
+ mDocument->ResetDocumentLWTheme();
+ }
+ } 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) {
+ // if the lwtheme changed, make sure to restyle appropriately
+ mDocument->ResetDocumentLWTheme();
+ } else if (aName == nsGkAtoms::drawtitle) {
+ SetDrawsTitle(false);
+ }
+ }
+}
+
+void ChromeObserver::NodeWillBeDestroyed(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 mozilla::dom
diff --git a/dom/xul/ChromeObserver.h b/dom/xul/ChromeObserver.h
new file mode 100644
index 0000000000..c651389487
--- /dev/null
+++ b/dom/xul/ChromeObserver.h
@@ -0,0 +1,42 @@
+/* -*- 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::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 SetDrawsTitle(bool aState);
+ 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 mozilla::dom
+
+#endif // mozilla_dom_ChromeObserver_h
diff --git a/dom/xul/MenuBarListener.cpp b/dom/xul/MenuBarListener.cpp
new file mode 100644
index 0000000000..995ec422c5
--- /dev/null
+++ b/dom/xul/MenuBarListener.cpp
@@ -0,0 +1,489 @@
+/* -*- 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 "MenuBarListener.h"
+#include "XULButtonElement.h"
+#include "mozilla/Attributes.h"
+#include "nsISound.h"
+
+// Drag & Drop, Clipboard
+#include "nsWidgetsCID.h"
+#include "nsCOMPtr.h"
+
+#include "nsContentUtils.h"
+#include "nsPIWindowRoot.h"
+#include "nsIFrame.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventBinding.h"
+#include "mozilla/dom/KeyboardEvent.h"
+#include "mozilla/dom/KeyboardEventBinding.h"
+#include "mozilla/dom/XULButtonElement.h"
+#include "mozilla/dom/XULMenuBarElement.h"
+#include "mozilla/dom/XULMenuParentElement.h"
+#include "nsXULPopupManager.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_ISUPPORTS(MenuBarListener, nsIDOMEventListener)
+
+MenuBarListener::MenuBarListener(XULMenuBarElement& aElement)
+ : mMenuBar(&aElement),
+ mEventTarget(aElement.GetComposedDoc()),
+ mAccessKeyDown(false),
+ mAccessKeyDownCanceled(false) {
+ MOZ_ASSERT(mEventTarget);
+ MOZ_ASSERT(mMenuBar);
+
+ // Hook up the menubar as a key listener on the whole document. This will
+ // see every keypress that occurs, but after everyone else does.
+
+ // Also hook up the listener to the window listening for focus events. This
+ // is so we can keep proper state as the user alt-tabs through processes.
+
+ mEventTarget->AddSystemEventListener(u"keypress"_ns, this, false);
+ mEventTarget->AddSystemEventListener(u"keydown"_ns, this, false);
+ mEventTarget->AddSystemEventListener(u"keyup"_ns, this, false);
+ mEventTarget->AddSystemEventListener(u"mozaccesskeynotfound"_ns, this, false);
+ // Need a capturing event listener if the user has blocked pages from
+ // overriding system keys so that we can prevent menu accesskeys from being
+ // cancelled.
+ mEventTarget->AddEventListener(u"keydown"_ns, this, true);
+
+ // mousedown event should be handled in all phase
+ mEventTarget->AddEventListener(u"mousedown"_ns, this, true);
+ mEventTarget->AddEventListener(u"mousedown"_ns, this, false);
+ mEventTarget->AddEventListener(u"blur"_ns, this, true);
+
+ mEventTarget->AddEventListener(u"MozDOMFullscreen:Entered"_ns, this, false);
+
+ // Needs to listen to the deactivate event of the window.
+ RefPtr<EventTarget> top = nsContentUtils::GetWindowRoot(mEventTarget);
+ if (!NS_WARN_IF(!top)) {
+ top->AddSystemEventListener(u"deactivate"_ns, this, true);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+MenuBarListener::~MenuBarListener() {
+ MOZ_ASSERT(!mEventTarget, "Should've detached always");
+}
+
+void MenuBarListener::Detach() {
+ if (!mMenuBar) {
+ MOZ_ASSERT(!mEventTarget);
+ return;
+ }
+ mEventTarget->RemoveSystemEventListener(u"keypress"_ns, this, false);
+ mEventTarget->RemoveSystemEventListener(u"keydown"_ns, this, false);
+ mEventTarget->RemoveSystemEventListener(u"keyup"_ns, this, false);
+ mEventTarget->RemoveSystemEventListener(u"mozaccesskeynotfound"_ns, this,
+ false);
+ mEventTarget->RemoveEventListener(u"keydown"_ns, this, true);
+
+ mEventTarget->RemoveEventListener(u"mousedown"_ns, this, true);
+ mEventTarget->RemoveEventListener(u"mousedown"_ns, this, false);
+ mEventTarget->RemoveEventListener(u"blur"_ns, this, true);
+
+ mEventTarget->RemoveEventListener(u"MozDOMFullscreen:Entered"_ns, this,
+ false);
+ RefPtr<EventTarget> top = nsContentUtils::GetWindowRoot(mEventTarget);
+ if (!NS_WARN_IF(!top)) {
+ top->RemoveSystemEventListener(u"deactivate"_ns, this, true);
+ }
+ mMenuBar = nullptr;
+ mEventTarget = nullptr;
+}
+
+void MenuBarListener::ToggleMenuActiveState(ByKeyboard aByKeyboard) {
+ RefPtr menuBar = mMenuBar;
+ if (menuBar->IsActive()) {
+ menuBar->SetActive(false);
+ } else {
+ if (aByKeyboard == ByKeyboard::Yes) {
+ menuBar->SetActiveByKeyboard();
+ }
+ // This will activate the menubar if needed.
+ menuBar->SelectFirstItem();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+nsresult MenuBarListener::KeyUp(Event* aKeyEvent) {
+ WidgetKeyboardEvent* nativeKeyEvent =
+ aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
+ if (!nativeKeyEvent) {
+ return NS_OK;
+ }
+
+ // handlers shouldn't be triggered by non-trusted events.
+ if (!nativeKeyEvent->IsTrusted()) {
+ return NS_OK;
+ }
+
+ const auto accessKey = LookAndFeel::GetMenuAccessKey();
+ if (!accessKey || !StaticPrefs::ui_key_menuAccessKeyFocuses()) {
+ return NS_OK;
+ }
+
+ // On a press of the ALT key by itself, we toggle the menu's
+ // active/inactive state.
+ if (!nativeKeyEvent->DefaultPrevented() && mAccessKeyDown &&
+ !mAccessKeyDownCanceled && nativeKeyEvent->mKeyCode == accessKey) {
+ // The access key was down and is now up, and no other
+ // keys were pressed in between.
+ bool toggleMenuActiveState = true;
+ if (!mMenuBar->IsActive()) {
+ // If the focused content is in a remote process, we should allow the
+ // focused web app to prevent to activate the menubar.
+ if (nativeKeyEvent->WillBeSentToRemoteProcess()) {
+ nativeKeyEvent->StopImmediatePropagation();
+ nativeKeyEvent->MarkAsWaitingReplyFromRemoteProcess();
+ return NS_OK;
+ }
+ // First, close all existing popups because other popups shouldn't
+ // handle key events when menubar is active and IME should be
+ // disabled.
+ if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
+ pm->Rollup({});
+ }
+ // If menubar active state is changed or the menubar is destroyed
+ // during closing the popups, we should do nothing anymore.
+ toggleMenuActiveState = !Destroyed() && !mMenuBar->IsActive();
+ }
+ if (toggleMenuActiveState) {
+ ToggleMenuActiveState(ByKeyboard::Yes);
+ }
+ }
+
+ mAccessKeyDown = false;
+ mAccessKeyDownCanceled = false;
+
+ if (!Destroyed() && mMenuBar->IsActive()) {
+ nativeKeyEvent->StopPropagation();
+ nativeKeyEvent->PreventDefault();
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+nsresult MenuBarListener::KeyPress(Event* aKeyEvent) {
+ // if event has already been handled, bail
+ if (!aKeyEvent || aKeyEvent->DefaultPrevented()) {
+ return NS_OK; // don't consume event
+ }
+
+ // handlers shouldn't be triggered by non-trusted events.
+ if (!aKeyEvent->IsTrusted()) {
+ return NS_OK;
+ }
+
+ const auto accessKey = LookAndFeel::GetMenuAccessKey();
+ if (!accessKey) {
+ return NS_OK;
+ }
+ // If accesskey handling was forwarded to a child process, wait for
+ // the mozaccesskeynotfound event before handling accesskeys.
+ WidgetKeyboardEvent* nativeKeyEvent =
+ aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
+ if (!nativeKeyEvent) {
+ return NS_OK;
+ }
+
+ RefPtr<KeyboardEvent> keyEvent = aKeyEvent->AsKeyboardEvent();
+ uint32_t keyCode = keyEvent->KeyCode();
+
+ // Cancel the access key flag unless we are pressing the access key.
+ if (keyCode != accessKey) {
+ mAccessKeyDownCanceled = true;
+ }
+
+#ifndef XP_MACOSX
+ // Need to handle F10 specially on Non-Mac platform.
+ if (nativeKeyEvent->mMessage == eKeyPress && keyCode == NS_VK_F10) {
+ if ((keyEvent->GetModifiersForMenuAccessKey() & ~MODIFIER_CONTROL) == 0) {
+ // If the keyboard event should activate the menubar and will be
+ // sent to a remote process, it should be executed with reply
+ // event from the focused remote process. Note that if the menubar
+ // is active, the event is already marked as "stop cross
+ // process dispatching". So, in that case, this won't wait
+ // reply from the remote content.
+ if (nativeKeyEvent->WillBeSentToRemoteProcess()) {
+ nativeKeyEvent->StopImmediatePropagation();
+ nativeKeyEvent->MarkAsWaitingReplyFromRemoteProcess();
+ return NS_OK;
+ }
+ // The F10 key just went down by itself or with ctrl pressed.
+ // In Windows, both of these activate the menu bar.
+ ToggleMenuActiveState(ByKeyboard::Yes);
+
+ if (mMenuBar && mMenuBar->IsActive()) {
+# ifdef MOZ_WIDGET_GTK
+ if (RefPtr child = mMenuBar->GetActiveMenuChild()) {
+ // In GTK, this also opens the first menu.
+ child->OpenMenuPopup(false);
+ }
+# endif
+ aKeyEvent->StopPropagation();
+ aKeyEvent->PreventDefault();
+ }
+ }
+
+ return NS_OK;
+ }
+#endif // !XP_MACOSX
+
+ RefPtr menuForKey = GetMenuForKeyEvent(*keyEvent);
+ if (!menuForKey) {
+#ifdef XP_WIN
+ // Behavior on Windows - this item is on the menu bar, beep and deactivate
+ // the menu bar.
+ // TODO(emilio): This is rather odd, and I cannot get the beep to work,
+ // but this matches what old code was doing...
+ if (mMenuBar && mMenuBar->IsActive() && mMenuBar->IsActiveByKeyboard()) {
+ if (nsCOMPtr<nsISound> sound = do_GetService("@mozilla.org/sound;1")) {
+ sound->Beep();
+ }
+ ToggleMenuActiveState(ByKeyboard::Yes);
+ }
+#endif
+ return NS_OK;
+ }
+
+ // If the keyboard event matches with a menu item's accesskey and
+ // will be sent to a remote process, it should be executed with
+ // reply event from the focused remote process. Note that if the
+ // menubar is active, the event is already marked as "stop cross
+ // process dispatching". So, in that case, this won't wait
+ // reply from the remote content.
+ if (nativeKeyEvent->WillBeSentToRemoteProcess()) {
+ nativeKeyEvent->StopImmediatePropagation();
+ nativeKeyEvent->MarkAsWaitingReplyFromRemoteProcess();
+ return NS_OK;
+ }
+
+ RefPtr menuBar = mMenuBar;
+ menuBar->SetActiveByKeyboard();
+ // This will activate the menubar as needed.
+ menuForKey->OpenMenuPopup(true);
+
+ // The opened menu will listen next keyup event.
+ // Therefore, we should clear the keydown flags here.
+ mAccessKeyDown = mAccessKeyDownCanceled = false;
+
+ aKeyEvent->StopPropagation();
+ aKeyEvent->PreventDefault();
+ return NS_OK;
+}
+
+dom::XULButtonElement* MenuBarListener::GetMenuForKeyEvent(
+ KeyboardEvent& aKeyEvent) {
+ if (!aKeyEvent.IsMenuAccessKeyPressed()) {
+ return nullptr;
+ }
+
+ uint32_t charCode = aKeyEvent.CharCode();
+ bool hasAccessKeyCandidates = charCode != 0;
+ if (!hasAccessKeyCandidates) {
+ WidgetKeyboardEvent* nativeKeyEvent =
+ aKeyEvent.WidgetEventPtr()->AsKeyboardEvent();
+ AutoTArray<uint32_t, 10> keys;
+ nativeKeyEvent->GetAccessKeyCandidates(keys);
+ hasAccessKeyCandidates = !keys.IsEmpty();
+ }
+
+ if (!hasAccessKeyCandidates) {
+ return nullptr;
+ }
+ // Do shortcut navigation.
+ // A letter was pressed. We want to see if a shortcut gets matched. If
+ // so, we'll know the menu got activated.
+ return mMenuBar->FindMenuWithShortcut(aKeyEvent);
+}
+
+void MenuBarListener::ReserveKeyIfNeeded(Event* aKeyEvent) {
+ WidgetKeyboardEvent* nativeKeyEvent =
+ aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
+ if (nsContentUtils::ShouldBlockReservedKeys(nativeKeyEvent)) {
+ nativeKeyEvent->MarkAsReservedByChrome();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+nsresult MenuBarListener::KeyDown(Event* aKeyEvent) {
+ // handlers shouldn't be triggered by non-trusted events.
+ if (!aKeyEvent || !aKeyEvent->IsTrusted()) {
+ return NS_OK;
+ }
+
+ RefPtr<KeyboardEvent> keyEvent = aKeyEvent->AsKeyboardEvent();
+ if (!keyEvent) {
+ return NS_OK;
+ }
+
+ uint32_t theChar = keyEvent->KeyCode();
+ uint16_t eventPhase = keyEvent->EventPhase();
+ bool capturing = (eventPhase == dom::Event_Binding::CAPTURING_PHASE);
+
+#ifndef XP_MACOSX
+ if (capturing && !mAccessKeyDown && theChar == NS_VK_F10 &&
+ (keyEvent->GetModifiersForMenuAccessKey() & ~MODIFIER_CONTROL) == 0) {
+ ReserveKeyIfNeeded(aKeyEvent);
+ }
+#endif
+
+ const auto accessKey = LookAndFeel::GetMenuAccessKey();
+ if (accessKey && StaticPrefs::ui_key_menuAccessKeyFocuses()) {
+ bool defaultPrevented = aKeyEvent->DefaultPrevented();
+
+ // No other modifiers can be down.
+ // Especially CTRL. CTRL+ALT == AltGR, and we'll break on non-US
+ // enhanced 102-key keyboards if we don't check this.
+ bool isAccessKeyDownEvent =
+ (theChar == accessKey &&
+ (keyEvent->GetModifiersForMenuAccessKey() &
+ ~LookAndFeel::GetMenuAccessKeyModifiers()) == 0);
+
+ if (!capturing && !mAccessKeyDown) {
+ // If accesskey isn't being pressed and the key isn't the accesskey,
+ // ignore the event.
+ if (!isAccessKeyDownEvent) {
+ return NS_OK;
+ }
+
+ // Otherwise, accept the accesskey state.
+ mAccessKeyDown = true;
+ // If default is prevented already, cancel the access key down.
+ mAccessKeyDownCanceled = defaultPrevented;
+ return NS_OK;
+ }
+
+ // If the pressed accesskey was canceled already or the event was
+ // consumed already, ignore the event.
+ if (mAccessKeyDownCanceled || defaultPrevented) {
+ return NS_OK;
+ }
+
+ // Some key other than the access key just went down,
+ // so we won't activate the menu bar when the access key is released.
+ mAccessKeyDownCanceled = !isAccessKeyDownEvent;
+ }
+
+ if (capturing && accessKey) {
+ if (GetMenuForKeyEvent(*keyEvent)) {
+ ReserveKeyIfNeeded(aKeyEvent);
+ }
+ }
+
+ return NS_OK; // means I am NOT consuming event
+}
+
+////////////////////////////////////////////////////////////////////////
+
+nsresult MenuBarListener::Blur(Event* aEvent) {
+ if (!IsMenuOpen() && mMenuBar->IsActive()) {
+ ToggleMenuActiveState(ByKeyboard::No);
+ mAccessKeyDown = false;
+ mAccessKeyDownCanceled = false;
+ }
+ return NS_OK; // means I am NOT consuming event
+}
+
+////////////////////////////////////////////////////////////////////////
+
+nsresult MenuBarListener::OnWindowDeactivated(Event* aEvent) {
+ // Reset the accesskey state because we cannot receive the keyup event for
+ // the pressing accesskey.
+ mAccessKeyDown = false;
+ mAccessKeyDownCanceled = false;
+ return NS_OK; // means I am NOT consuming event
+}
+
+bool MenuBarListener::IsMenuOpen() const {
+ auto* activeChild = mMenuBar->GetActiveMenuChild();
+ return activeChild && activeChild->IsMenuPopupOpen();
+}
+
+////////////////////////////////////////////////////////////////////////
+nsresult MenuBarListener::MouseDown(Event* aMouseEvent) {
+ // NOTE: MouseDown method listens all phases
+
+ // Even if the mousedown event is canceled, it means the user don't want
+ // to activate the menu. Therefore, we need to record it at capturing (or
+ // target) phase.
+ if (mAccessKeyDown) {
+ mAccessKeyDownCanceled = true;
+ }
+
+ // Don't do anything at capturing phase, any behavior should be cancelable.
+ if (aMouseEvent->EventPhase() == dom::Event_Binding::CAPTURING_PHASE) {
+ return NS_OK;
+ }
+
+ if (!IsMenuOpen() && mMenuBar->IsActive()) {
+ ToggleMenuActiveState(ByKeyboard::No);
+ }
+
+ return NS_OK; // means I am NOT consuming event
+}
+
+////////////////////////////////////////////////////////////////////////
+
+nsresult MenuBarListener::Fullscreen(Event* aEvent) {
+ if (mMenuBar->IsActive()) {
+ ToggleMenuActiveState(ByKeyboard::No);
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
+MenuBarListener::HandleEvent(Event* aEvent) {
+ // If the menu bar is collapsed, don't do anything.
+ if (!mMenuBar || !mMenuBar->GetPrimaryFrame() ||
+ !mMenuBar->GetPrimaryFrame()->StyleVisibility()->IsVisible()) {
+ return NS_OK;
+ }
+
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+
+ if (eventType.EqualsLiteral("keyup")) {
+ return KeyUp(aEvent);
+ }
+ if (eventType.EqualsLiteral("keydown")) {
+ return KeyDown(aEvent);
+ }
+ if (eventType.EqualsLiteral("keypress")) {
+ return KeyPress(aEvent);
+ }
+ if (eventType.EqualsLiteral("mozaccesskeynotfound")) {
+ return KeyPress(aEvent);
+ }
+ if (eventType.EqualsLiteral("blur")) {
+ return Blur(aEvent);
+ }
+ if (eventType.EqualsLiteral("deactivate")) {
+ return OnWindowDeactivated(aEvent);
+ }
+ if (eventType.EqualsLiteral("mousedown")) {
+ return MouseDown(aEvent);
+ }
+ if (eventType.EqualsLiteral("MozDOMFullscreen:Entered")) {
+ return Fullscreen(aEvent);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
+ return NS_OK;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/xul/MenuBarListener.h b/dom/xul/MenuBarListener.h
new file mode 100644
index 0000000000..2985a77079
--- /dev/null
+++ b/dom/xul/MenuBarListener.h
@@ -0,0 +1,78 @@
+/* -*- 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_MenuBarListener_h
+#define mozilla_dom_MenuBarListener_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "nsIDOMEventListener.h"
+
+namespace mozilla::dom {
+class Document;
+class EventTarget;
+class KeyboardEvent;
+class XULMenuBarElement;
+class XULButtonElement;
+
+class MenuBarListener final : public nsIDOMEventListener {
+ public:
+ explicit MenuBarListener(XULMenuBarElement&);
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ // Should be called when unbound from the document and so on.
+ void Detach();
+
+ protected:
+ virtual ~MenuBarListener();
+
+ bool IsMenuOpen() const;
+
+ MOZ_CAN_RUN_SCRIPT nsresult KeyUp(Event* aMouseEvent);
+ MOZ_CAN_RUN_SCRIPT nsresult KeyDown(Event* aMouseEvent);
+ MOZ_CAN_RUN_SCRIPT nsresult KeyPress(Event* aMouseEvent);
+ MOZ_CAN_RUN_SCRIPT nsresult Blur(Event* aEvent);
+ MOZ_CAN_RUN_SCRIPT nsresult OnWindowDeactivated(Event* aEvent);
+ MOZ_CAN_RUN_SCRIPT nsresult MouseDown(Event* aMouseEvent);
+ MOZ_CAN_RUN_SCRIPT nsresult Fullscreen(Event* aEvent);
+
+ /**
+ * Given a key event for an Alt+shortcut combination,
+ * return the menu, if any, that would be opened. If aPeek
+ * is false, then play a beep and deactivate the menubar on Windows.
+ */
+ XULButtonElement* GetMenuForKeyEvent(KeyboardEvent& aKeyEvent);
+
+ /**
+ * Call MarkAsReservedByChrome if the user's preferences indicate that
+ * the key should be chrome-only.
+ */
+ void ReserveKeyIfNeeded(Event* aKeyEvent);
+
+ // This should only be called by the MenuBarListener during event dispatch.
+ enum class ByKeyboard : bool { No, Yes };
+ MOZ_CAN_RUN_SCRIPT void ToggleMenuActiveState(ByKeyboard);
+
+ bool Destroyed() const { return !mMenuBar; }
+
+ // The menu bar object. Safe because it keeps us alive.
+ XULMenuBarElement* mMenuBar;
+ // The event target to listen to the events.
+ //
+ // Weak reference is safe because we clear the listener on unbind from the
+ // document.
+ Document* mEventTarget;
+ // Whether or not the ALT key is currently down.
+ bool mAccessKeyDown = false;
+ // Whether or not the ALT key down is canceled by other action.
+ bool mAccessKeyDownCanceled = false;
+};
+
+} // namespace mozilla::dom
+
+#endif // #ifndef mozilla_dom_MenuBarListener_h
diff --git a/dom/xul/XULBroadcastManager.cpp b/dom/xul/XULBroadcastManager.cpp
new file mode 100644
index 0000000000..c9afefd0c8
--- /dev/null
+++ b/dom/xul/XULBroadcastManager.cpp
@@ -0,0 +1,589 @@
+/* -*- 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::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;
+
+ RefPtr<Element> mBroadcaster;
+ RefPtr<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(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 (nsCOMPtr<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(nsGkAtoms::element, listeningToID);
+
+ nsAutoString broadcasterID;
+ aBroadcaster->GetAttr(nsGkAtoms::id, broadcasterID);
+
+ if (listeningToID != broadcasterID) continue;
+
+ // We are observing the broadcaster, but is this the right
+ // attribute?
+ nsAutoString listeningToAttribute;
+ child->AsElement()->GetAttr(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);
+
+ if (RefPtr<nsPresContext> presContext = mDocument->GetPresContext()) {
+ // 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(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(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) {
+ RefPtr<nsAtom> attrName = mDelayedAttrChangeBroadcasts[i].mAttrName;
+ RefPtr<Element> listener = mDelayedAttrChangeBroadcasts[i].mListener;
+ if (mDelayedAttrChangeBroadcasts[i].mNeedsAttrChange) {
+ const nsString& value = mDelayedAttrChangeBroadcasts[i].mAttr;
+ if (mDelayedAttrChangeBroadcasts[i].mSetAttr) {
+ listener->SetAttr(kNameSpaceID_None, attrName, value, true);
+ } else {
+ listener->UnsetAttr(kNameSpaceID_None, attrName, true);
+ }
+ }
+ RefPtr<Element> broadcaster =
+ mDelayedAttrChangeBroadcasts[i].mBroadcaster;
+ ExecuteOnBroadcastHandlerFor(broadcaster, listener, 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(nsGkAtoms::element, aBroadcasterID);
+ if (aBroadcasterID.IsEmpty()) {
+ return NS_FINDBROADCASTER_NOT_FOUND;
+ }
+ aElement->GetAttr(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(nsGkAtoms::observes, aBroadcasterID);
+
+ // Bail if there's no aBroadcasterID
+ if (aBroadcasterID.IsEmpty()) {
+ // Try the command attribute next.
+ aElement->GetAttr(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 mozilla::dom
diff --git a/dom/xul/XULBroadcastManager.h b/dom/xul/XULBroadcastManager.h
new file mode 100644
index 0000000000..77fe6168eb
--- /dev/null
+++ b/dom/xul/XULBroadcastManager.h
@@ -0,0 +1,91 @@
+/* -*- 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);
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY 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);
+
+ MOZ_CAN_RUN_SCRIPT 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/XULButtonElement.cpp b/dom/xul/XULButtonElement.cpp
new file mode 100644
index 0000000000..fa9ee28628
--- /dev/null
+++ b/dom/xul/XULButtonElement.cpp
@@ -0,0 +1,798 @@
+/* -*- 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 "XULButtonElement.h"
+#include "XULMenuParentElement.h"
+#include "XULPopupElement.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "mozilla/dom/AncestorIterator.h"
+#include "mozilla/dom/XULMenuBarElement.h"
+#include "nsGkAtoms.h"
+#include "nsITimer.h"
+#include "nsLayoutUtils.h"
+#include "nsCaseTreatment.h"
+#include "nsChangeHint.h"
+#include "nsMenuPopupFrame.h"
+#include "nsPlaceholderFrame.h"
+#include "nsPresContext.h"
+#include "nsXULPopupManager.h"
+#include "nsIDOMXULButtonElement.h"
+#include "nsISound.h"
+
+namespace mozilla::dom {
+
+XULButtonElement::XULButtonElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsXULElement(std::move(aNodeInfo)),
+ mIsAlwaysMenu(IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menulist,
+ nsGkAtoms::menuitem)) {}
+
+XULButtonElement::~XULButtonElement() {
+ StopBlinking();
+ KillMenuOpenTimer();
+}
+
+nsChangeHint XULButtonElement::GetAttributeChangeHint(const nsAtom* aAttribute,
+ int32_t aModType) const {
+ if (aAttribute == nsGkAtoms::type &&
+ IsAnyOfXULElements(nsGkAtoms::button, nsGkAtoms::toolbarbutton)) {
+ // type=menu switches to a menu frame.
+ return nsChangeHint_ReconstructFrame;
+ }
+ return nsXULElement::GetAttributeChangeHint(aAttribute, aModType);
+}
+
+// This global flag is used to record the timestamp when a menu was opened or
+// closed and is used to ignore the mousemove and mouseup events that would fire
+// on the menu after the mousedown occurred.
+static TimeStamp gMenuJustOpenedOrClosedTime = TimeStamp();
+
+void XULButtonElement::PopupOpened() {
+ if (!IsMenu()) {
+ return;
+ }
+ gMenuJustOpenedOrClosedTime = TimeStamp::Now();
+ SetAttr(kNameSpaceID_None, nsGkAtoms::open, u"true"_ns, true);
+}
+
+void XULButtonElement::PopupClosed(bool aDeselectMenu) {
+ if (!IsMenu()) {
+ return;
+ }
+ nsContentUtils::AddScriptRunner(
+ new nsUnsetAttrRunnable(this, nsGkAtoms::open));
+
+ if (aDeselectMenu) {
+ if (RefPtr<XULMenuParentElement> parent = GetMenuParent()) {
+ if (parent->GetActiveMenuChild() == this) {
+ parent->SetActiveMenuChild(nullptr);
+ }
+ }
+ }
+}
+
+bool XULButtonElement::IsMenuActive() const {
+ if (XULMenuParentElement* menu = GetMenuParent()) {
+ return menu->GetActiveMenuChild() == this;
+ }
+ return false;
+}
+
+void XULButtonElement::HandleEnterKeyPress(WidgetEvent& aEvent) {
+ if (IsDisabled()) {
+#ifdef XP_WIN
+ if (XULPopupElement* popup = GetContainingPopupElement()) {
+ if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
+ pm->HidePopup(
+ popup, {HidePopupOption::HideChain, HidePopupOption::DeselectMenu,
+ HidePopupOption::Async});
+ }
+ }
+#endif
+ return;
+ }
+ if (IsMenuPopupOpen()) {
+ return;
+ }
+ // The enter key press applies to us.
+ if (IsMenuItem()) {
+ ExecuteMenu(aEvent);
+ } else {
+ OpenMenuPopup(true);
+ }
+}
+
+bool XULButtonElement::IsMenuPopupOpen() {
+ nsMenuPopupFrame* popupFrame = GetMenuPopup(FlushType::None);
+ return popupFrame && popupFrame->IsOpen();
+}
+
+bool XULButtonElement::IsOnMenu() const {
+ auto* popup = XULPopupElement::FromNodeOrNull(GetMenuParent());
+ return popup && popup->IsMenu();
+}
+
+bool XULButtonElement::IsOnMenuList() const {
+ if (XULMenuParentElement* menu = GetMenuParent()) {
+ return menu->GetParent() &&
+ menu->GetParent()->IsXULElement(nsGkAtoms::menulist);
+ }
+ return false;
+}
+
+bool XULButtonElement::IsOnMenuBar() const {
+ if (XULMenuParentElement* menu = GetMenuParent()) {
+ return menu->IsMenuBar();
+ }
+ return false;
+}
+
+nsMenuPopupFrame* XULButtonElement::GetContainingPopupWithoutFlushing() const {
+ if (XULPopupElement* popup = GetContainingPopupElement()) {
+ return do_QueryFrame(popup->GetPrimaryFrame());
+ }
+ return nullptr;
+}
+
+XULPopupElement* XULButtonElement::GetContainingPopupElement() const {
+ return XULPopupElement::FromNodeOrNull(GetMenuParent());
+}
+
+bool XULButtonElement::IsOnContextMenu() const {
+ if (nsMenuPopupFrame* popup = GetContainingPopupWithoutFlushing()) {
+ return popup->IsContextMenu();
+ }
+ return false;
+}
+
+void XULButtonElement::ToggleMenuState() {
+ if (IsMenuPopupOpen()) {
+ CloseMenuPopup(false);
+ } else {
+ OpenMenuPopup(false);
+ }
+}
+
+void XULButtonElement::KillMenuOpenTimer() {
+ if (mMenuOpenTimer) {
+ mMenuOpenTimer->Cancel();
+ mMenuOpenTimer = nullptr;
+ }
+}
+
+void XULButtonElement::OpenMenuPopup(bool aSelectFirstItem) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!pm) {
+ return;
+ }
+
+ pm->KillMenuTimer();
+ if (!pm->MayShowMenu(this)) {
+ return;
+ }
+
+ if (RefPtr<XULMenuParentElement> parent = GetMenuParent()) {
+ parent->SetActiveMenuChild(this);
+ }
+
+ // Open the menu asynchronously.
+ OwnerDoc()->Dispatch(NS_NewRunnableFunction(
+ "AsyncOpenMenu", [self = RefPtr{this}, aSelectFirstItem] {
+ if (self->GetMenuParent() && !self->IsMenuActive()) {
+ return;
+ }
+ if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
+ pm->ShowMenu(self, aSelectFirstItem);
+ }
+ }));
+}
+
+void XULButtonElement::CloseMenuPopup(bool aDeselectMenu) {
+ gMenuJustOpenedOrClosedTime = TimeStamp::Now();
+ // Close the menu asynchronously
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!pm) {
+ return;
+ }
+ if (auto* popup = GetMenuPopupContent()) {
+ HidePopupOptions options{HidePopupOption::Async};
+ if (aDeselectMenu) {
+ options += HidePopupOption::DeselectMenu;
+ }
+ pm->HidePopup(popup, options);
+ }
+}
+
+int32_t XULButtonElement::MenuOpenCloseDelay() const {
+ if (IsOnMenuBar()) {
+ return 0;
+ }
+ return LookAndFeel::GetInt(LookAndFeel::IntID::SubmenuDelay, 300); // ms
+}
+
+void XULButtonElement::ExecuteMenu(Modifiers aModifiers, int16_t aButton,
+ bool aIsTrusted) {
+ MOZ_ASSERT(IsMenu());
+
+ StopBlinking();
+
+ auto menuType = GetMenuType();
+ if (NS_WARN_IF(!menuType)) {
+ return;
+ }
+
+ // Because the command event is firing asynchronously, a flag is needed to
+ // indicate whether user input is being handled. This ensures that a popup
+ // window won't get blocked.
+ const bool userinput = dom::UserActivation::IsHandlingUserInput();
+
+ // Flip "checked" state if we're a checkbox menu, or an un-checked radio menu.
+ bool needToFlipChecked = false;
+ if (*menuType == MenuType::Checkbox ||
+ (*menuType == MenuType::Radio && !GetXULBoolAttr(nsGkAtoms::checked))) {
+ needToFlipChecked = !AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
+ nsGkAtoms::_false, eCaseMatters);
+ }
+
+ mDelayedMenuCommandEvent = new nsXULMenuCommandEvent(
+ this, aIsTrusted, aModifiers, userinput, needToFlipChecked, aButton);
+ StartBlinking();
+}
+
+void XULButtonElement::StopBlinking() {
+ if (mMenuBlinkTimer) {
+ if (auto* parent = GetMenuParent()) {
+ parent->LockMenuUntilClosed(false);
+ }
+ mMenuBlinkTimer->Cancel();
+ mMenuBlinkTimer = nullptr;
+ }
+ mDelayedMenuCommandEvent = nullptr;
+}
+
+void XULButtonElement::PassMenuCommandEventToPopupManager() {
+ if (mDelayedMenuCommandEvent) {
+ if (RefPtr<nsXULPopupManager> pm = nsXULPopupManager::GetInstance()) {
+ RefPtr<nsXULMenuCommandEvent> event = std::move(mDelayedMenuCommandEvent);
+ nsCOMPtr<nsIContent> content = this;
+ pm->ExecuteMenu(content, event);
+ }
+ }
+ mDelayedMenuCommandEvent = nullptr;
+}
+
+static constexpr int32_t kBlinkDelay = 67; // milliseconds
+
+void XULButtonElement::StartBlinking() {
+ if (!LookAndFeel::GetInt(LookAndFeel::IntID::ChosenMenuItemsShouldBlink)) {
+ PassMenuCommandEventToPopupManager();
+ return;
+ }
+
+ // Blink off.
+ UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true);
+ if (auto* parent = GetMenuParent()) {
+ // Make this menu ignore events from now on.
+ parent->LockMenuUntilClosed(true);
+ }
+
+ // Set up a timer to blink back on.
+ NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mMenuBlinkTimer),
+ [](nsITimer*, void* aClosure) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ RefPtr self = static_cast<XULButtonElement*>(aClosure);
+ if (auto* parent = self->GetMenuParent()) {
+ if (parent->GetActiveMenuChild() == self) {
+ // Restore the highlighting if we're still the active item.
+ self->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, u"true"_ns,
+ true);
+ }
+ }
+ // Reuse our timer to actually execute.
+ self->mMenuBlinkTimer->InitWithNamedFuncCallback(
+ [](nsITimer*, void* aClosure) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ RefPtr self = static_cast<XULButtonElement*>(aClosure);
+ if (auto* parent = self->GetMenuParent()) {
+ parent->LockMenuUntilClosed(false);
+ }
+ self->PassMenuCommandEventToPopupManager();
+ self->StopBlinking();
+ },
+ aClosure, kBlinkDelay, nsITimer::TYPE_ONE_SHOT,
+ "XULButtonElement::ContinueBlinking");
+ },
+ this, kBlinkDelay, nsITimer::TYPE_ONE_SHOT,
+ "XULButtonElement::StartBlinking", GetMainThreadSerialEventTarget());
+}
+
+void XULButtonElement::UnbindFromTree(bool aNullParent) {
+ StopBlinking();
+ nsXULElement::UnbindFromTree(aNullParent);
+}
+
+void XULButtonElement::ExecuteMenu(WidgetEvent& aEvent) {
+ MOZ_ASSERT(IsMenu());
+ if (nsCOMPtr<nsISound> sound = do_GetService("@mozilla.org/sound;1")) {
+ sound->PlayEventSound(nsISound::EVENT_MENU_EXECUTE);
+ }
+
+ Modifiers modifiers = 0;
+ if (WidgetInputEvent* inputEvent = aEvent.AsInputEvent()) {
+ modifiers = inputEvent->mModifiers;
+ }
+
+ int16_t button = 0;
+ if (WidgetMouseEventBase* mouseEvent = aEvent.AsMouseEventBase()) {
+ button = mouseEvent->mButton;
+ }
+
+ ExecuteMenu(modifiers, button, aEvent.IsTrusted());
+}
+
+void XULButtonElement::PostHandleEventForMenus(
+ EventChainPostVisitor& aVisitor) {
+ auto* event = aVisitor.mEvent;
+
+ if (event->mOriginalTarget != this) {
+ return;
+ }
+
+ if (auto* parent = GetMenuParent()) {
+ if (NS_WARN_IF(parent->IsLocked())) {
+ return;
+ }
+ }
+
+ // If a menu just opened, ignore the mouseup event that might occur after a
+ // the mousedown event that opened it. However, if a different mousedown event
+ // occurs, just clear this flag.
+ if (!gMenuJustOpenedOrClosedTime.IsNull()) {
+ if (event->mMessage == eMouseDown) {
+ gMenuJustOpenedOrClosedTime = TimeStamp();
+ } else if (event->mMessage == eMouseUp) {
+ return;
+ }
+ }
+
+ if (event->mMessage == eKeyPress && !IsDisabled()) {
+ WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent();
+ uint32_t keyCode = keyEvent->mKeyCode;
+#ifdef XP_MACOSX
+ // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed)
+ if (!IsMenuPopupOpen() &&
+ ((keyEvent->mCharCode == ' ' && !keyEvent->IsMeta()) ||
+ (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN))) {
+ // When pressing space, don't open the menu if performing an incremental
+ // search.
+ if (keyEvent->mCharCode != ' ' ||
+ !nsMenuPopupFrame::IsWithinIncrementalTime(keyEvent->mTimeStamp)) {
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ OpenMenuPopup(false);
+ }
+ }
+#else
+ // On other platforms, toggle menulist on unmodified F4 or Alt arrow
+ if ((keyCode == NS_VK_F4 && !keyEvent->IsAlt()) ||
+ ((keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) && keyEvent->IsAlt())) {
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ ToggleMenuState();
+ }
+#endif
+ } else if (event->mMessage == eMouseDown &&
+ event->AsMouseEvent()->mButton == MouseButton::ePrimary &&
+#ifdef XP_MACOSX
+ // On mac, ctrl-click will send a context menu event from the
+ // widget, so we don't want to bring up the menu.
+ !event->AsMouseEvent()->IsControl() &&
+#endif
+ !IsDisabled() && !IsMenuItem()) {
+ // The menu item was selected. Bring up the menu.
+ // We have children.
+ // Don't prevent the default action here, since that will also cancel
+ // potential drag starts.
+ if (!IsOnMenu()) {
+ ToggleMenuState();
+ } else if (!IsMenuPopupOpen()) {
+ OpenMenuPopup(false);
+ }
+ } else if (event->mMessage == eMouseUp && IsMenuItem() && !IsDisabled() &&
+ !event->mFlags.mMultipleActionsPrevented) {
+ // We accept left and middle clicks on all menu items to activate the item.
+ // On context menus we also accept right click to activate the item, because
+ // right-clicking on an item in a context menu cannot open another context
+ // menu.
+ bool isMacCtrlClick = false;
+#ifdef XP_MACOSX
+ isMacCtrlClick = event->AsMouseEvent()->mButton == MouseButton::ePrimary &&
+ event->AsMouseEvent()->IsControl();
+#endif
+ bool clickMightOpenContextMenu =
+ event->AsMouseEvent()->mButton == MouseButton::eSecondary ||
+ isMacCtrlClick;
+ if (!clickMightOpenContextMenu || IsOnContextMenu()) {
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ ExecuteMenu(*event);
+ }
+ } else if (event->mMessage == eContextMenu && IsOnContextMenu() &&
+ !IsMenuItem() && !IsDisabled()) {
+ // Make sure we cancel default processing of the context menu event so
+ // that it doesn't bubble and get seen again by the popuplistener and show
+ // another context menu.
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ } else if (event->mMessage == eMouseOut) {
+ KillMenuOpenTimer();
+ if (RefPtr<XULMenuParentElement> parent = GetMenuParent()) {
+ if (parent->GetActiveMenuChild() == this) {
+ // Deactivate the menu on mouse out in some cases...
+ const bool shouldDeactivate = [&] {
+ if (IsMenuPopupOpen()) {
+ // If we're open we never deselect. PopupClosed will do as needed.
+ return false;
+ }
+ if (auto* menubar = XULMenuBarElement::FromNode(*parent)) {
+ // De-select when exiting a menubar item, if the menubar wasn't
+ // activated by keyboard.
+ return !menubar->IsActiveByKeyboard();
+ }
+ if (IsOnMenuList()) {
+ // Don't de-select if on a menu-list. That matches Chromium and our
+ // historical Windows behavior, see bug 1197913.
+ return false;
+ }
+ // De-select elsewhere.
+ return true;
+ }();
+
+ if (shouldDeactivate) {
+ parent->SetActiveMenuChild(nullptr);
+ }
+ }
+ }
+ } else if (event->mMessage == eMouseMove && (IsOnMenu() || IsOnMenuBar())) {
+ // Use a tolerance to address situations where a user might perform a
+ // "wiggly" click that is accompanied by near-simultaneous mousemove events.
+ const TimeDuration kTolerance = TimeDuration::FromMilliseconds(200);
+ if (!gMenuJustOpenedOrClosedTime.IsNull() &&
+ gMenuJustOpenedOrClosedTime + kTolerance < TimeStamp::Now()) {
+ gMenuJustOpenedOrClosedTime = TimeStamp();
+ return;
+ }
+
+ if (IsDisabled() && IsOnMenuList()) {
+ return;
+ }
+
+ RefPtr<XULMenuParentElement> parent = GetMenuParent();
+ MOZ_ASSERT(parent, "How did IsOnMenu{,Bar} return true then?");
+
+ const bool isOnOpenMenubar =
+ parent->IsMenuBar() && parent->GetActiveMenuChild() &&
+ parent->GetActiveMenuChild()->IsMenuPopupOpen();
+
+ parent->SetActiveMenuChild(this);
+
+ // We need to check if we really became the current menu item or not.
+ if (!IsMenuActive()) {
+ // We didn't (presumably because a context menu was active)
+ return;
+ }
+ if (IsDisabled() || IsMenuItem() || IsMenuPopupOpen() || mMenuOpenTimer) {
+ // Disabled, or already opening or what not.
+ return;
+ }
+
+ if (parent->IsMenuBar() && !isOnOpenMenubar) {
+ // We should only open on hover in the menubar iff the menubar is open
+ // already.
+ return;
+ }
+
+ // A timer is used so that it doesn't open if the user moves the mouse
+ // quickly past the menu. The MenuOpenCloseDelay ensures that only menus
+ // have this behaviour.
+ NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mMenuOpenTimer),
+ [](nsITimer*, void* aClosure) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ RefPtr self = static_cast<XULButtonElement*>(aClosure);
+ self->mMenuOpenTimer = nullptr;
+ if (self->IsMenuPopupOpen()) {
+ return;
+ }
+ // make sure we didn't open a context menu in the meantime
+ // (i.e. the user right-clicked while hovering over a submenu).
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!pm) {
+ return;
+ }
+ if (pm->HasContextMenu(nullptr) && !self->IsOnContextMenu()) {
+ return;
+ }
+ if (!self->IsMenuActive()) {
+ return;
+ }
+ self->OpenMenuPopup(false);
+ },
+ this, MenuOpenCloseDelay(), nsITimer::TYPE_ONE_SHOT,
+ "XULButtonElement::OpenMenu", GetMainThreadSerialEventTarget());
+ }
+}
+
+nsresult XULButtonElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
+ if (aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault) {
+ return nsXULElement::PostHandleEvent(aVisitor);
+ }
+
+ if (IsMenu()) {
+ PostHandleEventForMenus(aVisitor);
+ return nsXULElement::PostHandleEvent(aVisitor);
+ }
+
+ auto* event = aVisitor.mEvent;
+ switch (event->mMessage) {
+ case eBlur: {
+ Blurred();
+ break;
+ }
+ case eKeyDown: {
+ WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent();
+ if (!keyEvent) {
+ break;
+ }
+ if (keyEvent->ShouldWorkAsSpaceKey() && aVisitor.mPresContext) {
+ EventStateManager* esm = aVisitor.mPresContext->EventStateManager();
+ // :hover:active state
+ esm->SetContentState(this, ElementState::HOVER);
+ esm->SetContentState(this, ElementState::ACTIVE);
+ mIsHandlingKeyEvent = true;
+ }
+ break;
+ }
+
+// On mac, Return fires the default button, not the focused one.
+#ifndef XP_MACOSX
+ case eKeyPress: {
+ WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent();
+ if (!keyEvent) {
+ break;
+ }
+ if (NS_VK_RETURN == keyEvent->mKeyCode) {
+ if (RefPtr<nsIDOMXULButtonElement> button = AsXULButton()) {
+ if (MouseClicked(*keyEvent)) {
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ }
+ break;
+ }
+#endif
+
+ case eKeyUp: {
+ WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent();
+ if (!keyEvent) {
+ break;
+ }
+ if (keyEvent->ShouldWorkAsSpaceKey()) {
+ mIsHandlingKeyEvent = false;
+ ElementState buttonState = State();
+ if (buttonState.HasAllStates(ElementState::ACTIVE |
+ ElementState::HOVER) &&
+ aVisitor.mPresContext) {
+ // return to normal state
+ EventStateManager* esm = aVisitor.mPresContext->EventStateManager();
+ esm->SetContentState(nullptr, ElementState::ACTIVE);
+ esm->SetContentState(nullptr, ElementState::HOVER);
+ if (MouseClicked(*keyEvent)) {
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ }
+ break;
+ }
+
+ case eMouseClick: {
+ WidgetMouseEvent* mouseEvent = event->AsMouseEvent();
+ if (mouseEvent->IsLeftClickEvent()) {
+ if (MouseClicked(*mouseEvent)) {
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return nsXULElement::PostHandleEvent(aVisitor);
+}
+
+void XULButtonElement::Blurred() {
+ ElementState buttonState = State();
+ if (mIsHandlingKeyEvent &&
+ buttonState.HasAllStates(ElementState::ACTIVE | ElementState::HOVER)) {
+ // Return to normal state
+ if (nsPresContext* pc = OwnerDoc()->GetPresContext()) {
+ EventStateManager* esm = pc->EventStateManager();
+ esm->SetContentState(nullptr, ElementState::ACTIVE);
+ esm->SetContentState(nullptr, ElementState::HOVER);
+ }
+ }
+ mIsHandlingKeyEvent = false;
+}
+
+bool XULButtonElement::MouseClicked(WidgetGUIEvent& aEvent) {
+ // Don't execute if we're disabled.
+ if (IsDisabled() || !IsInComposedDoc()) {
+ return false;
+ }
+
+ // Have the content handle the event, propagating it according to normal DOM
+ // rules.
+ RefPtr<mozilla::PresShell> presShell = OwnerDoc()->GetPresShell();
+ if (!presShell) {
+ return false;
+ }
+
+ // Execute the oncommand event handler.
+ WidgetInputEvent* inputEvent = aEvent.AsInputEvent();
+ WidgetMouseEventBase* mouseEvent = aEvent.AsMouseEventBase();
+ WidgetKeyboardEvent* keyEvent = aEvent.AsKeyboardEvent();
+ // TODO: Set aSourceEvent?
+ nsContentUtils::DispatchXULCommand(
+ this, aEvent.IsTrusted(), /* aSourceEvent = */ nullptr, presShell,
+ inputEvent->IsControl(), inputEvent->IsAlt(), inputEvent->IsShift(),
+ inputEvent->IsMeta(),
+ mouseEvent ? mouseEvent->mInputSource
+ : (keyEvent ? MouseEvent_Binding::MOZ_SOURCE_KEYBOARD
+ : MouseEvent_Binding::MOZ_SOURCE_UNKNOWN),
+ mouseEvent ? mouseEvent->mButton : 0);
+ return true;
+}
+
+bool XULButtonElement::IsMenu() const {
+ if (mIsAlwaysMenu) {
+ return true;
+ }
+ return IsAnyOfXULElements(nsGkAtoms::button, nsGkAtoms::toolbarbutton) &&
+ AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::menu,
+ eCaseMatters);
+}
+
+void XULButtonElement::UncheckRadioSiblings() {
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
+ MOZ_ASSERT(GetMenuType() == Some(MenuType::Radio));
+ nsAutoString groupName;
+ GetAttr(nsGkAtoms::name, groupName);
+
+ nsIContent* parent = GetParent();
+ if (!parent) {
+ return;
+ }
+
+ auto ShouldUncheck = [&](const nsIContent& aSibling) {
+ const auto* button = XULButtonElement::FromNode(aSibling);
+ if (!button || button->GetMenuType() != Some(MenuType::Radio)) {
+ return false;
+ }
+ if (const auto* attr = button->GetParsedAttr(nsGkAtoms::name)) {
+ if (!attr->Equals(groupName, eCaseMatters)) {
+ return false;
+ }
+ } else if (!groupName.IsEmpty()) {
+ return false;
+ }
+ // we're in the same group, only uncheck if we're checked (for some reason,
+ // some tests rely on that specifically).
+ return button->GetXULBoolAttr(nsGkAtoms::checked);
+ };
+
+ for (nsIContent* child = parent->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child == this || !ShouldUncheck(*child)) {
+ continue;
+ }
+ child->AsElement()->UnsetAttr(nsGkAtoms::checked, IgnoreErrors());
+ }
+}
+
+void XULButtonElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue,
+ const nsAttrValue* aOldValue,
+ nsIPrincipal* aSubjectPrincipal,
+ bool aNotify) {
+ nsXULElement::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue,
+ aSubjectPrincipal, aNotify);
+ if (IsAlwaysMenu() && aNamespaceID == kNameSpaceID_None) {
+ // We need to uncheck radio siblings when we're a checked radio and switch
+ // groups, or become checked.
+ const bool shouldUncheckSiblings = [&] {
+ if (aName == nsGkAtoms::type || aName == nsGkAtoms::name) {
+ return *GetMenuType() == MenuType::Radio &&
+ GetXULBoolAttr(nsGkAtoms::checked);
+ }
+ if (aName == nsGkAtoms::checked && aValue &&
+ aValue->Equals(nsGkAtoms::_true, eCaseMatters)) {
+ return *GetMenuType() == MenuType::Radio;
+ }
+ return false;
+ }();
+ if (shouldUncheckSiblings) {
+ UncheckRadioSiblings();
+ }
+ }
+}
+
+auto XULButtonElement::GetMenuType() const -> Maybe<MenuType> {
+ if (!IsAlwaysMenu()) {
+ return Nothing();
+ }
+
+ static Element::AttrValuesArray values[] = {nsGkAtoms::checkbox,
+ nsGkAtoms::radio, nullptr};
+ switch (FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, values,
+ eCaseMatters)) {
+ case 0:
+ return Some(MenuType::Checkbox);
+ case 1:
+ return Some(MenuType::Radio);
+ default:
+ return Some(MenuType::Normal);
+ }
+}
+
+XULMenuBarElement* XULButtonElement::GetMenuBar() const {
+ if (!IsMenu()) {
+ return nullptr;
+ }
+ return FirstAncestorOfType<XULMenuBarElement>();
+}
+
+XULMenuParentElement* XULButtonElement::GetMenuParent() const {
+ if (IsXULElement(nsGkAtoms::menulist)) {
+ return nullptr;
+ }
+ return FirstAncestorOfType<XULMenuParentElement>();
+}
+
+XULPopupElement* XULButtonElement::GetMenuPopupContent() const {
+ if (!IsMenu()) {
+ return nullptr;
+ }
+ for (auto* child = GetFirstChild(); child; child = child->GetNextSibling()) {
+ if (auto* popup = XULPopupElement::FromNode(child)) {
+ return popup;
+ }
+ }
+ return nullptr;
+}
+
+nsMenuPopupFrame* XULButtonElement::GetMenuPopupWithoutFlushing() const {
+ return const_cast<XULButtonElement*>(this)->GetMenuPopup(FlushType::None);
+}
+
+nsMenuPopupFrame* XULButtonElement::GetMenuPopup(FlushType aFlushType) {
+ RefPtr popup = GetMenuPopupContent();
+ if (!popup) {
+ return nullptr;
+ }
+ return do_QueryFrame(popup->GetPrimaryFrame(aFlushType));
+}
+
+bool XULButtonElement::OpenedWithKey() const {
+ auto* menubar = GetMenuBar();
+ return menubar && menubar->IsActiveByKeyboard();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/xul/XULButtonElement.h b/dom/xul/XULButtonElement.h
new file mode 100644
index 0000000000..fc880210a2
--- /dev/null
+++ b/dom/xul/XULButtonElement.h
@@ -0,0 +1,123 @@
+/* -*- 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 dom_xul_XULButtonElement_h__
+#define dom_xul_XULButtonElement_h__
+
+#include "mozilla/Attributes.h"
+#include "nsINode.h"
+#include "nsXULElement.h"
+
+class nsMenuBarFrame;
+class nsMenuPopupFrame;
+class nsXULMenuCommandEvent;
+
+namespace mozilla::dom {
+
+class KeyboardEvent;
+class XULPopupElement;
+class XULMenuBarElement;
+class XULMenuParentElement;
+
+class XULButtonElement : public nsXULElement {
+ public:
+ explicit XULButtonElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+ ~XULButtonElement() override;
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY bool MouseClicked(WidgetGUIEvent&);
+ MOZ_CAN_RUN_SCRIPT nsresult PostHandleEvent(EventChainPostVisitor&) override;
+ MOZ_CAN_RUN_SCRIPT void PostHandleEventForMenus(EventChainPostVisitor&);
+ MOZ_CAN_RUN_SCRIPT void HandleEnterKeyPress(WidgetEvent&);
+
+ void PopupOpened();
+ MOZ_CAN_RUN_SCRIPT void PopupClosed(bool aDeselectMenu);
+
+ XULPopupElement* GetContainingPopupElement() const;
+ nsMenuPopupFrame* GetContainingPopupWithoutFlushing() const;
+ MOZ_CAN_RUN_SCRIPT void ToggleMenuState();
+ bool IsMenuPopupOpen();
+
+ bool IsMenuItem() const { return NodeInfo()->Equals(nsGkAtoms::menuitem); }
+ bool IsMenuList() const { return NodeInfo()->Equals(nsGkAtoms::menulist); }
+ bool IsMenuActive() const;
+ MOZ_CAN_RUN_SCRIPT void OpenMenuPopup(bool aSelectFirstItem);
+ void CloseMenuPopup(bool aDeselectMenu);
+
+ bool IsOnMenu() const;
+ bool IsOnMenuList() const;
+ bool IsOnMenuBar() const;
+ bool IsOnContextMenu() const;
+
+ XULMenuParentElement* GetMenuParent() const;
+
+ void UnbindFromTree(bool aNullParent) override;
+
+ MOZ_CAN_RUN_SCRIPT bool HandleKeyPress(KeyboardEvent& keyEvent);
+ bool OpenedWithKey() const;
+ // Called to execute our command handler.
+ MOZ_CAN_RUN_SCRIPT void ExecuteMenu(WidgetEvent&);
+ MOZ_CAN_RUN_SCRIPT void ExecuteMenu(Modifiers, int16_t aButton,
+ bool aIsTrusted);
+
+ // Whether we are a menu/menulist/menuitem element.
+ bool IsAlwaysMenu() const { return mIsAlwaysMenu; }
+ // Whether we should behave like a menu. This is the above plus buttons with
+ // type=menu attribute.
+ bool IsMenu() const;
+
+ nsChangeHint GetAttributeChangeHint(const nsAtom* aAttribute,
+ int32_t aModType) const override;
+ void AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue, const nsAttrValue* aOldValue,
+ nsIPrincipal* aSubjectPrincipal, bool aNotify) override;
+
+ NS_IMPL_FROMNODE_HELPER(XULButtonElement,
+ IsAnyOfXULElements(nsGkAtoms::checkbox,
+ nsGkAtoms::radio, nsGkAtoms::thumb,
+ nsGkAtoms::button, nsGkAtoms::menu,
+ nsGkAtoms::menulist,
+ nsGkAtoms::menuitem,
+ nsGkAtoms::toolbarbutton,
+ nsGkAtoms::toolbarpaletteitem,
+ nsGkAtoms::scrollbarbutton))
+
+ nsMenuPopupFrame* GetMenuPopup(FlushType aFlushType);
+ nsMenuPopupFrame* GetMenuPopupWithoutFlushing() const;
+ XULPopupElement* GetMenuPopupContent() const;
+ int32_t MenuOpenCloseDelay() const;
+
+ bool IsDisabled() const { return GetXULBoolAttr(nsGkAtoms::disabled); }
+
+ private:
+ XULMenuBarElement* GetMenuBar() const;
+ void Blurred();
+ enum class MenuType {
+ Checkbox,
+ Radio,
+ Normal,
+ };
+ Maybe<MenuType> GetMenuType() const;
+
+ void UncheckRadioSiblings();
+ void StopBlinking();
+ MOZ_CAN_RUN_SCRIPT void StartBlinking();
+ void KillMenuOpenTimer();
+ MOZ_CAN_RUN_SCRIPT void PassMenuCommandEventToPopupManager();
+
+ bool mIsHandlingKeyEvent = false;
+
+ // Whether this is a XULMenuElement.
+ const bool mIsAlwaysMenu;
+ RefPtr<nsXULMenuCommandEvent> mDelayedMenuCommandEvent;
+ nsCOMPtr<nsITimer> mMenuOpenTimer;
+ nsCOMPtr<nsITimer> mMenuBlinkTimer;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/xul/XULFrameElement.cpp b/dom/xul/XULFrameElement.cpp
new file mode 100644
index 0000000000..efbeb15829
--- /dev/null
+++ b/dom/xul/XULFrameElement.cpp
@@ -0,0 +1,200 @@
+/* -*- 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::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;
+ }
+
+ AsyncEventDispatcher::RunDOMEventWhenSafe(
+ *this, u"XULFrameLoaderCreated"_ns, CanBubble::eYes);
+ }
+
+ 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();
+}
+
+void 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 mozilla::dom
diff --git a/dom/xul/XULFrameElement.h b/dom/xul/XULFrameElement.h
new file mode 100644
index 0000000000..13a65f42e8
--- /dev/null
+++ b/dom/xul/XULFrameElement.h
@@ -0,0 +1,84 @@
+/* -*- 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
+ nsresult BindToTree(BindContext&, nsINode& aParent) override;
+ void UnbindFromTree(bool aNullParent) override;
+ void DestroyContent() override;
+
+ void 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/XULMenuBarElement.cpp b/dom/xul/XULMenuBarElement.cpp
new file mode 100644
index 0000000000..f32698b3cd
--- /dev/null
+++ b/dom/xul/XULMenuBarElement.cpp
@@ -0,0 +1,103 @@
+/* -*- 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 "XULMenuBarElement.h"
+#include "MenuBarListener.h"
+#include "XULButtonElement.h"
+#include "nsXULPopupManager.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/BindContext.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Try.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(XULMenuBarElement)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULMenuBarElement,
+ XULMenuParentElement)
+ if (tmp->mListener) {
+ tmp->mListener->Detach();
+ tmp->mListener = nullptr;
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XULMenuBarElement,
+ XULMenuParentElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListener)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(XULMenuBarElement,
+ XULMenuParentElement)
+
+XULMenuBarElement::XULMenuBarElement(
+ already_AddRefed<class NodeInfo>&& aNodeInfo)
+ : XULMenuParentElement(std::move(aNodeInfo)) {}
+
+XULMenuBarElement::~XULMenuBarElement() { MOZ_DIAGNOSTIC_ASSERT(!mListener); }
+
+void XULMenuBarElement::SetActive(bool aActiveFlag) {
+ // If the activity is not changed, there is nothing to do.
+ if (mIsActive == aActiveFlag) {
+ return;
+ }
+
+ // We can't activate a menubar outside of the document.
+ if (!IsInComposedDoc()) {
+ MOZ_ASSERT(!mIsActive, "How?");
+ return;
+ }
+
+ if (!aActiveFlag) {
+ // If there is a request to deactivate the menu bar, check to see whether
+ // there is a menu popup open for the menu bar. In this case, don't
+ // deactivate the menu bar.
+ if (auto* activeChild = GetActiveMenuChild()) {
+ if (activeChild->IsMenuPopupOpen()) {
+ return;
+ }
+ }
+ }
+
+ mIsActive = aActiveFlag;
+ if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
+ pm->SetActiveMenuBar(this, aActiveFlag);
+ }
+ if (!aActiveFlag) {
+ mActiveByKeyboard = false;
+ SetActiveMenuChild(nullptr);
+ }
+
+ RefPtr dispatcher = new AsyncEventDispatcher(
+ this, aActiveFlag ? u"DOMMenuBarActive"_ns : u"DOMMenuBarInactive"_ns,
+ CanBubble::eYes, ChromeOnlyDispatch::eNo);
+ DebugOnly<nsresult> rv = dispatcher->PostDOMEvent();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncEventDispatcher failed to dispatch");
+}
+
+nsresult XULMenuBarElement::BindToTree(BindContext& aContext,
+ nsINode& aParent) {
+ MOZ_TRY(XULMenuParentElement::BindToTree(aContext, aParent));
+ MOZ_DIAGNOSTIC_ASSERT(!mListener);
+ if (aContext.InComposedDoc()) {
+ mListener = new MenuBarListener(*this);
+ }
+ return NS_OK;
+}
+
+void XULMenuBarElement::UnbindFromTree(bool aNullParent) {
+ if (mListener) {
+ mListener->Detach();
+ mListener = nullptr;
+ }
+ if (NS_WARN_IF(mIsActive)) {
+ // Clean up silently when getting removed from the document while active.
+ mIsActive = false;
+ if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
+ pm->SetActiveMenuBar(this, false);
+ }
+ }
+ return XULMenuParentElement::UnbindFromTree(aNullParent);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/xul/XULMenuBarElement.h b/dom/xul/XULMenuBarElement.h
new file mode 100644
index 0000000000..117682642d
--- /dev/null
+++ b/dom/xul/XULMenuBarElement.h
@@ -0,0 +1,59 @@
+/* -*- 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 XULMenuBarElement_h__
+#define XULMenuBarElement_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "nsINode.h"
+#include "nsISupports.h"
+#include "XULMenuParentElement.h"
+
+namespace mozilla::dom {
+
+class KeyboardEvent;
+class XULButtonElement;
+class MenuBarListener;
+
+nsXULElement* NS_NewXULMenuBarElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+class XULMenuBarElement final : public XULMenuParentElement {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULMenuBarElement,
+ XULMenuParentElement)
+ NS_IMPL_FROMNODE_WITH_TAG(XULMenuBarElement, kNameSpaceID_XUL, menubar)
+
+ explicit XULMenuBarElement(already_AddRefed<class NodeInfo>&&);
+
+ MOZ_CAN_RUN_SCRIPT void SetActive(bool);
+ bool IsActive() const { return mIsActive; }
+
+ void SetActiveByKeyboard() { mActiveByKeyboard = true; }
+ bool IsActiveByKeyboard() const { return mActiveByKeyboard; }
+
+ nsresult BindToTree(BindContext&, nsINode& aParent) override;
+ void UnbindFromTree(bool aNullParent) override;
+
+ protected:
+ ~XULMenuBarElement() override;
+
+ // Whether or not the menu bar is active (a menu item is highlighted or
+ // shown).
+ bool mIsActive = false;
+
+ // Whether the menubar was made active via the keyboard.
+ bool mActiveByKeyboard = false;
+
+ // The event listener that listens to document key presses and so on.
+ RefPtr<MenuBarListener> mListener;
+};
+
+} // namespace mozilla::dom
+
+#endif // XULMenuBarElement_h
diff --git a/dom/xul/XULMenuElement.cpp b/dom/xul/XULMenuElement.cpp
new file mode 100644
index 0000000000..928e850012
--- /dev/null
+++ b/dom/xul/XULMenuElement.cpp
@@ -0,0 +1,82 @@
+/* -*- 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/XULMenuElement.h"
+#include "mozilla/StaticAnalysisFunctions.h"
+#include "mozilla/dom/XULButtonElement.h"
+#include "mozilla/dom/XULMenuElementBinding.h"
+#include "mozilla/dom/XULPopupElement.h"
+#include "mozilla/dom/KeyboardEvent.h"
+#include "mozilla/dom/KeyboardEventBinding.h"
+#include "nsXULPopupManager.h"
+#include "nsMenuPopupFrame.h"
+
+namespace mozilla::dom {
+
+JSObject* XULMenuElement::WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return XULMenuElement_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+Element* XULMenuElement::GetActiveMenuChild() {
+ RefPtr popup = GetMenuPopupContent();
+ return popup ? popup->GetActiveMenuChild() : nullptr;
+}
+
+void XULMenuElement::SetActiveMenuChild(Element* aChild) {
+ RefPtr popup = GetMenuPopupContent();
+ if (NS_WARN_IF(!popup)) {
+ return;
+ }
+
+ if (!aChild) {
+ popup->SetActiveMenuChild(nullptr);
+ return;
+ }
+ auto* button = XULButtonElement::FromNode(aChild);
+ if (NS_WARN_IF(!button) || NS_WARN_IF(!button->IsMenu())) {
+ return;
+ }
+ // KnownLive because it's aChild.
+ popup->SetActiveMenuChild(MOZ_KnownLive(button));
+}
+
+bool XULButtonElement::HandleKeyPress(KeyboardEvent& keyEvent) {
+ RefPtr<nsXULPopupManager> pm = nsXULPopupManager::GetInstance();
+ if (!pm) {
+ return false;
+ }
+
+ // if event has already been handled, bail
+ if (keyEvent.DefaultPrevented()) {
+ return false;
+ }
+
+ if (keyEvent.IsMenuAccessKeyPressed()) {
+ return false;
+ }
+
+ nsMenuPopupFrame* popupFrame = GetMenuPopup(FlushType::Frames);
+ if (NS_WARN_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);
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/xul/XULMenuElement.h b/dom/xul/XULMenuElement.h
new file mode 100644
index 0000000000..5def6839fc
--- /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 "XULButtonElement.h"
+#include "nsINode.h"
+#include "nsXULElement.h"
+
+namespace mozilla::dom {
+
+class KeyboardEvent;
+
+class XULMenuElement final : public XULButtonElement {
+ public:
+ explicit XULMenuElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : XULButtonElement(std::move(aNodeInfo)) {}
+
+ MOZ_CAN_RUN_SCRIPT void SetActiveMenuChild(Element*);
+ Element* GetActiveMenuChild();
+
+ NS_IMPL_FROMNODE_HELPER(XULMenuElement,
+ IsAnyOfXULElements(nsGkAtoms::menu,
+ nsGkAtoms::menulist));
+
+ private:
+ virtual ~XULMenuElement() = default;
+ JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+};
+
+} // namespace mozilla::dom
+
+#endif // XULMenuElement_h
diff --git a/dom/xul/XULMenuParentElement.cpp b/dom/xul/XULMenuParentElement.cpp
new file mode 100644
index 0000000000..c457187025
--- /dev/null
+++ b/dom/xul/XULMenuParentElement.cpp
@@ -0,0 +1,398 @@
+/* -*- 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 "XULMenuParentElement.h"
+#include "XULButtonElement.h"
+#include "XULMenuBarElement.h"
+#include "XULPopupElement.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/StaticAnalysisFunctions.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/KeyboardEvent.h"
+#include "mozilla/EventDispatcher.h"
+#include "nsDebug.h"
+#include "nsMenuPopupFrame.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+#include "nsUTF8Utils.h"
+#include "nsXULElement.h"
+#include "nsXULPopupManager.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(XULMenuParentElement,
+ nsXULElement)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(XULMenuParentElement, nsXULElement,
+ mActiveItem)
+
+XULMenuParentElement::XULMenuParentElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsXULElement(std::move(aNodeInfo)) {}
+
+XULMenuParentElement::~XULMenuParentElement() = default;
+
+class MenuActivateEvent final : public Runnable {
+ public:
+ MenuActivateEvent(Element* aMenu, bool aIsActivate)
+ : Runnable("MenuActivateEvent"), mMenu(aMenu), mIsActivate(aIsActivate) {}
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
+ nsAutoString domEventToFire;
+ if (mIsActivate) {
+ // Highlight the menu.
+ mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, u"true"_ns,
+ true);
+ // The menuactivated event is used by accessibility to track the user's
+ // movements through menus
+ domEventToFire.AssignLiteral("DOMMenuItemActive");
+ } else {
+ // Unhighlight the menu.
+ mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true);
+ domEventToFire.AssignLiteral("DOMMenuItemInactive");
+ }
+
+ RefPtr<nsPresContext> pc = mMenu->OwnerDoc()->GetPresContext();
+ RefPtr<dom::Event> event = NS_NewDOMEvent(mMenu, pc, nullptr);
+ event->InitEvent(domEventToFire, true, true);
+
+ event->SetTrusted(true);
+
+ EventDispatcher::DispatchDOMEvent(mMenu, nullptr, event, pc, nullptr);
+ return NS_OK;
+ }
+
+ private:
+ const RefPtr<Element> mMenu;
+ bool mIsActivate;
+};
+
+static void ActivateOrDeactivate(XULButtonElement& aButton, bool aActivate) {
+ if (!aButton.IsMenu()) {
+ return;
+ }
+
+ if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
+ if (aActivate) {
+ // Cancel the close timer if selecting a menu within the popup to be
+ // closed.
+ pm->CancelMenuTimer(aButton.GetContainingPopupWithoutFlushing());
+ } else if (auto* popup = aButton.GetMenuPopupWithoutFlushing()) {
+ if (popup->IsOpen()) {
+ // Set up the close timer if deselecting an open sub-menu.
+ pm->HidePopupAfterDelay(popup, aButton.MenuOpenCloseDelay());
+ }
+ }
+ }
+
+ nsCOMPtr<nsIRunnable> event = new MenuActivateEvent(&aButton, aActivate);
+ aButton.OwnerDoc()->Dispatch(event.forget());
+}
+
+XULButtonElement* XULMenuParentElement::GetContainingMenu() const {
+ if (IsMenuBar()) {
+ return nullptr;
+ }
+ auto* button = XULButtonElement::FromNodeOrNull(GetParent());
+ if (!button || !button->IsMenu()) {
+ return nullptr;
+ }
+ return button;
+}
+
+void XULMenuParentElement::LockMenuUntilClosed(bool aLock) {
+ if (IsMenuBar()) {
+ return;
+ }
+ mLocked = aLock;
+ // Lock/Unlock the parent menu too.
+ if (XULButtonElement* menu = GetContainingMenu()) {
+ if (XULMenuParentElement* parent = menu->GetMenuParent()) {
+ parent->LockMenuUntilClosed(aLock);
+ }
+ }
+}
+
+void XULMenuParentElement::SetActiveMenuChild(XULButtonElement* aChild,
+ ByKey aByKey) {
+ if (aChild == mActiveItem) {
+ return;
+ }
+
+ if (mActiveItem) {
+ ActivateOrDeactivate(*mActiveItem, false);
+ }
+ mActiveItem = nullptr;
+
+ if (auto* menuBar = XULMenuBarElement::FromNode(*this)) {
+ // KnownLive because `this` is known-live by definition.
+ MOZ_KnownLive(menuBar)->SetActive(!!aChild);
+ }
+
+ if (!aChild) {
+ return;
+ }
+
+ // When a menu opens a submenu, the mouse will often be moved onto a sibling
+ // before moving onto an item within the submenu, causing the parent to become
+ // deselected. We need to ensure that the parent menu is reselected when an
+ // item in the submenu is selected.
+ if (RefPtr menu = GetContainingMenu()) {
+ if (RefPtr parent = menu->GetMenuParent()) {
+ parent->SetActiveMenuChild(menu, aByKey);
+ }
+ }
+
+ mActiveItem = aChild;
+ ActivateOrDeactivate(*mActiveItem, true);
+
+ if (IsInMenuList()) {
+ if (nsMenuPopupFrame* f = do_QueryFrame(GetPrimaryFrame())) {
+ f->EnsureActiveMenuListItemIsVisible();
+#ifdef XP_WIN
+ // On Windows, a menulist should update its value whenever navigation was
+ // done by the keyboard.
+ //
+ // NOTE(emilio): This is a rather odd per-platform behavior difference,
+ // but other browsers also do this.
+ if (aByKey == ByKey::Yes && f->IsOpen()) {
+ // Fire a command event as the new item, but we don't want to close the
+ // menu, blink it, or update any other state of the menuitem. The
+ // command event will cause the item to be selected.
+ RefPtr<mozilla::PresShell> presShell = OwnerDoc()->GetPresShell();
+ nsContentUtils::DispatchXULCommand(aChild, /* aTrusted = */ true,
+ nullptr, presShell, false, false,
+ false, false);
+ }
+#endif
+ }
+ }
+}
+
+static bool IsValidMenuItem(const XULMenuParentElement& aMenuParent,
+ const nsIContent& aContent) {
+ const auto* button = XULButtonElement::FromNode(aContent);
+ if (!button || !button->IsMenu()) {
+ return false;
+ }
+ if (!button->GetPrimaryFrame()) {
+ // Hidden buttons are not focusable/activatable.
+ return false;
+ }
+ if (!button->IsDisabled()) {
+ return true;
+ }
+ // In the menubar or a menulist disabled items are always skipped.
+ const bool skipDisabled =
+ LookAndFeel::GetInt(LookAndFeel::IntID::SkipNavigatingDisabledMenuItem) ||
+ aMenuParent.IsMenuBar() || aMenuParent.IsInMenuList();
+ return !skipDisabled;
+}
+
+enum class StartKind { Parent, Item };
+
+template <bool aForward>
+static XULButtonElement* DoGetNextMenuItem(
+ const XULMenuParentElement& aMenuParent, const nsIContent& aStart,
+ StartKind aStartKind) {
+ nsIContent* start =
+ aStartKind == StartKind::Item
+ ? (aForward ? aStart.GetNextSibling() : aStart.GetPreviousSibling())
+ : (aForward ? aStart.GetFirstChild() : aStart.GetLastChild());
+ for (nsIContent* node = start; node;
+ node = aForward ? node->GetNextSibling() : node->GetPreviousSibling()) {
+ if (IsValidMenuItem(aMenuParent, *node)) {
+ return static_cast<XULButtonElement*>(node);
+ }
+ if (node->IsXULElement(nsGkAtoms::menugroup)) {
+ if (XULButtonElement* child = DoGetNextMenuItem<aForward>(
+ aMenuParent, *node, StartKind::Parent)) {
+ return child;
+ }
+ }
+ }
+ if (aStartKind == StartKind::Item && aStart.GetParent() &&
+ aStart.GetParent()->IsXULElement(nsGkAtoms::menugroup)) {
+ // We haven't found anything in aStart's sibling list, but if we're in a
+ // group we need to keep looking.
+ return DoGetNextMenuItem<aForward>(aMenuParent, *aStart.GetParent(),
+ StartKind::Item);
+ }
+ return nullptr;
+}
+
+XULButtonElement* XULMenuParentElement::GetFirstMenuItem() const {
+ return DoGetNextMenuItem<true>(*this, *this, StartKind::Parent);
+}
+
+XULButtonElement* XULMenuParentElement::GetLastMenuItem() const {
+ return DoGetNextMenuItem<false>(*this, *this, StartKind::Parent);
+}
+
+XULButtonElement* XULMenuParentElement::GetNextMenuItemFrom(
+ const XULButtonElement& aStartingItem) const {
+ return DoGetNextMenuItem<true>(*this, aStartingItem, StartKind::Item);
+}
+
+XULButtonElement* XULMenuParentElement::GetPrevMenuItemFrom(
+ const XULButtonElement& aStartingItem) const {
+ return DoGetNextMenuItem<false>(*this, aStartingItem, StartKind::Item);
+}
+
+XULButtonElement* XULMenuParentElement::GetNextMenuItem(Wrap aWrap) const {
+ if (mActiveItem) {
+ if (auto* next = GetNextMenuItemFrom(*mActiveItem)) {
+ return next;
+ }
+ if (aWrap == Wrap::No) {
+ return nullptr;
+ }
+ }
+ return GetFirstMenuItem();
+}
+
+XULButtonElement* XULMenuParentElement::GetPrevMenuItem(Wrap aWrap) const {
+ if (mActiveItem) {
+ if (auto* prev = GetPrevMenuItemFrom(*mActiveItem)) {
+ return prev;
+ }
+ if (aWrap == Wrap::No) {
+ return nullptr;
+ }
+ }
+ return GetLastMenuItem();
+}
+
+void XULMenuParentElement::SelectFirstItem() {
+ if (RefPtr firstItem = GetFirstMenuItem()) {
+ SetActiveMenuChild(firstItem);
+ }
+}
+
+XULButtonElement* XULMenuParentElement::FindMenuWithShortcut(
+ KeyboardEvent& aKeyEvent) const {
+ using AccessKeyArray = AutoTArray<uint32_t, 10>;
+ AccessKeyArray accessKeys;
+ WidgetKeyboardEvent* nativeKeyEvent =
+ aKeyEvent.WidgetEventPtr()->AsKeyboardEvent();
+ if (nativeKeyEvent) {
+ nativeKeyEvent->GetAccessKeyCandidates(accessKeys);
+ }
+ const uint32_t charCode = aKeyEvent.CharCode();
+ if (accessKeys.IsEmpty() && charCode) {
+ accessKeys.AppendElement(charCode);
+ }
+ if (accessKeys.IsEmpty()) {
+ return nullptr; // no character was pressed so just return
+ }
+ XULButtonElement* foundMenu = nullptr;
+ size_t foundIndex = AccessKeyArray::NoIndex;
+ for (auto* item = GetFirstMenuItem(); item;
+ item = GetNextMenuItemFrom(*item)) {
+ nsAutoString shortcutKey;
+ item->GetAttr(nsGkAtoms::accesskey, shortcutKey);
+ if (shortcutKey.IsEmpty()) {
+ continue;
+ }
+
+ ToLowerCase(shortcutKey);
+ const char16_t* start = shortcutKey.BeginReading();
+ const char16_t* end = shortcutKey.EndReading();
+ uint32_t ch = UTF16CharEnumerator::NextChar(&start, end);
+ size_t index = accessKeys.IndexOf(ch);
+ if (index == AccessKeyArray::NoIndex) {
+ continue;
+ }
+ if (foundIndex == AccessKeyArray::NoIndex || index < foundIndex) {
+ foundMenu = item;
+ foundIndex = index;
+ }
+ }
+ return foundMenu;
+}
+
+XULButtonElement* XULMenuParentElement::FindMenuWithShortcut(
+ const nsAString& aString, bool& aDoAction) const {
+ aDoAction = false;
+ uint32_t accessKeyMatchCount = 0;
+ uint32_t matchCount = 0;
+
+ XULButtonElement* foundAccessKeyMenu = nullptr;
+ XULButtonElement* foundMenuBeforeCurrent = nullptr;
+ XULButtonElement* foundMenuAfterCurrent = nullptr;
+
+ bool foundActive = false;
+ for (auto* item = GetFirstMenuItem(); item;
+ item = GetNextMenuItemFrom(*item)) {
+ nsAutoString textKey;
+ // Get the shortcut attribute.
+ item->GetAttr(nsGkAtoms::accesskey, textKey);
+ const bool isAccessKey = !textKey.IsEmpty();
+ if (textKey.IsEmpty()) { // No shortcut, try first letter
+ item->GetAttr(nsGkAtoms::label, textKey);
+ if (textKey.IsEmpty()) { // No label, try another attribute (value)
+ item->GetAttr(nsGkAtoms::value, textKey);
+ }
+ }
+
+ const bool isActive = item == GetActiveMenuChild();
+ foundActive |= isActive;
+
+ if (!StringBeginsWith(
+ nsContentUtils::TrimWhitespace<
+ nsContentUtils::IsHTMLWhitespaceOrNBSP>(textKey, false),
+ aString, nsCaseInsensitiveStringComparator)) {
+ continue;
+ }
+ // There is one match
+ matchCount++;
+ if (isAccessKey) {
+ // There is one shortcut-key match
+ accessKeyMatchCount++;
+ // Record the matched item. If there is only one matched shortcut
+ // item, do it
+ foundAccessKeyMenu = item;
+ }
+ // Get the active status
+ if (isActive && aString.Length() > 1 && !foundMenuBeforeCurrent) {
+ // If there is more than one char typed and the current item matches, the
+ // current item has highest priority, otherwise the item next to current
+ // has highest priority.
+ return item;
+ }
+ if (!foundActive || isActive) {
+ // It's a first candidate item located before/on the current item
+ if (!foundMenuBeforeCurrent) {
+ foundMenuBeforeCurrent = item;
+ }
+ } else {
+ if (!foundMenuAfterCurrent) {
+ foundMenuAfterCurrent = item;
+ }
+ }
+ }
+
+ aDoAction = !IsInMenuList() && (matchCount == 1 || accessKeyMatchCount == 1);
+
+ if (accessKeyMatchCount == 1) {
+ // We have one matched accesskey item
+ return foundAccessKeyMenu;
+ }
+ // If we have matched an item after the current, use it.
+ if (foundMenuAfterCurrent) {
+ return foundMenuAfterCurrent;
+ }
+ // If we haven't, use the item before the current, if any.
+ return foundMenuBeforeCurrent;
+}
+
+void XULMenuParentElement::HandleEnterKeyPress(WidgetEvent& aEvent) {
+ if (RefPtr child = GetActiveMenuChild()) {
+ child->HandleEnterKeyPress(aEvent);
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/xul/XULMenuParentElement.h b/dom/xul/XULMenuParentElement.h
new file mode 100644
index 0000000000..263ec058c1
--- /dev/null
+++ b/dom/xul/XULMenuParentElement.h
@@ -0,0 +1,80 @@
+/* -*- 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 XULMenuParentElement_h__
+#define XULMenuParentElement_h__
+
+#include "mozilla/Attributes.h"
+#include "nsISupports.h"
+#include "nsXULElement.h"
+
+namespace mozilla::dom {
+
+class KeyboardEvent;
+class XULButtonElement;
+
+nsXULElement* NS_NewXULMenuParentElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+class XULMenuParentElement : public nsXULElement {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULMenuParentElement, nsXULElement)
+
+ explicit XULMenuParentElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+ bool IsMenuBar() const { return NodeInfo()->Equals(nsGkAtoms::menubar); }
+ bool IsMenu() const { return !IsMenuBar(); }
+
+ bool IsLocked() const { return mLocked; }
+
+ void LockMenuUntilClosed(bool aLock);
+
+ bool IsInMenuList() const {
+ return GetParent() && GetParent()->IsXULElement(nsGkAtoms::menulist);
+ }
+
+ XULButtonElement* FindMenuWithShortcut(KeyboardEvent&) const;
+ XULButtonElement* FindMenuWithShortcut(const nsAString& aString,
+ bool& aDoAction) const;
+ MOZ_CAN_RUN_SCRIPT void HandleEnterKeyPress(WidgetEvent&);
+
+ NS_IMPL_FROMNODE_HELPER(XULMenuParentElement,
+ IsAnyOfXULElements(nsGkAtoms::menupopup,
+ nsGkAtoms::panel,
+ nsGkAtoms::tooltip,
+ nsGkAtoms::menubar));
+
+ XULButtonElement* GetActiveMenuChild() const { return mActiveItem.get(); }
+ enum class ByKey : bool { No, Yes };
+ MOZ_CAN_RUN_SCRIPT void SetActiveMenuChild(XULButtonElement*,
+ ByKey = ByKey::No);
+
+ XULButtonElement* GetFirstMenuItem() const;
+ XULButtonElement* GetLastMenuItem() const;
+
+ XULButtonElement* GetNextMenuItemFrom(const XULButtonElement&) const;
+ XULButtonElement* GetPrevMenuItemFrom(const XULButtonElement&) const;
+
+ enum class Wrap : bool { No, Yes };
+ XULButtonElement* GetNextMenuItem(Wrap = Wrap::Yes) const;
+ XULButtonElement* GetPrevMenuItem(Wrap = Wrap::Yes) const;
+
+ XULButtonElement* GetContainingMenu() const;
+
+ MOZ_CAN_RUN_SCRIPT void SelectFirstItem();
+
+ protected:
+ RefPtr<XULButtonElement> mActiveItem;
+ bool mLocked = false;
+
+ ~XULMenuParentElement() override;
+};
+
+} // namespace mozilla::dom
+
+#endif // XULMenuParentElement_h
diff --git a/dom/xul/XULPersist.cpp b/dom/xul/XULPersist.cpp
new file mode 100644
index 0000000000..5048bd3ddd
--- /dev/null
+++ b/dom/xul/XULPersist.cpp
@@ -0,0 +1,247 @@
+/* -*- 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"
+
+#include "nsIXULStore.h"
+#include "nsIStringEnumerator.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "nsContentUtils.h"
+#include "nsIAppWindow.h"
+
+namespace mozilla::dom {
+
+static bool IsRootElement(Element* aElement) {
+ return aElement->OwnerDoc()->GetRootElement() == aElement;
+}
+
+// FIXME: This is a hack to differentiate "attribute is missing" from "attribute
+// is present but empty". Use a newline to avoid virtually all collisions.
+// Ideally the XUL store would be able to store this more reasonably.
+constexpr auto kMissingAttributeToken = u"-moz-missing\n"_ns;
+
+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");
+
+ if (aNameSpaceID != kNameSpaceID_None) {
+ return;
+ }
+
+ // See if there is anything we need to persist in the localstore.
+ nsAutoString persist;
+ // Persistence of attributes of xul:window is handled in AppWindow.
+ if (aElement->GetAttr(nsGkAtoms::persist, persist) &&
+ ShouldPersistAttribute(aElement, aAttribute) &&
+ // 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*, nsAtom*>(
+ "dom::XULPersist::Persist", this, &XULPersist::Persist, aElement,
+ aAttribute));
+ }
+}
+
+void XULPersist::Persist(Element* aElement, nsAtom* aAttribute) {
+ if (!mDocument) {
+ return;
+ }
+ // For non-chrome documents, persistance is simply broken
+ if (!mDocument->NodePrincipal()->IsSystemPrincipal()) {
+ return;
+ }
+
+ if (!mLocalStore) {
+ mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
+ if (NS_WARN_IF(!mLocalStore)) {
+ return;
+ }
+ }
+
+ nsAutoString id;
+
+ aElement->GetAttr(nsGkAtoms::id, id);
+ nsAtomString attrstr(aAttribute);
+
+ nsAutoCString utf8uri;
+ nsresult rv = mDocument->GetDocumentURI()->GetSpec(utf8uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ // Persisting attributes to top level windows is handled by AppWindow.
+ if (IsRootElement(aElement)) {
+ if (nsCOMPtr<nsIAppWindow> win =
+ mDocument->GetAppWindowIfToplevelChrome()) {
+ return;
+ }
+ }
+
+ NS_ConvertUTF8toUTF16 uri(utf8uri);
+ nsAutoString valuestr;
+ if (!aElement->GetAttr(aAttribute, valuestr)) {
+ valuestr = kMissingAttributeToken;
+ }
+
+ mLocalStore->SetValue(uri, id, attrstr, valuestr);
+ 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.
+ if (!mLocalStore) {
+ mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
+ if (NS_WARN_IF(!mLocalStore)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ }
+
+ 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
+ nsCOMPtr<nsIStringEnumerator> ids;
+ rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool hasmore;
+ while (NS_SUCCEEDED(ids->HasMore(&hasmore)) && hasmore) {
+ nsAutoString id;
+ ids->GetNext(id);
+
+ // 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, uri, elements);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult XULPersist::ApplyPersistentAttributesToElements(
+ const nsAString& aID, const nsAString& aDocURI,
+ nsCOMArray<Element>& aElements) {
+ nsresult rv = NS_OK;
+ // Get a list of attributes for which persisted values are available
+ nsCOMPtr<nsIStringEnumerator> attrs;
+ rv = mLocalStore->GetAttributeEnumerator(aDocURI, aID, getter_AddRefs(attrs));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool hasmore;
+ while (NS_SUCCEEDED(attrs->HasMore(&hasmore)) && hasmore) {
+ nsAutoString attrstr;
+ attrs->GetNext(attrstr);
+
+ nsAutoString value;
+ rv = mLocalStore->GetValue(aDocURI, aID, attrstr, value);
+ 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;
+ }
+ }
+
+ if (value == kMissingAttributeToken) {
+ Unused << element->UnsetAttr(kNameSpaceID_None, attr, true);
+ } else {
+ Unused << element->SetAttr(kNameSpaceID_None, attr, value, true);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/xul/XULPersist.h b/dom/xul/XULPersist.h
new file mode 100644
index 0000000000..80cdac5dee
--- /dev/null
+++ b/dom/xul/XULPersist.h
@@ -0,0 +1,57 @@
+/* -*- 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
+# include "nsCOMPtr.h"
+class nsIXULStore;
+#endif
+
+template <typename T>
+class nsCOMArray;
+
+namespace mozilla::dom {
+
+/**
+ * This class synchronizes element attributes (such as window sizing) with the
+ * live elements in a Document and with the XULStore. The XULStore persists
+ * these attributes to the file system. This class is created and owned by the
+ * Document and must only be created in the parent process. It only presists
+ * chrome document element attributes.
+ */
+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, nsAtom* aAttribute);
+
+ private:
+ ~XULPersist();
+ nsresult ApplyPersistentAttributes();
+ nsresult ApplyPersistentAttributesToElements(const nsAString& aID,
+ const nsAString& aDocURI,
+ nsCOMArray<Element>& aElements);
+
+ nsCOMPtr<nsIXULStore> mLocalStore;
+
+ // A weak pointer to our document. Nulled out by DropDocumentReference.
+ Document* MOZ_NON_OWNING_REF mDocument;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_XULPersist_h
diff --git a/dom/xul/XULPopupElement.cpp b/dom/xul/XULPopupElement.cpp
new file mode 100644
index 0000000000..d60173900e
--- /dev/null
+++ b/dom/xul/XULPopupElement.cpp
@@ -0,0 +1,339 @@
+/* -*- 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 "XULMenuParentElement.h"
+#include "nsCOMPtr.h"
+#include "nsICSSDeclaration.h"
+#include "nsIContent.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsMenuPopupFrame.h"
+#include "nsStringFwd.h"
+#include "nsView.h"
+#include "mozilla/AppUnits.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/XULPopupElement.h"
+#include "mozilla/dom/XULButtonElement.h"
+#include "mozilla/dom/XULMenuElement.h"
+#include "mozilla/dom/XULPopupElementBinding.h"
+#ifdef MOZ_WAYLAND
+# include "mozilla/WidgetUtilsGtk.h"
+#endif
+
+namespace mozilla::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);
+}
+
+nsMenuPopupFrame* XULPopupElement::GetFrame(FlushType aFlushType) {
+ nsIFrame* f = GetPrimaryFrame(aFlushType);
+ MOZ_ASSERT(!f || f->IsMenuPopupFrame());
+ return static_cast<nsMenuPopupFrame*>(f);
+}
+
+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()) {
+ if (auto* menu = GetContainingMenu()) {
+ pm->ShowMenu(menu, 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) {
+ return;
+ }
+ HidePopupOptions options{HidePopupOption::DeselectMenu};
+ if (aCancel) {
+ options += HidePopupOption::IsRollup;
+ }
+ pm->HidePopup(this, options);
+}
+
+static Modifiers ConvertModifiers(const ActivateMenuItemOptions& aModifiers) {
+ Modifiers modifiers = 0;
+ if (aModifiers.mCtrlKey) {
+ modifiers |= MODIFIER_CONTROL;
+ }
+ if (aModifiers.mAltKey) {
+ modifiers |= MODIFIER_ALT;
+ }
+ if (aModifiers.mShiftKey) {
+ modifiers |= MODIFIER_SHIFT;
+ }
+ if (aModifiers.mMetaKey) {
+ modifiers |= MODIFIER_META;
+ }
+ return modifiers;
+}
+
+void XULPopupElement::PopupOpened(bool aSelectFirstItem) {
+ if (aSelectFirstItem) {
+ SelectFirstItem();
+ }
+ if (RefPtr button = GetContainingMenu()) {
+ if (RefPtr parent = button->GetMenuParent()) {
+ parent->SetActiveMenuChild(button);
+ }
+ }
+}
+
+void XULPopupElement::PopupClosed(bool aDeselectMenu) {
+ LockMenuUntilClosed(false);
+ SetActiveMenuChild(nullptr);
+ auto dispatcher = MakeRefPtr<AsyncEventDispatcher>(
+ this, u"DOMMenuInactive"_ns, CanBubble::eYes, ChromeOnlyDispatch::eNo);
+ dispatcher->PostDOMEvent();
+ if (RefPtr button = GetContainingMenu()) {
+ button->PopupClosed(aDeselectMenu);
+ }
+}
+
+void XULPopupElement::ActivateItem(Element& aItemElement,
+ const ActivateMenuItemOptions& aOptions,
+ ErrorResult& aRv) {
+ if (!Contains(&aItemElement)) {
+ return aRv.ThrowInvalidStateError("Menu item is not inside this menu.");
+ }
+
+ Modifiers modifiers = ConvertModifiers(aOptions);
+
+ // First, check if a native menu is open, and activate the item in it.
+ if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
+ if (pm->ActivateNativeMenuItem(&aItemElement, modifiers, aOptions.mButton,
+ aRv)) {
+ return;
+ }
+ }
+
+ auto* item = XULButtonElement::FromNode(aItemElement);
+ if (!item || !item->IsMenu()) {
+ return aRv.ThrowInvalidStateError("Not a menu item");
+ }
+
+ if (!item->GetPrimaryFrame(FlushType::Frames)) {
+ return aRv.ThrowInvalidStateError("Menu item is hidden");
+ }
+
+ auto* popup = item->GetContainingPopupElement();
+ if (!popup) {
+ return aRv.ThrowInvalidStateError("No popup");
+ }
+
+ nsMenuPopupFrame* frame = popup->GetFrame(FlushType::None);
+ if (!frame || !frame->IsOpen()) {
+ return aRv.ThrowInvalidStateError("Popup is not open");
+ }
+
+ // This is a chrome-only API, so we're trusted.
+ const bool trusted = true;
+ // KnownLive because item is aItemElement.
+ MOZ_KnownLive(item)->ExecuteMenu(modifiers, aOptions.mButton, trusted);
+}
+
+void XULPopupElement::MoveTo(int32_t aLeft, int32_t aTop) {
+ if (nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetPrimaryFrame())) {
+ menuPopupFrame->MoveTo(CSSIntPoint(aLeft, aTop), true);
+ }
+}
+
+void XULPopupElement::MoveToAnchor(Element* aAnchorElement,
+ const nsAString& aPosition, int32_t aXPos,
+ int32_t aYPos, bool aAttributesOverride) {
+ nsMenuPopupFrame* menuPopupFrame = GetFrame(FlushType::None);
+ if (menuPopupFrame && menuPopupFrame->IsVisibleOrShowing()) {
+ menuPopupFrame->MoveToAnchor(aAnchorElement, aPosition, aXPos, aYPos,
+ aAttributesOverride);
+ }
+}
+
+void XULPopupElement::SizeTo(int32_t aWidth, int32_t aHeight) {
+ nsAutoCString width;
+ nsAutoCString height;
+ width.AppendInt(aWidth);
+ width.AppendLiteral("px");
+ height.AppendInt(aHeight);
+ height.AppendLiteral("px");
+
+ nsCOMPtr<nsICSSDeclaration> style = Style();
+ style->SetProperty("width"_ns, width, ""_ns, IgnoreErrors());
+ style->SetProperty("height"_ns, height, ""_ns, IgnoreErrors());
+
+ // 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(false);
+ }
+}
+
+void XULPopupElement::GetState(nsString& aState) {
+ // set this here in case there's no frame for the popup
+ aState.AssignLiteral("closed");
+
+ if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
+ switch (pm->GetPopupState(this)) {
+ 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);
+}
+
+// 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(OwnerDoc()));
+
+ // Return an empty rectangle if the popup is not open.
+ nsMenuPopupFrame* menuPopupFrame =
+ do_QueryFrame(GetPrimaryFrame(FlushType::Frames));
+ if (!menuPopupFrame || !menuPopupFrame->IsOpen()) {
+ return rect.forget();
+ }
+
+ Maybe<CSSRect> screenRect;
+
+ if (menuPopupFrame->IsNativeMenu()) {
+ // For native menus we can't query the true size. Use the anchor rect
+ // instead, which at least has the position at which we were intending to
+ // open the menu.
+ screenRect = Some(CSSRect(menuPopupFrame->GetScreenAnchorRect()));
+ } else {
+ // For non-native menus, query the bounds from the widget.
+ if (nsView* view = menuPopupFrame->GetView()) {
+ if (nsIWidget* widget = view->GetWidget()) {
+ screenRect = Some(widget->GetScreenBounds() /
+ menuPopupFrame->PresContext()->CSSToDevPixelScale());
+ }
+ }
+ }
+
+ if (screenRect) {
+ rect->SetRect(screenRect->X(), screenRect->Y(), screenRect->Width(),
+ screenRect->Height());
+ }
+ return rect.forget();
+}
+
+void XULPopupElement::SetConstraintRect(dom::DOMRectReadOnly& aRect) {
+ nsMenuPopupFrame* menuPopupFrame =
+ do_QueryFrame(GetPrimaryFrame(FlushType::Frames));
+ if (menuPopupFrame) {
+ menuPopupFrame->SetOverrideConstraintRect(CSSIntRect::Truncate(
+ aRect.Left(), aRect.Top(), aRect.Width(), aRect.Height()));
+ }
+}
+
+bool XULPopupElement::IsWaylandDragSource() const {
+#ifdef MOZ_WAYLAND
+ nsMenuPopupFrame* f = do_QueryFrame(GetPrimaryFrame());
+ return f && f->IsDragSource();
+#else
+ return false;
+#endif
+}
+
+bool XULPopupElement::IsWaylandPopup() const {
+#ifdef MOZ_WAYLAND
+ return widget::GdkIsWaylandDisplay() || widget::IsXWaylandProtocol();
+#else
+ return false;
+#endif
+}
+
+} // namespace mozilla::dom
diff --git a/dom/xul/XULPopupElement.h b/dom/xul/XULPopupElement.h
new file mode 100644
index 0000000000..93095a7d51
--- /dev/null
+++ b/dom/xul/XULPopupElement.h
@@ -0,0 +1,116 @@
+/* -*- 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 "XULMenuParentElement.h"
+#include "mozilla/Attributes.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsINode.h"
+#include "nsWrapperCache.h"
+#include "nsString.h"
+#include "nsXULElement.h"
+
+class nsMenuPopupFrame;
+struct JSContext;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class DOMRect;
+class Element;
+class Event;
+class StringOrOpenPopupOptions;
+struct ActivateMenuItemOptions;
+
+nsXULElement* NS_NewXULPopupElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+class XULPopupElement : public XULMenuParentElement {
+ private:
+ nsMenuPopupFrame* GetFrame(FlushType);
+
+ public:
+ explicit XULPopupElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : XULMenuParentElement(std::move(aNodeInfo)) {}
+
+ void GetLabel(DOMString& aValue) const {
+ GetXULAttr(nsGkAtoms::label, aValue);
+ }
+ void SetLabel(const nsAString& aValue, ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::label, aValue, rv);
+ }
+
+ bool IsMenu() const { return IsXULElement(nsGkAtoms::menupopup); }
+
+ void GetPosition(DOMString& aValue) const {
+ GetXULAttr(nsGkAtoms::position, aValue);
+ }
+ void SetPosition(const nsAString& aValue, ErrorResult& rv) {
+ SetXULAttr(nsGkAtoms::position, aValue, rv);
+ }
+
+ 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);
+
+ MOZ_CAN_RUN_SCRIPT void ActivateItem(Element& aItemElement,
+ const ActivateMenuItemOptions& aOptions,
+ ErrorResult& aRv);
+
+ void GetState(nsString& aState);
+
+ MOZ_CAN_RUN_SCRIPT void PopupOpened(bool aSelectFirstItem);
+ MOZ_CAN_RUN_SCRIPT void PopupClosed(bool aDeselectMenu);
+
+ nsINode* GetTriggerNode() 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);
+
+ bool IsWaylandDragSource() const;
+ bool IsWaylandPopup() const;
+
+ NS_IMPL_FROMNODE_HELPER(XULPopupElement,
+ IsAnyOfXULElements(nsGkAtoms::menupopup,
+ nsGkAtoms::panel,
+ nsGkAtoms::tooltip));
+
+ 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/XULResizerElement.cpp b/dom/xul/XULResizerElement.cpp
new file mode 100644
index 0000000000..a0dbb485d5
--- /dev/null
+++ b/dom/xul/XULResizerElement.cpp
@@ -0,0 +1,354 @@
+/* -*- 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/XULResizerElement.h"
+#include "mozilla/dom/XULResizerElementBinding.h"
+
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/MouseEvents.h"
+#include "nsContentUtils.h"
+#include "nsICSSDeclaration.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsStyledElement.h"
+
+namespace mozilla::dom {
+
+nsXULElement* NS_NewXULResizerElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) {
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo);
+ auto* nim = nodeInfo->NodeInfoManager();
+ return new (nim) XULResizerElement(nodeInfo.forget());
+}
+
+static bool GetEventPoint(const WidgetGUIEvent* aEvent,
+ LayoutDeviceIntPoint& aPoint) {
+ NS_ENSURE_TRUE(aEvent, false);
+
+ const WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+ if (touchEvent) {
+ // return false if there is more than one touch on the page, or if
+ // we can't find a touch point
+ if (touchEvent->mTouches.Length() != 1) {
+ return false;
+ }
+
+ const dom::Touch* touch = touchEvent->mTouches.SafeElementAt(0);
+ if (!touch) {
+ return false;
+ }
+ aPoint = touch->mRefPoint;
+ } else {
+ aPoint = aEvent->mRefPoint;
+ }
+ return true;
+}
+
+JSObject* XULResizerElement::WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return XULResizerElement_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+XULResizerElement::Direction XULResizerElement::GetDirection() {
+ static const mozilla::dom::Element::AttrValuesArray strings[] = {
+ // clang-format off
+ nsGkAtoms::topleft, nsGkAtoms::top, nsGkAtoms::topright,
+ nsGkAtoms::left, nsGkAtoms::right,
+ nsGkAtoms::bottomleft, nsGkAtoms::bottom, nsGkAtoms::bottomright,
+ nsGkAtoms::bottomstart, nsGkAtoms::bottomend,
+ nullptr
+ // clang-format on
+ };
+
+ static const Direction directions[] = {
+ // clang-format off
+ {-1, -1}, {0, -1}, {1, -1},
+ {-1, 0}, {1, 0},
+ {-1, 1}, {0, 1}, {1, 1},
+ {-1, 1}, {1, 1}
+ // clang-format on
+ };
+
+ const auto* frame = GetPrimaryFrame();
+ if (!frame) {
+ return directions[0]; // default: topleft
+ }
+
+ int32_t index =
+ FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::dir, strings, eCaseMatters);
+ if (index < 0) {
+ return directions[0]; // default: topleft
+ }
+
+ if (index >= 8) {
+ // Directions 8 and higher are RTL-aware directions and should reverse the
+ // horizontal component if RTL.
+ auto wm = frame->GetWritingMode();
+ if (wm.IsPhysicalRTL()) {
+ Direction direction = directions[index];
+ direction.mHorizontal *= -1;
+ return direction;
+ }
+ }
+
+ return directions[index];
+}
+
+nsresult XULResizerElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
+ if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
+ PostHandleEventInternal(aVisitor);
+ }
+ return nsXULElement::PostHandleEvent(aVisitor);
+}
+
+Maybe<nsSize> XULResizerElement::GetCurrentSize() const {
+ nsIContent* contentToResize = GetContentToResize();
+ if (!contentToResize) {
+ return Nothing();
+ }
+ nsIFrame* frame = contentToResize->GetPrimaryFrame();
+ if (!frame) {
+ return Nothing();
+ }
+ return Some(frame->StylePosition()->mBoxSizing == StyleBoxSizing::Content
+ ? frame->GetContentRect().Size()
+ : frame->GetRect().Size());
+}
+
+void XULResizerElement::PostHandleEventInternal(
+ EventChainPostVisitor& aVisitor) {
+ bool doDefault = true;
+ const WidgetEvent& event = *aVisitor.mEvent;
+ switch (event.mMessage) {
+ case eTouchStart:
+ case eMouseDown: {
+ if (event.mClass == eTouchEventClass ||
+ (event.mClass == eMouseEventClass &&
+ event.AsMouseEvent()->mButton == MouseButton::ePrimary)) {
+ auto size = GetCurrentSize();
+ if (!size) {
+ break; // don't do anything if there's nothing to resize
+ }
+ // cache the content rectangle for the frame to resize
+ mMouseDownSize = *size;
+
+ // remember current mouse coordinates
+ auto* guiEvent = event.AsGUIEvent();
+ if (!GetEventPoint(guiEvent, mMouseDownPoint)) {
+ break;
+ }
+ mTrackingMouseMove = true;
+ PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState);
+ doDefault = false;
+ }
+ } break;
+
+ case eTouchMove:
+ case eMouseMove: {
+ if (mTrackingMouseMove) {
+ nsCOMPtr<nsIContent> contentToResize = GetContentToResize();
+ if (!contentToResize) {
+ break; // don't do anything if there's nothing to resize
+ }
+ nsIFrame* frame = contentToResize->GetPrimaryFrame();
+ if (!frame) {
+ break;
+ }
+
+ // both MouseMove and direction are negative when pointing to the
+ // top and left, and positive when pointing to the bottom and right
+
+ // retrieve the offset of the mousemove event relative to the mousedown.
+ // The difference is how much the resize needs to be
+ LayoutDeviceIntPoint refPoint;
+ auto* guiEvent = event.AsGUIEvent();
+ if (!GetEventPoint(guiEvent, refPoint)) {
+ break;
+ }
+
+ const nsPoint oldPos = nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ guiEvent->mWidget, mMouseDownPoint, RelativeTo{frame});
+ const nsPoint newPos = nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ guiEvent->mWidget, refPoint, RelativeTo{frame});
+
+ nsPoint mouseMove(newPos - oldPos);
+
+ // Determine which direction to resize by checking the dir attribute.
+ // For windows and menus, ensure that it can be resized in that
+ // direction.
+ Direction direction = GetDirection();
+
+ const CSSIntSize newSize = [&] {
+ nsSize newAuSize = mMouseDownSize;
+ // Check if there are any size constraints on this window.
+ newAuSize.width += direction.mHorizontal * mouseMove.x;
+ newAuSize.height += direction.mVertical * mouseMove.y;
+ if (newAuSize.width < AppUnitsPerCSSPixel() && mouseMove.x != 0) {
+ newAuSize.width = AppUnitsPerCSSPixel();
+ }
+ if (newAuSize.height < AppUnitsPerCSSPixel() && mouseMove.y != 0) {
+ newAuSize.height = AppUnitsPerCSSPixel();
+ }
+
+ // When changing the size in a direction, don't allow the new size to
+ // be less that the resizer's size. This ensures that content isn't
+ // resized too small as to make the resizer invisible.
+ if (auto* resizerFrame = GetPrimaryFrame()) {
+ nsRect resizerRect = resizerFrame->GetRect();
+ if (newAuSize.width < resizerRect.width && mouseMove.x != 0) {
+ newAuSize.width = resizerRect.width;
+ }
+ if (newAuSize.height < resizerRect.height && mouseMove.y != 0) {
+ newAuSize.height = resizerRect.height;
+ }
+ }
+
+ // Convert the rectangle into css pixels.
+ return CSSIntSize::FromAppUnitsRounded(newAuSize);
+ }();
+
+ // Only resize in a given direction if the new size doesn't match the
+ // current size.
+ if (auto currentSize = GetCurrentSize()) {
+ auto newAuSize = CSSIntSize::ToAppUnits(newSize);
+ if (newAuSize.width == currentSize->width) {
+ direction.mHorizontal = 0;
+ }
+ if (newAuSize.height == currentSize->height) {
+ direction.mVertical = 0;
+ }
+ }
+
+ SizeInfo sizeInfo, originalSizeInfo;
+ sizeInfo.width.AppendInt(newSize.width);
+ sizeInfo.height.AppendInt(newSize.height);
+ ResizeContent(contentToResize, direction, sizeInfo, &originalSizeInfo);
+ MaybePersistOriginalSize(contentToResize, originalSizeInfo);
+
+ doDefault = false;
+ }
+ } break;
+
+ case eMouseClick: {
+ auto* mouseEvent = event.AsMouseEvent();
+ if (mouseEvent->IsLeftClickEvent()) {
+ // Execute the oncommand event handler.
+ nsContentUtils::DispatchXULCommand(
+ this, false, nullptr, nullptr, mouseEvent->IsControl(),
+ mouseEvent->IsAlt(), mouseEvent->IsShift(), mouseEvent->IsMeta(),
+ mouseEvent->mInputSource, mouseEvent->mButton);
+ }
+ } break;
+
+ case eTouchEnd:
+ case eMouseUp: {
+ if (event.mClass == eTouchEventClass ||
+ (event.mClass == eMouseEventClass &&
+ event.AsMouseEvent()->mButton == MouseButton::ePrimary)) {
+ mTrackingMouseMove = false;
+ PresShell::ReleaseCapturingContent();
+ doDefault = false;
+ }
+ } break;
+
+ case eMouseDoubleClick: {
+ if (event.AsMouseEvent()->mButton == MouseButton::ePrimary) {
+ if (nsIContent* contentToResize = GetContentToResize()) {
+ RestoreOriginalSize(contentToResize);
+ }
+ }
+ } break;
+
+ default:
+ break;
+ }
+
+ if (!doDefault) {
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+}
+
+nsIContent* XULResizerElement::GetContentToResize() const {
+ if (!IsInComposedDoc()) {
+ return nullptr;
+ }
+ // Return the parent, but skip over native anonymous content
+ nsIContent* parent = GetParent();
+ return parent ? parent->FindFirstNonChromeOnlyAccessContent() : nullptr;
+}
+
+/* static */
+void XULResizerElement::ResizeContent(nsIContent* aContent,
+ const Direction& aDirection,
+ const SizeInfo& aSizeInfo,
+ SizeInfo* aOriginalSizeInfo) {
+ RefPtr inlineStyleContent = nsStyledElement::FromNode(aContent);
+ if (!inlineStyleContent) {
+ return;
+ }
+ nsCOMPtr<nsICSSDeclaration> decl = inlineStyleContent->Style();
+ if (aOriginalSizeInfo) {
+ decl->GetPropertyValue("width"_ns, aOriginalSizeInfo->width);
+ decl->GetPropertyValue("height"_ns, aOriginalSizeInfo->height);
+ }
+
+ // only set the property if the element could have changed in that
+ // direction
+ if (aDirection.mHorizontal) {
+ nsAutoCString widthstr(aSizeInfo.width);
+ if (!widthstr.IsEmpty() && !StringEndsWith(widthstr, "px"_ns)) {
+ widthstr.AppendLiteral("px");
+ }
+ decl->SetProperty("width"_ns, widthstr, ""_ns, IgnoreErrors());
+ }
+
+ if (aDirection.mVertical) {
+ nsAutoCString heightstr(aSizeInfo.height);
+ if (!heightstr.IsEmpty() && !StringEndsWith(heightstr, "px"_ns)) {
+ heightstr.AppendLiteral("px");
+ }
+ decl->SetProperty("height"_ns, heightstr, ""_ns, IgnoreErrors());
+ }
+}
+
+/* static */
+void XULResizerElement::MaybePersistOriginalSize(nsIContent* aContent,
+ const SizeInfo& aSizeInfo) {
+ nsresult rv;
+ aContent->GetProperty(nsGkAtoms::_moz_original_size, &rv);
+ if (rv != NS_PROPTABLE_PROP_NOT_THERE) {
+ return;
+ }
+
+ UniquePtr<SizeInfo> sizeInfo(new SizeInfo(aSizeInfo));
+ rv = aContent->SetProperty(
+ nsGkAtoms::_moz_original_size, sizeInfo.get(),
+ nsINode::DeleteProperty<XULResizerElement::SizeInfo>);
+ if (NS_SUCCEEDED(rv)) {
+ Unused << sizeInfo.release();
+ }
+}
+
+/* static */
+void XULResizerElement::RestoreOriginalSize(nsIContent* aContent) {
+ nsresult rv;
+ SizeInfo* sizeInfo = static_cast<SizeInfo*>(
+ aContent->GetProperty(nsGkAtoms::_moz_original_size, &rv));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ NS_ASSERTION(sizeInfo, "We set a null sizeInfo!?");
+ Direction direction = {1, 1};
+ ResizeContent(aContent, direction, *sizeInfo, nullptr);
+ aContent->RemoveProperty(nsGkAtoms::_moz_original_size);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/xul/XULResizerElement.h b/dom/xul/XULResizerElement.h
new file mode 100644
index 0000000000..f7c9e4d75d
--- /dev/null
+++ b/dom/xul/XULResizerElement.h
@@ -0,0 +1,62 @@
+/* -*- 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_XULResizerElement_h
+#define mozilla_dom_XULResizerElement_h
+
+#include "nsXULElement.h"
+#include "Units.h"
+
+namespace mozilla::dom {
+
+nsXULElement* NS_NewXULResizerElement(
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+class XULResizerElement final : public nsXULElement {
+ public:
+ explicit XULResizerElement(already_AddRefed<dom::NodeInfo>&& aNodeInfo)
+ : nsXULElement(std::move(aNodeInfo)) {}
+
+ MOZ_CAN_RUN_SCRIPT
+ nsresult PostHandleEvent(EventChainPostVisitor&) override;
+
+ private:
+ virtual ~XULResizerElement() = default;
+ JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+ MOZ_CAN_RUN_SCRIPT
+ void PostHandleEventInternal(EventChainPostVisitor&);
+
+ struct Direction {
+ int8_t mHorizontal;
+ int8_t mVertical;
+ };
+ Direction GetDirection();
+
+ nsIContent* GetContentToResize() const;
+ // The current size of the content to resize (if available).
+ Maybe<nsSize> GetCurrentSize() const;
+
+ struct SizeInfo {
+ nsCString width, height;
+ };
+ static void SizeInfoDtorFunc(void* aObject, nsAtom* aPropertyName,
+ void* aPropertyValue, void* aData);
+ static void ResizeContent(nsIContent* aContent, const Direction& aDirection,
+ const SizeInfo& aSizeInfo,
+ SizeInfo* aOriginalSizeInfo);
+ static void MaybePersistOriginalSize(nsIContent* aContent,
+ const SizeInfo& aSizeInfo);
+ static void RestoreOriginalSize(nsIContent* aContent);
+
+ nsSize mMouseDownSize;
+ LayoutDeviceIntPoint mMouseDownPoint;
+ bool mTrackingMouseMove = false;
+};
+
+} // namespace mozilla::dom
+
+#endif // XULResizerElement_h
diff --git a/dom/xul/XULTextElement.cpp b/dom/xul/XULTextElement.cpp
new file mode 100644
index 0000000000..04edfabc09
--- /dev/null
+++ b/dom/xul/XULTextElement.cpp
@@ -0,0 +1,49 @@
+/* -*- 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 "nsChangeHint.h"
+#include "nsIContent.h"
+#include "nsPresContext.h"
+#include "nsIScrollableFrame.h"
+#include "mozilla/dom/MutationEventBinding.h"
+#include "mozilla/dom/XULTextElement.h"
+#include "mozilla/dom/XULTextElementBinding.h"
+
+namespace mozilla::dom {
+
+nsChangeHint XULTextElement::GetAttributeChangeHint(const nsAtom* aAttribute,
+ int32_t aModType) const {
+ const bool reframe = [&] {
+ if (aAttribute == nsGkAtoms::value) {
+ // If we have an accesskey we need to recompute our -moz-label-content.
+ // Otherwise this is handled by either the attribute text node, or
+ // nsTextBoxFrame for crop="center".
+ return aModType == MutationEvent_Binding::ADDITION ||
+ aModType == MutationEvent_Binding::REMOVAL ||
+ HasAttr(nsGkAtoms::accesskey);
+ }
+ if (aAttribute == nsGkAtoms::crop || aAttribute == nsGkAtoms::accesskey) {
+ // value attr + crop="center" still uses nsTextBoxFrame. accesskey
+ // requires reframing as per the above comment.
+ return HasAttr(nsGkAtoms::value);
+ }
+ return false;
+ }();
+ if (reframe) {
+ return nsChangeHint_ReconstructFrame;
+ }
+ return nsXULElement::GetAttributeChangeHint(aAttribute, aModType);
+}
+
+JSObject* XULTextElement::WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return XULTextElement_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/xul/XULTextElement.h b/dom/xul/XULTextElement.h
new file mode 100644
index 0000000000..0c6f086b5e
--- /dev/null
+++ b/dom/xul/XULTextElement.h
@@ -0,0 +1,50 @@
+/* -*- 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::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, mozilla::IgnoreErrors());
+ }
+ 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);
+ }
+
+ nsChangeHint GetAttributeChangeHint(const nsAtom* aAttribute,
+ int32_t aModType) const override;
+
+ NS_IMPL_FROMNODE_HELPER(XULTextElement,
+ IsAnyOfXULElements(nsGkAtoms::label,
+ nsGkAtoms::description));
+
+ protected:
+ virtual ~XULTextElement() = default;
+ JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+};
+
+} // namespace mozilla::dom
+
+#endif // XULTextElement_h
diff --git a/dom/xul/XULTooltipElement.cpp b/dom/xul/XULTooltipElement.cpp
new file mode 100644
index 0000000000..cf9e46c1ca
--- /dev/null
+++ b/dom/xul/XULTooltipElement.cpp
@@ -0,0 +1,104 @@
+/* -*- 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::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);
+ ErrorResult error;
+ AppendChild(*description, error);
+
+ return error.StealNSResult();
+}
+
+void 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) &&
+ !AttrValueIs(kNameSpaceID_None, nsGkAtoms::titletip, 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 mozilla::dom
diff --git a/dom/xul/XULTooltipElement.h b/dom/xul/XULTooltipElement.h
new file mode 100644
index 0000000000..1f39ad7f67
--- /dev/null
+++ b/dom/xul/XULTooltipElement.h
@@ -0,0 +1,35 @@
+/* -*- 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::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();
+
+ void AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
+ const nsAttrValue* aValue, const nsAttrValue* aOldValue,
+ nsIPrincipal* aSubjectPrincipal, bool aNotify) override;
+ nsresult PostHandleEvent(EventChainPostVisitor& aVisitor) override;
+
+ protected:
+ virtual ~XULTooltipElement() = default;
+};
+
+} // namespace mozilla::dom
+
+#endif // XULPopupElement_h
diff --git a/dom/xul/XULTreeElement.cpp b/dom/xul/XULTreeElement.cpp
new file mode 100644
index 0000000000..e190b22556
--- /dev/null
+++ b/dom/xul/XULTreeElement.cpp
@@ -0,0 +1,411 @@
+/* -*- 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::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(ToSupports(OwnerDoc()), 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 mozilla::dom
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/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..970d57b457
--- /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
+skip-if(Android) 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..cbcf964a2b
--- /dev/null
+++ b/dom/xul/moz.build
@@ -0,0 +1,94 @@
+# -*- 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.toml"]
+MOCHITEST_MANIFESTS += ["test/mochitest.toml"]
+
+EXPORTS += [
+ "nsXULCommandDispatcher.h",
+ "nsXULElement.h",
+ "nsXULPrototypeDocument.h",
+ "nsXULSortService.h",
+]
+
+EXPORTS.mozilla.dom += [
+ "XULBroadcastManager.h",
+ "XULButtonElement.h",
+ "XULFrameElement.h",
+ "XULMenuBarElement.h",
+ "XULMenuElement.h",
+ "XULMenuParentElement.h",
+ "XULPersist.h",
+ "XULPopupElement.h",
+ "XULResizerElement.h",
+ "XULTextElement.h",
+ "XULTooltipElement.h",
+ "XULTreeElement.h",
+]
+
+UNIFIED_SOURCES += [
+ "MenuBarListener.cpp",
+ "nsXULCommandDispatcher.cpp",
+ "nsXULContentSink.cpp",
+ "nsXULContentUtils.cpp",
+ "nsXULElement.cpp",
+ "nsXULPopupListener.cpp",
+ "nsXULPrototypeCache.cpp",
+ "nsXULPrototypeDocument.cpp",
+ "nsXULSortService.cpp",
+ "XULBroadcastManager.cpp",
+ "XULButtonElement.cpp",
+ "XULFrameElement.cpp",
+ "XULMenuBarElement.cpp",
+ "XULMenuElement.cpp",
+ "XULMenuParentElement.cpp",
+ "XULPersist.cpp",
+ "XULPopupElement.cpp",
+ "XULResizerElement.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"
diff --git a/dom/xul/nsIBrowserController.idl b/dom/xul/nsIBrowserController.idl
new file mode 100644
index 0000000000..5a7b77529b
--- /dev/null
+++ b/dom/xul/nsIBrowserController.idl
@@ -0,0 +1,20 @@
+/* -*- 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..2f272faec1
--- /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..0b07086914
--- /dev/null
+++ b/dom/xul/nsXULCommandDispatcher.cpp
@@ -0,0 +1,435 @@
+/* -*- 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"
+#include "mozilla/dom/ElementBinding.h"
+
+using namespace mozilla;
+using mozilla::dom::Document;
+using mozilla::dom::Element;
+
+#ifdef DEBUG
+static LazyLogModule gCommandLog("nsXULCommandDispatcher");
+#endif
+
+////////////////////////////////////////////////////////////////////////
+
+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::AdvanceFocusIntoSubtree(Element* aElt) {
+ return MoveFocusIntoSubtree(aElt, /* aForward = */ true);
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::RewindFocus() {
+ return MoveFocusIntoSubtree(nullptr, /* aForward = */ false);
+}
+
+nsresult nsXULCommandDispatcher::MoveFocusIntoSubtree(Element* aElt,
+ bool aForward) {
+ nsCOMPtr<nsPIDOMWindowOuter> win;
+ GetRootFocusedContentAndWindow(getter_AddRefs(win));
+
+ RefPtr<Element> result;
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return NS_OK;
+ }
+ auto flags = nsFocusManager::ProgrammaticFocusFlags(dom::FocusOptions()) |
+ nsIFocusManager::FLAG_BYMOVEFOCUS;
+ auto type = aForward ? nsIFocusManager::MOVEFOCUS_FORWARD
+ : nsIFocusManager::MOVEFOCUS_BACKWARD;
+ return fm->MoveFocus(win, aElt, type, flags, getter_AddRefs(result));
+}
+
+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 (nsIContent* content : updaters) {
+#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(MOZ_KnownLive(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;
+}
+
+// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+MOZ_CAN_RUN_SCRIPT_BOUNDARY 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..1f988c752e
--- /dev/null
+++ b/dom/xul/nsXULCommandDispatcher.h
@@ -0,0 +1,82 @@
+/* -*- 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 "nsTArray.h"
+#include "mozilla/RefPtr.h"
+
+class nsPIDOMWindowOuter;
+class nsPIWindowRoot;
+
+namespace mozilla::dom {
+class Document;
+class Element;
+} // namespace mozilla::dom
+
+class nsXULCommandDispatcher : public nsIDOMXULCommandDispatcher,
+ public nsSupportsWeakReference {
+ using Document = mozilla::dom::Document;
+ using Element = mozilla::dom::Element;
+
+ public:
+ explicit nsXULCommandDispatcher(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();
+
+ Element* GetRootFocusedContentAndWindow(nsPIDOMWindowOuter** aWindow);
+ nsresult MoveFocusIntoSubtree(Element*, bool aForward);
+
+ RefPtr<Document> mDocument;
+
+ class Updater {
+ public:
+ Updater(Element* aElement, const nsAString& aEvents,
+ const nsAString& aTargets)
+ : mElement(aElement),
+ mEvents(aEvents),
+ mTargets(aTargets),
+ mNext(nullptr) {}
+
+ RefPtr<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..197c5c2b3b
--- /dev/null
+++ b/dom/xul/nsXULContentSink.cpp
@@ -0,0 +1,861 @@
+/* -*- 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 "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::DidBuildModel(bool aTerminated) {
+ nsCOMPtr<Document> doc(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;
+}
+
+void XULContentSinkImpl::WillResume() {
+ // XXX Notify the docshell, if necessary
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::SetParser(nsParserBase* aParser) {
+ mParser = aParser;
+ return NS_OK;
+}
+
+void XULContentSinkImpl::SetDocumentCharset(
+ NotNull<const Encoding*> aEncoding) {
+ nsCOMPtr<Document> doc(mDocument);
+ if (doc) {
+ doc->SetDocumentCharacterSet(aEncoding);
+ }
+}
+
+nsISupports* XULContentSinkImpl::GetTarget() { return ToSupports(mDocument); }
+
+//----------------------------------------------------------------------
+
+nsresult XULContentSinkImpl::Init(Document* aDocument,
+ nsXULPrototypeDocument* aPrototype) {
+ MOZ_ASSERT(aDocument != nullptr, "null ptr");
+ if (!aDocument) return NS_ERROR_NULL_POINTER;
+
+ mDocument = 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->HasStencil()) {
+ nsCOMPtr<Document> doc(mDocument);
+
+ script->mOutOfLine = false;
+ if (doc) {
+ script->Compile(mText, mTextLength, 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(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(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) && doc) {
+ 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..63fa144156
--- /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 "mozilla/WeakPtr.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 DidBuildModel(bool aTerminated) override;
+ NS_IMETHOD WillInterrupt(void) override;
+ void WillResume() 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;
+
+ mozilla::WeakPtr<mozilla::dom::Document> mDocument;
+ 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..109aa1975a
--- /dev/null
+++ b/dom/xul/nsXULContentUtils.cpp
@@ -0,0 +1,89 @@
+/* -*- 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 "mozilla/intl/LocaleService.h"
+#include "mozilla/intl/Collator.h"
+
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIContent.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;
+
+//------------------------------------------------------------------------
+
+const mozilla::intl::Collator* nsXULContentUtils::gCollator;
+
+//------------------------------------------------------------------------
+// Constructors n' stuff
+//
+
+nsresult nsXULContentUtils::Finish() {
+ if (gCollator) {
+ delete gCollator;
+ gCollator = nullptr;
+ }
+
+ return NS_OK;
+}
+
+const mozilla::intl::Collator* nsXULContentUtils::GetCollator() {
+ if (!gCollator) {
+ // Lazily initialize the Collator.
+ auto result = mozilla::intl::LocaleService::TryCreateComponent<
+ mozilla::intl::Collator>();
+ if (result.isErr()) {
+ NS_ERROR("couldn't create a mozilla::intl::Collator");
+ return nullptr;
+ }
+
+ auto collator = result.unwrap();
+
+ // Sort in a case-insensitive way, where "base" letters are considered
+ // equal, e.g: a = á, a = A, a ≠ b.
+ mozilla::intl::Collator::Options options{};
+ options.sensitivity = mozilla::intl::Collator::Sensitivity::Base;
+ auto optResult = collator->SetOptions(options);
+ if (optResult.isErr()) {
+ NS_ERROR("couldn't set options for mozilla::intl::Collator");
+ return nullptr;
+ }
+ gCollator = collator.release();
+ }
+
+ return gCollator;
+}
+
+//------------------------------------------------------------------------
+//
+
+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..c996bcec2b
--- /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 nsIContent;
+
+namespace mozilla::dom {
+class Element;
+}
+namespace mozilla::intl {
+class Collator;
+}
+
+class nsXULContentUtils {
+ protected:
+ const static mozilla::intl::Collator* gCollator;
+
+ 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 const mozilla::intl::Collator* GetCollator();
+};
+
+#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..78c29fb315
--- /dev/null
+++ b/dom/xul/nsXULElement.cpp
@@ -0,0 +1,2121 @@
+/* -*- 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 "XULButtonElement.h"
+#include "XULFrameElement.h"
+#include "XULMenuElement.h"
+#include "XULMenuBarElement.h"
+#include "XULPopupElement.h"
+#include "XULResizerElement.h"
+#include "XULTextElement.h"
+#include "XULTooltipElement.h"
+#include "XULTreeElement.h"
+#include "js/CompilationAndEvaluation.h"
+#include "js/CompileOptions.h" // JS::CompileOptions, JS::OwningCompileOptions, , JS::ReadOnlyCompileOptions, JS::ReadOnlyDecodeOptions, JS::DecodeOptions
+#include "js/experimental/CompileScript.h" // JS::NewFrontendContext, JS::DestroyFrontendContext, JS::SetNativeStackQuota, JS::ThreadStackQuotaForSize, JS::CompileGlobalScriptToStencil, JS::CompilationStorage
+#include "js/experimental/JSStencil.h" // JS::Stencil, JS::FrontendContext
+#include "js/SourceText.h"
+#include "js/Transcoding.h"
+#include "js/Utility.h"
+#include "jsapi.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ArrayIterator.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventQueue.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/RefPtr.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/ShutdownPhase.h"
+#include "mozilla/StaticAnalysisFunctions.h"
+#include "mozilla/StaticPrefs_javascript.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TaskController.h"
+#include "mozilla/UniquePtr.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 "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 "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;
+
+/* 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) {
+ // NOTE: If you add elements here, you probably also want to change
+ // mozilla::dom::binding_detail::HTMLConstructor in BindingUtils.cpp to take
+ // them into account, otherwise you'll start getting "Illegal constructor"
+ // exceptions in chrome code.
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo;
+ if (nodeInfo->Equals(nsGkAtoms::resizer)) {
+ return NS_NewXULResizerElement(nodeInfo.forget());
+ }
+
+ 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::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::menubar)) {
+ auto* nim = nodeInfo->NodeInfoManager();
+ return new (nim) XULMenuBarElement(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());
+ }
+
+ if (nodeInfo->Equals(nsGkAtoms::checkbox) ||
+ nodeInfo->Equals(nsGkAtoms::radio) ||
+ nodeInfo->Equals(nsGkAtoms::thumb) ||
+ nodeInfo->Equals(nsGkAtoms::button) ||
+ nodeInfo->Equals(nsGkAtoms::menuitem) ||
+ nodeInfo->Equals(nsGkAtoms::toolbarbutton) ||
+ nodeInfo->Equals(nsGkAtoms::toolbarpaletteitem) ||
+ nodeInfo->Equals(nsGkAtoms::scrollbarbutton)) {
+ auto* nim = nodeInfo->NodeInfoManager();
+ return new (nim) XULButtonElement(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);
+ }
+ }
+
+ 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);
+}
+
+nsXULElement::XULFocusability nsXULElement::GetXULFocusability(
+ bool aWithMouse) {
+#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 XULFocusability::NeverFocusable();
+ }
+#endif
+
+ XULFocusability result;
+ 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) {
+ return XULFocusability::NeverFocusable();
+ }
+ result.mDefaultFocusable = true;
+ }
+ if (Maybe<int32_t> attrVal = GetTabIndexAttrValue()) {
+ // The tabindex attribute was specified, so the element becomes
+ // focusable.
+ result.mDefaultFocusable = true;
+ result.mForcedFocusable.emplace(true);
+ result.mForcedTabIndexIfFocusable.emplace(attrVal.value());
+ }
+ if (xulControl && sTabFocusModelAppliesToXUL &&
+ !(sTabFocusModel & eTabFocus_formElementsMask) && IsNonList(mNodeInfo)) {
+ // 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.
+ result.mForcedTabIndexIfFocusable = Some(-1);
+ }
+ return result;
+}
+
+// XUL elements are not focusable unless explicitly opted-into it with
+// -moz-user-focus: normal, or the tabindex attribute.
+Focusable nsXULElement::IsFocusableWithoutStyle(bool aWithMouse) {
+ const auto focusability = GetXULFocusability(aWithMouse);
+ const bool focusable = focusability.mDefaultFocusable;
+ return {focusable,
+ focusable ? focusability.mForcedTabIndexIfFocusable.valueOr(-1) : -1};
+}
+
+bool nsXULElement::HasMenu() {
+ if (auto* button = XULButtonElement::FromNode(this)) {
+ return button->IsMenu();
+ }
+ return false;
+}
+
+void nsXULElement::OpenMenu(bool aOpenFlag) {
+ // Flush frames first. It's not clear why this is needed, see bug 1704670.
+ if (Document* doc = GetComposedDoc()) {
+ doc->FlushPendingNotifications(FlushType::Frames);
+ }
+
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!pm) {
+ return;
+ }
+
+ if (aOpenFlag) {
+ // Nothing will happen if this element isn't a menu.
+ pm->ShowMenu(this, false);
+ } else {
+ // Nothing will happen if this element isn't a menu.
+ pm->HideMenu(this);
+ }
+}
+
+Result<bool, nsresult> nsXULElement::PerformAccesskey(bool aKeyCausesActivation,
+ bool aIsTrustedEvent) {
+ if (IsXULElement(nsGkAtoms::label)) {
+ nsAutoString control;
+ GetAttr(nsGkAtoms::control, control);
+ if (control.IsEmpty()) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ // XXXsmaug Should we use ShadowRoot::GetElementById in case
+ // element is in Shadow DOM?
+ RefPtr<Document> document = GetUncomposedDoc();
+ if (!document) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ RefPtr<Element> element = document->GetElementById(control);
+ if (!element) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ // XXXedgar, This is mainly for HTMLElement which doesn't do visible
+ // check in PerformAccesskey. We probably should always do visible
+ // check on HTMLElement even if the PerformAccesskey is not redirected from
+ // label XULelement per spec.
+ nsIFrame* frame = element->GetPrimaryFrame();
+ if (!frame || !frame->IsVisibleConsideringAncestors()) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ return element->PerformAccesskey(aKeyCausesActivation, aIsTrustedEvent);
+ }
+
+ nsIFrame* frame = GetPrimaryFrame();
+ if (!frame || !frame->IsVisibleConsideringAncestors()) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ bool focused = false;
+ // Define behavior for each type of XUL element.
+ if (!IsXULElement(nsGkAtoms::toolbarbutton)) {
+ if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
+ RefPtr<Element> elementToFocus = this;
+ // for radio buttons, focus the radiogroup instead
+ if (IsXULElement(nsGkAtoms::radio)) {
+ if (nsCOMPtr<nsIDOMXULSelectControlItemElement> controlItem =
+ AsXULSelectControlItem()) {
+ bool disabled;
+ controlItem->GetDisabled(&disabled);
+ if (!disabled) {
+ controlItem->GetControl(getter_AddRefs(elementToFocus));
+ }
+ }
+ }
+
+ if (elementToFocus) {
+ fm->SetFocus(elementToFocus, nsIFocusManager::FLAG_BYKEY);
+
+ // Return true if the element became focused.
+ nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow();
+ focused = (window && window->GetFocusedElement() == elementToFocus);
+ }
+ }
+ }
+
+ if (aKeyCausesActivation && !IsXULElement(nsGkAtoms::menulist)) {
+ ClickWithInputSource(MouseEvent_Binding::MOZ_SOURCE_KEYBOARD,
+ aIsTrustedEvent);
+ return focused;
+ }
+
+ // If the accesskey won't cause the activation and the focus isn't changed,
+ // either. Return error so EventStateManager would try to find next element
+ // to handle the accesskey.
+ return focused ? Result<bool, nsresult>{focused} : Err(NS_ERROR_ABORT);
+}
+
+//----------------------------------------------------------------------
+
+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(aLocalName, value);
+ SetEventHandler(aLocalName, value, true);
+ }
+}
+
+void nsXULElement::AddListenerForAttributeIfNeeded(const nsAttrName& aName) {
+ if (aName.IsAtom()) {
+ AddListenerForAttributeIfNeeded(aName.Atom());
+ }
+}
+
+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, we don't load xul.css for other elements
+ // except scrollbars.
+ //
+ // This assertion makes sure no other XUL element is used in a non-XUL
+ // document.
+ nsAtom* tag = NodeInfo()->NameAtom();
+ MOZ_ASSERT(tag == nsGkAtoms::scrollbar ||
+ tag == nsGkAtoms::scrollbarbutton ||
+ tag == nsGkAtoms::scrollcorner || tag == nsGkAtoms::slider ||
+ tag == nsGkAtoms::thumb || tag == nsGkAtoms::resizer,
+ "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(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);
+ }
+
+ RegUnRegAccessKey(true);
+
+ 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);
+ }
+
+ RegUnRegAccessKey(false);
+
+ 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::RegUnRegAccessKey(bool aDoReg) {
+ // Don't try to register for unsupported elements
+ if (!SupportsAccessKey()) {
+ return;
+ }
+
+ nsStyledElement::RegUnRegAccessKey(aDoReg);
+}
+
+bool nsXULElement::SupportsAccessKey() const {
+ if (NodeInfo()->Equals(nsGkAtoms::label) && HasAttr(nsGkAtoms::control)) {
+ return true;
+ }
+
+ // XXX(ntim): check if description[value] or description[accesskey] are
+ // actually used, remove `value` from {Before/After}SetAttr if not the case
+ if (NodeInfo()->Equals(nsGkAtoms::description) && HasAttr(nsGkAtoms::value) &&
+ HasAttr(nsGkAtoms::control)) {
+ return true;
+ }
+
+ return IsAnyOfXULElements(nsGkAtoms::button, nsGkAtoms::toolbarbutton,
+ nsGkAtoms::checkbox, nsGkAtoms::tab,
+ nsGkAtoms::radio);
+}
+
+void nsXULElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) {
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aName == nsGkAtoms::accesskey || aName == nsGkAtoms::control ||
+ aName == nsGkAtoms::value) {
+ RegUnRegAccessKey(false);
+ } else if ((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(nsGkAtoms::observes, oldValue);
+ if (oldValue.IsEmpty()) {
+ GetAttr(nsGkAtoms::command, oldValue);
+ }
+ Document* doc = GetUncomposedDoc();
+ if (!oldValue.IsEmpty() && doc->HasXULBroadcastManager()) {
+ RefPtr<XULBroadcastManager> broadcastManager =
+ doc->GetXULBroadcastManager();
+ broadcastManager->RemoveListener(this);
+ }
+#ifdef DEBUG
+ } else if (aName == nsGkAtoms::usercontextid) {
+ const nsAttrValue* oldValue = GetParsedAttr(aName);
+ if (oldValue && (!aValue || !aValue->Equals(*oldValue))) {
+ MOZ_ASSERT(false,
+ "Changing usercontextid doesn't really work properly.");
+ }
+#endif
+ }
+ }
+
+ return nsStyledElement::BeforeSetAttr(aNamespaceID, aName, aValue, aNotify);
+}
+
+void 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::accesskey || aName == nsGkAtoms::control ||
+ aName == nsGkAtoms::value) {
+ RegUnRegAccessKey(true);
+ } else 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 MOZ_DOM_LIST
+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;
+ int16_t button = 0;
+ while (event) {
+ NS_ENSURE_STATE(event->GetOriginalTarget() != commandElt);
+ RefPtr<XULCommandEvent> commandEvent = event->AsXULCommandEvent();
+ if (commandEvent) {
+ event = commandEvent->GetSourceEvent();
+ inputSource = commandEvent->InputSource();
+ button = commandEvent->Button();
+ } 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, button);
+ } 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(nsGkAtoms::command, command);
+ MOZ_ASSERT(!command.IsEmpty());
+ return DispatchXULCommand(aVisitor, command);
+ }
+ return nsStyledElement::PreHandleEvent(aVisitor);
+}
+
+//----------------------------------------------------------------------
+// Implementation methods
+
+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);
+ // This helps to avoid commands being dispatched from
+ // XULButtonElement::PostHandleEventForMenu.
+ eventUp.mFlags.mMultipleActionsPrevented = true;
+ WidgetMouseEvent eventClick(aIsTrustedEvent, eMouseClick, nullptr,
+ WidgetMouseEvent::eReal);
+ eventDown.mInputSource = eventUp.mInputSource = eventClick.mInputSource =
+ aInputSource;
+
+ // send mouse down
+ nsEventStatus status = nsEventStatus_eIgnore;
+ EventDispatcher::Dispatch(this, context, &eventDown, nullptr, &status);
+
+ // send mouse up
+ status = nsEventStatus_eIgnore; // reset status
+ EventDispatcher::Dispatch(this, context, &eventUp, nullptr, &status);
+
+ // send mouse click
+ status = nsEventStatus_eIgnore; // reset status
+ EventDispatcher::Dispatch(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);
+ }
+}
+
+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;
+}
+
+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();
+ }
+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)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+//----------------------------------------------------------------------
+//
+// 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->HasStencil()) {
+ // 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;
+ }
+
+ if (mNodeInfo->Equals(nsGkAtoms::parsererror) &&
+ mNodeInfo->NamespaceEquals(
+ nsDependentAtomString(nsGkAtoms::nsuri_parsererror))) {
+ 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,
+ StyleCssRuleType::Style);
+ 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();
+}
+
+//----------------------------------------------------------------------
+//
+// nsXULPrototypeScript
+//
+
+nsXULPrototypeScript::nsXULPrototypeScript(uint32_t aLineNo)
+ : nsXULPrototypeNode(eType_Script),
+ mLineNo(aLineNo),
+ mSrcLoading(false),
+ mOutOfLine(true),
+ mSrcLoadWaiters(nullptr),
+ mStencil(nullptr) {}
+
+static nsresult WriteStencil(nsIObjectOutputStream* aStream, JSContext* aCx,
+ JS::Stencil* aStencil) {
+ JS::TranscodeBuffer buffer;
+ JS::TranscodeResult code;
+ code = JS::EncodeStencil(aCx, aStencil, buffer);
+
+ if (code != JS::TranscodeResult::Ok) {
+ if (code == JS::TranscodeResult::Throw) {
+ JS_ClearPendingException(aCx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ MOZ_ASSERT(IsTranscodeFailureResult(code));
+ return NS_ERROR_FAILURE;
+ }
+
+ size_t size = buffer.length();
+ if (size > UINT32_MAX) {
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = aStream->Write32(size);
+ if (NS_SUCCEEDED(rv)) {
+ // Ideally we could just pass "buffer" here. See bug 1566574.
+ rv = aStream->WriteBytes(Span(buffer.begin(), size));
+ }
+
+ return rv;
+}
+
+static nsresult ReadStencil(nsIObjectInputStream* aStream, JSContext* aCx,
+ const JS::ReadOnlyDecodeOptions& aOptions,
+ JS::Stencil** aStencilOut) {
+ // We don't serialize mutedError-ness of scripts, which is fine as long as
+ // we only serialize system and XUL-y things. We can detect this by checking
+ // where the caller wants us to deserialize.
+ //
+ // CompilationScope() could theoretically GC, so get that out of the way
+ // before comparing to the cx global.
+ JSObject* loaderGlobal = xpc::CompilationScope();
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsSystemCaller(aCx) ||
+ JS::CurrentGlobalOrNull(aCx) == loaderGlobal);
+
+ uint32_t size;
+ nsresult rv = aStream->Read32(&size);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ char* data;
+ rv = aStream->ReadBytes(size, &data);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // The decoded stencil shouldn't borrow from the XDR buffer.
+ MOZ_ASSERT(!aOptions.borrowBuffer);
+ auto cleanupData = MakeScopeExit([&]() { free(data); });
+
+ JS::TranscodeRange range(reinterpret_cast<uint8_t*>(data), size);
+
+ {
+ JS::TranscodeResult code;
+ RefPtr<JS::Stencil> stencil;
+ code = JS::DecodeStencil(aCx, aOptions, range, getter_AddRefs(stencil));
+ if (code != JS::TranscodeResult::Ok) {
+ if (code == JS::TranscodeResult::Throw) {
+ JS_ClearPendingException(aCx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ MOZ_ASSERT(IsTranscodeFailureResult(code));
+ return NS_ERROR_FAILURE;
+ }
+
+ stencil.forget(aStencilOut);
+ }
+
+ return rv;
+}
+
+void nsXULPrototypeScript::FillCompileOptions(JS::CompileOptions& aOptions,
+ const char* aFilename,
+ uint32_t aLineNo) {
+ // NOTE: This method shouldn't change any field which also exists in
+ // JS::InstantiateOptions. If such field is added,
+ // nsXULPrototypeScript::InstantiateScript should also call this method.
+
+ // 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.
+ aOptions.setSourceIsLazy(mOutOfLine);
+
+ aOptions.setIntroductionType(mOutOfLine ? "srcScript" : "inlineScript")
+ .setFileAndLine(aFilename, mOutOfLine ? 1 : aLineNo);
+}
+
+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 || !mStencil,
+ "script source still loading when serializing?!");
+ if (!mStencil) return NS_ERROR_FAILURE;
+
+ // Write basic prototype data
+ nsresult rv;
+ rv = aStream->Write32(mLineNo);
+ if (NS_FAILED(rv)) return rv;
+
+ JSContext* cx = jsapi.cx();
+ MOZ_ASSERT(xpc::CompilationScope() == JS::CurrentGlobalOrNull(cx));
+
+ return WriteStencil(aStream, cx, mStencil);
+}
+
+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->HasScript(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->GetScriptOutputStream(mSrcURI, getter_AddRefs(oos));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsresult tmp = Serialize(oos, aProtoDoc, nullptr);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = cache->FinishScriptOutputStream(mSrcURI);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ if (NS_FAILED(rv)) cache->AbortCaching();
+ return rv;
+}
+
+nsresult nsXULPrototypeScript::Deserialize(
+ nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
+ nsIURI* aDocumentURI,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
+ nsresult rv;
+ NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mStencil,
+ "prototype script not well-initialized when deserializing?!");
+
+ // Read basic prototype data
+ rv = aStream->Read32(&mLineNo);
+ if (NS_FAILED(rv)) return rv;
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(xpc::CompilationScope())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ JSContext* cx = jsapi.cx();
+
+ JS::DecodeOptions options;
+ RefPtr<JS::Stencil> newStencil;
+ rv = ReadStencil(aStream, cx, options, getter_AddRefs(newStencil));
+ NS_ENSURE_SUCCESS(rv, rv);
+ Set(newStencil);
+ 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) {
+ RefPtr<JS::Stencil> newStencil = cache->GetStencil(mSrcURI);
+ if (newStencil) {
+ Set(newStencil);
+ }
+ }
+ }
+
+ if (!mStencil) {
+ if (mSrcURI) {
+ rv = cache->GetScriptInputStream(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 mStencil 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")) {
+ cache->PutStencil(mSrcURI, GetStencil());
+ }
+ cache->FinishScriptInputStream(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;
+}
+
+#ifdef DEBUG
+static void CheckErrorsAndWarnings(JS::FrontendContext* aFc,
+ const JS::ReadOnlyCompileOptions& aOptions) {
+ if (JS::HadFrontendErrors(aFc)) {
+ const JSErrorReport* report = JS::GetFrontendErrorReport(aFc, aOptions);
+ if (report) {
+ const char* message = "<unknown>";
+ const char* filename = "<unknown>";
+
+ if (report->message().c_str()) {
+ message = report->message().c_str();
+ }
+ if (report->filename.c_str()) {
+ filename = report->filename.c_str();
+ }
+
+ NS_WARNING(
+ nsPrintfCString(
+ "Had compilation error in ScriptCompileTask: %s at %s:%u:%u",
+ message, filename, report->lineno,
+ report->column.oneOriginValue())
+ .get());
+ }
+
+ if (JS::HadFrontendOverRecursed(aFc)) {
+ NS_WARNING("Had over recursed in ScriptCompileTask");
+ }
+
+ if (JS::HadFrontendOutOfMemory(aFc)) {
+ NS_WARNING("Had out of memory in ScriptCompileTask");
+ }
+
+ if (JS::HadFrontendAllocationOverflow(aFc)) {
+ NS_WARNING("Had allocation overflow in ScriptCompileTask");
+ }
+ }
+
+ size_t count = JS::GetFrontendWarningCount(aFc);
+ for (size_t i = 0; i < count; i++) {
+ const JSErrorReport* report = JS::GetFrontendWarningAt(aFc, i, aOptions);
+
+ const char* message = "<unknown>";
+ const char* filename = "<unknown>";
+
+ if (report->message().c_str()) {
+ message = report->message().c_str();
+ }
+ if (report->filename.c_str()) {
+ filename = report->filename.c_str();
+ }
+
+ NS_WARNING(
+ nsPrintfCString(
+ "Had compilation warning in ScriptCompileTask: %s at %s:%u:%u",
+ message, filename, report->lineno, report->column.oneOriginValue())
+ .get());
+ }
+}
+#endif
+
+class ScriptCompileTask final : public Task {
+ public:
+ explicit ScriptCompileTask(UniquePtr<Utf8Unit[], JS::FreePolicy>&& aText,
+ size_t aTextLength)
+ : Task(Kind::OffMainThreadOnly, EventQueuePriority::Normal),
+ mOptions(JS::OwningCompileOptions::ForFrontendContext()),
+ mText(std::move(aText)),
+ mTextLength(aTextLength) {}
+
+ ~ScriptCompileTask() {
+ if (mFrontendContext) {
+ JS::DestroyFrontendContext(mFrontendContext);
+ }
+ }
+
+ nsresult Init(JS::CompileOptions& aOptions) {
+ mFrontendContext = JS::NewFrontendContext();
+ if (!mFrontendContext) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mOptions.copy(mFrontendContext, aOptions)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ void Compile() {
+ // NOTE: The stack limit must be set from the same thread that compiles.
+ size_t stackSize = TaskController::GetThreadStackSize();
+ JS::SetNativeStackQuota(mFrontendContext,
+ JS::ThreadStackQuotaForSize(stackSize));
+
+ JS::SourceText<Utf8Unit> srcBuf;
+ if (NS_WARN_IF(!srcBuf.init(mFrontendContext, mText.get(), mTextLength,
+ JS::SourceOwnership::Borrowed))) {
+ return;
+ }
+
+ JS::CompilationStorage compileStorage;
+ mStencil = JS::CompileGlobalScriptToStencil(mFrontendContext, mOptions,
+ srcBuf, compileStorage);
+#ifdef DEBUG
+ // Chrome-privileged code shouldn't have any compilation error.
+ CheckErrorsAndWarnings(mFrontendContext, mOptions);
+ MOZ_ASSERT(mStencil);
+#endif
+ }
+
+ public:
+ TaskResult Run() override {
+ Compile();
+ return TaskResult::Complete;
+ }
+
+ already_AddRefed<JS::Stencil> StealStencil() { return mStencil.forget(); }
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ bool GetName(nsACString& aName) override {
+ aName.AssignLiteral("ScriptCompileTask");
+ return true;
+ }
+#endif
+
+ private:
+ // Owning-pointer for the context associated with the script compilation.
+ //
+ // The context is allocated on main thread in Init method, and is freed on
+ // any thread in the destructor.
+ JS::FrontendContext* mFrontendContext = nullptr;
+
+ JS::OwningCompileOptions mOptions;
+
+ RefPtr<JS::Stencil> mStencil;
+
+ // The source text for this compilation.
+ UniquePtr<Utf8Unit[], JS::FreePolicy> mText;
+ size_t mTextLength;
+};
+
+class NotifyOffThreadScriptCompletedTask : public Task {
+ public:
+ NotifyOffThreadScriptCompletedTask(nsIOffThreadScriptReceiver* aReceiver,
+ ScriptCompileTask* aCompileTask)
+ : Task(Kind::MainThreadOnly, EventQueuePriority::Normal),
+ mReceiver(aReceiver),
+ mCompileTask(aCompileTask) {}
+
+ TaskResult Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (PastShutdownPhase(ShutdownPhase::XPCOMShutdownFinal)) {
+ return TaskResult::Complete;
+ }
+
+ RefPtr<JS::Stencil> stencil = mCompileTask->StealStencil();
+ mCompileTask = nullptr;
+
+ (void)mReceiver->OnScriptCompileComplete(
+ stencil, stencil ? NS_OK : NS_ERROR_FAILURE);
+
+ return TaskResult::Complete;
+ }
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ bool GetName(nsACString& aName) override {
+ aName.AssignLiteral("NotifyOffThreadScriptCompletedTask");
+ return true;
+ }
+#endif
+
+ private:
+ // NOTE:
+ // This field is main-thread only, and this task shouldn't be freed off
+ // main thread.
+ //
+ // This is guaranteed by not having off-thread tasks which depends on this
+ // task, or any other pointer from off-thread task to this task, because
+ // otherwise the off-thread task's mDependencies can be the last reference,
+ // which results in freeing this task off main thread.
+ //
+ // If such task is added, this field must be moved to separate storage.
+ nsCOMPtr<nsIOffThreadScriptReceiver> mReceiver;
+
+ RefPtr<ScriptCompileTask> mCompileTask;
+};
+
+nsresult StartOffThreadCompile(JS::CompileOptions& aOptions,
+ UniquePtr<Utf8Unit[], JS::FreePolicy>&& aText,
+ size_t aTextLength,
+ nsIOffThreadScriptReceiver* aOffThreadReceiver) {
+ RefPtr<ScriptCompileTask> compileTask =
+ new ScriptCompileTask(std::move(aText), aTextLength);
+
+ RefPtr<NotifyOffThreadScriptCompletedTask> notifyTask =
+ new NotifyOffThreadScriptCompletedTask(aOffThreadReceiver, compileTask);
+
+ nsresult rv = compileTask->Init(aOptions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ notifyTask->AddDependency(compileTask.get());
+
+ TaskController::Get()->AddTask(compileTask.forget());
+ TaskController::Get()->AddTask(notifyTask.forget());
+
+ return NS_OK;
+}
+
+nsresult nsXULPrototypeScript::Compile(const char16_t* aText,
+ size_t aTextLength, nsIURI* aURI,
+ uint32_t aLineNo, Document* aDocument) {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(xpc::CompilationScope())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ JSContext* cx = jsapi.cx();
+
+ JS::SourceText<char16_t> srcBuf;
+ if (NS_WARN_IF(!srcBuf.init(cx, aText, aTextLength,
+ JS::SourceOwnership::Borrowed))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString urlspec;
+ nsresult rv = aURI->GetSpec(urlspec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ JS::CompileOptions options(cx);
+ FillCompileOptions(options, urlspec.get(), aLineNo);
+
+ RefPtr<JS::Stencil> stencil =
+ JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
+ if (!stencil) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ Set(stencil);
+ return NS_OK;
+}
+
+nsresult nsXULPrototypeScript::CompileMaybeOffThread(
+ mozilla::UniquePtr<mozilla::Utf8Unit[], JS::FreePolicy>&& aText,
+ size_t aTextLength, nsIURI* aURI, uint32_t aLineNo, Document* aDocument,
+ nsIOffThreadScriptReceiver* aOffThreadReceiver) {
+ MOZ_ASSERT(aOffThreadReceiver);
+
+ nsAutoCString urlspec;
+ nsresult rv = aURI->GetSpec(urlspec);
+ if (NS_WARN_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, urlspec.get(), aLineNo);
+
+ // TODO: This uses the same heuristics and the same threshold as the
+ // JS::CanDecodeOffThread API, but the heuristics needs to be updated
+ // to reflect the change regarding the Stencil API, and also the thread
+ // management on the consumer side (bug 1840831).
+ static constexpr size_t OffThreadMinimumTextLength = 5 * 1000;
+
+ if (StaticPrefs::javascript_options_parallel_parsing() &&
+ aTextLength >= OffThreadMinimumTextLength) {
+ rv = StartOffThreadCompile(options, std::move(aText), aTextLength,
+ aOffThreadReceiver);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ JS::SourceText<Utf8Unit> srcBuf;
+ if (NS_WARN_IF(!srcBuf.init(cx, aText.get(), aTextLength,
+ JS::SourceOwnership::Borrowed))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<JS::Stencil> stencil =
+ JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
+ if (!stencil) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ Set(stencil);
+ }
+ return NS_OK;
+}
+
+nsresult nsXULPrototypeScript::InstantiateScript(
+ JSContext* aCx, JS::MutableHandle<JSScript*> aScript) {
+ MOZ_ASSERT(mStencil);
+
+ JS::CompileOptions options(aCx);
+ JS::InstantiateOptions instantiateOptions(options);
+ aScript.set(JS::InstantiateGlobalStencil(aCx, instantiateOptions, mStencil));
+ if (!aScript) {
+ JS_ClearPendingException(aCx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+void nsXULPrototypeScript::Set(JS::Stencil* aStencil) { mStencil = aStencil; }
+
+//----------------------------------------------------------------------
+//
+// 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..4002ec63dc
--- /dev/null
+++ b/dom/xul/nsXULElement.h
@@ -0,0 +1,568 @@
+/* -*- 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/experimental/JSStencil.h"
+#include "js/RootingAPI.h"
+#include "js/SourceText.h"
+#include "js/TracingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Utility.h" // JS::FreePolicy
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.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 nsIControllers;
+class nsIObjectInputStream;
+class nsIObjectOutputStream;
+class nsIOffThreadScriptReceiver;
+class nsIPrincipal;
+class nsIURI;
+class nsXULPrototypeDocument;
+class nsXULPrototypeNode;
+struct JSContext;
+
+using nsPrototypeArray = nsTArray<RefPtr<nsXULPrototypeNode>>;
+
+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();
+
+ 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() = default;
+
+ void FillCompileOptions(JS::CompileOptions& aOptions, const char* aFilename,
+ uint32_t aLineNo);
+
+ 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);
+
+ // Compile given JS source text synchronously.
+ //
+ // This method doesn't take the ownership of aText, but borrows during the
+ // compilation.
+ //
+ // If successfully compiled, `HasStencil()` returns true.
+ nsresult Compile(const char16_t* aText, size_t aTextLength, nsIURI* aURI,
+ uint32_t aLineNo, mozilla::dom::Document* aDocument);
+
+ // Compile given JS source text possibly in off-thread.
+ //
+ // This method takes the ownership of aText.
+ //
+ // If this doesn't use off-thread compilation and successfully compiled,
+ // `HasStencil()` returns true.
+ //
+ // If this uses off-thread compilation, `HasStencil()` returns false, and
+ // once the compilation finishes, aOffThreadReceiver gets notified with the
+ // compiled stencil. The callback is responsible for calling `Set()` with
+ // the stencil.
+ nsresult CompileMaybeOffThread(
+ mozilla::UniquePtr<mozilla::Utf8Unit[], JS::FreePolicy>&& aText,
+ size_t aTextLength, nsIURI* aURI, uint32_t aLineNo,
+ mozilla::dom::Document* aDocument,
+ nsIOffThreadScriptReceiver* aOffThreadReceiver);
+
+ void Set(JS::Stencil* aStencil);
+
+ bool HasStencil() { return mStencil; }
+
+ JS::Stencil* GetStencil() { return mStencil.get(); }
+
+ nsresult InstantiateScript(JSContext* aCx,
+ JS::MutableHandle<JSScript*> aScript);
+
+ nsCOMPtr<nsIURI> mSrcURI;
+ uint32_t mLineNo;
+ bool mSrcLoading;
+ bool mOutOfLine;
+ mozilla::dom::PrototypeDocumentContentSink*
+ mSrcLoadWaiters; // [OWNER] but not COMPtr
+ private:
+ RefPtr<JS::Stencil> mStencil;
+};
+
+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:
+ using Document = mozilla::dom::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)
+
+ // This doesn't work on XUL elements! You probably want
+ // GetXULBoolAttr(nsGkAtoms::disabled) or so.
+ // TODO(emilio): Maybe we should unify HTML and XUL here.
+ bool IsDisabled() const = delete;
+
+ // 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 MOZ_DOM_LIST
+ 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 bool HasMenu();
+ MOZ_CAN_RUN_SCRIPT void OpenMenu(bool aOpenFlag);
+
+ MOZ_CAN_RUN_SCRIPT
+ virtual mozilla::Result<bool, nsresult> PerformAccesskey(
+ bool aKeyCausesActivation, bool aIsTrustedEvent) override;
+ MOZ_CAN_RUN_SCRIPT void ClickWithInputSource(uint16_t aInputSource,
+ bool aIsTrustedEvent);
+ struct XULFocusability {
+ bool mDefaultFocusable = false;
+ mozilla::Maybe<bool> mForcedFocusable;
+ mozilla::Maybe<int32_t> mForcedTabIndexIfFocusable;
+
+ static XULFocusability NeverFocusable() {
+ return {false, mozilla::Some(false), mozilla::Some(-1)};
+ }
+ };
+ XULFocusability GetXULFocusability(bool aWithMouse);
+ Focusable IsFocusableWithoutStyle(bool aWithMouse) override;
+
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo*,
+ nsINode** aResult) const override;
+
+ virtual bool IsEventAttributeNameInternal(nsAtom* aName) override;
+
+ using DOMString = mozilla::dom::DOMString;
+ void GetXULAttr(nsAtom* aName, DOMString& aResult) const {
+ GetAttr(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,
+ mozilla::ErrorResult& aError) {
+ if (aValue) {
+ SetAttr(aName, u"true"_ns, aError);
+ } else {
+ UnsetAttr(aName, aError);
+ }
+ }
+
+ // WebIDL API
+ bool Autofocus() const { return BoolAttrIsTrue(nsGkAtoms::autofocus); }
+ void SetAutofocus(bool aAutofocus, ErrorResult& aRv) {
+ SetXULBoolAttr(nsGkAtoms::autofocus, aAutofocus, aRv);
+ }
+ bool Hidden() const { return BoolAttrIsTrue(nsGkAtoms::hidden); }
+ void SetHidden(bool aHidden) {
+ SetXULBoolAttr(nsGkAtoms::hidden, aHidden, mozilla::IgnoreErrors());
+ }
+ bool Collapsed() const { return BoolAttrIsTrue(nsGkAtoms::collapsed); }
+ void SetCollapsed(bool aCollapsed) {
+ SetXULBoolAttr(nsGkAtoms::collapsed, aCollapsed, mozilla::IgnoreErrors());
+ }
+ 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 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);
+ }
+ nsIControllers* GetControllers(mozilla::ErrorResult& rv);
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void Click(mozilla::dom::CallerType aCallerType);
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ 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;
+
+ 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);
+
+ void BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+ void AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
+ const nsAttrValue* aValue, const nsAttrValue* aOldValue,
+ nsIPrincipal* aSubjectPrincipal, bool aNotify) override;
+
+ bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
+ const nsAString& aValue,
+ nsIPrincipal* aMaybeScriptedPrincipal,
+ nsAttrValue& aResult) override;
+
+ 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;
+ }
+
+ bool SupportsAccessKey() const;
+ void RegUnRegAccessKey(bool aDoReg) override;
+ 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);
+
+ JSObject* WrapNode(JSContext*, 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..9f2ee88e8d
--- /dev/null
+++ b/dom/xul/nsXULPopupListener.cpp
@@ -0,0 +1,283 @@
+/* -*- 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 "XULButtonElement.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 "mozilla/dom/DocumentInlines.h"
+#include "nsServiceManagerUtils.h"
+#include "nsLayoutUtils.h"
+#include "mozilla/ReflowInput.h"
+#include "nsIObjectLoadingContent.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/EventStateManager.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"
+
+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.
+ nsCOMPtr<nsIContent> targetContent =
+ nsIContent::FromEventTargetOrNull(mouseEvent->GetTarget());
+ if (!targetContent) {
+ return NS_OK;
+ }
+
+ if (nsIContent* content =
+ nsIContent::FromEventTargetOrNull(mouseEvent->GetOriginalTarget())) {
+ if (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) {
+ // 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 && mouseEvent->Button() != 0) {
+ // Only open popups when the left mouse button is down.
+ return NS_OK;
+ }
+
+ // Open the popup. LaunchPopup will call StopPropagation and PreventDefault
+ // in the right situations.
+ LaunchPopup(mouseEvent);
+
+ return NS_OK;
+}
+
+// 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,
+ {HidePopupOption::DeselectMenu, HidePopupOption::Async});
+ 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(type, identifier);
+
+ if (identifier.IsEmpty()) {
+ hasPopupAttr =
+ mElement->GetAttr(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.
+ if (auto* button = XULButtonElement::FromNodeOrNull(popup->GetParent())) {
+ if (button->IsMenu()) {
+ 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(nsGkAtoms::position) ||
+ (mPopupContent->HasAttr(nsGkAtoms::popupanchor) &&
+ mPopupContent->HasAttr(nsGkAtoms::popupalign)))) {
+ pm->ShowPopup(mPopupContent, mElement, u""_ns, 0, 0, false, true, false,
+ aEvent);
+ } else {
+ CSSIntPoint pos = aEvent->ScreenPoint(CallerType::System);
+ pm->ShowPopupAtScreen(mPopupContent, pos.x, pos.y, mIsContext, aEvent);
+ }
+
+ return NS_OK;
+}
diff --git a/dom/xul/nsXULPopupListener.h b/dom/xul/nsXULPopupListener.h
new file mode 100644
index 0000000000..ac5a631118
--- /dev/null
+++ b/dom/xul/nsXULPopupListener.h
@@ -0,0 +1,59 @@
+/* -*- 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::dom {
+class Element;
+class MouseEvent;
+} // namespace mozilla::dom
+
+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:
+ // |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..c70492b07c
--- /dev/null
+++ b/dom/xul/nsXULPrototypeCache.cpp
@@ -0,0 +1,500 @@
+/* -*- 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 "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/experimental/JSStencil.h"
+#include "js/TracingAPI.h"
+
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_nglayout.h"
+#include "mozilla/scache/StartupCache.h"
+#include "mozilla/scache/StartupCacheUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/intl/LocaleService.h"
+
+using namespace mozilla;
+using namespace mozilla::scache;
+using mozilla::intl::LocaleService;
+
+static const char kXULCacheInfoKey[] = "nsXULPrototypeCache.startupCache";
+#define CACHE_PREFIX(aCompilationTarget) "xulcache/" aCompilationTarget
+
+static void DisableXULCacheChangedCallback(const char* aPref, void* aClosure) {
+ if (nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance()) {
+ if (!cache->IsEnabled()) {
+ // AbortCaching() calls Flush() for us.
+ cache->AbortCaching();
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+
+nsXULPrototypeCache* nsXULPrototypeCache::sInstance = nullptr;
+
+nsXULPrototypeCache::nsXULPrototypeCache() = default;
+
+NS_IMPL_ISUPPORTS(nsXULPrototypeCache, nsIObserver)
+
+/* static */
+nsXULPrototypeCache* nsXULPrototypeCache::GetInstance() {
+ if (!sInstance) {
+ NS_ADDREF(sInstance = new nsXULPrototypeCache());
+
+ Preferences::RegisterCallback(
+ DisableXULCacheChangedCallback,
+ nsDependentCString(
+ StaticPrefs::GetPrefName_nglayout_debug_disable_xul_cache()));
+
+ 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 = GetPrototypeInputStream(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.InsertOrUpdate(uri, RefPtr{aDocument});
+
+ return NS_OK;
+}
+
+JS::Stencil* nsXULPrototypeCache::GetStencil(nsIURI* aURI) {
+ if (auto* entry = mStencilTable.GetEntry(aURI)) {
+ return entry->mStencil;
+ }
+ return nullptr;
+}
+
+nsresult nsXULPrototypeCache::PutStencil(nsIURI* aURI, JS::Stencil* aStencil) {
+ MOZ_ASSERT(aStencil, "Need a non-NULL stencil");
+
+#ifdef DEBUG_BUG_392650
+ if (mStencilTable.Get(aURI)) {
+ nsAutoCString scriptName;
+ aURI->GetSpec(scriptName);
+ nsAutoCString message("Loaded script ");
+ message += scriptName;
+ message += " twice (bug 392650)";
+ NS_WARNING(message.get());
+ }
+#endif
+
+ mStencilTable.PutEntry(aURI)->mStencil = aStencil;
+
+ return NS_OK;
+}
+
+void nsXULPrototypeCache::Flush() {
+ mPrototypeTable.Clear();
+ mStencilTable.Clear();
+}
+
+bool nsXULPrototypeCache::IsEnabled() {
+ return !StaticPrefs::nglayout_debug_disable_xul_cache();
+}
+
+void nsXULPrototypeCache::AbortCaching() {
+ // 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 = GetPrototypeOutputStream(protoURI, getter_AddRefs(oos));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aPrototypeDocument->Write(oos);
+ NS_ENSURE_SUCCESS(rv, rv);
+ FinishPrototypeOutputStream(protoURI);
+ return NS_FAILED(rv) ? rv : rv2;
+}
+
+static nsresult PathifyURIForType(nsXULPrototypeCache::CacheType cacheType,
+ nsIURI* in, nsACString& out) {
+ switch (cacheType) {
+ case nsXULPrototypeCache::CacheType::Prototype:
+ return PathifyURI(CACHE_PREFIX("proto"), in, out);
+ case nsXULPrototypeCache::CacheType::Script:
+ return PathifyURI(CACHE_PREFIX("script"), in, out);
+ }
+ MOZ_ASSERT_UNREACHABLE("unknown cache type?");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult nsXULPrototypeCache::GetInputStream(CacheType cacheType, nsIURI* uri,
+ nsIObjectInputStream** stream) {
+ nsAutoCString spec;
+ nsresult rv = PathifyURIForType(cacheType, 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.InsertOrUpdate(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.InsertOrUpdate(uri, storageStream);
+ }
+ objectOutput.forget(stream);
+ return NS_OK;
+}
+
+nsresult nsXULPrototypeCache::FinishOutputStream(CacheType cacheType,
+ 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();
+
+ UniqueFreePtr<char[]> buf;
+ uint32_t len;
+ rv = NewBufferFromStorageStream(storageStream, &buf, &len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mStartupCacheURITable.GetEntry(uri)) {
+ nsAutoCString spec;
+ rv = PathifyURIForType(cacheType, 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(CacheType cacheType, nsIURI* uri,
+ bool* exists) {
+ if (mOutputStreamTable.Get(uri, nullptr)) {
+ *exists = true;
+ return NS_OK;
+ }
+ nsAutoCString spec;
+ nsresult rv = PathifyURIForType(cacheType, 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 (StaticPrefs::nglayout_debug_disable_xul_cache()) {
+ 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
+ // mozJSModuleLoader) 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 = UniqueFreePtr<char[]>(
+ reinterpret_cast<char*>(malloc(sizeof(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 (const auto& prototype : mPrototypeTable.Values()) {
+ prototype->MarkInCCGeneration(aGeneration);
+ }
+}
+
+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->mStencilTable.ShallowSizeOfExcludingThis(mallocSizeOf);
+ // TODO Report content inside mStencilTable?
+
+ 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..657bf92511
--- /dev/null
+++ b/dom/xul/nsXULPrototypeCache.h
@@ -0,0 +1,164 @@
+/* -*- 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 "nsBaseHashtable.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsInterfaceHashtable.h"
+#include "nsRefPtrHashtable.h"
+#include "nsURIHashKey.h"
+#include "nsXULPrototypeDocument.h"
+#include "nsIStorageStream.h"
+
+#include "mozilla/scache/StartupCache.h"
+#include "js/experimental/JSStencil.h"
+#include "mozilla/RefPtr.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:
+ enum class CacheType { Prototype, Script };
+
+ // 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);
+ void RemovePrototype(nsIURI* aURI) { mPrototypeTable.Remove(aURI); }
+
+ JS::Stencil* GetStencil(nsIURI* aURI);
+ nsresult PutStencil(nsIURI* aURI, JS::Stencil* aStencil);
+
+ /**
+ * 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.
+ */
+
+ inline nsresult GetPrototypeInputStream(nsIURI* aURI,
+ nsIObjectInputStream** objectInput) {
+ return GetInputStream(CacheType::Prototype, aURI, objectInput);
+ }
+ inline nsresult GetScriptInputStream(nsIURI* aURI,
+ nsIObjectInputStream** objectInput) {
+ return GetInputStream(CacheType::Script, aURI, objectInput);
+ }
+ inline nsresult FinishScriptInputStream(nsIURI* aURI) {
+ return FinishInputStream(aURI);
+ }
+
+ inline nsresult GetPrototypeOutputStream(
+ nsIURI* aURI, nsIObjectOutputStream** objectOutput) {
+ return GetOutputStream(aURI, objectOutput);
+ }
+ inline nsresult GetScriptOutputStream(nsIURI* aURI,
+ nsIObjectOutputStream** objectOutput) {
+ return GetOutputStream(aURI, objectOutput);
+ }
+
+ inline nsresult FinishPrototypeOutputStream(nsIURI* aURI) {
+ return FinishOutputStream(CacheType::Prototype, aURI);
+ }
+ inline nsresult FinishScriptOutputStream(nsIURI* aURI) {
+ return FinishOutputStream(CacheType::Script, aURI);
+ }
+
+ inline nsresult HasPrototype(nsIURI* aURI, bool* exists) {
+ return HasData(CacheType::Prototype, aURI, exists);
+ }
+ inline nsresult HasScript(nsIURI* aURI, bool* exists) {
+ return HasData(CacheType::Script, aURI, exists);
+ }
+
+ private:
+ nsresult GetInputStream(CacheType cacheType, nsIURI* uri,
+ nsIObjectInputStream** stream);
+ nsresult FinishInputStream(nsIURI* aURI);
+
+ nsresult GetOutputStream(nsIURI* aURI, nsIObjectOutputStream** objectOutput);
+ nsresult FinishOutputStream(CacheType cacheType, nsIURI* aURI);
+ nsresult HasData(CacheType cacheType, nsIURI* aURI, bool* exists);
+
+ public:
+ static nsXULPrototypeCache* GetInstance();
+ static nsXULPrototypeCache* MaybeGetInstance() { return sInstance; }
+
+ static void ReleaseGlobals() { NS_IF_RELEASE(sInstance); }
+
+ void MarkInCCGeneration(uint32_t aGeneration);
+
+ static void CollectMemoryReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData);
+
+ protected:
+ friend nsresult NS_NewXULPrototypeCache(REFNSIID aIID, void** aResult);
+
+ nsXULPrototypeCache();
+ virtual ~nsXULPrototypeCache() = default;
+
+ static nsXULPrototypeCache* sInstance;
+
+ nsRefPtrHashtable<nsURIHashKey, nsXULPrototypeDocument>
+ mPrototypeTable; // owns the prototypes
+
+ class StencilHashKey : public nsURIHashKey {
+ public:
+ explicit StencilHashKey(const nsIURI* aKey) : nsURIHashKey(aKey) {}
+ StencilHashKey(StencilHashKey&&) = default;
+
+ RefPtr<JS::Stencil> mStencil;
+ };
+
+ nsTHashtable<StencilHashKey> mStencilTable;
+
+ // 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..d4f2b7962b
--- /dev/null
+++ b/dom/xul/nsXULPrototypeDocument.cpp
@@ -0,0 +1,511 @@
+/* -*- 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), mWasL10nCached(false) {
+ ++gRefCnt;
+}
+
+nsresult nsXULPrototypeDocument::Init() {
+ mNodeInfoManager = new nsNodeInfoManager(nullptr, nullptr);
+ return NS_OK;
+}
+
+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::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;
+ }
+
+ MOZ_ASSERT(aElement->HasAttr(nsGkAtoms::datal10nid));
+
+ Document* doc = aElement->OwnerDoc();
+ if (RefPtr<nsXULPrototypeElement> proto =
+ doc->mL10nProtoElements.Get(aElement)) {
+ RebuildPrototypeFromElement(proto, aElement, aDeep);
+ }
+}
diff --git a/dom/xul/nsXULPrototypeDocument.h b/dom/xul/nsXULPrototypeDocument.h
new file mode 100644
index 0000000000..511f4b3e2b
--- /dev/null
+++ b/dom/xul/nsXULPrototypeDocument.h
@@ -0,0 +1,127 @@
+/* -*- 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:
+ using Callback = std::function<void()>;
+
+ // 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)
+
+ 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;
+
+ 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..a968ed99d6
--- /dev/null
+++ b/dom/xul/nsXULSortService.cpp
@@ -0,0 +1,387 @@
+/* -*- 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 "nsWhitespaceTokenizer.h"
+#include "nsXULSortService.h"
+#include "nsXULElement.h"
+#include "nsTArray.h"
+#include "nsUnicharUtils.h"
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/intl/Collator.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.
+// Its lifecycle is bound to the 'SortContainer' function scope,
+// so we can use raw pointers to avoid refcount noise during sorting.
+struct contentSortInfo {
+ nsIContent* content;
+ nsIContent* 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(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,
+ 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);
+ }
+
+ using mozilla::intl::Collator;
+ const Collator* collator = nsXULContentUtils::GetCollator();
+ if (collator) {
+ return collator->CompareStrings(aLeft, aRight);
+ }
+
+ return ::Compare(aLeft, aRight, nsCaseInsensitiveStringComparator);
+}
+
+static int testSortCallback(const contentSortInfo& left,
+ const contentSortInfo& right,
+ nsSortState& sortState) {
+ int32_t sortOrder = 0;
+
+ size_t length = sortState.sortKeys.Length();
+ for (size_t t = 0; t < length; t++) {
+ // compare attributes. Ignore namespaces for now.
+ nsAutoString leftstr, rightstr;
+ if (left.content->IsElement()) {
+ left.content->AsElement()->GetAttr(sortState.sortKeys[t], leftstr);
+ }
+ if (right.content->IsElement()) {
+ right.content->AsElement()->GetAttr(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) {
+ std::swap(aData[downPoint--], 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, 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 {
+ items.Sort([&aSortState](auto left, auto right) {
+ return testSortCallback(left, right, 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, mozilla::IgnoreErrors());
+
+ // 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(nsGkAtoms::sort, existingsort);
+ nsAutoString existingsortDirection;
+ aRootElement->GetAttr(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/398289-resource.xhtml b/dom/xul/test/398289-resource.xhtml
new file mode 100644
index 0000000000..6702027ef1
--- /dev/null
+++ b/dom/xul/test/398289-resource.xhtml
@@ -0,0 +1,49 @@
+<?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">
+ <style xmlns="http://www.w3.org/1999/xhtml">
+ .tab-middle { outline: none !important }
+ </style>
+
+ <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.toml b/dom/xul/test/chrome.toml
new file mode 100644
index 0000000000..3e239fe1d9
--- /dev/null
+++ b/dom/xul/test/chrome.toml
@@ -0,0 +1,42 @@
+[DEFAULT]
+support-files = [
+ "398289-resource.xhtml",
+ "window_bug583948.xhtml",
+]
+
+["test_accesskey.xhtml"]
+
+["test_bug199692.xhtml"]
+
+["test_bug311681.xhtml"]
+
+["test_bug391002.xhtml"]
+
+["test_bug398289.html"]
+
+["test_bug403868.xhtml"]
+
+["test_bug418216.xhtml"]
+
+["test_bug445177.xhtml"]
+
+["test_bug468176.xhtml"]
+
+["test_bug583948.xhtml"]
+
+["test_bug749367.xhtml"]
+
+["test_bug775972.xhtml"]
+
+["test_bug1070049_throw_from_script.xhtml"]
+
+["test_bug1290965.xhtml"]
+
+["test_bug1686822.xhtml"]
+support-files = ["window_bug1686822.xhtml"]
+
+["test_html_template.xhtml"]
+
+["test_import_xul_to_content.xhtml"]
+
+["test_xul_tabindex_focus.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.toml b/dom/xul/test/mochitest.toml
new file mode 100644
index 0000000000..70c4690b01
--- /dev/null
+++ b/dom/xul/test/mochitest.toml
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+["test_disable_scroll_frame_plain.html"]
diff --git a/dom/xul/test/test_accesskey.xhtml b/dom/xul/test/test_accesskey.xhtml
new file mode 100644
index 0000000000..c71fcf78a0
--- /dev/null
+++ b/dom/xul/test/test_accesskey.xhtml
@@ -0,0 +1,57 @@
+<?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=1699284
+-->
+<window title="Mozilla Bug 1699284"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<body>
+<label id="label1" accesskey="a">Label 1</label><button id="button1" accesskey="a">Button 1</button>
+<label id="label2" accesskey="a" tabindex="0">Label 2</label><button id="button2">Button 2</button>
+<label id="label3">Label 3</label><button id="button3" accesskey="a">Button 3</button>
+<label id="label4" accesskey="a" control="button4">Label 4</label><button id="button4" disabled="true">Button 4</button>
+<label id="label5" accesskey="a" control="button5">Label 5</label><button id="button5">Button 5</button>
+<!-- Tests code -->
+<script type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 1699284 **/
+
+function PerformAccessKey(aKey) {
+ synthesizeKey(aKey, navigator.platform.includes("Mac") ? { altKey: true, ctrlKey: true }
+ : { altKey: true, shiftKey: true });
+};
+
+add_task(async function test_accesskey() {
+ let [label1, label2, label3, label4, label5] = document.querySelectorAll("label");
+ let [button1, button2, button3, button4, button5] = document.querySelectorAll("button");
+
+ [
+ label1, label2, label3, label4, label5, button1, button2, button3, button4, button5
+ ].forEach(function(ele) {
+ ele.addEventListener("click", function(e) {
+ ok(false, `${e.target.id} should not receive click event`);
+ });
+ });
+
+ PerformAccessKey("a");
+ is(document.activeElement.id, button1.id, `focus should move to ${button1.id}`);
+
+ PerformAccessKey("a");
+ is(document.activeElement.id, button3.id, `focus should move to ${button3.id}`);
+
+ PerformAccessKey("a");
+ is(document.activeElement.id, button5.id, `focus should move to ${button5.id}`);
+
+ // Cycle back to first element
+ PerformAccessKey("a");
+ is(document.activeElement.id, button1.id, `focus should move to ${button1.id}`);
+});
+
+]]>
+</script>
+</body>
+</window>
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..d5fb83e6ff
--- /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 new Error("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..dbef1df844
--- /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">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <toolbarbutton oncommand="++countera;" id="a">A</toolbarbutton>
+ <toolbarbutton oncommand="++counterb;" id="b">B</toolbarbutton>
+ <script>
+ //<![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, 0, null, aEvent.inputSource);
+ 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, 0, null, aEvent.inputSource);
+ 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..6d8b9efe4e
--- /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");
+ 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..8caef2ca5b
--- /dev/null
+++ b/dom/xul/test/test_bug311681.xhtml
@@ -0,0 +1,107 @@
+<?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.
+ let 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.
+ 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.
+ // eslint-disable-next-line no-unused-vars
+ 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..c4873fe2b2
--- /dev/null
+++ b/dom/xul/test/test_bug398289.html
@@ -0,0 +1,41 @@
+<!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%; margin: 0; overflow: hidden;" onload="setTimeout(onBodyLoad, 0);">
+ <iframe id="test" style="border: 0; width: 100%; height: 100%" scrolling="no" src="398289-resource.xhtml"></iframe>
+
+ <script class="testbody">
+ var snap1, snap2;
+ SimpleTest.waitForExplicitFinish();
+
+ async function onBodyLoad() {
+ window.frames[0].document.getElementById("test").selectedIndex = 0;
+ window.frames[0].document.getElementById("test").selectedIndex = 1;
+
+ frames[0].scrollTo(0, 0);
+ scrollTo(0, 0);
+ snap1 = await snapshotWindow(window);
+
+ document.getElementById("test").onload = onFrameLoad;
+ window.frames[0].location.reload();
+ }
+
+ async function onFrameLoad() {
+ frames[0].scrollTo(0, 0);
+ scrollTo(0, 0);
+ 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_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_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_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..32db06e564
--- /dev/null
+++ b/dom/xul/test/test_import_xul_to_content.xhtml
@@ -0,0 +1,68 @@
+<?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[
+
+ 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.
+ // eslint-disable-next-line no-unused-vars
+ let dummy = doc.documentElement.offsetLeft;
+ });
+
+ // We add some XUL to a content document. This should generate a warning.
+ var elt = document.createXULElement("scrollbar");
+ 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>