From 90c42b7afda8ddab70e9fb67b006248afce8e083 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 3 Jun 2020 11:44:55 +0000 Subject: Fix missing include --- dom/media/eme/EMEUtils.h | 1 + 1 file changed, 1 insertion(+) diff --git a/dom/media/eme/EMEUtils.h b/dom/media/eme/EMEUtils.h index 3b5d88561..b8f565329 100644 --- a/dom/media/eme/EMEUtils.h +++ b/dom/media/eme/EMEUtils.h @@ -11,6 +11,7 @@ #include "mozilla/Logging.h" #include "nsString.h" #include "nsTArray.h" +#include "mozilla/dom/TypedArray.h" namespace mozilla { -- cgit v1.2.3 From fbab1f4f9eab329cc3c807ac4606afe194cea51b Mon Sep 17 00:00:00 2001 From: Moonchild Date: Sun, 7 Jun 2020 18:09:13 +0000 Subject: [DOM] Hard-disable webcomponents and customelements prefs (redwood) --- dom/base/CustomElementRegistry.cpp | 3 +++ dom/base/nsDocument.cpp | 15 ++++++++------- modules/libpref/init/all.js | 2 ++ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/dom/base/CustomElementRegistry.cpp b/dom/base/CustomElementRegistry.cpp index f582d635f..3f8322199 100644 --- a/dom/base/CustomElementRegistry.cpp +++ b/dom/base/CustomElementRegistry.cpp @@ -166,8 +166,11 @@ NS_INTERFACE_MAP_END /* static */ bool CustomElementRegistry::IsCustomElementEnabled(JSContext* aCx, JSObject* aObject) { + return false; +/* return Preferences::GetBool("dom.webcomponents.customelements.enabled") || Preferences::GetBool("dom.webcomponents.enabled"); +*/ } /* static */ already_AddRefed diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 380593737..81e2783a4 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -5709,9 +5709,9 @@ nsDocument::IsWebComponentsEnabled(JSContext* aCx, JSObject* aObject) { JS::Rooted obj(aCx, aObject); - if (Preferences::GetBool("dom.webcomponents.enabled")) { - return true; - } + //if (Preferences::GetBool("dom.webcomponents.enabled")) { + // return true; + //} // Check for the webcomponents permission. See Bug 1181555. JSAutoCompartment ac(aCx, obj); @@ -5725,9 +5725,9 @@ nsDocument::IsWebComponentsEnabled(JSContext* aCx, JSObject* aObject) bool nsDocument::IsWebComponentsEnabled(dom::NodeInfo* aNodeInfo) { - if (Preferences::GetBool("dom.webcomponents.enabled")) { - return true; - } + //if (Preferences::GetBool("dom.webcomponents.enabled")) { + // return true; + //} nsIDocument* doc = aNodeInfo->GetDocument(); // Use GetScopeObject() here so that data documents work the same way as the @@ -5740,6 +5740,7 @@ nsDocument::IsWebComponentsEnabled(dom::NodeInfo* aNodeInfo) bool nsDocument::IsWebComponentsEnabled(nsPIDOMWindowInner* aWindow) { +/* if (aWindow) { nsresult rv; nsCOMPtr permMgr = @@ -5753,7 +5754,7 @@ nsDocument::IsWebComponentsEnabled(nsPIDOMWindowInner* aWindow) return perm == nsIPermissionManager::ALLOW_ACTION; } - +*/ return false; } diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 0193c5ef1..17413b537 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -1248,8 +1248,10 @@ pref("privacy.trackingprotection.pbmode.enabled", false); pref("dom.event.contextmenu.enabled", true); pref("dom.event.clipboardevents.enabled", true); +/* pref("dom.webcomponents.enabled", false); pref("dom.webcomponents.customelements.enabled", false); +*/ pref("javascript.enabled", true); // Enable Array.prototype.values -- cgit v1.2.3 From 2516886ceb712165172be60cbc8890e5d8cde02b Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Sun, 7 Jun 2020 18:58:28 -0400 Subject: Issue #1580 - Add missing MP3Demuxer.h to MediaDecoderReader.h Follow up for dom/media work on Issue #80. Fixes non-optimized build bustage. --- dom/media/MediaDecoderReader.h | 1 + 1 file changed, 1 insertion(+) diff --git a/dom/media/MediaDecoderReader.h b/dom/media/MediaDecoderReader.h index f53c74689..a31687be8 100644 --- a/dom/media/MediaDecoderReader.h +++ b/dom/media/MediaDecoderReader.h @@ -17,6 +17,7 @@ #include "MediaMetadataManager.h" #include "MediaQueue.h" #include "MediaTimer.h" +#include "MP3Demuxer.h" #include "AudioCompactor.h" #include "Intervals.h" #include "TimeUnits.h" -- cgit v1.2.3 From a934165b573516f2dd7ef1b2879612ac87e2b548 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Mon, 8 Jun 2020 18:07:02 +0000 Subject: [NSS] Update exported symbols Some symbols were missing from export which might lead to build bustage. --- config/external/nss/nss.symbols | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/external/nss/nss.symbols b/config/external/nss/nss.symbols index 705af4a94..83f5dc524 100644 --- a/config/external/nss/nss.symbols +++ b/config/external/nss/nss.symbols @@ -112,6 +112,7 @@ CERT_GetCertChainFromCert CERT_GetCertEmailAddress CERT_GetCertificateDer CERT_GetCertificateRequestExtensions +CERT_GetCertKeyType CERT_GetCertTimes CERT_GetCertTrust CERT_GetCommonName @@ -270,6 +271,7 @@ NSS_Init NSS_Initialize NSS_InitWithMerge NSS_IsInitialized +NSS_OptionGet NSS_OptionSet NSS_NoDB_Init NSS_SecureMemcmp @@ -383,6 +385,7 @@ PK11_GetNextSymKey PK11_GetPadMechanism PK11_GetPrivateKeyNickname PK11_GetPrivateModulusLen +PK11_GetSlotFromPrivateKey PK11_GetSlotID PK11_GetSlotInfo PK11_GetSlotName -- cgit v1.2.3 From f51beb3fdab3d586af081ac14478ea59e0523af4 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Tue, 9 Jun 2020 04:18:32 +0000 Subject: Issue #1560 - Always include speech synthesis IPDLs This is entangled with the IPC messaging configuration so we need these IPDLs even if sppech synthesis is otherwise not being built. This resolves #1560 --- dom/media/webspeech/moz.build | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dom/media/webspeech/moz.build b/dom/media/webspeech/moz.build index 677e0656f..c61c63b72 100644 --- a/dom/media/webspeech/moz.build +++ b/dom/media/webspeech/moz.build @@ -3,5 +3,11 @@ # 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/. +# Due to IPC entanglement we always need the synthesis ipdls if CONFIG['MOZ_WEBSPEECH']: DIRS += ['synth'] +else: + IPDL_SOURCES += [ + 'synth/ipc/PSpeechSynthesis.ipdl', + 'synth/ipc/PSpeechSynthesisRequest.ipdl', + ] \ No newline at end of file -- cgit v1.2.3 From 6235023c8bff80273981c70a06cb9decb4a4ffa5 Mon Sep 17 00:00:00 2001 From: Lootyhoof Date: Sat, 6 Jun 2020 00:37:29 +0100 Subject: Issue MoonchildProductions/UXP#1578 - Add global menubar support for GTK --- layout/build/moz.build | 4 + layout/build/nsLayoutStatics.cpp | 7 + modules/libpref/init/all.js | 4 + toolkit/content/widgets/popup.xml | 10 +- toolkit/content/xul.css | 9 + widget/gtk/moz.build | 11 + widget/gtk/nsDbusmenu.cpp | 59 +++ widget/gtk/nsDbusmenu.h | 97 ++++ widget/gtk/nsMenu.cpp | 800 +++++++++++++++++++++++++++++++++ widget/gtk/nsMenu.h | 120 +++++ widget/gtk/nsMenuBar.cpp | 541 ++++++++++++++++++++++ widget/gtk/nsMenuBar.h | 103 +++++ widget/gtk/nsMenuContainer.cpp | 156 +++++++ widget/gtk/nsMenuContainer.h | 66 +++ widget/gtk/nsMenuItem.cpp | 712 +++++++++++++++++++++++++++++ widget/gtk/nsMenuItem.h | 77 ++++ widget/gtk/nsMenuObject.cpp | 634 ++++++++++++++++++++++++++ widget/gtk/nsMenuObject.h | 165 +++++++ widget/gtk/nsMenuSeparator.cpp | 74 +++ widget/gtk/nsMenuSeparator.h | 33 ++ widget/gtk/nsNativeMenuAtomList.h | 9 + widget/gtk/nsNativeMenuAtoms.cpp | 35 ++ widget/gtk/nsNativeMenuAtoms.h | 23 + widget/gtk/nsNativeMenuDocListener.cpp | 329 ++++++++++++++ widget/gtk/nsNativeMenuDocListener.h | 146 ++++++ widget/gtk/nsNativeMenuService.cpp | 485 ++++++++++++++++++++ widget/gtk/nsNativeMenuService.h | 80 ++++ widget/gtk/nsWidgetFactory.cpp | 8 + widget/gtk/nsWindow.cpp | 6 + widget/gtk/nsWindow.h | 6 + widget/moz.build | 4 +- xpfe/appshell/nsWebShellWindow.cpp | 2 +- 32 files changed, 4811 insertions(+), 4 deletions(-) create mode 100644 widget/gtk/nsDbusmenu.cpp create mode 100644 widget/gtk/nsDbusmenu.h create mode 100644 widget/gtk/nsMenu.cpp create mode 100644 widget/gtk/nsMenu.h create mode 100644 widget/gtk/nsMenuBar.cpp create mode 100644 widget/gtk/nsMenuBar.h create mode 100644 widget/gtk/nsMenuContainer.cpp create mode 100644 widget/gtk/nsMenuContainer.h create mode 100644 widget/gtk/nsMenuItem.cpp create mode 100644 widget/gtk/nsMenuItem.h create mode 100644 widget/gtk/nsMenuObject.cpp create mode 100644 widget/gtk/nsMenuObject.h create mode 100644 widget/gtk/nsMenuSeparator.cpp create mode 100644 widget/gtk/nsMenuSeparator.h create mode 100644 widget/gtk/nsNativeMenuAtomList.h create mode 100644 widget/gtk/nsNativeMenuAtoms.cpp create mode 100644 widget/gtk/nsNativeMenuAtoms.h create mode 100644 widget/gtk/nsNativeMenuDocListener.cpp create mode 100644 widget/gtk/nsNativeMenuDocListener.h create mode 100644 widget/gtk/nsNativeMenuService.cpp create mode 100644 widget/gtk/nsNativeMenuService.h diff --git a/layout/build/moz.build b/layout/build/moz.build index b98e8265a..70b075491 100644 --- a/layout/build/moz.build +++ b/layout/build/moz.build @@ -73,6 +73,10 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': '/dom/system', '/dom/system/android', ] +elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + LOCAL_INCLUDES += [ + '/widget/gtk', + ] if CONFIG['MOZ_WEBSPEECH']: LOCAL_INCLUDES += [ diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp index d71513268..6761931f6 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -124,6 +124,10 @@ #include "mozilla/StaticPresData.h" #include "mozilla/dom/WebIDLGlobalNameHash.h" +#ifdef MOZ_WIDGET_GTK +#include "nsNativeMenuAtoms.h" +#endif + using namespace mozilla; using namespace mozilla::net; using namespace mozilla::dom; @@ -158,6 +162,9 @@ nsLayoutStatics::Initialize() nsTextServicesDocument::RegisterAtoms(); nsHTMLTags::RegisterAtoms(); nsRDFAtoms::RegisterAtoms(); +#ifdef MOZ_WIDGET_GTK + nsNativeMenuAtoms::RegisterAtoms(); +#endif NS_SealStaticAtomTable(); diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 17413b537..f3cf4c74e 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -251,6 +251,10 @@ pref("browser.sessionhistory.max_total_viewers", -1); pref("browser.newtabpage.add_to_session_history", false); pref("ui.use_native_colors", true); +#ifdef MOZ_WIDGET_GTK +// Determines whether the menubar is shown in the global menubar or not. +pref("ui.use_global_menubar", false); +#endif pref("ui.click_hold_context_menus", false); // Duration of timeout of incremental search in menus (ms). 0 means infinite. pref("ui.menu.incremental_search.timeout", 1000); diff --git a/toolkit/content/widgets/popup.xml b/toolkit/content/widgets/popup.xml index bb1a5eeee..43c529780 100644 --- a/toolkit/content/widgets/popup.xml +++ b/toolkit/content/widgets/popup.xml @@ -25,8 +25,14 @@ - + + + diff --git a/toolkit/content/xul.css b/toolkit/content/xul.css index 24a6713f9..0aa0d3a21 100644 --- a/toolkit/content/xul.css +++ b/toolkit/content/xul.css @@ -307,6 +307,15 @@ toolbar[type="menubar"][autohide="true"][inactive="true"]:not([customizing="true } %endif +%ifdef MOZ_WIDGET_GTK +window[shellshowingmenubar="true"] menubar, +window[shellshowingmenubar="true"] +toolbar[type="menubar"]:not([customizing="true"]) { + /* If a system-wide global menubar is in use, hide the XUL menubar. */ + display: none !important; +} +%endif + toolbarseparator { -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbardecoration"); } diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build index baccb6ccd..8d621c0a0 100644 --- a/widget/gtk/moz.build +++ b/widget/gtk/moz.build @@ -24,10 +24,18 @@ UNIFIED_SOURCES += [ 'nsAppShell.cpp', 'nsBidiKeyboard.cpp', 'nsColorPicker.cpp', + 'nsDbusmenu.cpp', 'nsFilePicker.cpp', 'nsGtkKeyUtils.cpp', 'nsImageToPixbuf.cpp', 'nsLookAndFeel.cpp', + 'nsMenuBar.cpp', + 'nsMenuContainer.cpp', + 'nsMenuItem.cpp', + 'nsMenuObject.cpp', + 'nsMenuSeparator.cpp', + 'nsNativeMenuAtoms.cpp', + 'nsNativeMenuDocListener.cpp', 'nsNativeThemeGTK.cpp', 'nsScreenGtk.cpp', 'nsScreenManagerGtk.cpp', @@ -40,6 +48,8 @@ UNIFIED_SOURCES += [ ] SOURCES += [ + 'nsMenu.cpp', # conflicts with X11 headers + 'nsNativeMenuService.cpp', 'nsWindow.cpp', # conflicts with X11 headers ] @@ -104,6 +114,7 @@ FINAL_LIBRARY = 'xul' LOCAL_INCLUDES += [ '/layout/generic', + '/layout/style', '/layout/xul', '/other-licenses/atk-1.0', '/widget', diff --git a/widget/gtk/nsDbusmenu.cpp b/widget/gtk/nsDbusmenu.cpp new file mode 100644 index 000000000..2849536e9 --- /dev/null +++ b/widget/gtk/nsDbusmenu.cpp @@ -0,0 +1,59 @@ +/* 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 "nsDbusmenu.h" +#include "prlink.h" +#include "mozilla/ArrayUtils.h" + +#define FUNC(name, type, params) \ +nsDbusmenuFunctions::_##name##_fn nsDbusmenuFunctions::s_##name; +DBUSMENU_GLIB_FUNCTIONS +DBUSMENU_GTK_FUNCTIONS +#undef FUNC + +static PRLibrary *gDbusmenuGlib = nullptr; +static PRLibrary *gDbusmenuGtk = nullptr; + +typedef void (*nsDbusmenuFunc)(); +struct nsDbusmenuDynamicFunction { + const char *functionName; + nsDbusmenuFunc *function; +}; + +/* static */ nsresult +nsDbusmenuFunctions::Init() { +#define FUNC(name, type, params) \ + { #name, (nsDbusmenuFunc *)&nsDbusmenuFunctions::s_##name }, + static const nsDbusmenuDynamicFunction kDbusmenuGlibSymbols[] = { + DBUSMENU_GLIB_FUNCTIONS + }; + static const nsDbusmenuDynamicFunction kDbusmenuGtkSymbols[] = { + DBUSMENU_GTK_FUNCTIONS + }; + +#define LOAD_LIBRARY(symbol, name) \ + if (!g##symbol) { \ + g##symbol = PR_LoadLibrary(name); \ + if (!g##symbol) { \ + return NS_ERROR_FAILURE; \ + } \ + } \ + for (uint32_t i = 0; i < mozilla::ArrayLength(k##symbol##Symbols); ++i) { \ + *k##symbol##Symbols[i].function = \ + PR_FindFunctionSymbol(g##symbol, k##symbol##Symbols[i].functionName); \ + if (!*k##symbol##Symbols[i].function) { \ + return NS_ERROR_FAILURE; \ + } \ + } + + LOAD_LIBRARY(DbusmenuGlib, "libdbusmenu-glib.so.4") +#if (MOZ_WIDGET_GTK == 3) + LOAD_LIBRARY(DbusmenuGtk, "libdbusmenu-gtk3.so.4") +#else + LOAD_LIBRARY(DbusmenuGtk, "libdbusmenu-gtk.so.4") +#endif +#undef LOAD_LIBRARY + + return NS_OK; +} diff --git a/widget/gtk/nsDbusmenu.h b/widget/gtk/nsDbusmenu.h new file mode 100644 index 000000000..c0d9e7979 --- /dev/null +++ b/widget/gtk/nsDbusmenu.h @@ -0,0 +1,97 @@ +/* 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 __nsDbusmenu_h__ +#define __nsDbusmenu_h__ + +#include "nsError.h" + +#include +#include + +#define DBUSMENU_GLIB_FUNCTIONS \ + FUNC(dbusmenu_menuitem_child_add_position, gboolean, (DbusmenuMenuitem* mi, DbusmenuMenuitem* child, guint position)) \ + FUNC(dbusmenu_menuitem_child_append, gboolean, (DbusmenuMenuitem* mi, DbusmenuMenuitem* child)) \ + FUNC(dbusmenu_menuitem_child_delete, gboolean, (DbusmenuMenuitem* mi, DbusmenuMenuitem* child)) \ + FUNC(dbusmenu_menuitem_get_children, GList*, (DbusmenuMenuitem* mi)) \ + FUNC(dbusmenu_menuitem_new, DbusmenuMenuitem*, (void)) \ + FUNC(dbusmenu_menuitem_property_get, const gchar*, (DbusmenuMenuitem* mi, const gchar* property)) \ + FUNC(dbusmenu_menuitem_property_get_bool, gboolean, (DbusmenuMenuitem* mi, const gchar* property)) \ + FUNC(dbusmenu_menuitem_property_remove, void, (DbusmenuMenuitem* mi, const gchar* property)) \ + FUNC(dbusmenu_menuitem_property_set, gboolean, (DbusmenuMenuitem* mi, const gchar* property, const gchar* value)) \ + FUNC(dbusmenu_menuitem_property_set_bool, gboolean, (DbusmenuMenuitem* mi, const gchar* property, const gboolean value)) \ + FUNC(dbusmenu_menuitem_property_set_int, gboolean, (DbusmenuMenuitem* mi, const gchar* property, const gint value)) \ + FUNC(dbusmenu_menuitem_show_to_user, void, (DbusmenuMenuitem* mi, guint timestamp)) \ + FUNC(dbusmenu_menuitem_take_children, GList*, (DbusmenuMenuitem* mi)) \ + FUNC(dbusmenu_server_new, DbusmenuServer*, (const gchar* object)) \ + FUNC(dbusmenu_server_set_root, void, (DbusmenuServer* server, DbusmenuMenuitem* root)) \ + FUNC(dbusmenu_server_set_status, void, (DbusmenuServer* server, DbusmenuStatus status)) + +#define DBUSMENU_GTK_FUNCTIONS \ + FUNC(dbusmenu_menuitem_property_set_image, gboolean, (DbusmenuMenuitem* menuitem, const gchar* property, const GdkPixbuf* data)) \ + FUNC(dbusmenu_menuitem_property_set_shortcut, gboolean, (DbusmenuMenuitem* menuitem, guint key, GdkModifierType modifier)) + +typedef struct _DbusmenuMenuitem DbusmenuMenuitem; +typedef struct _DbusmenuServer DbusmenuServer; + +enum DbusmenuStatus { + DBUSMENU_STATUS_NORMAL, + DBUSMENU_STATUS_NOTICE +}; + +#define DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU "submenu" +#define DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY "children-display" +#define DBUSMENU_MENUITEM_PROP_ENABLED "enabled" +#define DBUSMENU_MENUITEM_PROP_ICON_DATA "icon-data" +#define DBUSMENU_MENUITEM_PROP_LABEL "label" +#define DBUSMENU_MENUITEM_PROP_SHORTCUT "shortcut" +#define DBUSMENU_MENUITEM_PROP_TYPE "type" +#define DBUSMENU_MENUITEM_PROP_TOGGLE_STATE "toggle-state" +#define DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE "toggle-type" +#define DBUSMENU_MENUITEM_PROP_VISIBLE "visible" +#define DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW "about-to-show" +#define DBUSMENU_MENUITEM_SIGNAL_EVENT "event" +#define DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED "item-activated" +#define DBUSMENU_MENUITEM_TOGGLE_CHECK "checkmark" +#define DBUSMENU_MENUITEM_TOGGLE_RADIO "radio" +#define DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED 1 +#define DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED 0 +#define DBUSMENU_SERVER_PROP_DBUS_OBJECT "dbus-object" + +class nsDbusmenuFunctions { +public: + nsDbusmenuFunctions() = delete; + + static nsresult Init(); + +#define FUNC(name, type, params) \ + typedef type (*_##name##_fn) params; \ + static _##name##_fn s_##name; + DBUSMENU_GLIB_FUNCTIONS + DBUSMENU_GTK_FUNCTIONS +#undef FUNC + +}; + +#define dbusmenu_menuitem_child_add_position nsDbusmenuFunctions::s_dbusmenu_menuitem_child_add_position +#define dbusmenu_menuitem_child_append nsDbusmenuFunctions::s_dbusmenu_menuitem_child_append +#define dbusmenu_menuitem_child_delete nsDbusmenuFunctions::s_dbusmenu_menuitem_child_delete +#define dbusmenu_menuitem_get_children nsDbusmenuFunctions::s_dbusmenu_menuitem_get_children +#define dbusmenu_menuitem_new nsDbusmenuFunctions::s_dbusmenu_menuitem_new +#define dbusmenu_menuitem_property_get nsDbusmenuFunctions::s_dbusmenu_menuitem_property_get +#define dbusmenu_menuitem_property_get_bool nsDbusmenuFunctions::s_dbusmenu_menuitem_property_get_bool +#define dbusmenu_menuitem_property_remove nsDbusmenuFunctions::s_dbusmenu_menuitem_property_remove +#define dbusmenu_menuitem_property_set nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set +#define dbusmenu_menuitem_property_set_bool nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_bool +#define dbusmenu_menuitem_property_set_int nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_int +#define dbusmenu_menuitem_show_to_user nsDbusmenuFunctions::s_dbusmenu_menuitem_show_to_user +#define dbusmenu_menuitem_take_children nsDbusmenuFunctions::s_dbusmenu_menuitem_take_children +#define dbusmenu_server_new nsDbusmenuFunctions::s_dbusmenu_server_new +#define dbusmenu_server_set_root nsDbusmenuFunctions::s_dbusmenu_server_set_root +#define dbusmenu_server_set_status nsDbusmenuFunctions::s_dbusmenu_server_set_status + +#define dbusmenu_menuitem_property_set_image nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_image +#define dbusmenu_menuitem_property_set_shortcut nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_shortcut + +#endif /* __nsDbusmenu_h__ */ diff --git a/widget/gtk/nsMenu.cpp b/widget/gtk/nsMenu.cpp new file mode 100644 index 000000000..073a4acf6 --- /dev/null +++ b/widget/gtk/nsMenu.cpp @@ -0,0 +1,800 @@ +/* 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/. */ + +#define _IMPL_NS_LAYOUT + +#include "mozilla/dom/Element.h" +#include "mozilla/Assertions.h" +#include "mozilla/GuardObjects.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Move.h" +#include "mozilla/StyleSetHandleInlines.h" +#include "nsAutoPtr.h" +#include "nsBindingManager.h" +#include "nsComponentManagerUtils.h" +#include "nsContentUtils.h" +#include "nsCSSValue.h" +#include "nsGkAtoms.h" +#include "nsGtkUtils.h" +#include "nsIAtom.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIPresShell.h" +#include "nsIRunnable.h" +#include "nsITimer.h" +#include "nsString.h" +#include "nsStyleContext.h" +#include "nsStyleSet.h" +#include "nsStyleStruct.h" +#include "nsThreadUtils.h" +#include "nsXBLBinding.h" +#include "nsXBLService.h" + +#include "nsNativeMenuAtoms.h" +#include "nsNativeMenuDocListener.h" + +#include + +#include "nsMenu.h" + +using namespace mozilla; + +class nsMenuContentInsertedEvent : public Runnable { +public: + nsMenuContentInsertedEvent(nsMenu* aMenu, + nsIContent* aContainer, + nsIContent* aChild, + nsIContent* aPrevSibling) : + mWeakMenu(aMenu), + mContainer(aContainer), + mChild(aChild), + mPrevSibling(aPrevSibling) { } + + NS_IMETHODIMP Run() { + if (!mWeakMenu) { + return NS_OK; + } + + static_cast(mWeakMenu.get())->HandleContentInserted(mContainer, + mChild, + mPrevSibling); + return NS_OK; + } + +private: + nsWeakMenuObject mWeakMenu; + + nsCOMPtr mContainer; + nsCOMPtr mChild; + nsCOMPtr mPrevSibling; +}; + +class nsMenuContentRemovedEvent : public Runnable { +public: + nsMenuContentRemovedEvent(nsMenu* aMenu, + nsIContent* aContainer, + nsIContent* aChild) : + mWeakMenu(aMenu), + mContainer(aContainer), + mChild(aChild) { } + + NS_IMETHODIMP Run() { + if (!mWeakMenu) { + return NS_OK; + } + + static_cast(mWeakMenu.get())->HandleContentRemoved(mContainer, + mChild); + return NS_OK; + } + +private: + nsWeakMenuObject mWeakMenu; + + nsCOMPtr mContainer; + nsCOMPtr mChild; +}; + +static void +DispatchMouseEvent(nsIContent* aTarget, mozilla::EventMessage aMsg) { + if (!aTarget) { + return; + } + + WidgetMouseEvent event(true, aMsg, nullptr, WidgetMouseEvent::eReal); + aTarget->DispatchDOMEvent(&event, nullptr, nullptr, nullptr); +} + +static void +AttachXBLBindings(nsIContent* aContent) { + nsIDocument* doc = aContent->OwnerDoc(); + nsIPresShell* shell = doc->GetShell(); + if (!shell) { + return; + } + + RefPtr sc = + shell->StyleSet()->AsGecko()->ResolveStyleFor(aContent->AsElement(), + nullptr); + if (!sc) { + return; + } + + const nsStyleDisplay* display = sc->StyleDisplay(); + if (!display->mBinding) { + return; + } + + nsXBLService* xbl = nsXBLService::GetInstance(); + if (!xbl) { + return; + } + + RefPtr binding; + bool dummy; + nsresult rv = xbl->LoadBindings(aContent, display->mBinding->GetURI(), + display->mBinding->mOriginPrincipal, + getter_AddRefs(binding), &dummy); + if ((NS_FAILED(rv) && rv != NS_ERROR_XBL_BLOCKED) || !binding) { + return; + } + + doc->BindingManager()->AddToAttachedQueue(binding); +} + +void +nsMenu::SetPopupState(EPopupState aState) { + mPopupState = aState; + + if (!mPopupContent) { + return; + } + + nsAutoString state; + switch (aState) { + case ePopupState_Showing: + state.Assign(NS_LITERAL_STRING("showing")); + break; + case ePopupState_Open: + state.Assign(NS_LITERAL_STRING("open")); + break; + case ePopupState_Hiding: + state.Assign(NS_LITERAL_STRING("hiding")); + break; + default: + break; + } + + if (state.IsEmpty()) { + mPopupContent->UnsetAttr(kNameSpaceID_None, + nsNativeMenuAtoms::_moz_nativemenupopupstate, + false); + } else { + mPopupContent->SetAttr(kNameSpaceID_None, + nsNativeMenuAtoms::_moz_nativemenupopupstate, + state, false); + } +} + +/* static */ void +nsMenu::DoOpenCallback(nsITimer* aTimer, void* aClosure) { + nsMenu* self = static_cast(aClosure); + + dbusmenu_menuitem_show_to_user(self->GetNativeData(), 0); + + self->mOpenDelayTimer = nullptr; +} + +/* static */ void +nsMenu::menu_event_cb(DbusmenuMenuitem* menu, + const gchar* name, + GVariant* value, + guint timestamp, + gpointer user_data) { + nsMenu* self = static_cast(user_data); + + nsAutoCString event(name); + + if (event.Equals(NS_LITERAL_CSTRING("closed"))) { + self->OnClose(); + return; + } + + if (event.Equals(NS_LITERAL_CSTRING("opened"))) { + self->OnOpen(); + return; + } +} + +void +nsMenu::MaybeAddPlaceholderItem() { + MOZ_ASSERT(!IsInBatchedUpdate(), + "Shouldn't be modifying the native menu structure now"); + + GList* children = dbusmenu_menuitem_get_children(GetNativeData()); + if (!children) { + MOZ_ASSERT(!mPlaceholderItem); + + mPlaceholderItem = dbusmenu_menuitem_new(); + if (!mPlaceholderItem) { + return; + } + + dbusmenu_menuitem_property_set_bool(mPlaceholderItem, + DBUSMENU_MENUITEM_PROP_VISIBLE, + false); + + MOZ_ALWAYS_TRUE( + dbusmenu_menuitem_child_append(GetNativeData(), mPlaceholderItem)); + } +} + +void +nsMenu::EnsureNoPlaceholderItem() { + MOZ_ASSERT(!IsInBatchedUpdate(), + "Shouldn't be modifying the native menu structure now"); + + if (!mPlaceholderItem) { + return; + } + + MOZ_ALWAYS_TRUE( + dbusmenu_menuitem_child_delete(GetNativeData(), mPlaceholderItem)); + MOZ_ASSERT(!dbusmenu_menuitem_get_children(GetNativeData())); + + g_object_unref(mPlaceholderItem); + mPlaceholderItem = nullptr; +} + +void +nsMenu::OnOpen() { + if (mNeedsRebuild) { + Build(); + } + + nsWeakMenuObject self(this); + nsCOMPtr origPopupContent(mPopupContent); + { + nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker; + + SetPopupState(ePopupState_Showing); + DispatchMouseEvent(mPopupContent, eXULPopupShowing); + + ContentNode()->SetAttr(kNameSpaceID_None, nsGkAtoms::open, + NS_LITERAL_STRING("true"), true); + } + + if (!self) { + // We were deleted! + return; + } + + // I guess that the popup could have changed + if (origPopupContent != mPopupContent) { + return; + } + + nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker; + + size_t count = ChildCount(); + for (size_t i = 0; i < count; ++i) { + ChildAt(i)->ContainerIsOpening(); + } + + SetPopupState(ePopupState_Open); + DispatchMouseEvent(mPopupContent, eXULPopupShown); +} + +void +nsMenu::Build() { + mNeedsRebuild = false; + + while (ChildCount() > 0) { + RemoveChildAt(0); + } + + InitializePopup(); + + if (!mPopupContent) { + return; + } + + uint32_t count = mPopupContent->GetChildCount(); + for (uint32_t i = 0; i < count; ++i) { + nsIContent* childContent = mPopupContent->GetChildAt(i); + + UniquePtr child = CreateChild(childContent); + + if (!child) { + continue; + } + + AppendChild(Move(child)); + } +} + +void +nsMenu::InitializePopup() { + nsCOMPtr oldPopupContent; + oldPopupContent.swap(mPopupContent); + + for (uint32_t i = 0; i < ContentNode()->GetChildCount(); ++i) { + nsIContent* child = ContentNode()->GetChildAt(i); + + int32_t dummy; + nsCOMPtr tag = child->OwnerDoc()->BindingManager()->ResolveTag(child, &dummy); + if (tag == nsGkAtoms::menupopup) { + mPopupContent = child; + break; + } + } + + if (oldPopupContent == mPopupContent) { + return; + } + + // The popup has changed + + if (oldPopupContent) { + DocListener()->UnregisterForContentChanges(oldPopupContent); + } + + SetPopupState(ePopupState_Closed); + + if (!mPopupContent) { + return; + } + + AttachXBLBindings(mPopupContent); + + DocListener()->RegisterForContentChanges(mPopupContent, this); +} + +void +nsMenu::RemoveChildAt(size_t aIndex) { + MOZ_ASSERT(IsInBatchedUpdate() || !mPlaceholderItem, + "Shouldn't have a placeholder menuitem"); + + nsMenuContainer::RemoveChildAt(aIndex, !IsInBatchedUpdate()); + StructureMutated(); + + if (!IsInBatchedUpdate()) { + MaybeAddPlaceholderItem(); + } +} + +void +nsMenu::RemoveChild(nsIContent* aChild) { + size_t index = IndexOf(aChild); + if (index == NoIndex) { + return; + } + + RemoveChildAt(index); +} + +void +nsMenu::InsertChildAfter(UniquePtr aChild, + nsIContent* aPrevSibling) { + if (!IsInBatchedUpdate()) { + EnsureNoPlaceholderItem(); + } + + nsMenuContainer::InsertChildAfter(Move(aChild), aPrevSibling, + !IsInBatchedUpdate()); + StructureMutated(); +} + +void +nsMenu::AppendChild(UniquePtr aChild) { + if (!IsInBatchedUpdate()) { + EnsureNoPlaceholderItem(); + } + + nsMenuContainer::AppendChild(Move(aChild), !IsInBatchedUpdate()); + StructureMutated(); +} + +bool +nsMenu::IsInBatchedUpdate() const { + return mBatchedUpdateState != eBatchedUpdateState_Inactive; +} + +void +nsMenu::StructureMutated() { + if (!IsInBatchedUpdate()) { + return; + } + + mBatchedUpdateState = eBatchedUpdateState_DidMutate; +} + +bool +nsMenu::CanOpen() const { + bool isVisible = dbusmenu_menuitem_property_get_bool(GetNativeData(), + DBUSMENU_MENUITEM_PROP_VISIBLE); + bool isDisabled = ContentNode()->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::disabled, + nsGkAtoms::_true, + eCaseMatters); + + return (isVisible && !isDisabled); +} + +void +nsMenu::HandleContentInserted(nsIContent* aContainer, + nsIContent* aChild, + nsIContent* aPrevSibling) { + if (aContainer == mPopupContent) { + UniquePtr child = CreateChild(aChild); + + if (child) { + InsertChildAfter(Move(child), aPrevSibling); + } + } else { + Build(); + } +} + +void +nsMenu::HandleContentRemoved(nsIContent* aContainer, nsIContent* aChild) { + if (aContainer == mPopupContent) { + RemoveChild(aChild); + } else { + Build(); + } +} + +void +nsMenu::InitializeNativeData() { + // Dbusmenu provides an "about-to-show" signal, and also "opened" and + // "closed" events. However, Unity is the only thing that sends + // both "about-to-show" and "opened" events. Unity 2D and the HUD only + // send "opened" events, so we ignore "about-to-show" (I don't think + // there's any real difference between them anyway). + // To complicate things, there are certain conditions where we don't + // get a "closed" event, so we need to be able to handle this :/ + g_signal_connect(G_OBJECT(GetNativeData()), "event", + G_CALLBACK(menu_event_cb), this); + + mNeedsRebuild = true; + mNeedsUpdate = true; + + MaybeAddPlaceholderItem(); + + AttachXBLBindings(ContentNode()); +} + +void +nsMenu::Update(nsStyleContext* aStyleContext) { + if (mNeedsUpdate) { + mNeedsUpdate = false; + + UpdateLabel(); + UpdateSensitivity(); + } + + UpdateVisibility(aStyleContext); + UpdateIcon(aStyleContext); +} + +nsMenuObject::PropertyFlags +nsMenu::SupportedProperties() const { + return static_cast( + nsMenuObject::ePropLabel | + nsMenuObject::ePropEnabled | + nsMenuObject::ePropVisible | + nsMenuObject::ePropIconData | + nsMenuObject::ePropChildDisplay + ); +} + +void +nsMenu::OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) { + MOZ_ASSERT(aContent == ContentNode() || aContent == mPopupContent, + "Received an event that wasn't meant for us!"); + + if (mNeedsUpdate) { + return; + } + + if (aContent != ContentNode()) { + return; + } + + if (!Parent()->IsBeingDisplayed()) { + mNeedsUpdate = true; + return; + } + + if (aAttribute == nsGkAtoms::disabled) { + UpdateSensitivity(); + } else if (aAttribute == nsGkAtoms::label || + aAttribute == nsGkAtoms::accesskey || + aAttribute == nsGkAtoms::crop) { + UpdateLabel(); + } else if (aAttribute == nsGkAtoms::hidden || + aAttribute == nsGkAtoms::collapsed) { + RefPtr sc = GetStyleContext(); + UpdateVisibility(sc); + } else if (aAttribute == nsGkAtoms::image) { + RefPtr sc = GetStyleContext(); + UpdateIcon(sc); + } +} + +void +nsMenu::OnContentInserted(nsIContent* aContainer, nsIContent* aChild, + nsIContent* aPrevSibling) { + MOZ_ASSERT(aContainer == ContentNode() || aContainer == mPopupContent, + "Received an event that wasn't meant for us!"); + + if (mNeedsRebuild) { + return; + } + + if (mPopupState == ePopupState_Closed) { + mNeedsRebuild = true; + return; + } + + nsContentUtils::AddScriptRunner( + new nsMenuContentInsertedEvent(this, aContainer, aChild, + aPrevSibling)); +} + +void +nsMenu::OnContentRemoved(nsIContent* aContainer, nsIContent* aChild) { + MOZ_ASSERT(aContainer == ContentNode() || aContainer == mPopupContent, + "Received an event that wasn't meant for us!"); + + if (mNeedsRebuild) { + return; + } + + if (mPopupState == ePopupState_Closed) { + mNeedsRebuild = true; + return; + } + + nsContentUtils::AddScriptRunner( + new nsMenuContentRemovedEvent(this, aContainer, aChild)); +} + +/* + * Some menus (eg, the History menu in Firefox) refresh themselves on + * opening by removing all children and then re-adding new ones. As this + * happens whilst the menu is opening in Unity, it causes some flickering + * as the menu popup is resized multiple times. To avoid this, we try to + * reuse native menu items when the menu structure changes during a + * batched update. If we can handle menu structure changes from Goanna + * just by updating properties of native menu items (rather than destroying + * and creating new ones), then we eliminate any flickering that occurs as + * the menu is opened. To do this, we don't modify any native menu items + * until the end of the update batch. + */ + +void +nsMenu::OnBeginUpdates(nsIContent* aContent) { + MOZ_ASSERT(aContent == ContentNode() || aContent == mPopupContent, + "Received an event that wasn't meant for us!"); + MOZ_ASSERT(!IsInBatchedUpdate(), "Already in an update batch!"); + + if (aContent != mPopupContent) { + return; + } + + mBatchedUpdateState = eBatchedUpdateState_Active; +} + +void +nsMenu::OnEndUpdates() { + if (!IsInBatchedUpdate()) { + return; + } + + bool didMutate = mBatchedUpdateState == eBatchedUpdateState_DidMutate; + mBatchedUpdateState = eBatchedUpdateState_Inactive; + + /* Optimize for the case where we only had attribute changes */ + if (!didMutate) { + return; + } + + EnsureNoPlaceholderItem(); + + GList* nextNativeChild = dbusmenu_menuitem_get_children(GetNativeData()); + DbusmenuMenuitem* nextOwnedNativeChild = nullptr; + + size_t count = ChildCount(); + + // Find the first native menu item that is `owned` by a corresponding + // Goanna menuitem + for (size_t i = 0; i < count; ++i) { + if (ChildAt(i)->GetNativeData()) { + nextOwnedNativeChild = ChildAt(i)->GetNativeData(); + break; + } + } + + // Now iterate over all Goanna menuitems + for (size_t i = 0; i < count; ++i) { + nsMenuObject* child = ChildAt(i); + + if (child->GetNativeData()) { + // This child already has a corresponding native menuitem. + // Remove all preceding orphaned native items. At this point, we + // modify the native menu structure. + while (nextNativeChild && + nextNativeChild->data != nextOwnedNativeChild) { + + DbusmenuMenuitem* data = + static_cast(nextNativeChild->data); + nextNativeChild = nextNativeChild->next; + + MOZ_ALWAYS_TRUE(dbusmenu_menuitem_child_delete(GetNativeData(), + data)); + } + + if (nextNativeChild) { + nextNativeChild = nextNativeChild->next; + } + + // Now find the next native menu item that is `owned` + nextOwnedNativeChild = nullptr; + for (size_t j = i + 1; j < count; ++j) { + if (ChildAt(j)->GetNativeData()) { + nextOwnedNativeChild = ChildAt(j)->GetNativeData(); + break; + } + } + } else { + // This child is new, and doesn't have a native menu item. Find one! + if (nextNativeChild && + nextNativeChild->data != nextOwnedNativeChild) { + + DbusmenuMenuitem* data = + static_cast(nextNativeChild->data); + + if (NS_SUCCEEDED(child->AdoptNativeData(data))) { + nextNativeChild = nextNativeChild->next; + } + } + + // There wasn't a suitable one available, so create a new one. + // At this point, we modify the native menu structure. + if (!child->GetNativeData()) { + child->CreateNativeData(); + MOZ_ALWAYS_TRUE( + dbusmenu_menuitem_child_add_position(GetNativeData(), + child->GetNativeData(), + i)); + } + } + } + + while (nextNativeChild) { + DbusmenuMenuitem* data = + static_cast(nextNativeChild->data); + nextNativeChild = nextNativeChild->next; + + MOZ_ALWAYS_TRUE(dbusmenu_menuitem_child_delete(GetNativeData(), data)); + } + + MaybeAddPlaceholderItem(); +} + +nsMenu::nsMenu(nsMenuContainer* aParent, nsIContent* aContent) : + nsMenuContainer(aParent, aContent), + mNeedsRebuild(false), + mNeedsUpdate(false), + mPlaceholderItem(nullptr), + mPopupState(ePopupState_Closed), + mBatchedUpdateState(eBatchedUpdateState_Inactive) { + MOZ_COUNT_CTOR(nsMenu); +} + +nsMenu::~nsMenu() { + if (IsInBatchedUpdate()) { + OnEndUpdates(); + } + + // Although nsTArray will take care of this in its destructor, + // we have to manually ensure children are removed from our native menu + // item, just in case our parent recycles us + while (ChildCount() > 0) { + RemoveChildAt(0); + } + + EnsureNoPlaceholderItem(); + + if (DocListener() && mPopupContent) { + DocListener()->UnregisterForContentChanges(mPopupContent); + } + + if (GetNativeData()) { + g_signal_handlers_disconnect_by_func(GetNativeData(), + FuncToGpointer(menu_event_cb), + this); + } + + MOZ_COUNT_DTOR(nsMenu); +} + +nsMenuObject::EType +nsMenu::Type() const { + return eType_Menu; +} + +bool +nsMenu::IsBeingDisplayed() const { + return mPopupState == ePopupState_Open; +} + +bool +nsMenu::NeedsRebuild() const { + return mNeedsRebuild; +} + +void +nsMenu::OpenMenu() { + if (!CanOpen()) { + return; + } + + if (mOpenDelayTimer) { + return; + } + + // Here, we synchronously fire popupshowing and popupshown events and then + // open the menu after a short delay. This allows the menu to refresh before + // it's shown, and avoids an issue where keyboard focus is not on the first + // item of the history menu in Firefox when opening it with the keyboard, + // because extra items to appear at the top of the menu + + OnOpen(); + + mOpenDelayTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + if (!mOpenDelayTimer) { + return; + } + + if (NS_FAILED(mOpenDelayTimer->InitWithFuncCallback(DoOpenCallback, + this, + 100, + nsITimer::TYPE_ONE_SHOT))) { + mOpenDelayTimer = nullptr; + } +} + +void +nsMenu::OnClose() { + if (mPopupState == ePopupState_Closed) { + return; + } + + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + + // We do this to avoid mutating our view of the menu until + // after we have finished + nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker; + + SetPopupState(ePopupState_Hiding); + DispatchMouseEvent(mPopupContent, eXULPopupHiding); + + // Sigh, make sure all of our descendants are closed, as we don't + // always get closed events for submenus when scrubbing quickly through + // the menu + size_t count = ChildCount(); + for (size_t i = 0; i < count; ++i) { + if (ChildAt(i)->Type() == nsMenuObject::eType_Menu) { + static_cast(ChildAt(i))->OnClose(); + } + } + + SetPopupState(ePopupState_Closed); + DispatchMouseEvent(mPopupContent, eXULPopupHidden); + + ContentNode()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open, true); +} diff --git a/widget/gtk/nsMenu.h b/widget/gtk/nsMenu.h new file mode 100644 index 000000000..a198a8e72 --- /dev/null +++ b/widget/gtk/nsMenu.h @@ -0,0 +1,120 @@ +/* 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 __nsMenu_h__ +#define __nsMenu_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" + +#include "nsDbusmenu.h" +#include "nsMenuContainer.h" +#include "nsMenuObject.h" + +#include + +class nsIAtom; +class nsIContent; +class nsITimer; +class nsStyleContext; + +#define NSMENU_NUMBER_OF_POPUPSTATE_BITS 2U +#define NSMENU_NUMBER_OF_FLAGS 4U + +// This class represents a menu +class nsMenu final : public nsMenuContainer { +public: + nsMenu(nsMenuContainer* aParent, nsIContent* aContent); + ~nsMenu(); + + nsMenuObject::EType Type() const override; + + bool IsBeingDisplayed() const override; + bool NeedsRebuild() const override; + + // Tell the desktop shell to display this menu + void OpenMenu(); + + // Normally called via the shell, but it's public so that child + // menuitems can do the shells work. Sigh.... + void OnClose(); + +private: + friend class nsMenuContentInsertedEvent; + friend class nsMenuContentRemovedEvent; + + enum EPopupState { + ePopupState_Closed, + ePopupState_Showing, + ePopupState_Open, + ePopupState_Hiding + }; + + void SetPopupState(EPopupState aState); + + static void DoOpenCallback(nsITimer* aTimer, void* aClosure); + static void menu_event_cb(DbusmenuMenuitem* menu, + const gchar* name, + GVariant* value, + guint timestamp, + gpointer user_data); + + // We add a placeholder item to empty menus so that Unity actually treats + // us as a proper menu, rather than a menuitem without a submenu + void MaybeAddPlaceholderItem(); + + // Removes a placeholder item if it exists and asserts that this succeeds + void EnsureNoPlaceholderItem(); + + void OnOpen(); + void Build(); + void InitializePopup(); + void RemoveChildAt(size_t aIndex); + void RemoveChild(nsIContent* aChild); + void InsertChildAfter(mozilla::UniquePtr aChild, + nsIContent* aPrevSibling); + void AppendChild(mozilla::UniquePtr aChild); + bool IsInBatchedUpdate() const; + void StructureMutated(); + bool CanOpen() const; + + void HandleContentInserted(nsIContent* aContainer, + nsIContent* aChild, + nsIContent* aPrevSibling); + void HandleContentRemoved(nsIContent* aContainer, + nsIContent* aChild); + + void InitializeNativeData() override; + void Update(nsStyleContext* aStyleContext) override; + nsMenuObject::PropertyFlags SupportedProperties() const override; + + void OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) override; + void OnContentInserted(nsIContent* aContainer, nsIContent* aChild, + nsIContent* aPrevSibling) override; + void OnContentRemoved(nsIContent* aContainer, nsIContent* aChild) override; + void OnBeginUpdates(nsIContent* aContent) override; + void OnEndUpdates() override; + + bool mNeedsRebuild; + bool mNeedsUpdate; + + DbusmenuMenuitem* mPlaceholderItem; + + EPopupState mPopupState; + + enum EBatchedUpdateState { + eBatchedUpdateState_Inactive, + eBatchedUpdateState_Active, + eBatchedUpdateState_DidMutate + }; + + EBatchedUpdateState mBatchedUpdateState; + + nsCOMPtr mPopupContent; + + nsCOMPtr mOpenDelayTimer; +}; + +#endif /* __nsMenu_h__ */ diff --git a/widget/gtk/nsMenuBar.cpp b/widget/gtk/nsMenuBar.cpp new file mode 100644 index 000000000..e7caf119c --- /dev/null +++ b/widget/gtk/nsMenuBar.cpp @@ -0,0 +1,541 @@ +/* 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/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/dom/Element.h" +#include "mozilla/Move.h" +#include "mozilla/Preferences.h" +#include "nsAutoPtr.h" +#include "nsContentUtils.h" +#include "nsIDocument.h" +#include "nsIDOMEvent.h" +#include "nsIDOMEventListener.h" +#include "nsIDOMEventTarget.h" +#include "nsIDOMKeyEvent.h" +#include "nsIRunnable.h" +#include "nsIWidget.h" +#include "nsTArray.h" +#include "nsUnicharUtils.h" + +#include "nsMenu.h" +#include "nsNativeMenuAtoms.h" +#include "nsNativeMenuService.h" + +#include +#include +#include +#include + +#include "nsMenuBar.h" + +using namespace mozilla; + +static bool +ShouldHandleKeyEvent(nsIDOMEvent* aEvent) { + bool handled, trusted = false; + aEvent->GetPreventDefault(&handled); + aEvent->GetIsTrusted(&trusted); + + if (handled || !trusted) { + return false; + } + + return true; +} + +class nsMenuBarContentInsertedEvent : public Runnable { +public: + nsMenuBarContentInsertedEvent(nsMenuBar* aMenuBar, + nsIContent* aChild, + nsIContent* aPrevSibling) : + mWeakMenuBar(aMenuBar), + mChild(aChild), + mPrevSibling(aPrevSibling) { } + + NS_IMETHODIMP Run() + { + if (!mWeakMenuBar) { + return NS_OK; + } + + static_cast(mWeakMenuBar.get())->HandleContentInserted(mChild, + mPrevSibling); + return NS_OK; + } + +private: + nsWeakMenuObject mWeakMenuBar; + + nsCOMPtr mChild; + nsCOMPtr mPrevSibling; +}; + +class nsMenuBarContentRemovedEvent : public Runnable { +public: + nsMenuBarContentRemovedEvent(nsMenuBar* aMenuBar, + nsIContent* aChild) : + mWeakMenuBar(aMenuBar), + mChild(aChild) { } + + NS_IMETHODIMP Run() + { + if (!mWeakMenuBar) { + return NS_OK; + } + + static_cast(mWeakMenuBar.get())->HandleContentRemoved(mChild); + return NS_OK; + } + +private: + nsWeakMenuObject mWeakMenuBar; + + nsCOMPtr mChild; +}; + +class nsMenuBar::DocEventListener final : public nsIDOMEventListener { +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + + DocEventListener(nsMenuBar* aOwner) : mOwner(aOwner) { }; + +private: + ~DocEventListener() { }; + + nsMenuBar* mOwner; +}; + +NS_IMPL_ISUPPORTS(nsMenuBar::DocEventListener, nsIDOMEventListener) + +NS_IMETHODIMP +nsMenuBar::DocEventListener::HandleEvent(nsIDOMEvent* aEvent) { + nsAutoString type; + nsresult rv = aEvent->GetType(type); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to determine event type"); + return rv; + } + + if (type.Equals(NS_LITERAL_STRING("focus"))) { + mOwner->Focus(); + } else if (type.Equals(NS_LITERAL_STRING("blur"))) { + mOwner->Blur(); + } else if (type.Equals(NS_LITERAL_STRING("keypress"))) { + rv = mOwner->Keypress(aEvent); + } else if (type.Equals(NS_LITERAL_STRING("keydown"))) { + rv = mOwner->KeyDown(aEvent); + } else if (type.Equals(NS_LITERAL_STRING("keyup"))) { + rv = mOwner->KeyUp(aEvent); + } + + return rv; +} + +nsMenuBar::nsMenuBar(nsIContent* aMenuBarNode) : + nsMenuContainer(new nsNativeMenuDocListener(aMenuBarNode), aMenuBarNode), + mTopLevel(nullptr), + mServer(nullptr), + mIsActive(false) { + MOZ_COUNT_CTOR(nsMenuBar); +} + +nsresult +nsMenuBar::Init(nsIWidget* aParent) { + MOZ_ASSERT(aParent); + + GdkWindow* gdkWin = static_cast( + aParent->GetNativeData(NS_NATIVE_WINDOW)); + if (!gdkWin) { + return NS_ERROR_FAILURE; + } + + gpointer user_data = nullptr; + gdk_window_get_user_data(gdkWin, &user_data); + if (!user_data || !GTK_IS_CONTAINER(user_data)) { + return NS_ERROR_FAILURE; + } + + mTopLevel = gtk_widget_get_toplevel(GTK_WIDGET(user_data)); + if (!mTopLevel) { + return NS_ERROR_FAILURE; + } + + g_object_ref(mTopLevel); + + nsAutoCString path; + path.Append(NS_LITERAL_CSTRING("/com/canonical/menu/")); + char xid[10]; + sprintf(xid, "%X", static_cast( + GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel)))); + path.Append(xid); + + mServer = dbusmenu_server_new(path.get()); + if (!mServer) { + return NS_ERROR_FAILURE; + } + + CreateNativeData(); + if (!GetNativeData()) { + return NS_ERROR_FAILURE; + } + + dbusmenu_server_set_root(mServer, GetNativeData()); + + mEventListener = new DocEventListener(this); + + mDocument = do_QueryInterface(ContentNode()->OwnerDoc()); + + mAccessKey = Preferences::GetInt("ui.key.menuAccessKey"); + if (mAccessKey == nsIDOMKeyEvent::DOM_VK_SHIFT) { + mAccessKeyMask = eModifierShift; + } else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_CONTROL) { + mAccessKeyMask = eModifierCtrl; + } else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_ALT) { + mAccessKeyMask = eModifierAlt; + } else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_META) { + mAccessKeyMask = eModifierMeta; + } else { + mAccessKeyMask = eModifierAlt; + } + + return NS_OK; +} + +void +nsMenuBar::Build() { + uint32_t count = ContentNode()->GetChildCount(); + for (uint32_t i = 0; i < count; ++i) { + nsIContent* childContent = ContentNode()->GetChildAt(i); + + UniquePtr child = CreateChild(childContent); + + if (!child) { + continue; + } + + AppendChild(Move(child)); + } +} + +void +nsMenuBar::DisconnectDocumentEventListeners() { + mDocument->RemoveEventListener(NS_LITERAL_STRING("focus"), + mEventListener, + true); + mDocument->RemoveEventListener(NS_LITERAL_STRING("blur"), + mEventListener, + true); + mDocument->RemoveEventListener(NS_LITERAL_STRING("keypress"), + mEventListener, + false); + mDocument->RemoveEventListener(NS_LITERAL_STRING("keydown"), + mEventListener, + false); + mDocument->RemoveEventListener(NS_LITERAL_STRING("keyup"), + mEventListener, + false); +} + +void +nsMenuBar::SetShellShowingMenuBar(bool aShowing) { + ContentNode()->OwnerDoc()->GetRootElement()->SetAttr( + kNameSpaceID_None, nsNativeMenuAtoms::shellshowingmenubar, + aShowing ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"), + true); +} + +void +nsMenuBar::Focus() { + ContentNode()->SetAttr(kNameSpaceID_None, nsNativeMenuAtoms::openedwithkey, + NS_LITERAL_STRING("false"), true); +} + +void +nsMenuBar::Blur() { + // We do this here in case we lose focus before getting the + // keyup event, which leaves the menubar state looking like + // the alt key is stuck down + dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL); +} + +nsMenuBar::ModifierFlags +nsMenuBar::GetModifiersFromEvent(nsIDOMKeyEvent* aEvent) { + ModifierFlags modifiers = static_cast(0); + bool modifier; + + aEvent->GetAltKey(&modifier); + if (modifier) { + modifiers = static_cast(modifiers | eModifierAlt); + } + + aEvent->GetShiftKey(&modifier); + if (modifier) { + modifiers = static_cast(modifiers | eModifierShift); + } + + aEvent->GetCtrlKey(&modifier); + if (modifier) { + modifiers = static_cast(modifiers | eModifierCtrl); + } + + aEvent->GetMetaKey(&modifier); + if (modifier) { + modifiers = static_cast(modifiers | eModifierMeta); + } + + return modifiers; +} + +nsresult +nsMenuBar::Keypress(nsIDOMEvent* aEvent) { + if (!ShouldHandleKeyEvent(aEvent)) { + return NS_OK; + } + + nsCOMPtr keyEvent = do_QueryInterface(aEvent); + if (!keyEvent) { + return NS_OK; + } + + ModifierFlags modifiers = GetModifiersFromEvent(keyEvent); + if (((modifiers & mAccessKeyMask) == 0) || + ((modifiers & ~mAccessKeyMask) != 0)) { + return NS_OK; + } + + uint32_t charCode; + keyEvent->GetCharCode(&charCode); + if (charCode == 0) { + return NS_OK; + } + + char16_t ch = char16_t(charCode); + char16_t chl = ToLowerCase(ch); + char16_t chu = ToUpperCase(ch); + + nsMenuObject* found = nullptr; + uint32_t count = ChildCount(); + for (uint32_t i = 0; i < count; ++i) { + nsAutoString accesskey; + ChildAt(i)->ContentNode()->GetAttr(kNameSpaceID_None, + nsGkAtoms::accesskey, + accesskey); + const nsAutoString::char_type* key = accesskey.BeginReading(); + if (*key == chu ||* key == chl) { + found = ChildAt(i); + break; + } + } + + if (!found || found->Type() != nsMenuObject::eType_Menu) { + return NS_OK; + } + + ContentNode()->SetAttr(kNameSpaceID_None, nsNativeMenuAtoms::openedwithkey, + NS_LITERAL_STRING("true"), true); + static_cast(found)->OpenMenu(); + + aEvent->StopPropagation(); + aEvent->PreventDefault(); + + return NS_OK; +} + +nsresult +nsMenuBar::KeyDown(nsIDOMEvent* aEvent) { + if (!ShouldHandleKeyEvent(aEvent)) { + return NS_OK; + } + + nsCOMPtr keyEvent = do_QueryInterface(aEvent); + if (!keyEvent) { + return NS_OK; + } + + uint32_t keyCode; + keyEvent->GetKeyCode(&keyCode); + ModifierFlags modifiers = GetModifiersFromEvent(keyEvent); + if ((keyCode != mAccessKey) || ((modifiers & ~mAccessKeyMask) != 0)) { + return NS_OK; + } + + dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NOTICE); + + return NS_OK; +} + +nsresult +nsMenuBar::KeyUp(nsIDOMEvent* aEvent) { + if (!ShouldHandleKeyEvent(aEvent)) { + return NS_OK; + } + + nsCOMPtr keyEvent = do_QueryInterface(aEvent); + if (!keyEvent) { + return NS_OK; + } + + uint32_t keyCode; + keyEvent->GetKeyCode(&keyCode); + if (keyCode == mAccessKey) { + dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL); + } + + return NS_OK; +} + +void +nsMenuBar::HandleContentInserted(nsIContent* aChild, nsIContent* aPrevSibling) { + UniquePtr child = CreateChild(aChild); + + if (!child) { + return; + } + + InsertChildAfter(Move(child), aPrevSibling); +} + +void +nsMenuBar::HandleContentRemoved(nsIContent* aChild) { + RemoveChild(aChild); +} + +void +nsMenuBar::OnContentInserted(nsIContent* aContainer, nsIContent* aChild, + nsIContent* aPrevSibling) { + MOZ_ASSERT(aContainer == ContentNode(), + "Received an event that wasn't meant for us"); + + nsContentUtils::AddScriptRunner( + new nsMenuBarContentInsertedEvent(this, aChild, aPrevSibling)); +} + +void +nsMenuBar::OnContentRemoved(nsIContent* aContainer, nsIContent* aChild) { + MOZ_ASSERT(aContainer == ContentNode(), + "Received an event that wasn't meant for us"); + + nsContentUtils::AddScriptRunner( + new nsMenuBarContentRemovedEvent(this, aChild)); +} + +nsMenuBar::~nsMenuBar() { + nsNativeMenuService* service = nsNativeMenuService::GetSingleton(); + if (service) { + service->NotifyNativeMenuBarDestroyed(this); + } + + if (ContentNode()) { + SetShellShowingMenuBar(false); + } + + // We want to destroy all children before dropping our reference + // to the doc listener + while (ChildCount() > 0) { + RemoveChildAt(0); + } + + if (mTopLevel) { + g_object_unref(mTopLevel); + } + + if (DocListener()) { + DocListener()->Stop(); + } + + if (mDocument) { + DisconnectDocumentEventListeners(); + } + + if (mServer) { + g_object_unref(mServer); + } + + MOZ_COUNT_DTOR(nsMenuBar); +} + +/* static */ UniquePtr +nsMenuBar::Create(nsIWidget* aParent, nsIContent* aMenuBarNode) { + UniquePtr menubar(new nsMenuBar(aMenuBarNode)); + if (NS_FAILED(menubar->Init(aParent))) { + return nullptr; + } + + return Move(menubar); +} + +nsMenuObject::EType +nsMenuBar::Type() const { + return eType_MenuBar; +} + +bool +nsMenuBar::IsBeingDisplayed() const { + return true; +} + +uint32_t +nsMenuBar::WindowId() const { + return static_cast(GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel))); +} + +nsAdoptingCString +nsMenuBar::ObjectPath() const { + gchar* tmp; + g_object_get(mServer, DBUSMENU_SERVER_PROP_DBUS_OBJECT, &tmp, NULL); + nsAdoptingCString result(tmp); + + return result; +} + +void +nsMenuBar::Activate() { + if (mIsActive) { + return; + } + + mIsActive = true; + + mDocument->AddEventListener(NS_LITERAL_STRING("focus"), + mEventListener, + true); + mDocument->AddEventListener(NS_LITERAL_STRING("blur"), + mEventListener, + true); + mDocument->AddEventListener(NS_LITERAL_STRING("keypress"), + mEventListener, + false); + mDocument->AddEventListener(NS_LITERAL_STRING("keydown"), + mEventListener, + false); + mDocument->AddEventListener(NS_LITERAL_STRING("keyup"), + mEventListener, + false); + + // Clear this. Not sure if we really need to though + ContentNode()->SetAttr(kNameSpaceID_None, nsNativeMenuAtoms::openedwithkey, + NS_LITERAL_STRING("false"), true); + + DocListener()->Start(); + Build(); + SetShellShowingMenuBar(true); +} + +void +nsMenuBar::Deactivate() { + if (!mIsActive) { + return; + } + + mIsActive = false; + + SetShellShowingMenuBar(false); + while (ChildCount() > 0) { + RemoveChildAt(0); + } + DocListener()->Stop(); + DisconnectDocumentEventListeners(); +} diff --git a/widget/gtk/nsMenuBar.h b/widget/gtk/nsMenuBar.h new file mode 100644 index 000000000..9ce179651 --- /dev/null +++ b/widget/gtk/nsMenuBar.h @@ -0,0 +1,103 @@ +/* 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 __nsMenuBar_h__ +#define __nsMenuBar_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +#include "nsDbusmenu.h" +#include "nsMenuContainer.h" +#include "nsMenuObject.h" + +#include + +class nsIAtom; +class nsIContent; +class nsIDOMEvent; +class nsIDOMKeyEvent; +class nsIWidget; +class nsMenuBarDocEventListener; + +/* + * The menubar class. There is one of these per window (and the window + * owns its menubar). Each menubar has an object path, and the service is + * responsible for telling the desktop shell which object path corresponds + * to a particular window. A menubar and its hierarchy also own a + * nsNativeMenuDocListener. + */ +class nsMenuBar final : public nsMenuContainer { +public: + ~nsMenuBar() override; + + static mozilla::UniquePtr Create(nsIWidget* aParent, + nsIContent* aMenuBarNode); + + nsMenuObject::EType Type() const override; + + bool IsBeingDisplayed() const override; + + // Get the native window ID for this menubar + uint32_t WindowId() const; + + // Get the object path for this menubar + nsAdoptingCString ObjectPath() const; + + // Get the top-level GtkWindow handle + GtkWidget* TopLevelWindow() { return mTopLevel; } + + // Called from the menuservice when the menubar is about to be registered. + // Causes the native menubar to be created, and the XUL menubar to be hidden + void Activate(); + + // Called from the menuservice when the menubar is no longer registered + // with the desktop shell. Will cause the XUL menubar to be shown again + void Deactivate(); + +private: + class DocEventListener; + friend class nsMenuBarContentInsertedEvent; + friend class nsMenuBarContentRemovedEvent; + + enum ModifierFlags { + eModifierShift = (1 << 0), + eModifierCtrl = (1 << 1), + eModifierAlt = (1 << 2), + eModifierMeta = (1 << 3) + }; + + nsMenuBar(nsIContent* aMenuBarNode); + nsresult Init(nsIWidget* aParent); + void Build(); + void DisconnectDocumentEventListeners(); + void SetShellShowingMenuBar(bool aShowing); + void Focus(); + void Blur(); + ModifierFlags GetModifiersFromEvent(nsIDOMKeyEvent* aEvent); + nsresult Keypress(nsIDOMEvent* aEvent); + nsresult KeyDown(nsIDOMEvent* aEvent); + nsresult KeyUp(nsIDOMEvent* aEvent); + + void HandleContentInserted(nsIContent* aChild, + nsIContent* aPrevSibling); + void HandleContentRemoved(nsIContent* aChild); + + void OnContentInserted(nsIContent* aContainer, nsIContent* aChild, + nsIContent* aPrevSibling) override; + void OnContentRemoved(nsIContent* aContainer, nsIContent* aChild) override; + + GtkWidget* mTopLevel; + DbusmenuServer* mServer; + nsCOMPtr mDocument; + RefPtr mEventListener; + + uint32_t mAccessKey; + ModifierFlags mAccessKeyMask; + bool mIsActive; +}; + +#endif /* __nsMenuBar_h__ */ diff --git a/widget/gtk/nsMenuContainer.cpp b/widget/gtk/nsMenuContainer.cpp new file mode 100644 index 000000000..081e98a6a --- /dev/null +++ b/widget/gtk/nsMenuContainer.cpp @@ -0,0 +1,156 @@ +/* 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/DebugOnly.h" +#include "mozilla/Move.h" +#include "nsGkAtoms.h" +#include "nsIAtom.h" +#include "nsIContent.h" + +#include "nsDbusmenu.h" +#include "nsMenu.h" +#include "nsMenuItem.h" +#include "nsMenuSeparator.h" + +#include "nsMenuContainer.h" + +using namespace mozilla; + +const nsMenuContainer::ChildTArray::index_type nsMenuContainer::NoIndex = nsMenuContainer::ChildTArray::NoIndex; + +typedef UniquePtr (*nsMenuObjectConstructor)(nsMenuContainer*, + nsIContent*); + +template +static UniquePtr CreateMenuObject(nsMenuContainer* aContainer, + nsIContent* aContent) { + return UniquePtr(new T(aContainer, aContent)); +} + +static nsMenuObjectConstructor +GetMenuObjectConstructor(nsIContent* aContent) { + if (aContent->IsXULElement(nsGkAtoms::menuitem)) { + return CreateMenuObject; + } else if (aContent->IsXULElement(nsGkAtoms::menu)) { + return CreateMenuObject; + } else if (aContent->IsXULElement(nsGkAtoms::menuseparator)) { + return CreateMenuObject; + } + + return nullptr; +} + +static bool +ContentIsSupported(nsIContent* aContent) { + return GetMenuObjectConstructor(aContent) ? true : false; +} + +nsMenuContainer::nsMenuContainer(nsMenuContainer* aParent, + nsIContent* aContent) : + nsMenuObject(aParent, aContent) { +} + +nsMenuContainer::nsMenuContainer(nsNativeMenuDocListener* aListener, + nsIContent* aContent) : + nsMenuObject(aListener, aContent) { +} + +UniquePtr +nsMenuContainer::CreateChild(nsIContent* aContent) { + nsMenuObjectConstructor ctor = GetMenuObjectConstructor(aContent); + if (!ctor) { + // There are plenty of node types we might stumble across that + // aren't supported + return nullptr; + } + + UniquePtr res = ctor(this, aContent); + return Move(res); +} + +size_t +nsMenuContainer::IndexOf(nsIContent* aChild) const { + if (!aChild) { + return NoIndex; + } + + size_t count = ChildCount(); + for (size_t i = 0; i < count; ++i) { + if (ChildAt(i)->ContentNode() == aChild) { + return i; + } + } + + return NoIndex; +} + +void +nsMenuContainer::RemoveChildAt(size_t aIndex, bool aUpdateNative) { + MOZ_ASSERT(aIndex < ChildCount()); + + if (aUpdateNative) { + MOZ_ALWAYS_TRUE( + dbusmenu_menuitem_child_delete(GetNativeData(), + ChildAt(aIndex)->GetNativeData())); + } + + mChildren.RemoveElementAt(aIndex); +} + +void +nsMenuContainer::RemoveChild(nsIContent* aChild, bool aUpdateNative) { + size_t index = IndexOf(aChild); + if (index == NoIndex) { + return; + } + + RemoveChildAt(index, aUpdateNative); +} + +void +nsMenuContainer::InsertChildAfter(UniquePtr aChild, + nsIContent* aPrevSibling, + bool aUpdateNative) { + size_t index = IndexOf(aPrevSibling); + MOZ_ASSERT(!aPrevSibling || index != NoIndex); + + ++index; + + if (aUpdateNative) { + aChild->CreateNativeData(); + MOZ_ALWAYS_TRUE( + dbusmenu_menuitem_child_add_position(GetNativeData(), + aChild->GetNativeData(), + index)); + } + + MOZ_ALWAYS_TRUE(mChildren.InsertElementAt(index, Move(aChild))); +} + +void +nsMenuContainer::AppendChild(UniquePtr aChild, + bool aUpdateNative) { + if (aUpdateNative) { + aChild->CreateNativeData(); + MOZ_ALWAYS_TRUE( + dbusmenu_menuitem_child_append(GetNativeData(), + aChild->GetNativeData())); + } + + MOZ_ALWAYS_TRUE(mChildren.AppendElement(Move(aChild))); +} + +bool +nsMenuContainer::NeedsRebuild() const { + return false; +} + +/* static */ nsIContent* +nsMenuContainer::GetPreviousSupportedSibling(nsIContent* aContent) { + do { + aContent = aContent->GetPreviousSibling(); + } while (aContent && !ContentIsSupported(aContent)); + + return aContent; +} diff --git a/widget/gtk/nsMenuContainer.h b/widget/gtk/nsMenuContainer.h new file mode 100644 index 000000000..95d65a2f1 --- /dev/null +++ b/widget/gtk/nsMenuContainer.h @@ -0,0 +1,66 @@ +/* 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 __nsMenuContainer_h__ +#define __nsMenuContainer_h__ + +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + +#include "nsMenuObject.h" + +class nsIContent; +class nsNativeMenuDocListener; + +// Base class for containers (menus and menubars) +class nsMenuContainer : public nsMenuObject { +public: + typedef nsTArray > ChildTArray; + + // Determine if this container is being displayed on screen. Must be + // implemented by subclasses. Must return true if the container is + // in the fully open state, or false otherwise + virtual bool IsBeingDisplayed() const = 0; + + // Determine if this container will be rebuilt the next time it opens. + // Returns false by default but can be overridden by subclasses + virtual bool NeedsRebuild() const; + + // Return the first previous sibling that is of a type supported by the + // menu system + static nsIContent* GetPreviousSupportedSibling(nsIContent* aContent); + + static const ChildTArray::index_type NoIndex; + +protected: + nsMenuContainer(nsMenuContainer* aParent, nsIContent* aContent); + nsMenuContainer(nsNativeMenuDocListener* aListener, nsIContent* aContent); + + // Create a new child element for the specified content node + mozilla::UniquePtr CreateChild(nsIContent* aContent); + + // Return the index of the child for the specified content node + size_t IndexOf(nsIContent* aChild) const; + + size_t ChildCount() const { return mChildren.Length(); } + nsMenuObject* ChildAt(size_t aIndex) const { return mChildren[aIndex].get(); } + + void RemoveChildAt(size_t aIndex, bool aUpdateNative = true); + + // Remove the child that owns the specified content node + void RemoveChild(nsIContent* aChild, bool aUpdateNative = true); + + // Insert a new child after the child that owns the specified content node + void InsertChildAfter(mozilla::UniquePtr aChild, + nsIContent* aPrevSibling, + bool aUpdateNative = true); + + void AppendChild(mozilla::UniquePtr aChild, + bool aUpdateNative = true); + +private: + ChildTArray mChildren; +}; + +#endif /* __nsMenuContainer_h__ */ diff --git a/widget/gtk/nsMenuItem.cpp b/widget/gtk/nsMenuItem.cpp new file mode 100644 index 000000000..00cc5477c --- /dev/null +++ b/widget/gtk/nsMenuItem.cpp @@ -0,0 +1,712 @@ +/* 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/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/dom/Element.h" +#include "mozilla/Move.h" +#include "mozilla/Preferences.h" +#include "mozilla/TextEvents.h" +#include "nsAutoPtr.h" +#include "nsContentUtils.h" +#include "nsCRT.h" +#include "nsGkAtoms.h" +#include "nsGtkUtils.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIDOMEvent.h" +#include "nsIDOMEventTarget.h" +#include "nsIDOMKeyEvent.h" +#include "nsIDOMXULCommandEvent.h" +#include "nsIRunnable.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsStyleContext.h" +#include "nsThreadUtils.h" + +#include "nsMenu.h" +#include "nsMenuBar.h" +#include "nsMenuContainer.h" +#include "nsNativeMenuDocListener.h" + +#include +#include +#if (MOZ_WIDGET_GTK == 3) +#include +#endif +#include +#include + +#include "nsMenuItem.h" + +using namespace mozilla; + +struct KeyCodeData { + const char* str; + size_t strlength; + uint32_t keycode; +}; + +static struct KeyCodeData gKeyCodes[] = { +#define NS_DEFINE_VK(aDOMKeyName, aDOMKeyCode) \ + { #aDOMKeyName, sizeof(#aDOMKeyName) - 1, aDOMKeyCode }, +#include "mozilla/VirtualKeyCodeList.h" +#undef NS_DEFINE_VK + { nullptr, 0, 0 } +}; + +struct KeyPair { + uint32_t DOMKeyCode; + guint GDKKeyval; +}; + +// +// Netscape keycodes are defined in widget/public/nsGUIEvent.h +// GTK keycodes are defined in +// +static const KeyPair gKeyPairs[] = { + { NS_VK_CANCEL, GDK_Cancel }, + { NS_VK_BACK, GDK_BackSpace }, + { NS_VK_TAB, GDK_Tab }, + { NS_VK_TAB, GDK_ISO_Left_Tab }, + { NS_VK_CLEAR, GDK_Clear }, + { NS_VK_RETURN, GDK_Return }, + { NS_VK_SHIFT, GDK_Shift_L }, + { NS_VK_SHIFT, GDK_Shift_R }, + { NS_VK_SHIFT, GDK_Shift_Lock }, + { NS_VK_CONTROL, GDK_Control_L }, + { NS_VK_CONTROL, GDK_Control_R }, + { NS_VK_ALT, GDK_Alt_L }, + { NS_VK_ALT, GDK_Alt_R }, + { NS_VK_META, GDK_Meta_L }, + { NS_VK_META, GDK_Meta_R }, + + // Assume that Super or Hyper is always mapped to physical Win key. + { NS_VK_WIN, GDK_Super_L }, + { NS_VK_WIN, GDK_Super_R }, + { NS_VK_WIN, GDK_Hyper_L }, + { NS_VK_WIN, GDK_Hyper_R }, + + // GTK's AltGraph key is similar to Mac's Option (Alt) key. However, + // unfortunately, browsers on Mac are using NS_VK_ALT for it even though + // it's really different from Alt key on Windows. + // On the other hand, GTK's AltGrapsh keys are really different from + // Alt key. However, there is no AltGrapsh key on Windows. On Windows, + // both Ctrl and Alt keys are pressed internally when AltGr key is pressed. + // For some languages' users, AltGraph key is important, so, web + // applications on such locale may want to know AltGraph key press. + // Therefore, we should map AltGr keycode for them only on GTK. + { NS_VK_ALTGR, GDK_ISO_Level3_Shift }, + { NS_VK_ALTGR, GDK_ISO_Level5_Shift }, + // We assume that Mode_switch is always used for level3 shift. + { NS_VK_ALTGR, GDK_Mode_switch }, + + { NS_VK_PAUSE, GDK_Pause }, + { NS_VK_CAPS_LOCK, GDK_Caps_Lock }, + { NS_VK_KANA, GDK_Kana_Lock }, + { NS_VK_KANA, GDK_Kana_Shift }, + { NS_VK_HANGUL, GDK_Hangul }, + // { NS_VK_JUNJA, GDK_XXX }, + // { NS_VK_FINAL, GDK_XXX }, + { NS_VK_HANJA, GDK_Hangul_Hanja }, + { NS_VK_KANJI, GDK_Kanji }, + { NS_VK_ESCAPE, GDK_Escape }, + { NS_VK_CONVERT, GDK_Henkan }, + { NS_VK_NONCONVERT, GDK_Muhenkan }, + // { NS_VK_ACCEPT, GDK_XXX }, + // { NS_VK_MODECHANGE, GDK_XXX }, + { NS_VK_SPACE, GDK_space }, + { NS_VK_PAGE_UP, GDK_Page_Up }, + { NS_VK_PAGE_DOWN, GDK_Page_Down }, + { NS_VK_END, GDK_End }, + { NS_VK_HOME, GDK_Home }, + { NS_VK_LEFT, GDK_Left }, + { NS_VK_UP, GDK_Up }, + { NS_VK_RIGHT, GDK_Right }, + { NS_VK_DOWN, GDK_Down }, + { NS_VK_SELECT, GDK_Select }, + { NS_VK_PRINT, GDK_Print }, + { NS_VK_EXECUTE, GDK_Execute }, + { NS_VK_PRINTSCREEN, GDK_Print }, + { NS_VK_INSERT, GDK_Insert }, + { NS_VK_DELETE, GDK_Delete }, + { NS_VK_HELP, GDK_Help }, + + // keypad keys + { NS_VK_LEFT, GDK_KP_Left }, + { NS_VK_RIGHT, GDK_KP_Right }, + { NS_VK_UP, GDK_KP_Up }, + { NS_VK_DOWN, GDK_KP_Down }, + { NS_VK_PAGE_UP, GDK_KP_Page_Up }, + // Not sure what these are + //{ NS_VK_, GDK_KP_Prior }, + //{ NS_VK_, GDK_KP_Next }, + { NS_VK_CLEAR, GDK_KP_Begin }, // Num-unlocked 5 + { NS_VK_PAGE_DOWN, GDK_KP_Page_Down }, + { NS_VK_HOME, GDK_KP_Home }, + { NS_VK_END, GDK_KP_End }, + { NS_VK_INSERT, GDK_KP_Insert }, + { NS_VK_DELETE, GDK_KP_Delete }, + { NS_VK_RETURN, GDK_KP_Enter }, + + { NS_VK_NUM_LOCK, GDK_Num_Lock }, + { NS_VK_SCROLL_LOCK,GDK_Scroll_Lock }, + + // Function keys + { NS_VK_F1, GDK_F1 }, + { NS_VK_F2, GDK_F2 }, + { NS_VK_F3, GDK_F3 }, + { NS_VK_F4, GDK_F4 }, + { NS_VK_F5, GDK_F5 }, + { NS_VK_F6, GDK_F6 }, + { NS_VK_F7, GDK_F7 }, + { NS_VK_F8, GDK_F8 }, + { NS_VK_F9, GDK_F9 }, + { NS_VK_F10, GDK_F10 }, + { NS_VK_F11, GDK_F11 }, + { NS_VK_F12, GDK_F12 }, + { NS_VK_F13, GDK_F13 }, + { NS_VK_F14, GDK_F14 }, + { NS_VK_F15, GDK_F15 }, + { NS_VK_F16, GDK_F16 }, + { NS_VK_F17, GDK_F17 }, + { NS_VK_F18, GDK_F18 }, + { NS_VK_F19, GDK_F19 }, + { NS_VK_F20, GDK_F20 }, + { NS_VK_F21, GDK_F21 }, + { NS_VK_F22, GDK_F22 }, + { NS_VK_F23, GDK_F23 }, + { NS_VK_F24, GDK_F24 }, + + // context menu key, keysym 0xff67, typically keycode 117 on 105-key (Microsoft) + // x86 keyboards, located between right 'Windows' key and right Ctrl key + { NS_VK_CONTEXT_MENU, GDK_Menu }, + { NS_VK_SLEEP, GDK_Sleep }, + + { NS_VK_ATTN, GDK_3270_Attn }, + { NS_VK_CRSEL, GDK_3270_CursorSelect }, + { NS_VK_EXSEL, GDK_3270_ExSelect }, + { NS_VK_EREOF, GDK_3270_EraseEOF }, + { NS_VK_PLAY, GDK_3270_Play }, + //{ NS_VK_ZOOM, GDK_XXX }, + { NS_VK_PA1, GDK_3270_PA1 }, +}; + +static guint +ConvertGeckoKeyNameToGDKKeyval(nsAString& aKeyName) { + NS_ConvertUTF16toUTF8 keyName(aKeyName); + ToUpperCase(keyName); // We want case-insensitive comparison with data + // stored as uppercase. + + uint32_t keyCode = 0; + + uint32_t keyNameLength = keyName.Length(); + const char* keyNameStr = keyName.get(); + for (uint16_t i = 0; i < ArrayLength(gKeyCodes); ++i) { + if (keyNameLength == gKeyCodes[i].strlength && + !nsCRT::strcmp(gKeyCodes[i].str, keyNameStr)) { + keyCode = gKeyCodes[i].keycode; + break; + } + } + + // First, try to handle alphanumeric input, not listed in nsKeycodes: + // most likely, more letters will be getting typed in than things in + // the key list, so we will look through these first. + + if (keyCode >= NS_VK_A && keyCode <= NS_VK_Z) { + // gdk and DOM both use the ASCII codes for these keys. + return keyCode; + } + + // numbers + if (keyCode >= NS_VK_0 && keyCode <= NS_VK_9) { + // gdk and DOM both use the ASCII codes for these keys. + return keyCode - NS_VK_0 + GDK_0; + } + + switch (keyCode) { + // keys in numpad + case NS_VK_MULTIPLY: return GDK_KP_Multiply; + case NS_VK_ADD: return GDK_KP_Add; + case NS_VK_SEPARATOR: return GDK_KP_Separator; + case NS_VK_SUBTRACT: return GDK_KP_Subtract; + case NS_VK_DECIMAL: return GDK_KP_Decimal; + case NS_VK_DIVIDE: return GDK_KP_Divide; + case NS_VK_NUMPAD0: return GDK_KP_0; + case NS_VK_NUMPAD1: return GDK_KP_1; + case NS_VK_NUMPAD2: return GDK_KP_2; + case NS_VK_NUMPAD3: return GDK_KP_3; + case NS_VK_NUMPAD4: return GDK_KP_4; + case NS_VK_NUMPAD5: return GDK_KP_5; + case NS_VK_NUMPAD6: return GDK_KP_6; + case NS_VK_NUMPAD7: return GDK_KP_7; + case NS_VK_NUMPAD8: return GDK_KP_8; + case NS_VK_NUMPAD9: return GDK_KP_9; + // other prinable keys + case NS_VK_SPACE: return GDK_space; + case NS_VK_COLON: return GDK_colon; + case NS_VK_SEMICOLON: return GDK_semicolon; + case NS_VK_LESS_THAN: return GDK_less; + case NS_VK_EQUALS: return GDK_equal; + case NS_VK_GREATER_THAN: return GDK_greater; + case NS_VK_QUESTION_MARK: return GDK_question; + case NS_VK_AT: return GDK_at; + case NS_VK_CIRCUMFLEX: return GDK_asciicircum; + case NS_VK_EXCLAMATION: return GDK_exclam; + case NS_VK_DOUBLE_QUOTE: return GDK_quotedbl; + case NS_VK_HASH: return GDK_numbersign; + case NS_VK_DOLLAR: return GDK_dollar; + case NS_VK_PERCENT: return GDK_percent; + case NS_VK_AMPERSAND: return GDK_ampersand; + case NS_VK_UNDERSCORE: return GDK_underscore; + case NS_VK_OPEN_PAREN: return GDK_parenleft; + case NS_VK_CLOSE_PAREN: return GDK_parenright; + case NS_VK_ASTERISK: return GDK_asterisk; + case NS_VK_PLUS: return GDK_plus; + case NS_VK_PIPE: return GDK_bar; + case NS_VK_HYPHEN_MINUS: return GDK_minus; + case NS_VK_OPEN_CURLY_BRACKET: return GDK_braceleft; + case NS_VK_CLOSE_CURLY_BRACKET: return GDK_braceright; + case NS_VK_TILDE: return GDK_asciitilde; + case NS_VK_COMMA: return GDK_comma; + case NS_VK_PERIOD: return GDK_period; + case NS_VK_SLASH: return GDK_slash; + case NS_VK_BACK_QUOTE: return GDK_grave; + case NS_VK_OPEN_BRACKET: return GDK_bracketleft; + case NS_VK_BACK_SLASH: return GDK_backslash; + case NS_VK_CLOSE_BRACKET: return GDK_bracketright; + case NS_VK_QUOTE: return GDK_apostrophe; + } + + // misc other things + for (uint32_t i = 0; i < ArrayLength(gKeyPairs); ++i) { + if (gKeyPairs[i].DOMKeyCode == keyCode) { + return gKeyPairs[i].GDKKeyval; + } + } + + return 0; +} + +class nsMenuItemUncheckSiblingsRunnable final : public Runnable { +public: + NS_IMETHODIMP Run() { + if (mMenuItem) { + static_cast(mMenuItem.get())->UncheckSiblings(); + } + return NS_OK; + } + + nsMenuItemUncheckSiblingsRunnable(nsMenuItem* aMenuItem) : + mMenuItem(aMenuItem) { }; + +private: + nsWeakMenuObject mMenuItem; +}; + +bool +nsMenuItem::IsCheckboxOrRadioItem() const { + return mType == eMenuItemType_Radio || + mType == eMenuItemType_CheckBox; +} + +/* static */ void +nsMenuItem::item_activated_cb(DbusmenuMenuitem* menuitem, + guint timestamp, + gpointer user_data) { + nsMenuItem* item = static_cast(user_data); + item->Activate(timestamp); +} + +void +nsMenuItem::Activate(uint32_t aTimestamp) { + GdkWindow* window = gtk_widget_get_window(MenuBar()->TopLevelWindow()); + gdk_x11_window_set_user_time( + window, std::min(aTimestamp, gdk_x11_get_server_time(window))); + + // We do this to avoid mutating our view of the menu until + // after we have finished + nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker; + + if (!ContentNode()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck, + nsGkAtoms::_false, eCaseMatters) && + (mType == eMenuItemType_CheckBox || + (mType == eMenuItemType_Radio && !mIsChecked))) { + ContentNode()->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, + mIsChecked ? + NS_LITERAL_STRING("false") : NS_LITERAL_STRING("true"), + true); + } + + nsIDocument* doc = ContentNode()->OwnerDoc(); + nsCOMPtr target = do_QueryInterface(ContentNode()); + nsCOMPtr domDoc = do_QueryInterface(doc); + if (domDoc && target) { + nsCOMPtr event; + domDoc->CreateEvent(NS_LITERAL_STRING("xulcommandevent"), + getter_AddRefs(event)); + nsCOMPtr command = do_QueryInterface(event); + if (command) { + command->InitCommandEvent(NS_LITERAL_STRING("command"), + true, true, doc->GetInnerWindow(), 0, + false, false, false, false, nullptr); + + event->SetTrusted(true); + bool dummy; + target->DispatchEvent(event, &dummy); + } + } + + // This kinda sucks, but Unity doesn't send a closed event + // after activating a menuitem + nsMenuObject* ancestor = Parent(); + while (ancestor && ancestor->Type() == eType_Menu) { + static_cast(ancestor)->OnClose(); + ancestor = ancestor->Parent(); + } +} + +void +nsMenuItem::CopyAttrFromNodeIfExists(nsIContent* aContent, nsIAtom* aAttribute) { + nsAutoString value; + if (aContent->GetAttr(kNameSpaceID_None, aAttribute, value)) { + ContentNode()->SetAttr(kNameSpaceID_None, aAttribute, value, true); + } +} + +void +nsMenuItem::UpdateState() { + if (!IsCheckboxOrRadioItem()) { + return; + } + + mIsChecked = ContentNode()->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::checked, + nsGkAtoms::_true, + eCaseMatters); + dbusmenu_menuitem_property_set_int(GetNativeData(), + DBUSMENU_MENUITEM_PROP_TOGGLE_STATE, + mIsChecked ? + DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED : + DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED); +} + +void +nsMenuItem::UpdateTypeAndState() { + static nsIContent::AttrValuesArray attrs[] = + { &nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr }; + int32_t type = ContentNode()->FindAttrValueIn(kNameSpaceID_None, + nsGkAtoms::type, + attrs, eCaseMatters); + + if (type >= 0 && type < 2) { + if (type == 0) { + dbusmenu_menuitem_property_set(GetNativeData(), + DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE, + DBUSMENU_MENUITEM_TOGGLE_CHECK); + mType = eMenuItemType_CheckBox; + } else if (type == 1) { + dbusmenu_menuitem_property_set(GetNativeData(), + DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE, + DBUSMENU_MENUITEM_TOGGLE_RADIO); + mType = eMenuItemType_Radio; + } + + UpdateState(); + } else { + dbusmenu_menuitem_property_remove(GetNativeData(), + DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE); + dbusmenu_menuitem_property_remove(GetNativeData(), + DBUSMENU_MENUITEM_PROP_TOGGLE_STATE); + mType = eMenuItemType_Normal; + } +} + +void +nsMenuItem::UpdateAccel() { + nsIDocument* doc = ContentNode()->GetUncomposedDoc(); + if (doc) { + nsCOMPtr oldKeyContent; + oldKeyContent.swap(mKeyContent); + + nsAutoString key; + ContentNode()->GetAttr(kNameSpaceID_None, nsGkAtoms::key, key); + if (!key.IsEmpty()) { + mKeyContent = doc->GetElementById(key); + } + + if (mKeyContent != oldKeyContent) { + if (oldKeyContent) { + DocListener()->UnregisterForContentChanges(oldKeyContent); + } + if (mKeyContent) { + DocListener()->RegisterForContentChanges(mKeyContent, this); + } + } + } + + if (!mKeyContent) { + dbusmenu_menuitem_property_remove(GetNativeData(), + DBUSMENU_MENUITEM_PROP_SHORTCUT); + return; + } + + nsAutoString modifiers; + mKeyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers); + + uint32_t modifier = 0; + + if (!modifiers.IsEmpty()) { + char* str = ToNewUTF8String(modifiers); + char* token = strtok(str, ", \t"); + while(token) { + if (nsCRT::strcmp(token, "shift") == 0) { + modifier |= GDK_SHIFT_MASK; + } else if (nsCRT::strcmp(token, "alt") == 0) { + modifier |= GDK_MOD1_MASK; + } else if (nsCRT::strcmp(token, "meta") == 0) { + modifier |= GDK_META_MASK; + } else if (nsCRT::strcmp(token, "control") == 0) { + modifier |= GDK_CONTROL_MASK; + } else if (nsCRT::strcmp(token, "accel") == 0) { + int32_t accel = Preferences::GetInt("ui.key.accelKey"); + if (accel == nsIDOMKeyEvent::DOM_VK_META) { + modifier |= GDK_META_MASK; + } else if (accel == nsIDOMKeyEvent::DOM_VK_ALT) { + modifier |= GDK_MOD1_MASK; + } else { + modifier |= GDK_CONTROL_MASK; + } + } + + token = strtok(nullptr, ", \t"); + } + + free(str); + } + + nsAutoString keyStr; + mKeyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyStr); + + guint key = 0; + if (!keyStr.IsEmpty()) { + key = gdk_unicode_to_keyval(*keyStr.BeginReading()); + } + + if (key == 0) { + mKeyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyStr); + if (!keyStr.IsEmpty()) { + key = ConvertGeckoKeyNameToGDKKeyval(keyStr); + } + } + + if (key == 0) { + key = GDK_VoidSymbol; + } + + if (key != GDK_VoidSymbol) { + dbusmenu_menuitem_property_set_shortcut(GetNativeData(), key, + static_cast(modifier)); + } else { + dbusmenu_menuitem_property_remove(GetNativeData(), + DBUSMENU_MENUITEM_PROP_SHORTCUT); + } +} + +nsMenuBar* +nsMenuItem::MenuBar() { + nsMenuObject* tmp = this; + while (tmp->Parent()) { + tmp = tmp->Parent(); + } + + MOZ_ASSERT(tmp->Type() == eType_MenuBar, "The top-level should be a menubar"); + + return static_cast(tmp); +} + +void +nsMenuItem::UncheckSiblings() { + if (!ContentNode()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::radio, eCaseMatters)) { + // If we're not a radio button, we don't care + return; + } + + nsAutoString name; + ContentNode()->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name); + + nsIContent* parent = ContentNode()->GetParent(); + if (!parent) { + return; + } + + uint32_t count = parent->GetChildCount(); + for (uint32_t i = 0; i < count; ++i) { + nsIContent* sibling = parent->GetChildAt(i); + + nsAutoString otherName; + sibling->GetAttr(kNameSpaceID_None, nsGkAtoms::name, otherName); + + if (sibling != ContentNode() && otherName == name && + sibling->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::radio, eCaseMatters)) { + sibling->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true); + } + } +} + +void +nsMenuItem::InitializeNativeData() { + g_signal_connect(G_OBJECT(GetNativeData()), + DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, + G_CALLBACK(item_activated_cb), this); + mNeedsUpdate = true; +} + +void +nsMenuItem::UpdateContentAttributes() { + nsIDocument* doc = ContentNode()->GetUncomposedDoc(); + if (!doc) { + return; + } + + nsAutoString command; + ContentNode()->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command); + if (command.IsEmpty()) { + return; + } + + nsCOMPtr commandContent = doc->GetElementById(command); + if (!commandContent) { + return; + } + + if (commandContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, + nsGkAtoms::_true, eCaseMatters)) { + ContentNode()->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, + NS_LITERAL_STRING("true"), true); + } else { + ContentNode()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true); + } + + CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::checked); + CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::accesskey); + CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::label); + CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::hidden); +} + +void +nsMenuItem::Update(nsStyleContext* aStyleContext) { + if (mNeedsUpdate) { + mNeedsUpdate = false; + + UpdateTypeAndState(); + UpdateAccel(); + UpdateLabel(); + UpdateSensitivity(); + } + + UpdateVisibility(aStyleContext); + UpdateIcon(aStyleContext); +} + +bool +nsMenuItem::IsCompatibleWithNativeData(DbusmenuMenuitem* aNativeData) const { + return nsCRT::strcmp(dbusmenu_menuitem_property_get(aNativeData, + DBUSMENU_MENUITEM_PROP_TYPE), + "separator") != 0; +} + +nsMenuObject::PropertyFlags +nsMenuItem::SupportedProperties() const { + return static_cast( + nsMenuObject::ePropLabel | + nsMenuObject::ePropEnabled | + nsMenuObject::ePropVisible | + nsMenuObject::ePropIconData | + nsMenuObject::ePropShortcut | + nsMenuObject::ePropToggleType | + nsMenuObject::ePropToggleState + ); +} + +void +nsMenuItem::OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) { + MOZ_ASSERT(aContent == ContentNode() || aContent == mKeyContent, + "Received an event that wasn't meant for us!"); + + if (aContent == ContentNode() && aAttribute == nsGkAtoms::checked && + aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked, + nsGkAtoms::_true, eCaseMatters)) { + nsContentUtils::AddScriptRunner( + new nsMenuItemUncheckSiblingsRunnable(this)); + } + + if (mNeedsUpdate) { + return; + } + + if (!Parent()->IsBeingDisplayed()) { + mNeedsUpdate = true; + return; + } + + if (aContent == ContentNode()) { + if (aAttribute == nsGkAtoms::key) { + UpdateAccel(); + } else if (aAttribute == nsGkAtoms::label || + aAttribute == nsGkAtoms::accesskey || + aAttribute == nsGkAtoms::crop) { + UpdateLabel(); + } else if (aAttribute == nsGkAtoms::disabled) { + UpdateSensitivity(); + } else if (aAttribute == nsGkAtoms::type) { + UpdateTypeAndState(); + } else if (aAttribute == nsGkAtoms::checked) { + UpdateState(); + } else if (aAttribute == nsGkAtoms::hidden || + aAttribute == nsGkAtoms::collapsed) { + RefPtr sc = GetStyleContext(); + UpdateVisibility(sc); + } else if (aAttribute == nsGkAtoms::image) { + RefPtr sc = GetStyleContext(); + UpdateIcon(sc); + } + } else if (aContent == mKeyContent && + (aAttribute == nsGkAtoms::key || + aAttribute == nsGkAtoms::keycode || + aAttribute == nsGkAtoms::modifiers)) { + UpdateAccel(); + } +} + +nsMenuItem::nsMenuItem(nsMenuContainer* aParent, nsIContent* aContent) : + nsMenuObject(aParent, aContent), + mType(eMenuItemType_Normal), + mIsChecked(false), + mNeedsUpdate(false) { + MOZ_COUNT_CTOR(nsMenuItem); +} + +nsMenuItem::~nsMenuItem() { + if (DocListener() && mKeyContent) { + DocListener()->UnregisterForContentChanges(mKeyContent); + } + + if (GetNativeData()) { + g_signal_handlers_disconnect_by_func(GetNativeData(), + FuncToGpointer(item_activated_cb), + this); + } + + MOZ_COUNT_DTOR(nsMenuItem); +} + +nsMenuObject::EType +nsMenuItem::Type() const { + return eType_MenuItem; +} diff --git a/widget/gtk/nsMenuItem.h b/widget/gtk/nsMenuItem.h new file mode 100644 index 000000000..e81b6e308 --- /dev/null +++ b/widget/gtk/nsMenuItem.h @@ -0,0 +1,77 @@ +/* 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 __nsMenuItem_h__ +#define __nsMenuItem_h__ + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" + +#include "nsDbusmenu.h" +#include "nsMenuObject.h" + +#include + +class nsIAtom; +class nsIContent; +class nsStyleContext; +class nsMenuBar; +class nsMenuContainer; + +/* + * This class represents 3 main classes of menuitems: labels, checkboxes and + * radio buttons (with/without an icon) + */ +class nsMenuItem final : public nsMenuObject { +public: + nsMenuItem(nsMenuContainer* aParent, nsIContent* aContent); + ~nsMenuItem() override; + + nsMenuObject::EType Type() const override; + +private: + friend class nsMenuItemUncheckSiblingsRunnable; + + enum { + eMenuItemFlag_ToggleState = (1 << 0) + }; + + enum EMenuItemType { + eMenuItemType_Normal, + eMenuItemType_Radio, + eMenuItemType_CheckBox + }; + + bool IsCheckboxOrRadioItem() const; + + static void item_activated_cb(DbusmenuMenuitem* menuitem, + guint timestamp, + gpointer user_data); + void Activate(uint32_t aTimestamp); + + void CopyAttrFromNodeIfExists(nsIContent* aContent, nsIAtom* aAtom); + void UpdateState(); + void UpdateTypeAndState(); + void UpdateAccel(); + nsMenuBar* MenuBar(); + void UncheckSiblings(); + + void InitializeNativeData() override; + void UpdateContentAttributes() override; + void Update(nsStyleContext* aStyleContext) override; + bool IsCompatibleWithNativeData(DbusmenuMenuitem* aNativeData) const override; + nsMenuObject::PropertyFlags SupportedProperties() const override; + + void OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) override; + + EMenuItemType mType; + + bool mIsChecked; + + bool mNeedsUpdate; + + nsCOMPtr mKeyContent; +}; + +#endif /* __nsMenuItem_h__ */ diff --git a/widget/gtk/nsMenuObject.cpp b/widget/gtk/nsMenuObject.cpp new file mode 100644 index 000000000..58d1716fd --- /dev/null +++ b/widget/gtk/nsMenuObject.cpp @@ -0,0 +1,634 @@ +/* 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 "ImageOps.h" +#include "imgIContainer.h" +#include "imgINotificationObserver.h" +#include "imgLoader.h" +#include "imgRequestProxy.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/dom/Element.h" +#include "mozilla/Preferences.h" +#include "nsAttrValue.h" +#include "nsComputedDOMStyle.h" +#include "nsContentUtils.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "nsIContentPolicy.h" +#include "nsIDocument.h" +#include "nsILoadGroup.h" +#include "nsImageToPixbuf.h" +#include "nsIPresShell.h" +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "nsPresContext.h" +#include "nsRect.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsStyleConsts.h" +#include "nsStyleContext.h" +#include "nsStyleStruct.h" +#include "nsUnicharUtils.h" + +#include "nsMenuContainer.h" +#include "nsNativeMenuAtoms.h" +#include "nsNativeMenuDocListener.h" + +#include +#include +#include + +#include "nsMenuObject.h" + +// X11's None clashes with StyleDisplay::None +#include "X11UndefineNone.h" + +#undef None + +using namespace mozilla; +using mozilla::image::ImageOps; + +#define MAX_WIDTH 350000 + +const char* gPropertyStrings[] = { +#define DBUSMENU_PROPERTY(e, s, b) s, + DBUSMENU_PROPERTIES +#undef DBUSMENU_PROPERTY + nullptr +}; + +nsWeakMenuObject* nsWeakMenuObject::sHead; +PangoLayout* gPangoLayout = nullptr; + +class nsMenuObjectIconLoader final : public imgINotificationObserver { +public: + NS_DECL_ISUPPORTS + NS_DECL_IMGINOTIFICATIONOBSERVER + + nsMenuObjectIconLoader(nsMenuObject* aOwner) : mOwner(aOwner) { }; + + void LoadIcon(nsStyleContext* aStyleContext); + void Destroy(); + +private: + ~nsMenuObjectIconLoader() { }; + + nsMenuObject* mOwner; + RefPtr mImageRequest; + nsCOMPtr mURI; + nsIntRect mImageRect; +}; + +NS_IMPL_ISUPPORTS(nsMenuObjectIconLoader, imgINotificationObserver) + +NS_IMETHODIMP +nsMenuObjectIconLoader::Notify(imgIRequest* aProxy, + int32_t aType, const nsIntRect* aRect) { + if (!mOwner) { + return NS_OK; + } + + if (aProxy != mImageRequest) { + return NS_ERROR_FAILURE; + } + + if (aType == imgINotificationObserver::LOAD_COMPLETE) { + uint32_t status = imgIRequest::STATUS_ERROR; + if (NS_FAILED(mImageRequest->GetImageStatus(&status)) || + (status & imgIRequest::STATUS_ERROR)) { + mImageRequest->Cancel(NS_BINDING_ABORTED); + mImageRequest = nullptr; + return NS_ERROR_FAILURE; + } + + nsCOMPtr image; + mImageRequest->GetImage(getter_AddRefs(image)); + MOZ_ASSERT(image); + + // Ask the image to decode at its intrinsic size. + int32_t width = 0, height = 0; + image->GetWidth(&width); + image->GetHeight(&height); + image->RequestDecodeForSize(nsIntSize(width, height), imgIContainer::FLAG_NONE); + return NS_OK; + } + + if (aType == imgINotificationObserver::DECODE_COMPLETE) { + mImageRequest->Cancel(NS_BINDING_ABORTED); + mImageRequest = nullptr; + return NS_OK; + } + + if (aType != imgINotificationObserver::FRAME_COMPLETE) { + return NS_OK; + } + + nsCOMPtr img; + mImageRequest->GetImage(getter_AddRefs(img)); + if (!img) { + return NS_ERROR_FAILURE; + } + + if (!mImageRect.IsEmpty()) { + img = ImageOps::Clip(img, mImageRect); + } + + int32_t width, height; + img->GetWidth(&width); + img->GetHeight(&height); + + if (width <= 0 || height <= 0) { + mOwner->ClearIcon(); + return NS_OK; + } + + if (width > 100 || height > 100) { + // The icon data needs to go across DBus. Make sure the icon + // data isn't too large, else our connection gets terminated and + // GDbus helpfully aborts the application. Thank you :) + NS_WARNING("Icon data too large"); + mOwner->ClearIcon(); + return NS_OK; + } + + GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(img); + if (pixbuf) { + dbusmenu_menuitem_property_set_image(mOwner->GetNativeData(), + DBUSMENU_MENUITEM_PROP_ICON_DATA, + pixbuf); + g_object_unref(pixbuf); + } + + return NS_OK; +} + +void +nsMenuObjectIconLoader::LoadIcon(nsStyleContext* aStyleContext) { + nsIDocument* doc = mOwner->ContentNode()->OwnerDoc(); + + nsCOMPtr uri; + nsIntRect imageRect; + imgRequestProxy* imageRequest = nullptr; + + nsAutoString uriString; + if (mOwner->ContentNode()->GetAttr(kNameSpaceID_None, nsGkAtoms::image, + uriString)) { + NS_NewURI(getter_AddRefs(uri), uriString); + } else { + nsIPresShell* shell = doc->GetShell(); + if (!shell) { + return; + } + + nsPresContext* pc = shell->GetPresContext(); + if (!pc || !aStyleContext) { + return; + } + + const nsStyleList* list = aStyleContext->StyleList(); + imageRequest = list->GetListStyleImage(); + if (imageRequest) { + imageRequest->GetURI(getter_AddRefs(uri)); + imageRect = list->mImageRegion.ToNearestPixels( + pc->AppUnitsPerDevPixel()); + } + } + + if (!uri) { + mOwner->ClearIcon(); + mURI = nullptr; + + if (mImageRequest) { + mImageRequest->Cancel(NS_BINDING_ABORTED); + mImageRequest = nullptr; + } + + return; + } + + bool same; + if (mURI && NS_SUCCEEDED(mURI->Equals(uri, &same)) && same && + (!imageRequest || imageRect == mImageRect)) { + return; + } + + if (mImageRequest) { + mImageRequest->Cancel(NS_BINDING_ABORTED); + mImageRequest = nullptr; + } + + mURI = uri; + + if (imageRequest) { + mImageRect = imageRect; + imageRequest->Clone(this, getter_AddRefs(mImageRequest)); + } else { + mImageRect.SetEmpty(); + nsCOMPtr loadGroup = doc->GetDocumentLoadGroup(); + RefPtr loader = + nsContentUtils::GetImgLoaderForDocument(doc); + if (!loader || !loadGroup) { + NS_WARNING("Failed to get loader or load group for image load"); + return; + } + + loader->LoadImage(uri, nullptr, nullptr, mozilla::net::RP_Unset, + nullptr, loadGroup, this, nullptr, nullptr, + nsIRequest::LOAD_NORMAL, nullptr, + nsIContentPolicy::TYPE_IMAGE, EmptyString(), + getter_AddRefs(mImageRequest)); + } +} + +void +nsMenuObjectIconLoader::Destroy() { + if (mImageRequest) { + mImageRequest->CancelAndForgetObserver(NS_BINDING_ABORTED); + mImageRequest = nullptr; + } + + mOwner = nullptr; +} + +static int +CalculateTextWidth(const nsAString& aText) { + if (!gPangoLayout) { + PangoFontMap* fontmap = pango_cairo_font_map_get_default(); + PangoContext* ctx = pango_font_map_create_context(fontmap); + gPangoLayout = pango_layout_new(ctx); + g_object_unref(ctx); + } + + pango_layout_set_text(gPangoLayout, NS_ConvertUTF16toUTF8(aText).get(), -1); + + int width, dummy; + pango_layout_get_size(gPangoLayout, &width, &dummy); + + return width; +} + +static const nsDependentString +GetEllipsis() { + static char16_t sBuf[4] = { 0, 0, 0, 0 }; + if (!sBuf[0]) { + nsAdoptingString ellipsis = Preferences::GetLocalizedString("intl.ellipsis"); + if (!ellipsis.IsEmpty()) { + uint32_t l = ellipsis.Length(); + const nsAdoptingString::char_type* c = ellipsis.BeginReading(); + uint32_t i = 0; + while (i < 3 && i < l) { + sBuf[i++] =* (c++); + } + } else { + sBuf[0] = '.'; + sBuf[1] = '.'; + sBuf[2] = '.'; + } + } + + return nsDependentString(sBuf); +} + +static int +GetEllipsisWidth() { + static int sEllipsisWidth = -1; + + if (sEllipsisWidth == -1) { + sEllipsisWidth = CalculateTextWidth(GetEllipsis()); + } + + return sEllipsisWidth; +} + +nsMenuObject::nsMenuObject(nsMenuContainer* aParent, nsIContent* aContent) : + mContent(aContent), + mListener(aParent->DocListener()), + mParent(aParent), + mNativeData(nullptr) { + MOZ_ASSERT(mContent); + MOZ_ASSERT(mListener); + MOZ_ASSERT(mParent); +} + +nsMenuObject::nsMenuObject(nsNativeMenuDocListener* aListener, + nsIContent* aContent) : + mContent(aContent), + mListener(aListener), + mParent(nullptr), + mNativeData(nullptr) { + MOZ_ASSERT(mContent); + MOZ_ASSERT(mListener); +} + +void +nsMenuObject::UpdateLabel() { + // Goanna stores the label and access key in separate attributes + // so we need to convert label="Foo_Bar"/accesskey="F" in to + // label="_Foo__Bar" for dbusmenu + + nsAutoString label; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label); + + nsAutoString accesskey; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accesskey); + + const nsAutoString::char_type* akey = accesskey.BeginReading(); + char16_t keyLower = ToLowerCase(*akey); + char16_t keyUpper = ToUpperCase(*akey); + + const nsAutoString::char_type* iter = label.BeginReading(); + const nsAutoString::char_type* end = label.EndReading(); + uint32_t length = label.Length(); + uint32_t pos = 0; + bool foundAccessKey = false; + + while (iter != end) { + if (*iter != char16_t('_')) { + if ((*iter != keyLower &&* iter != keyUpper) || foundAccessKey) { + ++iter; + ++pos; + continue; + } + foundAccessKey = true; + } + + label.SetLength(++length); + + iter = label.BeginReading() + pos; + end = label.EndReading(); + nsAutoString::char_type* cur = label.BeginWriting() + pos; + + memmove(cur + 1, cur, (length - 1 - pos)* sizeof(nsAutoString::char_type)); + * cur = nsAutoString::char_type('_'); + + iter += 2; + pos += 2; + } + + if (CalculateTextWidth(label) <= MAX_WIDTH) { + dbusmenu_menuitem_property_set(mNativeData, + DBUSMENU_MENUITEM_PROP_LABEL, + NS_ConvertUTF16toUTF8(label).get()); + return; + } + + // This sucks. + // This should be done at the point where the menu is drawn (hello Unity), + // but unfortunately it doesn't do that and will happily fill your entire + // screen width with a menu if you have a bookmark with a really long title. + // This leaves us with no other option but to ellipsize here, with no proper + // knowledge of Unity's render path, font size etc. This is better than nothing + nsAutoString truncated; + int target = MAX_WIDTH - GetEllipsisWidth(); + length = label.Length(); + + static nsIContent::AttrValuesArray strings[] = { + &nsGkAtoms::left, &nsGkAtoms::start, + &nsGkAtoms::center, &nsGkAtoms::right, + &nsGkAtoms::end, nullptr + }; + + int32_t type = mContent->FindAttrValueIn(kNameSpaceID_None, + nsGkAtoms::crop, + strings, eCaseMatters); + + switch (type) { + case 0: + case 1: + // FIXME: Implement left cropping + case 2: + // FIXME: Implement center cropping + case 3: + case 4: + default: + for (uint32_t i = 0; i < length; i++) { + truncated.Append(label.CharAt(i)); + if (CalculateTextWidth(truncated) > target) { + break; + } + } + + truncated.Append(GetEllipsis()); + } + + dbusmenu_menuitem_property_set(mNativeData, + DBUSMENU_MENUITEM_PROP_LABEL, + NS_ConvertUTF16toUTF8(truncated).get()); +} + +void +nsMenuObject::UpdateVisibility(nsStyleContext* aStyleContext) { + bool vis = true; + + if (aStyleContext && + (aStyleContext->StyleDisplay()->mDisplay == StyleDisplay::None || + aStyleContext->StyleVisibility()->mVisible == + NS_STYLE_VISIBILITY_COLLAPSE)) { + vis = false; + } + + dbusmenu_menuitem_property_set_bool(mNativeData, + DBUSMENU_MENUITEM_PROP_VISIBLE, + vis); +} + +void +nsMenuObject::UpdateSensitivity() { + bool disabled = mContent->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::disabled, + nsGkAtoms::_true, eCaseMatters); + + dbusmenu_menuitem_property_set_bool(mNativeData, + DBUSMENU_MENUITEM_PROP_ENABLED, + !disabled); + +} + +void +nsMenuObject::UpdateIcon(nsStyleContext* aStyleContext) { + if (ShouldShowIcon()) { + if (!mIconLoader) { + mIconLoader = new nsMenuObjectIconLoader(this); + } + + mIconLoader->LoadIcon(aStyleContext); + } else { + if (mIconLoader) { + mIconLoader->Destroy(); + mIconLoader = nullptr; + } + + ClearIcon(); + } +} + +already_AddRefed +nsMenuObject::GetStyleContext() { + nsIPresShell* shell = mContent->OwnerDoc()->GetShell(); + if (!shell) { + return nullptr; + } + + RefPtr sc = + nsComputedDOMStyle::GetStyleContextForElementNoFlush( + mContent->AsElement(), nullptr, shell); + + return sc.forget(); +} + +void +nsMenuObject::InitializeNativeData() { +} + +nsMenuObject::PropertyFlags +nsMenuObject::SupportedProperties() const { + return static_cast(0); +} + +bool +nsMenuObject::IsCompatibleWithNativeData(DbusmenuMenuitem* aNativeData) const { + return true; +} + +void +nsMenuObject::UpdateContentAttributes() { +} + +void +nsMenuObject::Update(nsStyleContext* aStyleContext) { +} + +bool +nsMenuObject::ShouldShowIcon() const { + // Ideally we want to know the visibility of the anonymous XUL image in + // our menuitem, but this isn't created because we don't have a frame. + // The following works by default (because xul.css hides images in menuitems + // that don't have the "menuitem-with-favicon" class). It's possible a third + // party theme could override this, but, oh well... + const nsAttrValue* classes = mContent->AsElement()->GetClasses(); + if (!classes) { + return false; + } + + for (uint32_t i = 0; i < classes->GetAtomCount(); ++i) { + if (classes->AtomAt(i) == nsNativeMenuAtoms::menuitem_with_favicon) { + return true; + } + } + + return false; +} + +void +nsMenuObject::ClearIcon() { + dbusmenu_menuitem_property_remove(mNativeData, + DBUSMENU_MENUITEM_PROP_ICON_DATA); +} + +nsMenuObject::~nsMenuObject() { + nsWeakMenuObject::NotifyDestroyed(this); + + if (mIconLoader) { + mIconLoader->Destroy(); + } + + if (mListener) { + mListener->UnregisterForContentChanges(mContent); + } + + if (mNativeData) { + g_object_unref(mNativeData); + mNativeData = nullptr; + } +} + +void +nsMenuObject::CreateNativeData() { + MOZ_ASSERT(mNativeData == nullptr, "This node already has a DbusmenuMenuitem. The old one will be leaked"); + + mNativeData = dbusmenu_menuitem_new(); + InitializeNativeData(); + if (mParent && mParent->IsBeingDisplayed()) { + ContainerIsOpening(); + } + + mListener->RegisterForContentChanges(mContent, this); +} + +nsresult +nsMenuObject::AdoptNativeData(DbusmenuMenuitem* aNativeData) { + MOZ_ASSERT(mNativeData == nullptr, "This node already has a DbusmenuMenuitem. The old one will be leaked"); + + if (!IsCompatibleWithNativeData(aNativeData)) { + return NS_ERROR_FAILURE; + } + + mNativeData = aNativeData; + g_object_ref(mNativeData); + + PropertyFlags supported = SupportedProperties(); + PropertyFlags mask = static_cast(1); + + for (uint32_t i = 0; gPropertyStrings[i]; ++i) { + if (!(mask & supported)) { + dbusmenu_menuitem_property_remove(mNativeData, gPropertyStrings[i]); + } + mask = static_cast(mask << 1); + } + + InitializeNativeData(); + if (mParent && mParent->IsBeingDisplayed()) { + ContainerIsOpening(); + } + + mListener->RegisterForContentChanges(mContent, this); + + return NS_OK; +} + +void +nsMenuObject::ContainerIsOpening() { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + + UpdateContentAttributes(); + + RefPtr sc = GetStyleContext(); + Update(sc); +} + +/* static */ void +nsWeakMenuObject::AddWeakReference(nsWeakMenuObject* aWeak) { + aWeak->mPrev = sHead; + sHead = aWeak; +} + +/* static */ void +nsWeakMenuObject::RemoveWeakReference(nsWeakMenuObject* aWeak) { + if (aWeak == sHead) { + sHead = aWeak->mPrev; + return; + } + + nsWeakMenuObject* weak = sHead; + while (weak && weak->mPrev != aWeak) { + weak = weak->mPrev; + } + + if (weak) { + weak->mPrev = aWeak->mPrev; + } +} + +/* static */ void +nsWeakMenuObject::NotifyDestroyed(nsMenuObject* aMenuObject) { + nsWeakMenuObject* weak = sHead; + while (weak) { + if (weak->mMenuObject == aMenuObject) { + weak->mMenuObject = nullptr; + } + + weak = weak->mPrev; + } +} diff --git a/widget/gtk/nsMenuObject.h b/widget/gtk/nsMenuObject.h new file mode 100644 index 000000000..c7637cd05 --- /dev/null +++ b/widget/gtk/nsMenuObject.h @@ -0,0 +1,165 @@ +/* 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 __nsMenuObject_h__ +#define __nsMenuObject_h__ + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" + +#include "nsDbusmenu.h" +#include "nsNativeMenuDocListener.h" + +class nsIAtom; +class nsIContent; +class nsStyleContext; +class nsMenuContainer; +class nsMenuObjectIconLoader; + +#define DBUSMENU_PROPERTIES \ + DBUSMENU_PROPERTY(Label, DBUSMENU_MENUITEM_PROP_LABEL, 0) \ + DBUSMENU_PROPERTY(Enabled, DBUSMENU_MENUITEM_PROP_ENABLED, 1) \ + DBUSMENU_PROPERTY(Visible, DBUSMENU_MENUITEM_PROP_VISIBLE, 2) \ + DBUSMENU_PROPERTY(IconData, DBUSMENU_MENUITEM_PROP_ICON_DATA, 3) \ + DBUSMENU_PROPERTY(Type, DBUSMENU_MENUITEM_PROP_TYPE, 4) \ + DBUSMENU_PROPERTY(Shortcut, DBUSMENU_MENUITEM_PROP_SHORTCUT, 5) \ + DBUSMENU_PROPERTY(ToggleType, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE, 6) \ + DBUSMENU_PROPERTY(ToggleState, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE, 7) \ + DBUSMENU_PROPERTY(ChildDisplay, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY, 8) + +/* + * This is the base class for all menu nodes. Each instance represents + * a single node in the menu hierarchy. It wraps the corresponding DOM node and + * native menu node, keeps them in sync and transfers events between the two. + * It is not reference counted - each node is owned by its parent (the top + * level menubar is owned by the window) and keeps a weak pointer to its + * parent (which is guaranteed to always be valid because a node will never + * outlive its parent). It is not safe to keep a reference to nsMenuObject + * externally. + */ +class nsMenuObject : public nsNativeMenuChangeObserver { +public: + enum EType { + eType_MenuBar, + eType_Menu, + eType_MenuItem + }; + + virtual ~nsMenuObject(); + + // Get the native menu item node + DbusmenuMenuitem* GetNativeData() const { return mNativeData; } + + // Get the parent menu object + nsMenuContainer* Parent() const { return mParent; } + + // Get the content node + nsIContent* ContentNode() const { return mContent; } + + // Get the type of this node. Must be provided by subclasses + virtual EType Type() const = 0; + + // Get the document listener + nsNativeMenuDocListener* DocListener() const { return mListener; } + + // Create the native menu item node (called by containers) + void CreateNativeData(); + + // Adopt the specified native menu item node (called by containers) + nsresult AdoptNativeData(DbusmenuMenuitem* aNativeData); + + // Called by the container to tell us that it's opening + void ContainerIsOpening(); + +protected: + nsMenuObject(nsMenuContainer* aParent, nsIContent* aContent); + nsMenuObject(nsNativeMenuDocListener* aListener, nsIContent* aContent); + + enum PropertyFlags { +#define DBUSMENU_PROPERTY(e, s, b) eProp##e = (1 << b), + DBUSMENU_PROPERTIES +#undef DBUSMENU_PROPERTY + }; + + void UpdateLabel(); + void UpdateVisibility(nsStyleContext* aStyleContext); + void UpdateSensitivity(); + void UpdateIcon(nsStyleContext* aStyleContext); + + already_AddRefed GetStyleContext(); + +private: + friend class nsMenuObjectIconLoader; + + // Set up initial properties on the native data, connect to signals etc. + // This should be implemented by subclasses + virtual void InitializeNativeData(); + + // Return the properties that this menu object type supports + // This should be implemented by subclasses + virtual PropertyFlags SupportedProperties() const; + + // Determine whether this menu object could use the specified + // native item. Returns true by default but can be overridden by subclasses + virtual bool + IsCompatibleWithNativeData(DbusmenuMenuitem* aNativeData) const; + + // Update attributes on this objects content node when the container opens. + // This is called before style resolution, and should be implemented by + // subclasses who want to modify attributes that might affect style. + // This will not be called when there are script blockers + virtual void UpdateContentAttributes(); + + // Update properties that should be refreshed when the container opens. + // This should be implemented by subclasses that have properties which + // need refreshing + virtual void Update(nsStyleContext* aStyleContext); + + bool ShouldShowIcon() const; + void ClearIcon(); + + nsCOMPtr mContent; + // mListener is a strong ref for simplicity - someone in the tree needs to + // own it, and this only really needs to be the top-level object (as no + // children outlives their parent). However, we need to keep it alive until + // after running the nsMenuObject destructor for the top-level menu object, + // hence the strong ref + RefPtr mListener; + nsMenuContainer* mParent; // [weak] + DbusmenuMenuitem* mNativeData; // [strong] + RefPtr mIconLoader; +}; + +// Keep a weak pointer to a menu object +class nsWeakMenuObject { +public: + nsWeakMenuObject() : mPrev(nullptr), mMenuObject(nullptr) {} + + nsWeakMenuObject(nsMenuObject* aMenuObject) : + mPrev(nullptr), mMenuObject(aMenuObject) + { + AddWeakReference(this); + } + + ~nsWeakMenuObject() { RemoveWeakReference(this); } + + nsMenuObject* get() const { return mMenuObject; } + + nsMenuObject* operator->() const { return mMenuObject; } + + explicit operator bool() const { return !!mMenuObject; } + + static void NotifyDestroyed(nsMenuObject* aMenuObject); + +private: + static void AddWeakReference(nsWeakMenuObject* aWeak); + static void RemoveWeakReference(nsWeakMenuObject* aWeak); + + nsWeakMenuObject* mPrev; + static nsWeakMenuObject* sHead; + + nsMenuObject* mMenuObject; +}; + +#endif /* __nsMenuObject_h__ */ diff --git a/widget/gtk/nsMenuSeparator.cpp b/widget/gtk/nsMenuSeparator.cpp new file mode 100644 index 000000000..893c5c7f0 --- /dev/null +++ b/widget/gtk/nsMenuSeparator.cpp @@ -0,0 +1,74 @@ +/* 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/Assertions.h" +#include "mozilla/Move.h" +#include "nsAutoPtr.h" +#include "nsCRT.h" +#include "nsGkAtoms.h" +#include "nsStyleContext.h" + +#include "nsDbusmenu.h" + +#include "nsMenuContainer.h" +#include "nsMenuSeparator.h" + +using namespace mozilla; + +void +nsMenuSeparator::InitializeNativeData() { + dbusmenu_menuitem_property_set(GetNativeData(), + DBUSMENU_MENUITEM_PROP_TYPE, + "separator"); +} + +void +nsMenuSeparator::Update(nsStyleContext* aContext) { + UpdateVisibility(aContext); +} + +bool +nsMenuSeparator::IsCompatibleWithNativeData(DbusmenuMenuitem* aNativeData) const { + return nsCRT::strcmp(dbusmenu_menuitem_property_get(aNativeData, + DBUSMENU_MENUITEM_PROP_TYPE), + "separator") == 0; +} + +nsMenuObject::PropertyFlags +nsMenuSeparator::SupportedProperties() const { + return static_cast( + nsMenuObject::ePropVisible | + nsMenuObject::ePropType + ); +} + +void +nsMenuSeparator::OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) { + MOZ_ASSERT(aContent == ContentNode(), "Received an event that wasn't meant for us!"); + + if (!Parent()->IsBeingDisplayed()) { + return; + } + + if (aAttribute == nsGkAtoms::hidden || + aAttribute == nsGkAtoms::collapsed) { + RefPtr sc = GetStyleContext(); + UpdateVisibility(sc); + } +} + +nsMenuSeparator::nsMenuSeparator(nsMenuContainer* aParent, + nsIContent* aContent) : + nsMenuObject(aParent, aContent) { + MOZ_COUNT_CTOR(nsMenuSeparator); +} + +nsMenuSeparator::~nsMenuSeparator() { + MOZ_COUNT_DTOR(nsMenuSeparator); +} + +nsMenuObject::EType +nsMenuSeparator::Type() const { + return eType_MenuItem; +} diff --git a/widget/gtk/nsMenuSeparator.h b/widget/gtk/nsMenuSeparator.h new file mode 100644 index 000000000..9ba770a85 --- /dev/null +++ b/widget/gtk/nsMenuSeparator.h @@ -0,0 +1,33 @@ +/* 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 __nsMenuSeparator_h__ +#define __nsMenuSeparator_h__ + +#include "mozilla/Attributes.h" + +#include "nsMenuObject.h" + +class nsIContent; +class nsIAtom; +class nsMenuContainer; + +// Menu separator class +class nsMenuSeparator final : public nsMenuObject { +public: + nsMenuSeparator(nsMenuContainer* aParent, nsIContent* aContent); + ~nsMenuSeparator(); + + nsMenuObject::EType Type() const override; + +private: + void InitializeNativeData() override; + void Update(nsStyleContext* aStyleContext) override; + bool IsCompatibleWithNativeData(DbusmenuMenuitem* aNativeData) const override; + nsMenuObject::PropertyFlags SupportedProperties() const override; + + void OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) override; +}; + +#endif /* __nsMenuSeparator_h__ */ diff --git a/widget/gtk/nsNativeMenuAtomList.h b/widget/gtk/nsNativeMenuAtomList.h new file mode 100644 index 000000000..4a8b3869a --- /dev/null +++ b/widget/gtk/nsNativeMenuAtomList.h @@ -0,0 +1,9 @@ +/* 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/. */ + +WIDGET_ATOM2(menuitem_with_favicon, "menuitem-with-favicon") +WIDGET_ATOM2(_moz_menubarkeeplocal, "_moz-menubarkeeplocal") +WIDGET_ATOM2(_moz_nativemenupopupstate, "_moz-nativemenupopupstate") +WIDGET_ATOM(openedwithkey) +WIDGET_ATOM(shellshowingmenubar) diff --git a/widget/gtk/nsNativeMenuAtoms.cpp b/widget/gtk/nsNativeMenuAtoms.cpp new file mode 100644 index 000000000..f43d8b24b --- /dev/null +++ b/widget/gtk/nsNativeMenuAtoms.cpp @@ -0,0 +1,35 @@ +/* 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 "nsIAtom.h" +#include "nsStaticAtom.h" + +#include "nsNativeMenuAtoms.h" + +using namespace mozilla; + +#define WIDGET_ATOM(_name) nsIAtom* nsNativeMenuAtoms::_name; +#define WIDGET_ATOM2(_name, _value) nsIAtom* nsNativeMenuAtoms::_name; +#include "nsNativeMenuAtomList.h" +#undef WIDGET_ATOM +#undef WIDGET_ATOM2 + +#define WIDGET_ATOM(name_) NS_STATIC_ATOM_BUFFER(name_##_buffer, #name_) +#define WIDGET_ATOM2(name_, value_) NS_STATIC_ATOM_BUFFER(name_##_buffer, value_) +#include "nsNativeMenuAtomList.h" +#undef WIDGET_ATOM +#undef WIDGET_ATOM2 + +static const nsStaticAtom gAtoms[] = { +#define WIDGET_ATOM(name_) NS_STATIC_ATOM(name_##_buffer, &nsNativeMenuAtoms::name_), +#define WIDGET_ATOM2(name_, value_) NS_STATIC_ATOM(name_##_buffer, &nsNativeMenuAtoms::name_), +#include "nsNativeMenuAtomList.h" +#undef WIDGET_ATOM +#undef WIDGET_ATOM2 +}; + +/* static */ void +nsNativeMenuAtoms::RegisterAtoms() { + NS_RegisterStaticAtoms(gAtoms); +} diff --git a/widget/gtk/nsNativeMenuAtoms.h b/widget/gtk/nsNativeMenuAtoms.h new file mode 100644 index 000000000..4a9766ee8 --- /dev/null +++ b/widget/gtk/nsNativeMenuAtoms.h @@ -0,0 +1,23 @@ +/* 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 __nsNativeMenuAtoms_h__ +#define __nsNativeMenuAtoms_h__ + +class nsIAtom; + +class nsNativeMenuAtoms { +public: + nsNativeMenuAtoms() = delete; + + static void RegisterAtoms(); + +#define WIDGET_ATOM(_name) static nsIAtom* _name; +#define WIDGET_ATOM2(_name, _value) static nsIAtom* _name; +#include "nsNativeMenuAtomList.h" +#undef WIDGET_ATOM +#undef WIDGET_ATOM2 +}; + +#endif /* __nsNativeMenuAtoms_h__ */ diff --git a/widget/gtk/nsNativeMenuDocListener.cpp b/widget/gtk/nsNativeMenuDocListener.cpp new file mode 100644 index 000000000..46a9c3aa9 --- /dev/null +++ b/widget/gtk/nsNativeMenuDocListener.cpp @@ -0,0 +1,329 @@ +/* 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/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/dom/Element.h" +#include "nsContentUtils.h" +#include "nsIAtom.h" +#include "nsIContent.h" +#include "nsIDocument.h" + +#include "nsMenuContainer.h" + +#include "nsNativeMenuDocListener.h" + +using namespace mozilla; + +uint32_t nsNativeMenuDocListener::sUpdateBlockersCount = 0; + +nsNativeMenuDocListenerTArray* gPendingListeners; + +/* + * Small helper which caches a single listener, so that consecutive + * events which go to the same node avoid multiple hash table lookups + */ +class MOZ_STACK_CLASS DispatchHelper { +public: + DispatchHelper(nsNativeMenuDocListener* aListener, + nsIContent* aContent + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : + mObserver(nullptr) { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + if (aContent == aListener->mLastSource) { + mObserver = aListener->mLastTarget; + } else { + mObserver = aListener->mContentToObserverTable.Get(aContent); + if (mObserver) { + aListener->mLastSource = aContent; + aListener->mLastTarget = mObserver; + } + } + } + + ~DispatchHelper() { }; + + nsNativeMenuChangeObserver* Observer() const { + return mObserver; + } + + bool HasObserver() const { + return !!mObserver; + } + +private: + nsNativeMenuChangeObserver* mObserver; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +NS_IMPL_ISUPPORTS(nsNativeMenuDocListener, nsIMutationObserver) + +nsNativeMenuDocListener::~nsNativeMenuDocListener() { + MOZ_ASSERT(mContentToObserverTable.Count() == 0, + "Some nodes forgot to unregister listeners. This is bad! (and we're lucky we made it this far)"); + MOZ_COUNT_DTOR(nsNativeMenuDocListener); +} + +void +nsNativeMenuDocListener::AttributeChanged(nsIDocument* aDocument, + mozilla::dom::Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue) { + if (sUpdateBlockersCount == 0) { + DoAttributeChanged(aElement, aAttribute); + return; + } + + MutationRecord* m =* mPendingMutations.AppendElement(new MutationRecord); + m->mType = MutationRecord::eAttributeChanged; + m->mTarget = aElement; + m->mAttribute = aAttribute; + + ScheduleFlush(this); +} + +void +nsNativeMenuDocListener::ContentAppended(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aFirstNewContent, + int32_t aNewIndexInContainer) { + for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) { + ContentInserted(aDocument, aContainer, c, 0); + } +} + +void +nsNativeMenuDocListener::ContentInserted(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer) { + nsIContent* prevSibling = nsMenuContainer::GetPreviousSupportedSibling(aChild); + + if (sUpdateBlockersCount == 0) { + DoContentInserted(aContainer, aChild, prevSibling); + return; + } + + MutationRecord* m =* mPendingMutations.AppendElement(new MutationRecord); + m->mType = MutationRecord::eContentInserted; + m->mTarget = aContainer; + m->mChild = aChild; + m->mPrevSibling = prevSibling; + + ScheduleFlush(this); +} + +void +nsNativeMenuDocListener::ContentRemoved(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer, + nsIContent* aPreviousSibling) { + if (sUpdateBlockersCount == 0) { + DoContentRemoved(aContainer, aChild); + return; + } + + MutationRecord* m =* mPendingMutations.AppendElement(new MutationRecord); + m->mType = MutationRecord::eContentRemoved; + m->mTarget = aContainer; + m->mChild = aChild; + + ScheduleFlush(this); +} + +void +nsNativeMenuDocListener::NodeWillBeDestroyed(const nsINode* aNode) { + mDocument = nullptr; +} + +void +nsNativeMenuDocListener::DoAttributeChanged(nsIContent* aContent, + nsIAtom* aAttribute) { + DispatchHelper h(this, aContent); + if (h.HasObserver()) { + h.Observer()->OnAttributeChanged(aContent, aAttribute); + } +} + +void +nsNativeMenuDocListener::DoContentInserted(nsIContent* aContainer, + nsIContent* aChild, + nsIContent* aPrevSibling) { + DispatchHelper h(this, aContainer); + if (h.HasObserver()) { + h.Observer()->OnContentInserted(aContainer, aChild, aPrevSibling); + } +} + +void +nsNativeMenuDocListener::DoContentRemoved(nsIContent* aContainer, + nsIContent* aChild) { + DispatchHelper h(this, aContainer); + if (h.HasObserver()) { + h.Observer()->OnContentRemoved(aContainer, aChild); + } +} + +void +nsNativeMenuDocListener::DoBeginUpdates(nsIContent* aTarget) { + DispatchHelper h(this, aTarget); + if (h.HasObserver()) { + h.Observer()->OnBeginUpdates(aTarget); + } +} + +void +nsNativeMenuDocListener::DoEndUpdates(nsIContent* aTarget) { + DispatchHelper h(this, aTarget); + if (h.HasObserver()) { + h.Observer()->OnEndUpdates(); + } +} + +void +nsNativeMenuDocListener::FlushPendingMutations() { + nsIContent* currentTarget = nullptr; + bool inUpdateSequence = false; + + while (mPendingMutations.Length() > 0) { + MutationRecord* m = mPendingMutations[0]; + + if (m->mTarget != currentTarget) { + if (inUpdateSequence) { + DoEndUpdates(currentTarget); + inUpdateSequence = false; + } + + currentTarget = m->mTarget; + + if (mPendingMutations.Length() > 1 && + mPendingMutations[1]->mTarget == currentTarget) { + DoBeginUpdates(currentTarget); + inUpdateSequence = true; + } + } + + switch (m->mType) { + case MutationRecord::eAttributeChanged: + DoAttributeChanged(m->mTarget, m->mAttribute); + break; + case MutationRecord::eContentInserted: + DoContentInserted(m->mTarget, m->mChild, m->mPrevSibling); + break; + case MutationRecord::eContentRemoved: + DoContentRemoved(m->mTarget, m->mChild); + break; + default: + NS_NOTREACHED("Invalid type"); + } + + mPendingMutations.RemoveElementAt(0); + } + + if (inUpdateSequence) { + DoEndUpdates(currentTarget); + } +} + +/* static */ void +nsNativeMenuDocListener::ScheduleFlush(nsNativeMenuDocListener* aListener) { + MOZ_ASSERT(sUpdateBlockersCount > 0, "Shouldn't be doing this now"); + + if (!gPendingListeners) { + gPendingListeners = new nsNativeMenuDocListenerTArray; + } + + if (gPendingListeners->IndexOf(aListener) == + nsNativeMenuDocListenerTArray::NoIndex) { + gPendingListeners->AppendElement(aListener); + } +} + +/* static */ void +nsNativeMenuDocListener::CancelFlush(nsNativeMenuDocListener* aListener) { + if (!gPendingListeners) { + return; + } + + gPendingListeners->RemoveElement(aListener); +} + +/* static */ void +nsNativeMenuDocListener::RemoveUpdateBlocker() { + if (sUpdateBlockersCount == 1 && gPendingListeners) { + while (gPendingListeners->Length() > 0) { + (*gPendingListeners)[0]->FlushPendingMutations(); + gPendingListeners->RemoveElementAt(0); + } + } + + MOZ_ASSERT(sUpdateBlockersCount > 0, "Negative update blockers count!"); + sUpdateBlockersCount--; +} + +nsNativeMenuDocListener::nsNativeMenuDocListener(nsIContent* aRootNode) : + mRootNode(aRootNode), + mDocument(nullptr), + mLastSource(nullptr), + mLastTarget(nullptr) { + MOZ_COUNT_CTOR(nsNativeMenuDocListener); +} + +void +nsNativeMenuDocListener::RegisterForContentChanges(nsIContent* aContent, + nsNativeMenuChangeObserver* aObserver) { + MOZ_ASSERT(aContent, "Need content parameter"); + MOZ_ASSERT(aObserver, "Need observer parameter"); + if (!aContent || !aObserver) { + return; + } + + DebugOnly old; + MOZ_ASSERT(!mContentToObserverTable.Get(aContent, &old) || old == aObserver, + "Multiple observers for the same content node are not supported"); + + mContentToObserverTable.Put(aContent, aObserver); +} + +void +nsNativeMenuDocListener::UnregisterForContentChanges(nsIContent* aContent) { + MOZ_ASSERT(aContent, "Need content parameter"); + if (!aContent) { + return; + } + + mContentToObserverTable.Remove(aContent); + if (aContent == mLastSource) { + mLastSource = nullptr; + mLastTarget = nullptr; + } +} + +void +nsNativeMenuDocListener::Start() { + if (mDocument) { + return; + } + + mDocument = mRootNode->OwnerDoc(); + if (!mDocument) { + return; + } + + mDocument->AddMutationObserver(this); +} + +void +nsNativeMenuDocListener::Stop() { + if (mDocument) { + mDocument->RemoveMutationObserver(this); + mDocument = nullptr; + } + + CancelFlush(this); + mPendingMutations.Clear(); +} diff --git a/widget/gtk/nsNativeMenuDocListener.h b/widget/gtk/nsNativeMenuDocListener.h new file mode 100644 index 000000000..c0a503da1 --- /dev/null +++ b/widget/gtk/nsNativeMenuDocListener.h @@ -0,0 +1,146 @@ +/* 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 __nsNativeMenuDocListener_h__ +#define __nsNativeMenuDocListener_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/GuardObjects.h" +#include "mozilla/RefPtr.h" +#include "nsAutoPtr.h" +#include "nsDataHashtable.h" +#include "nsStubMutationObserver.h" +#include "nsTArray.h" + +class nsIAtom; +class nsIContent; +class nsIDocument; +class nsNativeMenuChangeObserver; + +/* + * This class keeps a mapping of content nodes to observers and forwards DOM + * mutations to these. There is exactly one of these for every menubar. + */ +class nsNativeMenuDocListener final : nsStubMutationObserver { +public: + NS_DECL_ISUPPORTS + + nsNativeMenuDocListener(nsIContent* aRootNode); + + // Register an observer to receive mutation events for the specified + // content node. The caller must keep the observer alive until + // UnregisterForContentChanges is called. + void RegisterForContentChanges(nsIContent* aContent, + nsNativeMenuChangeObserver* aObserver); + + // Unregister the registered observer for the specified content node + void UnregisterForContentChanges(nsIContent* aContent); + + // Start listening to the document and forwarding DOM mutations to + // registered observers. + void Start(); + + // Stop listening to the document. No DOM mutations will be forwarded + // to registered observers. + void Stop(); + + /* + * This class is intended to be used inside GObject signal handlers. + * It allows us to queue updates until we have finished delivering + * events to Goanna, and then we can batch updates to our view of the + * menu. This allows us to do menu updates without altering the structure + * seen by the OS. + */ + class MOZ_STACK_CLASS BlockUpdatesScope { + public: + BlockUpdatesScope(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + nsNativeMenuDocListener::AddUpdateBlocker(); + } + + ~BlockUpdatesScope() { + nsNativeMenuDocListener::RemoveUpdateBlocker(); + } + + private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + }; + +private: + friend class DispatchHelper; + + struct MutationRecord { + enum RecordType { + eAttributeChanged, + eContentInserted, + eContentRemoved + } mType; + + nsCOMPtr mTarget; + nsCOMPtr mChild; + nsCOMPtr mPrevSibling; + nsCOMPtr mAttribute; + }; + + ~nsNativeMenuDocListener(); + + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED + + void DoAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute); + void DoContentInserted(nsIContent* aContainer, + nsIContent* aChild, + nsIContent* aPrevSibling); + void DoContentRemoved(nsIContent* aContainer, nsIContent* aChild); + void DoBeginUpdates(nsIContent* aTarget); + void DoEndUpdates(nsIContent* aTarget); + + void FlushPendingMutations(); + static void ScheduleFlush(nsNativeMenuDocListener* aListener); + static void CancelFlush(nsNativeMenuDocListener* aListener); + + static void AddUpdateBlocker() { + ++sUpdateBlockersCount; + } + static void RemoveUpdateBlocker(); + + nsCOMPtr mRootNode; + nsIDocument* mDocument; + nsIContent* mLastSource; + nsNativeMenuChangeObserver* mLastTarget; + nsTArray > mPendingMutations; + nsDataHashtable, nsNativeMenuChangeObserver* > mContentToObserverTable; + + static uint32_t sUpdateBlockersCount; +}; + +typedef nsTArray > nsNativeMenuDocListenerTArray; + +/* + * Implemented by classes that want to listen to mutation events from content + * nodes. + */ +class nsNativeMenuChangeObserver { +public: + virtual void OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) {} + + virtual void OnContentInserted(nsIContent* aContainer, + nsIContent* aChild, + nsIContent* aPrevSibling) {} + + virtual void OnContentRemoved(nsIContent* aContainer, nsIContent* aChild) {} + + // Signals the start of a sequence of more than 1 event for the specified + // node. This only happens when events are flushed as all BlockUpdatesScope + // instances go out of scope + virtual void OnBeginUpdates(nsIContent* aContent) {}; + + // Signals the end of a sequence of events + virtual void OnEndUpdates() {}; +}; + +#endif /* __nsNativeMenuDocListener_h__ */ diff --git a/widget/gtk/nsNativeMenuService.cpp b/widget/gtk/nsNativeMenuService.cpp new file mode 100644 index 000000000..eabe77ca8 --- /dev/null +++ b/widget/gtk/nsNativeMenuService.cpp @@ -0,0 +1,485 @@ +/* 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/Assertions.h" +#include "mozilla/Move.h" +#include "mozilla/Preferences.h" +#include "mozilla/UniquePtr.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsCRT.h" +#include "nsGtkUtils.h" +#include "nsIContent.h" +#include "nsIWidget.h" +#include "nsServiceManagerUtils.h" +#include "nsWindow.h" +#include "prlink.h" + +#include "nsDbusmenu.h" +#include "nsMenuBar.h" +#include "nsNativeMenuAtoms.h" +#include "nsNativeMenuDocListener.h" + +#include +#include +#include + +#include "nsNativeMenuService.h" + +using namespace mozilla; + +nsNativeMenuService* nsNativeMenuService::sService = nullptr; + +extern PangoLayout* gPangoLayout; +extern nsNativeMenuDocListenerTArray* gPendingListeners; + +static const nsTArray::index_type NoIndex = nsTArray::NoIndex; + +#if not GLIB_CHECK_VERSION(2,26,0) +enum GBusType { + G_BUS_TYPE_STARTER = -1, + G_BUS_TYPE_NONE = 0, + G_BUS_TYPE_SYSTEM = 1, + G_BUS_TYPE_SESSION = 2 +}; + +enum GDBusProxyFlags { + G_DBUS_PROXY_FLAGS_NONE = 0, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES = 1 << 0, + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS = 1 << 1, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START = 1 << 2, + G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES = 1 << 3 +}; + +enum GDBusCallFlags { + G_DBUS_CALL_FLAGS_NONE = 0, + G_DBUS_CALL_FLAGS_NO_AUTO_START = 1 << 0 +}; + +typedef _GDBusInterfaceInfo GDBusInterfaceInfo; +typedef _GDBusProxy GDBusProxy; +typedef _GVariant GVariant; +#endif + +#undef g_dbus_proxy_new_for_bus +#undef g_dbus_proxy_new_for_bus_finish +#undef g_dbus_proxy_call +#undef g_dbus_proxy_call_finish +#undef g_dbus_proxy_get_name_owner + +typedef void (*_g_dbus_proxy_new_for_bus_fn)(GBusType, GDBusProxyFlags, + GDBusInterfaceInfo*, + const gchar*, const gchar*, + const gchar*, GCancellable*, + GAsyncReadyCallback, gpointer); + +typedef GDBusProxy* (*_g_dbus_proxy_new_for_bus_finish_fn)(GAsyncResult*, + GError**); +typedef void (*_g_dbus_proxy_call_fn)(GDBusProxy*, const gchar*, GVariant*, + GDBusCallFlags, gint, GCancellable*, + GAsyncReadyCallback, gpointer); +typedef GVariant* (*_g_dbus_proxy_call_finish_fn)(GDBusProxy*, GAsyncResult*, + GError**); +typedef gchar* (*_g_dbus_proxy_get_name_owner_fn)(GDBusProxy*); + +static _g_dbus_proxy_new_for_bus_fn _g_dbus_proxy_new_for_bus; +static _g_dbus_proxy_new_for_bus_finish_fn _g_dbus_proxy_new_for_bus_finish; +static _g_dbus_proxy_call_fn _g_dbus_proxy_call; +static _g_dbus_proxy_call_finish_fn _g_dbus_proxy_call_finish; +static _g_dbus_proxy_get_name_owner_fn _g_dbus_proxy_get_name_owner; + +#define g_dbus_proxy_new_for_bus _g_dbus_proxy_new_for_bus +#define g_dbus_proxy_new_for_bus_finish _g_dbus_proxy_new_for_bus_finish +#define g_dbus_proxy_call _g_dbus_proxy_call +#define g_dbus_proxy_call_finish _g_dbus_proxy_call_finish +#define g_dbus_proxy_get_name_owner _g_dbus_proxy_get_name_owner + +static PRLibrary* gGIOLib = nullptr; + +static nsresult +GDBusInit() { + gGIOLib = PR_LoadLibrary("libgio-2.0.so.0"); + if (!gGIOLib) { + return NS_ERROR_FAILURE; + } + + g_dbus_proxy_new_for_bus = (_g_dbus_proxy_new_for_bus_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_new_for_bus"); + g_dbus_proxy_new_for_bus_finish = (_g_dbus_proxy_new_for_bus_finish_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_new_for_bus_finish"); + g_dbus_proxy_call = (_g_dbus_proxy_call_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_call"); + g_dbus_proxy_call_finish = (_g_dbus_proxy_call_finish_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_call_finish"); + g_dbus_proxy_get_name_owner = (_g_dbus_proxy_get_name_owner_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_get_name_owner"); + + if (!g_dbus_proxy_new_for_bus || + !g_dbus_proxy_new_for_bus_finish || + !g_dbus_proxy_call || + !g_dbus_proxy_call_finish || + !g_dbus_proxy_get_name_owner) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsNativeMenuService, nsINativeMenuService) + +nsNativeMenuService::nsNativeMenuService() : + mCreateProxyCancellable(nullptr), mDbusProxy(nullptr), mOnline(false) { +} + +nsNativeMenuService::~nsNativeMenuService() { + SetOnline(false); + + if (mCreateProxyCancellable) { + g_cancellable_cancel(mCreateProxyCancellable); + g_object_unref(mCreateProxyCancellable); + mCreateProxyCancellable = nullptr; + } + + // Make sure we disconnect map-event handlers + while (mMenuBars.Length() > 0) { + NotifyNativeMenuBarDestroyed(mMenuBars[0]); + } + + Preferences::UnregisterCallback(PrefChangedCallback, + "ui.use_global_menubar"); + + if (mDbusProxy) { + g_signal_handlers_disconnect_by_func(mDbusProxy, + FuncToGpointer(name_owner_changed_cb), + NULL); + g_object_unref(mDbusProxy); + } + + if (gPendingListeners) { + delete gPendingListeners; + gPendingListeners = nullptr; + } + if (gPangoLayout) { + g_object_unref(gPangoLayout); + gPangoLayout = nullptr; + } + + MOZ_ASSERT(sService == this); + sService = nullptr; +} + +nsresult +nsNativeMenuService::Init() { + nsresult rv = nsDbusmenuFunctions::Init(); + if (NS_FAILED(rv)) { + return rv; + } + + rv = GDBusInit(); + if (NS_FAILED(rv)) { + return rv; + } + + Preferences::RegisterCallback(PrefChangedCallback, + "ui.use_global_menubar"); + + mCreateProxyCancellable = g_cancellable_new(); + + g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION, + static_cast( + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START), + nullptr, + "com.canonical.AppMenu.Registrar", + "/com/canonical/AppMenu/Registrar", + "com.canonical.AppMenu.Registrar", + mCreateProxyCancellable, proxy_created_cb, + nullptr); + + /* We don't technically know that the shell will draw the menubar until + * we know whether anybody owns the name of the menubar service on the + * session bus. However, discovering this happens asynchronously so + * we optimize for the common case here by assuming that the shell will + * draw window menubars if we are running inside Unity. This should + * mean that we avoid temporarily displaying the window menubar ourselves + */ + const char* desktop = getenv("XDG_CURRENT_DESKTOP"); + if (nsCRT::strcmp(desktop, "Unity") == 0) { + SetOnline(true); + } + + return NS_OK; +} + +/* static */ void +nsNativeMenuService::EnsureInitialized() { + if (sService) { + return; + } + nsCOMPtr service = + do_GetService("@mozilla.org/widget/nativemenuservice;1"); +} + +void +nsNativeMenuService::SetOnline(bool aOnline) { + if (!Preferences::GetBool("ui.use_global_menubar", true)) { + aOnline = false; + } + + mOnline = aOnline; + if (aOnline) { + for (uint32_t i = 0; i < mMenuBars.Length(); ++i) { + RegisterNativeMenuBar(mMenuBars[i]); + } + } else { + for (uint32_t i = 0; i < mMenuBars.Length(); ++i) { + mMenuBars[i]->Deactivate(); + } + } +} + +void +nsNativeMenuService::RegisterNativeMenuBar(nsMenuBar* aMenuBar) { + if (!mOnline) { + return; + } + + // This will effectively create the native menubar for + // exporting over the session bus, and hide the XUL menubar + aMenuBar->Activate(); + + if (!mDbusProxy || + !gtk_widget_get_mapped(aMenuBar->TopLevelWindow()) || + mMenuBarRegistrationCancellables.Get(aMenuBar, nullptr)) { + // Don't go further if we don't have a proxy for the shell menu + // service, the window isn't mapped or there is a request in progress. + return; + } + + uint32_t xid = aMenuBar->WindowId(); + nsAdoptingCString path = aMenuBar->ObjectPath(); + if (xid == 0 || path.IsEmpty()) { + NS_WARNING("Menubar has invalid XID or object path"); + return; + } + + GCancellable* cancellable = g_cancellable_new(); + mMenuBarRegistrationCancellables.Put(aMenuBar, cancellable); + + // We keep a weak ref because we can't assume that GDBus cancellation + // is reliable (see https://launchpad.net/bugs/953562) + + g_dbus_proxy_call(mDbusProxy, "RegisterWindow", + g_variant_new("(uo)", xid, path.get()), + G_DBUS_CALL_FLAGS_NONE, -1, + cancellable, + register_native_menubar_cb, aMenuBar); +} + +/* static */ void +nsNativeMenuService::name_owner_changed_cb(GObject* gobject, + GParamSpec* pspec, + gpointer user_data) { + nsNativeMenuService::GetSingleton()->OnNameOwnerChanged(); +} + +/* static */ void +nsNativeMenuService::proxy_created_cb(GObject* source_object, + GAsyncResult* res, + gpointer user_data) { + GError* error = nullptr; + GDBusProxy* proxy = g_dbus_proxy_new_for_bus_finish(res, &error); + if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_error_free(error); + return; + } + + if (error) { + g_error_free(error); + } + + // We need this check because we can't assume that GDBus cancellation + // is reliable (see https://launchpad.net/bugs/953562) + nsNativeMenuService* self = nsNativeMenuService::GetSingleton(); + if (!self) { + if (proxy) { + g_object_unref(proxy); + } + return; + } + + self->OnProxyCreated(proxy); +} + +/* static */ void +nsNativeMenuService::register_native_menubar_cb(GObject* source_object, + GAsyncResult* res, + gpointer user_data) { + nsMenuBar* menuBar = static_cast(user_data); + + GError* error = nullptr; + GVariant* results = g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object), + res, &error); + if (results) { + // There's nothing useful in the response + g_variant_unref(results); + } + + bool success = error ? false : true; + if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_error_free(error); + return; + } + + if (error) { + g_error_free(error); + } + + nsNativeMenuService* self = nsNativeMenuService::GetSingleton(); + if (!self) { + return; + } + + self->OnNativeMenuBarRegistered(menuBar, success); +} + +/* static */ gboolean +nsNativeMenuService::map_event_cb(GtkWidget* widget, + GdkEvent* event, + gpointer user_data) { + nsMenuBar* menubar = static_cast(user_data); + nsNativeMenuService::GetSingleton()->RegisterNativeMenuBar(menubar); + + return FALSE; +} + +void +nsNativeMenuService::OnNameOwnerChanged() { + char* owner = g_dbus_proxy_get_name_owner(mDbusProxy); + SetOnline(owner ? true : false); + g_free(owner); +} + +void +nsNativeMenuService::OnProxyCreated(GDBusProxy* aProxy) { + mDbusProxy = aProxy; + + g_object_unref(mCreateProxyCancellable); + mCreateProxyCancellable = nullptr; + + if (!mDbusProxy) { + SetOnline(false); + return; + } + + g_signal_connect(mDbusProxy, "notify::g-name-owner", + G_CALLBACK(name_owner_changed_cb), nullptr); + + OnNameOwnerChanged(); +} + +void +nsNativeMenuService::OnNativeMenuBarRegistered(nsMenuBar* aMenuBar, + bool aSuccess) { + // Don't assume that GDBus cancellation is reliable (ie, |aMenuBar| might + // have already been deleted (see https://launchpad.net/bugs/953562) + GCancellable* cancellable = nullptr; + if (!mMenuBarRegistrationCancellables.Get(aMenuBar, &cancellable)) { + return; + } + + g_object_unref(cancellable); + mMenuBarRegistrationCancellables.Remove(aMenuBar); + + if (!aSuccess) { + aMenuBar->Deactivate(); + } +} + +/* static */ void +nsNativeMenuService::PrefChangedCallback(const char* aPref, + void* aClosure) { + nsNativeMenuService::GetSingleton()->PrefChanged(); +} + +void +nsNativeMenuService::PrefChanged() { + if (!mDbusProxy) { + SetOnline(false); + return; + } + + OnNameOwnerChanged(); +} + +NS_IMETHODIMP +nsNativeMenuService::CreateNativeMenuBar(nsIWidget* aParent, + nsIContent* aMenuBarNode) { + NS_ENSURE_ARG(aParent); + NS_ENSURE_ARG(aMenuBarNode); + + if (aMenuBarNode->AttrValueIs(kNameSpaceID_None, + nsNativeMenuAtoms::_moz_menubarkeeplocal, + nsGkAtoms::_true, + eCaseMatters)) { + return NS_OK; + } + + UniquePtr menubar(nsMenuBar::Create(aParent, aMenuBarNode)); + if (!menubar) { + NS_WARNING("Failed to create menubar"); + return NS_ERROR_FAILURE; + } + + // Unity forgets our window if it is unmapped by the application, which + // happens with some extensions that add "minimize to tray" type + // functionality. We hook on to the MapNotify event to re-register our menu + // with Unity + g_signal_connect(G_OBJECT(menubar->TopLevelWindow()), + "map-event", G_CALLBACK(map_event_cb), + menubar.get()); + + mMenuBars.AppendElement(menubar.get()); + RegisterNativeMenuBar(menubar.get()); + + static_cast(aParent)->SetMenuBar(Move(menubar)); + + return NS_OK; +} + +/* static */ already_AddRefed +nsNativeMenuService::GetInstanceForServiceManager() { + RefPtr service(sService); + + if (service) { + return service.forget(); + } + + service = new nsNativeMenuService(); + + if (NS_FAILED(service->Init())) { + return nullptr; + } + + sService = service.get(); + return service.forget(); +} + +/* static */ nsNativeMenuService* +nsNativeMenuService::GetSingleton() { + EnsureInitialized(); + return sService; +} + +void +nsNativeMenuService::NotifyNativeMenuBarDestroyed(nsMenuBar* aMenuBar) { + g_signal_handlers_disconnect_by_func(aMenuBar->TopLevelWindow(), + FuncToGpointer(map_event_cb), + aMenuBar); + + mMenuBars.RemoveElement(aMenuBar); + + GCancellable* cancellable = nullptr; + if (mMenuBarRegistrationCancellables.Get(aMenuBar, &cancellable)) { + mMenuBarRegistrationCancellables.Remove(aMenuBar); + g_cancellable_cancel(cancellable); + g_object_unref(cancellable); + } +} diff --git a/widget/gtk/nsNativeMenuService.h b/widget/gtk/nsNativeMenuService.h new file mode 100644 index 000000000..5ce022526 --- /dev/null +++ b/widget/gtk/nsNativeMenuService.h @@ -0,0 +1,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 __nsNativeMenuService_h__ +#define __nsNativeMenuService_h__ + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "nsDataHashtable.h" +#include "nsINativeMenuService.h" +#include "nsTArray.h" + +#include +#include +#include + +class nsMenuBar; + +/* + * The main native menu service singleton. nsWebShellWindow calls in to this when + * a new top level window is created. + * + * Menubars are owned by their nsWindow. This service holds a weak reference to + * each menubar for the purpose of re-registering them with the shell if it + * needs to. The menubar is responsible for notifying the service when the last + * reference to it is dropped. + */ +class nsNativeMenuService final : public nsINativeMenuService { +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CreateNativeMenuBar(nsIWidget* aParent, nsIContent* aMenuBarNode) override; + + // Returns the singleton addref'd for the service manager + static already_AddRefed GetInstanceForServiceManager(); + + // Returns the singleton without increasing the reference count + static nsNativeMenuService* GetSingleton(); + + // Called by a menubar when it is deleted + void NotifyNativeMenuBarDestroyed(nsMenuBar* aMenuBar); + +private: + nsNativeMenuService(); + ~nsNativeMenuService(); + nsresult Init(); + + static void EnsureInitialized(); + void SetOnline(bool aOnline); + void RegisterNativeMenuBar(nsMenuBar* aMenuBar); + static void name_owner_changed_cb(GObject* gobject, + GParamSpec* pspec, + gpointer user_data); + static void proxy_created_cb(GObject* source_object, + GAsyncResult* res, + gpointer user_data); + static void register_native_menubar_cb(GObject* source_object, + GAsyncResult* res, + gpointer user_data); + static gboolean map_event_cb(GtkWidget* widget, GdkEvent* event, + gpointer user_data); + void OnNameOwnerChanged(); + void OnProxyCreated(GDBusProxy* aProxy); + void OnNativeMenuBarRegistered(nsMenuBar* aMenuBar, + bool aSuccess); + static void PrefChangedCallback(const char* aPref, void* aClosure); + void PrefChanged(); + + GCancellable* mCreateProxyCancellable; + GDBusProxy* mDbusProxy; + bool mOnline; + nsTArray mMenuBars; + nsDataHashtable, GCancellable*> mMenuBarRegistrationCancellables; + + static bool sShutdown; + static nsNativeMenuService* sService; +}; + +#endif /* __nsNativeMenuService_h__ */ diff --git a/widget/gtk/nsWidgetFactory.cpp b/widget/gtk/nsWidgetFactory.cpp index 7e4274377..a1508d1d6 100644 --- a/widget/gtk/nsWidgetFactory.cpp +++ b/widget/gtk/nsWidgetFactory.cpp @@ -49,6 +49,8 @@ #include "GfxInfoX11.h" #endif +#include "nsNativeMenuService.h" + #include "nsNativeThemeGTK.h" #include "nsIComponentRegistrar.h" @@ -121,6 +123,9 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init) } #endif +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsNativeMenuService, + nsNativeMenuService::GetInstanceForServiceManager) + #ifdef NS_PRINTING NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecGTK) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsGTK, Init) @@ -223,6 +228,7 @@ NS_DEFINE_NAMED_CID(NS_IMAGE_TO_PIXBUF_CID); NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID); NS_DEFINE_NAMED_CID(NS_GFXINFO_CID); #endif +NS_DEFINE_NAMED_CID(NS_NATIVEMENUSERVICE_CID); static const mozilla::Module::CIDEntry kWidgetCIDs[] = { @@ -258,6 +264,7 @@ static const mozilla::Module::CIDEntry kWidgetCIDs[] = { { &kNS_IDLE_SERVICE_CID, false, nullptr, nsIdleServiceGTKConstructor }, { &kNS_GFXINFO_CID, false, nullptr, mozilla::widget::GfxInfoConstructor }, #endif + { &kNS_NATIVEMENUSERVICE_CID, true, NULL, nsNativeMenuServiceConstructor }, { nullptr } }; @@ -295,6 +302,7 @@ static const mozilla::Module::ContractIDEntry kWidgetContracts[] = { { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID }, { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID }, #endif + { "@mozilla.org/widget/nativemenuservice;1", &kNS_NATIVEMENUSERVICE_CID }, { nullptr } }; diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp index e4e69c1b4..6f222a705 100644 --- a/widget/gtk/nsWindow.cpp +++ b/widget/gtk/nsWindow.cpp @@ -67,6 +67,7 @@ #include "mozilla/Assertions.h" #include "mozilla/Likely.h" +#include "mozilla/Move.h" #include "mozilla/Preferences.h" #include "nsIPrefService.h" #include "nsIGConfService.h" @@ -5175,6 +5176,11 @@ nsWindow::HideWindowChrome(bool aShouldHide) return NS_OK; } +void +nsWindow::SetMenuBar(UniquePtr aMenuBar) { + mMenuBar = mozilla::Move(aMenuBar); +} + bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel, bool aAlwaysRollup) diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h index 49a8d4baf..c45176cea 100644 --- a/widget/gtk/nsWindow.h +++ b/widget/gtk/nsWindow.h @@ -35,6 +35,8 @@ #include "IMContextWrapper.h" +#include "nsMenuBar.h" + #undef LOG #ifdef MOZ_LOGGING @@ -162,6 +164,8 @@ public: nsIScreen* aTargetScreen = nullptr) override; NS_IMETHOD HideWindowChrome(bool aShouldHide) override; + void SetMenuBar(mozilla::UniquePtr aMenuBar); + /** * GetLastUserInputTime returns a timestamp for the most recent user input * event. This is intended for pointer grab requests (including drags). @@ -569,6 +573,8 @@ private: RefPtr mIMContext; mozilla::UniquePtr mCurrentTimeGetter; + + mozilla::UniquePtr mMenuBar; }; class nsChildWindow : public nsWindow { diff --git a/widget/moz.build b/widget/moz.build index 3ca4c9785..3a52805b0 100644 --- a/widget/moz.build +++ b/widget/moz.build @@ -38,10 +38,12 @@ elif toolkit == 'cocoa': 'nsITaskbarProgress.idl', ] EXPORTS += [ - 'nsINativeMenuService.h', 'nsIPrintDialogService.h', ] +if toolkit in ('cocoa', 'gtk2', 'gtk3'): + EXPORTS += ['nsINativeMenuService.h'] + # Don't build the DSO under the 'build' directory as windows does. # # The DSOs get built in the toolkit dir itself. Do this so that diff --git a/xpfe/appshell/nsWebShellWindow.cpp b/xpfe/appshell/nsWebShellWindow.cpp index f703be728..2893e7868 100644 --- a/xpfe/appshell/nsWebShellWindow.cpp +++ b/xpfe/appshell/nsWebShellWindow.cpp @@ -73,7 +73,7 @@ #include "nsPIWindowRoot.h" -#ifdef XP_MACOSX +#if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK) #include "nsINativeMenuService.h" #define USE_NATIVE_MENUS #endif -- cgit v1.2.3 From 7f238b1cf9775b43b119eb9a5e2ea0fe79fa5add Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Tue, 9 Jun 2020 10:42:36 -0400 Subject: Follow up to Issue #1578 - Preprocess the popup binding --- toolkit/content/jar.mn | 2 +- toolkit/content/widgets/popup.xml | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn index 8b7b35b61..d79403605 100644 --- a/toolkit/content/jar.mn +++ b/toolkit/content/jar.mn @@ -96,7 +96,7 @@ toolkit.jar: content/global/bindings/menulist.xml (widgets/menulist.xml) content/global/bindings/notification.xml (widgets/notification.xml) content/global/bindings/numberbox.xml (widgets/numberbox.xml) - content/global/bindings/popup.xml (widgets/popup.xml) +* content/global/bindings/popup.xml (widgets/popup.xml) * content/global/bindings/preferences.xml (widgets/preferences.xml) content/global/bindings/progressmeter.xml (widgets/progressmeter.xml) content/global/bindings/radio.xml (widgets/radio.xml) diff --git a/toolkit/content/widgets/popup.xml b/toolkit/content/widgets/popup.xml index 43c529780..c8a395c40 100644 --- a/toolkit/content/widgets/popup.xml +++ b/toolkit/content/widgets/popup.xml @@ -25,14 +25,21 @@ +#ifdef MOZ_WIDGET_GTK - + + ]]> + +#else + +#endif -- cgit v1.2.3 From 0e0feed5daa8751b04bda7b4caac3854995cb0c8 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 10 Jun 2020 01:19:37 +0000 Subject: Issue #1585 - Replace node.rootNode with node.getRootNode() This removes the (default disabled) node.rootNode readonly attribute and replaces it with a node.getRootNode() function per WhatWG spec discussion. Based on work by John Dai --- dom/base/nsINode.cpp | 25 ++++++ dom/base/nsINode.h | 10 ++- dom/tests/mochitest/webcomponents/mochitest.ini | 1 + .../mochitest/webcomponents/test_bug1269155.html | 89 ++++++++++++++++++++++ dom/webidl/Node.webidl | 9 ++- modules/libpref/init/all.js | 7 -- testing/web-platform/meta/dom/historical.html.ini | 4 - testing/web-platform/meta/dom/interfaces.html.ini | 57 -------------- .../web-platform/meta/dom/nodes/rootNode.html.ini | 15 ---- 9 files changed, 128 insertions(+), 89 deletions(-) create mode 100644 dom/tests/mochitest/webcomponents/test_bug1269155.html delete mode 100644 testing/web-platform/meta/dom/nodes/rootNode.html.ini diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp index 212110b72..355bf0ebf 100644 --- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp @@ -107,6 +107,7 @@ #include "GeometryUtils.h" #include "nsIAnimationObserver.h" #include "nsChildContentList.h" +#include "mozilla/dom/NodeBinding.h" #ifdef ACCESSIBILITY #include "mozilla/dom/AccessibleNode.h" @@ -250,6 +251,30 @@ nsINode::GetTextEditorRootContent(nsIEditor** aEditor) return nullptr; } +nsINode* nsINode::GetRootNode(const GetRootNodeOptions& aOptions) +{ + if (aOptions.mComposed) { + if (IsInComposedDoc() && GetComposedDoc()) { + return OwnerDoc(); + } + + nsINode* node = this; + ShadowRoot* shadowRootParent = nullptr; + while(node) { + node = node->SubtreeRoot(); + shadowRootParent = ShadowRoot::FromNode(node); + if (!shadowRootParent) { + break; + } + node = shadowRootParent->GetHost(); + } + + return node; + } + + return SubtreeRoot(); +} + nsINode* nsINode::SubtreeRoot() const { diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h index d82f5f899..43d44db60 100644 --- a/dom/base/nsINode.h +++ b/dom/base/nsINode.h @@ -83,6 +83,7 @@ template class Sequence; class Text; class TextOrElementOrDocument; struct DOMPointInit; +struct GetRootNodeOptions; } // namespace dom } // namespace mozilla @@ -942,10 +943,11 @@ public: */ nsINode* SubtreeRoot() const; - nsINode* RootNode() const - { - return SubtreeRoot(); - } + /* + * Get context object's shadow-including root if options's composed is true, + * and context object's root otherwise. + */ + nsINode* GetRootNode(const mozilla::dom::GetRootNodeOptions& aOptions); /** * See nsIDOMEventTarget diff --git a/dom/tests/mochitest/webcomponents/mochitest.ini b/dom/tests/mochitest/webcomponents/mochitest.ini index 496f7ea4d..f05140c57 100644 --- a/dom/tests/mochitest/webcomponents/mochitest.ini +++ b/dom/tests/mochitest/webcomponents/mochitest.ini @@ -46,3 +46,4 @@ support-files = [test_style_fallback_content.html] [test_unresolved_pseudo_class.html] [test_link_prefetch.html] +[test_bug1269155.html] diff --git a/dom/tests/mochitest/webcomponents/test_bug1269155.html b/dom/tests/mochitest/webcomponents/test_bug1269155.html new file mode 100644 index 000000000..f280ae1d2 --- /dev/null +++ b/dom/tests/mochitest/webcomponents/test_bug1269155.html @@ -0,0 +1,89 @@ + + + + + Test for Bug 1269155 + + + + + +Mozilla Bug 1269155 +

+ +
+
+
+ + + diff --git a/dom/webidl/Node.webidl b/dom/webidl/Node.webidl index 7d18899b0..0a14e3624 100644 --- a/dom/webidl/Node.webidl +++ b/dom/webidl/Node.webidl @@ -38,8 +38,8 @@ interface Node : EventTarget { readonly attribute boolean isConnected; [Pure] readonly attribute Document? ownerDocument; - [Pure, Pref="dom.node.rootNode.enabled"] - readonly attribute Node rootNode; + [Pure] + Node getRootNode(optional GetRootNodeOptions options); [Pure] readonly attribute Node? parentNode; [Pure] @@ -113,3 +113,8 @@ interface Node : EventTarget { readonly attribute AccessibleNode? accessibleNode; #endif }; + +dictionary GetRootNodeOptions { + boolean composed = false; +}; + diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index f3cf4c74e..5ee49372f 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -5383,13 +5383,6 @@ pref("layout.css.color-adjust.enabled", true); pref("dom.audiochannel.audioCompeting", false); pref("dom.audiochannel.audioCompeting.allAgents", false); -// Disable Node.rootNode in release builds. -#ifdef RELEASE_OR_BETA -pref("dom.node.rootNode.enabled", false); -#else -pref("dom.node.rootNode.enabled", true); -#endif - // Default media volume pref("media.default_volume", "1.0"); diff --git a/testing/web-platform/meta/dom/historical.html.ini b/testing/web-platform/meta/dom/historical.html.ini index 6894d4868..42c737606 100644 --- a/testing/web-platform/meta/dom/historical.html.ini +++ b/testing/web-platform/meta/dom/historical.html.ini @@ -12,7 +12,3 @@ expected: FAIL bug: 660660 - [Node member must be nuked: rootNode] - disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1303802 - bug: 1269155 - diff --git a/testing/web-platform/meta/dom/interfaces.html.ini b/testing/web-platform/meta/dom/interfaces.html.ini index 2a0c6da04..71833f090 100644 --- a/testing/web-platform/meta/dom/interfaces.html.ini +++ b/testing/web-platform/meta/dom/interfaces.html.ini @@ -1,6 +1,5 @@ [interfaces.html] type: testharness - prefs: [dom.node.rootNode.enabled:true] [Document interface: attribute origin] expected: FAIL @@ -48,60 +47,4 @@ [Text interface: document.createTextNode("abc") must inherit property "assignedSlot" with the proper type (2)] expected: FAIL - [Node interface: operation getRootNode(GetRootNodeOptions)] - expected: FAIL - - [Node interface: new Document() must inherit property "getRootNode" with the proper type (17)] - expected: FAIL - - [Node interface: calling getRootNode(GetRootNodeOptions) on new Document() with too few arguments must throw TypeError] - expected: FAIL - - [Node interface: xmlDoc must inherit property "getRootNode" with the proper type (17)] - expected: FAIL - - [Node interface: calling getRootNode(GetRootNodeOptions) on xmlDoc with too few arguments must throw TypeError] - expected: FAIL - - [Node interface: document.doctype must inherit property "getRootNode" with the proper type (17)] - expected: FAIL - - [Node interface: calling getRootNode(GetRootNodeOptions) on document.doctype with too few arguments must throw TypeError] - expected: FAIL - - [Node interface: document.createDocumentFragment() must inherit property "getRootNode" with the proper type (17)] - expected: FAIL - - [Node interface: calling getRootNode(GetRootNodeOptions) on document.createDocumentFragment() with too few arguments must throw TypeError] - expected: FAIL - - [Node interface: element must inherit property "getRootNode" with the proper type (17)] - expected: FAIL - - [Node interface: calling getRootNode(GetRootNodeOptions) on element with too few arguments must throw TypeError] - expected: FAIL - - [Node interface: document.querySelector("[id\]").attributes[0\] must inherit property "getRootNode" with the proper type (17)] - expected: FAIL - - [Node interface: calling getRootNode(GetRootNodeOptions) on document.querySelector("[id\]").attributes[0\] with too few arguments must throw TypeError] - expected: FAIL - - [Node interface: document.createTextNode("abc") must inherit property "getRootNode" with the proper type (17)] - expected: FAIL - - [Node interface: calling getRootNode(GetRootNodeOptions) on document.createTextNode("abc") with too few arguments must throw TypeError] - expected: FAIL - - [Node interface: xmlDoc.createProcessingInstruction("abc", "def") must inherit property "getRootNode" with the proper type (17)] - expected: FAIL - - [Node interface: calling getRootNode(GetRootNodeOptions) on xmlDoc.createProcessingInstruction("abc", "def") with too few arguments must throw TypeError] - expected: FAIL - - [Node interface: document.createComment("abc") must inherit property "getRootNode" with the proper type (17)] - expected: FAIL - - [Node interface: calling getRootNode(GetRootNodeOptions) on document.createComment("abc") with too few arguments must throw TypeError] - expected: FAIL diff --git a/testing/web-platform/meta/dom/nodes/rootNode.html.ini b/testing/web-platform/meta/dom/nodes/rootNode.html.ini deleted file mode 100644 index 59533aebb..000000000 --- a/testing/web-platform/meta/dom/nodes/rootNode.html.ini +++ /dev/null @@ -1,15 +0,0 @@ -[rootNode.html] - type: testharness - prefs: [dom.node.rootNode.enabled:true] - [getRootNode() must return the context object when it does not have any parent] - expected: FAIL - - [getRootNode() must return the parent node of the context object when the context object has a single ancestor not in a document] - expected: FAIL - - [getRootNode() must return the document when a node is in document] - expected: FAIL - - [getRootNode() must return a document fragment when a node is in the fragment] - expected: FAIL - -- cgit v1.2.3 From e9d41c99681e3a8d910e5490db61e9ff81f7a241 Mon Sep 17 00:00:00 2001 From: adeshkp Date: Mon, 8 Jun 2020 09:17:35 -0400 Subject: Issue #1542 - Fix a typo in wasm which was causing build failure on ARM --- js/src/wasm/WasmBaselineCompile.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/js/src/wasm/WasmBaselineCompile.cpp b/js/src/wasm/WasmBaselineCompile.cpp index 8dc5c104f..7162e3338 100644 --- a/js/src/wasm/WasmBaselineCompile.cpp +++ b/js/src/wasm/WasmBaselineCompile.cpp @@ -3391,7 +3391,7 @@ class BaseCompiler #ifdef JS_CODEGEN_ARM void loadI32(MemoryAccessDesc access, bool isSigned, RegI32 ptr, Register rt) { - if (access.byteSize() > 1 && IsUnaligned(ins->access())) { + if (access.byteSize() > 1 && IsUnaligned(access)) { masm.add32(HeapReg, ptr.reg); SecondScratchRegisterScope scratch(*this); masm.emitUnalignedLoad(isSigned, access.byteSize(), ptr.reg, scratch, rt, 0); @@ -3405,7 +3405,7 @@ class BaseCompiler void storeI32(MemoryAccessDesc access, RegI32 ptr, Register rt) { - if (access.byteSize() > 1 && IsUnaligned(ins->access())) { + if (access.byteSize() > 1 && IsUnaligned(access)) { masm.add32(HeapReg, ptr.reg); masm.emitUnalignedStore(access.byteSize(), ptr.reg, rt, 0); } else { @@ -3419,7 +3419,7 @@ class BaseCompiler void loadI64(MemoryAccessDesc access, RegI32 ptr, RegI64 dest) { - if (IsUnaligned(ins->access())) { + if (IsUnaligned(access)) { masm.add32(HeapReg, ptr.reg); SecondScratchRegisterScope scratch(*this); masm.emitUnalignedLoad(IsSigned(false), ByteSize(4), ptr.reg, scratch, dest.reg.low, @@ -3440,7 +3440,7 @@ class BaseCompiler void storeI64(MemoryAccessDesc access, RegI32 ptr, RegI64 src) { - if (IsUnaligned(ins->access())) { + if (IsUnaligned(access)) { masm.add32(HeapReg, ptr.reg); masm.emitUnalignedStore(ByteSize(4), ptr.reg, src.reg.low, 0); masm.emitUnalignedStore(ByteSize(4), ptr.reg, src.reg.high, 4); @@ -3459,7 +3459,7 @@ class BaseCompiler void loadF32(MemoryAccessDesc access, RegI32 ptr, RegF32 dest, RegI32 tmp1) { masm.add32(HeapReg, ptr.reg); - if (IsUnaligned(ins->access())) { + if (IsUnaligned(access)) { SecondScratchRegisterScope scratch(*this); masm.emitUnalignedLoad(IsSigned(false), ByteSize(4), ptr.reg, scratch, tmp1.reg, 0); masm.ma_vxfer(tmp1.reg, dest.reg); @@ -3473,7 +3473,7 @@ class BaseCompiler void storeF32(MemoryAccessDesc access, RegI32 ptr, RegF32 src, RegI32 tmp1) { masm.add32(HeapReg, ptr.reg); - if (IsUnaligned(ins->access())) { + if (IsUnaligned(access)) { masm.ma_vxfer(src.reg, tmp1.reg); masm.emitUnalignedStore(ByteSize(4), ptr.reg, tmp1.reg, 0); } else { @@ -3486,7 +3486,7 @@ class BaseCompiler void loadF64(MemoryAccessDesc access, RegI32 ptr, RegF64 dest, RegI32 tmp1, RegI32 tmp2) { masm.add32(HeapReg, ptr.reg); - if (IsUnaligned(ins->access())) { + if (IsUnaligned(access)) { SecondScratchRegisterScope scratch(*this); masm.emitUnalignedLoad(IsSigned(false), ByteSize(4), ptr.reg, scratch, tmp1.reg, 0); masm.emitUnalignedLoad(IsSigned(false), ByteSize(4), ptr.reg, scratch, tmp2.reg, 4); @@ -3501,7 +3501,7 @@ class BaseCompiler void storeF64(MemoryAccessDesc access, RegI32 ptr, RegF64 src, RegI32 tmp1, RegI32 tmp2) { masm.add32(HeapReg, ptr.reg); - if (IsUnaligned(ins->access())) { + if (IsUnaligned(access)) { masm.ma_vxfer(src.reg, tmp1.reg, tmp2.reg); masm.emitUnalignedStore(ByteSize(4), ptr.reg, tmp1.reg, 0); masm.emitUnalignedStore(ByteSize(4), ptr.reg, tmp2.reg, 4); -- cgit v1.2.3 From 2b41c85019bd5fd7e62556b5d5bf6ace2a6d6963 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 10 Jun 2020 21:08:58 +0000 Subject: Issue #1587 - Part 1: Implement FetchController/FetchSignal interface --- dom/fetch/FetchController.cpp | 110 +++++++++++++++++++++ dom/fetch/FetchController.h | 66 +++++++++++++ dom/fetch/FetchSignal.cpp | 72 ++++++++++++++ dom/fetch/FetchSignal.h | 47 +++++++++ dom/fetch/Request.h | 1 + dom/fetch/moz.build | 4 + .../mochitest/fetch/file_fetch_controller.html | 40 ++++++++ dom/tests/mochitest/fetch/mochitest.ini | 2 + .../mochitest/fetch/test_fetch_controller.html | 40 ++++++++ dom/webidl/FetchController.webidl | 15 +++ dom/webidl/FetchSignal.webidl | 13 +++ dom/webidl/Request.webidl | 3 + dom/webidl/moz.build | 2 + dom/workers/WorkerPrefs.h | 1 + 14 files changed, 416 insertions(+) create mode 100644 dom/fetch/FetchController.cpp create mode 100644 dom/fetch/FetchController.h create mode 100644 dom/fetch/FetchSignal.cpp create mode 100644 dom/fetch/FetchSignal.h create mode 100644 dom/tests/mochitest/fetch/file_fetch_controller.html create mode 100644 dom/tests/mochitest/fetch/test_fetch_controller.html create mode 100644 dom/webidl/FetchController.webidl create mode 100644 dom/webidl/FetchSignal.webidl diff --git a/dom/fetch/FetchController.cpp b/dom/fetch/FetchController.cpp new file mode 100644 index 000000000..b3d8a4d9c --- /dev/null +++ b/dom/fetch/FetchController.cpp @@ -0,0 +1,110 @@ +/* -*- 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 "FetchController.h" +#include "FetchSignal.h" +#include "mozilla/dom/FetchControllerBinding.h" +#include "WorkerPrivate.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FetchController, mGlobal, mSignal) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(FetchController) +NS_IMPL_CYCLE_COLLECTING_RELEASE(FetchController) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FetchController) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +/* static */ bool +FetchController::IsEnabled(JSContext* aCx, JSObject* aGlobal) +{ + if (NS_IsMainThread()) { + return Preferences::GetBool("dom.fetchController.enabled", false); + } + + using namespace workers; + + // Otherwise, check the pref via the WorkerPrivate + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); + if (!workerPrivate) { + return false; + } + + return workerPrivate->FetchControllerEnabled(); +} + +/* static */ already_AddRefed +FetchController::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) +{ + nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + RefPtr fetchController = new FetchController(global); + return fetchController.forget(); +} + +FetchController::FetchController(nsIGlobalObject* aGlobal) + : mGlobal(aGlobal) + , mAborted(false) +{} + +JSObject* +FetchController::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return FetchControllerBinding::Wrap(aCx, this, aGivenProto); +} + +nsIGlobalObject* +FetchController::GetParentObject() const +{ + return mGlobal; +} + +FetchSignal* +FetchController::Signal() +{ + if (!mSignal) { + mSignal = new FetchSignal(this, mAborted); + } + + return mSignal; +} + +void +FetchController::Abort() +{ + if (mAborted) { + return; + } + + mAborted = true; + + if (mSignal) { + mSignal->Abort(); + } +} + +void +FetchController::Follow(FetchSignal& aSignal) +{ + // TODO +} + +void +FetchController::Unfollow(FetchSignal& aSignal) +{ + // TODO +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/fetch/FetchController.h b/dom/fetch/FetchController.h new file mode 100644 index 000000000..854c6f974 --- /dev/null +++ b/dom/fetch/FetchController.h @@ -0,0 +1,66 @@ +/* -*- 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_FetchController_h +#define mozilla_dom_FetchController_h + +#include "mozilla/dom/BindingDeclarations.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "mozilla/ErrorResult.h" +#include "nsIGlobalObject.h" + +namespace mozilla { +namespace dom { + +class FetchSignal; + +class FetchController final : public nsISupports + , public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FetchController) + + static bool + IsEnabled(JSContext* aCx, JSObject* aGlobal); + + static already_AddRefed + Constructor(const GlobalObject& aGlobal, ErrorResult& aRv); + + explicit FetchController(nsIGlobalObject* aGlobal); + + JSObject* + WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + nsIGlobalObject* + GetParentObject() const; + + FetchSignal* + Signal(); + + void + Abort(); + + void + Follow(FetchSignal& aSignal); + + void + Unfollow(FetchSignal& aSignal); + +private: + ~FetchController() = default; + + nsCOMPtr mGlobal; + RefPtr mSignal; + + bool mAborted; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_FetchController_h diff --git a/dom/fetch/FetchSignal.cpp b/dom/fetch/FetchSignal.cpp new file mode 100644 index 000000000..4395dbcf2 --- /dev/null +++ b/dom/fetch/FetchSignal.cpp @@ -0,0 +1,72 @@ +/* -*- 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 "FetchSignal.h" +#include "FetchController.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/FetchSignalBinding.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_CLASS(FetchSignal) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FetchSignal, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mController) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FetchSignal, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mController) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FetchSignal) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(FetchSignal, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(FetchSignal, DOMEventTargetHelper) + +FetchSignal::FetchSignal(FetchController* aController, + bool aAborted) + : DOMEventTargetHelper(aController->GetParentObject()) + , mController(aController) + , mAborted(aAborted) +{} + +JSObject* +FetchSignal::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return FetchSignalBinding::Wrap(aCx, this, aGivenProto); +} + +bool +FetchSignal::Aborted() const +{ + return mAborted; +} + +void +FetchSignal::Abort() +{ + MOZ_ASSERT(!mAborted); + mAborted = true; + + EventInit init; + init.mBubbles = false; + init.mCancelable = false; + + // TODO which kind of event should we dispatch here? + + RefPtr event = + Event::Constructor(this, NS_LITERAL_STRING("abort"), init); + event->SetTrusted(true); + + DispatchDOMEvent(nullptr, event, nullptr, nullptr); +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/fetch/FetchSignal.h b/dom/fetch/FetchSignal.h new file mode 100644 index 000000000..5bb16b834 --- /dev/null +++ b/dom/fetch/FetchSignal.h @@ -0,0 +1,47 @@ +/* -*- 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_FetchSignal_h +#define mozilla_dom_FetchSignal_h + +#include "mozilla/DOMEventTargetHelper.h" + +namespace mozilla { +namespace dom { + +class FetchController; + +class FetchSignal final : public DOMEventTargetHelper +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchSignal, DOMEventTargetHelper) + + FetchSignal(FetchController* aController, bool aAborted); + + JSObject* + WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + bool + Aborted() const; + + void + Abort(); + + IMPL_EVENT_HANDLER(abort); + +private: + ~FetchSignal() = default; + + RefPtr mController; + + bool mAborted; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_FetchSignal_h diff --git a/dom/fetch/Request.h b/dom/fetch/Request.h index d33c74812..56a75e5af 100644 --- a/dom/fetch/Request.h +++ b/dom/fetch/Request.h @@ -12,6 +12,7 @@ #include "nsWrapperCache.h" #include "mozilla/dom/Fetch.h" +#include "mozilla/dom/FetchSignal.h" #include "mozilla/dom/InternalRequest.h" // Required here due to certain WebIDL enums/classes being declared in both // files. diff --git a/dom/fetch/moz.build b/dom/fetch/moz.build index be820ab57..757f857f2 100644 --- a/dom/fetch/moz.build +++ b/dom/fetch/moz.build @@ -7,8 +7,10 @@ EXPORTS.mozilla.dom += [ 'ChannelInfo.h', 'Fetch.h', + 'FetchController.h', 'FetchDriver.h', 'FetchIPCTypes.h', + 'FetchSignal.h', 'FetchUtil.h', 'Headers.h', 'InternalHeaders.h', @@ -27,7 +29,9 @@ UNIFIED_SOURCES += [ SOURCES += [ 'ChannelInfo.cpp', + 'FetchController.cpp', 'FetchDriver.cpp', + 'FetchSignal.cpp', 'FetchUtil.cpp', 'Headers.cpp', 'InternalHeaders.cpp', diff --git a/dom/tests/mochitest/fetch/file_fetch_controller.html b/dom/tests/mochitest/fetch/file_fetch_controller.html new file mode 100644 index 000000000..6efa2fe0a --- /dev/null +++ b/dom/tests/mochitest/fetch/file_fetch_controller.html @@ -0,0 +1,40 @@ + diff --git a/dom/tests/mochitest/fetch/mochitest.ini b/dom/tests/mochitest/fetch/mochitest.ini index cf4477463..6dba08f98 100644 --- a/dom/tests/mochitest/fetch/mochitest.ini +++ b/dom/tests/mochitest/fetch/mochitest.ini @@ -1,6 +1,7 @@ [DEFAULT] support-files = fetch_test_framework.js + file_fetch_controller.html test_fetch_basic.js test_fetch_basic_http.js test_fetch_cors.js @@ -41,6 +42,7 @@ support-files = [test_fetch_basic_http.html] [test_fetch_basic_http_sw_reroute.html] [test_fetch_basic_http_sw_empty_reroute.html] +[test_fetch_controller.html] [test_fetch_cors.html] skip-if = toolkit == 'android' # Bug 1210282 [test_fetch_cors_sw_reroute.html] diff --git a/dom/tests/mochitest/fetch/test_fetch_controller.html b/dom/tests/mochitest/fetch/test_fetch_controller.html new file mode 100644 index 000000000..812fb9161 --- /dev/null +++ b/dom/tests/mochitest/fetch/test_fetch_controller.html @@ -0,0 +1,40 @@ + + + + + Test FetchController + + + + + + + + diff --git a/dom/webidl/FetchController.webidl b/dom/webidl/FetchController.webidl new file mode 100644 index 000000000..c5b1cc6da --- /dev/null +++ b/dom/webidl/FetchController.webidl @@ -0,0 +1,15 @@ +/* -*- Mode: IDL; 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/. + */ + +[Constructor(), Exposed=(Window,Worker), + Func="FetchController::IsEnabled"] +interface FetchController { + readonly attribute FetchSignal signal; + + void abort(); + void follow(FetchSignal signal); + void unfollow(FetchSignal signal); +}; diff --git a/dom/webidl/FetchSignal.webidl b/dom/webidl/FetchSignal.webidl new file mode 100644 index 000000000..965355c20 --- /dev/null +++ b/dom/webidl/FetchSignal.webidl @@ -0,0 +1,13 @@ +/* -*- Mode: IDL; 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/. + */ + +[Exposed=(Window,Worker), + Func="FetchController::IsEnabled"] +interface FetchSignal : EventTarget { + readonly attribute boolean aborted; + + attribute EventHandler onabort; +}; diff --git a/dom/webidl/Request.webidl b/dom/webidl/Request.webidl index e29c084d0..00497456a 100644 --- a/dom/webidl/Request.webidl +++ b/dom/webidl/Request.webidl @@ -47,6 +47,9 @@ dictionary RequestInit { RequestCache cache; RequestRedirect redirect; DOMString integrity; + + [Func="FetchController::IsEnabled"] + FetchSignal signal; }; // Gecko currently does not ship RequestContext, so please don't use it in IDL diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 5e913585e..0843bac59 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -141,7 +141,9 @@ WEBIDL_FILES = [ 'ExtendableMessageEvent.webidl', 'FakePluginTagInit.webidl', 'Fetch.webidl', + 'FetchController.webidl', 'FetchEvent.webidl', + 'FetchSignal.webidl', 'File.webidl', 'FileList.webidl', 'FileMode.webidl', diff --git a/dom/workers/WorkerPrefs.h b/dom/workers/WorkerPrefs.h index 9a1be4801..d3e018b62 100644 --- a/dom/workers/WorkerPrefs.h +++ b/dom/workers/WorkerPrefs.h @@ -39,6 +39,7 @@ WORKER_SIMPLE_PREF("dom.push.enabled", PushEnabled, PUSH_ENABLED) WORKER_SIMPLE_PREF("dom.requestcontext.enabled", RequestContextEnabled, REQUESTCONTEXT_ENABLED) WORKER_SIMPLE_PREF("gfx.offscreencanvas.enabled", OffscreenCanvasEnabled, OFFSCREENCANVAS_ENABLED) WORKER_SIMPLE_PREF("dom.webkitBlink.dirPicker.enabled", WebkitBlinkDirectoryPickerEnabled, DOM_WEBKITBLINK_DIRPICKER_WEBKITBLINK) +WORKER_SIMPLE_PREF("dom.fetchController.enabled", FetchControllerEnabled, FETCHCONTROLLER_ENABLED) WORKER_PREF("dom.workers.latestJSVersion", JSVersionChanged) WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) WORKER_PREF("general.appname.override", AppNameOverrideChanged) -- cgit v1.2.3 From 9e2da53a02356244c5dc0a3e7b7ec916740d3d51 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 10 Jun 2020 21:33:28 +0000 Subject: Issue #1587 - Part 2: Implement controller follow/unfollow --- dom/fetch/FetchController.cpp | 23 ++++++- dom/fetch/FetchController.h | 11 +++- dom/fetch/FetchSignal.cpp | 77 +++++++++++++++++++++- dom/fetch/FetchSignal.h | 31 +++++++++ .../mochitest/fetch/file_fetch_controller.html | 68 +++++++++++++++++++ 5 files changed, 202 insertions(+), 8 deletions(-) diff --git a/dom/fetch/FetchController.cpp b/dom/fetch/FetchController.cpp index b3d8a4d9c..2eb40b980 100644 --- a/dom/fetch/FetchController.cpp +++ b/dom/fetch/FetchController.cpp @@ -12,7 +12,8 @@ namespace mozilla { namespace dom { -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FetchController, mGlobal, mSignal) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FetchController, mGlobal, mSignal, + mFollowingSignal) NS_IMPL_CYCLE_COLLECTING_ADDREF(FetchController) NS_IMPL_CYCLE_COLLECTING_RELEASE(FetchController) @@ -97,13 +98,29 @@ FetchController::Abort() void FetchController::Follow(FetchSignal& aSignal) { - // TODO + FetchSignal::Follower::Follow(&aSignal); } void FetchController::Unfollow(FetchSignal& aSignal) { - // TODO + if (mFollowingSignal != &aSignal) { + return; + } + + FetchSignal::Follower::Unfollow(); +} + +FetchSignal* +FetchController::Following() const +{ + return mFollowingSignal; +} + +void +FetchController::Aborted() +{ + Abort(); } } // dom namespace diff --git a/dom/fetch/FetchController.h b/dom/fetch/FetchController.h index 854c6f974..7a0132dca 100644 --- a/dom/fetch/FetchController.h +++ b/dom/fetch/FetchController.h @@ -8,6 +8,7 @@ #define mozilla_dom_FetchController_h #include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/FetchSignal.h" #include "nsCycleCollectionParticipant.h" #include "nsWrapperCache.h" #include "mozilla/ErrorResult.h" @@ -16,10 +17,9 @@ namespace mozilla { namespace dom { -class FetchSignal; - class FetchController final : public nsISupports , public nsWrapperCache + , public FetchSignal::Follower { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS @@ -51,6 +51,13 @@ public: void Unfollow(FetchSignal& aSignal); + FetchSignal* + Following() const; + + // FetchSignal::Follower + + void Aborted() override; + private: ~FetchController() = default; diff --git a/dom/fetch/FetchSignal.cpp b/dom/fetch/FetchSignal.cpp index 4395dbcf2..1924263e8 100644 --- a/dom/fetch/FetchSignal.cpp +++ b/dom/fetch/FetchSignal.cpp @@ -16,12 +16,12 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(FetchSignal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FetchSignal, DOMEventTargetHelper) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mController) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mController) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FetchSignal, DOMEventTargetHelper) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mController) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mController) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FetchSignal) @@ -55,6 +55,11 @@ FetchSignal::Abort() MOZ_ASSERT(!mAborted); mAborted = true; + // Let's inform the followers. + for (uint32_t i = 0; i < mFollowers.Length(); ++i) { + mFollowers[i]->Aborted(); + } + EventInit init; init.mBubbles = false; init.mCancelable = false; @@ -65,7 +70,73 @@ FetchSignal::Abort() Event::Constructor(this, NS_LITERAL_STRING("abort"), init); event->SetTrusted(true); - DispatchDOMEvent(nullptr, event, nullptr, nullptr); + bool dummy; + DispatchEvent(event, &dummy); +} + +void +FetchSignal::AddFollower(FetchSignal::Follower* aFollower) +{ + MOZ_DIAGNOSTIC_ASSERT(aFollower); + if (!mFollowers.Contains(aFollower)) { + mFollowers.AppendElement(aFollower); + } +} + +void +FetchSignal::RemoveFollower(FetchSignal::Follower* aFollower) +{ + MOZ_DIAGNOSTIC_ASSERT(aFollower); + mFollowers.RemoveElement(aFollower); +} + +bool +FetchSignal::CanAcceptFollower(FetchSignal::Follower* aFollower) const +{ + MOZ_DIAGNOSTIC_ASSERT(aFollower); + + if (aFollower == mController) { + return false; + } + + FetchSignal* following = mController->Following(); + if (!following) { + return true; + } + + return following->CanAcceptFollower(aFollower); +} + +// FetchSignal::Follower +// ---------------------------------------------------------------------------- + +FetchSignal::Follower::~Follower() +{ + Unfollow(); +} + +void +FetchSignal::Follower::Follow(FetchSignal* aSignal) +{ + MOZ_DIAGNOSTIC_ASSERT(aSignal); + + if (!aSignal->CanAcceptFollower(this)) { + return; + } + + Unfollow(); + + mFollowingSignal = aSignal; + aSignal->AddFollower(this); +} + +void +FetchSignal::Follower::Unfollow() +{ + if (mFollowingSignal) { + mFollowingSignal->RemoveFollower(this); + mFollowingSignal = nullptr; + } } } // dom namespace diff --git a/dom/fetch/FetchSignal.h b/dom/fetch/FetchSignal.h index 5bb16b834..5d2f13c68 100644 --- a/dom/fetch/FetchSignal.h +++ b/dom/fetch/FetchSignal.h @@ -13,10 +13,29 @@ namespace mozilla { namespace dom { class FetchController; +class FetchSignal; class FetchSignal final : public DOMEventTargetHelper { public: + // This class must be implemented by objects who want to follow a FetchSignal. + class Follower + { + public: + virtual void Aborted() = 0; + + protected: + virtual ~Follower(); + + void + Follow(FetchSignal* aSignal); + + void + Unfollow(); + + RefPtr mFollowingSignal; + }; + NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchSignal, DOMEventTargetHelper) @@ -33,11 +52,23 @@ public: IMPL_EVENT_HANDLER(abort); + void + AddFollower(Follower* aFollower); + + void + RemoveFollower(Follower* aFollower); + + bool + CanAcceptFollower(Follower* aFollower) const; + private: ~FetchSignal() = default; RefPtr mController; + // Raw pointers. Follower unregisters itself in the DTOR. + nsTArray mFollowers; + bool mAborted; }; diff --git a/dom/tests/mochitest/fetch/file_fetch_controller.html b/dom/tests/mochitest/fetch/file_fetch_controller.html index 6efa2fe0a..791d21b2b 100644 --- a/dom/tests/mochitest/fetch/file_fetch_controller.html +++ b/dom/tests/mochitest/fetch/file_fetch_controller.html @@ -21,8 +21,76 @@ function testWebIDL() { next(); } +function testUpdateData() { + var fc = new FetchController(); + + is(fc.signal.aborted, false, "By default FetchSignal.aborted is false"); + + fc.abort(); + is(fc.signal.aborted, true, "Signal is aborted"); + + next(); +} + +function testFollowingOurself() { + // Let's follow ourself + var fc = new FetchController(); + fc.follow(fc.signal); + + fc.abort(); + is(fc.signal.aborted, true, "Signal is aborted"); + + next(); +} + +function testFollowingOther() { + // Let's follow another one + var fc1 = new FetchController(); + var fc2 = new FetchController(); + fc1.follow(fc2.signal); + + fc2.abort(); + + is(fc1.signal.aborted, true, "Signal is aborted"); + is(fc2.signal.aborted, true, "Signal is aborted"); + + next(); +} + +function testFollowingLoop() { + // fc1 -> fc2 -> fc3 -> fc1 + var fc1 = new FetchController(); + var fc2 = new FetchController(); + var fc3 = new FetchController(); + fc1.follow(fc2.signal); + fc2.follow(fc3.signal); + fc3.follow(fc1.signal); + + fc3.abort(); + + is(fc1.signal.aborted, true, "Signal is aborted"); + is(fc2.signal.aborted, true, "Signal is aborted"); + is(fc3.signal.aborted, true, "Signal is aborted"); + + next(); +} + +function testAbortEvent() { + var fc = new FetchController(); + fc.signal.onabort = function(e) { + is(e.type, "abort", "Abort received"); + next(); + } + fc.abort(); +} + var steps = [ testWebIDL, + testUpdateData, + testFollowingOurself, + testFollowingOther, + testFollowingLoop, + testAbortEvent, ]; function next() { -- cgit v1.2.3 From a04d67dd12d4999b28e74744150770482a3c5a3d Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 10 Jun 2020 21:51:44 +0000 Subject: Issue #1587 - Part 3: Hook FetchSignal up to the Fetch API --- dom/fetch/Fetch.cpp | 158 ++++++++++++++++++--- dom/fetch/FetchDriver.cpp | 45 ++++-- dom/fetch/FetchDriver.h | 29 +++- dom/fetch/FetchSignal.cpp | 8 ++ dom/fetch/FetchSignal.h | 1 + .../mochitest/fetch/file_fetch_controller.html | 81 +++++++++++ 6 files changed, 292 insertions(+), 30 deletions(-) diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp index f944352e3..11e93205c 100644 --- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp @@ -52,41 +52,137 @@ namespace dom { using namespace workers; +// This class helps the proxying of FetchSignal changes cross threads. +class FetchSignalProxy final : public FetchSignal::Follower +{ + // This is created and released on the main-thread. + RefPtr mSignalMainThread; + + // This value is used only for the creation of FetchSignal on the + // main-thread. They are not updated. + const bool mAborted; + + // This runnable propagates changes from the FetchSignal on workers to the + // FetchSignal on main-thread. + class FetchSignalProxyRunnable final : public Runnable + { + RefPtr mProxy; + + public: + explicit FetchSignalProxyRunnable(FetchSignalProxy* aProxy) + : mProxy(aProxy) + {} + + NS_IMETHOD + Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + FetchSignal* signal = mProxy->GetOrCreateSignalForMainThread(); + signal->Abort(); + return NS_OK; + } + }; + +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FetchSignalProxy) + + explicit FetchSignalProxy(FetchSignal* aSignal) + : mAborted(aSignal->Aborted()) + { + Follow(aSignal); + } + + void + Aborted() override + { + RefPtr runnable = + new FetchSignalProxyRunnable(this); + NS_DispatchToMainThread(runnable); + } + + FetchSignal* + GetOrCreateSignalForMainThread() + { + MOZ_ASSERT(NS_IsMainThread()); + if (!mSignalMainThread) { + mSignalMainThread = new FetchSignal(mAborted); + } + return mSignalMainThread; + } + + void + Shutdown() + { + Unfollow(); + } + +private: + ~FetchSignalProxy() + { + NS_ReleaseOnMainThread(mSignalMainThread.forget()); + } +}; + class WorkerFetchResolver final : public FetchDriverObserver { friend class MainThreadFetchRunnable; + friend class WorkerFetchResponseEndBase; friend class WorkerFetchResponseEndRunnable; friend class WorkerFetchResponseRunnable; RefPtr mPromiseProxy; + RefPtr mSignalProxy; + public: // Returns null if worker is shutting down. static already_AddRefed - Create(workers::WorkerPrivate* aWorkerPrivate, Promise* aPromise) + Create(workers::WorkerPrivate* aWorkerPrivate, Promise* aPromise, + FetchSignal* aSignal) { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); - RefPtr proxy = PromiseWorkerProxy::Create(aWorkerPrivate, aPromise); + RefPtr proxy = + PromiseWorkerProxy::Create(aWorkerPrivate, aPromise); if (!proxy) { return nullptr; } - RefPtr r = new WorkerFetchResolver(proxy); + RefPtr signalProxy; + if (aSignal) { + signalProxy = new FetchSignalProxy(aSignal); + } + + RefPtr r = new WorkerFetchResolver(proxy, signalProxy); return r.forget(); } + FetchSignal* + GetFetchSignal() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mSignalProxy) { + return nullptr; + } + + return mSignalProxy->GetOrCreateSignalForMainThread(); + } + void OnResponseAvailableInternal(InternalResponse* aResponse) override; void - OnResponseEnd() override; + OnResponseEnd(FetchDriverObserver::EndReason eReason) override; private: - explicit WorkerFetchResolver(PromiseWorkerProxy* aProxy) + WorkerFetchResolver(PromiseWorkerProxy* aProxy, + FetchSignalProxy* aSignalProxy) : mPromiseProxy(aProxy) + , mSignalProxy(aSignalProxy) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(mPromiseProxy); + } ~WorkerFetchResolver() @@ -115,8 +211,12 @@ public: mDocument = aDocument; } - virtual void OnResponseEnd() override + void OnResponseEnd(FetchDriverObserver::EndReason aReason) override { + if (aReason == eAborted) { + mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + } + FlushConsoleReport(); } @@ -170,9 +270,11 @@ public: fetch->SetWorkerScript(spec); } + RefPtr signal = mResolver->GetFetchSignal(); + // ...but release it before calling Fetch, because mResolver's callback can // be called synchronously and they want the mutex, too. - return fetch->Fetch(mResolver); + return fetch->Fetch(signal, mResolver); } }; @@ -210,6 +312,12 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, RefPtr r = request->GetInternalRequest(); + RefPtr signal; + if (aInit.mSignal.WasPassed()) { + signal = &aInit.mSignal.Value(); + // Let's FetchDriver to deal with an already aborted signal. + } + if (NS_IsMainThread()) { nsCOMPtr window = do_QueryInterface(aGlobal); nsCOMPtr doc; @@ -240,7 +348,7 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, RefPtr fetch = new FetchDriver(r, principal, loadGroup); fetch->SetDocument(doc); resolver->SetDocument(doc); - aRv = fetch->Fetch(resolver); + aRv = fetch->Fetch(signal, resolver); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } @@ -252,7 +360,7 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, r->SetSkipServiceWorker(); } - RefPtr resolver = WorkerFetchResolver::Create(worker, p); + RefPtr resolver = WorkerFetchResolver::Create(worker, p, signal); if (!resolver) { NS_WARNING("Could not add WorkerFetchResolver workerHolder to worker"); aRv.Throw(NS_ERROR_DOM_ABORT_ERR); @@ -306,6 +414,7 @@ public: , mResolver(aResolver) , mInternalResponse(aResponse) { + MOZ_ASSERT(mResolver); } bool @@ -332,9 +441,13 @@ public: class WorkerFetchResponseEndBase { RefPtr mPromiseProxy; + RefPtr mSignalProxy; + public: - explicit WorkerFetchResponseEndBase(PromiseWorkerProxy* aPromiseProxy) + WorkerFetchResponseEndBase(PromiseWorkerProxy* aPromiseProxy, + FetchSignalProxy* aSignalProxy) : mPromiseProxy(aPromiseProxy) + , mSignalProxy(aSignalProxy) { MOZ_ASSERT(mPromiseProxy); } @@ -344,7 +457,16 @@ public: { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); + + RefPtr promise = mPromiseProxy->WorkerPromise(); + promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + mPromiseProxy->CleanUp(); + + if (mSignalProxy) { + mSignalProxy->Shutdown(); + mSignalProxy = nullptr; + } } }; @@ -352,9 +474,10 @@ class WorkerFetchResponseEndRunnable final : public MainThreadWorkerRunnable , public WorkerFetchResponseEndBase { public: - explicit WorkerFetchResponseEndRunnable(PromiseWorkerProxy* aPromiseProxy) + WorkerFetchResponseEndRunnable(PromiseWorkerProxy* aPromiseProxy, + FetchSignalProxy* aSignalProxy) : MainThreadWorkerRunnable(aPromiseProxy->GetWorkerPrivate()) - , WorkerFetchResponseEndBase(aPromiseProxy) + , WorkerFetchResponseEndBase(aPromiseProxy, aSignalProxy) { } @@ -379,9 +502,10 @@ class WorkerFetchResponseEndControlRunnable final : public MainThreadWorkerContr , public WorkerFetchResponseEndBase { public: - explicit WorkerFetchResponseEndControlRunnable(PromiseWorkerProxy* aPromiseProxy) + WorkerFetchResponseEndControlRunnable(PromiseWorkerProxy* aPromiseProxy, + FetchSignalProxy* aSignalProxy) : MainThreadWorkerControlRunnable(aPromiseProxy->GetWorkerPrivate()) - , WorkerFetchResponseEndBase(aPromiseProxy) + , WorkerFetchResponseEndBase(aPromiseProxy, aSignalProxy) { } @@ -415,7 +539,7 @@ WorkerFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse) } void -WorkerFetchResolver::OnResponseEnd() +WorkerFetchResolver::OnResponseEnd(FetchDriverObserver::EndReason aReason) { AssertIsOnMainThread(); MutexAutoLock lock(mPromiseProxy->Lock()); @@ -426,11 +550,11 @@ WorkerFetchResolver::OnResponseEnd() FlushConsoleReport(); RefPtr r = - new WorkerFetchResponseEndRunnable(mPromiseProxy); + new WorkerFetchResponseEndRunnable(mPromiseProxy, mSignalProxy); if (!r->Dispatch()) { RefPtr cr = - new WorkerFetchResponseEndControlRunnable(mPromiseProxy); + new WorkerFetchResponseEndControlRunnable(mPromiseProxy, mSignalProxy); // This can fail if the worker thread is canceled or killed causing // the PromiseWorkerProxy to give up its WorkerHolder immediately, // allowing the worker thread to become Dead. diff --git a/dom/fetch/FetchDriver.cpp b/dom/fetch/FetchDriver.cpp index 6294b0dc5..448ec64cd 100644 --- a/dom/fetch/FetchDriver.cpp +++ b/dom/fetch/FetchDriver.cpp @@ -67,7 +67,7 @@ FetchDriver::~FetchDriver() } nsresult -FetchDriver::Fetch(FetchDriverObserver* aObserver) +FetchDriver::Fetch(FetchSignal* aSignal, FetchDriverObserver* aObserver) { workers::AssertIsOnMainThread(); #ifdef DEBUG @@ -90,6 +90,18 @@ FetchDriver::Fetch(FetchDriverObserver* aObserver) } mRequest->SetPrincipalInfo(Move(principalInfo)); + + // If the signal is aborted, it's time to inform the observer and terminate + // the operation. + if (aSignal) { + if (aSignal->Aborted()) { + Aborted(); + return NS_OK; + } + + Follow(aSignal); + } + if (NS_FAILED(HttpFetch())) { FailWithNetworkError(); } @@ -114,11 +126,7 @@ FetchDriver::HttpFetch() nsAutoCString url; mRequest->GetURL(url); nsCOMPtr uri; - rv = NS_NewURI(getter_AddRefs(uri), - url, - nullptr, - nullptr, - ios); + rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, nullptr, ios); NS_ENSURE_SUCCESS(rv, rv); // Unsafe requests aren't allowed with when using no-core mode. @@ -380,6 +388,8 @@ FetchDriver::HttpFetch() NS_ENSURE_SUCCESS(rv, rv); // Step 4 onwards of "HTTP Fetch" is handled internally by Necko. + + mChannel = chan; return NS_OK; } already_AddRefed @@ -433,9 +443,11 @@ FetchDriver::FailWithNetworkError() #ifdef DEBUG mResponseAvailableCalled = true; #endif - mObserver->OnResponseEnd(); + mObserver->OnResponseEnd(FetchDriverObserver::eByNetworking); mObserver = nullptr; } + + mChannel = nullptr; } namespace { @@ -777,10 +789,11 @@ FetchDriver::OnStopRequest(nsIRequest* aRequest, #endif } - mObserver->OnResponseEnd(); + mObserver->OnResponseEnd(FetchDriverObserver::eByNetworking); mObserver = nullptr; } + mChannel = nullptr; return NS_OK; } @@ -921,5 +934,21 @@ FetchDriver::SetRequestHeaders(nsIHttpChannel* aChannel) const } } +void FetchDriver::Aborted() +{ + if (mObserver) { +#ifdef DEBUG + mResponseAvailableCalled = true; +#endif + mObserver->OnResponseEnd(FetchDriverObserver::eAborted); + mObserver = nullptr; + } + + if (mChannel) { + mChannel->Cancel(NS_BINDING_ABORTED); + mChannel = nullptr; + } +} + } // namespace dom } // namespace mozilla diff --git a/dom/fetch/FetchDriver.h b/dom/fetch/FetchDriver.h index f74298a48..0ca9a34ee 100644 --- a/dom/fetch/FetchDriver.h +++ b/dom/fetch/FetchDriver.h @@ -12,6 +12,7 @@ #include "nsIStreamListener.h" #include "nsIThreadRetargetableStreamListener.h" #include "mozilla/ConsoleReportCollector.h" +#include "mozilla/dom/FetchSignal.h" #include "mozilla/dom/SRIMetadata.h" #include "mozilla/RefPtr.h" @@ -49,7 +50,14 @@ public: mGotResponseAvailable = true; OnResponseAvailableInternal(aResponse); } - virtual void OnResponseEnd() + + enum EndReason + { + eAborted, + eByNetworking, + }; + + virtual void OnResponseEnd(EndReason aReason) { }; nsIConsoleReportCollector* GetReporter() const @@ -58,6 +66,7 @@ public: } virtual void FlushConsoleReport() = 0; + protected: virtual ~FetchDriverObserver() { }; @@ -72,7 +81,8 @@ private: class FetchDriver final : public nsIStreamListener, public nsIChannelEventSink, public nsIInterfaceRequestor, - public nsIThreadRetargetableStreamListener + public nsIThreadRetargetableStreamListener, + public FetchSignal::Follower { public: NS_DECL_ISUPPORTS @@ -82,9 +92,12 @@ public: NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER - explicit FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal, - nsILoadGroup* aLoadGroup); - NS_IMETHOD Fetch(FetchDriverObserver* aObserver); + FetchDriver(InternalRequest* aRequest, + nsIPrincipal* aPrincipal, + nsILoadGroup* aLoadGroup); + + nsresult Fetch(FetchSignal* aSignal, + FetchDriverObserver* aObserver); void SetDocument(nsIDocument* aDocument); @@ -96,6 +109,11 @@ public: mWorkerScript = aWorkerScirpt; } + // FetchSignal::Follower + + void + Aborted() override; + private: nsCOMPtr mPrincipal; nsCOMPtr mLoadGroup; @@ -104,6 +122,7 @@ private: nsCOMPtr mPipeOutputStream; RefPtr mObserver; nsCOMPtr mDocument; + nsCOMPtr mChannel; nsAutoPtr mSRIDataVerifier; SRIMetadata mSRIMetadata; nsCString mWorkerScript; diff --git a/dom/fetch/FetchSignal.cpp b/dom/fetch/FetchSignal.cpp index 1924263e8..07ad6b53d 100644 --- a/dom/fetch/FetchSignal.cpp +++ b/dom/fetch/FetchSignal.cpp @@ -37,6 +37,10 @@ FetchSignal::FetchSignal(FetchController* aController, , mAborted(aAborted) {} +FetchSignal::FetchSignal(bool aAborted) + : mAborted(aAborted) +{} + JSObject* FetchSignal::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { @@ -95,6 +99,10 @@ FetchSignal::CanAcceptFollower(FetchSignal::Follower* aFollower) const { MOZ_DIAGNOSTIC_ASSERT(aFollower); + if (!mController) { + return true; + } + if (aFollower == mController) { return false; } diff --git a/dom/fetch/FetchSignal.h b/dom/fetch/FetchSignal.h index 5d2f13c68..4970f03de 100644 --- a/dom/fetch/FetchSignal.h +++ b/dom/fetch/FetchSignal.h @@ -40,6 +40,7 @@ public: NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchSignal, DOMEventTargetHelper) FetchSignal(FetchController* aController, bool aAborted); + explicit FetchSignal(bool aAborted); JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; diff --git a/dom/tests/mochitest/fetch/file_fetch_controller.html b/dom/tests/mochitest/fetch/file_fetch_controller.html index 791d21b2b..026ff16a8 100644 --- a/dom/tests/mochitest/fetch/file_fetch_controller.html +++ b/dom/tests/mochitest/fetch/file_fetch_controller.html @@ -84,13 +84,94 @@ function testAbortEvent() { fc.abort(); } +function testAbortedFetch() { + var fc = new FetchController(); + fc.abort(); + + fetch('data:,foo', { signal: fc.signal }).then(() => { + ok(false, "Fetch should not return a resolved promise"); + }, e => { + is(e.name, "AbortError", "We have an abort error"); + }).then(next); +} + +function testFetchAndAbort() { + var fc = new FetchController(); + + var p = fetch('data:,foo', { signal: fc.signal }); + fc.abort(); + + p.then(() => { + ok(false, "Fetch should not return a resolved promise"); + }, e => { + is(e.name, "AbortError", "We have an abort error"); + }).then(next); +} + +function testWorkerAbortedFetch() { + function worker() { + var fc = new FetchController(); + fc.abort(); + + fetch('data:,foo', { signal: fc.signal }).then(() => { + postMessage(false); + }, e => { + postMessage(e.name == "AbortError"); + }); + } + + var str = worker.toString(); + var content = str.substring(0, str.length - 1).split('\n').splice(1).join(' '); + var url = URL.createObjectURL(new Blob([content], { type: "application/javascript" })); + var w = new Worker(url); + w.onmessage = function(e) { + ok(e.data, "Abort + Fetch works in workers"); + next(); + } +} + +function testWorkerFetchAndAbort() { + function worker() { + var fc = new FetchController(); + + var p = fetch('data:,foo', { signal: fc.signal }); + fc.abort(); + + p.then(() => { + postMessage(false); + }, e => { + postMessage(e.name == "AbortError"); + }); + } + + var str = worker.toString(); + var content = str.substring(0, str.length - 1).split('\n').splice(1).join(' '); + var url = URL.createObjectURL(new Blob([content], { type: "application/javascript" })); + var w = new Worker(url); + w.onmessage = function(e) { + ok(e.data, "Abort + Fetch works in workers"); + next(); + } +} + var steps = [ + // Simple stuff testWebIDL, testUpdateData, + + // Following algorithm testFollowingOurself, testFollowingOther, testFollowingLoop, + + // Event propagation testAbortEvent, + + // fetch + signaling + testAbortedFetch, + testFetchAndAbort, + testWorkerAbortedFetch, + testWorkerFetchAndAbort, ]; function next() { -- cgit v1.2.3 From 3f3d626434b2533182ca0ee90adc92a1e7db373c Mon Sep 17 00:00:00 2001 From: Moonchild Date: Thu, 11 Jun 2020 08:22:04 +0000 Subject: Issue #1587 - Part 4: Implement FetchObserver --- dom/base/nsGkAtomList.h | 2 + dom/fetch/FetchObserver.cpp | 66 ++++++++++++++++++++++ dom/fetch/FetchObserver.h | 46 +++++++++++++++ dom/fetch/moz.build | 2 + dom/tests/mochitest/fetch/file_fetch_observer.html | 33 +++++++++++ dom/tests/mochitest/fetch/mochitest.ini | 2 + dom/tests/mochitest/fetch/test_fetch_observer.html | 40 +++++++++++++ dom/webidl/FetchObserver.webidl | 27 +++++++++ dom/webidl/Request.webidl | 3 + dom/webidl/moz.build | 1 + dom/workers/WorkerPrefs.h | 1 + 11 files changed, 223 insertions(+) create mode 100644 dom/fetch/FetchObserver.cpp create mode 100644 dom/fetch/FetchObserver.h create mode 100644 dom/tests/mochitest/fetch/file_fetch_observer.html create mode 100644 dom/tests/mochitest/fetch/test_fetch_observer.html create mode 100644 dom/webidl/FetchObserver.webidl diff --git a/dom/base/nsGkAtomList.h b/dom/base/nsGkAtomList.h index 73a3a02b1..96f5acf3a 100644 --- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -910,7 +910,9 @@ GK_ATOM(onreadystatechange, "onreadystatechange") GK_ATOM(onreceived, "onreceived") GK_ATOM(onremoteheld, "onremoteheld") GK_ATOM(onremoteresumed, "onremoteresumed") +GK_ATOM(onrequestprogress, "onrequestprogress") GK_ATOM(onresourcetimingbufferfull, "onresourcetimingbufferfull") +GK_ATOM(onresponseprogress, "onresponseprogress") GK_ATOM(onretrieving, "onretrieving") GK_ATOM(onRequest, "onRequest") GK_ATOM(onrequestmediaplaystatus, "onrequestmediaplaystatus") diff --git a/dom/fetch/FetchObserver.cpp b/dom/fetch/FetchObserver.cpp new file mode 100644 index 000000000..bc8c6fc2b --- /dev/null +++ b/dom/fetch/FetchObserver.cpp @@ -0,0 +1,66 @@ +/* -*- 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 "FetchObserver.h" +#include "WorkerPrivate.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_CLASS(FetchObserver) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FetchObserver, + DOMEventTargetHelper) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FetchObserver, + DOMEventTargetHelper) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FetchObserver) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(FetchObserver, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(FetchObserver, DOMEventTargetHelper) + +/* static */ bool +FetchObserver::IsEnabled(JSContext* aCx, JSObject* aGlobal) +{ + if (NS_IsMainThread()) { + return Preferences::GetBool("dom.fetchObserver.enabled", false); + } + + using namespace workers; + + // Otherwise, check the pref via the WorkerPrivate + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); + if (!workerPrivate) { + return false; + } + + return workerPrivate->FetchObserverEnabled(); +} + +FetchObserver::FetchObserver(nsIGlobalObject* aGlobal, + FetchState aState) + : DOMEventTargetHelper(aGlobal) + , mState(aState) +{} + +JSObject* +FetchObserver::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return FetchObserverBinding::Wrap(aCx, this, aGivenProto); +} + +FetchState +FetchObserver::State() const +{ + return mState; +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/fetch/FetchObserver.h b/dom/fetch/FetchObserver.h new file mode 100644 index 000000000..81f8e7b09 --- /dev/null +++ b/dom/fetch/FetchObserver.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_FetchObserver_h +#define mozilla_dom_FetchObserver_h + +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/dom/FetchObserverBinding.h" + +namespace mozilla { +namespace dom { + +class FetchObserver final : public DOMEventTargetHelper +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchObserver, DOMEventTargetHelper) + + static bool + IsEnabled(JSContext* aCx, JSObject* aGlobal); + + FetchObserver(nsIGlobalObject* aGlobal, FetchState aState); + + JSObject* + WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + FetchState + State() const; + + IMPL_EVENT_HANDLER(statechange); + IMPL_EVENT_HANDLER(requestprogress); + IMPL_EVENT_HANDLER(responseprogress); + +private: + ~FetchObserver() = default; + + FetchState mState; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_FetchObserver_h diff --git a/dom/fetch/moz.build b/dom/fetch/moz.build index 757f857f2..82fd99173 100644 --- a/dom/fetch/moz.build +++ b/dom/fetch/moz.build @@ -10,6 +10,7 @@ EXPORTS.mozilla.dom += [ 'FetchController.h', 'FetchDriver.h', 'FetchIPCTypes.h', + 'FetchObserver.h', 'FetchSignal.h', 'FetchUtil.h', 'Headers.h', @@ -31,6 +32,7 @@ SOURCES += [ 'ChannelInfo.cpp', 'FetchController.cpp', 'FetchDriver.cpp', + 'FetchObserver.cpp', 'FetchSignal.cpp', 'FetchUtil.cpp', 'Headers.cpp', diff --git a/dom/tests/mochitest/fetch/file_fetch_observer.html b/dom/tests/mochitest/fetch/file_fetch_observer.html new file mode 100644 index 000000000..97af584ec --- /dev/null +++ b/dom/tests/mochitest/fetch/file_fetch_observer.html @@ -0,0 +1,33 @@ + diff --git a/dom/tests/mochitest/fetch/mochitest.ini b/dom/tests/mochitest/fetch/mochitest.ini index 6dba08f98..60fa454f4 100644 --- a/dom/tests/mochitest/fetch/mochitest.ini +++ b/dom/tests/mochitest/fetch/mochitest.ini @@ -5,6 +5,7 @@ support-files = test_fetch_basic.js test_fetch_basic_http.js test_fetch_cors.js + file_fetch_observer.html test_formdataparsing.js test_headers_common.js test_request.js @@ -49,6 +50,7 @@ skip-if = toolkit == 'android' # Bug 1210282 skip-if = toolkit == 'android' # Bug 1210282 [test_fetch_cors_sw_empty_reroute.html] skip-if = toolkit == 'android' # Bug 1210282 +[test_fetch_observer.html] [test_formdataparsing.html] [test_formdataparsing_sw_reroute.html] [test_request.html] diff --git a/dom/tests/mochitest/fetch/test_fetch_observer.html b/dom/tests/mochitest/fetch/test_fetch_observer.html new file mode 100644 index 000000000..2b6c0362d --- /dev/null +++ b/dom/tests/mochitest/fetch/test_fetch_observer.html @@ -0,0 +1,40 @@ + + + + + Test FetchObserver + + + + + + + + diff --git a/dom/webidl/FetchObserver.webidl b/dom/webidl/FetchObserver.webidl new file mode 100644 index 000000000..eecd67e66 --- /dev/null +++ b/dom/webidl/FetchObserver.webidl @@ -0,0 +1,27 @@ +/* -*- Mode: IDL; 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/. + */ + +callback interface ObserverCallback { + void handleEvent(FetchObserver observer); +}; + +enum FetchState { + // Pending states + "requesting", "responding", + // Final states + "aborted", "errored", "complete" +}; + +[Exposed=(Window,Worker), + Func="FetchObserver::IsEnabled"] +interface FetchObserver : EventTarget { + readonly attribute FetchState state; + + // Events + attribute EventHandler onstatechange; + attribute EventHandler onrequestprogress; + attribute EventHandler onresponseprogress; +}; diff --git a/dom/webidl/Request.webidl b/dom/webidl/Request.webidl index 00497456a..8c6e33da3 100644 --- a/dom/webidl/Request.webidl +++ b/dom/webidl/Request.webidl @@ -50,6 +50,9 @@ dictionary RequestInit { [Func="FetchController::IsEnabled"] FetchSignal signal; + + [Func="FetchObserver::IsEnabled"] + ObserverCallback observe; }; // Gecko currently does not ship RequestContext, so please don't use it in IDL diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 0843bac59..0963e3d01 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -143,6 +143,7 @@ WEBIDL_FILES = [ 'Fetch.webidl', 'FetchController.webidl', 'FetchEvent.webidl', + 'FetchObserver.webidl', 'FetchSignal.webidl', 'File.webidl', 'FileList.webidl', diff --git a/dom/workers/WorkerPrefs.h b/dom/workers/WorkerPrefs.h index d3e018b62..374f41ecf 100644 --- a/dom/workers/WorkerPrefs.h +++ b/dom/workers/WorkerPrefs.h @@ -40,6 +40,7 @@ WORKER_SIMPLE_PREF("dom.requestcontext.enabled", RequestContextEnabled, REQUESTC WORKER_SIMPLE_PREF("gfx.offscreencanvas.enabled", OffscreenCanvasEnabled, OFFSCREENCANVAS_ENABLED) WORKER_SIMPLE_PREF("dom.webkitBlink.dirPicker.enabled", WebkitBlinkDirectoryPickerEnabled, DOM_WEBKITBLINK_DIRPICKER_WEBKITBLINK) WORKER_SIMPLE_PREF("dom.fetchController.enabled", FetchControllerEnabled, FETCHCONTROLLER_ENABLED) +WORKER_SIMPLE_PREF("dom.fetchObserver.enabled", FetchObserverEnabled, FETCHOBSERVER_ENABLED) WORKER_PREF("dom.workers.latestJSVersion", JSVersionChanged) WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) WORKER_PREF("general.appname.override", AppNameOverrideChanged) -- cgit v1.2.3 From bb7060582934f087b71702d1e83f762d5fd6f0af Mon Sep 17 00:00:00 2001 From: Moonchild Date: Thu, 11 Jun 2020 08:51:07 +0000 Subject: Issue #1587 - Part 5: Hook FetchObserver up to the Fetch API --- dom/fetch/Fetch.cpp | 170 ++++++++++++++++----- dom/fetch/FetchDriver.cpp | 37 +++++ dom/fetch/FetchDriver.h | 2 + dom/fetch/FetchObserver.cpp | 57 ++++++- dom/fetch/FetchObserver.h | 10 +- .../mochitest/fetch/file_fetch_controller.html | 40 +---- dom/tests/mochitest/fetch/file_fetch_observer.html | 115 +++++++++++++- dom/tests/mochitest/fetch/mochitest.ini | 2 + dom/tests/mochitest/fetch/slow.sjs | 11 ++ dom/tests/mochitest/fetch/test_fetch_observer.html | 3 +- .../mochitest/fetch/worker_fetch_controller.js | 27 ++++ 11 files changed, 399 insertions(+), 75 deletions(-) create mode 100644 dom/tests/mochitest/fetch/slow.sjs create mode 100644 dom/tests/mochitest/fetch/worker_fetch_controller.js diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp index 11e93205c..04aa7fd91 100644 --- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp @@ -39,6 +39,7 @@ #include "mozilla/dom/URLSearchParams.h" #include "mozilla/dom/workers/ServiceWorkerManager.h" +#include "FetchObserver.h" #include "InternalRequest.h" #include "InternalResponse.h" @@ -126,18 +127,20 @@ private: class WorkerFetchResolver final : public FetchDriverObserver { friend class MainThreadFetchRunnable; + friend class WorkerDataAvailableRunnable; friend class WorkerFetchResponseEndBase; friend class WorkerFetchResponseEndRunnable; friend class WorkerFetchResponseRunnable; RefPtr mPromiseProxy; RefPtr mSignalProxy; + RefPtr mFetchObserver; public: // Returns null if worker is shutting down. static already_AddRefed Create(workers::WorkerPrivate* aWorkerPrivate, Promise* aPromise, - FetchSignal* aSignal) + FetchSignal* aSignal, FetchObserver* aObserver) { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); @@ -152,7 +155,8 @@ public: signalProxy = new FetchSignalProxy(aSignal); } - RefPtr r = new WorkerFetchResolver(proxy, signalProxy); + RefPtr r = + new WorkerFetchResolver(proxy, signalProxy, aObserver); return r.forget(); } @@ -174,15 +178,19 @@ public: void OnResponseEnd(FetchDriverObserver::EndReason eReason) override; + void + OnDataAvailable() override; + private: WorkerFetchResolver(PromiseWorkerProxy* aProxy, - FetchSignalProxy* aSignalProxy) + FetchSignalProxy* aSignalProxy, + FetchObserver* aObserver) : mPromiseProxy(aProxy) , mSignalProxy(aSignalProxy) + , mFetchObserver(aObserver) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(mPromiseProxy); - } ~WorkerFetchResolver() @@ -196,12 +204,16 @@ class MainThreadFetchResolver final : public FetchDriverObserver { RefPtr mPromise; RefPtr mResponse; + RefPtr mFetchObserver; nsCOMPtr mDocument; NS_DECL_OWNINGTHREAD public: - explicit MainThreadFetchResolver(Promise* aPromise); + MainThreadFetchResolver(Promise* aPromise, FetchObserver* aObserver) + : mPromise(aPromise) + , mFetchObserver(aObserver) + {} void OnResponseAvailableInternal(InternalResponse* aResponse) override; @@ -217,9 +229,14 @@ public: mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); } + mFetchObserver = nullptr; + FlushConsoleReport(); } + void + OnDataAvailable() override; + private: ~MainThreadFetchResolver(); @@ -318,6 +335,12 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, // Let's FetchDriver to deal with an already aborted signal. } + RefPtr observer; + if (aInit.mObserve.WasPassed()) { + observer = new FetchObserver(aGlobal, signal); + aInit.mObserve.Value().HandleEvent(*observer); + } + if (NS_IsMainThread()) { nsCOMPtr window = do_QueryInterface(aGlobal); nsCOMPtr doc; @@ -344,7 +367,8 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, } } - RefPtr resolver = new MainThreadFetchResolver(p); + RefPtr resolver = + new MainThreadFetchResolver(p, observer); RefPtr fetch = new FetchDriver(r, principal, loadGroup); fetch->SetDocument(doc); resolver->SetDocument(doc); @@ -360,7 +384,8 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, r->SetSkipServiceWorker(); } - RefPtr resolver = WorkerFetchResolver::Create(worker, p, signal); + RefPtr resolver = + WorkerFetchResolver::Create(worker, p, signal, observer); if (!resolver) { NS_WARNING("Could not add WorkerFetchResolver workerHolder to worker"); aRv.Throw(NS_ERROR_DOM_ABORT_ERR); @@ -374,11 +399,6 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, return p.forget(); } -MainThreadFetchResolver::MainThreadFetchResolver(Promise* aPromise) - : mPromise(aPromise) -{ -} - void MainThreadFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse) { @@ -386,16 +406,39 @@ MainThreadFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse AssertIsOnMainThread(); if (aResponse->Type() != ResponseType::Error) { + if (mFetchObserver) { + mFetchObserver->SetState(FetchState::Complete); + } + nsCOMPtr go = mPromise->GetParentObject(); mResponse = new Response(go, aResponse); mPromise->MaybeResolve(mResponse); } else { + if (mFetchObserver) { + mFetchObserver->SetState(FetchState::Errored); + } + ErrorResult result; result.ThrowTypeError(); mPromise->MaybeReject(result); } } +void +MainThreadFetchResolver::OnDataAvailable() +{ + NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver); + AssertIsOnMainThread(); + + if (!mFetchObserver) { + return; + } + + if (mFetchObserver->State() == FetchState::Requesting) { + mFetchObserver->SetState(FetchState::Responding); + } +} + MainThreadFetchResolver::~MainThreadFetchResolver() { NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver); @@ -426,10 +469,18 @@ public: RefPtr promise = mResolver->mPromiseProxy->WorkerPromise(); if (mInternalResponse->Type() != ResponseType::Error) { + if (mResolver->mFetchObserver) { + mResolver->mFetchObserver->SetState(FetchState::Complete); + } + RefPtr global = aWorkerPrivate->GlobalScope(); RefPtr response = new Response(global, mInternalResponse); promise->MaybeResolve(response); } else { + if (mResolver->mFetchObserver) { + mResolver->mFetchObserver->SetState(FetchState::Errored); + } + ErrorResult result; result.ThrowTypeError(); promise->MaybeReject(result); @@ -438,18 +489,42 @@ public: } }; +class WorkerDataAvailableRunnable final : public MainThreadWorkerRunnable +{ + RefPtr mResolver; +public: + WorkerDataAvailableRunnable(WorkerPrivate* aWorkerPrivate, + WorkerFetchResolver* aResolver) + : MainThreadWorkerRunnable(aWorkerPrivate) + , mResolver(aResolver) + { + } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + if (mResolver->mFetchObserver && + mResolver->mFetchObserver->State() == FetchState::Requesting) { + mResolver->mFetchObserver->SetState(FetchState::Responding); + } + + return true; + } +}; + class WorkerFetchResponseEndBase { - RefPtr mPromiseProxy; - RefPtr mSignalProxy; +protected: + RefPtr mResolver; public: - WorkerFetchResponseEndBase(PromiseWorkerProxy* aPromiseProxy, - FetchSignalProxy* aSignalProxy) - : mPromiseProxy(aPromiseProxy) - , mSignalProxy(aSignalProxy) + explicit WorkerFetchResponseEndBase(WorkerFetchResolver* aResolver) + : mResolver(aResolver) { - MOZ_ASSERT(mPromiseProxy); + MOZ_ASSERT(aResolver); } void @@ -458,14 +533,13 @@ public: MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); - RefPtr promise = mPromiseProxy->WorkerPromise(); - promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + mResolver->mPromiseProxy->CleanUp(); - mPromiseProxy->CleanUp(); + mResolver->mFetchObserver = nullptr; - if (mSignalProxy) { - mSignalProxy->Shutdown(); - mSignalProxy = nullptr; + if (mResolver->mSignalProxy) { + mResolver->mSignalProxy->Shutdown(); + mResolver->mSignalProxy = nullptr; } } }; @@ -473,17 +547,26 @@ public: class WorkerFetchResponseEndRunnable final : public MainThreadWorkerRunnable , public WorkerFetchResponseEndBase { + FetchDriverObserver::EndReason mReason; + public: - WorkerFetchResponseEndRunnable(PromiseWorkerProxy* aPromiseProxy, - FetchSignalProxy* aSignalProxy) - : MainThreadWorkerRunnable(aPromiseProxy->GetWorkerPrivate()) - , WorkerFetchResponseEndBase(aPromiseProxy, aSignalProxy) + WorkerFetchResponseEndRunnable(WorkerPrivate* aWorkerPrivate, + WorkerFetchResolver* aResolver, + FetchDriverObserver::EndReason aReason) + : MainThreadWorkerRunnable(aWorkerPrivate) + , WorkerFetchResponseEndBase(aResolver) + , mReason(aReason) { } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { + if (mReason == FetchDriverObserver::eAborted) { + RefPtr promise = mResolver->mPromiseProxy->WorkerPromise(); + promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + } + WorkerRunInternal(aWorkerPrivate); return true; } @@ -502,10 +585,10 @@ class WorkerFetchResponseEndControlRunnable final : public MainThreadWorkerContr , public WorkerFetchResponseEndBase { public: - WorkerFetchResponseEndControlRunnable(PromiseWorkerProxy* aPromiseProxy, - FetchSignalProxy* aSignalProxy) - : MainThreadWorkerControlRunnable(aPromiseProxy->GetWorkerPrivate()) - , WorkerFetchResponseEndBase(aPromiseProxy, aSignalProxy) + WorkerFetchResponseEndControlRunnable(WorkerPrivate* aWorkerPrivate, + WorkerFetchResolver* aResolver) + : MainThreadWorkerControlRunnable(aWorkerPrivate) + , WorkerFetchResponseEndBase(aResolver) { } @@ -538,6 +621,21 @@ WorkerFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse) } } +void +WorkerFetchResolver::OnDataAvailable() +{ + AssertIsOnMainThread(); + + MutexAutoLock lock(mPromiseProxy->Lock()); + if (mPromiseProxy->CleanedUp()) { + return; + } + + RefPtr r = + new WorkerDataAvailableRunnable(mPromiseProxy->GetWorkerPrivate(), this); + Unused << r->Dispatch(); +} + void WorkerFetchResolver::OnResponseEnd(FetchDriverObserver::EndReason aReason) { @@ -550,11 +648,13 @@ WorkerFetchResolver::OnResponseEnd(FetchDriverObserver::EndReason aReason) FlushConsoleReport(); RefPtr r = - new WorkerFetchResponseEndRunnable(mPromiseProxy, mSignalProxy); + new WorkerFetchResponseEndRunnable(mPromiseProxy->GetWorkerPrivate(), + this, aReason); if (!r->Dispatch()) { RefPtr cr = - new WorkerFetchResponseEndControlRunnable(mPromiseProxy, mSignalProxy); + new WorkerFetchResponseEndControlRunnable(mPromiseProxy->GetWorkerPrivate(), + this); // This can fail if the worker thread is canceled or killed causing // the PromiseWorkerProxy to give up its WorkerHolder immediately, // allowing the worker thread to become Dead. diff --git a/dom/fetch/FetchDriver.cpp b/dom/fetch/FetchDriver.cpp index 448ec64cd..e8d726ce1 100644 --- a/dom/fetch/FetchDriver.cpp +++ b/dom/fetch/FetchDriver.cpp @@ -667,6 +667,31 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest, return NS_OK; } +namespace { + +// Runnable to call the observer OnDataAvailable on the main-thread. +class DataAvailableRunnable final : public Runnable +{ + RefPtr mObserver; + +public: + explicit DataAvailableRunnable(FetchDriverObserver* aObserver) + : mObserver(aObserver) + { + MOZ_ASSERT(aObserver); + } + + NS_IMETHOD + Run() override + { + mObserver->OnDataAvailable(); + mObserver = nullptr; + return NS_OK; + } +}; + +} // anonymous namespace + NS_IMETHODIMP FetchDriver::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, @@ -678,6 +703,18 @@ FetchDriver::OnDataAvailable(nsIRequest* aRequest, // called between OnStartRequest and OnStopRequest, so we don't need to worry // about races. + if (mObserver) { + if (NS_IsMainThread()) { + mObserver->OnDataAvailable(); + } else { + RefPtr runnable = new DataAvailableRunnable(mObserver); + nsresult rv = NS_DispatchToMainThread(runnable); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + } + uint32_t aRead; MOZ_ASSERT(mResponse); MOZ_ASSERT(mPipeOutputStream); diff --git a/dom/fetch/FetchDriver.h b/dom/fetch/FetchDriver.h index 0ca9a34ee..e942aa242 100644 --- a/dom/fetch/FetchDriver.h +++ b/dom/fetch/FetchDriver.h @@ -67,6 +67,8 @@ public: virtual void FlushConsoleReport() = 0; + virtual void OnDataAvailable() = 0; + protected: virtual ~FetchDriverObserver() { }; diff --git a/dom/fetch/FetchObserver.cpp b/dom/fetch/FetchObserver.cpp index bc8c6fc2b..982f0ad49 100644 --- a/dom/fetch/FetchObserver.cpp +++ b/dom/fetch/FetchObserver.cpp @@ -6,6 +6,7 @@ #include "FetchObserver.h" #include "WorkerPrivate.h" +#include "mozilla/dom/Event.h" namespace mozilla { namespace dom { @@ -45,10 +46,14 @@ FetchObserver::IsEnabled(JSContext* aCx, JSObject* aGlobal) } FetchObserver::FetchObserver(nsIGlobalObject* aGlobal, - FetchState aState) + FetchSignal* aSignal) : DOMEventTargetHelper(aGlobal) - , mState(aState) -{} + , mState(FetchState::Requesting) +{ + if (aSignal) { + Follow(aSignal); + } +} JSObject* FetchObserver::WrapObject(JSContext* aCx, JS::Handle aGivenProto) @@ -62,5 +67,51 @@ FetchObserver::State() const return mState; } +void +FetchObserver::Aborted() +{ + SetState(FetchState::Aborted); +} + +void +FetchObserver::SetState(FetchState aState) +{ + MOZ_ASSERT(mState < aState); + + if (mState == FetchState::Aborted || + mState == FetchState::Errored || + mState == FetchState::Complete) { + // We are already in a final state. + return; + } + + // We cannot pass from Requesting to Complete directly. + if (mState == FetchState::Requesting && + aState == FetchState::Complete) { + SetState(FetchState::Responding); + } + + mState = aState; + + if (mState == FetchState::Aborted || + mState == FetchState::Errored || + mState == FetchState::Complete) { + Unfollow(); + } + + EventInit init; + init.mBubbles = false; + init.mCancelable = false; + + // TODO which kind of event should we dispatch here? + + RefPtr event = + Event::Constructor(this, NS_LITERAL_STRING("statechange"), init); + event->SetTrusted(true); + + bool dummy; + DispatchEvent(event, &dummy); +} + } // dom namespace } // mozilla namespace diff --git a/dom/fetch/FetchObserver.h b/dom/fetch/FetchObserver.h index 81f8e7b09..45adf2ba1 100644 --- a/dom/fetch/FetchObserver.h +++ b/dom/fetch/FetchObserver.h @@ -9,11 +9,13 @@ #include "mozilla/DOMEventTargetHelper.h" #include "mozilla/dom/FetchObserverBinding.h" +#include "mozilla/dom/FetchSignal.h" namespace mozilla { namespace dom { class FetchObserver final : public DOMEventTargetHelper + , public FetchSignal::Follower { public: NS_DECL_ISUPPORTS_INHERITED @@ -22,7 +24,7 @@ public: static bool IsEnabled(JSContext* aCx, JSObject* aGlobal); - FetchObserver(nsIGlobalObject* aGlobal, FetchState aState); + FetchObserver(nsIGlobalObject* aGlobal, FetchSignal* aSignal); JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; @@ -34,6 +36,12 @@ public: IMPL_EVENT_HANDLER(requestprogress); IMPL_EVENT_HANDLER(responseprogress); + void + Aborted() override; + + void + SetState(FetchState aState); + private: ~FetchObserver() = default; diff --git a/dom/tests/mochitest/fetch/file_fetch_controller.html b/dom/tests/mochitest/fetch/file_fetch_controller.html index 026ff16a8..e4137aac9 100644 --- a/dom/tests/mochitest/fetch/file_fetch_controller.html +++ b/dom/tests/mochitest/fetch/file_fetch_controller.html @@ -88,7 +88,7 @@ function testAbortedFetch() { var fc = new FetchController(); fc.abort(); - fetch('data:,foo', { signal: fc.signal }).then(() => { + fetch('slow.sjs', { signal: fc.signal }).then(() => { ok(false, "Fetch should not return a resolved promise"); }, e => { is(e.name, "AbortError", "We have an abort error"); @@ -98,7 +98,7 @@ function testAbortedFetch() { function testFetchAndAbort() { var fc = new FetchController(); - var p = fetch('data:,foo', { signal: fc.signal }); + var p = fetch('slow.sjs', { signal: fc.signal }); fc.abort(); p.then(() => { @@ -109,49 +109,21 @@ function testFetchAndAbort() { } function testWorkerAbortedFetch() { - function worker() { - var fc = new FetchController(); - fc.abort(); - - fetch('data:,foo', { signal: fc.signal }).then(() => { - postMessage(false); - }, e => { - postMessage(e.name == "AbortError"); - }); - } - - var str = worker.toString(); - var content = str.substring(0, str.length - 1).split('\n').splice(1).join(' '); - var url = URL.createObjectURL(new Blob([content], { type: "application/javascript" })); - var w = new Worker(url); + var w = new Worker('worker_fetch_controller.js'); w.onmessage = function(e) { ok(e.data, "Abort + Fetch works in workers"); next(); } + w.postMessage('testWorkerAbortedFetch'); } function testWorkerFetchAndAbort() { - function worker() { - var fc = new FetchController(); - - var p = fetch('data:,foo', { signal: fc.signal }); - fc.abort(); - - p.then(() => { - postMessage(false); - }, e => { - postMessage(e.name == "AbortError"); - }); - } - - var str = worker.toString(); - var content = str.substring(0, str.length - 1).split('\n').splice(1).join(' '); - var url = URL.createObjectURL(new Blob([content], { type: "application/javascript" })); - var w = new Worker(url); + var w = new Worker('worker_fetch_controller.js'); w.onmessage = function(e) { ok(e.data, "Abort + Fetch works in workers"); next(); } + w.postMessage('testWorkerFetchAndAbort'); } var steps = [ diff --git a/dom/tests/mochitest/fetch/file_fetch_observer.html b/dom/tests/mochitest/fetch/file_fetch_observer.html index 97af584ec..a172a18dc 100644 --- a/dom/tests/mochitest/fetch/file_fetch_observer.html +++ b/dom/tests/mochitest/fetch/file_fetch_observer.html @@ -10,12 +10,125 @@ function is(a, b, msg) { function testObserver() { ok("FetchObserver" in self, "We have a FetchObserver prototype"); - fetch('data:,foo', { observe: o => { + fetch('http://mochi.test:8888/tests/dom/tests/mochitest/fetch/slow.sjs', { observe: o => { + ok(!!o, "We have an observer"); + ok(o instanceof FetchObserver, "The correct object has been passed"); + is(o.state, "requesting", "By default the state is requesting"); + next(); }}); } +function testObserveAbort() { + var fc = new FetchController(); + + fetch('http://mochi.test:8888/tests/dom/tests/mochitest/fetch/slow.sjs', { + signal: fc.signal, + observe: o => { + o.onstatechange = () => { + ok(true, "StateChange event dispatched"); + if (o.state == "aborted") { + ok(true, "Aborted!"); + next(); + } + } + fc.abort(); + } + }); +} + +function testObserveComplete() { + var fc = new FetchController(); + + fetch('http://mochi.test:8888/tests/dom/tests/mochitest/fetch/slow.sjs', { + signal: fc.signal, + observe: o => { + o.onstatechange = () => { + ok(true, "StateChange event dispatched"); + if (o.state == "complete") { + ok(true, "Operation completed"); + next(); + } + } + } + }); +} + +function testObserveErrored() { + var fc = new FetchController(); + + fetch('foo: bar', { + signal: fc.signal, + observe: o => { + o.onstatechange = () => { + ok(true, "StateChange event dispatched"); + if (o.state == "errored") { + ok(true, "Operation completed"); + next(); + } + } + } + }); +} + +function testObserveResponding() { + var fc = new FetchController(); + + fetch('http://mochi.test:8888/tests/dom/tests/mochitest/fetch/slow.sjs', { + signal: fc.signal, + observe: o => { + o.onstatechange = () => { + if (o.state == "responding") { + ok(true, "We have responding events"); + next(); + } + } + } + }); +} + +function workify(worker) { + function methods() { + function ok(a, msg) { + postMessage( { type: 'check', state: !!a, message: msg }); + }; + function is(a, b, msg) { + postMessage( { type: 'check', state: a === b, message: msg }); + }; + function next() { + postMessage( { type: 'finish' }); + }; + } + + var str = methods.toString(); + var methodsContent = str.substring(0, str.length - 1).split('\n').splice(1).join('\n'); + + str = worker.toString(); + var workerContent = str.substring(0, str.length - 1).split('\n').splice(1).join('\n'); + + var content = methodsContent + workerContent; + var url = URL.createObjectURL(new Blob([content], { type: "application/javascript" })); + var w = new Worker(url); + w.onmessage = e => { + if (e.data.type == 'check') { + ok(e.data.state, "WORKER: " + e.data.message); + } else if (e.data.type == 'finish') { + next(); + } else { + ok(false, "Something went wrong"); + } + } +} + var steps = [ testObserver, + testObserveAbort, + function() { workify(testObserveAbort); }, + testObserveComplete, + function() { workify(testObserveComplete); }, + testObserveErrored, + function() { workify(testObserveErrored); }, + testObserveResponding, + function() { workify(testObserveResponding); }, ]; function next() { diff --git a/dom/tests/mochitest/fetch/mochitest.ini b/dom/tests/mochitest/fetch/mochitest.ini index 60fa454f4..7493ede50 100644 --- a/dom/tests/mochitest/fetch/mochitest.ini +++ b/dom/tests/mochitest/fetch/mochitest.ini @@ -17,11 +17,13 @@ support-files = reroute.html reroute.js reroute.js^headers^ + slow.sjs sw_reroute.js empty.js empty.js^headers^ worker_temporaryFileBlob.js common_temporaryFileBlob.js + worker_fetch_controller.js !/dom/xhr/tests/file_XHR_binary1.bin !/dom/xhr/tests/file_XHR_binary1.bin^headers^ !/dom/xhr/tests/file_XHR_binary2.bin diff --git a/dom/tests/mochitest/fetch/slow.sjs b/dom/tests/mochitest/fetch/slow.sjs new file mode 100644 index 000000000..feab0f1fc --- /dev/null +++ b/dom/tests/mochitest/fetch/slow.sjs @@ -0,0 +1,11 @@ +function handleRequest(request, response) +{ + response.processAsync(); + + timer = Components.classes["@mozilla.org/timer;1"]. + createInstance(Components.interfaces.nsITimer); + timer.init(function() { + response.write("Here the content. But slowly."); + response.finish(); + }, 1000, Components.interfaces.nsITimer.TYPE_ONE_SHOT); +} diff --git a/dom/tests/mochitest/fetch/test_fetch_observer.html b/dom/tests/mochitest/fetch/test_fetch_observer.html index 2b6c0362d..2af86977c 100644 --- a/dom/tests/mochitest/fetch/test_fetch_observer.html +++ b/dom/tests/mochitest/fetch/test_fetch_observer.html @@ -12,7 +12,8 @@ diff --git a/dom/abort/tests/mochitest.ini b/dom/abort/tests/mochitest.ini new file mode 100644 index 000000000..5ecc7048e --- /dev/null +++ b/dom/abort/tests/mochitest.ini @@ -0,0 +1,6 @@ +[DEFAULT] +support-files = + file_fetch_controller.html + worker_fetch_controller.js + +[test_fetch_controller.html] diff --git a/dom/abort/tests/moz.build b/dom/abort/tests/moz.build new file mode 100644 index 000000000..8e5cb5d71 --- /dev/null +++ b/dom/abort/tests/moz.build @@ -0,0 +1,8 @@ +# -*- 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/. + +MOCHITEST_MANIFESTS += ['mochitest.ini'] + diff --git a/dom/abort/tests/test_fetch_controller.html b/dom/abort/tests/test_fetch_controller.html new file mode 100644 index 000000000..812fb9161 --- /dev/null +++ b/dom/abort/tests/test_fetch_controller.html @@ -0,0 +1,40 @@ + + + + + Test FetchController + + + + + + + + diff --git a/dom/abort/tests/worker_fetch_controller.js b/dom/abort/tests/worker_fetch_controller.js new file mode 100644 index 000000000..6b008fea8 --- /dev/null +++ b/dom/abort/tests/worker_fetch_controller.js @@ -0,0 +1,27 @@ +function testWorkerAbortedFetch() { + var fc = new FetchController(); + fc.abort(); + + fetch('slow.sjs', { signal: fc.signal }).then(() => { + postMessage(false); + }, e => { + postMessage(e.name == "AbortError"); + }); +} + +function testWorkerFetchAndAbort() { + var fc = new FetchController(); + + var p = fetch('slow.sjs', { signal: fc.signal }); + fc.abort(); + + p.then(() => { + postMessage(false); + }, e => { + postMessage(e.name == "AbortError"); + }); +} + +onmessage = function(e) { + self[e.data](); +} diff --git a/dom/fetch/FetchController.cpp b/dom/fetch/FetchController.cpp deleted file mode 100644 index 2eb40b980..000000000 --- a/dom/fetch/FetchController.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/* -*- 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 "FetchController.h" -#include "FetchSignal.h" -#include "mozilla/dom/FetchControllerBinding.h" -#include "WorkerPrivate.h" - -namespace mozilla { -namespace dom { - -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FetchController, mGlobal, mSignal, - mFollowingSignal) - -NS_IMPL_CYCLE_COLLECTING_ADDREF(FetchController) -NS_IMPL_CYCLE_COLLECTING_RELEASE(FetchController) - -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FetchController) - NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END - -/* static */ bool -FetchController::IsEnabled(JSContext* aCx, JSObject* aGlobal) -{ - if (NS_IsMainThread()) { - return Preferences::GetBool("dom.fetchController.enabled", false); - } - - using namespace workers; - - // Otherwise, check the pref via the WorkerPrivate - WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); - if (!workerPrivate) { - return false; - } - - return workerPrivate->FetchControllerEnabled(); -} - -/* static */ already_AddRefed -FetchController::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) -{ - nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); - if (!global) { - aRv.Throw(NS_ERROR_FAILURE); - return nullptr; - } - - RefPtr fetchController = new FetchController(global); - return fetchController.forget(); -} - -FetchController::FetchController(nsIGlobalObject* aGlobal) - : mGlobal(aGlobal) - , mAborted(false) -{} - -JSObject* -FetchController::WrapObject(JSContext* aCx, JS::Handle aGivenProto) -{ - return FetchControllerBinding::Wrap(aCx, this, aGivenProto); -} - -nsIGlobalObject* -FetchController::GetParentObject() const -{ - return mGlobal; -} - -FetchSignal* -FetchController::Signal() -{ - if (!mSignal) { - mSignal = new FetchSignal(this, mAborted); - } - - return mSignal; -} - -void -FetchController::Abort() -{ - if (mAborted) { - return; - } - - mAborted = true; - - if (mSignal) { - mSignal->Abort(); - } -} - -void -FetchController::Follow(FetchSignal& aSignal) -{ - FetchSignal::Follower::Follow(&aSignal); -} - -void -FetchController::Unfollow(FetchSignal& aSignal) -{ - if (mFollowingSignal != &aSignal) { - return; - } - - FetchSignal::Follower::Unfollow(); -} - -FetchSignal* -FetchController::Following() const -{ - return mFollowingSignal; -} - -void -FetchController::Aborted() -{ - Abort(); -} - -} // dom namespace -} // mozilla namespace diff --git a/dom/fetch/FetchController.h b/dom/fetch/FetchController.h deleted file mode 100644 index 7a0132dca..000000000 --- a/dom/fetch/FetchController.h +++ /dev/null @@ -1,73 +0,0 @@ -/* -*- 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_FetchController_h -#define mozilla_dom_FetchController_h - -#include "mozilla/dom/BindingDeclarations.h" -#include "mozilla/dom/FetchSignal.h" -#include "nsCycleCollectionParticipant.h" -#include "nsWrapperCache.h" -#include "mozilla/ErrorResult.h" -#include "nsIGlobalObject.h" - -namespace mozilla { -namespace dom { - -class FetchController final : public nsISupports - , public nsWrapperCache - , public FetchSignal::Follower -{ -public: - NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FetchController) - - static bool - IsEnabled(JSContext* aCx, JSObject* aGlobal); - - static already_AddRefed - Constructor(const GlobalObject& aGlobal, ErrorResult& aRv); - - explicit FetchController(nsIGlobalObject* aGlobal); - - JSObject* - WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - - nsIGlobalObject* - GetParentObject() const; - - FetchSignal* - Signal(); - - void - Abort(); - - void - Follow(FetchSignal& aSignal); - - void - Unfollow(FetchSignal& aSignal); - - FetchSignal* - Following() const; - - // FetchSignal::Follower - - void Aborted() override; - -private: - ~FetchController() = default; - - nsCOMPtr mGlobal; - RefPtr mSignal; - - bool mAborted; -}; - -} // dom namespace -} // mozilla namespace - -#endif // mozilla_dom_FetchController_h diff --git a/dom/fetch/FetchSignal.cpp b/dom/fetch/FetchSignal.cpp deleted file mode 100644 index 07ad6b53d..000000000 --- a/dom/fetch/FetchSignal.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/* -*- 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 "FetchSignal.h" -#include "FetchController.h" -#include "mozilla/dom/Event.h" -#include "mozilla/dom/FetchSignalBinding.h" - -namespace mozilla { -namespace dom { - -NS_IMPL_CYCLE_COLLECTION_CLASS(FetchSignal) - -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FetchSignal, - DOMEventTargetHelper) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mController) -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END - -NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FetchSignal, - DOMEventTargetHelper) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mController) -NS_IMPL_CYCLE_COLLECTION_UNLINK_END - -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FetchSignal) -NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) - -NS_IMPL_ADDREF_INHERITED(FetchSignal, DOMEventTargetHelper) -NS_IMPL_RELEASE_INHERITED(FetchSignal, DOMEventTargetHelper) - -FetchSignal::FetchSignal(FetchController* aController, - bool aAborted) - : DOMEventTargetHelper(aController->GetParentObject()) - , mController(aController) - , mAborted(aAborted) -{} - -FetchSignal::FetchSignal(bool aAborted) - : mAborted(aAborted) -{} - -JSObject* -FetchSignal::WrapObject(JSContext* aCx, JS::Handle aGivenProto) -{ - return FetchSignalBinding::Wrap(aCx, this, aGivenProto); -} - -bool -FetchSignal::Aborted() const -{ - return mAborted; -} - -void -FetchSignal::Abort() -{ - MOZ_ASSERT(!mAborted); - mAborted = true; - - // Let's inform the followers. - for (uint32_t i = 0; i < mFollowers.Length(); ++i) { - mFollowers[i]->Aborted(); - } - - EventInit init; - init.mBubbles = false; - init.mCancelable = false; - - // TODO which kind of event should we dispatch here? - - RefPtr event = - Event::Constructor(this, NS_LITERAL_STRING("abort"), init); - event->SetTrusted(true); - - bool dummy; - DispatchEvent(event, &dummy); -} - -void -FetchSignal::AddFollower(FetchSignal::Follower* aFollower) -{ - MOZ_DIAGNOSTIC_ASSERT(aFollower); - if (!mFollowers.Contains(aFollower)) { - mFollowers.AppendElement(aFollower); - } -} - -void -FetchSignal::RemoveFollower(FetchSignal::Follower* aFollower) -{ - MOZ_DIAGNOSTIC_ASSERT(aFollower); - mFollowers.RemoveElement(aFollower); -} - -bool -FetchSignal::CanAcceptFollower(FetchSignal::Follower* aFollower) const -{ - MOZ_DIAGNOSTIC_ASSERT(aFollower); - - if (!mController) { - return true; - } - - if (aFollower == mController) { - return false; - } - - FetchSignal* following = mController->Following(); - if (!following) { - return true; - } - - return following->CanAcceptFollower(aFollower); -} - -// FetchSignal::Follower -// ---------------------------------------------------------------------------- - -FetchSignal::Follower::~Follower() -{ - Unfollow(); -} - -void -FetchSignal::Follower::Follow(FetchSignal* aSignal) -{ - MOZ_DIAGNOSTIC_ASSERT(aSignal); - - if (!aSignal->CanAcceptFollower(this)) { - return; - } - - Unfollow(); - - mFollowingSignal = aSignal; - aSignal->AddFollower(this); -} - -void -FetchSignal::Follower::Unfollow() -{ - if (mFollowingSignal) { - mFollowingSignal->RemoveFollower(this); - mFollowingSignal = nullptr; - } -} - -} // dom namespace -} // mozilla namespace diff --git a/dom/fetch/FetchSignal.h b/dom/fetch/FetchSignal.h deleted file mode 100644 index 4970f03de..000000000 --- a/dom/fetch/FetchSignal.h +++ /dev/null @@ -1,79 +0,0 @@ -/* -*- 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_FetchSignal_h -#define mozilla_dom_FetchSignal_h - -#include "mozilla/DOMEventTargetHelper.h" - -namespace mozilla { -namespace dom { - -class FetchController; -class FetchSignal; - -class FetchSignal final : public DOMEventTargetHelper -{ -public: - // This class must be implemented by objects who want to follow a FetchSignal. - class Follower - { - public: - virtual void Aborted() = 0; - - protected: - virtual ~Follower(); - - void - Follow(FetchSignal* aSignal); - - void - Unfollow(); - - RefPtr mFollowingSignal; - }; - - NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchSignal, DOMEventTargetHelper) - - FetchSignal(FetchController* aController, bool aAborted); - explicit FetchSignal(bool aAborted); - - JSObject* - WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - - bool - Aborted() const; - - void - Abort(); - - IMPL_EVENT_HANDLER(abort); - - void - AddFollower(Follower* aFollower); - - void - RemoveFollower(Follower* aFollower); - - bool - CanAcceptFollower(Follower* aFollower) const; - -private: - ~FetchSignal() = default; - - RefPtr mController; - - // Raw pointers. Follower unregisters itself in the DTOR. - nsTArray mFollowers; - - bool mAborted; -}; - -} // dom namespace -} // mozilla namespace - -#endif // mozilla_dom_FetchSignal_h diff --git a/dom/fetch/moz.build b/dom/fetch/moz.build index 82fd99173..e2b466428 100644 --- a/dom/fetch/moz.build +++ b/dom/fetch/moz.build @@ -7,11 +7,9 @@ EXPORTS.mozilla.dom += [ 'ChannelInfo.h', 'Fetch.h', - 'FetchController.h', 'FetchDriver.h', 'FetchIPCTypes.h', 'FetchObserver.h', - 'FetchSignal.h', 'FetchUtil.h', 'Headers.h', 'InternalHeaders.h', @@ -30,10 +28,8 @@ UNIFIED_SOURCES += [ SOURCES += [ 'ChannelInfo.cpp', - 'FetchController.cpp', 'FetchDriver.cpp', 'FetchObserver.cpp', - 'FetchSignal.cpp', 'FetchUtil.cpp', 'Headers.cpp', 'InternalHeaders.cpp', diff --git a/dom/moz.build b/dom/moz.build index 89c539b4b..8a958982f 100644 --- a/dom/moz.build +++ b/dom/moz.build @@ -37,6 +37,7 @@ interfaces = [ DIRS += ['interfaces/' + i for i in interfaces] DIRS += [ + 'abort', 'animation', 'apps', 'base', diff --git a/dom/tests/mochitest/fetch/file_fetch_controller.html b/dom/tests/mochitest/fetch/file_fetch_controller.html deleted file mode 100644 index e4137aac9..000000000 --- a/dom/tests/mochitest/fetch/file_fetch_controller.html +++ /dev/null @@ -1,161 +0,0 @@ - diff --git a/dom/tests/mochitest/fetch/mochitest.ini b/dom/tests/mochitest/fetch/mochitest.ini index 7493ede50..a9447d0d9 100644 --- a/dom/tests/mochitest/fetch/mochitest.ini +++ b/dom/tests/mochitest/fetch/mochitest.ini @@ -1,7 +1,6 @@ [DEFAULT] support-files = fetch_test_framework.js - file_fetch_controller.html test_fetch_basic.js test_fetch_basic_http.js test_fetch_cors.js @@ -23,7 +22,6 @@ support-files = empty.js^headers^ worker_temporaryFileBlob.js common_temporaryFileBlob.js - worker_fetch_controller.js !/dom/xhr/tests/file_XHR_binary1.bin !/dom/xhr/tests/file_XHR_binary1.bin^headers^ !/dom/xhr/tests/file_XHR_binary2.bin @@ -45,7 +43,6 @@ support-files = [test_fetch_basic_http.html] [test_fetch_basic_http_sw_reroute.html] [test_fetch_basic_http_sw_empty_reroute.html] -[test_fetch_controller.html] [test_fetch_cors.html] skip-if = toolkit == 'android' # Bug 1210282 [test_fetch_cors_sw_reroute.html] diff --git a/dom/tests/mochitest/fetch/test_fetch_controller.html b/dom/tests/mochitest/fetch/test_fetch_controller.html deleted file mode 100644 index 812fb9161..000000000 --- a/dom/tests/mochitest/fetch/test_fetch_controller.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - Test FetchController - - - - - - - - diff --git a/dom/tests/mochitest/fetch/worker_fetch_controller.js b/dom/tests/mochitest/fetch/worker_fetch_controller.js deleted file mode 100644 index 6b008fea8..000000000 --- a/dom/tests/mochitest/fetch/worker_fetch_controller.js +++ /dev/null @@ -1,27 +0,0 @@ -function testWorkerAbortedFetch() { - var fc = new FetchController(); - fc.abort(); - - fetch('slow.sjs', { signal: fc.signal }).then(() => { - postMessage(false); - }, e => { - postMessage(e.name == "AbortError"); - }); -} - -function testWorkerFetchAndAbort() { - var fc = new FetchController(); - - var p = fetch('slow.sjs', { signal: fc.signal }); - fc.abort(); - - p.then(() => { - postMessage(false); - }, e => { - postMessage(e.name == "AbortError"); - }); -} - -onmessage = function(e) { - self[e.data](); -} -- cgit v1.2.3 From 21403c31e0288ad3c1921019f6f6185a0019a9a4 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Thu, 11 Jun 2020 23:20:52 +0000 Subject: Issue #1587 - Part 7: Rename FetchController to AbortController Also renames FetchSignal to AbortSignal. Includes renaming the various controlling prefs to enable. --- dom/abort/AbortController.cpp | 127 ++++++++++++++++ dom/abort/AbortController.h | 73 ++++++++++ dom/abort/AbortSignal.cpp | 151 +++++++++++++++++++ dom/abort/AbortSignal.h | 79 ++++++++++ dom/abort/FetchController.cpp | 127 ---------------- dom/abort/FetchController.h | 73 ---------- dom/abort/FetchSignal.cpp | 151 ------------------- dom/abort/FetchSignal.h | 79 ---------- dom/abort/moz.build | 8 +- dom/abort/tests/file_abort_controller.html | 161 +++++++++++++++++++++ dom/abort/tests/file_fetch_controller.html | 161 --------------------- dom/abort/tests/mochitest.ini | 4 +- dom/abort/tests/test_abort_controller.html | 40 +++++ dom/abort/tests/test_fetch_controller.html | 40 ----- dom/abort/tests/worker_abort_controller.js | 27 ++++ dom/abort/tests/worker_fetch_controller.js | 27 ---- dom/fetch/Fetch.cpp | 52 +++---- dom/fetch/FetchDriver.cpp | 2 +- dom/fetch/FetchDriver.h | 8 +- dom/fetch/FetchObserver.cpp | 2 +- dom/fetch/FetchObserver.h | 6 +- dom/fetch/Request.h | 2 +- dom/tests/mochitest/fetch/file_fetch_observer.html | 8 +- dom/webidl/AbortController.webidl | 15 ++ dom/webidl/AbortSignal.webidl | 13 ++ dom/webidl/FetchController.webidl | 15 -- dom/webidl/FetchSignal.webidl | 13 -- dom/webidl/Request.webidl | 4 +- dom/webidl/moz.build | 4 +- dom/workers/WorkerPrefs.h | 2 +- 30 files changed, 737 insertions(+), 737 deletions(-) create mode 100644 dom/abort/AbortController.cpp create mode 100644 dom/abort/AbortController.h create mode 100644 dom/abort/AbortSignal.cpp create mode 100644 dom/abort/AbortSignal.h delete mode 100644 dom/abort/FetchController.cpp delete mode 100644 dom/abort/FetchController.h delete mode 100644 dom/abort/FetchSignal.cpp delete mode 100644 dom/abort/FetchSignal.h create mode 100644 dom/abort/tests/file_abort_controller.html delete mode 100644 dom/abort/tests/file_fetch_controller.html create mode 100644 dom/abort/tests/test_abort_controller.html delete mode 100644 dom/abort/tests/test_fetch_controller.html create mode 100644 dom/abort/tests/worker_abort_controller.js delete mode 100644 dom/abort/tests/worker_fetch_controller.js create mode 100644 dom/webidl/AbortController.webidl create mode 100644 dom/webidl/AbortSignal.webidl delete mode 100644 dom/webidl/FetchController.webidl delete mode 100644 dom/webidl/FetchSignal.webidl diff --git a/dom/abort/AbortController.cpp b/dom/abort/AbortController.cpp new file mode 100644 index 000000000..4043b7bf3 --- /dev/null +++ b/dom/abort/AbortController.cpp @@ -0,0 +1,127 @@ +/* -*- 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 "AbortController.h" +#include "AbortSignal.h" +#include "mozilla/dom/AbortControllerBinding.h" +#include "WorkerPrivate.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AbortController, mGlobal, mSignal, + mFollowingSignal) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(AbortController) +NS_IMPL_CYCLE_COLLECTING_RELEASE(AbortController) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbortController) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +/* static */ bool +AbortController::IsEnabled(JSContext* aCx, JSObject* aGlobal) +{ + if (NS_IsMainThread()) { + return Preferences::GetBool("dom.abortController.enabled", false); + } + + using namespace workers; + + // Otherwise, check the pref via the WorkerPrivate + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); + if (!workerPrivate) { + return false; + } + + return workerPrivate->AbortControllerEnabled(); +} + +/* static */ already_AddRefed +AbortController::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) +{ + nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + RefPtr abortController = new AbortController(global); + return abortController.forget(); +} + +AbortController::AbortController(nsIGlobalObject* aGlobal) + : mGlobal(aGlobal) + , mAborted(false) +{} + +JSObject* +AbortController::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return AbortControllerBinding::Wrap(aCx, this, aGivenProto); +} + +nsIGlobalObject* +AbortController::GetParentObject() const +{ + return mGlobal; +} + +AbortSignal* +AbortController::Signal() +{ + if (!mSignal) { + mSignal = new AbortSignal(this, mAborted); + } + + return mSignal; +} + +void +AbortController::Abort() +{ + if (mAborted) { + return; + } + + mAborted = true; + + if (mSignal) { + mSignal->Abort(); + } +} + +void +AbortController::Follow(AbortSignal& aSignal) +{ + AbortSignal::Follower::Follow(&aSignal); +} + +void +AbortController::Unfollow(AbortSignal& aSignal) +{ + if (mFollowingSignal != &aSignal) { + return; + } + + AbortSignal::Follower::Unfollow(); +} + +AbortSignal* +AbortController::Following() const +{ + return mFollowingSignal; +} + +void +AbortController::Aborted() +{ + Abort(); +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/abort/AbortController.h b/dom/abort/AbortController.h new file mode 100644 index 000000000..9ce4253c9 --- /dev/null +++ b/dom/abort/AbortController.h @@ -0,0 +1,73 @@ +/* -*- 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_AbortController_h +#define mozilla_dom_AbortController_h + +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/AbortSignal.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "mozilla/ErrorResult.h" +#include "nsIGlobalObject.h" + +namespace mozilla { +namespace dom { + +class AbortController final : public nsISupports + , public nsWrapperCache + , public AbortSignal::Follower +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbortController) + + static bool + IsEnabled(JSContext* aCx, JSObject* aGlobal); + + static already_AddRefed + Constructor(const GlobalObject& aGlobal, ErrorResult& aRv); + + explicit AbortController(nsIGlobalObject* aGlobal); + + JSObject* + WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + nsIGlobalObject* + GetParentObject() const; + + AbortSignal* + Signal(); + + void + Abort(); + + void + Follow(AbortSignal& aSignal); + + void + Unfollow(AbortSignal& aSignal); + + AbortSignal* + Following() const; + + // AbortSignal::Follower + + void Aborted() override; + +private: + ~AbortController() = default; + + nsCOMPtr mGlobal; + RefPtr mSignal; + + bool mAborted; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_AbortController_h diff --git a/dom/abort/AbortSignal.cpp b/dom/abort/AbortSignal.cpp new file mode 100644 index 000000000..38b0a2492 --- /dev/null +++ b/dom/abort/AbortSignal.cpp @@ -0,0 +1,151 @@ +/* -*- 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 "AbortSignal.h" +#include "AbortController.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/AbortSignalBinding.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_CLASS(AbortSignal) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AbortSignal, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mController) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AbortSignal, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mController) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AbortSignal) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(AbortSignal, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(AbortSignal, DOMEventTargetHelper) + +AbortSignal::AbortSignal(AbortController* aController, + bool aAborted) + : DOMEventTargetHelper(aController->GetParentObject()) + , mController(aController) + , mAborted(aAborted) +{} + +AbortSignal::AbortSignal(bool aAborted) + : mAborted(aAborted) +{} + +JSObject* +AbortSignal::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return AbortSignalBinding::Wrap(aCx, this, aGivenProto); +} + +bool +AbortSignal::Aborted() const +{ + return mAborted; +} + +void +AbortSignal::Abort() +{ + MOZ_ASSERT(!mAborted); + mAborted = true; + + // Let's inform the followers. + for (uint32_t i = 0; i < mFollowers.Length(); ++i) { + mFollowers[i]->Aborted(); + } + + EventInit init; + init.mBubbles = false; + init.mCancelable = false; + + // TODO which kind of event should we dispatch here? + + RefPtr event = + Event::Constructor(this, NS_LITERAL_STRING("abort"), init); + event->SetTrusted(true); + + bool dummy; + DispatchEvent(event, &dummy); +} + +void +AbortSignal::AddFollower(AbortSignal::Follower* aFollower) +{ + MOZ_DIAGNOSTIC_ASSERT(aFollower); + if (!mFollowers.Contains(aFollower)) { + mFollowers.AppendElement(aFollower); + } +} + +void +AbortSignal::RemoveFollower(AbortSignal::Follower* aFollower) +{ + MOZ_DIAGNOSTIC_ASSERT(aFollower); + mFollowers.RemoveElement(aFollower); +} + +bool +AbortSignal::CanAcceptFollower(AbortSignal::Follower* aFollower) const +{ + MOZ_DIAGNOSTIC_ASSERT(aFollower); + + if (!mController) { + return true; + } + + if (aFollower == mController) { + return false; + } + + AbortSignal* following = mController->Following(); + if (!following) { + return true; + } + + return following->CanAcceptFollower(aFollower); +} + +// AbortSignal::Follower +// ---------------------------------------------------------------------------- + +AbortSignal::Follower::~Follower() +{ + Unfollow(); +} + +void +AbortSignal::Follower::Follow(AbortSignal* aSignal) +{ + MOZ_DIAGNOSTIC_ASSERT(aSignal); + + if (!aSignal->CanAcceptFollower(this)) { + return; + } + + Unfollow(); + + mFollowingSignal = aSignal; + aSignal->AddFollower(this); +} + +void +AbortSignal::Follower::Unfollow() +{ + if (mFollowingSignal) { + mFollowingSignal->RemoveFollower(this); + mFollowingSignal = nullptr; + } +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/abort/AbortSignal.h b/dom/abort/AbortSignal.h new file mode 100644 index 000000000..c5045dbd6 --- /dev/null +++ b/dom/abort/AbortSignal.h @@ -0,0 +1,79 @@ +/* -*- 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_AbortSignal_h +#define mozilla_dom_AbortSignal_h + +#include "mozilla/DOMEventTargetHelper.h" + +namespace mozilla { +namespace dom { + +class AbortController; +class AbortSignal; + +class AbortSignal final : public DOMEventTargetHelper +{ +public: + // This class must be implemented by objects who want to follow a AbortSignal. + class Follower + { + public: + virtual void Aborted() = 0; + + protected: + virtual ~Follower(); + + void + Follow(AbortSignal* aSignal); + + void + Unfollow(); + + RefPtr mFollowingSignal; + }; + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AbortSignal, DOMEventTargetHelper) + + AbortSignal(AbortController* aController, bool aAborted); + explicit AbortSignal(bool aAborted); + + JSObject* + WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + bool + Aborted() const; + + void + Abort(); + + IMPL_EVENT_HANDLER(abort); + + void + AddFollower(Follower* aFollower); + + void + RemoveFollower(Follower* aFollower); + + bool + CanAcceptFollower(Follower* aFollower) const; + +private: + ~AbortSignal() = default; + + RefPtr mController; + + // Raw pointers. Follower unregisters itself in the DTOR. + nsTArray mFollowers; + + bool mAborted; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_AbortSignal_h diff --git a/dom/abort/FetchController.cpp b/dom/abort/FetchController.cpp deleted file mode 100644 index 2eb40b980..000000000 --- a/dom/abort/FetchController.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/* -*- 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 "FetchController.h" -#include "FetchSignal.h" -#include "mozilla/dom/FetchControllerBinding.h" -#include "WorkerPrivate.h" - -namespace mozilla { -namespace dom { - -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FetchController, mGlobal, mSignal, - mFollowingSignal) - -NS_IMPL_CYCLE_COLLECTING_ADDREF(FetchController) -NS_IMPL_CYCLE_COLLECTING_RELEASE(FetchController) - -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FetchController) - NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END - -/* static */ bool -FetchController::IsEnabled(JSContext* aCx, JSObject* aGlobal) -{ - if (NS_IsMainThread()) { - return Preferences::GetBool("dom.fetchController.enabled", false); - } - - using namespace workers; - - // Otherwise, check the pref via the WorkerPrivate - WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); - if (!workerPrivate) { - return false; - } - - return workerPrivate->FetchControllerEnabled(); -} - -/* static */ already_AddRefed -FetchController::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) -{ - nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); - if (!global) { - aRv.Throw(NS_ERROR_FAILURE); - return nullptr; - } - - RefPtr fetchController = new FetchController(global); - return fetchController.forget(); -} - -FetchController::FetchController(nsIGlobalObject* aGlobal) - : mGlobal(aGlobal) - , mAborted(false) -{} - -JSObject* -FetchController::WrapObject(JSContext* aCx, JS::Handle aGivenProto) -{ - return FetchControllerBinding::Wrap(aCx, this, aGivenProto); -} - -nsIGlobalObject* -FetchController::GetParentObject() const -{ - return mGlobal; -} - -FetchSignal* -FetchController::Signal() -{ - if (!mSignal) { - mSignal = new FetchSignal(this, mAborted); - } - - return mSignal; -} - -void -FetchController::Abort() -{ - if (mAborted) { - return; - } - - mAborted = true; - - if (mSignal) { - mSignal->Abort(); - } -} - -void -FetchController::Follow(FetchSignal& aSignal) -{ - FetchSignal::Follower::Follow(&aSignal); -} - -void -FetchController::Unfollow(FetchSignal& aSignal) -{ - if (mFollowingSignal != &aSignal) { - return; - } - - FetchSignal::Follower::Unfollow(); -} - -FetchSignal* -FetchController::Following() const -{ - return mFollowingSignal; -} - -void -FetchController::Aborted() -{ - Abort(); -} - -} // dom namespace -} // mozilla namespace diff --git a/dom/abort/FetchController.h b/dom/abort/FetchController.h deleted file mode 100644 index 7a0132dca..000000000 --- a/dom/abort/FetchController.h +++ /dev/null @@ -1,73 +0,0 @@ -/* -*- 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_FetchController_h -#define mozilla_dom_FetchController_h - -#include "mozilla/dom/BindingDeclarations.h" -#include "mozilla/dom/FetchSignal.h" -#include "nsCycleCollectionParticipant.h" -#include "nsWrapperCache.h" -#include "mozilla/ErrorResult.h" -#include "nsIGlobalObject.h" - -namespace mozilla { -namespace dom { - -class FetchController final : public nsISupports - , public nsWrapperCache - , public FetchSignal::Follower -{ -public: - NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FetchController) - - static bool - IsEnabled(JSContext* aCx, JSObject* aGlobal); - - static already_AddRefed - Constructor(const GlobalObject& aGlobal, ErrorResult& aRv); - - explicit FetchController(nsIGlobalObject* aGlobal); - - JSObject* - WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - - nsIGlobalObject* - GetParentObject() const; - - FetchSignal* - Signal(); - - void - Abort(); - - void - Follow(FetchSignal& aSignal); - - void - Unfollow(FetchSignal& aSignal); - - FetchSignal* - Following() const; - - // FetchSignal::Follower - - void Aborted() override; - -private: - ~FetchController() = default; - - nsCOMPtr mGlobal; - RefPtr mSignal; - - bool mAborted; -}; - -} // dom namespace -} // mozilla namespace - -#endif // mozilla_dom_FetchController_h diff --git a/dom/abort/FetchSignal.cpp b/dom/abort/FetchSignal.cpp deleted file mode 100644 index 07ad6b53d..000000000 --- a/dom/abort/FetchSignal.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/* -*- 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 "FetchSignal.h" -#include "FetchController.h" -#include "mozilla/dom/Event.h" -#include "mozilla/dom/FetchSignalBinding.h" - -namespace mozilla { -namespace dom { - -NS_IMPL_CYCLE_COLLECTION_CLASS(FetchSignal) - -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FetchSignal, - DOMEventTargetHelper) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mController) -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END - -NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FetchSignal, - DOMEventTargetHelper) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mController) -NS_IMPL_CYCLE_COLLECTION_UNLINK_END - -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FetchSignal) -NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) - -NS_IMPL_ADDREF_INHERITED(FetchSignal, DOMEventTargetHelper) -NS_IMPL_RELEASE_INHERITED(FetchSignal, DOMEventTargetHelper) - -FetchSignal::FetchSignal(FetchController* aController, - bool aAborted) - : DOMEventTargetHelper(aController->GetParentObject()) - , mController(aController) - , mAborted(aAborted) -{} - -FetchSignal::FetchSignal(bool aAborted) - : mAborted(aAborted) -{} - -JSObject* -FetchSignal::WrapObject(JSContext* aCx, JS::Handle aGivenProto) -{ - return FetchSignalBinding::Wrap(aCx, this, aGivenProto); -} - -bool -FetchSignal::Aborted() const -{ - return mAborted; -} - -void -FetchSignal::Abort() -{ - MOZ_ASSERT(!mAborted); - mAborted = true; - - // Let's inform the followers. - for (uint32_t i = 0; i < mFollowers.Length(); ++i) { - mFollowers[i]->Aborted(); - } - - EventInit init; - init.mBubbles = false; - init.mCancelable = false; - - // TODO which kind of event should we dispatch here? - - RefPtr event = - Event::Constructor(this, NS_LITERAL_STRING("abort"), init); - event->SetTrusted(true); - - bool dummy; - DispatchEvent(event, &dummy); -} - -void -FetchSignal::AddFollower(FetchSignal::Follower* aFollower) -{ - MOZ_DIAGNOSTIC_ASSERT(aFollower); - if (!mFollowers.Contains(aFollower)) { - mFollowers.AppendElement(aFollower); - } -} - -void -FetchSignal::RemoveFollower(FetchSignal::Follower* aFollower) -{ - MOZ_DIAGNOSTIC_ASSERT(aFollower); - mFollowers.RemoveElement(aFollower); -} - -bool -FetchSignal::CanAcceptFollower(FetchSignal::Follower* aFollower) const -{ - MOZ_DIAGNOSTIC_ASSERT(aFollower); - - if (!mController) { - return true; - } - - if (aFollower == mController) { - return false; - } - - FetchSignal* following = mController->Following(); - if (!following) { - return true; - } - - return following->CanAcceptFollower(aFollower); -} - -// FetchSignal::Follower -// ---------------------------------------------------------------------------- - -FetchSignal::Follower::~Follower() -{ - Unfollow(); -} - -void -FetchSignal::Follower::Follow(FetchSignal* aSignal) -{ - MOZ_DIAGNOSTIC_ASSERT(aSignal); - - if (!aSignal->CanAcceptFollower(this)) { - return; - } - - Unfollow(); - - mFollowingSignal = aSignal; - aSignal->AddFollower(this); -} - -void -FetchSignal::Follower::Unfollow() -{ - if (mFollowingSignal) { - mFollowingSignal->RemoveFollower(this); - mFollowingSignal = nullptr; - } -} - -} // dom namespace -} // mozilla namespace diff --git a/dom/abort/FetchSignal.h b/dom/abort/FetchSignal.h deleted file mode 100644 index 4970f03de..000000000 --- a/dom/abort/FetchSignal.h +++ /dev/null @@ -1,79 +0,0 @@ -/* -*- 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_FetchSignal_h -#define mozilla_dom_FetchSignal_h - -#include "mozilla/DOMEventTargetHelper.h" - -namespace mozilla { -namespace dom { - -class FetchController; -class FetchSignal; - -class FetchSignal final : public DOMEventTargetHelper -{ -public: - // This class must be implemented by objects who want to follow a FetchSignal. - class Follower - { - public: - virtual void Aborted() = 0; - - protected: - virtual ~Follower(); - - void - Follow(FetchSignal* aSignal); - - void - Unfollow(); - - RefPtr mFollowingSignal; - }; - - NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchSignal, DOMEventTargetHelper) - - FetchSignal(FetchController* aController, bool aAborted); - explicit FetchSignal(bool aAborted); - - JSObject* - WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - - bool - Aborted() const; - - void - Abort(); - - IMPL_EVENT_HANDLER(abort); - - void - AddFollower(Follower* aFollower); - - void - RemoveFollower(Follower* aFollower); - - bool - CanAcceptFollower(Follower* aFollower) const; - -private: - ~FetchSignal() = default; - - RefPtr mController; - - // Raw pointers. Follower unregisters itself in the DTOR. - nsTArray mFollowers; - - bool mAborted; -}; - -} // dom namespace -} // mozilla namespace - -#endif // mozilla_dom_FetchSignal_h diff --git a/dom/abort/moz.build b/dom/abort/moz.build index e7d8146e5..cb48ee15f 100644 --- a/dom/abort/moz.build +++ b/dom/abort/moz.build @@ -10,13 +10,13 @@ with Files("**"): TEST_DIRS += ['tests'] EXPORTS.mozilla.dom += [ - 'FetchController.h', - 'FetchSignal.h', + 'AbortController.h', + 'AbortSignal.h', ] UNIFIED_SOURCES += [ - 'FetchController.cpp', - 'FetchSignal.cpp', + 'AbortController.cpp', + 'AbortSignal.cpp', ] LOCAL_INCLUDES += [ diff --git a/dom/abort/tests/file_abort_controller.html b/dom/abort/tests/file_abort_controller.html new file mode 100644 index 000000000..e4137aac9 --- /dev/null +++ b/dom/abort/tests/file_abort_controller.html @@ -0,0 +1,161 @@ + diff --git a/dom/abort/tests/file_fetch_controller.html b/dom/abort/tests/file_fetch_controller.html deleted file mode 100644 index e4137aac9..000000000 --- a/dom/abort/tests/file_fetch_controller.html +++ /dev/null @@ -1,161 +0,0 @@ - diff --git a/dom/abort/tests/mochitest.ini b/dom/abort/tests/mochitest.ini index 5ecc7048e..c8cc95fda 100644 --- a/dom/abort/tests/mochitest.ini +++ b/dom/abort/tests/mochitest.ini @@ -1,6 +1,6 @@ [DEFAULT] support-files = - file_fetch_controller.html + file_abort_controller.html worker_fetch_controller.js -[test_fetch_controller.html] +[test_abort_controller.html] diff --git a/dom/abort/tests/test_abort_controller.html b/dom/abort/tests/test_abort_controller.html new file mode 100644 index 000000000..812fb9161 --- /dev/null +++ b/dom/abort/tests/test_abort_controller.html @@ -0,0 +1,40 @@ + + + + + Test FetchController + + + + + + + + diff --git a/dom/abort/tests/test_fetch_controller.html b/dom/abort/tests/test_fetch_controller.html deleted file mode 100644 index 812fb9161..000000000 --- a/dom/abort/tests/test_fetch_controller.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - Test FetchController - - - - - - - - diff --git a/dom/abort/tests/worker_abort_controller.js b/dom/abort/tests/worker_abort_controller.js new file mode 100644 index 000000000..6fd1c7888 --- /dev/null +++ b/dom/abort/tests/worker_abort_controller.js @@ -0,0 +1,27 @@ +function testWorkerAbortedFetch() { + var fc = new AbortController(); + fc.abort(); + + fetch('slow.sjs', { signal: fc.signal }).then(() => { + postMessage(false); + }, e => { + postMessage(e.name == "AbortError"); + }); +} + +function testWorkerFetchAndAbort() { + var fc = new AbortController(); + + var p = fetch('slow.sjs', { signal: fc.signal }); + fc.abort(); + + p.then(() => { + postMessage(false); + }, e => { + postMessage(e.name == "AbortError"); + }); +} + +onmessage = function(e) { + self[e.data](); +} diff --git a/dom/abort/tests/worker_fetch_controller.js b/dom/abort/tests/worker_fetch_controller.js deleted file mode 100644 index 6b008fea8..000000000 --- a/dom/abort/tests/worker_fetch_controller.js +++ /dev/null @@ -1,27 +0,0 @@ -function testWorkerAbortedFetch() { - var fc = new FetchController(); - fc.abort(); - - fetch('slow.sjs', { signal: fc.signal }).then(() => { - postMessage(false); - }, e => { - postMessage(e.name == "AbortError"); - }); -} - -function testWorkerFetchAndAbort() { - var fc = new FetchController(); - - var p = fetch('slow.sjs', { signal: fc.signal }); - fc.abort(); - - p.then(() => { - postMessage(false); - }, e => { - postMessage(e.name == "AbortError"); - }); -} - -onmessage = function(e) { - self[e.data](); -} diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp index 04aa7fd91..4dbe2de0a 100644 --- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp @@ -53,24 +53,24 @@ namespace dom { using namespace workers; -// This class helps the proxying of FetchSignal changes cross threads. -class FetchSignalProxy final : public FetchSignal::Follower +// This class helps the proxying of AbortSignal changes cross threads. +class AbortSignalProxy final : public AbortSignal::Follower { // This is created and released on the main-thread. - RefPtr mSignalMainThread; + RefPtr mSignalMainThread; - // This value is used only for the creation of FetchSignal on the + // This value is used only for the creation of AbortSignal on the // main-thread. They are not updated. const bool mAborted; - // This runnable propagates changes from the FetchSignal on workers to the - // FetchSignal on main-thread. - class FetchSignalProxyRunnable final : public Runnable + // This runnable propagates changes from the AbortSignal on workers to the + // AbortSignal on main-thread. + class AbortSignalProxyRunnable final : public Runnable { - RefPtr mProxy; + RefPtr mProxy; public: - explicit FetchSignalProxyRunnable(FetchSignalProxy* aProxy) + explicit AbortSignalProxyRunnable(AbortSignalProxy* aProxy) : mProxy(aProxy) {} @@ -78,16 +78,16 @@ class FetchSignalProxy final : public FetchSignal::Follower Run() override { MOZ_ASSERT(NS_IsMainThread()); - FetchSignal* signal = mProxy->GetOrCreateSignalForMainThread(); + AbortSignal* signal = mProxy->GetOrCreateSignalForMainThread(); signal->Abort(); return NS_OK; } }; public: - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FetchSignalProxy) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbortSignalProxy) - explicit FetchSignalProxy(FetchSignal* aSignal) + explicit AbortSignalProxy(AbortSignal* aSignal) : mAborted(aSignal->Aborted()) { Follow(aSignal); @@ -96,17 +96,17 @@ public: void Aborted() override { - RefPtr runnable = - new FetchSignalProxyRunnable(this); + RefPtr runnable = + new AbortSignalProxyRunnable(this); NS_DispatchToMainThread(runnable); } - FetchSignal* + AbortSignal* GetOrCreateSignalForMainThread() { MOZ_ASSERT(NS_IsMainThread()); if (!mSignalMainThread) { - mSignalMainThread = new FetchSignal(mAborted); + mSignalMainThread = new AbortSignal(mAborted); } return mSignalMainThread; } @@ -118,7 +118,7 @@ public: } private: - ~FetchSignalProxy() + ~AbortSignalProxy() { NS_ReleaseOnMainThread(mSignalMainThread.forget()); } @@ -133,14 +133,14 @@ class WorkerFetchResolver final : public FetchDriverObserver friend class WorkerFetchResponseRunnable; RefPtr mPromiseProxy; - RefPtr mSignalProxy; + RefPtr mSignalProxy; RefPtr mFetchObserver; public: // Returns null if worker is shutting down. static already_AddRefed Create(workers::WorkerPrivate* aWorkerPrivate, Promise* aPromise, - FetchSignal* aSignal, FetchObserver* aObserver) + AbortSignal* aSignal, FetchObserver* aObserver) { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); @@ -150,9 +150,9 @@ public: return nullptr; } - RefPtr signalProxy; + RefPtr signalProxy; if (aSignal) { - signalProxy = new FetchSignalProxy(aSignal); + signalProxy = new AbortSignalProxy(aSignal); } RefPtr r = @@ -160,8 +160,8 @@ public: return r.forget(); } - FetchSignal* - GetFetchSignal() + AbortSignal* + GetAbortSignal() { MOZ_ASSERT(NS_IsMainThread()); @@ -183,7 +183,7 @@ public: private: WorkerFetchResolver(PromiseWorkerProxy* aProxy, - FetchSignalProxy* aSignalProxy, + AbortSignalProxy* aSignalProxy, FetchObserver* aObserver) : mPromiseProxy(aProxy) , mSignalProxy(aSignalProxy) @@ -287,7 +287,7 @@ public: fetch->SetWorkerScript(spec); } - RefPtr signal = mResolver->GetFetchSignal(); + RefPtr signal = mResolver->GetAbortSignal(); // ...but release it before calling Fetch, because mResolver's callback can // be called synchronously and they want the mutex, too. @@ -329,7 +329,7 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, RefPtr r = request->GetInternalRequest(); - RefPtr signal; + RefPtr signal; if (aInit.mSignal.WasPassed()) { signal = &aInit.mSignal.Value(); // Let's FetchDriver to deal with an already aborted signal. diff --git a/dom/fetch/FetchDriver.cpp b/dom/fetch/FetchDriver.cpp index e8d726ce1..067e32db4 100644 --- a/dom/fetch/FetchDriver.cpp +++ b/dom/fetch/FetchDriver.cpp @@ -67,7 +67,7 @@ FetchDriver::~FetchDriver() } nsresult -FetchDriver::Fetch(FetchSignal* aSignal, FetchDriverObserver* aObserver) +FetchDriver::Fetch(AbortSignal* aSignal, FetchDriverObserver* aObserver) { workers::AssertIsOnMainThread(); #ifdef DEBUG diff --git a/dom/fetch/FetchDriver.h b/dom/fetch/FetchDriver.h index e942aa242..57dffa5a7 100644 --- a/dom/fetch/FetchDriver.h +++ b/dom/fetch/FetchDriver.h @@ -12,7 +12,7 @@ #include "nsIStreamListener.h" #include "nsIThreadRetargetableStreamListener.h" #include "mozilla/ConsoleReportCollector.h" -#include "mozilla/dom/FetchSignal.h" +#include "mozilla/dom/AbortSignal.h" #include "mozilla/dom/SRIMetadata.h" #include "mozilla/RefPtr.h" @@ -84,7 +84,7 @@ class FetchDriver final : public nsIStreamListener, public nsIChannelEventSink, public nsIInterfaceRequestor, public nsIThreadRetargetableStreamListener, - public FetchSignal::Follower + public AbortSignal::Follower { public: NS_DECL_ISUPPORTS @@ -98,7 +98,7 @@ public: nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup); - nsresult Fetch(FetchSignal* aSignal, + nsresult Fetch(AbortSignal* aSignal, FetchDriverObserver* aObserver); void @@ -111,7 +111,7 @@ public: mWorkerScript = aWorkerScirpt; } - // FetchSignal::Follower + // AbortSignal::Follower void Aborted() override; diff --git a/dom/fetch/FetchObserver.cpp b/dom/fetch/FetchObserver.cpp index 982f0ad49..93d93773f 100644 --- a/dom/fetch/FetchObserver.cpp +++ b/dom/fetch/FetchObserver.cpp @@ -46,7 +46,7 @@ FetchObserver::IsEnabled(JSContext* aCx, JSObject* aGlobal) } FetchObserver::FetchObserver(nsIGlobalObject* aGlobal, - FetchSignal* aSignal) + AbortSignal* aSignal) : DOMEventTargetHelper(aGlobal) , mState(FetchState::Requesting) { diff --git a/dom/fetch/FetchObserver.h b/dom/fetch/FetchObserver.h index 45adf2ba1..5cd835b3d 100644 --- a/dom/fetch/FetchObserver.h +++ b/dom/fetch/FetchObserver.h @@ -9,13 +9,13 @@ #include "mozilla/DOMEventTargetHelper.h" #include "mozilla/dom/FetchObserverBinding.h" -#include "mozilla/dom/FetchSignal.h" +#include "mozilla/dom/AbortSignal.h" namespace mozilla { namespace dom { class FetchObserver final : public DOMEventTargetHelper - , public FetchSignal::Follower + , public AbortSignal::Follower { public: NS_DECL_ISUPPORTS_INHERITED @@ -24,7 +24,7 @@ public: static bool IsEnabled(JSContext* aCx, JSObject* aGlobal); - FetchObserver(nsIGlobalObject* aGlobal, FetchSignal* aSignal); + FetchObserver(nsIGlobalObject* aGlobal, AbortSignal* aSignal); JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; diff --git a/dom/fetch/Request.h b/dom/fetch/Request.h index 56a75e5af..f6fe9be7b 100644 --- a/dom/fetch/Request.h +++ b/dom/fetch/Request.h @@ -12,7 +12,7 @@ #include "nsWrapperCache.h" #include "mozilla/dom/Fetch.h" -#include "mozilla/dom/FetchSignal.h" +#include "mozilla/dom/AbortSignal.h" #include "mozilla/dom/InternalRequest.h" // Required here due to certain WebIDL enums/classes being declared in both // files. diff --git a/dom/tests/mochitest/fetch/file_fetch_observer.html b/dom/tests/mochitest/fetch/file_fetch_observer.html index a172a18dc..668e609ec 100644 --- a/dom/tests/mochitest/fetch/file_fetch_observer.html +++ b/dom/tests/mochitest/fetch/file_fetch_observer.html @@ -19,7 +19,7 @@ function testObserver() { } function testObserveAbort() { - var fc = new FetchController(); + var fc = new AbortController(); fetch('http://mochi.test:8888/tests/dom/tests/mochitest/fetch/slow.sjs', { signal: fc.signal, @@ -37,7 +37,7 @@ function testObserveAbort() { } function testObserveComplete() { - var fc = new FetchController(); + var fc = new AbortController(); fetch('http://mochi.test:8888/tests/dom/tests/mochitest/fetch/slow.sjs', { signal: fc.signal, @@ -54,7 +54,7 @@ function testObserveComplete() { } function testObserveErrored() { - var fc = new FetchController(); + var fc = new AbortController(); fetch('foo: bar', { signal: fc.signal, @@ -71,7 +71,7 @@ function testObserveErrored() { } function testObserveResponding() { - var fc = new FetchController(); + var fc = new AbortController(); fetch('http://mochi.test:8888/tests/dom/tests/mochitest/fetch/slow.sjs', { signal: fc.signal, diff --git a/dom/webidl/AbortController.webidl b/dom/webidl/AbortController.webidl new file mode 100644 index 000000000..f5d8f317b --- /dev/null +++ b/dom/webidl/AbortController.webidl @@ -0,0 +1,15 @@ +/* -*- Mode: IDL; 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/. + */ + +[Constructor(), Exposed=(Window,Worker), + Func="AbortController::IsEnabled"] +interface AbortController { + readonly attribute AbortSignal signal; + + void abort(); + void follow(AbortSignal signal); + void unfollow(AbortSignal signal); +}; diff --git a/dom/webidl/AbortSignal.webidl b/dom/webidl/AbortSignal.webidl new file mode 100644 index 000000000..b4b03bb7e --- /dev/null +++ b/dom/webidl/AbortSignal.webidl @@ -0,0 +1,13 @@ +/* -*- Mode: IDL; 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/. + */ + +[Exposed=(Window,Worker), + Func="AbortController::IsEnabled"] +interface AbortSignal : EventTarget { + readonly attribute boolean aborted; + + attribute EventHandler onabort; +}; diff --git a/dom/webidl/FetchController.webidl b/dom/webidl/FetchController.webidl deleted file mode 100644 index c5b1cc6da..000000000 --- a/dom/webidl/FetchController.webidl +++ /dev/null @@ -1,15 +0,0 @@ -/* -*- Mode: IDL; 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/. - */ - -[Constructor(), Exposed=(Window,Worker), - Func="FetchController::IsEnabled"] -interface FetchController { - readonly attribute FetchSignal signal; - - void abort(); - void follow(FetchSignal signal); - void unfollow(FetchSignal signal); -}; diff --git a/dom/webidl/FetchSignal.webidl b/dom/webidl/FetchSignal.webidl deleted file mode 100644 index 965355c20..000000000 --- a/dom/webidl/FetchSignal.webidl +++ /dev/null @@ -1,13 +0,0 @@ -/* -*- Mode: IDL; 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/. - */ - -[Exposed=(Window,Worker), - Func="FetchController::IsEnabled"] -interface FetchSignal : EventTarget { - readonly attribute boolean aborted; - - attribute EventHandler onabort; -}; diff --git a/dom/webidl/Request.webidl b/dom/webidl/Request.webidl index 8c6e33da3..fe6a63ec0 100644 --- a/dom/webidl/Request.webidl +++ b/dom/webidl/Request.webidl @@ -48,8 +48,8 @@ dictionary RequestInit { RequestRedirect redirect; DOMString integrity; - [Func="FetchController::IsEnabled"] - FetchSignal signal; + [Func="AbortController::IsEnabled"] + AbortSignal signal; [Func="FetchObserver::IsEnabled"] ObserverCallback observe; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 0963e3d01..d8309c265 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -17,6 +17,8 @@ PREPROCESSED_WEBIDL_FILES = [ ] WEBIDL_FILES = [ + 'AbortController.webidl', + 'AbortSignal.webidl', 'AbstractWorker.webidl', 'AnalyserNode.webidl', 'Animatable.webidl', @@ -141,10 +143,8 @@ WEBIDL_FILES = [ 'ExtendableMessageEvent.webidl', 'FakePluginTagInit.webidl', 'Fetch.webidl', - 'FetchController.webidl', 'FetchEvent.webidl', 'FetchObserver.webidl', - 'FetchSignal.webidl', 'File.webidl', 'FileList.webidl', 'FileMode.webidl', diff --git a/dom/workers/WorkerPrefs.h b/dom/workers/WorkerPrefs.h index 374f41ecf..b552d8956 100644 --- a/dom/workers/WorkerPrefs.h +++ b/dom/workers/WorkerPrefs.h @@ -39,7 +39,7 @@ WORKER_SIMPLE_PREF("dom.push.enabled", PushEnabled, PUSH_ENABLED) WORKER_SIMPLE_PREF("dom.requestcontext.enabled", RequestContextEnabled, REQUESTCONTEXT_ENABLED) WORKER_SIMPLE_PREF("gfx.offscreencanvas.enabled", OffscreenCanvasEnabled, OFFSCREENCANVAS_ENABLED) WORKER_SIMPLE_PREF("dom.webkitBlink.dirPicker.enabled", WebkitBlinkDirectoryPickerEnabled, DOM_WEBKITBLINK_DIRPICKER_WEBKITBLINK) -WORKER_SIMPLE_PREF("dom.fetchController.enabled", FetchControllerEnabled, FETCHCONTROLLER_ENABLED) +WORKER_SIMPLE_PREF("dom.abortController.enabled", AbortControllerEnabled, ABORTCONTROLLER_ENABLED) WORKER_SIMPLE_PREF("dom.fetchObserver.enabled", FetchObserverEnabled, FETCHOBSERVER_ENABLED) WORKER_PREF("dom.workers.latestJSVersion", JSVersionChanged) WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) -- cgit v1.2.3 From 301ae8c2df5dcec2c8d736e0397db563b621b9fd Mon Sep 17 00:00:00 2001 From: Moonchild Date: Fri, 12 Jun 2020 01:08:26 +0000 Subject: Issue #1587 - Part 8: Remove controller follow/unfollow Since it didn't end up being in the final spec after all. --- dom/abort/AbortController.cpp | 31 +------------------ dom/abort/AbortController.h | 14 --------- dom/abort/AbortSignal.cpp | 27 ----------------- dom/abort/AbortSignal.h | 3 -- dom/abort/tests/file_abort_controller.html | 48 ------------------------------ dom/webidl/AbortController.webidl | 2 -- 6 files changed, 1 insertion(+), 124 deletions(-) diff --git a/dom/abort/AbortController.cpp b/dom/abort/AbortController.cpp index 4043b7bf3..bd8159e7b 100644 --- a/dom/abort/AbortController.cpp +++ b/dom/abort/AbortController.cpp @@ -12,8 +12,7 @@ namespace mozilla { namespace dom { -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AbortController, mGlobal, mSignal, - mFollowingSignal) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AbortController, mGlobal, mSignal) NS_IMPL_CYCLE_COLLECTING_ADDREF(AbortController) NS_IMPL_CYCLE_COLLECTING_RELEASE(AbortController) @@ -95,33 +94,5 @@ AbortController::Abort() } } -void -AbortController::Follow(AbortSignal& aSignal) -{ - AbortSignal::Follower::Follow(&aSignal); -} - -void -AbortController::Unfollow(AbortSignal& aSignal) -{ - if (mFollowingSignal != &aSignal) { - return; - } - - AbortSignal::Follower::Unfollow(); -} - -AbortSignal* -AbortController::Following() const -{ - return mFollowingSignal; -} - -void -AbortController::Aborted() -{ - Abort(); -} - } // dom namespace } // mozilla namespace diff --git a/dom/abort/AbortController.h b/dom/abort/AbortController.h index 9ce4253c9..0b99dc49c 100644 --- a/dom/abort/AbortController.h +++ b/dom/abort/AbortController.h @@ -19,7 +19,6 @@ namespace dom { class AbortController final : public nsISupports , public nsWrapperCache - , public AbortSignal::Follower { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS @@ -45,19 +44,6 @@ public: void Abort(); - void - Follow(AbortSignal& aSignal); - - void - Unfollow(AbortSignal& aSignal); - - AbortSignal* - Following() const; - - // AbortSignal::Follower - - void Aborted() override; - private: ~AbortController() = default; diff --git a/dom/abort/AbortSignal.cpp b/dom/abort/AbortSignal.cpp index 38b0a2492..20f36d2ab 100644 --- a/dom/abort/AbortSignal.cpp +++ b/dom/abort/AbortSignal.cpp @@ -68,8 +68,6 @@ AbortSignal::Abort() init.mBubbles = false; init.mCancelable = false; - // TODO which kind of event should we dispatch here? - RefPtr event = Event::Constructor(this, NS_LITERAL_STRING("abort"), init); event->SetTrusted(true); @@ -94,27 +92,6 @@ AbortSignal::RemoveFollower(AbortSignal::Follower* aFollower) mFollowers.RemoveElement(aFollower); } -bool -AbortSignal::CanAcceptFollower(AbortSignal::Follower* aFollower) const -{ - MOZ_DIAGNOSTIC_ASSERT(aFollower); - - if (!mController) { - return true; - } - - if (aFollower == mController) { - return false; - } - - AbortSignal* following = mController->Following(); - if (!following) { - return true; - } - - return following->CanAcceptFollower(aFollower); -} - // AbortSignal::Follower // ---------------------------------------------------------------------------- @@ -128,10 +105,6 @@ AbortSignal::Follower::Follow(AbortSignal* aSignal) { MOZ_DIAGNOSTIC_ASSERT(aSignal); - if (!aSignal->CanAcceptFollower(this)) { - return; - } - Unfollow(); mFollowingSignal = aSignal; diff --git a/dom/abort/AbortSignal.h b/dom/abort/AbortSignal.h index c5045dbd6..35e582942 100644 --- a/dom/abort/AbortSignal.h +++ b/dom/abort/AbortSignal.h @@ -59,9 +59,6 @@ public: void RemoveFollower(Follower* aFollower); - bool - CanAcceptFollower(Follower* aFollower) const; - private: ~AbortSignal() = default; diff --git a/dom/abort/tests/file_abort_controller.html b/dom/abort/tests/file_abort_controller.html index e4137aac9..3a15fa346 100644 --- a/dom/abort/tests/file_abort_controller.html +++ b/dom/abort/tests/file_abort_controller.html @@ -32,49 +32,6 @@ function testUpdateData() { next(); } -function testFollowingOurself() { - // Let's follow ourself - var fc = new FetchController(); - fc.follow(fc.signal); - - fc.abort(); - is(fc.signal.aborted, true, "Signal is aborted"); - - next(); -} - -function testFollowingOther() { - // Let's follow another one - var fc1 = new FetchController(); - var fc2 = new FetchController(); - fc1.follow(fc2.signal); - - fc2.abort(); - - is(fc1.signal.aborted, true, "Signal is aborted"); - is(fc2.signal.aborted, true, "Signal is aborted"); - - next(); -} - -function testFollowingLoop() { - // fc1 -> fc2 -> fc3 -> fc1 - var fc1 = new FetchController(); - var fc2 = new FetchController(); - var fc3 = new FetchController(); - fc1.follow(fc2.signal); - fc2.follow(fc3.signal); - fc3.follow(fc1.signal); - - fc3.abort(); - - is(fc1.signal.aborted, true, "Signal is aborted"); - is(fc2.signal.aborted, true, "Signal is aborted"); - is(fc3.signal.aborted, true, "Signal is aborted"); - - next(); -} - function testAbortEvent() { var fc = new FetchController(); fc.signal.onabort = function(e) { @@ -131,11 +88,6 @@ var steps = [ testWebIDL, testUpdateData, - // Following algorithm - testFollowingOurself, - testFollowingOther, - testFollowingLoop, - // Event propagation testAbortEvent, diff --git a/dom/webidl/AbortController.webidl b/dom/webidl/AbortController.webidl index f5d8f317b..4e9124075 100644 --- a/dom/webidl/AbortController.webidl +++ b/dom/webidl/AbortController.webidl @@ -10,6 +10,4 @@ interface AbortController { readonly attribute AbortSignal signal; void abort(); - void follow(AbortSignal signal); - void unfollow(AbortSignal signal); }; -- cgit v1.2.3 From e2403eeb5ac9009b9a7b794059fe6df245cf74dd Mon Sep 17 00:00:00 2001 From: Moonchild Date: Fri, 12 Jun 2020 01:46:48 +0000 Subject: Issue #1587 - Part 9: Immediately reject an already-aborted signal --- dom/fetch/Fetch.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp index 4dbe2de0a..191f4cfc3 100644 --- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp @@ -332,7 +332,12 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, RefPtr signal; if (aInit.mSignal.WasPassed()) { signal = &aInit.mSignal.Value(); - // Let's FetchDriver to deal with an already aborted signal. + } + + if (signal && signal->Aborted()) { + // An already aborted signal should reject immediately. + aRv.Throw(NS_ERROR_DOM_ABORT_ERR); + return nullptr; } RefPtr observer; -- cgit v1.2.3 From f5b146a460b33c06a97c7fe252e0983aed522e5b Mon Sep 17 00:00:00 2001 From: Moonchild Date: Fri, 12 Jun 2020 01:47:54 +0000 Subject: Issue #1587 - Part 10: Expose controlling pref and enable by default --- modules/libpref/init/all.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 5ee49372f..6edd41e70 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4741,6 +4741,9 @@ pref("dom.vibrator.max_vibrate_list_len", 128); // Disabled by default to reduce private data exposure. pref("dom.battery.enabled", false); +// Abort API +pref("dom.abortController.enabled", true); + // Push pref("dom.push.enabled", false); -- cgit v1.2.3 From aa19e3d8baa47df038d0bae548226a6b8fc736a8 Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Tue, 9 Jun 2020 23:37:32 -0400 Subject: Bug 1613623 - For IMAP, no longer allow STARTTLS when server sends PREAUTH greeting. Tag #1312 --- mailnews/imap/src/nsImapProtocol.cpp | 54 +++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/mailnews/imap/src/nsImapProtocol.cpp b/mailnews/imap/src/nsImapProtocol.cpp index 940d87cbd..97e61a40e 100644 --- a/mailnews/imap/src/nsImapProtocol.cpp +++ b/mailnews/imap/src/nsImapProtocol.cpp @@ -1526,28 +1526,44 @@ void nsImapProtocol::EstablishServerConnection() } else if (!PL_strncasecmp(serverResponse, ESC_PREAUTH, ESC_PREAUTH_LEN)) { - // we've been pre-authenticated. - // we can skip the whole password step, right into the - // kAuthenticated state - GetServerStateParser().PreauthSetAuthenticatedState(); + // PREAUTH greeting received. We've been pre-authenticated by the server. + // We can skip sending a password and transition right into the + // kAuthenticated state; but we won't if the user has configured STARTTLS. + // (STARTTLS can only occur with the server in non-authenticated state.) + if (!(m_socketType == nsMsgSocketType::alwaysSTARTTLS || + m_socketType == nsMsgSocketType::trySTARTTLS)) { + GetServerStateParser().PreauthSetAuthenticatedState(); - if (GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined) - Capability(); + if (GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined) + Capability(); - if ( !(GetServerStateParser().GetCapabilityFlag() & - (kIMAP4Capability | kIMAP4rev1Capability | kIMAP4other) ) ) - { - // AlertUserEvent_UsingId(MK_MSG_IMAP_SERVER_NOT_IMAP4); - SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib - } - else - { - // let's record the user as authenticated. - m_imapServerSink->SetUserAuthenticated(true); + if (!(GetServerStateParser().GetCapabilityFlag() & + (kIMAP4Capability | kIMAP4rev1Capability | kIMAP4other))) { + // AlertUserEventUsingId(MK_MSG_IMAP_SERVER_NOT_IMAP4); + SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib + } else { + // let's record the user as authenticated. + m_imapServerSink->SetUserAuthenticated(true); - ProcessAfterAuthenticated(); - // the connection was a success - SetConnectionStatus(NS_OK); + ProcessAfterAuthenticated(); + // the connection was a success + SetConnectionStatus(NS_OK); + } + } else { + // STARTTLS is configured so don't transition to authenticated state. Just + // alert the user, log the error and drop the connection. This may + // indicate a man-in-the middle attack if the user is not expecting + // PREAUTH. The user must change the connection security setting to other + // than STARTTLS to allow PREAUTH to be accepted on subsequent IMAP + // connections. + AlertUserEventUsingName("imapServerDisconnected"); + const nsCString &hostName = GetImapHostName(); + MOZ_LOG( + IMAP, LogLevel::Error, + ("PREAUTH received from IMAP server %s because STARTTLS selected. " + "Connection dropped", + hostName.get())); + SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib } } -- cgit v1.2.3 From 4d9da86152bb29e82c4e9b0be7393936f1595f94 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Sun, 14 Jun 2020 21:43:38 +0000 Subject: Issue #1594 - restore UAC elevation in the uninstaller. Effectively backs out BZ 1333789 which was uplifted to Firefox before we forked for UXP. Resolves #1594 --- toolkit/mozapps/installer/windows/nsis/common.nsh | 35 +++++------------------ 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/toolkit/mozapps/installer/windows/nsis/common.nsh b/toolkit/mozapps/installer/windows/nsis/common.nsh index 57a25df9d..ed15a09f5 100755 --- a/toolkit/mozapps/installer/windows/nsis/common.nsh +++ b/toolkit/mozapps/installer/windows/nsis/common.nsh @@ -5120,6 +5120,9 @@ ${GetParameters} $R8 + ; Require elevation if the user can elevate + ${ElevateUAC} + ${If} $R8 != "" ; Default install type StrCpy $InstallType ${INSTALLTYPE_BASIC} @@ -5172,28 +5175,14 @@ FileClose $R5 Delete $R6 ${If} ${Errors} - ; Attempt to elevate and then try again. - ${ElevateUAC} - GetTempFileName $R6 "$INSTDIR" - FileOpen $R5 "$R6" w - FileWrite $R5 "Write Access Test" - FileClose $R5 - Delete $R6 - ${If} ${Errors} - ; Nothing initialized so no need to call OnEndCommon - Quit - ${EndIf} + ; Nothing initialized so no need to call OnEndCommon + Quit ${EndIf} ${Else} CreateDirectory "$INSTDIR" ${If} ${Errors} - ; Attempt to elevate and then try again. - ${ElevateUAC} - CreateDirectory "$INSTDIR" - ${If} ${Errors} - ; Nothing initialized so no need to call OnEndCommon - Quit - ${EndIf} + ; Nothing initialized so no need to call OnEndCommon + Quit ${EndIf} ${EndIf} @@ -5225,20 +5214,10 @@ ${EndIf} !endif ${EndIf} - ${Else} - ; If this isn't an INI install, we need to try to elevate now. - ; We'll check the user's permission level later on to determine the - ; default install path (which will be the real install path for /S). - ; If an INI file is used, we try to elevate down that path when needed. - ${ElevateUAC} ${EndUnless} ${EndIf} ClearErrors - ${IfNot} ${Silent} - ${ElevateUAC} - ${EndIf} - Pop $R5 Pop $R6 Pop $R7 -- cgit v1.2.3 From d41fe2f55a0a2d342ef5e415855bdaa62b22b389 Mon Sep 17 00:00:00 2001 From: Lootyhoof Date: Sun, 14 Jun 2020 23:23:23 +0100 Subject: Follow up to Issue #1578 - Remove unnecessary MOZ_ASSERT --- widget/gtk/nsNativeMenuService.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/widget/gtk/nsNativeMenuService.cpp b/widget/gtk/nsNativeMenuService.cpp index eabe77ca8..7b92e73e8 100644 --- a/widget/gtk/nsNativeMenuService.cpp +++ b/widget/gtk/nsNativeMenuService.cpp @@ -160,7 +160,6 @@ nsNativeMenuService::~nsNativeMenuService() { gPangoLayout = nullptr; } - MOZ_ASSERT(sService == this); sService = nullptr; } -- cgit v1.2.3 From 1fa428a7898b213149cc4737a71e79aeb18d60cc Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sat, 27 Jun 2020 02:09:19 +0200 Subject: Issue #1602 - Make sure we have a JSObject before trying to get global. Dynamic script loading/unloading (thank you modules) can yank the script out from under us before the JS API for it is initialized, leading to null deref crashes. This adds a simple check if the passed-in object is sane and present. Resolves #1602 --- dom/base/ScriptSettings.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dom/base/ScriptSettings.cpp b/dom/base/ScriptSettings.cpp index d67f2167a..92ab221c9 100644 --- a/dom/base/ScriptSettings.cpp +++ b/dom/base/ScriptSettings.cpp @@ -485,7 +485,13 @@ AutoJSAPI::Init(nsIGlobalObject* aGlobalObject) bool AutoJSAPI::Init(JSObject* aObject) { - return Init(xpc::NativeGlobal(aObject)); + nsIGlobalObject* global = nullptr; + if (aObject) + global = xpc::NativeGlobal(aObject); + if (global) + return Init(global); + else + return false; } bool -- cgit v1.2.3 From 016313d559f71ba0d269e54b6c6e60dbe1e63280 Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Fri, 22 May 2020 11:02:24 -0400 Subject: Bug 1316302 - Part 1: Helper methods for HTMLEditRules::WillDeleteSelection() should have an out argument to indicates if it actually handles the action When HTMLEditRules::WillDeleteSelection() tries to remove something from the end/start of a block to its last/first text node but it's contained by block elements, it tries to join the container and the block. However, JoinBlocks() always fails to join them since it's impossible operation. In this case, HTMLEditRules::WillDeleteSelection() should retry to remove something in the leaf, however, it's impossible for now because JoinBlocks() and its helper methods don't return if it handles the action actually. This patch renames |JoinBlocks()| to |TryToJoinBlocks()| for representing what it is. And this patch adds |bool* aHandled| to the helper methods. Then, *aHandled and *aCancel are now always returns the result of each method. Therefore, for merging the result of multiple helper methods, callers need to receive the result with temporary variables and merge them by themselves. Note that when they modify DOM node actually or the action should do nothing (for example, selection is across tables), aHandled is set to true. Tag #1563 --- editor/libeditor/HTMLEditRules.cpp | 142 +++++++++++++++++++++++-------------- editor/libeditor/HTMLEditRules.h | 66 +++++++++++++++-- 2 files changed, 149 insertions(+), 59 deletions(-) diff --git a/editor/libeditor/HTMLEditRules.cpp b/editor/libeditor/HTMLEditRules.cpp index 545e22f70..13411b4f6 100644 --- a/editor/libeditor/HTMLEditRules.cpp +++ b/editor/libeditor/HTMLEditRules.cpp @@ -1844,10 +1844,10 @@ HTMLEditRules::WillDeleteSelection(Selection* aSelection, // origCollapsed is used later to determine whether we should join blocks. We // don't really care about bCollapsed because it will be modified by - // ExtendSelectionForDelete later. JoinBlocks should happen if the original - // selection is collapsed and the cursor is at the end of a block element, in - // which case ExtendSelectionForDelete would always make the selection not - // collapsed. + // ExtendSelectionForDelete later. TryToJoinBlocks() should happen if the + // original selection is collapsed and the cursor is at the end of a block + // element, in which case ExtendSelectionForDelete would always make the + // selection not collapsed. bool bCollapsed = aSelection->Collapsed(); bool join = false; bool origCollapsed = bCollapsed; @@ -2196,9 +2196,13 @@ HTMLEditRules::WillDeleteSelection(Selection* aSelection, address_of(selPointNode), &selPointOffset); NS_ENSURE_STATE(leftNode && leftNode->IsContent() && rightNode && rightNode->IsContent()); + bool handled = false, canceled = false; + rv = TryToJoinBlocks(*leftNode->AsContent(), *rightNode->AsContent(), + &canceled, &handled); + // TODO: If it does nothing and previous or next node is a text node, + // we should modify it. *aHandled = true; - rv = JoinBlocks(*leftNode->AsContent(), *rightNode->AsContent(), - aCancel); + *aCancel |= canceled; NS_ENSURE_SUCCESS(rv, rv); } aSelection->Collapse(selPointNode, selPointOffset); @@ -2247,9 +2251,14 @@ HTMLEditRules::WillDeleteSelection(Selection* aSelection, AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(selPointNode), &selPointOffset); NS_ENSURE_STATE(leftNode->IsContent() && rightNode->IsContent()); + bool handled = false, canceled = false; + rv = TryToJoinBlocks(*leftNode->AsContent(), *rightNode->AsContent(), + &canceled, &handled); + // This should claim that trying to join the block means that + // this handles the action because the caller shouldn't do anything + // anymore in this case. *aHandled = true; - rv = JoinBlocks(*leftNode->AsContent(), *rightNode->AsContent(), - aCancel); + *aCancel |= canceled; NS_ENSURE_SUCCESS(rv, rv); } aSelection->Collapse(selPointNode, selPointOffset); @@ -2421,7 +2430,10 @@ HTMLEditRules::WillDeleteSelection(Selection* aSelection, } if (join) { - rv = JoinBlocks(*leftParent, *rightParent, aCancel); + bool handled = false, canceled = false; + rv = TryToJoinBlocks(*leftParent, *rightParent, &canceled, &handled); + MOZ_ASSERT(*aHandled); + *aCancel |= canceled; NS_ENSURE_SUCCESS(rv, rv); } } @@ -2571,21 +2583,17 @@ HTMLEditRules::GetGoodSelPointForNode(nsINode& aNode, return ret; } - -/** - * This method is used to join two block elements. The right element is always - * joined to the left element. If the elements are the same type and not - * nested within each other, JoinNodesSmart is called (example, joining two - * list items together into one). If the elements are not the same type, or - * one is a descendant of the other, we instead destroy the right block placing - * its children into leftblock. DTD containment rules are followed throughout. - */ nsresult -HTMLEditRules::JoinBlocks(nsIContent& aLeftNode, - nsIContent& aRightNode, - bool* aCanceled) +HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, + nsIContent& aRightNode, + bool* aCanceled, + bool* aHandled) { MOZ_ASSERT(aCanceled); + MOZ_ASSERT(aHandled); + + *aCanceled = false; + *aHandled = false; NS_ENSURE_STATE(mHTMLEditor); RefPtr htmlEditor(mHTMLEditor); @@ -2601,6 +2609,7 @@ HTMLEditRules::JoinBlocks(nsIContent& aLeftNode, HTMLEditUtils::IsTableElement(rightBlock)) { // Do not try to merge table elements *aCanceled = true; + *aHandled = true; return NS_OK; } @@ -2617,6 +2626,7 @@ HTMLEditRules::JoinBlocks(nsIContent& aLeftNode, // Bail if both blocks the same if (leftBlock == rightBlock) { *aCanceled = true; + *aHandled = true; return NS_OK; } @@ -2624,6 +2634,7 @@ HTMLEditRules::JoinBlocks(nsIContent& aLeftNode, if (HTMLEditUtils::IsList(leftBlock) && HTMLEditUtils::IsListItem(rightBlock) && rightBlock->GetParentNode() == leftBlock) { + *aHandled = true; return NS_OK; } @@ -2694,11 +2705,15 @@ HTMLEditRules::JoinBlocks(nsIContent& aLeftNode, rv = htmlEditor->MoveNode(child, leftList, -1); NS_ENSURE_SUCCESS(rv, rv); } + // XXX Should this set to true only when above for loop moves the node? + *aHandled = true; } else { - MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset); + bool handled = false; + MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset, &handled); + *aHandled |= handled; } - if (brNode) { - htmlEditor->DeleteNode(brNode); + if (brNode && NS_SUCCEEDED(htmlEditor->DeleteNode(brNode))) { + *aHandled = true; } // Offset below is where you find yourself in leftBlock when you traverse // upwards from rightBlock @@ -2730,7 +2745,9 @@ HTMLEditRules::JoinBlocks(nsIContent& aLeftNode, nsCOMPtr brNode = CheckForInvisibleBR(*leftBlock, BRLocation::beforeBlock, leftOffset); if (mergeLists) { - MoveContents(*rightList, *leftList, &leftOffset); + bool handled = false; + MoveContents(*rightList, *leftList, &leftOffset, &handled); + *aHandled |= handled; } else { // Left block is a parent of right block, and the parent of the previous // visible content. Right block is a child and contains the contents we @@ -2786,12 +2803,14 @@ HTMLEditRules::JoinBlocks(nsIContent& aLeftNode, NS_ENSURE_TRUE(previousContentParent, NS_ERROR_NULL_POINTER); + bool handled = false; rv = MoveBlock(*previousContentParent->AsElement(), *rightBlock, - previousContentOffset, rightOffset); + previousContentOffset, rightOffset, &handled); + *aHandled |= handled; NS_ENSURE_SUCCESS(rv, rv); } - if (brNode) { - htmlEditor->DeleteNode(brNode); + if (brNode && NS_SUCCEEDED(htmlEditor->DeleteNode(brNode))) { + *aHandled = true; } } else { // Normal case. Blocks are siblings, or at least close enough. An example @@ -2815,32 +2834,37 @@ HTMLEditRules::JoinBlocks(nsIContent& aLeftNode, ConvertListType(rightBlock, getter_AddRefs(newBlock), existingList, nsGkAtoms::li); } + *aHandled = true; } else { // Nodes are dissimilar types. - rv = MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset); + bool handled = false; + rv = + MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset, &handled); + *aHandled |= handled; NS_ENSURE_SUCCESS(rv, rv); } if (brNode) { rv = htmlEditor->DeleteNode(brNode); + // XXX In other top level if/else-if blocks, the result of DeleteNode() + // is ignored. Why does only this result is respected? NS_ENSURE_SUCCESS(rv, rv); + *aHandled = true; } } return NS_OK; } - -/** - * Moves the content from aRightBlock starting from aRightOffset into - * aLeftBlock at aLeftOffset. Note that the "block" might merely be inline - * nodes between
s, or between blocks, etc. DTD containment rules are - * followed throughout. - */ nsresult HTMLEditRules::MoveBlock(Element& aLeftBlock, Element& aRightBlock, int32_t aLeftOffset, - int32_t aRightOffset) + int32_t aRightOffset, + bool* aHandled) { + MOZ_ASSERT(aHandled); + + *aHandled = false; + nsTArray> arrayOfNodes; // GetNodesFromPoint is the workhorse that figures out what we wnat to move. nsresult rv = GetNodesFromPoint(EditorDOMPoint(&aRightBlock, aRightOffset), @@ -2851,15 +2875,20 @@ HTMLEditRules::MoveBlock(Element& aLeftBlock, // get the node to act on if (IsBlockNode(arrayOfNodes[i])) { // For block nodes, move their contents only, then delete block. + bool handled = false; rv = MoveContents(*arrayOfNodes[i]->AsElement(), aLeftBlock, - &aLeftOffset); + &aLeftOffset, &handled); + *aHandled |= handled; NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_STATE(mHTMLEditor); rv = mHTMLEditor->DeleteNode(arrayOfNodes[i]); + *aHandled = true; } else { // Otherwise move the content as is, checking against the DTD. + bool handled = false; rv = MoveNodeSmart(*arrayOfNodes[i]->AsContent(), aLeftBlock, - &aLeftOffset); + &aLeftOffset, &handled); + *aHandled |= handled; } } @@ -2868,17 +2897,16 @@ HTMLEditRules::MoveBlock(Element& aLeftBlock, return NS_OK; } -/** - * This method is used to move node aNode to (aDestElement, aInOutDestOffset). - * DTD containment rules are followed throughout. aInOutDestOffset is updated - * to point _after_ inserted content. - */ nsresult HTMLEditRules::MoveNodeSmart(nsIContent& aNode, Element& aDestElement, - int32_t* aInOutDestOffset) + int32_t* aInOutDestOffset, + bool* aHandled) { MOZ_ASSERT(aInOutDestOffset); + MOZ_ASSERT(aHandled); + + *aHandled = false; NS_ENSURE_STATE(mHTMLEditor); RefPtr htmlEditor(mHTMLEditor); @@ -2892,37 +2920,43 @@ HTMLEditRules::MoveNodeSmart(nsIContent& aNode, if (*aInOutDestOffset != -1) { (*aInOutDestOffset)++; } + // XXX Should we check if the node is actually moved in this case? + *aHandled = true; } else { // If it can't, move its children (if any), and then delete it. if (aNode.IsElement()) { - nsresult rv = - MoveContents(*aNode.AsElement(), aDestElement, aInOutDestOffset); + bool handled = false; + nsresult rv = MoveContents(*aNode.AsElement(), aDestElement, + aInOutDestOffset, &handled); + *aHandled |= handled; NS_ENSURE_SUCCESS(rv, rv); } nsresult rv = htmlEditor->DeleteNode(&aNode); NS_ENSURE_SUCCESS(rv, rv); + *aHandled = true; } return NS_OK; } -/** - * Moves the _contents_ of aElement to (aDestElement, aInOutDestOffset). DTD - * containment rules are followed throughout. aInOutDestOffset is updated to - * point _after_ inserted content. - */ nsresult HTMLEditRules::MoveContents(Element& aElement, Element& aDestElement, - int32_t* aInOutDestOffset) + int32_t* aInOutDestOffset, + bool* aHandled) { MOZ_ASSERT(aInOutDestOffset); + MOZ_ASSERT(aHandled); + + *aHandled = false; NS_ENSURE_TRUE(&aElement != &aDestElement, NS_ERROR_ILLEGAL_VALUE); while (aElement.GetFirstChild()) { + bool handled = false; nsresult rv = MoveNodeSmart(*aElement.GetFirstChild(), aDestElement, - aInOutDestOffset); + aInOutDestOffset, &handled); + *aHandled |= handled; NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; diff --git a/editor/libeditor/HTMLEditRules.h b/editor/libeditor/HTMLEditRules.h index 40c5e2afd..6cdfa57cf 100644 --- a/editor/libeditor/HTMLEditRules.h +++ b/editor/libeditor/HTMLEditRules.h @@ -163,14 +163,70 @@ protected: nsresult InsertBRIfNeeded(Selection* aSelection); mozilla::EditorDOMPoint GetGoodSelPointForNode(nsINode& aNode, nsIEditor::EDirection aAction); - nsresult JoinBlocks(nsIContent& aLeftNode, nsIContent& aRightNode, - bool* aCanceled); + + /** + * TryToJoinBlocks() tries to join two block elements. The right element is + * always joined to the left element. If the elements are the same type and + * not nested within each other, JoinNodesSmart() is called (example, joining + * two list items together into one). If the elements are not the same type, + * or one is a descendant of the other, we instead destroy the right block + * placing its children into leftblock. DTD containment rules are followed + * throughout. + * + * @param aCanceled returns true if the operation should do nothing anymore + * even if this doesn't join the blocks. + * NOTE: When this returns an error, nobody should refer + * the result of this. + * @param aHandled returns true if this actually handles the request. + * Note that this may return true even if this does not + * join the block. E.g., if the blocks shouldn't be + * joined or it's impossible to join them but it's not + * unexpected case, this returns true with this. + * NOTE: When this returns an error, nobody should refer + * the result of this. + */ + nsresult TryToJoinBlocks(nsIContent& aLeftNode, nsIContent& aRightNode, + bool* aCanceled, bool* aHandled); + + /** + * MoveBlock() moves the content from aRightBlock starting from aRightOffset + * into aLeftBlock at aLeftOffset. Note that the "block" can be inline nodes + * between
s, or between blocks, etc. DTD containment rules are followed + * throughout. + * + * @param aHandled returns true if this actually joins the nodes. + * NOTE: When this returns an error, nobody should refer + * the result of this. + */ nsresult MoveBlock(Element& aLeftBlock, Element& aRightBlock, - int32_t aLeftOffset, int32_t aRightOffset); + int32_t aLeftOffset, int32_t aRightOffset, + bool* aHandled); + + /** + * MoveNodeSmart() moves aNode to (aDestElement, aInOutDestOffset). + * DTD containment rules are followed throughout. + * + * @param aOffset returns the point after inserted content. + * @param aHandled returns true if this actually moves the + * nodes. + * NOTE: When this returns an error, nobody + * should refer the result of this. + */ nsresult MoveNodeSmart(nsIContent& aNode, Element& aDestElement, - int32_t* aOffset); + int32_t* aInOutDestOffset, bool* aHandled); + + /** + * MoveContents() moves the contents of aElement to (aDestElement, + * aInOutDestOffset). DTD containment rules are followed throughout. + * + * @param aInOutDestOffset updated to point after inserted content. + * @param aHandled returns true if this actually moves the + * nodes. + * NOTE: When this returns an error, nobody + * should refer the result of this. + */ nsresult MoveContents(Element& aElement, Element& aDestElement, - int32_t* aOffset); + int32_t* aInOutDestOffset, bool* aHandled); nsresult DeleteNonTableElements(nsINode* aNode); nsresult WillMakeList(Selection* aSelection, const nsAString* aListType, -- cgit v1.2.3 From 016c55f4094d99cc4aaa2f8ac0f2c22adbf96a5b Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Fri, 22 May 2020 11:03:43 -0400 Subject: Bug 1316302 - Part 2: WillDeleteSelection() should retry to handle it when selection is collapsed and JoinBlocks() doesn't handle nor cancel the action When selection is collapsed and JoinBlocks() doesn't handle nor cancel the action, WillDeleteSelection() should move selection to the start/end of leftmost/rightmost editable leaf node and retry to handle the action again. For avoiding infinite loop, it checks if selected node is changed actually before calling itself again. Tag #1563 --- editor/libeditor/HTMLEditRules.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/editor/libeditor/HTMLEditRules.cpp b/editor/libeditor/HTMLEditRules.cpp index 13411b4f6..156bbc179 100644 --- a/editor/libeditor/HTMLEditRules.cpp +++ b/editor/libeditor/HTMLEditRules.cpp @@ -2199,12 +2199,24 @@ HTMLEditRules::WillDeleteSelection(Selection* aSelection, bool handled = false, canceled = false; rv = TryToJoinBlocks(*leftNode->AsContent(), *rightNode->AsContent(), &canceled, &handled); - // TODO: If it does nothing and previous or next node is a text node, - // we should modify it. - *aHandled = true; + *aHandled |= handled; *aCancel |= canceled; NS_ENSURE_SUCCESS(rv, rv); } + + // If TryToJoinBlocks() didn't handle it and it's not canceled, + // user may want to modify the start leaf node or the last leaf node + // of the block. + if (!*aHandled && !*aCancel && leafNode != startNode) { + int32_t offset = + aAction == nsIEditor::ePrevious ? + static_cast(leafNode->Length()) : 0; + aSelection->Collapse(leafNode, offset); + return WillDeleteSelection(aSelection, aAction, aStripWrappers, + aCancel, aHandled); + } + + // Otherwise, we must have deleted the selection as user expected. aSelection->Collapse(selPointNode, selPointOffset); return NS_OK; } -- cgit v1.2.3 From d4cd571021b3651f4f2c6f3a2846e48ad726bcae Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Fri, 22 May 2020 11:48:40 -0400 Subject: Bug 1316302 - Part 3: Create EditActionResult class for making the methods which return nsresult, handled and canceled with out params In a lot of places, edit action handlers and their helper methods return nsresult and aHandled and aCanceled with out params. However, the out params cause the code complicated since: * it's not unclear if the method will overwrite aHandled and aCanceled value. * callers need to create temporary variable event if some of them are not necessary. This patch rewrites the helper methods of HTMLEditRules::WillDeleteSelection() with it. Tag #1563 --- editor/libeditor/EditorUtils.h | 113 +++++++++++++++ editor/libeditor/HTMLEditRules.cpp | 274 +++++++++++++++++++++---------------- editor/libeditor/HTMLEditRules.h | 54 ++++---- 3 files changed, 292 insertions(+), 149 deletions(-) diff --git a/editor/libeditor/EditorUtils.h b/editor/libeditor/EditorUtils.h index 34286da8a..15ec0b62d 100644 --- a/editor/libeditor/EditorUtils.h +++ b/editor/libeditor/EditorUtils.h @@ -30,6 +30,119 @@ namespace dom { class Selection; } // namespace dom +/*************************************************************************** + * EditActionResult is useful to return multiple results of an editor + * action handler without out params. + * Note that when you return an anonymous instance from a method, you should + * use EditActionIgnored(), EditActionHandled() or EditActionCanceled() for + * easier to read. In other words, EditActionResult should be used when + * declaring return type of a method, being an argument or defined as a local + * variable. + */ +class MOZ_STACK_CLASS EditActionResult final +{ +public: + bool Succeeded() const { return NS_SUCCEEDED(mRv); } + bool Failed() const { return NS_FAILED(mRv); } + nsresult Rv() const { return mRv; } + bool Canceled() const { return mCanceled; } + bool Handled() const { return mHandled; } + + EditActionResult SetResult(nsresult aRv) + { + mRv = aRv; + return *this; + } + EditActionResult MarkAsCanceled() + { + mCanceled = true; + return *this; + } + EditActionResult MarkAsHandled() + { + mHandled = true; + return *this; + } + + explicit EditActionResult(nsresult aRv) + : mRv(aRv) + , mCanceled(false) + , mHandled(false) + { + } + + EditActionResult& operator|=(const EditActionResult& aOther) + { + mCanceled |= aOther.mCanceled; + mHandled |= aOther.mHandled; + // When both result are same, keep the result. + if (mRv == aOther.mRv) { + return *this; + } + // If one of the results is error, use NS_ERROR_FAILURE. + if (Failed() || aOther.Failed()) { + mRv = NS_ERROR_FAILURE; + } else { + // Otherwise, use generic success code, NS_OK. + mRv = NS_OK; + } + return *this; + } + +private: + nsresult mRv; + bool mCanceled; + bool mHandled; + + EditActionResult(nsresult aRv, bool aCanceled, bool aHandled) + : mRv(aRv) + , mCanceled(aCanceled) + , mHandled(aHandled) + { + } + + EditActionResult() + : mRv(NS_ERROR_NOT_INITIALIZED) + , mCanceled(false) + , mHandled(false) + { + } + + friend EditActionResult EditActionIgnored(nsresult aRv); + friend EditActionResult EditActionHandled(nsresult aRv); + friend EditActionResult EditActionCanceled(nsresult aRv); +}; + +/*************************************************************************** + * When an edit action handler (or its helper) does nothing, + * EditActionIgnored should be returned. + */ +inline EditActionResult +EditActionIgnored(nsresult aRv = NS_OK) +{ + return EditActionResult(aRv, false, false); +} + +/*************************************************************************** + * When an edit action handler (or its helper) handled and not canceled, + * EditActionHandled should be returned. + */ +inline EditActionResult +EditActionHandled(nsresult aRv = NS_OK) +{ + return EditActionResult(aRv, false, true); +} + +/*************************************************************************** + * When an edit action handler (or its helper) handled and canceled, + * EditActionHandled should be returned. + */ +inline EditActionResult +EditActionCanceled(nsresult aRv = NS_OK) +{ + return EditActionResult(aRv, true, true); +} + /*************************************************************************** * stack based helper class for batching a collection of txns inside a * placeholder txn. diff --git a/editor/libeditor/HTMLEditRules.cpp b/editor/libeditor/HTMLEditRules.cpp index 156bbc179..fb732f05a 100644 --- a/editor/libeditor/HTMLEditRules.cpp +++ b/editor/libeditor/HTMLEditRules.cpp @@ -2196,12 +2196,13 @@ HTMLEditRules::WillDeleteSelection(Selection* aSelection, address_of(selPointNode), &selPointOffset); NS_ENSURE_STATE(leftNode && leftNode->IsContent() && rightNode && rightNode->IsContent()); - bool handled = false, canceled = false; - rv = TryToJoinBlocks(*leftNode->AsContent(), *rightNode->AsContent(), - &canceled, &handled); - *aHandled |= handled; - *aCancel |= canceled; - NS_ENSURE_SUCCESS(rv, rv); + EditActionResult ret = + TryToJoinBlocks(*leftNode->AsContent(), *rightNode->AsContent()); + *aHandled |= ret.Handled(); + *aCancel |= ret.Canceled(); + if (NS_WARN_IF(ret.Failed())) { + return ret.Rv(); + } } // If TryToJoinBlocks() didn't handle it and it's not canceled, @@ -2263,15 +2264,16 @@ HTMLEditRules::WillDeleteSelection(Selection* aSelection, AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(selPointNode), &selPointOffset); NS_ENSURE_STATE(leftNode->IsContent() && rightNode->IsContent()); - bool handled = false, canceled = false; - rv = TryToJoinBlocks(*leftNode->AsContent(), *rightNode->AsContent(), - &canceled, &handled); + EditActionResult ret = + TryToJoinBlocks(*leftNode->AsContent(), *rightNode->AsContent()); // This should claim that trying to join the block means that // this handles the action because the caller shouldn't do anything // anymore in this case. *aHandled = true; - *aCancel |= canceled; - NS_ENSURE_SUCCESS(rv, rv); + *aCancel |= ret.Canceled(); + if (NS_WARN_IF(ret.Failed())) { + return ret.Rv(); + } } aSelection->Collapse(selPointNode, selPointOffset); return NS_OK; @@ -2442,11 +2444,12 @@ HTMLEditRules::WillDeleteSelection(Selection* aSelection, } if (join) { - bool handled = false, canceled = false; - rv = TryToJoinBlocks(*leftParent, *rightParent, &canceled, &handled); + EditActionResult ret = TryToJoinBlocks(*leftParent, *rightParent); MOZ_ASSERT(*aHandled); - *aCancel |= canceled; - NS_ENSURE_SUCCESS(rv, rv); + *aCancel |= ret.Canceled(); + if (NS_WARN_IF(ret.Failed())) { + return ret.Rv(); + } } } } @@ -2595,59 +2598,58 @@ HTMLEditRules::GetGoodSelPointForNode(nsINode& aNode, return ret; } -nsresult +EditActionResult HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, - nsIContent& aRightNode, - bool* aCanceled, - bool* aHandled) + nsIContent& aRightNode) { - MOZ_ASSERT(aCanceled); - MOZ_ASSERT(aHandled); - - *aCanceled = false; - *aHandled = false; + if (NS_WARN_IF(!mHTMLEditor)) { + return EditActionIgnored(NS_ERROR_UNEXPECTED); + } - NS_ENSURE_STATE(mHTMLEditor); RefPtr htmlEditor(mHTMLEditor); nsCOMPtr leftBlock = htmlEditor->GetBlock(aLeftNode); nsCOMPtr rightBlock = htmlEditor->GetBlock(aRightNode); // Sanity checks - NS_ENSURE_TRUE(leftBlock && rightBlock, NS_ERROR_NULL_POINTER); - NS_ENSURE_STATE(leftBlock != rightBlock); + if (NS_WARN_IF(!leftBlock) || NS_WARN_IF(!rightBlock)) { + return EditActionIgnored(NS_ERROR_NULL_POINTER); + } + if (NS_WARN_IF(leftBlock == rightBlock)) { + return EditActionIgnored(NS_ERROR_UNEXPECTED); + } if (HTMLEditUtils::IsTableElement(leftBlock) || HTMLEditUtils::IsTableElement(rightBlock)) { // Do not try to merge table elements - *aCanceled = true; - *aHandled = true; - return NS_OK; + return EditActionCanceled(); } // Make sure we don't try to move things into HR's, which look like blocks // but aren't containers if (leftBlock->IsHTMLElement(nsGkAtoms::hr)) { leftBlock = htmlEditor->GetBlockNodeParent(leftBlock); + if (NS_WARN_IF(!leftBlock)) { + return EditActionIgnored(NS_ERROR_UNEXPECTED); + } } if (rightBlock->IsHTMLElement(nsGkAtoms::hr)) { rightBlock = htmlEditor->GetBlockNodeParent(rightBlock); + if (NS_WARN_IF(!rightBlock)) { + return EditActionIgnored(NS_ERROR_UNEXPECTED); + } } - NS_ENSURE_STATE(leftBlock && rightBlock); // Bail if both blocks the same if (leftBlock == rightBlock) { - *aCanceled = true; - *aHandled = true; - return NS_OK; + return EditActionIgnored(); } // Joining a list item to its parent is a NOP. if (HTMLEditUtils::IsList(leftBlock) && HTMLEditUtils::IsListItem(rightBlock) && rightBlock->GetParentNode() == leftBlock) { - *aHandled = true; - return NS_OK; + return EditActionHandled(); } // Special rule here: if we are trying to join list items, and they are in @@ -2681,6 +2683,7 @@ HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, // offset below is where you find yourself in rightBlock when you traverse // upwards from leftBlock + EditActionResult ret(NS_OK); if (EditorUtils::IsDescendantOf(leftBlock, rightBlock, &rightOffset)) { // Tricky case. Left block is inside right block. Do ws adjustment. This // just destroys non-visible ws at boundaries we will be joining. @@ -2688,7 +2691,9 @@ HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, nsresult rv = WSRunObject::ScrubBlockBoundary(htmlEditor, WSRunObject::kBlockEnd, leftBlock); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return EditActionIgnored(rv); + } { // We can't just track rightBlock because it's an Element. @@ -2698,11 +2703,16 @@ HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, rv = WSRunObject::ScrubBlockBoundary(htmlEditor, WSRunObject::kAfterBlock, rightBlock, rightOffset); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return EditActionIgnored(rv); + } + if (trackingRightBlock->IsElement()) { rightBlock = trackingRightBlock->AsElement(); } else { - NS_ENSURE_STATE(trackingRightBlock->GetParentElement()); + if (NS_WARN_IF(!trackingRightBlock->GetParentElement())) { + return EditActionIgnored(NS_ERROR_UNEXPECTED); + } rightBlock = trackingRightBlock->GetParentElement(); } } @@ -2715,17 +2725,22 @@ HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, for (nsCOMPtr child = rightList->GetChildAt(offset); child; child = rightList->GetChildAt(rightOffset)) { rv = htmlEditor->MoveNode(child, leftList, -1); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return EditActionIgnored(rv); + } } // XXX Should this set to true only when above for loop moves the node? - *aHandled = true; + ret.MarkAsHandled(); } else { - bool handled = false; - MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset, &handled); - *aHandled |= handled; + // XXX Why do we ignore the result of MoveBlock()? + EditActionResult retMoveBlock = + MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset); + if (retMoveBlock.Handled()) { + ret.MarkAsHandled(); + } } if (brNode && NS_SUCCEEDED(htmlEditor->DeleteNode(brNode))) { - *aHandled = true; + ret.MarkAsHandled(); } // Offset below is where you find yourself in leftBlock when you traverse // upwards from rightBlock @@ -2735,7 +2750,10 @@ HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, nsresult rv = WSRunObject::ScrubBlockBoundary(htmlEditor, WSRunObject::kBlockStart, rightBlock); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return EditActionIgnored(rv); + } + { // We can't just track leftBlock because it's an Element, so track // something else. @@ -2745,11 +2763,16 @@ HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, rv = WSRunObject::ScrubBlockBoundary(htmlEditor, WSRunObject::kBeforeBlock, leftBlock, leftOffset); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return EditActionIgnored(rv); + } + if (trackingLeftBlock->IsElement()) { leftBlock = trackingLeftBlock->AsElement(); } else { - NS_ENSURE_STATE(trackingLeftBlock->GetParentElement()); + if (NS_WARN_IF(!trackingLeftBlock->GetParentElement())) { + return EditActionIgnored(NS_ERROR_UNEXPECTED); + } leftBlock = trackingLeftBlock->GetParentElement(); } } @@ -2757,9 +2780,12 @@ HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, nsCOMPtr brNode = CheckForInvisibleBR(*leftBlock, BRLocation::beforeBlock, leftOffset); if (mergeLists) { - bool handled = false; - MoveContents(*rightList, *leftList, &leftOffset, &handled); - *aHandled |= handled; + // XXX Why do we ignore the result of MoveContents()? + EditActionResult retMoveContents = + MoveContents(*rightList, *leftList, &leftOffset); + if (retMoveContents.Handled()) { + ret.MarkAsHandled(); + } } else { // Left block is a parent of right block, and the parent of the previous // visible content. Right block is a child and contains the contents we @@ -2804,7 +2830,9 @@ HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, &previousContentOffset, nullptr, nullptr, nullptr, getter_AddRefs(splittedPreviousContent)); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return EditActionIgnored(rv); + } if (splittedPreviousContent) { previousContentParent = splittedPreviousContent->GetParentNode(); @@ -2813,16 +2841,18 @@ HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, } } - NS_ENSURE_TRUE(previousContentParent, NS_ERROR_NULL_POINTER); + if (NS_WARN_IF(!previousContentParent)) { + return EditActionIgnored(NS_ERROR_NULL_POINTER); + } - bool handled = false; - rv = MoveBlock(*previousContentParent->AsElement(), *rightBlock, - previousContentOffset, rightOffset, &handled); - *aHandled |= handled; - NS_ENSURE_SUCCESS(rv, rv); + ret |= MoveBlock(*previousContentParent->AsElement(), *rightBlock, + previousContentOffset, rightOffset); + if (NS_WARN_IF(ret.Failed())) { + return ret; + } } if (brNode && NS_SUCCEEDED(htmlEditor->DeleteNode(brNode))) { - *aHandled = true; + ret.MarkAsHandled(); } } else { // Normal case. Blocks are siblings, or at least close enough. An example @@ -2833,7 +2863,9 @@ HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, // Adjust whitespace at block boundaries nsresult rv = WSRunObject::PrepareToJoinBlocks(htmlEditor, leftBlock, rightBlock); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return EditActionIgnored(rv); + } // Do br adjustment. nsCOMPtr brNode = CheckForInvisibleBR(*leftBlock, BRLocation::blockEnd); @@ -2846,81 +2878,83 @@ HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, ConvertListType(rightBlock, getter_AddRefs(newBlock), existingList, nsGkAtoms::li); } - *aHandled = true; + ret.MarkAsHandled(); } else { // Nodes are dissimilar types. - bool handled = false; - rv = - MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset, &handled); - *aHandled |= handled; - NS_ENSURE_SUCCESS(rv, rv); + ret |= MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset); + if (NS_WARN_IF(ret.Failed())) { + return ret; + } } if (brNode) { rv = htmlEditor->DeleteNode(brNode); // XXX In other top level if/else-if blocks, the result of DeleteNode() // is ignored. Why does only this result is respected? - NS_ENSURE_SUCCESS(rv, rv); - *aHandled = true; + if (NS_WARN_IF(NS_FAILED(rv))) { + return ret.SetResult(rv); + } + ret.MarkAsHandled(); } } - return NS_OK; + return ret; } -nsresult +EditActionResult HTMLEditRules::MoveBlock(Element& aLeftBlock, Element& aRightBlock, int32_t aLeftOffset, - int32_t aRightOffset, - bool* aHandled) + int32_t aRightOffset) { - MOZ_ASSERT(aHandled); - - *aHandled = false; - nsTArray> arrayOfNodes; // GetNodesFromPoint is the workhorse that figures out what we wnat to move. nsresult rv = GetNodesFromPoint(EditorDOMPoint(&aRightBlock, aRightOffset), EditAction::makeList, arrayOfNodes, TouchContent::yes); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return EditActionIgnored(rv); + } + + EditActionResult ret(NS_OK); for (uint32_t i = 0; i < arrayOfNodes.Length(); i++) { // get the node to act on if (IsBlockNode(arrayOfNodes[i])) { // For block nodes, move their contents only, then delete block. - bool handled = false; - rv = MoveContents(*arrayOfNodes[i]->AsElement(), aLeftBlock, - &aLeftOffset, &handled); - *aHandled |= handled; - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_STATE(mHTMLEditor); + ret |= + MoveContents(*arrayOfNodes[i]->AsElement(), aLeftBlock, &aLeftOffset); + if (NS_WARN_IF(ret.Failed())) { + return ret; + } + if (NS_WARN_IF(!mHTMLEditor)) { + return ret.SetResult(NS_ERROR_UNEXPECTED); + } rv = mHTMLEditor->DeleteNode(arrayOfNodes[i]); - *aHandled = true; + ret.MarkAsHandled(); } else { // Otherwise move the content as is, checking against the DTD. - bool handled = false; - rv = MoveNodeSmart(*arrayOfNodes[i]->AsContent(), aLeftBlock, - &aLeftOffset, &handled); - *aHandled |= handled; + ret |= + MoveNodeSmart(*arrayOfNodes[i]->AsContent(), aLeftBlock, &aLeftOffset); } } // XXX We're only checking return value of the last iteration - NS_ENSURE_SUCCESS(rv, rv); - return NS_OK; + if (NS_WARN_IF(ret.Failed())) { + return ret; + } + + return ret; } -nsresult +EditActionResult HTMLEditRules::MoveNodeSmart(nsIContent& aNode, Element& aDestElement, - int32_t* aInOutDestOffset, - bool* aHandled) + int32_t* aInOutDestOffset) { MOZ_ASSERT(aInOutDestOffset); - MOZ_ASSERT(aHandled); - *aHandled = false; + if (NS_WARN_IF(!mHTMLEditor)) { + return EditActionIgnored(NS_ERROR_UNEXPECTED); + } - NS_ENSURE_STATE(mHTMLEditor); RefPtr htmlEditor(mHTMLEditor); // Check if this node can go into the destination node @@ -2928,50 +2962,52 @@ HTMLEditRules::MoveNodeSmart(nsIContent& aNode, // If it can, move it there nsresult rv = htmlEditor->MoveNode(&aNode, &aDestElement, *aInOutDestOffset); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return EditActionIgnored(rv); + } if (*aInOutDestOffset != -1) { (*aInOutDestOffset)++; } // XXX Should we check if the node is actually moved in this case? - *aHandled = true; + return EditActionHandled(); } else { // If it can't, move its children (if any), and then delete it. + EditActionResult ret(NS_OK); if (aNode.IsElement()) { - bool handled = false; - nsresult rv = MoveContents(*aNode.AsElement(), aDestElement, - aInOutDestOffset, &handled); - *aHandled |= handled; - NS_ENSURE_SUCCESS(rv, rv); + ret = MoveContents(*aNode.AsElement(), aDestElement, aInOutDestOffset); + if (NS_WARN_IF(ret.Failed())) { + return ret; + } } nsresult rv = htmlEditor->DeleteNode(&aNode); - NS_ENSURE_SUCCESS(rv, rv); - *aHandled = true; + if (NS_WARN_IF(NS_FAILED(rv))) { + return ret.SetResult(rv); + } + return ret.MarkAsHandled(); } - return NS_OK; } -nsresult +EditActionResult HTMLEditRules::MoveContents(Element& aElement, Element& aDestElement, - int32_t* aInOutDestOffset, - bool* aHandled) + int32_t* aInOutDestOffset) { MOZ_ASSERT(aInOutDestOffset); - MOZ_ASSERT(aHandled); - *aHandled = false; - - NS_ENSURE_TRUE(&aElement != &aDestElement, NS_ERROR_ILLEGAL_VALUE); + if (NS_WARN_IF(&aElement == &aDestElement)) { + return EditActionIgnored(NS_ERROR_ILLEGAL_VALUE); + } + EditActionResult ret(NS_OK); while (aElement.GetFirstChild()) { - bool handled = false; - nsresult rv = MoveNodeSmart(*aElement.GetFirstChild(), aDestElement, - aInOutDestOffset, &handled); - *aHandled |= handled; - NS_ENSURE_SUCCESS(rv, rv); + ret |= + MoveNodeSmart(*aElement.GetFirstChild(), aDestElement, aInOutDestOffset); + if (NS_WARN_IF(ret.Failed())) { + return ret; + } } - return NS_OK; + return ret; } diff --git a/editor/libeditor/HTMLEditRules.h b/editor/libeditor/HTMLEditRules.h index 6cdfa57cf..5525fdf24 100644 --- a/editor/libeditor/HTMLEditRules.h +++ b/editor/libeditor/HTMLEditRules.h @@ -28,6 +28,7 @@ class nsRange; namespace mozilla { +class EditActionResult; class HTMLEditor; class RulesInfo; class TextEditor; @@ -173,20 +174,16 @@ protected: * placing its children into leftblock. DTD containment rules are followed * throughout. * - * @param aCanceled returns true if the operation should do nothing anymore - * even if this doesn't join the blocks. - * NOTE: When this returns an error, nobody should refer - * the result of this. - * @param aHandled returns true if this actually handles the request. - * Note that this may return true even if this does not - * join the block. E.g., if the blocks shouldn't be - * joined or it's impossible to join them but it's not + * @return Sets canceled to true if the operation should do + * nothing anymore even if this doesn't join the blocks. + * Sets handled to true if this actually handles the + * request. Note that this may set it to true even if this + * does not join the block. E.g., if the blocks shouldn't + * be joined or it's impossible to join them but it's not * unexpected case, this returns true with this. - * NOTE: When this returns an error, nobody should refer - * the result of this. */ - nsresult TryToJoinBlocks(nsIContent& aLeftNode, nsIContent& aRightNode, - bool* aCanceled, bool* aHandled); + EditActionResult TryToJoinBlocks(nsIContent& aLeftNode, + nsIContent& aRightNode); /** * MoveBlock() moves the content from aRightBlock starting from aRightOffset @@ -194,39 +191,36 @@ protected: * between
s, or between blocks, etc. DTD containment rules are followed * throughout. * - * @param aHandled returns true if this actually joins the nodes. - * NOTE: When this returns an error, nobody should refer - * the result of this. + * @return Sets handled to true if this actually joins the nodes. + * canceled is always false. */ - nsresult MoveBlock(Element& aLeftBlock, Element& aRightBlock, - int32_t aLeftOffset, int32_t aRightOffset, - bool* aHandled); + EditActionResult MoveBlock(Element& aLeftBlock, Element& aRightBlock, + int32_t aLeftOffset, int32_t aRightOffset); /** * MoveNodeSmart() moves aNode to (aDestElement, aInOutDestOffset). * DTD containment rules are followed throughout. * * @param aOffset returns the point after inserted content. - * @param aHandled returns true if this actually moves the - * nodes. - * NOTE: When this returns an error, nobody - * should refer the result of this. + * @return Sets true to handled if this actually moves + * the nodes. + * canceled is always false. */ - nsresult MoveNodeSmart(nsIContent& aNode, Element& aDestElement, - int32_t* aInOutDestOffset, bool* aHandled); + EditActionResult MoveNodeSmart(nsIContent& aNode, Element& aDestElement, + int32_t* aInOutDestOffset); /** * MoveContents() moves the contents of aElement to (aDestElement, * aInOutDestOffset). DTD containment rules are followed throughout. * * @param aInOutDestOffset updated to point after inserted content. - * @param aHandled returns true if this actually moves the - * nodes. - * NOTE: When this returns an error, nobody - * should refer the result of this. + * @return Sets true to handled if this actually moves + * the nodes. + * canceled is always false. */ - nsresult MoveContents(Element& aElement, Element& aDestElement, - int32_t* aInOutDestOffset, bool* aHandled); + EditActionResult MoveContents(Element& aElement, Element& aDestElement, + int32_t* aInOutDestOffset); + nsresult DeleteNonTableElements(nsINode* aNode); nsresult WillMakeList(Selection* aSelection, const nsAString* aListType, -- cgit v1.2.3 From d0126b96cd6527e6dc89530b333fb0c196aba30d Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Fri, 22 May 2020 11:55:44 -0400 Subject: Bug 1316302 - Part 4: Refine HTMLEditRules::TryToJoinBlocks() and HTMLEditRules::MoveNodeSmart() with early return style for making scope of EditActionResult variable smaller For now, let's make the scope of EditActionResult variable in them smaller without big change. Tag #1563 --- editor/libeditor/HTMLEditRules.cpp | 112 +++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/editor/libeditor/HTMLEditRules.cpp b/editor/libeditor/HTMLEditRules.cpp index fb732f05a..d3cbb8775 100644 --- a/editor/libeditor/HTMLEditRules.cpp +++ b/editor/libeditor/HTMLEditRules.cpp @@ -2683,7 +2683,6 @@ HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, // offset below is where you find yourself in rightBlock when you traverse // upwards from leftBlock - EditActionResult ret(NS_OK); if (EditorUtils::IsDescendantOf(leftBlock, rightBlock, &rightOffset)) { // Tricky case. Left block is inside right block. Do ws adjustment. This // just destroys non-visible ws at boundaries we will be joining. @@ -2719,6 +2718,7 @@ HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, // Do br adjustment. nsCOMPtr brNode = CheckForInvisibleBR(*leftBlock, BRLocation::blockEnd); + EditActionResult ret(NS_OK); if (mergeLists) { // The idea here is to take all children in rightList that are past // offset, and pull them into leftlist. @@ -2742,9 +2742,12 @@ HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, if (brNode && NS_SUCCEEDED(htmlEditor->DeleteNode(brNode))) { ret.MarkAsHandled(); } + return ret; + } + // Offset below is where you find yourself in leftBlock when you traverse // upwards from rightBlock - } else if (EditorUtils::IsDescendantOf(rightBlock, leftBlock, &leftOffset)) { + if (EditorUtils::IsDescendantOf(rightBlock, leftBlock, &leftOffset)) { // Tricky case. Right block is inside left block. Do ws adjustment. This // just destroys non-visible ws at boundaries we will be joining. nsresult rv = WSRunObject::ScrubBlockBoundary(htmlEditor, @@ -2779,6 +2782,7 @@ HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, // Do br adjustment. nsCOMPtr brNode = CheckForInvisibleBR(*leftBlock, BRLocation::beforeBlock, leftOffset); + EditActionResult ret(NS_OK); if (mergeLists) { // XXX Why do we ignore the result of MoveContents()? EditActionResult retMoveContents = @@ -2854,47 +2858,49 @@ HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode, if (brNode && NS_SUCCEEDED(htmlEditor->DeleteNode(brNode))) { ret.MarkAsHandled(); } - } else { - // Normal case. Blocks are siblings, or at least close enough. An example - // of the latter is

paragraph

  • one
  • two
  • three
. The - // first li and the p are not true siblings, but we still want to join them - // if you backspace from li into p. + return ret; + } - // Adjust whitespace at block boundaries - nsresult rv = - WSRunObject::PrepareToJoinBlocks(htmlEditor, leftBlock, rightBlock); - if (NS_WARN_IF(NS_FAILED(rv))) { - return EditActionIgnored(rv); - } - // Do br adjustment. - nsCOMPtr brNode = - CheckForInvisibleBR(*leftBlock, BRLocation::blockEnd); - if (mergeLists || leftBlock->NodeInfo()->NameAtom() == - rightBlock->NodeInfo()->NameAtom()) { - // Nodes are same type. merge them. - EditorDOMPoint pt = JoinNodesSmart(*leftBlock, *rightBlock); - if (pt.node && mergeLists) { - nsCOMPtr newBlock; - ConvertListType(rightBlock, getter_AddRefs(newBlock), - existingList, nsGkAtoms::li); - } - ret.MarkAsHandled(); - } else { - // Nodes are dissimilar types. - ret |= MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset); - if (NS_WARN_IF(ret.Failed())) { - return ret; - } + // Normal case. Blocks are siblings, or at least close enough. An example + // of the latter is

paragraph

  • one
  • two
  • three
. The + // first li and the p are not true siblings, but we still want to join them + // if you backspace from li into p. + + // Adjust whitespace at block boundaries + nsresult rv = + WSRunObject::PrepareToJoinBlocks(htmlEditor, leftBlock, rightBlock); + if (NS_WARN_IF(NS_FAILED(rv))) { + return EditActionIgnored(rv); + } + // Do br adjustment. + nsCOMPtr brNode = + CheckForInvisibleBR(*leftBlock, BRLocation::blockEnd); + EditActionResult ret(NS_OK); + if (mergeLists || leftBlock->NodeInfo()->NameAtom() == + rightBlock->NodeInfo()->NameAtom()) { + // Nodes are same type. merge them. + EditorDOMPoint pt = JoinNodesSmart(*leftBlock, *rightBlock); + if (pt.node && mergeLists) { + nsCOMPtr newBlock; + ConvertListType(rightBlock, getter_AddRefs(newBlock), + existingList, nsGkAtoms::li); + } + ret.MarkAsHandled(); + } else { + // Nodes are dissimilar types. + ret |= MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset); + if (NS_WARN_IF(ret.Failed())) { + return ret; } - if (brNode) { - rv = htmlEditor->DeleteNode(brNode); - // XXX In other top level if/else-if blocks, the result of DeleteNode() - // is ignored. Why does only this result is respected? - if (NS_WARN_IF(NS_FAILED(rv))) { - return ret.SetResult(rv); - } - ret.MarkAsHandled(); + } + if (brNode) { + rv = htmlEditor->DeleteNode(brNode); + // XXX In other top level if blocks, the result of DeleteNode() + // is ignored. Why does only this result is respected? + if (NS_WARN_IF(NS_FAILED(rv))) { + return ret.SetResult(rv); } + ret.MarkAsHandled(); } return ret; } @@ -2970,22 +2976,22 @@ HTMLEditRules::MoveNodeSmart(nsIContent& aNode, } // XXX Should we check if the node is actually moved in this case? return EditActionHandled(); - } else { - // If it can't, move its children (if any), and then delete it. - EditActionResult ret(NS_OK); - if (aNode.IsElement()) { - ret = MoveContents(*aNode.AsElement(), aDestElement, aInOutDestOffset); - if (NS_WARN_IF(ret.Failed())) { - return ret; - } - } + } - nsresult rv = htmlEditor->DeleteNode(&aNode); - if (NS_WARN_IF(NS_FAILED(rv))) { - return ret.SetResult(rv); + // If it can't, move its children (if any), and then delete it. + EditActionResult ret(NS_OK); + if (aNode.IsElement()) { + ret = MoveContents(*aNode.AsElement(), aDestElement, aInOutDestOffset); + if (NS_WARN_IF(ret.Failed())) { + return ret; } - return ret.MarkAsHandled(); } + + nsresult rv = htmlEditor->DeleteNode(&aNode); + if (NS_WARN_IF(NS_FAILED(rv))) { + return ret.SetResult(rv); + } + return ret.MarkAsHandled(); } EditActionResult -- cgit v1.2.3 From 0633844f46858135ee62d396829c6292492ca117 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Tue, 30 Jun 2020 11:32:07 +0000 Subject: Issue #1603 - Part 1: Reorganize ScriptLoader/ScriptElement - Moves scripting parts of DOM into 'dom/script' - Renames nsScript{Loader/Element} to Script{Loader/Element} - Adjusts all callers --- dom/base/ImportManager.cpp | 8 +- dom/base/ImportManager.h | 8 +- dom/base/Location.cpp | 2 +- dom/base/ScriptSettings.cpp | 839 ------- dom/base/ScriptSettings.h | 465 ---- dom/base/moz.build | 7 - dom/base/nsContentPermissionHelper.cpp | 3 +- dom/base/nsContentSink.cpp | 2 +- dom/base/nsContentSink.h | 7 +- dom/base/nsDocument.cpp | 4 +- dom/base/nsDocument.h | 6 +- dom/base/nsFrameMessageManager.cpp | 8 +- dom/base/nsGlobalWindow.cpp | 2 +- dom/base/nsIDocument.h | 4 +- dom/base/nsIScriptElement.h | 329 --- dom/base/nsIScriptLoaderObserver.idl | 47 - dom/base/nsInProcessTabChildGlobal.cpp | 2 +- dom/base/nsJSEnvironment.cpp | 2 +- dom/base/nsScriptElement.cpp | 150 -- dom/base/nsScriptElement.h | 52 - dom/base/nsScriptLoader.cpp | 3061 ------------------------- dom/base/nsScriptLoader.h | 717 ------ dom/console/Console.cpp | 2 +- dom/events/DOMEventTargetHelper.cpp | 2 +- dom/html/HTMLScriptElement.cpp | 2 +- dom/html/HTMLScriptElement.h | 7 +- dom/html/nsGenericHTMLElement.cpp | 2 +- dom/html/nsHTMLContentSink.cpp | 2 +- dom/messagechannel/MessagePort.cpp | 2 +- dom/moz.build | 1 + dom/script/ScriptElement.cpp | 150 ++ dom/script/ScriptElement.h | 58 + dom/script/ScriptLoader.cpp | 3063 ++++++++++++++++++++++++++ dom/script/ScriptLoader.h | 719 ++++++ dom/script/ScriptSettings.cpp | 839 +++++++ dom/script/ScriptSettings.h | 465 ++++ dom/script/moz.build | 33 + dom/script/nsIScriptElement.h | 329 +++ dom/script/nsIScriptLoaderObserver.idl | 47 + dom/svg/SVGScriptElement.cpp | 4 +- dom/svg/SVGScriptElement.h | 6 +- dom/workers/ScriptLoader.cpp | 20 +- dom/workers/ServiceWorkerManager.cpp | 2 +- dom/workers/ServiceWorkerScriptCache.cpp | 14 +- dom/worklet/Worklet.cpp | 8 +- dom/xml/nsXMLContentSink.cpp | 2 +- dom/xml/nsXMLFragmentContentSink.cpp | 2 +- dom/xslt/xslt/txMozillaXMLOutput.cpp | 8 +- dom/xul/XULDocument.cpp | 8 +- dom/xul/XULDocument.h | 2 +- dom/xul/nsXULContentSink.cpp | 2 +- js/xpconnect/loader/mozJSSubScriptLoader.cpp | 12 +- js/xpconnect/src/XPCJSContext.cpp | 6 +- parser/html/nsHtml5DocumentBuilder.cpp | 2 +- parser/html/nsHtml5OplessBuilder.cpp | 2 +- parser/html/nsHtml5TreeOpExecutor.cpp | 2 +- parser/html/nsParserUtils.cpp | 4 +- parser/htmlparser/nsParser.cpp | 2 +- 58 files changed, 5798 insertions(+), 5758 deletions(-) delete mode 100644 dom/base/ScriptSettings.cpp delete mode 100644 dom/base/ScriptSettings.h delete mode 100644 dom/base/nsIScriptElement.h delete mode 100644 dom/base/nsIScriptLoaderObserver.idl delete mode 100644 dom/base/nsScriptElement.cpp delete mode 100644 dom/base/nsScriptElement.h delete mode 100644 dom/base/nsScriptLoader.cpp delete mode 100644 dom/base/nsScriptLoader.h create mode 100644 dom/script/ScriptElement.cpp create mode 100644 dom/script/ScriptElement.h create mode 100644 dom/script/ScriptLoader.cpp create mode 100644 dom/script/ScriptLoader.h create mode 100644 dom/script/ScriptSettings.cpp create mode 100644 dom/script/ScriptSettings.h create mode 100644 dom/script/moz.build create mode 100644 dom/script/nsIScriptElement.h create mode 100644 dom/script/nsIScriptLoaderObserver.idl diff --git a/dom/base/ImportManager.cpp b/dom/base/ImportManager.cpp index d0e514b59..1f4d376b3 100644 --- a/dom/base/ImportManager.cpp +++ b/dom/base/ImportManager.cpp @@ -6,6 +6,7 @@ #include "ImportManager.h" +#include "mozilla/dom/ScriptLoader.h" #include "mozilla/EventListenerManager.h" #include "HTMLLinkElement.h" #include "nsContentPolicyUtils.h" @@ -18,7 +19,6 @@ #include "nsIDOMEvent.h" #include "nsIPrincipal.h" #include "nsIScriptObjectPrincipal.h" -#include "nsScriptLoader.h" #include "nsNetUtil.h" //----------------------------------------------------------------------------- @@ -156,7 +156,7 @@ ImportLoader::Updater::UpdateMainReferrer(uint32_t aNewIdx) // Our nearest predecessor has changed. So let's add the ScriptLoader to the // new one if there is any. And remove it from the old one. RefPtr manager = mLoader->Manager(); - nsScriptLoader* loader = mLoader->mDocument->ScriptLoader(); + ScriptLoader* loader = mLoader->mDocument->ScriptLoader(); ImportLoader*& pred = mLoader->mBlockingPredecessor; ImportLoader* newPred = manager->GetNearestPredecessor(newMainReferrer); if (pred) { @@ -339,7 +339,7 @@ ImportLoader::DispatchEventIfFinished(nsINode* aNode) } void -ImportLoader::AddBlockedScriptLoader(nsScriptLoader* aScriptLoader) +ImportLoader::AddBlockedScriptLoader(ScriptLoader* aScriptLoader) { if (mBlockedScriptLoaders.Contains(aScriptLoader)) { return; @@ -352,7 +352,7 @@ ImportLoader::AddBlockedScriptLoader(nsScriptLoader* aScriptLoader) } bool -ImportLoader::RemoveBlockedScriptLoader(nsScriptLoader* aScriptLoader) +ImportLoader::RemoveBlockedScriptLoader(ScriptLoader* aScriptLoader) { aScriptLoader->RemoveParserBlockingScriptExecutionBlocker(); return mBlockedScriptLoaders.RemoveElement(aScriptLoader); diff --git a/dom/base/ImportManager.h b/dom/base/ImportManager.h index 258d4691c..ccc00125a 100644 --- a/dom/base/ImportManager.h +++ b/dom/base/ImportManager.h @@ -45,8 +45,8 @@ #include "nsIStreamListener.h" #include "nsIWeakReferenceUtils.h" #include "nsRefPtrHashtable.h" -#include "nsScriptLoader.h" #include "nsURIHashKey.h" +#include "mozilla/dom/ScriptLoader.h" class nsIDocument; class nsIPrincipal; @@ -184,8 +184,8 @@ public: // and wait for that to run its scripts. We keep track of all the // ScriptRunners that are waiting for this import. NOTE: updating // the main referrer might change this list. - void AddBlockedScriptLoader(nsScriptLoader* aScriptLoader); - bool RemoveBlockedScriptLoader(nsScriptLoader* aScriptLoader); + void AddBlockedScriptLoader(ScriptLoader* aScriptLoader); + bool RemoveBlockedScriptLoader(ScriptLoader* aScriptLoader); void SetBlockingPredecessor(ImportLoader* aLoader); private: @@ -230,7 +230,7 @@ private: // List of pending ScriptLoaders that are waiting for this import // to finish. - nsTArray> mBlockedScriptLoaders; + nsTArray> mBlockedScriptLoaders; // There is always exactly one referrer link that is flagged as // the main referrer the primary link. This is the one that is diff --git a/dom/base/Location.cpp b/dom/base/Location.cpp index 1483c32f9..308e9a4ff 100644 --- a/dom/base/Location.cpp +++ b/dom/base/Location.cpp @@ -32,9 +32,9 @@ #include "mozilla/Likely.h" #include "nsCycleCollectionParticipant.h" #include "nsNullPrincipal.h" -#include "ScriptSettings.h" #include "mozilla/Unused.h" #include "mozilla/dom/LocationBinding.h" +#include "mozilla/dom/ScriptSettings.h" namespace mozilla { namespace dom { diff --git a/dom/base/ScriptSettings.cpp b/dom/base/ScriptSettings.cpp deleted file mode 100644 index 92ab221c9..000000000 --- a/dom/base/ScriptSettings.cpp +++ /dev/null @@ -1,839 +0,0 @@ -/* -*- 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/ScriptSettings.h" -#include "mozilla/ThreadLocal.h" -#include "mozilla/Assertions.h" -#include "mozilla/CycleCollectedJSContext.h" - -#include "jsapi.h" -#include "xpcpublic.h" -#include "nsIGlobalObject.h" -#include "nsIDocShell.h" -#include "nsIScriptGlobalObject.h" -#include "nsIScriptContext.h" -#include "nsContentUtils.h" -#include "nsGlobalWindow.h" -#include "nsPIDOMWindow.h" -#include "nsTArray.h" -#include "nsJSUtils.h" -#include "nsDOMJSUtils.h" -#include "WorkerPrivate.h" - -namespace mozilla { -namespace dom { - -static MOZ_THREAD_LOCAL(ScriptSettingsStackEntry*) sScriptSettingsTLS; -static bool sScriptSettingsTLSInitialized; - -class ScriptSettingsStack { -public: - static ScriptSettingsStackEntry* Top() { - return sScriptSettingsTLS.get(); - } - - static void Push(ScriptSettingsStackEntry *aEntry) { - MOZ_ASSERT(!aEntry->mOlder); - // Whenever JSAPI use is disabled, the next stack entry pushed must - // not be an AutoIncumbentScript. - MOZ_ASSERT_IF(!Top() || Top()->NoJSAPI(), - !aEntry->IsIncumbentScript()); - // Whenever the top entry is not an incumbent canidate, the next stack entry - // pushed must not be an AutoIncumbentScript. - MOZ_ASSERT_IF(Top() && !Top()->IsIncumbentCandidate(), - !aEntry->IsIncumbentScript()); - - aEntry->mOlder = Top(); - sScriptSettingsTLS.set(aEntry); - } - - static void Pop(ScriptSettingsStackEntry *aEntry) { - MOZ_ASSERT(aEntry == Top()); - sScriptSettingsTLS.set(aEntry->mOlder); - } - - static nsIGlobalObject* IncumbentGlobal() { - ScriptSettingsStackEntry *entry = Top(); - while (entry) { - if (entry->IsIncumbentCandidate()) { - return entry->mGlobalObject; - } - entry = entry->mOlder; - } - return nullptr; - } - - static ScriptSettingsStackEntry* EntryPoint() { - ScriptSettingsStackEntry *entry = Top(); - while (entry) { - if (entry->IsEntryCandidate()) { - return entry; - } - entry = entry->mOlder; - } - return nullptr; - } - - static nsIGlobalObject* EntryGlobal() { - ScriptSettingsStackEntry *entry = EntryPoint(); - if (!entry) { - return nullptr; - } - return entry->mGlobalObject; - } - -#ifdef DEBUG - static ScriptSettingsStackEntry* TopNonIncumbentScript() { - ScriptSettingsStackEntry *entry = Top(); - while (entry) { - if (!entry->IsIncumbentScript()) { - return entry; - } - entry = entry->mOlder; - } - return nullptr; - } -#endif // DEBUG - -}; - -static unsigned long gRunToCompletionListeners = 0; - -void -UseEntryScriptProfiling() -{ - MOZ_ASSERT(NS_IsMainThread()); - ++gRunToCompletionListeners; -} - -void -UnuseEntryScriptProfiling() -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(gRunToCompletionListeners > 0); - --gRunToCompletionListeners; -} - -void -InitScriptSettings() -{ - bool success = sScriptSettingsTLS.init(); - if (!success) { - MOZ_CRASH(); - } - - sScriptSettingsTLS.set(nullptr); - sScriptSettingsTLSInitialized = true; -} - -void -DestroyScriptSettings() -{ - MOZ_ASSERT(sScriptSettingsTLS.get() == nullptr); -} - -bool -ScriptSettingsInitialized() -{ - return sScriptSettingsTLSInitialized; -} - -ScriptSettingsStackEntry::ScriptSettingsStackEntry(nsIGlobalObject *aGlobal, - Type aType) - : mGlobalObject(aGlobal) - , mType(aType) - , mOlder(nullptr) -{ - MOZ_ASSERT_IF(IsIncumbentCandidate() && !NoJSAPI(), mGlobalObject); - MOZ_ASSERT(!mGlobalObject || mGlobalObject->GetGlobalJSObject(), - "Must have an actual JS global for the duration on the stack"); - MOZ_ASSERT(!mGlobalObject || - JS_IsGlobalObject(mGlobalObject->GetGlobalJSObject()), - "No outer windows allowed"); -} - -ScriptSettingsStackEntry::~ScriptSettingsStackEntry() -{ - // We must have an actual JS global for the entire time this is on the stack. - MOZ_ASSERT_IF(mGlobalObject, mGlobalObject->GetGlobalJSObject()); -} - -// If the entry or incumbent global ends up being something that the subject -// principal doesn't subsume, we don't want to use it. This never happens on -// the web, but can happen with asymmetric privilege relationships (i.e. -// nsExpandedPrincipal and System Principal). -// -// The most correct thing to use instead would be the topmost global on the -// callstack whose principal is subsumed by the subject principal. But that's -// hard to compute, so we just substitute the global of the current -// compartment. In practice, this is fine. -// -// Note that in particular things like: -// -// |SpecialPowers.wrap(crossOriginWindow).eval(open())| -// -// trigger this case. Although both the entry global and the current global -// have normal principals, the use of Gecko-specific System-Principaled JS -// puts the code from two different origins on the callstack at once, which -// doesn't happen normally on the web. -static nsIGlobalObject* -ClampToSubject(nsIGlobalObject* aGlobalOrNull) -{ - if (!aGlobalOrNull || !NS_IsMainThread()) { - return aGlobalOrNull; - } - - nsIPrincipal* globalPrin = aGlobalOrNull->PrincipalOrNull(); - NS_ENSURE_TRUE(globalPrin, GetCurrentGlobal()); - if (!nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller()->SubsumesConsideringDomain(globalPrin)) { - return GetCurrentGlobal(); - } - - return aGlobalOrNull; -} - -nsIGlobalObject* -GetEntryGlobal() -{ - return ClampToSubject(ScriptSettingsStack::EntryGlobal()); -} - -nsIDocument* -GetEntryDocument() -{ - nsIGlobalObject* global = GetEntryGlobal(); - nsCOMPtr entryWin = do_QueryInterface(global); - - // If our entry global isn't a window, see if it's an addon scope associated - // with a window. If it is, the caller almost certainly wants that rather - // than null. - if (!entryWin && global) { - if (auto* win = xpc::AddonWindowOrNull(global->GetGlobalJSObject())) { - entryWin = win->AsInner(); - } - } - - return entryWin ? entryWin->GetExtantDoc() : nullptr; -} - -nsIGlobalObject* -GetIncumbentGlobal() -{ - // We need the current JSContext in order to check the JS for - // scripted frames that may have appeared since anyone last - // manipulated the stack. If it's null, that means that there - // must be no entry global on the stack, and therefore no incumbent - // global either. - JSContext *cx = nsContentUtils::GetCurrentJSContextForThread(); - if (!cx) { - MOZ_ASSERT(ScriptSettingsStack::EntryGlobal() == nullptr); - return nullptr; - } - - // See what the JS engine has to say. If we've got a scripted caller - // override in place, the JS engine will lie to us and pretend that - // there's nothing on the JS stack, which will cause us to check the - // incumbent script stack below. - if (JSObject *global = JS::GetScriptedCallerGlobal(cx)) { - return ClampToSubject(xpc::NativeGlobal(global)); - } - - // Ok, nothing from the JS engine. Let's use whatever's on the - // explicit stack. - return ClampToSubject(ScriptSettingsStack::IncumbentGlobal()); -} - -nsIGlobalObject* -GetCurrentGlobal() -{ - JSContext *cx = nsContentUtils::GetCurrentJSContextForThread(); - if (!cx) { - return nullptr; - } - - JSObject *global = JS::CurrentGlobalOrNull(cx); - if (!global) { - return nullptr; - } - - return xpc::NativeGlobal(global); -} - -nsIPrincipal* -GetWebIDLCallerPrincipal() -{ - MOZ_ASSERT(NS_IsMainThread()); - ScriptSettingsStackEntry *entry = ScriptSettingsStack::EntryPoint(); - - // If we have an entry point that is not NoJSAPI, we know it must be an - // AutoEntryScript. - if (!entry || entry->NoJSAPI()) { - return nullptr; - } - AutoEntryScript* aes = static_cast(entry); - - return aes->mWebIDLCallerPrincipal; -} - -bool -IsJSAPIActive() -{ - ScriptSettingsStackEntry* topEntry = ScriptSettingsStack::Top(); - return topEntry && !topEntry->NoJSAPI(); -} - -namespace danger { -JSContext* -GetJSContext() -{ - return CycleCollectedJSContext::Get()->Context(); -} -} // namespace danger - -JS::RootingContext* -RootingCx() -{ - return CycleCollectedJSContext::Get()->RootingCx(); -} - -AutoJSAPI::AutoJSAPI() - : ScriptSettingsStackEntry(nullptr, eJSAPI) - , mCx(nullptr) - , mIsMainThread(false) // For lack of anything better -{ -} - -AutoJSAPI::~AutoJSAPI() -{ - if (!mCx) { - // No need to do anything here: we never managed to Init, so can't have an - // exception on our (nonexistent) JSContext. We also don't need to restore - // any state on it. Finally, we never made it to pushing outselves onto the - // ScriptSettingsStack, so shouldn't pop. - MOZ_ASSERT(ScriptSettingsStack::Top() != this); - return; - } - - ReportException(); - - if (mOldWarningReporter.isSome()) { - JS::SetWarningReporter(cx(), mOldWarningReporter.value()); - } - - // Leave the request before popping. - if (mIsMainThread) { - mAutoRequest.reset(); - } - - ScriptSettingsStack::Pop(this); -} - -void -WarningOnlyErrorReporter(JSContext* aCx, JSErrorReport* aRep); - -void -AutoJSAPI::InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal, - JSContext* aCx, bool aIsMainThread) -{ - MOZ_ASSERT(aCx); - MOZ_ASSERT(aCx == danger::GetJSContext()); - MOZ_ASSERT(aIsMainThread == NS_IsMainThread()); - MOZ_ASSERT(bool(aGlobalObject) == bool(aGlobal)); - MOZ_ASSERT_IF(aGlobalObject, aGlobalObject->GetGlobalJSObject() == aGlobal); -#ifdef DEBUG - bool haveException = JS_IsExceptionPending(aCx); -#endif // DEBUG - - mCx = aCx; - mIsMainThread = aIsMainThread; - mGlobalObject = aGlobalObject; - if (aIsMainThread) { - // We _could_ just unconditionally emplace mAutoRequest here. It's just not - // needed on worker threads, and we're hoping to kill it on the main thread - // too. - mAutoRequest.emplace(mCx); - } - if (aGlobal) { - JS::ExposeObjectToActiveJS(aGlobal); - } - mAutoNullableCompartment.emplace(mCx, aGlobal); - - ScriptSettingsStack::Push(this); - - mOldWarningReporter.emplace(JS::GetWarningReporter(aCx)); - - JS::SetWarningReporter(aCx, WarningOnlyErrorReporter); - -#ifdef DEBUG - if (haveException) { - JS::Rooted exn(aCx); - JS_GetPendingException(aCx, &exn); - - JS_ClearPendingException(aCx); - if (exn.isObject()) { - JS::Rooted exnObj(aCx, &exn.toObject()); - - nsAutoJSString stack, filename, name, message; - int32_t line; - - JS::Rooted tmp(aCx); - if (!JS_GetProperty(aCx, exnObj, "filename", &tmp)) { - JS_ClearPendingException(aCx); - } - if (tmp.isUndefined()) { - if (!JS_GetProperty(aCx, exnObj, "fileName", &tmp)) { - JS_ClearPendingException(aCx); - } - } - - if (!filename.init(aCx, tmp)) { - JS_ClearPendingException(aCx); - } - - if (!JS_GetProperty(aCx, exnObj, "stack", &tmp) || - !stack.init(aCx, tmp)) { - JS_ClearPendingException(aCx); - } - - if (!JS_GetProperty(aCx, exnObj, "name", &tmp) || - !name.init(aCx, tmp)) { - JS_ClearPendingException(aCx); - } - - if (!JS_GetProperty(aCx, exnObj, "message", &tmp) || - !message.init(aCx, tmp)) { - JS_ClearPendingException(aCx); - } - - if (!JS_GetProperty(aCx, exnObj, "lineNumber", &tmp) || - !JS::ToInt32(aCx, tmp, &line)) { - JS_ClearPendingException(aCx); - line = 0; - } - - printf_stderr("PREEXISTING EXCEPTION OBJECT: '%s: %s'\n%s:%d\n%s\n", - NS_ConvertUTF16toUTF8(name).get(), - NS_ConvertUTF16toUTF8(message).get(), - NS_ConvertUTF16toUTF8(filename).get(), line, - NS_ConvertUTF16toUTF8(stack).get()); - } else { - // It's a primitive... not much we can do other than stringify it. - nsAutoJSString exnStr; - if (!exnStr.init(aCx, exn)) { - JS_ClearPendingException(aCx); - } - - printf_stderr("PREEXISTING EXCEPTION PRIMITIVE: %s\n", - NS_ConvertUTF16toUTF8(exnStr).get()); - } - MOZ_ASSERT(false, "We had an exception; we should not have"); - } -#endif // DEBUG -} - -AutoJSAPI::AutoJSAPI(nsIGlobalObject* aGlobalObject, - bool aIsMainThread, - Type aType) - : ScriptSettingsStackEntry(aGlobalObject, aType) - , mIsMainThread(aIsMainThread) -{ - MOZ_ASSERT(aGlobalObject); - MOZ_ASSERT(aGlobalObject->GetGlobalJSObject(), "Must have a JS global"); - MOZ_ASSERT(aIsMainThread == NS_IsMainThread()); - - InitInternal(aGlobalObject, aGlobalObject->GetGlobalJSObject(), - danger::GetJSContext(), aIsMainThread); -} - -void -AutoJSAPI::Init() -{ - MOZ_ASSERT(!mCx, "An AutoJSAPI should only be initialised once"); - - InitInternal(/* aGlobalObject */ nullptr, /* aGlobal */ nullptr, - danger::GetJSContext(), NS_IsMainThread()); -} - -bool -AutoJSAPI::Init(nsIGlobalObject* aGlobalObject, JSContext* aCx) -{ - MOZ_ASSERT(!mCx, "An AutoJSAPI should only be initialised once"); - MOZ_ASSERT(aCx); - - if (NS_WARN_IF(!aGlobalObject)) { - return false; - } - - JSObject* global = aGlobalObject->GetGlobalJSObject(); - if (NS_WARN_IF(!global)) { - return false; - } - - InitInternal(aGlobalObject, global, aCx, NS_IsMainThread()); - return true; -} - -bool -AutoJSAPI::Init(nsIGlobalObject* aGlobalObject) -{ - return Init(aGlobalObject, danger::GetJSContext()); -} - -bool -AutoJSAPI::Init(JSObject* aObject) -{ - nsIGlobalObject* global = nullptr; - if (aObject) - global = xpc::NativeGlobal(aObject); - if (global) - return Init(global); - else - return false; -} - -bool -AutoJSAPI::Init(nsPIDOMWindowInner* aWindow, JSContext* aCx) -{ - return Init(nsGlobalWindow::Cast(aWindow), aCx); -} - -bool -AutoJSAPI::Init(nsPIDOMWindowInner* aWindow) -{ - return Init(nsGlobalWindow::Cast(aWindow)); -} - -bool -AutoJSAPI::Init(nsGlobalWindow* aWindow, JSContext* aCx) -{ - return Init(static_cast(aWindow), aCx); -} - -bool -AutoJSAPI::Init(nsGlobalWindow* aWindow) -{ - return Init(static_cast(aWindow)); -} - -// Even with autoJSAPIOwnsErrorReporting, the JS engine still sends warning -// reports to the JSErrorReporter as soon as they are generated. These go -// directly to the console, so we can handle them easily here. -// -// Eventually, SpiderMonkey will have a special-purpose callback for warnings -// only. -void -WarningOnlyErrorReporter(JSContext* aCx, JSErrorReport* aRep) -{ - MOZ_ASSERT(JSREPORT_IS_WARNING(aRep->flags)); - if (!NS_IsMainThread()) { - // Reporting a warning on workers is a bit complicated because we have to - // climb our parent chain until we get to the main thread. So go ahead and - // just go through the worker ReportError codepath here. - // - // That said, it feels like we should be able to short-circuit things a bit - // here by posting an appropriate runnable to the main thread directly... - // Worth looking into sometime. - workers::WorkerPrivate* worker = workers::GetWorkerPrivateFromContext(aCx); - MOZ_ASSERT(worker); - - worker->ReportError(aCx, JS::ConstUTF8CharsZ(), aRep); - return; - } - - RefPtr xpcReport = new xpc::ErrorReport(); - nsGlobalWindow* win = xpc::CurrentWindowOrNull(aCx); - if (!win) { - // We run addons in a separate privileged compartment, but if we're in an - // addon compartment we should log warnings to the console of the associated - // DOM Window. - win = xpc::AddonWindowOrNull(JS::CurrentGlobalOrNull(aCx)); - } - xpcReport->Init(aRep, nullptr, nsContentUtils::IsCallerChrome(), - win ? win->AsInner()->WindowID() : 0); - xpcReport->LogToConsole(); -} - -void -AutoJSAPI::ReportException() -{ - if (!HasException()) { - return; - } - - // AutoJSAPI uses a JSAutoNullableCompartment, and may be in a null - // compartment when the destructor is called. However, the JS engine - // requires us to be in a compartment when we fetch the pending exception. - // In this case, we enter the privileged junk scope and don't dispatch any - // error events. - JS::Rooted errorGlobal(cx(), JS::CurrentGlobalOrNull(cx())); - if (!errorGlobal) { - if (mIsMainThread) { - errorGlobal = xpc::PrivilegedJunkScope(); - } else { - errorGlobal = workers::GetCurrentThreadWorkerGlobal(); - } - } - JSAutoCompartment ac(cx(), errorGlobal); - JS::Rooted exn(cx()); - js::ErrorReport jsReport(cx()); - if (StealException(&exn) && - jsReport.init(cx(), exn, js::ErrorReport::WithSideEffects)) { - if (mIsMainThread) { - RefPtr xpcReport = new xpc::ErrorReport(); - - RefPtr win = xpc::WindowGlobalOrNull(errorGlobal); - if (!win) { - // We run addons in a separate privileged compartment, but they still - // expect to trigger the onerror handler of their associated DOM Window. - win = xpc::AddonWindowOrNull(errorGlobal); - } - nsPIDOMWindowInner* inner = win ? win->AsInner() : nullptr; - xpcReport->Init(jsReport.report(), jsReport.toStringResult().c_str(), - nsContentUtils::IsCallerChrome(), - inner ? inner->WindowID() : 0); - if (inner && jsReport.report()->errorNumber != JSMSG_OUT_OF_MEMORY) { - JS::RootingContext* rcx = JS::RootingContext::get(cx()); - DispatchScriptErrorEvent(inner, rcx, xpcReport, exn); - } else { - JS::Rooted stack(cx(), - xpc::FindExceptionStackForConsoleReport(inner, exn)); - xpcReport->LogToConsoleWithStack(stack); - } - } else { - // On a worker, we just use the worker error reporting mechanism and don't - // bother with xpc::ErrorReport. This will ensure that all the right - // events (which are a lot more complicated than in the window case) get - // fired. - workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate(); - MOZ_ASSERT(worker); - MOZ_ASSERT(worker->GetJSContext() == cx()); - // Before invoking ReportError, put the exception back on the context, - // because it may want to put it in its error events and has no other way - // to get hold of it. After we invoke ReportError, clear the exception on - // cx(), just in case ReportError didn't. - JS_SetPendingException(cx(), exn); - worker->ReportError(cx(), jsReport.toStringResult(), jsReport.report()); - ClearException(); - } - } else { - NS_WARNING("OOMed while acquiring uncaught exception from JSAPI"); - ClearException(); - } -} - -bool -AutoJSAPI::PeekException(JS::MutableHandle aVal) -{ - MOZ_ASSERT_IF(mIsMainThread, IsStackTop()); - MOZ_ASSERT(HasException()); - MOZ_ASSERT(js::GetContextCompartment(cx())); - if (!JS_GetPendingException(cx(), aVal)) { - return false; - } - return true; -} - -bool -AutoJSAPI::StealException(JS::MutableHandle aVal) -{ - if (!PeekException(aVal)) { - return false; - } - JS_ClearPendingException(cx()); - return true; -} - -#ifdef DEBUG -bool -AutoJSAPI::IsStackTop() const -{ - return ScriptSettingsStack::TopNonIncumbentScript() == this; -} -#endif // DEBUG - -AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject, - const char *aReason, - bool aIsMainThread) - : AutoJSAPI(aGlobalObject, aIsMainThread, eEntryScript) - , mWebIDLCallerPrincipal(nullptr) -{ - MOZ_ASSERT(aGlobalObject); - - if (aIsMainThread && gRunToCompletionListeners > 0) { - mDocShellEntryMonitor.emplace(cx(), aReason); - } -} - -AutoEntryScript::AutoEntryScript(JSObject* aObject, - const char *aReason, - bool aIsMainThread) - : AutoEntryScript(xpc::NativeGlobal(aObject), aReason, aIsMainThread) -{ -} - -AutoEntryScript::~AutoEntryScript() -{ - // GC when we pop a script entry point. This is a useful heuristic that helps - // us out on certain (flawed) benchmarks like sunspider, because it lets us - // avoid GCing during the timing loop. - JS_MaybeGC(cx()); -} - -AutoEntryScript::DocshellEntryMonitor::DocshellEntryMonitor(JSContext* aCx, - const char* aReason) - : JS::dbg::AutoEntryMonitor(aCx) - , mReason(aReason) -{ -} - -void -AutoEntryScript::DocshellEntryMonitor::Entry(JSContext* aCx, JSFunction* aFunction, - JSScript* aScript, JS::Handle aAsyncStack, - const char* aAsyncCause) -{ - JS::Rooted rootedFunction(aCx); - if (aFunction) { - rootedFunction = aFunction; - } - JS::Rooted rootedScript(aCx); - if (aScript) { - rootedScript = aScript; - } - - nsCOMPtr window = - do_QueryInterface(xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx))); - if (!window || !window->GetDocShell() || - !window->GetDocShell()->GetRecordProfileTimelineMarkers()) { - return; - } - - nsCOMPtr docShellForJSRunToCompletion = window->GetDocShell(); - nsString filename; - uint32_t lineNumber = 0; - - js::AutoStableStringChars functionName(aCx); - if (rootedFunction) { - JS::Rooted displayId(aCx, JS_GetFunctionDisplayId(rootedFunction)); - if (displayId) { - if (!functionName.initTwoByte(aCx, displayId)) { - JS_ClearPendingException(aCx); - return; - } - } - } - - if (!rootedScript) { - rootedScript = JS_GetFunctionScript(aCx, rootedFunction); - } - if (rootedScript) { - filename = NS_ConvertUTF8toUTF16(JS_GetScriptFilename(rootedScript)); - lineNumber = JS_GetScriptBaseLineNumber(aCx, rootedScript); - } - - if (!filename.IsEmpty() || functionName.isTwoByte()) { - const char16_t* functionNameChars = functionName.isTwoByte() ? - functionName.twoByteChars() : nullptr; - - docShellForJSRunToCompletion->NotifyJSRunToCompletionStart(mReason, - functionNameChars, - filename.BeginReading(), - lineNumber, aAsyncStack, - aAsyncCause); - } -} - -void -AutoEntryScript::DocshellEntryMonitor::Exit(JSContext* aCx) -{ - nsCOMPtr window = - do_QueryInterface(xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx))); - // Not really worth checking GetRecordProfileTimelineMarkers here. - if (window && window->GetDocShell()) { - nsCOMPtr docShellForJSRunToCompletion = window->GetDocShell(); - docShellForJSRunToCompletion->NotifyJSRunToCompletionStop(); - } -} - -AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject) - : ScriptSettingsStackEntry(aGlobalObject, eIncumbentScript) - , mCallerOverride(nsContentUtils::GetCurrentJSContextForThread()) -{ - ScriptSettingsStack::Push(this); -} - -AutoIncumbentScript::~AutoIncumbentScript() -{ - ScriptSettingsStack::Pop(this); -} - -AutoNoJSAPI::AutoNoJSAPI() - : ScriptSettingsStackEntry(nullptr, eNoJSAPI) -{ - ScriptSettingsStack::Push(this); -} - -AutoNoJSAPI::~AutoNoJSAPI() -{ - ScriptSettingsStack::Pop(this); -} - -} // namespace dom - -AutoJSContext::AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) - : mCx(nullptr) -{ - JS::AutoSuppressGCAnalysis nogc; - MOZ_ASSERT(!mCx, "mCx should not be initialized!"); - MOZ_ASSERT(NS_IsMainThread()); - - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - - if (dom::IsJSAPIActive()) { - mCx = dom::danger::GetJSContext(); - } else { - mJSAPI.Init(); - mCx = mJSAPI.cx(); - } -} - -AutoJSContext::operator JSContext*() const -{ - return mCx; -} - -AutoSafeJSContext::AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) - : AutoJSAPI() -{ - MOZ_ASSERT(NS_IsMainThread()); - - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - - DebugOnly ok = Init(xpc::UnprivilegedJunkScope()); - MOZ_ASSERT(ok, - "This is quite odd. We should have crashed in the " - "xpc::NativeGlobal() call if xpc::UnprivilegedJunkScope() " - "returned null, and inited correctly otherwise!"); -} - -AutoSlowOperation::AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) - : AutoJSAPI() -{ - MOZ_ASSERT(NS_IsMainThread()); - - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - - Init(); -} - -void -AutoSlowOperation::CheckForInterrupt() -{ - // JS_CheckForInterrupt expects us to be in a compartment. - JSAutoCompartment ac(cx(), xpc::UnprivilegedJunkScope()); - JS_CheckForInterrupt(cx()); -} - -} // namespace mozilla diff --git a/dom/base/ScriptSettings.h b/dom/base/ScriptSettings.h deleted file mode 100644 index 05e62f55e..000000000 --- a/dom/base/ScriptSettings.h +++ /dev/null @@ -1,465 +0,0 @@ -/* -*- 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/. */ - -/* Utilities for managing the script settings object stack defined in webapps */ - -#ifndef mozilla_dom_ScriptSettings_h -#define mozilla_dom_ScriptSettings_h - -#include "MainThreadUtils.h" -#include "nsIGlobalObject.h" -#include "nsIPrincipal.h" - -#include "mozilla/Maybe.h" - -#include "jsapi.h" -#include "js/Debug.h" - -class nsPIDOMWindowInner; -class nsGlobalWindow; -class nsIScriptContext; -class nsIDocument; -class nsIDocShell; - -namespace mozilla { -namespace dom { - -/* - * System-wide setup/teardown routines. Init and Destroy should be invoked - * once each, at startup and shutdown (respectively). - */ -void InitScriptSettings(); -void DestroyScriptSettings(); -bool ScriptSettingsInitialized(); - -/* - * Static helpers in ScriptSettings which track the number of listeners - * of Javascript RunToCompletion events. These should be used by the code in - * nsDocShell::SetRecordProfileTimelineMarkers to indicate to script - * settings that script run-to-completion needs to be monitored. - * SHOULD BE CALLED ONLY BY MAIN THREAD. - */ -void UseEntryScriptProfiling(); -void UnuseEntryScriptProfiling(); - -// To implement a web-compatible browser, it is often necessary to obtain the -// global object that is "associated" with the currently-running code. This -// process is made more complicated by the fact that, historically, different -// algorithms have operated with different definitions of the "associated" -// global. -// -// HTML5 formalizes this into two concepts: the "incumbent global" and the -// "entry global". The incumbent global corresponds to the global of the -// current script being executed, whereas the entry global corresponds to the -// global of the script where the current JS execution began. -// -// There is also a potentially-distinct third global that is determined by the -// current compartment. This roughly corresponds with the notion of Realms in -// ECMAScript. -// -// Suppose some event triggers an event listener in window |A|, which invokes a -// scripted function in window |B|, which invokes the |window.location.href| -// setter in window |C|. The entry global would be |A|, the incumbent global -// would be |B|, and the current compartment would be that of |C|. -// -// In general, it's best to use to use the most-closely-associated global -// unless the spec says to do otherwise. In 95% of the cases, the global of -// the current compartment (GetCurrentGlobal()) is the right thing. For -// example, WebIDL constructors (new C.XMLHttpRequest()) are initialized with -// the global of the current compartment (i.e. |C|). -// -// The incumbent global is very similar, but differs in a few edge cases. For -// example, if window |B| does |C.location.href = "..."|, the incumbent global -// used for the navigation algorithm is B, because no script from |C| was ever run. -// -// The entry global is used for various things like computing base URIs, mostly -// for historical reasons. -// -// Note that all of these functions return bonafide global objects. This means -// that, for Windows, they always return the inner. - -// Returns the global associated with the top-most Candidate Entry Point on -// the Script Settings Stack. See the HTML spec. This may be null. -nsIGlobalObject* GetEntryGlobal(); - -// If the entry global is a window, returns its extant document. Otherwise, -// returns null. -nsIDocument* GetEntryDocument(); - -// Returns the global associated with the top-most entry of the the Script -// Settings Stack. See the HTML spec. This may be null. -nsIGlobalObject* GetIncumbentGlobal(); - -// Returns the global associated with the current compartment. This may be null. -nsIGlobalObject* GetCurrentGlobal(); - -// JS-implemented WebIDL presents an interesting situation with respect to the -// subject principal. A regular C++-implemented API can simply examine the -// compartment of the most-recently-executed script, and use that to infer the -// responsible party. However, JS-implemented APIs are run with system -// principal, and thus clobber the subject principal of the script that -// invoked the API. So we have to do some extra work to keep track of this -// information. -// -// We therefore implement the following behavior: -// * Each Script Settings Object has an optional WebIDL Caller Principal field. -// This defaults to null. -// * When we push an Entry Point in preparation to run a JS-implemented WebIDL -// callback, we grab the subject principal at the time of invocation, and -// store that as the WebIDL Caller Principal. -// * When non-null, callers can query this principal from script via an API on -// Components.utils. -nsIPrincipal* GetWebIDLCallerPrincipal(); - -// This may be used by callers that know that their incumbent global is non- -// null (i.e. they know there have been no System Caller pushes since the -// inner-most script execution). -inline JSObject& IncumbentJSGlobal() -{ - return *GetIncumbentGlobal()->GetGlobalJSObject(); -} - -// Returns whether JSAPI is active right now. If it is not, working with a -// JSContext you grab from somewhere random is not OK and you should be doing -// AutoJSAPI or AutoEntryScript to get yourself a properly set up JSContext. -bool IsJSAPIActive(); - -namespace danger { - -// Get the JSContext for this thread. This is in the "danger" namespace because -// we generally want people using AutoJSAPI instead, unless they really know -// what they're doing. -JSContext* GetJSContext(); - -} // namespace danger - -JS::RootingContext* RootingCx(); - -class ScriptSettingsStack; -class ScriptSettingsStackEntry { - friend class ScriptSettingsStack; - -public: - ~ScriptSettingsStackEntry(); - - bool NoJSAPI() const { return mType == eNoJSAPI; } - bool IsEntryCandidate() const { - return mType == eEntryScript || mType == eNoJSAPI; - } - bool IsIncumbentCandidate() { return mType != eJSAPI; } - bool IsIncumbentScript() { return mType == eIncumbentScript; } - -protected: - enum Type { - eEntryScript, - eIncumbentScript, - eJSAPI, - eNoJSAPI - }; - - ScriptSettingsStackEntry(nsIGlobalObject *aGlobal, - Type aEntryType); - - nsCOMPtr mGlobalObject; - Type mType; - -private: - ScriptSettingsStackEntry *mOlder; -}; - -/* - * For any interaction with JSAPI, an AutoJSAPI (or one of its subclasses) - * must be on the stack. - * - * This base class should be instantiated as-is when the caller wants to use - * JSAPI but doesn't expect to run script. The caller must then call one of its - * Init functions before being able to access the JSContext through cx(). - * Its current duties are as-follows (see individual Init comments for details): - * - * * Grabbing an appropriate JSContext, and, on the main thread, pushing it onto - * the JSContext stack. - * * Entering an initial (possibly null) compartment, to ensure that the - * previously entered compartment for that JSContext is not used by mistake. - * * Reporting any exceptions left on the JSRuntime, unless the caller steals - * or silences them. - * * On main thread, entering a JSAutoRequest. - * - * Additionally, the following duties are planned, but not yet implemented: - * - * * De-poisoning the JSRuntime to allow manipulation of JSAPI. This requires - * implementing the poisoning first. For now, this de-poisoning - * effectively corresponds to having a non-null cx on the stack. - * - * In situations where the consumer expects to run script, AutoEntryScript - * should be used, which does additional manipulation of the script settings - * stack. In bug 991758, we'll add hard invariants to SpiderMonkey, such that - * any attempt to run script without an AutoEntryScript on the stack will - * fail. This prevents system code from accidentally triggering script - * execution at inopportune moments via surreptitious getters and proxies. - */ -class MOZ_STACK_CLASS AutoJSAPI : protected ScriptSettingsStackEntry { -public: - // Trivial constructor. One of the Init functions must be called before - // accessing the JSContext through cx(). - AutoJSAPI(); - - ~AutoJSAPI(); - - // This uses the SafeJSContext (or worker equivalent), and enters a null - // compartment, so that the consumer is forced to select a compartment to - // enter before manipulating objects. - // - // This variant will ensure that any errors reported by this AutoJSAPI as it - // comes off the stack will not fire error events or be associated with any - // particular web-visible global. - void Init(); - - // This uses the SafeJSContext (or worker equivalent), and enters the - // compartment of aGlobalObject. - // If aGlobalObject or its associated JS global are null then it returns - // false and use of cx() will cause an assertion. - // - // If aGlobalObject represents a web-visible global, errors reported by this - // AutoJSAPI as it comes off the stack will fire the relevant error events and - // show up in the corresponding web console. - MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject); - - // This is a helper that grabs the native global associated with aObject and - // invokes the above Init() with that. - MOZ_MUST_USE bool Init(JSObject* aObject); - - // Unsurprisingly, this uses aCx and enters the compartment of aGlobalObject. - // If aGlobalObject or its associated JS global are null then it returns - // false and use of cx() will cause an assertion. - // If aCx is null it will cause an assertion. - // - // If aGlobalObject represents a web-visible global, errors reported by this - // AutoJSAPI as it comes off the stack will fire the relevant error events and - // show up in the corresponding web console. - MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject, JSContext* aCx); - - // Convenience functions to take an nsPIDOMWindow* or nsGlobalWindow*, - // when it is more easily available than an nsIGlobalObject. - MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow); - MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow, JSContext* aCx); - - MOZ_MUST_USE bool Init(nsGlobalWindow* aWindow); - MOZ_MUST_USE bool Init(nsGlobalWindow* aWindow, JSContext* aCx); - - JSContext* cx() const { - MOZ_ASSERT(mCx, "Must call Init before using an AutoJSAPI"); - MOZ_ASSERT(IsStackTop()); - return mCx; - } - -#ifdef DEBUG - bool IsStackTop() const; -#endif - - // If HasException, report it. Otherwise, a no-op. - void ReportException(); - - bool HasException() const { - MOZ_ASSERT(IsStackTop()); - return JS_IsExceptionPending(cx()); - }; - - // Transfers ownership of the current exception from the JS engine to the - // caller. Callers must ensure that HasException() is true, and that cx() - // is in a non-null compartment. - // - // Note that this fails if and only if we OOM while wrapping the exception - // into the current compartment. - MOZ_MUST_USE bool StealException(JS::MutableHandle aVal); - - // Peek the current exception from the JS engine, without stealing it. - // Callers must ensure that HasException() is true, and that cx() is in a - // non-null compartment. - // - // Note that this fails if and only if we OOM while wrapping the exception - // into the current compartment. - MOZ_MUST_USE bool PeekException(JS::MutableHandle aVal); - - void ClearException() { - MOZ_ASSERT(IsStackTop()); - JS_ClearPendingException(cx()); - } - -protected: - // Protected constructor for subclasses. This constructor initialises the - // AutoJSAPI, so Init must NOT be called on subclasses that use this. - AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, Type aType); - -private: - mozilla::Maybe mAutoRequest; - mozilla::Maybe mAutoNullableCompartment; - JSContext *mCx; - - // Whether we're mainthread or not; set when we're initialized. - bool mIsMainThread; - Maybe mOldWarningReporter; - - void InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal, - JSContext* aCx, bool aIsMainThread); - - AutoJSAPI(const AutoJSAPI&) = delete; - AutoJSAPI& operator= (const AutoJSAPI&) = delete; -}; - -/* - * A class that represents a new script entry point. - * - * |aReason| should be a statically-allocated C string naming the reason we're - * invoking JavaScript code: "setTimeout", "event", and so on. The devtools use - * these strings to label JS execution in timeline and profiling displays. - */ -class MOZ_STACK_CLASS AutoEntryScript : public AutoJSAPI { -public: - AutoEntryScript(nsIGlobalObject* aGlobalObject, - const char *aReason, - bool aIsMainThread = NS_IsMainThread()); - - AutoEntryScript(JSObject* aObject, // Any object from the relevant global - const char *aReason, - bool aIsMainThread = NS_IsMainThread()); - - ~AutoEntryScript(); - - void SetWebIDLCallerPrincipal(nsIPrincipal *aPrincipal) { - mWebIDLCallerPrincipal = aPrincipal; - } - -private: - // A subclass of AutoEntryMonitor that notifies the docshell. - class DocshellEntryMonitor final : public JS::dbg::AutoEntryMonitor - { - public: - DocshellEntryMonitor(JSContext* aCx, const char* aReason); - - // Please note that |aAsyncCause| here is owned by the caller, and its - // lifetime must outlive the lifetime of the DocshellEntryMonitor object. - // In practice, |aAsyncCause| is identical to |aReason| passed into - // the AutoEntryScript constructor, so the lifetime requirements are - // trivially satisfied by |aReason| being a statically allocated string. - void Entry(JSContext* aCx, JSFunction* aFunction, - JS::Handle aAsyncStack, - const char* aAsyncCause) override - { - Entry(aCx, aFunction, nullptr, aAsyncStack, aAsyncCause); - } - - void Entry(JSContext* aCx, JSScript* aScript, - JS::Handle aAsyncStack, - const char* aAsyncCause) override - { - Entry(aCx, nullptr, aScript, aAsyncStack, aAsyncCause); - } - - void Exit(JSContext* aCx) override; - - private: - void Entry(JSContext* aCx, JSFunction* aFunction, JSScript* aScript, - JS::Handle aAsyncStack, - const char* aAsyncCause); - - const char* mReason; - }; - - // It's safe to make this a weak pointer, since it's the subject principal - // when we go on the stack, so can't go away until after we're gone. In - // particular, this is only used from the CallSetup constructor, and only in - // the aIsJSImplementedWebIDL case. And in that case, the subject principal - // is the principal of the callee function that is part of the CallArgs just a - // bit up the stack, and which will outlive us. So we know the principal - // can't go away until then either. - nsIPrincipal* MOZ_NON_OWNING_REF mWebIDLCallerPrincipal; - friend nsIPrincipal* GetWebIDLCallerPrincipal(); - - Maybe mDocShellEntryMonitor; -}; - -/* - * A class that can be used to force a particular incumbent script on the stack. - */ -class AutoIncumbentScript : protected ScriptSettingsStackEntry { -public: - explicit AutoIncumbentScript(nsIGlobalObject* aGlobalObject); - ~AutoIncumbentScript(); - -private: - JS::AutoHideScriptedCaller mCallerOverride; -}; - -/* - * A class to put the JS engine in an unusable state. The subject principal - * will become System, the information on the script settings stack is - * rendered inaccessible, and JSAPI may not be manipulated until the class is - * either popped or an AutoJSAPI instance is subsequently pushed. - * - * This class may not be instantiated if an exception is pending. - */ -class AutoNoJSAPI : protected ScriptSettingsStackEntry { -public: - explicit AutoNoJSAPI(); - ~AutoNoJSAPI(); -}; - -} // namespace dom - -/** - * Use AutoJSContext when you need a JS context on the stack but don't have one - * passed as a parameter. AutoJSContext will take care of finding the most - * appropriate JS context and release it when leaving the stack. - */ -class MOZ_RAII AutoJSContext { -public: - explicit AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); - operator JSContext*() const; - -protected: - JSContext* mCx; - dom::AutoJSAPI mJSAPI; - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER -}; - -/** - * AutoSafeJSContext is similar to AutoJSContext but will only return the safe - * JS context. That means it will never call nsContentUtils::GetCurrentJSContext(). - * - * Note - This is deprecated. Please use AutoJSAPI instead. - */ -class MOZ_RAII AutoSafeJSContext : public dom::AutoJSAPI { -public: - explicit AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); - operator JSContext*() const - { - return cx(); - } - -private: - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER -}; - -/** - * Use AutoSlowOperation when native side calls many JS callbacks in a row - * and slow script dialog should be activated if too much time is spent going - * through those callbacks. - * AutoSlowOperation puts a JSAutoRequest on the stack so that we don't continue - * to reset the watchdog and CheckForInterrupt can be then used to check whether - * JS execution should be interrupted. - */ -class MOZ_RAII AutoSlowOperation : public dom::AutoJSAPI -{ -public: - explicit AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); - void CheckForInterrupt(); -private: - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER -}; - -} // namespace mozilla - -#endif // mozilla_dom_ScriptSettings_h diff --git a/dom/base/moz.build b/dom/base/moz.build index 89f1785ca..aadafe412 100755 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -26,7 +26,6 @@ XPIDL_SOURCES += [ 'nsIObjectLoadingContent.idl', 'nsIRemoteWindowContext.idl', 'nsIScriptChannel.idl', - 'nsIScriptLoaderObserver.idl', 'nsISelection.idl', 'nsISelectionController.idl', 'nsISelectionDisplay.idl', @@ -96,7 +95,6 @@ EXPORTS += [ 'nsINode.h', 'nsINodeList.h', 'nsIScriptContext.h', - 'nsIScriptElement.h', 'nsIScriptGlobalObject.h', 'nsIScriptNameSpaceManager.h', 'nsIScriptObjectPrincipal.h', @@ -117,7 +115,6 @@ EXPORTS += [ 'nsRange.h', 'nsReferencedElement.h', 'nsSandboxFlags.h', - 'nsScriptLoader.h', 'nsStructuredCloneContainer.h', 'nsStubAnimationObserver.h', 'nsStubDocumentObserver.h', @@ -208,7 +205,6 @@ EXPORTS.mozilla.dom += [ 'ResponsiveImageSelector.h', 'SameProcessMessageQueue.h', 'ScreenOrientation.h', - 'ScriptSettings.h', 'ShadowRoot.h', 'SimpleTreeIterator.h', 'StructuredCloneHolder.h', @@ -328,8 +324,6 @@ SOURCES += [ 'nsRange.cpp', 'nsReferencedElement.cpp', 'nsScreen.cpp', - 'nsScriptElement.cpp', - 'nsScriptLoader.cpp', 'nsScriptNameSpaceManager.cpp', 'nsStructuredCloneContainer.cpp', 'nsStubAnimationObserver.cpp', @@ -356,7 +350,6 @@ SOURCES += [ 'ResponsiveImageSelector.cpp', 'SameProcessMessageQueue.cpp', 'ScreenOrientation.cpp', - 'ScriptSettings.cpp', 'ShadowRoot.cpp', 'StructuredCloneHolder.cpp', 'StyleSheetList.cpp', diff --git a/dom/base/nsContentPermissionHelper.cpp b/dom/base/nsContentPermissionHelper.cpp index c57fc6233..eaaec2a41 100644 --- a/dom/base/nsContentPermissionHelper.cpp +++ b/dom/base/nsContentPermissionHelper.cpp @@ -29,9 +29,8 @@ #include "nsIDocument.h" #include "nsIDOMEvent.h" #include "nsWeakPtr.h" -#include "ScriptSettings.h" -using mozilla::Unused; // +using mozilla::Unused; using namespace mozilla::dom; using namespace mozilla; diff --git a/dom/base/nsContentSink.cpp b/dom/base/nsContentSink.cpp index 490f0ec17..1e6465a1b 100644 --- a/dom/base/nsContentSink.cpp +++ b/dom/base/nsContentSink.cpp @@ -10,7 +10,6 @@ */ #include "nsContentSink.h" -#include "nsScriptLoader.h" #include "nsIDocument.h" #include "nsIDOMDocument.h" #include "mozilla/css/Loader.h" @@ -49,6 +48,7 @@ #include "nsHTMLDNSPrefetch.h" #include "nsIObserverService.h" #include "mozilla/Preferences.h" +#include "mozilla/dom/ScriptLoader.h" #include "nsParserConstants.h" #include "nsSandboxFlags.h" diff --git a/dom/base/nsContentSink.h b/dom/base/nsContentSink.h index b1a758874..2d914a8d7 100644 --- a/dom/base/nsContentSink.h +++ b/dom/base/nsContentSink.h @@ -36,13 +36,16 @@ class nsIAtom; class nsIChannel; class nsIContent; class nsNodeInfoManager; -class nsScriptLoader; class nsIApplicationCache; namespace mozilla { namespace css { class Loader; } // namespace css + +namespace dom { +class ScriptLoader; +} // namespace dom } // namespace mozilla #ifdef DEBUG @@ -273,7 +276,7 @@ protected: nsCOMPtr mDocShell; RefPtr mCSSLoader; RefPtr mNodeInfoManager; - RefPtr mScriptLoader; + RefPtr mScriptLoader; // back off timer notification after count int32_t mBackoffCount; diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 81e2783a4..d0634b0f9 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -1880,7 +1880,7 @@ nsDocument::Init() mScopeObject = do_GetWeakReference(global); MOZ_ASSERT(mScopeObject); - mScriptLoader = new nsScriptLoader(this); + mScriptLoader = new dom::ScriptLoader(this); mozilla::HoldJSObjects(this); @@ -4685,7 +4685,7 @@ nsDocument::GetWindowInternal() const return win; } -nsScriptLoader* +ScriptLoader* nsDocument::ScriptLoader() { return mScriptLoader; diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index 8ea4993f0..a319ad13e 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -31,7 +31,6 @@ #include "nsJSThingHashtable.h" #include "nsIScriptObjectPrincipal.h" #include "nsIURI.h" -#include "nsScriptLoader.h" #include "nsIRadioGroupContainer.h" #include "nsILayoutHistoryState.h" #include "nsIRequest.h" @@ -60,6 +59,7 @@ #include "mozilla/MemoryReporting.h" #include "mozilla/PendingAnimationTracker.h" #include "mozilla/dom/DOMImplementation.h" +#include "mozilla/dom/ScriptLoader.h" #include "mozilla/dom/StyleSheetList.h" #include "nsDataHashtable.h" #include "mozilla/TimeStamp.h" @@ -674,7 +674,7 @@ public: /** * Get the script loader for this document */ - virtual nsScriptLoader* ScriptLoader() override; + virtual mozilla::dom::ScriptLoader* ScriptLoader() override; /** * Add/Remove an element to the document's id and name hashes @@ -1417,7 +1417,7 @@ public: RefPtr mListenerManager; RefPtr mDOMStyleSheets; RefPtr mStyleSheetSetList; - RefPtr mScriptLoader; + RefPtr mScriptLoader; nsDocHeaderData* mHeaderData; /* mIdentifierMap works as follows for IDs: * 1) Attribute changes affect the table immediately (removing and adding diff --git a/dom/base/nsFrameMessageManager.cpp b/dom/base/nsFrameMessageManager.cpp index bba4232aa..331931f19 100644 --- a/dom/base/nsFrameMessageManager.cpp +++ b/dom/base/nsFrameMessageManager.cpp @@ -19,7 +19,6 @@ #include "nsJSUtils.h" #include "nsJSPrincipals.h" #include "nsNetUtil.h" -#include "nsScriptLoader.h" #include "nsFrameLoader.h" #include "nsIXULRuntime.h" #include "nsIScriptError.h" @@ -38,6 +37,7 @@ #include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/ProcessGlobal.h" #include "mozilla/dom/SameProcessMessageQueue.h" +#include "mozilla/dom/ScriptLoader.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/ipc/BlobChild.h" #include "mozilla/dom/ipc/BlobParent.h" @@ -1786,9 +1786,9 @@ nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript( if (NS_FAILED(NS_ReadInputStreamToString(input, buffer, avail))) { return; } - nsScriptLoader::ConvertToUTF16(channel, (uint8_t*)buffer.get(), avail, - EmptyString(), nullptr, - dataStringBuf, dataStringLength); + ScriptLoader::ConvertToUTF16(channel, (uint8_t*)buffer.get(), avail, + EmptyString(), nullptr, + dataStringBuf, dataStringLength); } JS::SourceBufferHolder srcBuf(dataStringBuf, dataStringLength, diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index dd1fe4586..d696d826b 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -62,7 +62,7 @@ #include "nsReadableUtils.h" #include "nsDOMClassInfo.h" #include "nsJSEnvironment.h" -#include "ScriptSettings.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/Preferences.h" #include "mozilla/Likely.h" #include "mozilla/Sprintf.h" diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index fdaee39ca..506acc7e4 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -91,7 +91,6 @@ class nsIVariant; class nsViewManager; class nsPresContext; class nsRange; -class nsScriptLoader; class nsSMILAnimationController; class nsTextNode; class nsWindowSizes; @@ -152,6 +151,7 @@ enum class OrientationType : uint32_t; class ProcessingInstruction; class Promise; class Selection; +class ScriptLoader; class StyleSheetList; class SVGDocument; class SVGSVGElement; @@ -1290,7 +1290,7 @@ public: /** * Get the script loader for this document */ - virtual nsScriptLoader* ScriptLoader() = 0; + virtual mozilla::dom::ScriptLoader* ScriptLoader() = 0; /** * Add/Remove an element to the document's id and name hashes diff --git a/dom/base/nsIScriptElement.h b/dom/base/nsIScriptElement.h deleted file mode 100644 index 470d51c94..000000000 --- a/dom/base/nsIScriptElement.h +++ /dev/null @@ -1,329 +0,0 @@ -/* -*- 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 nsIScriptElement_h___ -#define nsIScriptElement_h___ - -#include "nsISupports.h" -#include "nsIURI.h" -#include "nsCOMPtr.h" -#include "nsIScriptLoaderObserver.h" -#include "nsWeakPtr.h" -#include "nsIParser.h" -#include "nsContentCreatorFunctions.h" -#include "nsIDOMHTMLScriptElement.h" -#include "mozilla/CORSMode.h" - -#define NS_ISCRIPTELEMENT_IID \ -{ 0xe60fca9b, 0x1b96, 0x4e4e, \ - { 0xa9, 0xb4, 0xdc, 0x98, 0x4f, 0x88, 0x3f, 0x9c } } - -/** - * Internal interface implemented by script elements - */ -class nsIScriptElement : public nsIScriptLoaderObserver { -public: - NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTELEMENT_IID) - - explicit nsIScriptElement(mozilla::dom::FromParser aFromParser) - : mLineNumber(1), - mAlreadyStarted(false), - mMalformed(false), - mDoneAddingChildren(aFromParser == mozilla::dom::NOT_FROM_PARSER || - aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT), - mForceAsync(aFromParser == mozilla::dom::NOT_FROM_PARSER || - aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT), - mFrozen(false), - mDefer(false), - mAsync(false), - mExternal(false), - mParserCreated(aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT ? - mozilla::dom::NOT_FROM_PARSER : aFromParser), - // Fragment parser-created scripts (if executable) - // behave like script-created scripts. - mCreatorParser(nullptr) - { - } - - /** - * Content type identifying the scripting language. Can be empty, in - * which case javascript will be assumed. - * Return false if type attribute is not found. - */ - virtual bool GetScriptType(nsAString& type) = 0; - - /** - * Location of script source text. Can return null, in which case - * this is assumed to be an inline script element. - */ - nsIURI* GetScriptURI() - { - NS_PRECONDITION(mFrozen, "Not ready for this call yet!"); - return mUri; - } - - /** - * Script source text for inline script elements. - */ - virtual void GetScriptText(nsAString& text) = 0; - - virtual void GetScriptCharset(nsAString& charset) = 0; - - /** - * Freezes the return values of GetScriptDeferred(), GetScriptAsync() and - * GetScriptURI() so that subsequent modifications to the attributes don't - * change execution behavior. - */ - virtual void FreezeUriAsyncDefer() = 0; - - /** - * Is the script deferred. Currently only supported by HTML scripts. - */ - bool GetScriptDeferred() - { - NS_PRECONDITION(mFrozen, "Not ready for this call yet!"); - return mDefer; - } - - /** - * Is the script async. Currently only supported by HTML scripts. - */ - bool GetScriptAsync() - { - NS_PRECONDITION(mFrozen, "Not ready for this call yet!"); - return mAsync; - } - - /** - * Is the script an external script? - */ - bool GetScriptExternal() - { - NS_PRECONDITION(mFrozen, "Not ready for this call yet!"); - return mExternal; - } - - /** - * Returns how the element was created. - */ - mozilla::dom::FromParser GetParserCreated() - { - return mParserCreated; - } - - void SetScriptLineNumber(uint32_t aLineNumber) - { - mLineNumber = aLineNumber; - } - uint32_t GetScriptLineNumber() - { - return mLineNumber; - } - - void SetIsMalformed() - { - mMalformed = true; - } - bool IsMalformed() - { - return mMalformed; - } - - void PreventExecution() - { - mAlreadyStarted = true; - } - - void LoseParserInsertedness() - { - mFrozen = false; - mUri = nullptr; - mCreatorParser = nullptr; - mParserCreated = mozilla::dom::NOT_FROM_PARSER; - bool async = false; - nsCOMPtr htmlScript = do_QueryInterface(this); - if (htmlScript) { - htmlScript->GetAsync(&async); - } - mForceAsync = !async; - } - - void SetCreatorParser(nsIParser* aParser) - { - mCreatorParser = do_GetWeakReference(aParser); - } - - /** - * Unblocks the creator parser - */ - void UnblockParser() - { - nsCOMPtr parser = do_QueryReferent(mCreatorParser); - if (parser) { - parser->UnblockParser(); - } - } - - /** - * Attempts to resume parsing asynchronously - */ - void ContinueParserAsync() - { - nsCOMPtr parser = do_QueryReferent(mCreatorParser); - if (parser) { - parser->ContinueInterruptedParsingAsync(); - } - } - - /** - * Informs the creator parser that the evaluation of this script is starting - */ - void BeginEvaluating() - { - nsCOMPtr parser = do_QueryReferent(mCreatorParser); - if (parser) { - parser->PushDefinedInsertionPoint(); - } - } - - /** - * Informs the creator parser that the evaluation of this script is ending - */ - void EndEvaluating() - { - nsCOMPtr parser = do_QueryReferent(mCreatorParser); - if (parser) { - parser->PopDefinedInsertionPoint(); - } - } - - /** - * Retrieves a pointer to the creator parser if this has one or null if not - */ - already_AddRefed GetCreatorParser() - { - nsCOMPtr parser = do_QueryReferent(mCreatorParser); - return parser.forget(); - } - - /** - * This method is called when the parser finishes creating the script - * element's children, if any are present. - * - * @return whether the parser will be blocked while this script is being - * loaded - */ - bool AttemptToExecute() - { - mDoneAddingChildren = true; - bool block = MaybeProcessScript(); - if (!mAlreadyStarted) { - // Need to lose parser-insertedness here to allow another script to cause - // execution later. - LoseParserInsertedness(); - } - return block; - } - - /** - * Get the CORS mode of the script element - */ - virtual mozilla::CORSMode GetCORSMode() const - { - /* Default to no CORS */ - return mozilla::CORS_NONE; - } - - /** - * Fire an error event - */ - virtual nsresult FireErrorEvent() = 0; - -protected: - /** - * Processes the script if it's in the document-tree and links to or - * contains a script. Once it has been evaluated there is no way to make it - * reevaluate the script, you'll have to create a new element. This also means - * that when adding a src attribute to an element that already contains an - * inline script, the script referenced by the src attribute will not be - * loaded. - * - * In order to be able to use multiple childNodes, or to use the - * fallback mechanism of using both inline script and linked script you have - * to add all attributes and childNodes before adding the element to the - * document-tree. - * - * @return whether the parser will be blocked while this script is being - * loaded - */ - virtual bool MaybeProcessScript() = 0; - - /** - * The start line number of the script. - */ - uint32_t mLineNumber; - - /** - * The "already started" flag per HTML5. - */ - bool mAlreadyStarted; - - /** - * The script didn't have an end tag. - */ - bool mMalformed; - - /** - * False if parser-inserted but the parser hasn't triggered running yet. - */ - bool mDoneAddingChildren; - - /** - * If true, the .async property returns true instead of reflecting the - * content attribute. - */ - bool mForceAsync; - - /** - * Whether src, defer and async are frozen. - */ - bool mFrozen; - - /** - * The effective deferredness. - */ - bool mDefer; - - /** - * The effective asyncness. - */ - bool mAsync; - - /** - * The effective externalness. A script can be external with mUri being null - * if the src attribute contained an invalid URL string. - */ - bool mExternal; - - /** - * Whether this element was parser-created. - */ - mozilla::dom::FromParser mParserCreated; - - /** - * The effective src (or null if no src). - */ - nsCOMPtr mUri; - - /** - * The creator parser of a non-defer, non-async parser-inserted script. - */ - nsWeakPtr mCreatorParser; -}; - -NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptElement, NS_ISCRIPTELEMENT_IID) - -#endif // nsIScriptElement_h___ diff --git a/dom/base/nsIScriptLoaderObserver.idl b/dom/base/nsIScriptLoaderObserver.idl deleted file mode 100644 index ed7196525..000000000 --- a/dom/base/nsIScriptLoaderObserver.idl +++ /dev/null @@ -1,47 +0,0 @@ -/* -*- 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 "nsISupports.idl" - -interface nsIScriptElement; -interface nsIURI; - -[scriptable, uuid(7b787204-76fb-4764-96f1-fb7a666db4f4)] -interface nsIScriptLoaderObserver : nsISupports { - - /** - * The script is available for evaluation. For inline scripts, this - * method will be called synchronously. For externally loaded scripts, - * this method will be called when the load completes. - * - * @param aResult A result code representing the result of loading - * a script. If this is a failure code, script evaluation - * will not occur. - * @param aElement The element being processed. - * @param aIsInline Is this an inline script or externally loaded? - * @param aURI What is the URI of the script (the document URI if - * it is inline). - * @param aLineNo At what line does the script appear (generally 1 - * if it is a loaded script). - */ - void scriptAvailable(in nsresult aResult, - in nsIScriptElement aElement, - in boolean aIsInline, - in nsIURI aURI, - in int32_t aLineNo); - - /** - * The script has been evaluated. - * - * @param aResult A result code representing the success or failure of - * the script evaluation. - * @param aElement The element being processed. - * @param aIsInline Is this an inline script or externally loaded? - */ - void scriptEvaluated(in nsresult aResult, - in nsIScriptElement aElement, - in boolean aIsInline); - -}; diff --git a/dom/base/nsInProcessTabChildGlobal.cpp b/dom/base/nsInProcessTabChildGlobal.cpp index 10ccf4aec..2e129f9f0 100644 --- a/dom/base/nsInProcessTabChildGlobal.cpp +++ b/dom/base/nsInProcessTabChildGlobal.cpp @@ -11,13 +11,13 @@ #include "nsIComponentManager.h" #include "nsIServiceManager.h" #include "nsComponentManagerUtils.h" -#include "nsScriptLoader.h" #include "nsFrameLoader.h" #include "xpcpublic.h" #include "nsIMozBrowserFrame.h" #include "nsDOMClassInfoID.h" #include "mozilla/EventDispatcher.h" #include "mozilla/dom/SameProcessMessageQueue.h" +#include "mozilla/dom/ScriptLoader.h" using namespace mozilla; using namespace mozilla::dom; diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index efea3ee40..605b1917f 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -37,7 +37,6 @@ #include "nsXPCOMCIDInternal.h" #include "nsIXULRuntime.h" #include "nsTextFormatter.h" -#include "ScriptSettings.h" #include "xpcpublic.h" @@ -55,6 +54,7 @@ #include "mozilla/dom/DOMException.h" #include "mozilla/dom/DOMExceptionBinding.h" #include "mozilla/dom/ErrorEvent.h" +#include "mozilla/dom/ScriptSettings.h" #include "nsAXPCNativeCallContext.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/Telemetry.h" diff --git a/dom/base/nsScriptElement.cpp b/dom/base/nsScriptElement.cpp deleted file mode 100644 index ebeb18f81..000000000 --- a/dom/base/nsScriptElement.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/* -*- 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 "nsScriptElement.h" -#include "mozilla/BasicEvents.h" -#include "mozilla/EventDispatcher.h" -#include "mozilla/dom/Element.h" -#include "nsContentUtils.h" -#include "nsPresContext.h" -#include "nsScriptLoader.h" -#include "nsIParser.h" -#include "nsGkAtoms.h" -#include "nsContentSink.h" - -using namespace mozilla; -using namespace mozilla::dom; - -NS_IMETHODIMP -nsScriptElement::ScriptAvailable(nsresult aResult, - nsIScriptElement *aElement, - bool aIsInline, - nsIURI *aURI, - int32_t aLineNo) -{ - if (!aIsInline && NS_FAILED(aResult)) { - nsCOMPtr parser = do_QueryReferent(mCreatorParser); - if (parser) { - parser->PushDefinedInsertionPoint(); - } - nsresult rv = FireErrorEvent(); - if (parser) { - parser->PopDefinedInsertionPoint(); - } - return rv; - } - return NS_OK; -} - -/* virtual */ nsresult -nsScriptElement::FireErrorEvent() -{ - nsCOMPtr cont = - do_QueryInterface((nsIScriptElement*) this); - - return nsContentUtils::DispatchTrustedEvent(cont->OwnerDoc(), - cont, - NS_LITERAL_STRING("error"), - false /* bubbles */, - false /* cancelable */); -} - -NS_IMETHODIMP -nsScriptElement::ScriptEvaluated(nsresult aResult, - nsIScriptElement *aElement, - bool aIsInline) -{ - nsresult rv = NS_OK; - if (!aIsInline) { - nsCOMPtr cont = - do_QueryInterface((nsIScriptElement*) this); - - RefPtr presContext = - nsContentUtils::GetContextForContent(cont); - - nsEventStatus status = nsEventStatus_eIgnore; - EventMessage message = NS_SUCCEEDED(aResult) ? eLoad : eLoadError; - WidgetEvent event(true, message); - // Load event doesn't bubble. - event.mFlags.mBubbles = (message != eLoad); - - EventDispatcher::Dispatch(cont, presContext, &event, nullptr, &status); - } - - return rv; -} - -void -nsScriptElement::CharacterDataChanged(nsIDocument *aDocument, - nsIContent* aContent, - CharacterDataChangeInfo* aInfo) -{ - MaybeProcessScript(); -} - -void -nsScriptElement::AttributeChanged(nsIDocument* aDocument, - Element* aElement, - int32_t aNameSpaceID, - nsIAtom* aAttribute, - int32_t aModType, - const nsAttrValue* aOldValue) -{ - MaybeProcessScript(); -} - -void -nsScriptElement::ContentAppended(nsIDocument* aDocument, - nsIContent* aContainer, - nsIContent* aFirstNewContent, - int32_t aNewIndexInContainer) -{ - MaybeProcessScript(); -} - -void -nsScriptElement::ContentInserted(nsIDocument *aDocument, - nsIContent* aContainer, - nsIContent* aChild, - int32_t aIndexInContainer) -{ - MaybeProcessScript(); -} - -bool -nsScriptElement::MaybeProcessScript() -{ - nsCOMPtr cont = - do_QueryInterface((nsIScriptElement*) this); - - NS_ASSERTION(cont->DebugGetSlots()->mMutationObservers.Contains(this), - "You forgot to add self as observer"); - - if (mAlreadyStarted || !mDoneAddingChildren || - !cont->GetComposedDoc() || mMalformed || !HasScriptContent()) { - return false; - } - - FreezeUriAsyncDefer(); - - mAlreadyStarted = true; - - nsIDocument* ownerDoc = cont->OwnerDoc(); - nsCOMPtr parser = ((nsIScriptElement*) this)->GetCreatorParser(); - if (parser) { - nsCOMPtr sink = parser->GetContentSink(); - if (sink) { - nsCOMPtr parserDoc = do_QueryInterface(sink->GetTarget()); - if (ownerDoc != parserDoc) { - // Willful violation of HTML5 as of 2010-12-01 - return false; - } - } - } - - RefPtr loader = ownerDoc->ScriptLoader(); - return loader->ProcessScriptElement(this); -} diff --git a/dom/base/nsScriptElement.h b/dom/base/nsScriptElement.h deleted file mode 100644 index 4a2a584ac..000000000 --- a/dom/base/nsScriptElement.h +++ /dev/null @@ -1,52 +0,0 @@ -/* -*- 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 nsScriptElement_h -#define nsScriptElement_h - -#include "mozilla/Attributes.h" -#include "nsIScriptLoaderObserver.h" -#include "nsIScriptElement.h" -#include "nsStubMutationObserver.h" - -/** - * Baseclass useful for script elements (such as and - * ). Currently the class assumes that only the 'src' - * attribute and the children of the class affect what script to execute. - */ - -class nsScriptElement : public nsIScriptElement, - public nsStubMutationObserver -{ -public: - // nsIScriptLoaderObserver - NS_DECL_NSISCRIPTLOADEROBSERVER - - // nsIMutationObserver - NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED - NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED - NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED - NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED - - explicit nsScriptElement(mozilla::dom::FromParser aFromParser) - : nsIScriptElement(aFromParser) - { - } - - virtual nsresult FireErrorEvent() override; - -protected: - // Internal methods - - /** - * Check if this element contains any script, linked or inline - */ - virtual bool HasScriptContent() = 0; - - virtual bool MaybeProcessScript() override; -}; - -#endif // nsScriptElement_h diff --git a/dom/base/nsScriptLoader.cpp b/dom/base/nsScriptLoader.cpp deleted file mode 100644 index 25482fe7b..000000000 --- a/dom/base/nsScriptLoader.cpp +++ /dev/null @@ -1,3061 +0,0 @@ -/* -*- 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/. */ - -/* - * A class that handles loading and evaluation of - + diff --git a/dom/base/test/jsmodules/test_syntaxErrorAsync.html b/dom/base/test/jsmodules/test_syntaxErrorAsync.html index 35d923755..3593d9dd7 100644 --- a/dom/base/test/jsmodules/test_syntaxErrorAsync.html +++ b/dom/base/test/jsmodules/test_syntaxErrorAsync.html @@ -4,20 +4,27 @@ - + diff --git a/dom/base/test/jsmodules/test_syntaxErrorInline.html b/dom/base/test/jsmodules/test_syntaxErrorInline.html index 705bc5902..b85b954ec 100644 --- a/dom/base/test/jsmodules/test_syntaxErrorInline.html +++ b/dom/base/test/jsmodules/test_syntaxErrorInline.html @@ -4,22 +4,29 @@ - -