summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CLOBBER2
-rw-r--r--config/external/nss/nss.symbols3
-rw-r--r--dom/abort/AbortController.cpp98
-rw-r--r--dom/abort/AbortController.h59
-rw-r--r--dom/abort/AbortSignal.cpp124
-rw-r--r--dom/abort/AbortSignal.h76
-rw-r--r--dom/abort/moz.build26
-rw-r--r--dom/abort/tests/file_abort_controller.html113
-rw-r--r--dom/abort/tests/mochitest.ini6
-rw-r--r--dom/abort/tests/moz.build8
-rw-r--r--dom/abort/tests/test_abort_controller.html40
-rw-r--r--dom/abort/tests/worker_abort_controller.js27
-rw-r--r--dom/base/CustomElementRegistry.cpp3
-rw-r--r--dom/base/ImportManager.cpp8
-rw-r--r--dom/base/ImportManager.h8
-rw-r--r--dom/base/Location.cpp2
-rwxr-xr-xdom/base/moz.build7
-rw-r--r--dom/base/nsContentPermissionHelper.cpp3
-rw-r--r--dom/base/nsContentSink.cpp2
-rw-r--r--dom/base/nsContentSink.h7
-rw-r--r--dom/base/nsDocument.cpp19
-rw-r--r--dom/base/nsDocument.h6
-rw-r--r--dom/base/nsFrameMessageManager.cpp8
-rw-r--r--dom/base/nsGkAtomList.h2
-rw-r--r--dom/base/nsGlobalWindow.cpp2
-rw-r--r--dom/base/nsIDocument.h4
-rw-r--r--dom/base/nsINode.cpp25
-rw-r--r--dom/base/nsINode.h10
-rw-r--r--dom/base/nsInProcessTabChildGlobal.cpp2
-rw-r--r--dom/base/nsJSEnvironment.cpp2
-rw-r--r--dom/base/nsJSUtils.cpp13
-rw-r--r--dom/base/nsJSUtils.h8
-rw-r--r--dom/base/nsPlainTextSerializer.h1
-rw-r--r--dom/base/test/jsmodules/test_syntaxError.html15
-rw-r--r--dom/base/test/jsmodules/test_syntaxErrorAsync.html15
-rw-r--r--dom/base/test/jsmodules/test_syntaxErrorInline.html15
-rw-r--r--dom/base/test/jsmodules/test_syntaxErrorInlineAsync.html15
-rwxr-xr-xdom/console/Console.cpp2
-rw-r--r--dom/events/DOMEventTargetHelper.cpp2
-rw-r--r--dom/fetch/Fetch.cpp289
-rw-r--r--dom/fetch/FetchDriver.cpp82
-rw-r--r--dom/fetch/FetchDriver.h31
-rw-r--r--dom/fetch/FetchObserver.cpp117
-rw-r--r--dom/fetch/FetchObserver.h54
-rw-r--r--dom/fetch/Request.h1
-rw-r--r--dom/fetch/moz.build2
-rw-r--r--dom/html/HTMLScriptElement.cpp2
-rw-r--r--dom/html/HTMLScriptElement.h7
-rw-r--r--dom/html/nsGenericHTMLElement.cpp2
-rw-r--r--dom/html/nsHTMLContentSink.cpp2
-rw-r--r--dom/media/MediaDecoderReader.h1
-rw-r--r--dom/media/webspeech/moz.build6
-rw-r--r--dom/messagechannel/MessagePort.cpp2
-rw-r--r--dom/moz.build2
-rw-r--r--dom/script/ModuleLoadRequest.cpp139
-rw-r--r--dom/script/ModuleLoadRequest.h87
-rw-r--r--dom/script/ModuleScript.cpp122
-rw-r--r--dom/script/ModuleScript.h53
-rw-r--r--dom/script/ScriptElement.cpp (renamed from dom/base/nsScriptElement.cpp)60
-rw-r--r--dom/script/ScriptElement.h (renamed from dom/base/nsScriptElement.h)18
-rw-r--r--dom/script/ScriptLoadHandler.cpp216
-rw-r--r--dom/script/ScriptLoadHandler.h76
-rw-r--r--dom/script/ScriptLoader.cpp (renamed from dom/base/nsScriptLoader.cpp)1121
-rw-r--r--dom/script/ScriptLoader.h (renamed from dom/base/nsScriptLoader.h)228
-rw-r--r--dom/script/ScriptSettings.cpp (renamed from dom/base/ScriptSettings.cpp)8
-rw-r--r--dom/script/ScriptSettings.h (renamed from dom/base/ScriptSettings.h)0
-rw-r--r--dom/script/moz.build36
-rw-r--r--dom/script/nsIScriptElement.h (renamed from dom/base/nsIScriptElement.h)0
-rw-r--r--dom/script/nsIScriptLoaderObserver.idl (renamed from dom/base/nsIScriptLoaderObserver.idl)0
-rw-r--r--dom/svg/SVGScriptElement.cpp4
-rw-r--r--dom/svg/SVGScriptElement.h6
-rw-r--r--dom/tests/mochitest/fetch/file_fetch_observer.html146
-rw-r--r--dom/tests/mochitest/fetch/mochitest.ini3
-rw-r--r--dom/tests/mochitest/fetch/slow.sjs11
-rw-r--r--dom/tests/mochitest/fetch/test_fetch_observer.html41
-rw-r--r--dom/tests/mochitest/webcomponents/mochitest.ini1
-rw-r--r--dom/tests/mochitest/webcomponents/test_bug1269155.html89
-rw-r--r--dom/webidl/AbortController.webidl13
-rw-r--r--dom/webidl/AbortSignal.webidl13
-rw-r--r--dom/webidl/FetchObserver.webidl27
-rw-r--r--dom/webidl/Node.webidl9
-rw-r--r--dom/webidl/Request.webidl6
-rw-r--r--dom/webidl/moz.build3
-rw-r--r--dom/workers/ScriptLoader.cpp20
-rw-r--r--dom/workers/ServiceWorkerManager.cpp2
-rw-r--r--dom/workers/ServiceWorkerScriptCache.cpp14
-rw-r--r--dom/workers/WorkerPrefs.h2
-rw-r--r--dom/worklet/Worklet.cpp8
-rw-r--r--dom/xml/nsXMLContentSink.cpp2
-rw-r--r--dom/xml/nsXMLFragmentContentSink.cpp2
-rw-r--r--dom/xslt/xslt/txMozillaXMLOutput.cpp8
-rw-r--r--dom/xul/XULDocument.cpp8
-rw-r--r--dom/xul/XULDocument.h2
-rw-r--r--dom/xul/nsXULContentSink.cpp2
-rw-r--r--editor/libeditor/EditorUtils.h113
-rw-r--r--editor/libeditor/HTMLEditRules.cpp346
-rw-r--r--editor/libeditor/HTMLEditRules.h66
-rw-r--r--image/encoders/jpeg/nsJPEGEncoder.cpp11
-rw-r--r--js/src/builtin/Module.js534
-rw-r--r--js/src/builtin/ModuleObject.cpp150
-rw-r--r--js/src/builtin/ModuleObject.h56
-rw-r--r--js/src/builtin/ReflectParse.cpp2
-rw-r--r--js/src/builtin/SelfHostingDefines.h18
-rw-r--r--js/src/builtin/Utilities.js19
-rw-r--r--js/src/frontend/BytecodeCompiler.cpp2
-rw-r--r--js/src/frontend/BytecodeEmitter.cpp2
-rw-r--r--js/src/frontend/Parser.cpp2
-rw-r--r--js/src/jit-test/tests/modules/ambiguous-star-export.js15
-rw-r--r--js/src/jit-test/tests/modules/bad-namespace-created.js17
-rw-r--r--js/src/jit-test/tests/modules/bug-1284486.js39
-rw-r--r--js/src/jit-test/tests/modules/bug-1287410.js2
-rw-r--r--js/src/jit-test/tests/modules/bug-1394492.js6
-rw-r--r--js/src/jit-test/tests/modules/global-scope.js18
-rw-r--r--js/src/jit-test/tests/modules/module-evaluation.js33
-rw-r--r--js/src/js.msg10
-rw-r--r--js/src/jsapi.cpp20
-rw-r--r--js/src/jsapi.h19
-rwxr-xr-xjs/src/jsdate.cpp19
-rw-r--r--js/src/vm/CommonPropertyNames.h4
-rw-r--r--js/src/vm/NativeObject.h5
-rw-r--r--js/src/vm/SelfHosting.cpp65
-rw-r--r--js/src/wasm/WasmBaselineCompile.cpp16
-rw-r--r--js/xpconnect/loader/mozJSSubScriptLoader.cpp12
-rw-r--r--js/xpconnect/src/XPCJSContext.cpp6
-rw-r--r--layout/build/moz.build4
-rw-r--r--layout/build/nsLayoutStatics.cpp7
-rw-r--r--mailnews/imap/src/nsImapProtocol.cpp54
-rw-r--r--media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.c7
-rw-r--r--media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.h2
-rw-r--r--media/mtransport/third_party/nICEr/src/ice/ice_component.c12
-rw-r--r--modules/libpref/init/all.js16
-rw-r--r--netwerk/base/nsStandardURL.cpp8
-rw-r--r--parser/html/nsHtml5DocumentBuilder.cpp2
-rw-r--r--parser/html/nsHtml5OplessBuilder.cpp2
-rw-r--r--parser/html/nsHtml5TreeOpExecutor.cpp2
-rw-r--r--parser/html/nsParserUtils.cpp4
-rw-r--r--parser/htmlparser/nsParser.cpp2
-rw-r--r--security/nss/coreconf/coreconf.dep1
-rw-r--r--security/nss/lib/freebl/mpi/mpi.c378
-rw-r--r--security/nss/lib/freebl/mpi/mpi.h1
-rw-r--r--security/nss/lib/freebl/mpi/mplogic.c45
-rw-r--r--security/nss/lib/nss/nss.h4
-rw-r--r--security/nss/lib/softoken/softkver.h4
-rw-r--r--security/nss/lib/util/nssutil.h4
-rw-r--r--testing/web-platform/meta/dom/historical.html.ini4
-rw-r--r--testing/web-platform/meta/dom/interfaces.html.ini57
-rw-r--r--testing/web-platform/meta/dom/nodes/rootNode.html.ini15
-rw-r--r--toolkit/content/jar.mn2
-rw-r--r--toolkit/content/widgets/popup.xml13
-rw-r--r--toolkit/content/xul.css9
-rwxr-xr-xtoolkit/mozapps/installer/windows/nsis/common.nsh35
-rw-r--r--uriloader/prefetch/nsOfflineCacheUpdate.cpp8
-rw-r--r--widget/gtk/moz.build11
-rw-r--r--widget/gtk/nsDbusmenu.cpp59
-rw-r--r--widget/gtk/nsDbusmenu.h97
-rw-r--r--widget/gtk/nsMenu.cpp800
-rw-r--r--widget/gtk/nsMenu.h120
-rw-r--r--widget/gtk/nsMenuBar.cpp541
-rw-r--r--widget/gtk/nsMenuBar.h103
-rw-r--r--widget/gtk/nsMenuContainer.cpp156
-rw-r--r--widget/gtk/nsMenuContainer.h66
-rw-r--r--widget/gtk/nsMenuItem.cpp712
-rw-r--r--widget/gtk/nsMenuItem.h77
-rw-r--r--widget/gtk/nsMenuObject.cpp634
-rw-r--r--widget/gtk/nsMenuObject.h165
-rw-r--r--widget/gtk/nsMenuSeparator.cpp74
-rw-r--r--widget/gtk/nsMenuSeparator.h33
-rw-r--r--widget/gtk/nsNativeMenuAtomList.h9
-rw-r--r--widget/gtk/nsNativeMenuAtoms.cpp35
-rw-r--r--widget/gtk/nsNativeMenuAtoms.h23
-rw-r--r--widget/gtk/nsNativeMenuDocListener.cpp329
-rw-r--r--widget/gtk/nsNativeMenuDocListener.h146
-rw-r--r--widget/gtk/nsNativeMenuService.cpp484
-rw-r--r--widget/gtk/nsNativeMenuService.h80
-rw-r--r--widget/gtk/nsWidgetFactory.cpp8
-rw-r--r--widget/gtk/nsWindow.cpp6
-rw-r--r--widget/gtk/nsWindow.h6
-rw-r--r--widget/moz.build4
-rw-r--r--xpfe/appshell/nsWebShellWindow.cpp2
179 files changed, 9276 insertions, 1827 deletions
diff --git a/CLOBBER b/CLOBBER
index 3337ea551..1f24ed9cd 100644
--- a/CLOBBER
+++ b/CLOBBER
@@ -22,5 +22,5 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
-Clobber for NSS Update
+Clobber for NSS update
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
diff --git a/dom/abort/AbortController.cpp b/dom/abort/AbortController.cpp
new file mode 100644
index 000000000..bd8159e7b
--- /dev/null
+++ b/dom/abort/AbortController.cpp
@@ -0,0 +1,98 @@
+/* -*- 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)
+
+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>
+AbortController::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
+{
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<AbortController> abortController = new AbortController(global);
+ return abortController.forget();
+}
+
+AbortController::AbortController(nsIGlobalObject* aGlobal)
+ : mGlobal(aGlobal)
+ , mAborted(false)
+{}
+
+JSObject*
+AbortController::WrapObject(JSContext* aCx, JS::Handle<JSObject*> 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();
+ }
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/abort/AbortController.h b/dom/abort/AbortController.h
new file mode 100644
index 000000000..0b99dc49c
--- /dev/null
+++ b/dom/abort/AbortController.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbortController)
+
+ static bool
+ IsEnabled(JSContext* aCx, JSObject* aGlobal);
+
+ static already_AddRefed<AbortController>
+ Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
+
+ explicit AbortController(nsIGlobalObject* aGlobal);
+
+ JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ nsIGlobalObject*
+ GetParentObject() const;
+
+ AbortSignal*
+ Signal();
+
+ void
+ Abort();
+
+private:
+ ~AbortController() = default;
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ RefPtr<AbortSignal> 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..20f36d2ab
--- /dev/null
+++ b/dom/abort/AbortSignal.cpp
@@ -0,0 +1,124 @@
+/* -*- 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<JSObject*> 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;
+
+ RefPtr<Event> 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);
+}
+
+// AbortSignal::Follower
+// ----------------------------------------------------------------------------
+
+AbortSignal::Follower::~Follower()
+{
+ Unfollow();
+}
+
+void
+AbortSignal::Follower::Follow(AbortSignal* aSignal)
+{
+ MOZ_DIAGNOSTIC_ASSERT(aSignal);
+
+ 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..35e582942
--- /dev/null
+++ b/dom/abort/AbortSignal.h
@@ -0,0 +1,76 @@
+/* -*- 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<AbortSignal> 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<JSObject*> aGivenProto) override;
+
+ bool
+ Aborted() const;
+
+ void
+ Abort();
+
+ IMPL_EVENT_HANDLER(abort);
+
+ void
+ AddFollower(Follower* aFollower);
+
+ void
+ RemoveFollower(Follower* aFollower);
+
+private:
+ ~AbortSignal() = default;
+
+ RefPtr<AbortController> mController;
+
+ // Raw pointers. Follower unregisters itself in the DTOR.
+ nsTArray<Follower*> mFollowers;
+
+ bool mAborted;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_AbortSignal_h
diff --git a/dom/abort/moz.build b/dom/abort/moz.build
new file mode 100644
index 000000000..cb48ee15f
--- /dev/null
+++ b/dom/abort/moz.build
@@ -0,0 +1,26 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM")
+
+TEST_DIRS += ['tests']
+
+EXPORTS.mozilla.dom += [
+ 'AbortController.h',
+ 'AbortSignal.h',
+]
+
+UNIFIED_SOURCES += [
+ 'AbortController.cpp',
+ 'AbortSignal.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '../workers',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/abort/tests/file_abort_controller.html b/dom/abort/tests/file_abort_controller.html
new file mode 100644
index 000000000..3a15fa346
--- /dev/null
+++ b/dom/abort/tests/file_abort_controller.html
@@ -0,0 +1,113 @@
+<script>
+function ok(a, msg) {
+ parent.postMessage({ type: "check", status: !!a, message: msg }, "*");
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+function testWebIDL() {
+ ok("FetchController" in self, "We have a FetchController prototype");
+ ok("FetchSignal" in self, "We have a FetchSignal prototype");
+
+ var fc = new FetchController();
+ ok(!!fc, "FetchController can be created");
+ ok(fc instanceof FetchController, "FetchController is a FetchController");
+
+ ok(!!fc.signal, "FetchController has a signal");
+ ok(fc.signal instanceof FetchSignal, "fetchSignal is a FetchSignal");
+ is(fc.signal.aborted, false, "By default FetchSignal.aborted is false");
+ 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 testAbortEvent() {
+ var fc = new FetchController();
+ fc.signal.onabort = function(e) {
+ is(e.type, "abort", "Abort received");
+ next();
+ }
+ fc.abort();
+}
+
+function testAbortedFetch() {
+ var fc = new FetchController();
+ fc.abort();
+
+ 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");
+ }).then(next);
+}
+
+function testFetchAndAbort() {
+ var fc = new FetchController();
+
+ var p = fetch('slow.sjs', { 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() {
+ 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() {
+ 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 = [
+ // Simple stuff
+ testWebIDL,
+ testUpdateData,
+
+ // Event propagation
+ testAbortEvent,
+
+ // fetch + signaling
+ testAbortedFetch,
+ testFetchAndAbort,
+ testWorkerAbortedFetch,
+ testWorkerFetchAndAbort,
+];
+
+function next() {
+ if (!steps.length) {
+ parent.postMessage({ type: "finish" }, "*");
+ return;
+ }
+
+ var step = steps.shift();
+ step();
+}
+
+next();
+
+</script>
diff --git a/dom/abort/tests/mochitest.ini b/dom/abort/tests/mochitest.ini
new file mode 100644
index 000000000..c8cc95fda
--- /dev/null
+++ b/dom/abort/tests/mochitest.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+ file_abort_controller.html
+ worker_fetch_controller.js
+
+[test_abort_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_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 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test FetchController</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+
+SpecialPowers.pushPrefEnv({"set": [["dom.fetchController.enabled", true ]]}, () => {
+ let ifr = document.createElement('iframe');
+ ifr.src = "file_fetch_controller.html";
+ document.body.appendChild(ifr);
+
+ onmessage = function(e) {
+ if (e.data.type == "finish") {
+ SimpleTest.finish();
+ return;
+ }
+
+ if (e.data.type == "check") {
+ ok(e.data.status, e.data.message);
+ return;
+ }
+
+ ok(false, "Something when wrong.");
+ }
+});
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
+
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/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<CustomElementRegistry>
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<ImportManager> 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<RefPtr<nsScriptLoader>> mBlockedScriptLoaders;
+ nsTArray<RefPtr<ScriptLoader>> 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/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; // <snicker>
+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<nsIDocShell> mDocShell;
RefPtr<mozilla::css::Loader> mCSSLoader;
RefPtr<nsNodeInfoManager> mNodeInfoManager;
- RefPtr<nsScriptLoader> mScriptLoader;
+ RefPtr<mozilla::dom::ScriptLoader> mScriptLoader;
// back off timer notification after count
int32_t mBackoffCount;
diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp
index 380593737..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;
@@ -5709,9 +5709,9 @@ nsDocument::IsWebComponentsEnabled(JSContext* aCx, JSObject* aObject)
{
JS::Rooted<JSObject*> 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<nsIPermissionManager> permMgr =
@@ -5753,7 +5754,7 @@ nsDocument::IsWebComponentsEnabled(nsPIDOMWindowInner* aWindow)
return perm == nsIPermissionManager::ALLOW_ACTION;
}
-
+*/
return false;
}
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<mozilla::EventListenerManager> mListenerManager;
RefPtr<mozilla::dom::StyleSheetList> mDOMStyleSheets;
RefPtr<nsDOMStyleSheetSetList> mStyleSheetSetList;
- RefPtr<nsScriptLoader> mScriptLoader;
+ RefPtr<mozilla::dom::ScriptLoader> 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/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/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/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<typename> 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/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/nsJSUtils.cpp b/dom/base/nsJSUtils.cpp
index c9cec96db..5c7e20424 100644
--- a/dom/base/nsJSUtils.cpp
+++ b/dom/base/nsJSUtils.cpp
@@ -306,17 +306,18 @@ nsJSUtils::CompileModule(JSContext* aCx,
}
nsresult
-nsJSUtils::ModuleDeclarationInstantiation(JSContext* aCx, JS::Handle<JSObject*> aModule)
+nsJSUtils::ModuleInstantiate(JSContext* aCx, JS::Handle<JSObject*> aModule)
{
- PROFILER_LABEL("nsJSUtils", "ModuleDeclarationInstantiation",
+ PROFILER_LABEL("nsJSUtils", "ModuleInstantiate",
js::ProfileEntry::Category::JS);
MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(nsContentUtils::IsInMicroTask());
NS_ENSURE_TRUE(xpc::Scriptability::Get(aModule).Allowed(), NS_OK);
- if (!JS::ModuleDeclarationInstantiation(aCx, aModule)) {
+ if (!JS::ModuleInstantiate(aCx, aModule)) {
return NS_ERROR_FAILURE;
}
@@ -324,9 +325,9 @@ nsJSUtils::ModuleDeclarationInstantiation(JSContext* aCx, JS::Handle<JSObject*>
}
nsresult
-nsJSUtils::ModuleEvaluation(JSContext* aCx, JS::Handle<JSObject*> aModule)
+nsJSUtils::ModuleEvaluate(JSContext* aCx, JS::Handle<JSObject*> aModule)
{
- PROFILER_LABEL("nsJSUtils", "ModuleEvaluation",
+ PROFILER_LABEL("nsJSUtils", "ModuleEvaluate",
js::ProfileEntry::Category::JS);
MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
@@ -335,7 +336,7 @@ nsJSUtils::ModuleEvaluation(JSContext* aCx, JS::Handle<JSObject*> aModule)
NS_ENSURE_TRUE(xpc::Scriptability::Get(aModule).Allowed(), NS_OK);
- if (!JS::ModuleEvaluation(aCx, aModule)) {
+ if (!JS::ModuleEvaluate(aCx, aModule)) {
return NS_ERROR_FAILURE;
}
diff --git a/dom/base/nsJSUtils.h b/dom/base/nsJSUtils.h
index 4affab2d3..9eea6ae83 100644
--- a/dom/base/nsJSUtils.h
+++ b/dom/base/nsJSUtils.h
@@ -116,11 +116,11 @@ public:
JS::CompileOptions &aCompileOptions,
JS::MutableHandle<JSObject*> aModule);
- static nsresult ModuleDeclarationInstantiation(JSContext* aCx,
- JS::Handle<JSObject*> aModule);
+ static nsresult ModuleInstantiate(JSContext* aCx,
+ JS::Handle<JSObject*> aModule);
- static nsresult ModuleEvaluation(JSContext* aCx,
- JS::Handle<JSObject*> aModule);
+ static nsresult ModuleEvaluate(JSContext* aCx,
+ JS::Handle<JSObject*> aModule);
// Returns false if an exception got thrown on aCx. Passing a null
// aElement is allowed; that wil produce an empty aScopeChain.
diff --git a/dom/base/nsPlainTextSerializer.h b/dom/base/nsPlainTextSerializer.h
index 650a8e3e7..58aeb4207 100644
--- a/dom/base/nsPlainTextSerializer.h
+++ b/dom/base/nsPlainTextSerializer.h
@@ -16,6 +16,7 @@
#include "mozilla/Attributes.h"
#include "nsCOMPtr.h"
#include "nsIAtom.h"
+#include "nsCycleCollectionParticipant.h"
#include "nsIContentSerializer.h"
#include "nsIDocumentEncoder.h"
#include "nsILineBreaker.h"
diff --git a/dom/base/test/jsmodules/test_syntaxError.html b/dom/base/test/jsmodules/test_syntaxError.html
index 53f95c96c..5bd688fe3 100644
--- a/dom/base/test/jsmodules/test_syntaxError.html
+++ b/dom/base/test/jsmodules/test_syntaxError.html
@@ -4,20 +4,27 @@
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script>
var wasRun = false;
- var hadSyntaxError = false;
+ var errorCount = 0;
+ var syntaxErrorCount = 0;
+ var eventCount = 0;
SimpleTest.waitForExplicitFinish();
window.onerror = handleError;
function handleError(message, url, line, column, error) {
- hadSyntaxError = error instanceof SyntaxError;
+ errorCount++;
+ if (error instanceof SyntaxError) {
+ syntaxErrorCount++;
+ }
}
function testError() {
ok(!wasRun, 'Check script was not run');
- ok(hadSyntaxError, 'Check that a SyntaxError was thrown');
+ ok(errorCount == 1, 'Check that an error was reported');
+ ok(syntaxErrorCount == 1, 'Check that a syntax error was reported');
+ ok(eventCount == 0, 'Check that no error event was fired');
SimpleTest.finish();
}
</script>
-<script type="module" src="module_badSyntax.js"></script>
+<script type="module" src="module_badSyntax.js" onerror="eventCount++"></script>
<body onload='testError()'></body>
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 @@
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script>
var wasRun = false;
- var hadSyntaxError = false;
+ var errorCount = 0;
+ var syntaxErrorCount = 0;
+ var eventCount = 0;
SimpleTest.waitForExplicitFinish();
window.onerror = handleError;
function handleError(message, url, line, column, error) {
- hadSyntaxError = error instanceof SyntaxError;
+ errorCount++;
+ if (error instanceof SyntaxError) {
+ syntaxErrorCount++;
+ }
}
function testError() {
ok(!wasRun, 'Check script was not run');
- ok(hadSyntaxError, 'Check that a SyntaxError was thrown');
+ ok(errorCount == 1, 'Check that an error was reported');
+ ok(syntaxErrorCount == 1, 'Check that a syntax error was reported');
+ ok(eventCount == 0, 'Check that no error event was fired');
SimpleTest.finish();
}
</script>
-<script type="module" src="module_badSyntax.js" async></script>
+<script type="module" src="module_badSyntax.js" async onerror="eventCount++"></script>
<body onload='testError()'></body>
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 @@
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script>
var wasRun = false;
- var hadSyntaxError = false;
+ var errorCount = 0;
+ var syntaxErrorCount = 0;
+ var eventCount = 0;
SimpleTest.waitForExplicitFinish();
window.onerror = handleError;
function handleError(message, url, line, column, error) {
- hadSyntaxError = error instanceof SyntaxError;
+ errorCount++;
+ if (error instanceof SyntaxError) {
+ syntaxErrorCount++;
+ }
}
function testError() {
ok(!wasRun, 'Check script was not run');
- ok(hadSyntaxError, 'Check that a SyntaxError was thrown');
+ ok(errorCount == 1, 'Check that an error was reported');
+ ok(syntaxErrorCount == 1, 'Check that a syntax error was reported');
+ ok(eventCount == 0, 'Check that no error event was fired');
SimpleTest.finish();
}
</script>
-<script type="module">
+<script type="module" onerror="eventCount++">
// Module with a syntax error.
some invalid js syntax;
wasRun = true;
diff --git a/dom/base/test/jsmodules/test_syntaxErrorInlineAsync.html b/dom/base/test/jsmodules/test_syntaxErrorInlineAsync.html
index 5e7992823..cc4bf1257 100644
--- a/dom/base/test/jsmodules/test_syntaxErrorInlineAsync.html
+++ b/dom/base/test/jsmodules/test_syntaxErrorInlineAsync.html
@@ -4,22 +4,29 @@
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script>
var wasRun = false;
- var hadSyntaxError = false;
+ var errorCount = 0;
+ var syntaxErrorCount = 0;
+ var eventCount = 0;
SimpleTest.waitForExplicitFinish();
window.onerror = handleError;
function handleError(message, url, line, column, error) {
- hadSyntaxError = error instanceof SyntaxError;
+ errorCount++;
+ if (error instanceof SyntaxError) {
+ syntaxErrorCount++;
+ }
}
function testError() {
ok(!wasRun, 'Check script was not run');
- ok(hadSyntaxError, 'Check that a SyntaxError was thrown');
+ ok(errorCount == 1, 'Check that an error was reported');
+ ok(syntaxErrorCount == 1, 'Check that a syntax error was reported');
+ ok(eventCount == 0, 'Check that no error event was fired');
SimpleTest.finish();
}
</script>
-<script type="module" async>
+<script type="module" async onerror="eventCount++">
// Module with a syntax error.
some invalid js syntax;
wasRun = true;
diff --git a/dom/console/Console.cpp b/dom/console/Console.cpp
index b174172e0..119a259fe 100755
--- a/dom/console/Console.cpp
+++ b/dom/console/Console.cpp
@@ -12,6 +12,7 @@
#include "mozilla/dom/File.h"
#include "mozilla/dom/FunctionBinding.h"
#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/StructuredCloneHolder.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/WorkletGlobalScope.h"
@@ -22,7 +23,6 @@
#include "nsGlobalWindow.h"
#include "nsJSUtils.h"
#include "nsNetUtil.h"
-#include "ScriptSettings.h"
#include "WorkerPrivate.h"
#include "WorkerRunnable.h"
#include "WorkerScope.h"
diff --git a/dom/events/DOMEventTargetHelper.cpp b/dom/events/DOMEventTargetHelper.cpp
index 7daf7f7a7..ea68ead9d 100644
--- a/dom/events/DOMEventTargetHelper.cpp
+++ b/dom/events/DOMEventTargetHelper.cpp
@@ -8,7 +8,7 @@
#include "nsIDocument.h"
#include "mozilla/Sprintf.h"
#include "nsGlobalWindow.h"
-#include "ScriptSettings.h"
+#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventListenerManager.h"
diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp
index f944352e3..191f4cfc3 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"
@@ -52,38 +53,141 @@ namespace dom {
using namespace workers;
+// 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<AbortSignal> mSignalMainThread;
+
+ // 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 AbortSignal on workers to the
+ // AbortSignal on main-thread.
+ class AbortSignalProxyRunnable final : public Runnable
+ {
+ RefPtr<AbortSignalProxy> mProxy;
+
+ public:
+ explicit AbortSignalProxyRunnable(AbortSignalProxy* aProxy)
+ : mProxy(aProxy)
+ {}
+
+ NS_IMETHOD
+ Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ AbortSignal* signal = mProxy->GetOrCreateSignalForMainThread();
+ signal->Abort();
+ return NS_OK;
+ }
+ };
+
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbortSignalProxy)
+
+ explicit AbortSignalProxy(AbortSignal* aSignal)
+ : mAborted(aSignal->Aborted())
+ {
+ Follow(aSignal);
+ }
+
+ void
+ Aborted() override
+ {
+ RefPtr<AbortSignalProxyRunnable> runnable =
+ new AbortSignalProxyRunnable(this);
+ NS_DispatchToMainThread(runnable);
+ }
+
+ AbortSignal*
+ GetOrCreateSignalForMainThread()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mSignalMainThread) {
+ mSignalMainThread = new AbortSignal(mAborted);
+ }
+ return mSignalMainThread;
+ }
+
+ void
+ Shutdown()
+ {
+ Unfollow();
+ }
+
+private:
+ ~AbortSignalProxy()
+ {
+ NS_ReleaseOnMainThread(mSignalMainThread.forget());
+ }
+};
+
class WorkerFetchResolver final : public FetchDriverObserver
{
friend class MainThreadFetchRunnable;
+ friend class WorkerDataAvailableRunnable;
+ friend class WorkerFetchResponseEndBase;
friend class WorkerFetchResponseEndRunnable;
friend class WorkerFetchResponseRunnable;
RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ RefPtr<AbortSignalProxy> mSignalProxy;
+ RefPtr<FetchObserver> mFetchObserver;
+
public:
// Returns null if worker is shutting down.
static already_AddRefed<WorkerFetchResolver>
- Create(workers::WorkerPrivate* aWorkerPrivate, Promise* aPromise)
+ Create(workers::WorkerPrivate* aWorkerPrivate, Promise* aPromise,
+ AbortSignal* aSignal, FetchObserver* aObserver)
{
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
- RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(aWorkerPrivate, aPromise);
+ RefPtr<PromiseWorkerProxy> proxy =
+ PromiseWorkerProxy::Create(aWorkerPrivate, aPromise);
if (!proxy) {
return nullptr;
}
- RefPtr<WorkerFetchResolver> r = new WorkerFetchResolver(proxy);
+ RefPtr<AbortSignalProxy> signalProxy;
+ if (aSignal) {
+ signalProxy = new AbortSignalProxy(aSignal);
+ }
+
+ RefPtr<WorkerFetchResolver> r =
+ new WorkerFetchResolver(proxy, signalProxy, aObserver);
return r.forget();
}
+ AbortSignal*
+ GetAbortSignal()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mSignalProxy) {
+ return nullptr;
+ }
+
+ return mSignalProxy->GetOrCreateSignalForMainThread();
+ }
+
void
OnResponseAvailableInternal(InternalResponse* aResponse) override;
void
- OnResponseEnd() override;
+ OnResponseEnd(FetchDriverObserver::EndReason eReason) override;
+
+ void
+ OnDataAvailable() override;
private:
- explicit WorkerFetchResolver(PromiseWorkerProxy* aProxy)
+ WorkerFetchResolver(PromiseWorkerProxy* aProxy,
+ AbortSignalProxy* aSignalProxy,
+ FetchObserver* aObserver)
: mPromiseProxy(aProxy)
+ , mSignalProxy(aSignalProxy)
+ , mFetchObserver(aObserver)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(mPromiseProxy);
@@ -100,12 +204,16 @@ class MainThreadFetchResolver final : public FetchDriverObserver
{
RefPtr<Promise> mPromise;
RefPtr<Response> mResponse;
+ RefPtr<FetchObserver> mFetchObserver;
nsCOMPtr<nsIDocument> mDocument;
NS_DECL_OWNINGTHREAD
public:
- explicit MainThreadFetchResolver(Promise* aPromise);
+ MainThreadFetchResolver(Promise* aPromise, FetchObserver* aObserver)
+ : mPromise(aPromise)
+ , mFetchObserver(aObserver)
+ {}
void
OnResponseAvailableInternal(InternalResponse* aResponse) override;
@@ -115,11 +223,20 @@ public:
mDocument = aDocument;
}
- virtual void OnResponseEnd() override
+ void OnResponseEnd(FetchDriverObserver::EndReason aReason) override
{
+ if (aReason == eAborted) {
+ mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ mFetchObserver = nullptr;
+
FlushConsoleReport();
}
+ void
+ OnDataAvailable() override;
+
private:
~MainThreadFetchResolver();
@@ -170,9 +287,11 @@ public:
fetch->SetWorkerScript(spec);
}
+ RefPtr<AbortSignal> signal = mResolver->GetAbortSignal();
+
// ...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 +329,23 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
RefPtr<InternalRequest> r = request->GetInternalRequest();
+ RefPtr<AbortSignal> signal;
+ if (aInit.mSignal.WasPassed()) {
+ signal = &aInit.mSignal.Value();
+ }
+
+ if (signal && signal->Aborted()) {
+ // An already aborted signal should reject immediately.
+ aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+ return nullptr;
+ }
+
+ RefPtr<FetchObserver> observer;
+ if (aInit.mObserve.WasPassed()) {
+ observer = new FetchObserver(aGlobal, signal);
+ aInit.mObserve.Value().HandleEvent(*observer);
+ }
+
if (NS_IsMainThread()) {
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
nsCOMPtr<nsIDocument> doc;
@@ -236,11 +372,12 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
}
}
- RefPtr<MainThreadFetchResolver> resolver = new MainThreadFetchResolver(p);
+ RefPtr<MainThreadFetchResolver> resolver =
+ new MainThreadFetchResolver(p, observer);
RefPtr<FetchDriver> 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 +389,8 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
r->SetSkipServiceWorker();
}
- RefPtr<WorkerFetchResolver> resolver = WorkerFetchResolver::Create(worker, p);
+ RefPtr<WorkerFetchResolver> 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);
@@ -266,11 +404,6 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
return p.forget();
}
-MainThreadFetchResolver::MainThreadFetchResolver(Promise* aPromise)
- : mPromise(aPromise)
-{
-}
-
void
MainThreadFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse)
{
@@ -278,16 +411,39 @@ MainThreadFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse
AssertIsOnMainThread();
if (aResponse->Type() != ResponseType::Error) {
+ if (mFetchObserver) {
+ mFetchObserver->SetState(FetchState::Complete);
+ }
+
nsCOMPtr<nsIGlobalObject> go = mPromise->GetParentObject();
mResponse = new Response(go, aResponse);
mPromise->MaybeResolve(mResponse);
} else {
+ if (mFetchObserver) {
+ mFetchObserver->SetState(FetchState::Errored);
+ }
+
ErrorResult result;
result.ThrowTypeError<MSG_FETCH_FAILED>();
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);
@@ -306,6 +462,7 @@ public:
, mResolver(aResolver)
, mInternalResponse(aResponse)
{
+ MOZ_ASSERT(mResolver);
}
bool
@@ -317,10 +474,18 @@ public:
RefPtr<Promise> promise = mResolver->mPromiseProxy->WorkerPromise();
if (mInternalResponse->Type() != ResponseType::Error) {
+ if (mResolver->mFetchObserver) {
+ mResolver->mFetchObserver->SetState(FetchState::Complete);
+ }
+
RefPtr<nsIGlobalObject> global = aWorkerPrivate->GlobalScope();
RefPtr<Response> response = new Response(global, mInternalResponse);
promise->MaybeResolve(response);
} else {
+ if (mResolver->mFetchObserver) {
+ mResolver->mFetchObserver->SetState(FetchState::Errored);
+ }
+
ErrorResult result;
result.ThrowTypeError<MSG_FETCH_FAILED>();
promise->MaybeReject(result);
@@ -329,14 +494,42 @@ public:
}
};
+class WorkerDataAvailableRunnable final : public MainThreadWorkerRunnable
+{
+ RefPtr<WorkerFetchResolver> 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<PromiseWorkerProxy> mPromiseProxy;
+protected:
+ RefPtr<WorkerFetchResolver> mResolver;
+
public:
- explicit WorkerFetchResponseEndBase(PromiseWorkerProxy* aPromiseProxy)
- : mPromiseProxy(aPromiseProxy)
+ explicit WorkerFetchResponseEndBase(WorkerFetchResolver* aResolver)
+ : mResolver(aResolver)
{
- MOZ_ASSERT(mPromiseProxy);
+ MOZ_ASSERT(aResolver);
}
void
@@ -344,23 +537,41 @@ public:
{
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
- mPromiseProxy->CleanUp();
+
+ mResolver->mPromiseProxy->CleanUp();
+
+ mResolver->mFetchObserver = nullptr;
+
+ if (mResolver->mSignalProxy) {
+ mResolver->mSignalProxy->Shutdown();
+ mResolver->mSignalProxy = nullptr;
+ }
}
};
class WorkerFetchResponseEndRunnable final : public MainThreadWorkerRunnable
, public WorkerFetchResponseEndBase
{
+ FetchDriverObserver::EndReason mReason;
+
public:
- explicit WorkerFetchResponseEndRunnable(PromiseWorkerProxy* aPromiseProxy)
- : MainThreadWorkerRunnable(aPromiseProxy->GetWorkerPrivate())
- , WorkerFetchResponseEndBase(aPromiseProxy)
+ 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> promise = mResolver->mPromiseProxy->WorkerPromise();
+ promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ }
+
WorkerRunInternal(aWorkerPrivate);
return true;
}
@@ -379,9 +590,10 @@ class WorkerFetchResponseEndControlRunnable final : public MainThreadWorkerContr
, public WorkerFetchResponseEndBase
{
public:
- explicit WorkerFetchResponseEndControlRunnable(PromiseWorkerProxy* aPromiseProxy)
- : MainThreadWorkerControlRunnable(aPromiseProxy->GetWorkerPrivate())
- , WorkerFetchResponseEndBase(aPromiseProxy)
+ WorkerFetchResponseEndControlRunnable(WorkerPrivate* aWorkerPrivate,
+ WorkerFetchResolver* aResolver)
+ : MainThreadWorkerControlRunnable(aWorkerPrivate)
+ , WorkerFetchResponseEndBase(aResolver)
{
}
@@ -415,7 +627,22 @@ WorkerFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse)
}
void
-WorkerFetchResolver::OnResponseEnd()
+WorkerFetchResolver::OnDataAvailable()
+{
+ AssertIsOnMainThread();
+
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ if (mPromiseProxy->CleanedUp()) {
+ return;
+ }
+
+ RefPtr<WorkerDataAvailableRunnable> r =
+ new WorkerDataAvailableRunnable(mPromiseProxy->GetWorkerPrivate(), this);
+ Unused << r->Dispatch();
+}
+
+void
+WorkerFetchResolver::OnResponseEnd(FetchDriverObserver::EndReason aReason)
{
AssertIsOnMainThread();
MutexAutoLock lock(mPromiseProxy->Lock());
@@ -426,11 +653,13 @@ WorkerFetchResolver::OnResponseEnd()
FlushConsoleReport();
RefPtr<WorkerFetchResponseEndRunnable> r =
- new WorkerFetchResponseEndRunnable(mPromiseProxy);
+ new WorkerFetchResponseEndRunnable(mPromiseProxy->GetWorkerPrivate(),
+ this, aReason);
if (!r->Dispatch()) {
RefPtr<WorkerFetchResponseEndControlRunnable> cr =
- new WorkerFetchResponseEndControlRunnable(mPromiseProxy);
+ 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 6294b0dc5..067e32db4 100644
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -67,7 +67,7 @@ FetchDriver::~FetchDriver()
}
nsresult
-FetchDriver::Fetch(FetchDriverObserver* aObserver)
+FetchDriver::Fetch(AbortSignal* 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<nsIURI> 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<InternalResponse>
@@ -433,9 +443,11 @@ FetchDriver::FailWithNetworkError()
#ifdef DEBUG
mResponseAvailableCalled = true;
#endif
- mObserver->OnResponseEnd();
+ mObserver->OnResponseEnd(FetchDriverObserver::eByNetworking);
mObserver = nullptr;
}
+
+ mChannel = nullptr;
}
namespace {
@@ -655,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<FetchDriverObserver> 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,
@@ -666,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> 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);
@@ -777,10 +826,11 @@ FetchDriver::OnStopRequest(nsIRequest* aRequest,
#endif
}
- mObserver->OnResponseEnd();
+ mObserver->OnResponseEnd(FetchDriverObserver::eByNetworking);
mObserver = nullptr;
}
+ mChannel = nullptr;
return NS_OK;
}
@@ -921,5 +971,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..57dffa5a7 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/AbortSignal.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,9 @@ public:
}
virtual void FlushConsoleReport() = 0;
+
+ virtual void OnDataAvailable() = 0;
+
protected:
virtual ~FetchDriverObserver()
{ };
@@ -72,7 +83,8 @@ private:
class FetchDriver final : public nsIStreamListener,
public nsIChannelEventSink,
public nsIInterfaceRequestor,
- public nsIThreadRetargetableStreamListener
+ public nsIThreadRetargetableStreamListener,
+ public AbortSignal::Follower
{
public:
NS_DECL_ISUPPORTS
@@ -82,9 +94,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(AbortSignal* aSignal,
+ FetchDriverObserver* aObserver);
void
SetDocument(nsIDocument* aDocument);
@@ -96,6 +111,11 @@ public:
mWorkerScript = aWorkerScirpt;
}
+ // AbortSignal::Follower
+
+ void
+ Aborted() override;
+
private:
nsCOMPtr<nsIPrincipal> mPrincipal;
nsCOMPtr<nsILoadGroup> mLoadGroup;
@@ -104,6 +124,7 @@ private:
nsCOMPtr<nsIOutputStream> mPipeOutputStream;
RefPtr<FetchDriverObserver> mObserver;
nsCOMPtr<nsIDocument> mDocument;
+ nsCOMPtr<nsIChannel> mChannel;
nsAutoPtr<SRICheckDataVerifier> mSRIDataVerifier;
SRIMetadata mSRIMetadata;
nsCString mWorkerScript;
diff --git a/dom/fetch/FetchObserver.cpp b/dom/fetch/FetchObserver.cpp
new file mode 100644
index 000000000..93d93773f
--- /dev/null
+++ b/dom/fetch/FetchObserver.cpp
@@ -0,0 +1,117 @@
+/* -*- 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"
+#include "mozilla/dom/Event.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,
+ AbortSignal* aSignal)
+ : DOMEventTargetHelper(aGlobal)
+ , mState(FetchState::Requesting)
+{
+ if (aSignal) {
+ Follow(aSignal);
+ }
+}
+
+JSObject*
+FetchObserver::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return FetchObserverBinding::Wrap(aCx, this, aGivenProto);
+}
+
+FetchState
+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 =
+ 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
new file mode 100644
index 000000000..5cd835b3d
--- /dev/null
+++ b/dom/fetch/FetchObserver.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FetchObserver_h
+#define mozilla_dom_FetchObserver_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/FetchObserverBinding.h"
+#include "mozilla/dom/AbortSignal.h"
+
+namespace mozilla {
+namespace dom {
+
+class FetchObserver final : public DOMEventTargetHelper
+ , public AbortSignal::Follower
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchObserver, DOMEventTargetHelper)
+
+ static bool
+ IsEnabled(JSContext* aCx, JSObject* aGlobal);
+
+ FetchObserver(nsIGlobalObject* aGlobal, AbortSignal* aSignal);
+
+ JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ FetchState
+ State() const;
+
+ IMPL_EVENT_HANDLER(statechange);
+ IMPL_EVENT_HANDLER(requestprogress);
+ IMPL_EVENT_HANDLER(responseprogress);
+
+ void
+ Aborted() override;
+
+ void
+ SetState(FetchState aState);
+
+private:
+ ~FetchObserver() = default;
+
+ FetchState mState;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_FetchObserver_h
diff --git a/dom/fetch/Request.h b/dom/fetch/Request.h
index d33c74812..f6fe9be7b 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/AbortSignal.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..e2b466428 100644
--- a/dom/fetch/moz.build
+++ b/dom/fetch/moz.build
@@ -9,6 +9,7 @@ EXPORTS.mozilla.dom += [
'Fetch.h',
'FetchDriver.h',
'FetchIPCTypes.h',
+ 'FetchObserver.h',
'FetchUtil.h',
'Headers.h',
'InternalHeaders.h',
@@ -28,6 +29,7 @@ UNIFIED_SOURCES += [
SOURCES += [
'ChannelInfo.cpp',
'FetchDriver.cpp',
+ 'FetchObserver.cpp',
'FetchUtil.cpp',
'Headers.cpp',
'InternalHeaders.cpp',
diff --git a/dom/html/HTMLScriptElement.cpp b/dom/html/HTMLScriptElement.cpp
index 095b9b77d..ddeb925eb 100644
--- a/dom/html/HTMLScriptElement.cpp
+++ b/dom/html/HTMLScriptElement.cpp
@@ -37,7 +37,7 @@ HTMLScriptElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
HTMLScriptElement::HTMLScriptElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
FromParser aFromParser)
: nsGenericHTMLElement(aNodeInfo)
- , nsScriptElement(aFromParser)
+ , ScriptElement(aFromParser)
{
AddMutationObserver(this);
}
diff --git a/dom/html/HTMLScriptElement.h b/dom/html/HTMLScriptElement.h
index 19ceb414f..073cf7faf 100644
--- a/dom/html/HTMLScriptElement.h
+++ b/dom/html/HTMLScriptElement.h
@@ -8,16 +8,16 @@
#define mozilla_dom_HTMLScriptElement_h
#include "nsIDOMHTMLScriptElement.h"
-#include "nsScriptElement.h"
#include "nsGenericHTMLElement.h"
#include "mozilla/Attributes.h"
+#include "mozilla/dom/ScriptElement.h"
namespace mozilla {
namespace dom {
class HTMLScriptElement final : public nsGenericHTMLElement,
public nsIDOMHTMLScriptElement,
- public nsScriptElement
+ public ScriptElement
{
public:
using Element::GetText;
@@ -96,7 +96,8 @@ protected:
virtual ~HTMLScriptElement();
virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
- // nsScriptElement
+
+ // ScriptElement
virtual bool HasScriptContent() override;
};
diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp
index 1e2f1c186..ef077cfb2 100644
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -48,7 +48,6 @@
#include "nsIDocShell.h"
#include "nsNameSpaceManager.h"
#include "nsError.h"
-#include "nsScriptLoader.h"
#include "nsRuleData.h"
#include "nsIPrincipal.h"
#include "nsContainerFrame.h"
@@ -92,6 +91,7 @@
#include "mozilla/dom/FromParser.h"
#include "mozilla/dom/Link.h"
#include "mozilla/BloomFilter.h"
+#include "mozilla/dom/ScriptLoader.h"
#include "nsVariant.h"
#include "nsDOMTokenList.h"
diff --git a/dom/html/nsHTMLContentSink.cpp b/dom/html/nsHTMLContentSink.cpp
index 3e8e019b8..409b225ef 100644
--- a/dom/html/nsHTMLContentSink.cpp
+++ b/dom/html/nsHTMLContentSink.cpp
@@ -19,10 +19,10 @@
#include "nsIHTMLContentSink.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
-#include "nsScriptLoader.h"
#include "nsIURI.h"
#include "nsIContentViewer.h"
#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/ScriptLoader.h"
#include "nsToken.h"
#include "nsIAppShell.h"
#include "nsCRT.h"
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"
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
diff --git a/dom/messagechannel/MessagePort.cpp b/dom/messagechannel/MessagePort.cpp
index 56204da99..fcbe36a72 100644
--- a/dom/messagechannel/MessagePort.cpp
+++ b/dom/messagechannel/MessagePort.cpp
@@ -16,6 +16,7 @@
#include "mozilla/dom/MessagePortBinding.h"
#include "mozilla/dom/MessagePortChild.h"
#include "mozilla/dom/PMessagePort.h"
+#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/StructuredCloneTags.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerScope.h"
@@ -28,7 +29,6 @@
#include "nsContentUtils.h"
#include "nsGlobalWindow.h"
#include "nsPresContext.h"
-#include "ScriptSettings.h"
#include "SharedMessagePortMessage.h"
#include "nsIBFCacheEntry.h"
diff --git a/dom/moz.build b/dom/moz.build
index 89c539b4b..7888ccd69 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',
@@ -99,6 +100,7 @@ DIRS += [
'performance',
'xhr',
'worklet',
+ 'script',
]
if CONFIG['OS_ARCH'] == 'WINNT':
diff --git a/dom/script/ModuleLoadRequest.cpp b/dom/script/ModuleLoadRequest.cpp
new file mode 100644
index 000000000..d62214304
--- /dev/null
+++ b/dom/script/ModuleLoadRequest.cpp
@@ -0,0 +1,139 @@
+/* -*- 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 "ModuleLoadRequest.h"
+#include "ModuleScript.h"
+#include "ScriptLoader.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ModuleLoadRequest)
+NS_INTERFACE_MAP_END_INHERITING(ScriptLoadRequest)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ModuleLoadRequest, ScriptLoadRequest,
+ mBaseURL,
+ mLoader,
+ mParent,
+ mModuleScript,
+ mImports)
+
+NS_IMPL_ADDREF_INHERITED(ModuleLoadRequest, ScriptLoadRequest)
+NS_IMPL_RELEASE_INHERITED(ModuleLoadRequest, ScriptLoadRequest)
+
+ModuleLoadRequest::ModuleLoadRequest(nsIScriptElement* aElement,
+ uint32_t aVersion,
+ CORSMode aCORSMode,
+ const SRIMetadata &aIntegrity,
+ ScriptLoader* aLoader)
+ : ScriptLoadRequest(ScriptKind::Module,
+ aElement,
+ aVersion,
+ aCORSMode,
+ aIntegrity),
+ mIsTopLevel(true),
+ mLoader(aLoader)
+{}
+
+void ModuleLoadRequest::Cancel()
+{
+ ScriptLoadRequest::Cancel();
+ mModuleScript = nullptr;
+ mProgress = ScriptLoadRequest::Progress::Ready;
+ CancelImports();
+ mReady.RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__);
+}
+
+void
+ModuleLoadRequest::CancelImports()
+{
+ for (size_t i = 0; i < mImports.Length(); i++) {
+ mImports[i]->Cancel();
+ }
+}
+
+void
+ModuleLoadRequest::SetReady()
+{
+ // Mark a module as ready to execute. This means that this module and all it
+ // dependencies have had their source loaded, parsed as a module and the
+ // modules instantiated.
+ //
+ // The mReady promise is used to ensure that when all dependencies of a module
+ // have become ready, DependenciesLoaded is called on that module
+ // request. This is set up in StartFetchingModuleDependencies.
+
+#ifdef DEBUG
+ for (size_t i = 0; i < mImports.Length(); i++) {
+ MOZ_ASSERT(mImports[i]->IsReadyToRun());
+ }
+#endif
+
+ ScriptLoadRequest::SetReady();
+ mReady.ResolveIfExists(true, __func__);
+}
+
+void
+ModuleLoadRequest::ModuleLoaded()
+{
+ // A module that was found to be marked as fetching in the module map has now
+ // been loaded.
+
+ mModuleScript = mLoader->GetFetchedModule(mURI);
+ if (!mModuleScript || mModuleScript->IsErrored()) {
+ ModuleErrored();
+ return;
+ }
+
+ mLoader->StartFetchingModuleDependencies(this);
+}
+
+void
+ModuleLoadRequest::ModuleErrored()
+{
+ mLoader->CheckModuleDependenciesLoaded(this);
+ MOZ_ASSERT(!mModuleScript || mModuleScript->IsErrored());
+
+ CancelImports();
+ SetReady();
+ LoadFinished();
+}
+
+void
+ModuleLoadRequest::DependenciesLoaded()
+{
+ // The module and all of its dependencies have been successfully fetched and
+ // compiled.
+
+ MOZ_ASSERT(mModuleScript);
+
+ mLoader->CheckModuleDependenciesLoaded(this);
+ SetReady();
+ LoadFinished();
+}
+
+void
+ModuleLoadRequest::LoadFailed()
+{
+ // We failed to load the source text or an error occurred unrelated to the
+ // content of the module (e.g. OOM).
+
+ MOZ_ASSERT(!mModuleScript);
+
+ Cancel();
+ LoadFinished();
+}
+
+void
+ModuleLoadRequest::LoadFinished()
+{
+ mLoader->ProcessLoadedModuleTree(this);
+ mLoader = nullptr;
+ mParent = nullptr;
+}
+
+} // dom namespace
+} // mozilla namespace \ No newline at end of file
diff --git a/dom/script/ModuleLoadRequest.h b/dom/script/ModuleLoadRequest.h
new file mode 100644
index 000000000..7b06dd2cf
--- /dev/null
+++ b/dom/script/ModuleLoadRequest.h
@@ -0,0 +1,87 @@
+/* -*- 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_ModuleLoadRequest_h
+#define mozilla_dom_ModuleLoadRequest_h
+
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/MozPromise.h"
+
+namespace mozilla {
+namespace dom {
+
+class ModuleScript;
+class ScriptLoader;
+
+// A load request for a module, created for every top level module script and
+// every module import. Load request can share a ModuleScript if there are
+// multiple imports of the same module.
+
+class ModuleLoadRequest final : public ScriptLoadRequest
+{
+ ~ModuleLoadRequest() {}
+
+ ModuleLoadRequest(const ModuleLoadRequest& aOther) = delete;
+ ModuleLoadRequest(ModuleLoadRequest&& aOther) = delete;
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ModuleLoadRequest, ScriptLoadRequest)
+
+ ModuleLoadRequest(nsIScriptElement* aElement,
+ uint32_t aVersion,
+ CORSMode aCORSMode,
+ const SRIMetadata& aIntegrity,
+ ScriptLoader* aLoader);
+
+ bool IsTopLevel() const {
+ return mIsTopLevel;
+ }
+
+ void SetReady() override;
+ void Cancel() override;
+
+ void ModuleLoaded();
+ void ModuleErrored();
+ void DependenciesLoaded();
+ void LoadFailed();
+
+private:
+ void LoadFinished();
+ void CancelImports();
+
+public:
+ // Is this a request for a top level module script or an import?
+ bool mIsTopLevel;
+
+ // The base URL used for resolving relative module imports.
+ nsCOMPtr<nsIURI> mBaseURL;
+
+ // Pointer to the script loader, used to trigger actions when the module load
+ // finishes.
+ RefPtr<ScriptLoader> mLoader;
+
+ // The importing module, or nullptr for top level module scripts. Used to
+ // implement the ancestor list checked when fetching module dependencies.
+ RefPtr<ModuleLoadRequest> mParent;
+
+ // Set to a module script object after a successful load or nullptr on
+ // failure.
+ RefPtr<ModuleScript> mModuleScript;
+
+ // A promise that is completed on successful load of this module and all of
+ // its dependencies, indicating that the module is ready for instantiation and
+ // evaluation.
+ MozPromiseHolder<GenericPromise> mReady;
+
+ // Array of imported modules.
+ nsTArray<RefPtr<ModuleLoadRequest>> mImports;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_ModuleLoadRequest_h \ No newline at end of file
diff --git a/dom/script/ModuleScript.cpp b/dom/script/ModuleScript.cpp
new file mode 100644
index 000000000..28b97a3cb
--- /dev/null
+++ b/dom/script/ModuleScript.cpp
@@ -0,0 +1,122 @@
+/* -*- 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 <script> elements.
+ */
+
+#include "ModuleScript.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "ScriptLoader.h"
+
+namespace mozilla {
+namespace dom {
+
+// A single module script. May be used to satisfy multiple load requests.
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleScript)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ModuleScript)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ModuleScript)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoader)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mBaseURL)
+ tmp->UnlinkModuleRecord();
+ tmp->mError.setUndefined();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ModuleScript)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoader)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ModuleScript)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mModuleRecord)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mError)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleScript)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleScript)
+
+ModuleScript::ModuleScript(ScriptLoader *aLoader, nsIURI* aBaseURL)
+ : mLoader(aLoader),
+ mBaseURL(aBaseURL)
+{
+ MOZ_ASSERT(mLoader);
+ MOZ_ASSERT(mBaseURL);
+ MOZ_ASSERT(!mModuleRecord);
+ MOZ_ASSERT(mError.isUndefined());
+}
+
+void
+ModuleScript::UnlinkModuleRecord()
+{
+ // Remove module's back reference to this object request if present.
+ if (mModuleRecord) {
+ MOZ_ASSERT(JS::GetModuleHostDefinedField(mModuleRecord).toPrivate() ==
+ this);
+ JS::SetModuleHostDefinedField(mModuleRecord, JS::UndefinedValue());
+ mModuleRecord = nullptr;
+ }
+}
+
+ModuleScript::~ModuleScript()
+{
+ // The object may be destroyed without being unlinked first.
+ UnlinkModuleRecord();
+ DropJSObjects(this);
+}
+
+void
+ModuleScript::SetModuleRecord(JS::Handle<JSObject*> aModuleRecord)
+{
+ MOZ_ASSERT(!mModuleRecord);
+ MOZ_ASSERT(mError.isUndefined());
+
+ mModuleRecord = aModuleRecord;
+
+ // Make module's host defined field point to this module script object.
+ // This is cleared in the UnlinkModuleRecord().
+ JS::SetModuleHostDefinedField(mModuleRecord, JS::PrivateValue(this));
+ HoldJSObjects(this);
+}
+
+void
+ModuleScript::SetPreInstantiationError(const JS::Value& aError)
+{
+ MOZ_ASSERT(!aError.isUndefined());
+
+ UnlinkModuleRecord();
+ mError = aError;
+
+ HoldJSObjects(this);
+}
+
+bool
+ModuleScript::IsErrored() const
+{
+ if (!mModuleRecord) {
+ MOZ_ASSERT(!mError.isUndefined());
+ return true;
+ }
+
+ return JS::IsModuleErrored(mModuleRecord);
+}
+
+JS::Value
+ModuleScript::Error() const
+{
+ MOZ_ASSERT(IsErrored());
+
+ if (!mModuleRecord) {
+ return mError;
+ }
+
+ return JS::GetModuleError(mModuleRecord);
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/script/ModuleScript.h b/dom/script/ModuleScript.h
new file mode 100644
index 000000000..571359859
--- /dev/null
+++ b/dom/script/ModuleScript.h
@@ -0,0 +1,53 @@
+/* -*- 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_ModuleScript_h
+#define mozilla_dom_ModuleScript_h
+
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "jsapi.h"
+
+class nsIURI;
+
+namespace mozilla {
+namespace dom {
+
+class ScriptLoader;
+
+class ModuleScript final : public nsISupports
+{
+ RefPtr<ScriptLoader> mLoader;
+ nsCOMPtr<nsIURI> mBaseURL;
+ JS::Heap<JSObject*> mModuleRecord;
+ JS::Heap<JS::Value> mError;
+
+ ~ModuleScript();
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ModuleScript)
+
+ ModuleScript(ScriptLoader* aLoader,
+ nsIURI* aBaseURL);
+
+ void SetModuleRecord(JS::Handle<JSObject*> aModuleRecord);
+ void SetPreInstantiationError(const JS::Value& aError);
+
+ ScriptLoader* Loader() const { return mLoader; }
+ JSObject* ModuleRecord() const { return mModuleRecord; }
+ nsIURI* BaseURL() const { return mBaseURL; }
+
+ bool IsErrored() const;
+ JS::Value Error() const;
+
+ void UnlinkModuleRecord();
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_ModuleScript_h \ No newline at end of file
diff --git a/dom/base/nsScriptElement.cpp b/dom/script/ScriptElement.cpp
index ebeb18f81..0cb17dcb0 100644
--- a/dom/base/nsScriptElement.cpp
+++ b/dom/script/ScriptElement.cpp
@@ -4,13 +4,13 @@
* 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 "ScriptElement.h"
+#include "ScriptLoader.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"
@@ -19,11 +19,11 @@ using namespace mozilla;
using namespace mozilla::dom;
NS_IMETHODIMP
-nsScriptElement::ScriptAvailable(nsresult aResult,
- nsIScriptElement *aElement,
- bool aIsInline,
- nsIURI *aURI,
- int32_t aLineNo)
+ScriptElement::ScriptAvailable(nsresult aResult,
+ nsIScriptElement *aElement,
+ bool aIsInline,
+ nsIURI *aURI,
+ int32_t aLineNo)
{
if (!aIsInline && NS_FAILED(aResult)) {
nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
@@ -40,7 +40,7 @@ nsScriptElement::ScriptAvailable(nsresult aResult,
}
/* virtual */ nsresult
-nsScriptElement::FireErrorEvent()
+ScriptElement::FireErrorEvent()
{
nsCOMPtr<nsIContent> cont =
do_QueryInterface((nsIScriptElement*) this);
@@ -53,9 +53,9 @@ nsScriptElement::FireErrorEvent()
}
NS_IMETHODIMP
-nsScriptElement::ScriptEvaluated(nsresult aResult,
- nsIScriptElement *aElement,
- bool aIsInline)
+ScriptElement::ScriptEvaluated(nsresult aResult,
+ nsIScriptElement *aElement,
+ bool aIsInline)
{
nsresult rv = NS_OK;
if (!aIsInline) {
@@ -78,44 +78,44 @@ nsScriptElement::ScriptEvaluated(nsresult aResult,
}
void
-nsScriptElement::CharacterDataChanged(nsIDocument *aDocument,
- nsIContent* aContent,
- CharacterDataChangeInfo* aInfo)
+ScriptElement::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)
+ScriptElement::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)
+ScriptElement::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer)
{
MaybeProcessScript();
}
void
-nsScriptElement::ContentInserted(nsIDocument *aDocument,
- nsIContent* aContainer,
- nsIContent* aChild,
- int32_t aIndexInContainer)
+ScriptElement::ContentInserted(nsIDocument *aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
{
MaybeProcessScript();
}
bool
-nsScriptElement::MaybeProcessScript()
+ScriptElement::MaybeProcessScript()
{
nsCOMPtr<nsIContent> cont =
do_QueryInterface((nsIScriptElement*) this);
@@ -145,6 +145,6 @@ nsScriptElement::MaybeProcessScript()
}
}
- RefPtr<nsScriptLoader> loader = ownerDoc->ScriptLoader();
+ RefPtr<ScriptLoader> loader = ownerDoc->ScriptLoader();
return loader->ProcessScriptElement(this);
}
diff --git a/dom/base/nsScriptElement.h b/dom/script/ScriptElement.h
index 4a2a584ac..0babda674 100644
--- a/dom/base/nsScriptElement.h
+++ b/dom/script/ScriptElement.h
@@ -4,22 +4,25 @@
* 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
+#ifndef mozilla_dom_ScriptElement_h
+#define mozilla_dom_ScriptElement_h
#include "mozilla/Attributes.h"
#include "nsIScriptLoaderObserver.h"
#include "nsIScriptElement.h"
#include "nsStubMutationObserver.h"
+namespace mozilla {
+namespace dom {
+
/**
* Baseclass useful for script elements (such as <xhtml:script> and
* <svg:script>). 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
+class ScriptElement : public nsIScriptElement,
+ public nsStubMutationObserver
{
public:
// nsIScriptLoaderObserver
@@ -31,7 +34,7 @@ public:
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
- explicit nsScriptElement(mozilla::dom::FromParser aFromParser)
+ explicit ScriptElement(FromParser aFromParser)
: nsIScriptElement(aFromParser)
{
}
@@ -49,4 +52,7 @@ protected:
virtual bool MaybeProcessScript() override;
};
-#endif // nsScriptElement_h
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_ScriptElement_h
diff --git a/dom/script/ScriptLoadHandler.cpp b/dom/script/ScriptLoadHandler.cpp
new file mode 100644
index 000000000..80fb70f6a
--- /dev/null
+++ b/dom/script/ScriptLoadHandler.cpp
@@ -0,0 +1,216 @@
+/* -*- 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 <script> elements.
+ */
+
+#include "ScriptLoadHandler.h"
+#include "ScriptLoader.h"
+#include "nsContentUtils.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+ScriptLoadHandler::ScriptLoadHandler(ScriptLoader *aScriptLoader,
+ ScriptLoadRequest *aRequest,
+ mozilla::dom::SRICheckDataVerifier *aSRIDataVerifier)
+ : mScriptLoader(aScriptLoader),
+ mRequest(aRequest),
+ mSRIDataVerifier(aSRIDataVerifier),
+ mSRIStatus(NS_OK),
+ mDecoder(),
+ mBuffer()
+{}
+
+ScriptLoadHandler::~ScriptLoadHandler()
+{}
+
+NS_IMPL_ISUPPORTS(ScriptLoadHandler, nsIIncrementalStreamLoaderObserver)
+
+NS_IMETHODIMP
+ScriptLoadHandler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
+ nsISupports* aContext,
+ uint32_t aDataLength,
+ const uint8_t* aData,
+ uint32_t *aConsumedLength)
+{
+ if (mRequest->IsCanceled()) {
+ // If request cancelled, ignore any incoming data.
+ *aConsumedLength = aDataLength;
+ return NS_OK;
+ }
+
+ if (!EnsureDecoder(aLoader, aData, aDataLength,
+ /* aEndOfStream = */ false)) {
+ return NS_OK;
+ }
+
+ // Below we will/shall consume entire data chunk.
+ *aConsumedLength = aDataLength;
+
+ // Decoder has already been initialized. -- trying to decode all loaded bytes.
+ nsresult rv = TryDecodeRawData(aData, aDataLength,
+ /* aEndOfStream = */ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If SRI is required for this load, appending new bytes to the hash.
+ if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
+ mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
+ }
+
+ return rv;
+}
+
+nsresult
+ScriptLoadHandler::TryDecodeRawData(const uint8_t* aData,
+ uint32_t aDataLength,
+ bool aEndOfStream)
+{
+ int32_t srcLen = aDataLength;
+ const char* src = reinterpret_cast<const char *>(aData);
+ int32_t dstLen;
+ nsresult rv =
+ mDecoder->GetMaxLength(src, srcLen, &dstLen);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t haveRead = mBuffer.length();
+
+ CheckedInt<uint32_t> capacity = haveRead;
+ capacity += dstLen;
+
+ if (!capacity.isValid() || !mBuffer.reserve(capacity.value())) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = mDecoder->Convert(src,
+ &srcLen,
+ mBuffer.begin() + haveRead,
+ &dstLen);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ haveRead += dstLen;
+ MOZ_ASSERT(haveRead <= capacity.value(), "mDecoder produced more data than expected");
+ MOZ_ALWAYS_TRUE(mBuffer.resizeUninitialized(haveRead));
+
+ return NS_OK;
+}
+
+bool
+ScriptLoadHandler::EnsureDecoder(nsIIncrementalStreamLoader *aLoader,
+ const uint8_t* aData,
+ uint32_t aDataLength,
+ bool aEndOfStream)
+{
+ // Check if decoder has already been created.
+ if (mDecoder) {
+ return true;
+ }
+
+ nsAutoCString charset;
+
+ // JavaScript modules are always UTF-8.
+ if (mRequest->IsModuleRequest()) {
+ charset = "UTF-8";
+ mDecoder = EncodingUtils::DecoderForEncoding(charset);
+ return true;
+ }
+
+ // Determine if BOM check should be done. This occurs either
+ // if end-of-stream has been reached, or at least 3 bytes have
+ // been read from input.
+ if (!aEndOfStream && (aDataLength < 3)) {
+ return false;
+ }
+
+ // Do BOM detection.
+ if (nsContentUtils::CheckForBOM(aData, aDataLength, charset)) {
+ mDecoder = EncodingUtils::DecoderForEncoding(charset);
+ return true;
+ }
+
+ // BOM detection failed, check content stream for charset.
+ nsCOMPtr<nsIRequest> req;
+ nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
+ NS_ASSERTION(req, "StreamLoader's request went away prematurely");
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
+
+ if (channel &&
+ NS_SUCCEEDED(channel->GetContentCharset(charset)) &&
+ EncodingUtils::FindEncodingForLabel(charset, charset)) {
+ mDecoder = EncodingUtils::DecoderForEncoding(charset);
+ return true;
+ }
+
+ // Check the hint charset from the script element or preload
+ // request.
+ nsAutoString hintCharset;
+ if (!mRequest->IsPreload()) {
+ mRequest->mElement->GetScriptCharset(hintCharset);
+ } else {
+ nsTArray<ScriptLoader::PreloadInfo>::index_type i =
+ mScriptLoader->mPreloads.IndexOf(mRequest, 0,
+ ScriptLoader::PreloadRequestComparator());
+
+ NS_ASSERTION(i != mScriptLoader->mPreloads.NoIndex,
+ "Incorrect preload bookkeeping");
+ hintCharset = mScriptLoader->mPreloads[i].mCharset;
+ }
+
+ if (EncodingUtils::FindEncodingForLabel(hintCharset, charset)) {
+ mDecoder = EncodingUtils::DecoderForEncoding(charset);
+ return true;
+ }
+
+ // Get the charset from the charset of the document.
+ if (mScriptLoader->mDocument) {
+ charset = mScriptLoader->mDocument->GetDocumentCharacterSet();
+ mDecoder = EncodingUtils::DecoderForEncoding(charset);
+ return true;
+ }
+
+ // Curiously, there are various callers that don't pass aDocument. The
+ // fallback in the old code was ISO-8859-1, which behaved like
+ // windows-1252. Saying windows-1252 for clarity and for compliance
+ // with the Encoding Standard.
+ charset = "windows-1252";
+ mDecoder = EncodingUtils::DecoderForEncoding(charset);
+ return true;
+}
+
+NS_IMETHODIMP
+ScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
+ nsISupports* aContext,
+ nsresult aStatus,
+ uint32_t aDataLength,
+ const uint8_t* aData)
+{
+ if (!mRequest->IsCanceled()) {
+ DebugOnly<bool> encoderSet =
+ EnsureDecoder(aLoader, aData, aDataLength, /* aEndOfStream = */ true);
+ MOZ_ASSERT(encoderSet);
+ DebugOnly<nsresult> rv = TryDecodeRawData(aData, aDataLength,
+ /* aEndOfStream = */ true);
+
+ // If SRI is required for this load, appending new bytes to the hash.
+ if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
+ mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
+ }
+ }
+
+ // we have to mediate and use mRequest.
+ return mScriptLoader->OnStreamComplete(aLoader, mRequest, aStatus, mSRIStatus,
+ mBuffer, mSRIDataVerifier);
+}
+
+} // dom namespace
+} // mozilla namespace \ No newline at end of file
diff --git a/dom/script/ScriptLoadHandler.h b/dom/script/ScriptLoadHandler.h
new file mode 100644
index 000000000..b70f87397
--- /dev/null
+++ b/dom/script/ScriptLoadHandler.h
@@ -0,0 +1,76 @@
+/* -*- 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 <script> elements.
+ */
+
+#ifndef mozilla_dom_ScriptLoadHandler_h
+#define mozilla_dom_ScriptLoadHandler_h
+
+#include "nsIIncrementalStreamLoader.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Vector.h"
+
+namespace mozilla {
+namespace dom {
+
+class ScriptLoadRequest;
+class ScriptLoader;
+class SRICheckDataVerifier;
+
+class ScriptLoadHandler final : public nsIIncrementalStreamLoaderObserver
+{
+public:
+ explicit ScriptLoadHandler(ScriptLoader* aScriptLoader,
+ ScriptLoadRequest* aRequest,
+ SRICheckDataVerifier* aSRIDataVerifier);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
+
+private:
+ virtual ~ScriptLoadHandler();
+
+ /*
+ * Try to decode some raw data.
+ */
+ nsresult TryDecodeRawData(const uint8_t* aData, uint32_t aDataLength,
+ bool aEndOfStream);
+
+ /*
+ * Discover the charset by looking at the stream data, the script
+ * tag, and other indicators. Returns true if charset has been
+ * discovered.
+ */
+ bool EnsureDecoder(nsIIncrementalStreamLoader *aLoader,
+ const uint8_t* aData, uint32_t aDataLength,
+ bool aEndOfStream);
+
+ // ScriptLoader which will handle the parsed script.
+ RefPtr<ScriptLoader> mScriptLoader;
+
+ // The ScriptLoadRequest for this load.
+ RefPtr<ScriptLoadRequest> mRequest;
+
+ // SRI data verifier.
+ nsAutoPtr<SRICheckDataVerifier> mSRIDataVerifier;
+
+ // Status of SRI data operations.
+ nsresult mSRIStatus;
+
+ // Unicode decoder for charset.
+ nsCOMPtr<nsIUnicodeDecoder> mDecoder;
+
+ // Accumulated decoded char buffer.
+ mozilla::Vector<char16_t> mBuffer;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif //mozilla_dom_ScriptLoader_h
diff --git a/dom/base/nsScriptLoader.cpp b/dom/script/ScriptLoader.cpp
index 25482fe7b..a53098974 100644
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -4,11 +4,10 @@
* 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 <script> elements.
- */
-
-#include "nsScriptLoader.h"
+#include "ScriptLoader.h"
+#include "ScriptLoadHandler.h"
+#include "ModuleLoadRequest.h"
+#include "ModuleScript.h"
#include "prsystem.h"
#include "jsapi.h"
@@ -58,19 +57,19 @@
#include "mozilla/Unused.h"
#include "nsIScriptError.h"
-using namespace mozilla;
-using namespace mozilla::dom;
-
using JS::SourceBufferHolder;
+namespace mozilla {
+namespace dom {
+
static LazyLogModule gCspPRLog("CSP");
void
-ImplCycleCollectionUnlink(nsScriptLoadRequestList& aField);
+ImplCycleCollectionUnlink(ScriptLoadRequestList& aField);
void
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
- nsScriptLoadRequestList& aField,
+ ScriptLoadRequestList& aField,
const char* aName,
uint32_t aFlags = 0);
@@ -78,23 +77,23 @@ ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
// nsScriptLoadRequest
//////////////////////////////////////////////////////////////
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsScriptLoadRequest)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptLoadRequest)
NS_INTERFACE_MAP_END
-NS_IMPL_CYCLE_COLLECTING_ADDREF(nsScriptLoadRequest)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(nsScriptLoadRequest)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoadRequest)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoadRequest)
-NS_IMPL_CYCLE_COLLECTION_CLASS(nsScriptLoadRequest)
+NS_IMPL_CYCLE_COLLECTION_CLASS(ScriptLoadRequest)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsScriptLoadRequest)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mElement)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsScriptLoadRequest)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-nsScriptLoadRequest::~nsScriptLoadRequest()
+ScriptLoadRequest::~ScriptLoadRequest()
{
js_free(mScriptTextBuf);
@@ -107,21 +106,21 @@ nsScriptLoadRequest::~nsScriptLoadRequest()
}
void
-nsScriptLoadRequest::SetReady()
+ScriptLoadRequest::SetReady()
{
MOZ_ASSERT(mProgress != Progress::Ready);
mProgress = Progress::Ready;
}
void
-nsScriptLoadRequest::Cancel()
+ScriptLoadRequest::Cancel()
{
MaybeCancelOffThreadScript();
mIsCanceled = true;
}
void
-nsScriptLoadRequest::MaybeCancelOffThreadScript()
+ScriptLoadRequest::MaybeCancelOffThreadScript()
{
MOZ_ASSERT(NS_IsMainThread());
@@ -134,306 +133,28 @@ nsScriptLoadRequest::MaybeCancelOffThreadScript()
mOffThreadToken = nullptr;
}
-//////////////////////////////////////////////////////////////
-// nsModuleLoadRequest
-//////////////////////////////////////////////////////////////
-
-// A load request for a module, created for every top level module script and
-// every module import. Load request can share an nsModuleScript if there are
-// multiple imports of the same module.
-
-class nsModuleLoadRequest final : public nsScriptLoadRequest
-{
- ~nsModuleLoadRequest() {}
-
- nsModuleLoadRequest(const nsModuleLoadRequest& aOther) = delete;
- nsModuleLoadRequest(nsModuleLoadRequest&& aOther) = delete;
-
-public:
- NS_DECL_ISUPPORTS_INHERITED
- NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsModuleLoadRequest, nsScriptLoadRequest)
-
- nsModuleLoadRequest(nsIScriptElement* aElement,
- uint32_t aVersion,
- CORSMode aCORSMode,
- const SRIMetadata& aIntegrity,
- nsScriptLoader* aLoader);
-
- bool IsTopLevel() const {
- return mIsTopLevel;
- }
-
- void SetReady() override;
- void Cancel() override;
-
- void ModuleLoaded();
- void DependenciesLoaded();
- void LoadFailed();
-
- // Is this a request for a top level module script or an import?
- bool mIsTopLevel;
-
- // The base URL used for resolving relative module imports.
- nsCOMPtr<nsIURI> mBaseURL;
-
- // Pointer to the script loader, used to trigger actions when the module load
- // finishes.
- RefPtr<nsScriptLoader> mLoader;
-
- // The importing module, or nullptr for top level module scripts. Used to
- // implement the ancestor list checked when fetching module dependencies.
- RefPtr<nsModuleLoadRequest> mParent;
-
- // Set to a module script object after a successful load or nullptr on
- // failure.
- RefPtr<nsModuleScript> mModuleScript;
-
- // A promise that is completed on successful load of this module and all of
- // its dependencies, indicating that the module is ready for instantiation and
- // evaluation.
- MozPromiseHolder<GenericPromise> mReady;
-
- // Array of imported modules.
- nsTArray<RefPtr<nsModuleLoadRequest>> mImports;
-};
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsModuleLoadRequest)
-NS_INTERFACE_MAP_END_INHERITING(nsScriptLoadRequest)
-
-NS_IMPL_CYCLE_COLLECTION_INHERITED(nsModuleLoadRequest, nsScriptLoadRequest,
- mBaseURL,
- mLoader,
- mParent,
- mModuleScript,
- mImports)
-
-NS_IMPL_ADDREF_INHERITED(nsModuleLoadRequest, nsScriptLoadRequest)
-NS_IMPL_RELEASE_INHERITED(nsModuleLoadRequest, nsScriptLoadRequest)
-
-nsModuleLoadRequest::nsModuleLoadRequest(nsIScriptElement* aElement,
- uint32_t aVersion,
- CORSMode aCORSMode,
- const SRIMetadata &aIntegrity,
- nsScriptLoader* aLoader)
- : nsScriptLoadRequest(nsScriptKind::Module,
- aElement,
- aVersion,
- aCORSMode,
- aIntegrity),
- mIsTopLevel(true),
- mLoader(aLoader)
-{}
-
-inline nsModuleLoadRequest*
-nsScriptLoadRequest::AsModuleRequest()
+inline ModuleLoadRequest*
+ScriptLoadRequest::AsModuleRequest()
{
MOZ_ASSERT(IsModuleRequest());
- return static_cast<nsModuleLoadRequest*>(this);
-}
-
-void nsModuleLoadRequest::Cancel()
-{
- nsScriptLoadRequest::Cancel();
- mModuleScript = nullptr;
- mProgress = nsScriptLoadRequest::Progress::Ready;
- for (size_t i = 0; i < mImports.Length(); i++) {
- mImports[i]->Cancel();
- }
- mReady.RejectIfExists(NS_ERROR_FAILURE, __func__);
-}
-
-void
-nsModuleLoadRequest::SetReady()
-{
-#ifdef DEBUG
- for (size_t i = 0; i < mImports.Length(); i++) {
- MOZ_ASSERT(mImports[i]->IsReadyToRun());
- }
-#endif
-
- nsScriptLoadRequest::SetReady();
- mReady.ResolveIfExists(true, __func__);
-}
-
-void
-nsModuleLoadRequest::ModuleLoaded()
-{
- // A module that was found to be marked as fetching in the module map has now
- // been loaded.
-
- mModuleScript = mLoader->GetFetchedModule(mURI);
- mLoader->StartFetchingModuleDependencies(this);
-}
-
-void
-nsModuleLoadRequest::DependenciesLoaded()
-{
- // The module and all of its dependencies have been successfully fetched and
- // compiled.
-
- if (!mLoader->InstantiateModuleTree(this)) {
- LoadFailed();
- return;
- }
-
- SetReady();
- mLoader->ProcessLoadedModuleTree(this);
- mLoader = nullptr;
- mParent = nullptr;
-}
-
-void
-nsModuleLoadRequest::LoadFailed()
-{
- Cancel();
- mLoader->ProcessLoadedModuleTree(this);
- mLoader = nullptr;
- mParent = nullptr;
-}
-
-//////////////////////////////////////////////////////////////
-// nsModuleScript
-//////////////////////////////////////////////////////////////
-
-// A single module script. May be used to satisfy multiple load requests.
-
-class nsModuleScript final : public nsISupports
-{
- enum InstantiationState {
- Uninstantiated,
- Instantiated,
- Errored
- };
-
- RefPtr<nsScriptLoader> mLoader;
- nsCOMPtr<nsIURI> mBaseURL;
- JS::Heap<JSObject*> mModuleRecord;
- JS::Heap<JS::Value> mException;
- InstantiationState mInstantiationState;
-
- ~nsModuleScript();
-
-public:
- NS_DECL_CYCLE_COLLECTING_ISUPPORTS
- NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsModuleScript)
-
- nsModuleScript(nsScriptLoader* aLoader,
- nsIURI* aBaseURL,
- JS::Handle<JSObject*> aModuleRecord);
-
- nsScriptLoader* Loader() const { return mLoader; }
- JSObject* ModuleRecord() const { return mModuleRecord; }
- JS::Value Exception() const { return mException; }
- nsIURI* BaseURL() const { return mBaseURL; }
-
- void SetInstantiationResult(JS::Handle<JS::Value> aMaybeException);
- bool IsUninstantiated() const {
- return mInstantiationState == Uninstantiated;
- }
- bool IsInstantiated() const {
- return mInstantiationState == Instantiated;
- }
- bool InstantiationFailed() const {
- return mInstantiationState == Errored;
- }
-
- void UnlinkModuleRecord();
-};
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsModuleScript)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_CYCLE_COLLECTION_CLASS(nsModuleScript)
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsModuleScript)
- NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoader)
- NS_IMPL_CYCLE_COLLECTION_UNLINK(mBaseURL)
- tmp->UnlinkModuleRecord();
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsModuleScript)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoader)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsModuleScript)
- NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mModuleRecord)
- NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mException)
-NS_IMPL_CYCLE_COLLECTION_TRACE_END
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(nsModuleScript)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(nsModuleScript)
-
-nsModuleScript::nsModuleScript(nsScriptLoader *aLoader, nsIURI* aBaseURL,
- JS::Handle<JSObject*> aModuleRecord)
- : mLoader(aLoader),
- mBaseURL(aBaseURL),
- mModuleRecord(aModuleRecord),
- mInstantiationState(Uninstantiated)
-{
- MOZ_ASSERT(mLoader);
- MOZ_ASSERT(mBaseURL);
- MOZ_ASSERT(mModuleRecord);
- MOZ_ASSERT(mException.isUndefined());
-
- // Make module's host defined field point to this module script object.
- // This is cleared in the UnlinkModuleRecord().
- JS::SetModuleHostDefinedField(mModuleRecord, JS::PrivateValue(this));
- HoldJSObjects(this);
-}
-
-void
-nsModuleScript::UnlinkModuleRecord()
-{
- // Remove module's back reference to this object request if present.
- if (mModuleRecord) {
- MOZ_ASSERT(JS::GetModuleHostDefinedField(mModuleRecord).toPrivate() ==
- this);
- JS::SetModuleHostDefinedField(mModuleRecord, JS::UndefinedValue());
- }
- mModuleRecord = nullptr;
- mException.setUndefined();
-}
-
-nsModuleScript::~nsModuleScript()
-{
- if (mModuleRecord) {
- // The object may be destroyed without being unlinked first.
- UnlinkModuleRecord();
- }
- DropJSObjects(this);
-}
-
-void
-nsModuleScript::SetInstantiationResult(JS::Handle<JS::Value> aMaybeException)
-{
- MOZ_ASSERT(mInstantiationState == Uninstantiated);
- MOZ_ASSERT(mModuleRecord);
- MOZ_ASSERT(mException.isUndefined());
-
- if (aMaybeException.isUndefined()) {
- mInstantiationState = Instantiated;
- } else {
- mModuleRecord = nullptr;
- mException = aMaybeException;
- mInstantiationState = Errored;
- }
+ return static_cast<ModuleLoadRequest*>(this);
}
//////////////////////////////////////////////////////////////
-// nsScriptLoadRequestList
+// ScriptLoadRequestList
//////////////////////////////////////////////////////////////
-nsScriptLoadRequestList::~nsScriptLoadRequestList()
+ScriptLoadRequestList::~ScriptLoadRequestList()
{
Clear();
}
void
-nsScriptLoadRequestList::Clear()
+ScriptLoadRequestList::Clear()
{
while (!isEmpty()) {
- RefPtr<nsScriptLoadRequest> first = StealFirst();
+ RefPtr<ScriptLoadRequest> first = StealFirst();
first->Cancel();
// And just let it go out of scope and die.
}
@@ -441,9 +162,9 @@ nsScriptLoadRequestList::Clear()
#ifdef DEBUG
bool
-nsScriptLoadRequestList::Contains(nsScriptLoadRequest* aElem) const
+ScriptLoadRequestList::Contains(ScriptLoadRequest* aElem) const
{
- for (const nsScriptLoadRequest* req = getFirst();
+ for (const ScriptLoadRequest* req = getFirst();
req; req = req->getNext()) {
if (req == aElem) {
return true;
@@ -455,20 +176,20 @@ nsScriptLoadRequestList::Contains(nsScriptLoadRequest* aElem) const
#endif // DEBUG
inline void
-ImplCycleCollectionUnlink(nsScriptLoadRequestList& aField)
+ImplCycleCollectionUnlink(ScriptLoadRequestList& aField)
{
while (!aField.isEmpty()) {
- RefPtr<nsScriptLoadRequest> first = aField.StealFirst();
+ RefPtr<ScriptLoadRequest> first = aField.StealFirst();
}
}
inline void
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
- nsScriptLoadRequestList& aField,
+ ScriptLoadRequestList& aField,
const char* aName,
uint32_t aFlags)
{
- for (nsScriptLoadRequest* request = aField.getFirst();
+ for (ScriptLoadRequest* request = aField.getFirst();
request; request = request->getNext())
{
CycleCollectionNoteChild(aCallback, request, aName, aFlags);
@@ -476,18 +197,18 @@ ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
}
//////////////////////////////////////////////////////////////
-// nsScriptLoader::PreloadInfo
+// ScriptLoader::PreloadInfo
//////////////////////////////////////////////////////////////
inline void
-ImplCycleCollectionUnlink(nsScriptLoader::PreloadInfo& aField)
+ImplCycleCollectionUnlink(ScriptLoader::PreloadInfo& aField)
{
ImplCycleCollectionUnlink(aField.mRequest);
}
inline void
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
- nsScriptLoader::PreloadInfo& aField,
+ ScriptLoader::PreloadInfo& aField,
const char* aName,
uint32_t aFlags = 0)
{
@@ -495,13 +216,13 @@ ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
}
//////////////////////////////////////////////////////////////
-// nsScriptLoader
+// ScriptLoader
//////////////////////////////////////////////////////////////
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsScriptLoader)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptLoader)
NS_INTERFACE_MAP_END
-NS_IMPL_CYCLE_COLLECTION(nsScriptLoader,
+NS_IMPL_CYCLE_COLLECTION(ScriptLoader,
mNonAsyncExternalScriptInsertedRequests,
mLoadingAsyncRequests,
mLoadedAsyncRequests,
@@ -512,10 +233,10 @@ NS_IMPL_CYCLE_COLLECTION(nsScriptLoader,
mPendingChildLoaders,
mFetchedModules)
-NS_IMPL_CYCLE_COLLECTING_ADDREF(nsScriptLoader)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(nsScriptLoader)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoader)
-nsScriptLoader::nsScriptLoader(nsIDocument *aDocument)
+ScriptLoader::ScriptLoader(nsIDocument *aDocument)
: mDocument(aDocument),
mParserBlockingBlockerCount(0),
mBlockerCount(0),
@@ -528,7 +249,7 @@ nsScriptLoader::nsScriptLoader(nsIDocument *aDocument)
{
}
-nsScriptLoader::~nsScriptLoader()
+ScriptLoader::~ScriptLoader()
{
mObservers.Clear();
@@ -536,27 +257,27 @@ nsScriptLoader::~nsScriptLoader()
mParserBlockingRequest->FireScriptAvailable(NS_ERROR_ABORT);
}
- for (nsScriptLoadRequest* req = mXSLTRequests.getFirst(); req;
+ for (ScriptLoadRequest* req = mXSLTRequests.getFirst(); req;
req = req->getNext()) {
req->FireScriptAvailable(NS_ERROR_ABORT);
}
- for (nsScriptLoadRequest* req = mDeferRequests.getFirst(); req;
+ for (ScriptLoadRequest* req = mDeferRequests.getFirst(); req;
req = req->getNext()) {
req->FireScriptAvailable(NS_ERROR_ABORT);
}
- for (nsScriptLoadRequest* req = mLoadingAsyncRequests.getFirst(); req;
+ for (ScriptLoadRequest* req = mLoadingAsyncRequests.getFirst(); req;
req = req->getNext()) {
req->FireScriptAvailable(NS_ERROR_ABORT);
}
- for (nsScriptLoadRequest* req = mLoadedAsyncRequests.getFirst(); req;
+ for (ScriptLoadRequest* req = mLoadedAsyncRequests.getFirst(); req;
req = req->getNext()) {
req->FireScriptAvailable(NS_ERROR_ABORT);
}
- for(nsScriptLoadRequest* req = mNonAsyncExternalScriptInsertedRequests.getFirst();
+ for(ScriptLoadRequest* req = mNonAsyncExternalScriptInsertedRequests.getFirst();
req;
req = req->getNext()) {
req->FireScriptAvailable(NS_ERROR_ABORT);
@@ -623,11 +344,11 @@ IsScriptEventHandler(nsIContent* aScriptElement)
}
nsresult
-nsScriptLoader::CheckContentPolicy(nsIDocument* aDocument,
- nsISupports *aContext,
- nsIURI *aURI,
- const nsAString &aType,
- bool aIsPreLoad)
+ScriptLoader::CheckContentPolicy(nsIDocument* aDocument,
+ nsISupports *aContext,
+ nsIURI *aURI,
+ const nsAString &aType,
+ bool aIsPreLoad)
{
nsContentPolicyType contentPolicyType = aIsPreLoad
? nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD
@@ -654,7 +375,7 @@ nsScriptLoader::CheckContentPolicy(nsIDocument* aDocument,
}
bool
-nsScriptLoader::ModuleScriptsEnabled()
+ScriptLoader::ModuleScriptsEnabled()
{
static bool sEnabledForContent = false;
static bool sCachedPref = false;
@@ -667,7 +388,7 @@ nsScriptLoader::ModuleScriptsEnabled()
}
bool
-nsScriptLoader::ModuleMapContainsModule(nsModuleLoadRequest *aRequest) const
+ScriptLoader::ModuleMapContainsModule(ModuleLoadRequest *aRequest) const
{
// Returns whether we have fetched, or are currently fetching, a module script
// for the request's URL.
@@ -676,7 +397,7 @@ nsScriptLoader::ModuleMapContainsModule(nsModuleLoadRequest *aRequest) const
}
bool
-nsScriptLoader::IsFetchingModule(nsModuleLoadRequest *aRequest) const
+ScriptLoader::IsFetchingModule(ModuleLoadRequest *aRequest) const
{
bool fetching = mFetchingModules.Contains(aRequest->mURI);
MOZ_ASSERT_IF(fetching, !mFetchedModules.Contains(aRequest->mURI));
@@ -684,7 +405,7 @@ nsScriptLoader::IsFetchingModule(nsModuleLoadRequest *aRequest) const
}
void
-nsScriptLoader::SetModuleFetchStarted(nsModuleLoadRequest *aRequest)
+ScriptLoader::SetModuleFetchStarted(ModuleLoadRequest *aRequest)
{
// Update the module map to indicate that a module is currently being fetched.
@@ -694,24 +415,26 @@ nsScriptLoader::SetModuleFetchStarted(nsModuleLoadRequest *aRequest)
}
void
-nsScriptLoader::SetModuleFetchFinishedAndResumeWaitingRequests(nsModuleLoadRequest *aRequest,
- nsresult aResult)
+ScriptLoader::SetModuleFetchFinishedAndResumeWaitingRequests(ModuleLoadRequest *aRequest,
+ nsresult aResult)
{
- // Update module map with the result of fetching a single module script. The
- // module script pointer is nullptr on error.
-
- MOZ_ASSERT(!aRequest->IsReadyToRun());
+ // Update module map with the result of fetching a single module script.
+ //
+ // If any requests for the same URL are waiting on this one to complete, they
+ // will have ModuleLoaded or LoadFailed on them when the promise is
+ // resolved/rejected. This is set up in StartLoad.
RefPtr<GenericPromise::Private> promise;
MOZ_ALWAYS_TRUE(mFetchingModules.Get(aRequest->mURI, getter_AddRefs(promise)));
mFetchingModules.Remove(aRequest->mURI);
- RefPtr<nsModuleScript> ms(aRequest->mModuleScript);
- MOZ_ASSERT(NS_SUCCEEDED(aResult) == (ms != nullptr));
- mFetchedModules.Put(aRequest->mURI, ms);
+ RefPtr<ModuleScript> moduleScript(aRequest->mModuleScript);
+ MOZ_ASSERT(NS_FAILED(aResult) == !moduleScript);
+
+ mFetchedModules.Put(aRequest->mURI, moduleScript);
if (promise) {
- if (ms) {
+ if (moduleScript) {
promise->Resolve(true, __func__);
} else {
promise->Reject(aResult, __func__);
@@ -720,7 +443,7 @@ nsScriptLoader::SetModuleFetchFinishedAndResumeWaitingRequests(nsModuleLoadReque
}
RefPtr<GenericPromise>
-nsScriptLoader::WaitForModuleFetch(nsModuleLoadRequest *aRequest)
+ScriptLoader::WaitForModuleFetch(ModuleLoadRequest *aRequest)
{
MOZ_ASSERT(ModuleMapContainsModule(aRequest));
@@ -733,7 +456,7 @@ nsScriptLoader::WaitForModuleFetch(nsModuleLoadRequest *aRequest)
return promise;
}
- RefPtr<nsModuleScript> ms;
+ RefPtr<ModuleScript> ms;
MOZ_ALWAYS_TRUE(mFetchedModules.Get(aRequest->mURI, getter_AddRefs(ms)));
if (!ms) {
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
@@ -742,36 +465,46 @@ nsScriptLoader::WaitForModuleFetch(nsModuleLoadRequest *aRequest)
return GenericPromise::CreateAndResolve(true, __func__);
}
-nsModuleScript*
-nsScriptLoader::GetFetchedModule(nsIURI* aURL) const
+ModuleScript*
+ScriptLoader::GetFetchedModule(nsIURI* aURL) const
{
bool found;
- nsModuleScript* ms = mFetchedModules.GetWeak(aURL, &found);
+ ModuleScript* ms = mFetchedModules.GetWeak(aURL, &found);
MOZ_ASSERT(found);
return ms;
}
nsresult
-nsScriptLoader::ProcessFetchedModuleSource(nsModuleLoadRequest* aRequest)
+ScriptLoader::ProcessFetchedModuleSource(ModuleLoadRequest* aRequest)
{
MOZ_ASSERT(!aRequest->mModuleScript);
nsresult rv = CreateModuleScript(aRequest);
+ MOZ_ASSERT(NS_FAILED(rv) == !aRequest->mModuleScript);
+
SetModuleFetchFinishedAndResumeWaitingRequests(aRequest, rv);
free(aRequest->mScriptTextBuf);
aRequest->mScriptTextBuf = nullptr;
aRequest->mScriptTextLength = 0;
- if (NS_SUCCEEDED(rv)) {
+ if (NS_FAILED(rv)) {
+ aRequest->LoadFailed();
+ return rv;
+ }
+
+ if (!aRequest->mModuleScript->IsErrored()) {
StartFetchingModuleDependencies(aRequest);
}
- return rv;
+ return NS_OK;
}
+static nsresult
+ResolveRequestedModules(ModuleLoadRequest* aRequest, nsCOMArray<nsIURI>& aUrls);
+
nsresult
-nsScriptLoader::CreateModuleScript(nsModuleLoadRequest* aRequest)
+ScriptLoader::CreateModuleScript(ModuleLoadRequest* aRequest)
{
MOZ_ASSERT(!aRequest->mModuleScript);
MOZ_ASSERT(aRequest->mBaseURL);
@@ -823,9 +556,34 @@ nsScriptLoader::CreateModuleScript(nsModuleLoadRequest* aRequest)
}
}
MOZ_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr));
- if (module) {
- aRequest->mModuleScript =
- new nsModuleScript(this, aRequest->mBaseURL, module);
+ RefPtr<ModuleScript> moduleScript = new ModuleScript(this, aRequest->mBaseURL);
+ aRequest->mModuleScript = moduleScript;
+
+ if (!module) {
+ // Compilation failed
+ MOZ_ASSERT(aes.HasException());
+ JS::Rooted<JS::Value> error(cx);
+ if (!aes.StealException(&error)) {
+ aRequest->mModuleScript = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ moduleScript->SetPreInstantiationError(error);
+ aRequest->ModuleErrored();
+ return NS_OK;
+ }
+
+ moduleScript->SetModuleRecord(module);
+
+ // Validate requested modules and treat failure to resolve module specifiers
+ // the same as a parse error.
+ nsCOMArray<nsIURI> urls;
+ rv = ResolveRequestedModules(aRequest, urls);
+ if (NS_FAILED(rv)) {
+ // ResolveRequestedModules sets pre-instanitation error on failure.
+ MOZ_ASSERT(moduleScript->IsErrored());
+ aRequest->ModuleErrored();
+ return NS_OK;
}
}
@@ -835,8 +593,8 @@ nsScriptLoader::CreateModuleScript(nsModuleLoadRequest* aRequest)
}
static bool
-ThrowTypeError(JSContext* aCx, nsModuleScript* aScript,
- const nsString& aMessage)
+CreateTypeError(JSContext* aCx, ModuleScript* aScript, const nsString& aMessage,
+ JS::MutableHandle<JS::Value> aError)
{
JS::Rooted<JSObject*> module(aCx, aScript->ModuleRecord());
JS::Rooted<JSScript*> script(aCx, JS::GetModuleScript(aCx, module));
@@ -851,18 +609,12 @@ ThrowTypeError(JSContext* aCx, nsModuleScript* aScript,
return false;
}
- JS::Rooted<JS::Value> error(aCx);
- if (!JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, 0, 0, nullptr,
- message, &error)) {
- return false;
- }
-
- JS_SetPendingException(aCx, error);
- return false;
+ return JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, 0, 0, nullptr,
+ message, aError);
}
-static bool
-HandleResolveFailure(JSContext* aCx, nsModuleScript* aScript,
+static nsresult
+HandleResolveFailure(JSContext* aCx, ModuleScript* aScript,
const nsAString& aSpecifier)
{
// TODO: How can we get the line number of the failed import?
@@ -870,23 +622,17 @@ HandleResolveFailure(JSContext* aCx, nsModuleScript* aScript,
nsAutoString message(NS_LITERAL_STRING("Error resolving module specifier: "));
message.Append(aSpecifier);
- return ThrowTypeError(aCx, aScript, message);
-}
-
-static bool
-HandleModuleNotFound(JSContext* aCx, nsModuleScript* aScript,
- const nsAString& aSpecifier)
-{
- // TODO: How can we get the line number of the failed import?
-
- nsAutoString message(NS_LITERAL_STRING("Resolved module not found in map: "));
- message.Append(aSpecifier);
+ JS::Rooted<JS::Value> error(aCx);
+ if (!CreateTypeError(aCx, aScript, message, &error)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
- return ThrowTypeError(aCx, aScript, message);
+ aScript->SetPreInstantiationError(error);
+ return NS_OK;
}
static already_AddRefed<nsIURI>
-ResolveModuleSpecifier(nsModuleScript* aScript,
+ResolveModuleSpecifier(ModuleScript* aScript,
const nsAString& aSpecifier)
{
// The following module specifiers are allowed by the spec:
@@ -921,7 +667,7 @@ ResolveModuleSpecifier(nsModuleScript* aScript,
}
static nsresult
-RequestedModuleIsInAncestorList(nsModuleLoadRequest* aRequest, nsIURI* aURL, bool* aResult)
+RequestedModuleIsInAncestorList(ModuleLoadRequest* aRequest, nsIURI* aURL, bool* aResult)
{
const size_t ImportDepthLimit = 100;
@@ -947,9 +693,9 @@ RequestedModuleIsInAncestorList(nsModuleLoadRequest* aRequest, nsIURI* aURL, boo
}
static nsresult
-ResolveRequestedModules(nsModuleLoadRequest* aRequest, nsCOMArray<nsIURI> &aUrls)
+ResolveRequestedModules(ModuleLoadRequest* aRequest, nsCOMArray<nsIURI> &aUrls)
{
- nsModuleScript* ms = aRequest->mModuleScript;
+ ModuleScript* ms = aRequest->mModuleScript;
AutoJSAPI jsapi;
if (!jsapi.Init(ms->ModuleRecord())) {
@@ -977,10 +723,11 @@ ResolveRequestedModules(nsModuleLoadRequest* aRequest, nsCOMArray<nsIURI> &aUrls
}
// Let url be the result of resolving a module specifier given module script and requested.
- nsModuleScript* ms = aRequest->mModuleScript;
+ ModuleScript* ms = aRequest->mModuleScript;
nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(ms, specifier);
if (!uri) {
- HandleResolveFailure(cx, ms, specifier);
+ nsresult rv = HandleResolveFailure(cx, ms, specifier);
+ NS_ENSURE_SUCCESS(rv, rv);
return NS_ERROR_FAILURE;
}
@@ -996,16 +743,18 @@ ResolveRequestedModules(nsModuleLoadRequest* aRequest, nsCOMArray<nsIURI> &aUrls
}
void
-nsScriptLoader::StartFetchingModuleDependencies(nsModuleLoadRequest* aRequest)
+ScriptLoader::StartFetchingModuleDependencies(ModuleLoadRequest* aRequest)
{
MOZ_ASSERT(aRequest->mModuleScript);
+ MOZ_ASSERT(!aRequest->mModuleScript->IsErrored());
MOZ_ASSERT(!aRequest->IsReadyToRun());
- aRequest->mProgress = nsModuleLoadRequest::Progress::FetchingImports;
+
+ aRequest->mProgress = ModuleLoadRequest::Progress::FetchingImports;
nsCOMArray<nsIURI> urls;
nsresult rv = ResolveRequestedModules(aRequest, urls);
if (NS_FAILED(rv)) {
- aRequest->LoadFailed();
+ aRequest->ModuleErrored();
return;
}
@@ -1028,19 +777,19 @@ nsScriptLoader::StartFetchingModuleDependencies(nsModuleLoadRequest* aRequest)
RefPtr<GenericPromise::AllPromiseType> allReady =
GenericPromise::All(AbstractThread::GetCurrent(), importsReady);
allReady->Then(AbstractThread::GetCurrent(), __func__, aRequest,
- &nsModuleLoadRequest::DependenciesLoaded,
- &nsModuleLoadRequest::LoadFailed);
+ &ModuleLoadRequest::DependenciesLoaded,
+ &ModuleLoadRequest::ModuleErrored);
}
RefPtr<GenericPromise>
-nsScriptLoader::StartFetchingModuleAndDependencies(nsModuleLoadRequest* aRequest,
- nsIURI* aURI)
+ScriptLoader::StartFetchingModuleAndDependencies(ModuleLoadRequest* aRequest,
+ nsIURI* aURI)
{
MOZ_ASSERT(aURI);
- RefPtr<nsModuleLoadRequest> childRequest =
- new nsModuleLoadRequest(aRequest->mElement, aRequest->mJSVersion,
- aRequest->mCORSMode, aRequest->mIntegrity, this);
+ RefPtr<ModuleLoadRequest> childRequest =
+ new ModuleLoadRequest(aRequest->mElement, aRequest->mJSVersion,
+ aRequest->mCORSMode, aRequest->mIntegrity, this);
childRequest->mIsTopLevel = false;
childRequest->mURI = aURI;
@@ -1052,6 +801,7 @@ nsScriptLoader::StartFetchingModuleAndDependencies(nsModuleLoadRequest* aRequest
nsresult rv = StartLoad(childRequest, NS_LITERAL_STRING("module"), false);
if (NS_FAILED(rv)) {
+ MOZ_ASSERT(!childRequest->mModuleScript);
childRequest->mReady.Reject(rv, __func__);
return ready;
}
@@ -1060,6 +810,7 @@ nsScriptLoader::StartFetchingModuleAndDependencies(nsModuleLoadRequest* aRequest
return ready;
}
+// 8.1.3.8.1 HostResolveImportedModule(referencingModule, specifier)
bool
HostResolveImportedModule(JSContext* aCx, unsigned argc, JS::Value* vp)
{
@@ -1070,35 +821,29 @@ HostResolveImportedModule(JSContext* aCx, unsigned argc, JS::Value* vp)
// Let referencing module script be referencingModule.[[HostDefined]].
JS::Value value = JS::GetModuleHostDefinedField(module);
- auto script = static_cast<nsModuleScript*>(value.toPrivate());
+ auto script = static_cast<ModuleScript*>(value.toPrivate());
MOZ_ASSERT(script->ModuleRecord() == module);
// Let url be the result of resolving a module specifier given referencing
- // module script and specifier. If the result is failure, throw a TypeError
- // exception and abort these steps.
+ // module script and specifier.
nsAutoJSString string;
if (!string.init(aCx, specifier)) {
return false;
}
nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(script, string);
- if (!uri) {
- return HandleResolveFailure(aCx, script, string);
- }
- // Let resolved module script be the value of the entry in module map whose
- // key is url. If no such entry exists, throw a TypeError exception and abort
- // these steps.
- nsModuleScript* ms = script->Loader()->GetFetchedModule(uri);
- if (!ms) {
- return HandleModuleNotFound(aCx, script, string);
- }
+ // This cannot fail because resolving a module specifier must have been
+ // previously successful with these same two arguments.
+ MOZ_ASSERT(uri, "Failed to resolve previously-resolved module specifier");
- if (ms->InstantiationFailed()) {
- JS::Rooted<JS::Value> exception(aCx, ms->Exception());
- JS_SetPendingException(aCx, exception);
- return false;
- }
+ // Let resolved module script be moduleMap[url]. (This entry must exist for us
+ // to have gotten to this point.)
+
+ ModuleScript* ms = script->Loader()->GetFetchedModule(uri);
+ MOZ_ASSERT(ms, "Resolved module not found in module map");
+
+ MOZ_ASSERT(!ms->IsErrored());
*vp = JS::ObjectValue(*ms->ModuleRecord());
return true;
@@ -1123,9 +868,35 @@ EnsureModuleResolveHook(JSContext* aCx)
}
void
-nsScriptLoader::ProcessLoadedModuleTree(nsModuleLoadRequest* aRequest)
+ScriptLoader::CheckModuleDependenciesLoaded(ModuleLoadRequest* aRequest)
+{
+ RefPtr<ModuleScript> moduleScript = aRequest->mModuleScript;
+ if (moduleScript && !moduleScript->IsErrored()) {
+ for (auto childRequest : aRequest->mImports) {
+ ModuleScript* childScript = childRequest->mModuleScript;
+ if (!childScript) {
+ // Load error
+ aRequest->mModuleScript = nullptr;
+ return;
+ } else if (childScript->IsErrored()) {
+ // Script error
+ moduleScript->SetPreInstantiationError(childScript->Error());
+ return;
+ }
+ }
+ }
+}
+
+void
+ScriptLoader::ProcessLoadedModuleTree(ModuleLoadRequest* aRequest)
{
if (aRequest->IsTopLevel()) {
+ ModuleScript* moduleScript = aRequest->mModuleScript;
+ if (moduleScript && !moduleScript->IsErrored()) {
+ if (!InstantiateModuleTree(aRequest)) {
+ aRequest->mModuleScript = nullptr;
+ }
+ }
MaybeMoveToLoadedList(aRequest);
ProcessPendingRequests();
}
@@ -1136,70 +907,45 @@ nsScriptLoader::ProcessLoadedModuleTree(nsModuleLoadRequest* aRequest)
}
bool
-nsScriptLoader::InstantiateModuleTree(nsModuleLoadRequest* aRequest)
+ScriptLoader::InstantiateModuleTree(ModuleLoadRequest* aRequest)
{
- // Perform eager instantiation of the loaded module tree.
+ // Instantiate a top-level module and record any error.
MOZ_ASSERT(aRequest);
+ MOZ_ASSERT(aRequest->IsTopLevel());
- nsModuleScript* ms = aRequest->mModuleScript;
- MOZ_ASSERT(ms);
- if (!ms || !ms->ModuleRecord()) {
- return false;
- }
+ ModuleScript* moduleScript = aRequest->mModuleScript;
+ MOZ_ASSERT(moduleScript);
+ MOZ_ASSERT(moduleScript->ModuleRecord());
+ nsAutoMicroTask mt;
AutoJSAPI jsapi;
- if (NS_WARN_IF(!jsapi.Init(ms->ModuleRecord()))) {
+ if (NS_WARN_IF(!jsapi.Init(moduleScript->ModuleRecord()))) {
return false;
}
nsresult rv = EnsureModuleResolveHook(jsapi.cx());
NS_ENSURE_SUCCESS(rv, false);
- JS::Rooted<JSObject*> module(jsapi.cx(), ms->ModuleRecord());
- bool ok = NS_SUCCEEDED(nsJSUtils::ModuleDeclarationInstantiation(jsapi.cx(), module));
+ JS::Rooted<JSObject*> module(jsapi.cx(), moduleScript->ModuleRecord());
+ bool ok = NS_SUCCEEDED(nsJSUtils::ModuleInstantiate(jsapi.cx(), module));
- JS::RootedValue exception(jsapi.cx());
if (!ok) {
MOZ_ASSERT(jsapi.HasException());
+ JS::RootedValue exception(jsapi.cx());
if (!jsapi.StealException(&exception)) {
return false;
}
MOZ_ASSERT(!exception.isUndefined());
- }
-
- // Mark this module and any uninstantiated dependencies found via depth-first
- // search as instantiated and record any error.
-
- mozilla::Vector<nsModuleLoadRequest*, 1> requests;
- if (!requests.append(aRequest)) {
- return false;
- }
-
- while (!requests.empty()) {
- nsModuleLoadRequest* request = requests.popCopy();
- nsModuleScript* ms = request->mModuleScript;
- if (!ms->IsUninstantiated()) {
- continue;
- }
-
- ms->SetInstantiationResult(exception);
-
- for (auto import : request->mImports) {
- if (import->mModuleScript->IsUninstantiated() &&
- !requests.append(import))
- {
- return false;
- }
- }
+ // Ignore the exception. It will be recorded in the module record.
}
return true;
}
nsresult
-nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType,
- bool aScriptFromHead)
+ScriptLoader::StartLoad(ScriptLoadRequest *aRequest, const nsAString &aType,
+ bool aScriptFromHead)
{
MOZ_ASSERT(aRequest->IsLoading());
NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER);
@@ -1212,12 +958,12 @@ nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType,
if (aRequest->IsModuleRequest()) {
// Check whether the module has been fetched or is currently being fetched,
// and if so wait for it.
- nsModuleLoadRequest* request = aRequest->AsModuleRequest();
+ ModuleLoadRequest* request = aRequest->AsModuleRequest();
if (ModuleMapContainsModule(request)) {
WaitForModuleFetch(request)
->Then(AbstractThread::GetCurrent(), __func__, request,
- &nsModuleLoadRequest::ModuleLoaded,
- &nsModuleLoadRequest::LoadFailed);
+ &ModuleLoadRequest::ModuleLoaded,
+ &ModuleLoadRequest::LoadFailed);
return NS_OK;
}
@@ -1330,8 +1076,8 @@ nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType,
mReporter);
}
- RefPtr<nsScriptLoadHandler> handler =
- new nsScriptLoadHandler(this, aRequest, sriDataVerifier.forget());
+ RefPtr<ScriptLoadHandler> handler =
+ new ScriptLoadHandler(this, aRequest, sriDataVerifier.forget());
nsCOMPtr<nsIIncrementalStreamLoader> loader;
rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), handler);
@@ -1341,22 +1087,22 @@ nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType,
}
bool
-nsScriptLoader::PreloadURIComparator::Equals(const PreloadInfo &aPi,
- nsIURI * const &aURI) const
+ScriptLoader::PreloadURIComparator::Equals(const PreloadInfo &aPi,
+ nsIURI * const &aURI) const
{
bool same;
return NS_SUCCEEDED(aPi.mRequest->mURI->Equals(aURI, &same)) &&
same;
}
-class nsScriptRequestProcessor : public Runnable
+class ScriptRequestProcessor : public Runnable
{
private:
- RefPtr<nsScriptLoader> mLoader;
- RefPtr<nsScriptLoadRequest> mRequest;
+ RefPtr<ScriptLoader> mLoader;
+ RefPtr<ScriptLoadRequest> mRequest;
public:
- nsScriptRequestProcessor(nsScriptLoader* aLoader,
- nsScriptLoadRequest* aRequest)
+ ScriptRequestProcessor(ScriptLoader* aLoader,
+ ScriptLoadRequest* aRequest)
: mLoader(aLoader)
, mRequest(aRequest)
{}
@@ -1428,24 +1174,23 @@ CSPAllowsInlineScript(nsIScriptElement *aElement, nsIDocument *aDocument)
return allowInlineScript;
}
-nsScriptLoadRequest*
-nsScriptLoader::CreateLoadRequest(nsScriptKind aKind,
- nsIScriptElement* aElement,
- uint32_t aVersion, CORSMode aCORSMode,
- const SRIMetadata &aIntegrity)
+ScriptLoadRequest*
+ScriptLoader::CreateLoadRequest(ScriptKind aKind,
+ nsIScriptElement* aElement,
+ uint32_t aVersion, CORSMode aCORSMode,
+ const SRIMetadata &aIntegrity)
{
- if (aKind == nsScriptKind::Classic) {
- return new nsScriptLoadRequest(aKind, aElement, aVersion, aCORSMode,
- aIntegrity);
+ if (aKind == ScriptKind::Classic) {
+ return new ScriptLoadRequest(aKind, aElement, aVersion, aCORSMode,
+ aIntegrity);
}
- MOZ_ASSERT(aKind == nsScriptKind::Module);
- return new nsModuleLoadRequest(aElement, aVersion, aCORSMode, aIntegrity,
- this);
+ MOZ_ASSERT(aKind == ScriptKind::Module);
+ return new ModuleLoadRequest(aElement, aVersion, aCORSMode, aIntegrity, this);
}
bool
-nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
+ScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
{
// We need a document to evaluate scripts.
NS_ENSURE_TRUE(mDocument, false);
@@ -1471,10 +1216,10 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
nsAutoString type;
bool hasType = aElement->GetScriptType(type);
- nsScriptKind scriptKind = nsScriptKind::Classic;
+ ScriptKind scriptKind = ScriptKind::Classic;
if (!type.IsEmpty()) {
if (ModuleScriptsEnabled() && type.LowerCaseEqualsASCII("module")) {
- scriptKind = nsScriptKind::Module;
+ scriptKind = ScriptKind::Module;
} else {
NS_ENSURE_TRUE(ParseTypeAttribute(type, &version), false);
}
@@ -1498,7 +1243,7 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
// "The nomodule attribute must not be specified on module scripts (and will
// be ignored if it is)."
if (ModuleScriptsEnabled() &&
- scriptKind == nsScriptKind::Classic &&
+ scriptKind == ScriptKind::Classic &&
scriptContent->IsHTMLElement() &&
scriptContent->HasAttr(kNameSpaceID_None, nsGkAtoms::nomodule)) {
return false;
@@ -1506,7 +1251,7 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
// Step 15. and later in the HTML5 spec
nsresult rv = NS_OK;
- RefPtr<nsScriptLoadRequest> request;
+ RefPtr<ScriptLoadRequest> request;
if (aElement->GetScriptExternal()) {
// external script
nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
@@ -1562,7 +1307,7 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
integrity);
if (!integrity.IsEmpty()) {
MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
- ("nsScriptLoader::ProcessScriptElement, integrity=%s",
+ ("ScriptLoader::ProcessScriptElement, integrity=%s",
NS_ConvertUTF16toUTF8(integrity).get()));
nsAutoCString sourceUri;
if (mDocument->GetDocumentURI()) {
@@ -1704,19 +1449,25 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
request->mLineNo = aElement->GetScriptLineNumber();
if (request->IsModuleRequest()) {
- nsModuleLoadRequest* modReq = request->AsModuleRequest();
+ ModuleLoadRequest* modReq = request->AsModuleRequest();
modReq->mBaseURL = mDocument->GetDocBaseURI();
rv = CreateModuleScript(modReq);
- NS_ENSURE_SUCCESS(rv, false);
- StartFetchingModuleDependencies(modReq);
+ MOZ_ASSERT(NS_FAILED(rv) == !modReq->mModuleScript);
+ if (NS_FAILED(rv)) {
+ modReq->LoadFailed();
+ return false;
+ }
if (aElement->GetScriptAsync()) {
mLoadingAsyncRequests.AppendElement(request);
} else {
AddDeferRequest(request);
}
+ if (!modReq->mModuleScript->IsErrored()) {
+ StartFetchingModuleDependencies(modReq);
+ }
return false;
}
- request->mProgress = nsScriptLoadRequest::Progress::Ready;
+ request->mProgress = ScriptLoadRequest::Progress::Ready;
if (aElement->GetParserCreated() == FROM_PARSER_XSLT &&
(!ReadyToExecuteParserBlockingScripts() || !mXSLTRequests.isEmpty())) {
// Need to maintain order for XSLT-inserted scripts
@@ -1728,7 +1479,7 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
if (aElement->GetParserCreated() == NOT_FROM_PARSER) {
NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
"A script-inserted script is inserted without an update batch?");
- nsContentUtils::AddScriptRunner(new nsScriptRequestProcessor(this,
+ nsContentUtils::AddScriptRunner(new ScriptRequestProcessor(this,
request));
return false;
}
@@ -1758,13 +1509,13 @@ namespace {
class NotifyOffThreadScriptLoadCompletedRunnable : public Runnable
{
- RefPtr<nsScriptLoadRequest> mRequest;
- RefPtr<nsScriptLoader> mLoader;
+ RefPtr<ScriptLoadRequest> mRequest;
+ RefPtr<ScriptLoader> mLoader;
void *mToken;
public:
- NotifyOffThreadScriptLoadCompletedRunnable(nsScriptLoadRequest* aRequest,
- nsScriptLoader* aLoader)
+ NotifyOffThreadScriptLoadCompletedRunnable(ScriptLoadRequest* aRequest,
+ ScriptLoader* aLoader)
: mRequest(aRequest), mLoader(aLoader), mToken(nullptr)
{}
@@ -1781,21 +1532,17 @@ public:
} /* anonymous namespace */
nsresult
-nsScriptLoader::ProcessOffThreadRequest(nsScriptLoadRequest* aRequest)
+ScriptLoader::ProcessOffThreadRequest(ScriptLoadRequest* aRequest)
{
- MOZ_ASSERT(aRequest->mProgress == nsScriptLoadRequest::Progress::Compiling);
+ MOZ_ASSERT(aRequest->mProgress == ScriptLoadRequest::Progress::Compiling);
MOZ_ASSERT(!aRequest->mWasCompiledOMT);
aRequest->mWasCompiledOMT = true;
if (aRequest->IsModuleRequest()) {
MOZ_ASSERT(aRequest->mOffThreadToken);
- nsModuleLoadRequest* request = aRequest->AsModuleRequest();
- nsresult rv = ProcessFetchedModuleSource(request);
- if (NS_FAILED(rv)) {
- request->LoadFailed();
- }
- return rv;
+ ModuleLoadRequest* request = aRequest->AsModuleRequest();
+ return ProcessFetchedModuleSource(request);
}
aRequest->SetReady();
@@ -1837,8 +1584,8 @@ NotifyOffThreadScriptLoadCompletedRunnable::Run()
// We want these to be dropped on the main thread, once we return from this
// function.
- RefPtr<nsScriptLoadRequest> request = mRequest.forget();
- RefPtr<nsScriptLoader> loader = mLoader.forget();
+ RefPtr<ScriptLoadRequest> request = mRequest.forget();
+ RefPtr<ScriptLoader> loader = mLoader.forget();
request->mOffThreadToken = mToken;
nsresult rv = loader->ProcessOffThreadRequest(request);
@@ -1856,7 +1603,7 @@ OffThreadScriptLoaderCallback(void *aToken, void *aCallbackData)
}
nsresult
-nsScriptLoader::AttemptAsyncScriptCompile(nsScriptLoadRequest* aRequest)
+ScriptLoader::AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest)
{
MOZ_ASSERT_IF(!aRequest->IsModuleRequest(), aRequest->IsReadyToRun());
MOZ_ASSERT(!aRequest->mWasCompiledOMT);
@@ -1909,14 +1656,14 @@ nsScriptLoader::AttemptAsyncScriptCompile(nsScriptLoadRequest* aRequest)
}
mDocument->BlockOnload();
- aRequest->mProgress = nsScriptLoadRequest::Progress::Compiling;
+ aRequest->mProgress = ScriptLoadRequest::Progress::Compiling;
Unused << runnable.forget();
return NS_OK;
}
nsresult
-nsScriptLoader::CompileOffThreadOrProcessRequest(nsScriptLoadRequest* aRequest)
+ScriptLoader::CompileOffThreadOrProcessRequest(ScriptLoadRequest* aRequest)
{
NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
"Processing requests when running scripts is unsafe.");
@@ -1934,7 +1681,7 @@ nsScriptLoader::CompileOffThreadOrProcessRequest(nsScriptLoadRequest* aRequest)
}
SourceBufferHolder
-nsScriptLoader::GetScriptSource(nsScriptLoadRequest* aRequest, nsAutoString& inlineData)
+ScriptLoader::GetScriptSource(ScriptLoadRequest* aRequest, nsAutoString& inlineData)
{
// Return a SourceBufferHolder object holding the script's source text.
// |inlineData| is used to hold the text for inline objects.
@@ -1955,7 +1702,7 @@ nsScriptLoader::GetScriptSource(nsScriptLoadRequest* aRequest, nsAutoString& inl
}
nsresult
-nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest)
+ScriptLoader::ProcessRequest(ScriptLoadRequest* aRequest)
{
NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
"Processing requests when running scripts is unsafe.");
@@ -1967,7 +1714,7 @@ nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest)
if (aRequest->IsModuleRequest() &&
!aRequest->AsModuleRequest()->mModuleScript)
{
- // There was an error parsing a module script. Nothing to do here.
+ // There was an error fetching a module script. Nothing to do here.
FireScriptAvailable(NS_ERROR_FAILURE, aRequest);
return NS_OK;
}
@@ -2055,8 +1802,8 @@ nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest)
}
void
-nsScriptLoader::FireScriptAvailable(nsresult aResult,
- nsScriptLoadRequest* aRequest)
+ScriptLoader::FireScriptAvailable(nsresult aResult,
+ ScriptLoadRequest* aRequest)
{
for (int32_t i = 0; i < mObservers.Count(); i++) {
nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
@@ -2069,8 +1816,8 @@ nsScriptLoader::FireScriptAvailable(nsresult aResult,
}
void
-nsScriptLoader::FireScriptEvaluated(nsresult aResult,
- nsScriptLoadRequest* aRequest)
+ScriptLoader::FireScriptEvaluated(nsresult aResult,
+ ScriptLoadRequest* aRequest)
{
for (int32_t i = 0; i < mObservers.Count(); i++) {
nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
@@ -2082,7 +1829,7 @@ nsScriptLoader::FireScriptEvaluated(nsresult aResult,
}
already_AddRefed<nsIScriptGlobalObject>
-nsScriptLoader::GetScriptGlobalObject()
+ScriptLoader::GetScriptGlobalObject()
{
nsCOMPtr<nsIDocument> master = mDocument->MasterDocument();
nsPIDOMWindowInner *pwin = master->GetInnerWindow();
@@ -2103,10 +1850,10 @@ nsScriptLoader::GetScriptGlobalObject()
}
nsresult
-nsScriptLoader::FillCompileOptionsForRequest(const AutoJSAPI&jsapi,
- nsScriptLoadRequest* aRequest,
- JS::Handle<JSObject*> aScopeChain,
- JS::CompileOptions* aOptions)
+ScriptLoader::FillCompileOptionsForRequest(const AutoJSAPI&jsapi,
+ ScriptLoadRequest* aRequest,
+ JS::Handle<JSObject*> aScopeChain,
+ JS::CompileOptions* aOptions)
{
// It's very important to use aRequest->mURI, not the final URI of the channel
// aRequest ended up getting script data from, as the script filename.
@@ -2150,7 +1897,7 @@ nsScriptLoader::FillCompileOptionsForRequest(const AutoJSAPI&jsapi,
}
nsresult
-nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest)
+ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest)
{
// We need a document to evaluate scripts.
if (!mDocument) {
@@ -2189,8 +1936,8 @@ nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest)
// http://www.whatwg.org/specs/web-apps/current-work/#execute-the-script-block
nsAutoMicroTask mt;
AutoEntryScript aes(globalObject, "<script> element", true);
- JS::Rooted<JSObject*> global(aes.cx(),
- globalObject->GetGlobalJSObject());
+ JSContext* cx = aes.cx();
+ JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
bool oldProcessingScriptTag = context->GetProcessingScriptTag();
context->SetProcessingScriptTag(true);
@@ -2211,28 +1958,38 @@ nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest)
}
if (aRequest->IsModuleRequest()) {
- nsModuleLoadRequest* request = aRequest->AsModuleRequest();
+ rv = EnsureModuleResolveHook(cx);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ModuleLoadRequest* request = aRequest->AsModuleRequest();
MOZ_ASSERT(request->mModuleScript);
MOZ_ASSERT(!request->mOffThreadToken);
- nsModuleScript* ms = request->mModuleScript;
- MOZ_ASSERT(!ms->IsUninstantiated());
- if (ms->InstantiationFailed()) {
- JS::Rooted<JS::Value> exception(aes.cx(), ms->Exception());
- JS_SetPendingException(aes.cx(), exception);
- rv = NS_ERROR_FAILURE;
- } else {
- JS::Rooted<JSObject*> module(aes.cx(), ms->ModuleRecord());
- MOZ_ASSERT(module);
- rv = nsJSUtils::ModuleEvaluation(aes.cx(), module);
+
+ ModuleScript* moduleScript = request->mModuleScript;
+ if (moduleScript->IsErrored()) {
+ // Module has an error status
+ JS::Rooted<JS::Value> error(cx, moduleScript->Error());
+ JS_SetPendingException(cx, error);
+ return NS_OK; // An error is reported by AutoEntryScript.
+ }
+
+ JS::Rooted<JSObject*> module(cx, moduleScript->ModuleRecord());
+ MOZ_ASSERT(module);
+
+ rv = nsJSUtils::ModuleEvaluate(cx, module);
+ MOZ_ASSERT(NS_FAILED(rv) == aes.HasException());
+ if (NS_FAILED(rv)) {
+ // Evaluation failed
+ rv = NS_OK; // An error is reported by AutoEntryScript.
}
} else {
- JS::CompileOptions options(aes.cx());
+ JS::CompileOptions options(cx);
rv = FillCompileOptionsForRequest(aes, aRequest, global, &options);
if (NS_SUCCEEDED(rv)) {
nsAutoString inlineData;
SourceBufferHolder srcBuf = GetScriptSource(aRequest, inlineData);
- rv = nsJSUtils::EvaluateString(aes.cx(), srcBuf, global, options,
+ rv = nsJSUtils::EvaluateString(cx, srcBuf, global, options,
aRequest->OffThreadTokenPtr());
}
}
@@ -2243,7 +2000,7 @@ nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest)
}
void
-nsScriptLoader::ProcessPendingRequestsAsync()
+ScriptLoader::ProcessPendingRequestsAsync()
{
if (mParserBlockingRequest ||
!mXSLTRequests.isEmpty() ||
@@ -2252,14 +2009,14 @@ nsScriptLoader::ProcessPendingRequestsAsync()
!mDeferRequests.isEmpty() ||
!mPendingChildLoaders.IsEmpty()) {
NS_DispatchToCurrentThread(NewRunnableMethod(this,
- &nsScriptLoader::ProcessPendingRequests));
+ &ScriptLoader::ProcessPendingRequests));
}
}
void
-nsScriptLoader::ProcessPendingRequests()
+ScriptLoader::ProcessPendingRequests()
{
- RefPtr<nsScriptLoadRequest> request;
+ RefPtr<ScriptLoadRequest> request;
if (mParserBlockingRequest &&
mParserBlockingRequest->IsReadyToRun() &&
@@ -2311,7 +2068,7 @@ nsScriptLoader::ProcessPendingRequests()
while (!mPendingChildLoaders.IsEmpty() &&
ReadyToExecuteParserBlockingScripts()) {
- RefPtr<nsScriptLoader> child = mPendingChildLoaders[0];
+ RefPtr<ScriptLoader> child = mPendingChildLoaders[0];
mPendingChildLoaders.RemoveElementAt(0);
child->RemoveParserBlockingScriptExecutionBlocker();
}
@@ -2337,7 +2094,7 @@ nsScriptLoader::ProcessPendingRequests()
}
bool
-nsScriptLoader::ReadyToExecuteParserBlockingScripts()
+ScriptLoader::ReadyToExecuteParserBlockingScripts()
{
// Make sure the SelfReadyToExecuteParserBlockingScripts check is first, so
// that we don't block twice on an ancestor.
@@ -2346,7 +2103,7 @@ nsScriptLoader::ReadyToExecuteParserBlockingScripts()
}
for (nsIDocument* doc = mDocument; doc; doc = doc->GetParentDocument()) {
- nsScriptLoader* ancestor = doc->ScriptLoader();
+ ScriptLoader* ancestor = doc->ScriptLoader();
if (!ancestor->SelfReadyToExecuteParserBlockingScripts() &&
ancestor->AddPendingChildLoader(this)) {
AddParserBlockingScriptExecutionBlocker();
@@ -2394,10 +2151,10 @@ nsScriptLoader::ReadyToExecuteParserBlockingScripts()
}
/* static */ nsresult
-nsScriptLoader::ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData,
- uint32_t aLength, const nsAString& aHintCharset,
- nsIDocument* aDocument,
- char16_t*& aBufOut, size_t& aLengthOut)
+ScriptLoader::ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData,
+ uint32_t aLength, const nsAString& aHintCharset,
+ nsIDocument* aDocument,
+ char16_t*& aBufOut, size_t& aLengthOut)
{
if (!aLength) {
aBufOut = nullptr;
@@ -2474,14 +2231,14 @@ nsScriptLoader::ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData,
}
nsresult
-nsScriptLoader::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
- nsISupports* aContext,
- nsresult aChannelStatus,
- nsresult aSRIStatus,
- mozilla::Vector<char16_t> &aString,
- mozilla::dom::SRICheckDataVerifier* aSRIDataVerifier)
-{
- nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext);
+ScriptLoader::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
+ nsISupports* aContext,
+ nsresult aChannelStatus,
+ nsresult aSRIStatus,
+ mozilla::Vector<char16_t> &aString,
+ mozilla::dom::SRICheckDataVerifier* aSRIDataVerifier)
+{
+ ScriptLoadRequest* request = static_cast<ScriptLoadRequest*>(aContext);
NS_ASSERTION(request, "null request in stream complete handler");
NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
@@ -2511,7 +2268,7 @@ nsScriptLoader::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
if (loadInfo->GetEnforceSRI()) {
MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
- ("nsScriptLoader::OnStreamComplete, required SRI not found"));
+ ("ScriptLoader::OnStreamComplete, required SRI not found"));
nsCOMPtr<nsIContentSecurityPolicy> csp;
loadInfo->LoadingPrincipal()->GetCsp(getter_AddRefs(csp));
nsAutoCString violationURISpec;
@@ -2544,29 +2301,29 @@ nsScriptLoader::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
MOZ_ASSERT_IF(request->IsModuleRequest(),
request->AsModuleRequest()->IsTopLevel());
if (request->isInList()) {
- RefPtr<nsScriptLoadRequest> req = mDeferRequests.Steal(request);
+ RefPtr<ScriptLoadRequest> req = mDeferRequests.Steal(request);
FireScriptAvailable(rv, req);
}
} else if (request->mIsAsync) {
MOZ_ASSERT_IF(request->IsModuleRequest(),
request->AsModuleRequest()->IsTopLevel());
if (request->isInList()) {
- RefPtr<nsScriptLoadRequest> req = mLoadingAsyncRequests.Steal(request);
+ RefPtr<ScriptLoadRequest> req = mLoadingAsyncRequests.Steal(request);
FireScriptAvailable(rv, req);
}
} else if (request->mIsNonAsyncScriptInserted) {
if (request->isInList()) {
- RefPtr<nsScriptLoadRequest> req =
+ RefPtr<ScriptLoadRequest> req =
mNonAsyncExternalScriptInsertedRequests.Steal(request);
FireScriptAvailable(rv, req);
}
} else if (request->mIsXSLT) {
if (request->isInList()) {
- RefPtr<nsScriptLoadRequest> req = mXSLTRequests.Steal(request);
+ RefPtr<ScriptLoadRequest> req = mXSLTRequests.Steal(request);
FireScriptAvailable(rv, req);
}
} else if (request->IsModuleRequest()) {
- nsModuleLoadRequest* modReq = request->AsModuleRequest();
+ ModuleLoadRequest* modReq = request->AsModuleRequest();
MOZ_ASSERT(!modReq->IsTopLevel());
MOZ_ASSERT(!modReq->isInList());
modReq->Cancel();
@@ -2597,19 +2354,19 @@ nsScriptLoader::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
}
void
-nsScriptLoader::UnblockParser(nsScriptLoadRequest* aParserBlockingRequest)
+ScriptLoader::UnblockParser(ScriptLoadRequest* aParserBlockingRequest)
{
aParserBlockingRequest->mElement->UnblockParser();
}
void
-nsScriptLoader::ContinueParserAsync(nsScriptLoadRequest* aParserBlockingRequest)
+ScriptLoader::ContinueParserAsync(ScriptLoadRequest* aParserBlockingRequest)
{
aParserBlockingRequest->mElement->ContinueParserAsync();
}
uint32_t
-nsScriptLoader::NumberOfProcessors()
+ScriptLoader::NumberOfProcessors()
{
if (mNumberOfProcessors > 0)
return mNumberOfProcessors;
@@ -2621,7 +2378,7 @@ nsScriptLoader::NumberOfProcessors()
}
void
-nsScriptLoader::MaybeMoveToLoadedList(nsScriptLoadRequest* aRequest)
+ScriptLoader::MaybeMoveToLoadedList(ScriptLoadRequest* aRequest)
{
MOZ_ASSERT(aRequest->IsReadyToRun());
@@ -2631,17 +2388,17 @@ nsScriptLoader::MaybeMoveToLoadedList(nsScriptLoadRequest* aRequest)
if (aRequest->mIsAsync) {
MOZ_ASSERT(aRequest->isInList());
if (aRequest->isInList()) {
- RefPtr<nsScriptLoadRequest> req = mLoadingAsyncRequests.Steal(aRequest);
+ RefPtr<ScriptLoadRequest> req = mLoadingAsyncRequests.Steal(aRequest);
mLoadedAsyncRequests.AppendElement(req);
}
}
}
nsresult
-nsScriptLoader::PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
- nsIIncrementalStreamLoader* aLoader,
- nsresult aStatus,
- mozilla::Vector<char16_t> &aString)
+ScriptLoader::PrepareLoadedRequest(ScriptLoadRequest* aRequest,
+ nsIIncrementalStreamLoader* aLoader,
+ nsresult aStatus,
+ mozilla::Vector<char16_t> &aString)
{
if (NS_FAILED(aStatus)) {
return aStatus;
@@ -2713,7 +2470,7 @@ nsScriptLoader::PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
"aRequest should be pending!");
if (aRequest->IsModuleRequest()) {
- nsModuleLoadRequest* request = aRequest->AsModuleRequest();
+ ModuleLoadRequest* request = aRequest->AsModuleRequest();
// When loading a module, only responses with a JavaScript MIME type are
// acceptable.
@@ -2744,7 +2501,7 @@ nsScriptLoader::PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
MOZ_ASSERT(!aRequest->IsModuleRequest());
nsresult rv = AttemptAsyncScriptCompile(aRequest);
if (rv == NS_OK) {
- MOZ_ASSERT(aRequest->mProgress == nsScriptLoadRequest::Progress::Compiling,
+ MOZ_ASSERT(aRequest->mProgress == ScriptLoadRequest::Progress::Compiling,
"Request should be off-thread compiling now.");
return NS_OK;
}
@@ -2763,7 +2520,7 @@ nsScriptLoader::PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
}
void
-nsScriptLoader::ParsingComplete(bool aTerminated)
+ScriptLoader::ParsingComplete(bool aTerminated)
{
if (mDeferEnabled) {
// Have to check because we apparently get ParsingComplete
@@ -2789,12 +2546,12 @@ nsScriptLoader::ParsingComplete(bool aTerminated)
}
void
-nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
- const nsAString &aType,
- const nsAString &aCrossOrigin,
- const nsAString& aIntegrity,
- bool aScriptFromHead,
- const mozilla::net::ReferrerPolicy aReferrerPolicy)
+ScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
+ const nsAString &aType,
+ const nsAString &aCrossOrigin,
+ const nsAString& aIntegrity,
+ bool aScriptFromHead,
+ const mozilla::net::ReferrerPolicy aReferrerPolicy)
{
NS_ENSURE_TRUE_VOID(mDocument);
// Check to see if scripts has been turned off.
@@ -2810,7 +2567,7 @@ nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
SRIMetadata sriMetadata;
if (!aIntegrity.IsEmpty()) {
MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
- ("nsScriptLoader::PreloadURI, integrity=%s",
+ ("ScriptLoader::PreloadURI, integrity=%s",
NS_ConvertUTF16toUTF8(aIntegrity).get()));
nsAutoCString sourceUri;
if (mDocument->GetDocumentURI()) {
@@ -2819,8 +2576,8 @@ nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
SRICheck::IntegrityMetadata(aIntegrity, sourceUri, mReporter, &sriMetadata);
}
- RefPtr<nsScriptLoadRequest> request =
- CreateLoadRequest(nsScriptKind::Classic, nullptr, 0,
+ RefPtr<ScriptLoadRequest> request =
+ CreateLoadRequest(ScriptKind::Classic, nullptr, 0,
Element::StringToCORSMode(aCrossOrigin), sriMetadata);
request->mURI = aURI;
request->mIsInline = false;
@@ -2837,7 +2594,7 @@ nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
}
void
-nsScriptLoader::AddDeferRequest(nsScriptLoadRequest* aRequest)
+ScriptLoader::AddDeferRequest(ScriptLoadRequest* aRequest)
{
aRequest->mIsDefer = true;
mDeferRequests.AppendElement(aRequest);
@@ -2850,7 +2607,7 @@ nsScriptLoader::AddDeferRequest(nsScriptLoadRequest* aRequest)
}
bool
-nsScriptLoader::MaybeRemovedDeferRequests()
+ScriptLoader::MaybeRemovedDeferRequests()
{
if (mDeferRequests.isEmpty() && mDocument &&
mBlockingDOMContentLoaded) {
@@ -2861,201 +2618,5 @@ nsScriptLoader::MaybeRemovedDeferRequests()
return false;
}
-//////////////////////////////////////////////////////////////
-// nsScriptLoadHandler
-//////////////////////////////////////////////////////////////
-
-nsScriptLoadHandler::nsScriptLoadHandler(nsScriptLoader *aScriptLoader,
- nsScriptLoadRequest *aRequest,
- mozilla::dom::SRICheckDataVerifier *aSRIDataVerifier)
- : mScriptLoader(aScriptLoader),
- mRequest(aRequest),
- mSRIDataVerifier(aSRIDataVerifier),
- mSRIStatus(NS_OK),
- mDecoder(),
- mBuffer()
-{}
-
-nsScriptLoadHandler::~nsScriptLoadHandler()
-{}
-
-NS_IMPL_ISUPPORTS(nsScriptLoadHandler, nsIIncrementalStreamLoaderObserver)
-
-NS_IMETHODIMP
-nsScriptLoadHandler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
- nsISupports* aContext,
- uint32_t aDataLength,
- const uint8_t* aData,
- uint32_t *aConsumedLength)
-{
- if (mRequest->IsCanceled()) {
- // If request cancelled, ignore any incoming data.
- *aConsumedLength = aDataLength;
- return NS_OK;
- }
-
- if (!EnsureDecoder(aLoader, aData, aDataLength,
- /* aEndOfStream = */ false)) {
- return NS_OK;
- }
-
- // Below we will/shall consume entire data chunk.
- *aConsumedLength = aDataLength;
-
- // Decoder has already been initialized. -- trying to decode all loaded bytes.
- nsresult rv = TryDecodeRawData(aData, aDataLength,
- /* aEndOfStream = */ false);
- NS_ENSURE_SUCCESS(rv, rv);
-
- // If SRI is required for this load, appending new bytes to the hash.
- if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
- mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
- }
-
- return rv;
-}
-
-nsresult
-nsScriptLoadHandler::TryDecodeRawData(const uint8_t* aData,
- uint32_t aDataLength,
- bool aEndOfStream)
-{
- int32_t srcLen = aDataLength;
- const char* src = reinterpret_cast<const char *>(aData);
- int32_t dstLen;
- nsresult rv =
- mDecoder->GetMaxLength(src, srcLen, &dstLen);
-
- NS_ENSURE_SUCCESS(rv, rv);
-
- uint32_t haveRead = mBuffer.length();
-
- CheckedInt<uint32_t> capacity = haveRead;
- capacity += dstLen;
-
- if (!capacity.isValid() || !mBuffer.reserve(capacity.value())) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
-
- rv = mDecoder->Convert(src,
- &srcLen,
- mBuffer.begin() + haveRead,
- &dstLen);
-
- NS_ENSURE_SUCCESS(rv, rv);
-
- haveRead += dstLen;
- MOZ_ASSERT(haveRead <= capacity.value(), "mDecoder produced more data than expected");
- MOZ_ALWAYS_TRUE(mBuffer.resizeUninitialized(haveRead));
-
- return NS_OK;
-}
-
-bool
-nsScriptLoadHandler::EnsureDecoder(nsIIncrementalStreamLoader *aLoader,
- const uint8_t* aData,
- uint32_t aDataLength,
- bool aEndOfStream)
-{
- // Check if decoder has already been created.
- if (mDecoder) {
- return true;
- }
-
- nsAutoCString charset;
-
- // JavaScript modules are always UTF-8.
- if (mRequest->IsModuleRequest()) {
- charset = "UTF-8";
- mDecoder = EncodingUtils::DecoderForEncoding(charset);
- return true;
- }
-
- // Determine if BOM check should be done. This occurs either
- // if end-of-stream has been reached, or at least 3 bytes have
- // been read from input.
- if (!aEndOfStream && (aDataLength < 3)) {
- return false;
- }
-
- // Do BOM detection.
- if (nsContentUtils::CheckForBOM(aData, aDataLength, charset)) {
- mDecoder = EncodingUtils::DecoderForEncoding(charset);
- return true;
- }
-
- // BOM detection failed, check content stream for charset.
- nsCOMPtr<nsIRequest> req;
- nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
- NS_ASSERTION(req, "StreamLoader's request went away prematurely");
- NS_ENSURE_SUCCESS(rv, false);
-
- nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
-
- if (channel &&
- NS_SUCCEEDED(channel->GetContentCharset(charset)) &&
- EncodingUtils::FindEncodingForLabel(charset, charset)) {
- mDecoder = EncodingUtils::DecoderForEncoding(charset);
- return true;
- }
-
- // Check the hint charset from the script element or preload
- // request.
- nsAutoString hintCharset;
- if (!mRequest->IsPreload()) {
- mRequest->mElement->GetScriptCharset(hintCharset);
- } else {
- nsTArray<nsScriptLoader::PreloadInfo>::index_type i =
- mScriptLoader->mPreloads.IndexOf(mRequest, 0,
- nsScriptLoader::PreloadRequestComparator());
-
- NS_ASSERTION(i != mScriptLoader->mPreloads.NoIndex,
- "Incorrect preload bookkeeping");
- hintCharset = mScriptLoader->mPreloads[i].mCharset;
- }
-
- if (EncodingUtils::FindEncodingForLabel(hintCharset, charset)) {
- mDecoder = EncodingUtils::DecoderForEncoding(charset);
- return true;
- }
-
- // Get the charset from the charset of the document.
- if (mScriptLoader->mDocument) {
- charset = mScriptLoader->mDocument->GetDocumentCharacterSet();
- mDecoder = EncodingUtils::DecoderForEncoding(charset);
- return true;
- }
-
- // Curiously, there are various callers that don't pass aDocument. The
- // fallback in the old code was ISO-8859-1, which behaved like
- // windows-1252. Saying windows-1252 for clarity and for compliance
- // with the Encoding Standard.
- charset = "windows-1252";
- mDecoder = EncodingUtils::DecoderForEncoding(charset);
- return true;
-}
-
-NS_IMETHODIMP
-nsScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
- nsISupports* aContext,
- nsresult aStatus,
- uint32_t aDataLength,
- const uint8_t* aData)
-{
- if (!mRequest->IsCanceled()) {
- DebugOnly<bool> encoderSet =
- EnsureDecoder(aLoader, aData, aDataLength, /* aEndOfStream = */ true);
- MOZ_ASSERT(encoderSet);
- DebugOnly<nsresult> rv = TryDecodeRawData(aData, aDataLength,
- /* aEndOfStream = */ true);
-
- // If SRI is required for this load, appending new bytes to the hash.
- if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
- mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
- }
- }
-
- // we have to mediate and use mRequest.
- return mScriptLoader->OnStreamComplete(aLoader, mRequest, aStatus, mSRIStatus,
- mBuffer, mSRIDataVerifier);
-}
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/base/nsScriptLoader.h b/dom/script/ScriptLoader.h
index a00239be5..e6b75bf3b 100644
--- a/dom/base/nsScriptLoader.h
+++ b/dom/script/ScriptLoader.h
@@ -4,12 +4,8 @@
* 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 <script> elements.
- */
-
-#ifndef __nsScriptLoader_h__
-#define __nsScriptLoader_h__
+#ifndef mozilla_dom_ScriptLoader_h
+#define mozilla_dom_ScriptLoader_h
#include "nsCOMPtr.h"
#include "nsRefPtrHashtable.h"
@@ -25,14 +21,10 @@
#include "mozilla/CORSMode.h"
#include "mozilla/dom/SRIMetadata.h"
#include "mozilla/dom/SRICheck.h"
-#include "mozilla/LinkedList.h"
#include "mozilla/MozPromise.h"
#include "mozilla/net/ReferrerPolicy.h"
#include "mozilla/Vector.h"
-class nsModuleLoadRequest;
-class nsModuleScript;
-class nsScriptLoadRequestList;
class nsIURI;
namespace JS {
@@ -41,37 +33,39 @@ namespace JS {
namespace mozilla {
namespace dom {
+
class AutoJSAPI;
-} // namespace dom
-} // namespace mozilla
+class ModuleLoadRequest;
+class ModuleScript;
+class ScriptLoadRequestList;
//////////////////////////////////////////////////////////////
// Per-request data structure
//////////////////////////////////////////////////////////////
-enum class nsScriptKind {
+enum class ScriptKind {
Classic,
Module
};
-class nsScriptLoadRequest : public nsISupports,
- private mozilla::LinkedListElement<nsScriptLoadRequest>
+class ScriptLoadRequest : public nsISupports,
+ private mozilla::LinkedListElement<ScriptLoadRequest>
{
- typedef LinkedListElement<nsScriptLoadRequest> super;
+ typedef LinkedListElement<ScriptLoadRequest> super;
- // Allow LinkedListElement<nsScriptLoadRequest> to cast us to itself as needed.
- friend class mozilla::LinkedListElement<nsScriptLoadRequest>;
- friend class nsScriptLoadRequestList;
+ // Allow LinkedListElement<ScriptLoadRequest> to cast us to itself as needed.
+ friend class mozilla::LinkedListElement<ScriptLoadRequest>;
+ friend class ScriptLoadRequestList;
protected:
- virtual ~nsScriptLoadRequest();
+ virtual ~ScriptLoadRequest();
public:
- nsScriptLoadRequest(nsScriptKind aKind,
- nsIScriptElement* aElement,
- uint32_t aVersion,
- mozilla::CORSMode aCORSMode,
- const mozilla::dom::SRIMetadata &aIntegrity)
+ ScriptLoadRequest(ScriptKind aKind,
+ nsIScriptElement* aElement,
+ uint32_t aVersion,
+ mozilla::CORSMode aCORSMode,
+ const mozilla::dom::SRIMetadata &aIntegrity)
: mKind(aKind),
mElement(aElement),
mProgress(Progress::Loading),
@@ -95,14 +89,14 @@ public:
}
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
- NS_DECL_CYCLE_COLLECTION_CLASS(nsScriptLoadRequest)
+ NS_DECL_CYCLE_COLLECTION_CLASS(ScriptLoadRequest)
bool IsModuleRequest() const
{
- return mKind == nsScriptKind::Module;
+ return mKind == ScriptKind::Module;
}
- nsModuleLoadRequest* AsModuleRequest();
+ ModuleLoadRequest* AsModuleRequest();
void FireScriptAvailable(nsresult aResult)
{
@@ -154,7 +148,7 @@ public:
using super::getNext;
using super::isInList;
- const nsScriptKind mKind;
+ const ScriptKind mKind;
nsCOMPtr<nsIScriptElement> mElement;
Progress mProgress; // Are we still waiting for a load to complete?
bool mIsInline; // Is the script inline or loaded?
@@ -179,23 +173,23 @@ public:
mozilla::net::ReferrerPolicy mReferrerPolicy;
};
-class nsScriptLoadRequestList : private mozilla::LinkedList<nsScriptLoadRequest>
+class ScriptLoadRequestList : private mozilla::LinkedList<ScriptLoadRequest>
{
- typedef mozilla::LinkedList<nsScriptLoadRequest> super;
+ typedef mozilla::LinkedList<ScriptLoadRequest> super;
public:
- ~nsScriptLoadRequestList();
+ ~ScriptLoadRequestList();
void Clear();
#ifdef DEBUG
- bool Contains(nsScriptLoadRequest* aElem) const;
+ bool Contains(ScriptLoadRequest* aElem) const;
#endif // DEBUG
using super::getFirst;
using super::isEmpty;
- void AppendElement(nsScriptLoadRequest* aElem)
+ void AppendElement(ScriptLoadRequest* aElem)
{
MOZ_ASSERT(!aElem->isInList());
NS_ADDREF(aElem);
@@ -203,36 +197,37 @@ public:
}
MOZ_MUST_USE
- already_AddRefed<nsScriptLoadRequest> Steal(nsScriptLoadRequest* aElem)
+ already_AddRefed<ScriptLoadRequest> Steal(ScriptLoadRequest* aElem)
{
aElem->removeFrom(*this);
return dont_AddRef(aElem);
}
MOZ_MUST_USE
- already_AddRefed<nsScriptLoadRequest> StealFirst()
+ already_AddRefed<ScriptLoadRequest> StealFirst()
{
MOZ_ASSERT(!isEmpty());
return Steal(getFirst());
}
- void Remove(nsScriptLoadRequest* aElem)
+ void Remove(ScriptLoadRequest* aElem)
{
aElem->removeFrom(*this);
NS_RELEASE(aElem);
}
};
+class ScriptLoadHandler;
//////////////////////////////////////////////////////////////
// Script loader implementation
//////////////////////////////////////////////////////////////
-class nsScriptLoader final : public nsISupports
+class ScriptLoader final : public nsISupports
{
class MOZ_STACK_CLASS AutoCurrentScriptUpdater
{
public:
- AutoCurrentScriptUpdater(nsScriptLoader* aScriptLoader,
+ AutoCurrentScriptUpdater(ScriptLoader* aScriptLoader,
nsIScriptElement* aCurrentScript)
: mOldScript(aScriptLoader->mCurrentScript)
, mScriptLoader(aScriptLoader)
@@ -245,19 +240,19 @@ class nsScriptLoader final : public nsISupports
}
private:
nsCOMPtr<nsIScriptElement> mOldScript;
- nsScriptLoader* mScriptLoader;
+ ScriptLoader* mScriptLoader;
};
- friend class nsModuleLoadRequest;
- friend class nsScriptRequestProcessor;
- friend class nsScriptLoadHandler;
+ friend class ModuleLoadRequest;
+ friend class ScriptRequestProcessor;
+ friend class ScriptLoadHandler;
friend class AutoCurrentScriptUpdater;
public:
- explicit nsScriptLoader(nsIDocument* aDocument);
+ explicit ScriptLoader(nsIDocument* aDocument);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
- NS_DECL_CYCLE_COLLECTION_CLASS(nsScriptLoader)
+ NS_DECL_CYCLE_COLLECTION_CLASS(ScriptLoader)
/**
* The loader maintains a weak reference to the document with
@@ -395,7 +390,7 @@ public:
/**
* Handle the completion of a stream. This is called by the
- * nsScriptLoadHandler object which observes the IncrementalStreamLoader
+ * ScriptLoadHandler object which observes the IncrementalStreamLoader
* loading the script.
*/
nsresult OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
@@ -463,17 +458,17 @@ public:
* Process a request that was deferred so that the script could be compiled
* off thread.
*/
- nsresult ProcessOffThreadRequest(nsScriptLoadRequest *aRequest);
+ nsresult ProcessOffThreadRequest(ScriptLoadRequest *aRequest);
- bool AddPendingChildLoader(nsScriptLoader* aChild) {
+ bool AddPendingChildLoader(ScriptLoader* aChild) {
return mPendingChildLoaders.AppendElement(aChild) != nullptr;
}
private:
- virtual ~nsScriptLoader();
+ virtual ~ScriptLoader();
- nsScriptLoadRequest* CreateLoadRequest(
- nsScriptKind aKind,
+ ScriptLoadRequest* CreateLoadRequest(
+ ScriptKind aKind,
nsIScriptElement* aElement,
uint32_t aVersion,
mozilla::CORSMode aCORSMode,
@@ -482,12 +477,12 @@ private:
/**
* Unblocks the creator parser of the parser-blocking scripts.
*/
- void UnblockParser(nsScriptLoadRequest* aParserBlockingRequest);
+ void UnblockParser(ScriptLoadRequest* aParserBlockingRequest);
/**
* Asynchronously resumes the creator parser of the parser-blocking scripts.
*/
- void ContinueParserAsync(nsScriptLoadRequest* aParserBlockingRequest);
+ void ContinueParserAsync(ScriptLoadRequest* aParserBlockingRequest);
/**
@@ -502,7 +497,7 @@ private:
/**
* Start a load for aRequest's URI.
*/
- nsresult StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType,
+ nsresult StartLoad(ScriptLoadRequest *aRequest, const nsAString &aType,
bool aScriptFromHead);
/**
@@ -539,83 +534,84 @@ private:
return mEnabled && !mBlockerCount;
}
- nsresult AttemptAsyncScriptCompile(nsScriptLoadRequest* aRequest);
- nsresult ProcessRequest(nsScriptLoadRequest* aRequest);
- nsresult CompileOffThreadOrProcessRequest(nsScriptLoadRequest* aRequest);
+ nsresult AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest);
+ nsresult ProcessRequest(ScriptLoadRequest* aRequest);
+ nsresult CompileOffThreadOrProcessRequest(ScriptLoadRequest* aRequest);
void FireScriptAvailable(nsresult aResult,
- nsScriptLoadRequest* aRequest);
+ ScriptLoadRequest* aRequest);
void FireScriptEvaluated(nsresult aResult,
- nsScriptLoadRequest* aRequest);
- nsresult EvaluateScript(nsScriptLoadRequest* aRequest);
+ ScriptLoadRequest* aRequest);
+ nsresult EvaluateScript(ScriptLoadRequest* aRequest);
already_AddRefed<nsIScriptGlobalObject> GetScriptGlobalObject();
nsresult FillCompileOptionsForRequest(const mozilla::dom::AutoJSAPI& jsapi,
- nsScriptLoadRequest* aRequest,
+ ScriptLoadRequest* aRequest,
JS::Handle<JSObject*> aScopeChain,
JS::CompileOptions* aOptions);
uint32_t NumberOfProcessors();
- nsresult PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
+ nsresult PrepareLoadedRequest(ScriptLoadRequest* aRequest,
nsIIncrementalStreamLoader* aLoader,
nsresult aStatus,
mozilla::Vector<char16_t> &aString);
- void AddDeferRequest(nsScriptLoadRequest* aRequest);
+ void AddDeferRequest(ScriptLoadRequest* aRequest);
bool MaybeRemovedDeferRequests();
- void MaybeMoveToLoadedList(nsScriptLoadRequest* aRequest);
+ void MaybeMoveToLoadedList(ScriptLoadRequest* aRequest);
- JS::SourceBufferHolder GetScriptSource(nsScriptLoadRequest* aRequest,
+ JS::SourceBufferHolder GetScriptSource(ScriptLoadRequest* aRequest,
nsAutoString& inlineData);
bool ModuleScriptsEnabled();
- void SetModuleFetchStarted(nsModuleLoadRequest *aRequest);
- void SetModuleFetchFinishedAndResumeWaitingRequests(nsModuleLoadRequest *aRequest,
+ void SetModuleFetchStarted(ModuleLoadRequest *aRequest);
+ void SetModuleFetchFinishedAndResumeWaitingRequests(ModuleLoadRequest *aRequest,
nsresult aResult);
- bool IsFetchingModule(nsModuleLoadRequest *aRequest) const;
+ bool IsFetchingModule(ModuleLoadRequest *aRequest) const;
- bool ModuleMapContainsModule(nsModuleLoadRequest *aRequest) const;
- RefPtr<mozilla::GenericPromise> WaitForModuleFetch(nsModuleLoadRequest *aRequest);
- nsModuleScript* GetFetchedModule(nsIURI* aURL) const;
+ bool ModuleMapContainsModule(ModuleLoadRequest *aRequest) const;
+ RefPtr<mozilla::GenericPromise> WaitForModuleFetch(ModuleLoadRequest *aRequest);
+ ModuleScript* GetFetchedModule(nsIURI* aURL) const;
friend bool
HostResolveImportedModule(JSContext* aCx, unsigned argc, JS::Value* vp);
- nsresult CreateModuleScript(nsModuleLoadRequest* aRequest);
- nsresult ProcessFetchedModuleSource(nsModuleLoadRequest* aRequest);
- void ProcessLoadedModuleTree(nsModuleLoadRequest* aRequest);
- bool InstantiateModuleTree(nsModuleLoadRequest* aRequest);
- void StartFetchingModuleDependencies(nsModuleLoadRequest* aRequest);
+ nsresult CreateModuleScript(ModuleLoadRequest* aRequest);
+ nsresult ProcessFetchedModuleSource(ModuleLoadRequest* aRequest);
+ void CheckModuleDependenciesLoaded(ModuleLoadRequest* aRequest);
+ void ProcessLoadedModuleTree(ModuleLoadRequest* aRequest);
+ bool InstantiateModuleTree(ModuleLoadRequest* aRequest);
+ void StartFetchingModuleDependencies(ModuleLoadRequest* aRequest);
RefPtr<mozilla::GenericPromise>
- StartFetchingModuleAndDependencies(nsModuleLoadRequest* aRequest, nsIURI* aURI);
+ StartFetchingModuleAndDependencies(ModuleLoadRequest* aRequest, nsIURI* aURI);
nsIDocument* mDocument; // [WEAK]
nsCOMArray<nsIScriptLoaderObserver> mObservers;
- nsScriptLoadRequestList mNonAsyncExternalScriptInsertedRequests;
+ ScriptLoadRequestList mNonAsyncExternalScriptInsertedRequests;
// mLoadingAsyncRequests holds async requests while they're loading; when they
// have been loaded they are moved to mLoadedAsyncRequests.
- nsScriptLoadRequestList mLoadingAsyncRequests;
- nsScriptLoadRequestList mLoadedAsyncRequests;
- nsScriptLoadRequestList mDeferRequests;
- nsScriptLoadRequestList mXSLTRequests;
- RefPtr<nsScriptLoadRequest> mParserBlockingRequest;
+ ScriptLoadRequestList mLoadingAsyncRequests;
+ ScriptLoadRequestList mLoadedAsyncRequests;
+ ScriptLoadRequestList mDeferRequests;
+ ScriptLoadRequestList mXSLTRequests;
+ RefPtr<ScriptLoadRequest> mParserBlockingRequest;
// In mRequests, the additional information here is stored by the element.
struct PreloadInfo {
- RefPtr<nsScriptLoadRequest> mRequest;
+ RefPtr<ScriptLoadRequest> mRequest;
nsString mCharset;
};
- friend void ImplCycleCollectionUnlink(nsScriptLoader::PreloadInfo& aField);
+ friend void ImplCycleCollectionUnlink(ScriptLoader::PreloadInfo& aField);
friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
- nsScriptLoader::PreloadInfo& aField,
+ ScriptLoader::PreloadInfo& aField,
const char* aName, uint32_t aFlags);
struct PreloadRequestComparator {
- bool Equals(const PreloadInfo &aPi, nsScriptLoadRequest * const &aRequest)
+ bool Equals(const PreloadInfo &aPi, ScriptLoadRequest * const &aRequest)
const
{
return aRequest == aPi.mRequest;
@@ -628,7 +624,7 @@ private:
nsCOMPtr<nsIScriptElement> mCurrentScript;
nsCOMPtr<nsIScriptElement> mCurrentParserInsertedScript;
- nsTArray< RefPtr<nsScriptLoader> > mPendingChildLoaders;
+ nsTArray< RefPtr<ScriptLoader> > mPendingChildLoaders;
uint32_t mParserBlockingBlockerCount;
uint32_t mBlockerCount;
uint32_t mNumberOfProcessors;
@@ -639,58 +635,11 @@ private:
// Module map
nsRefPtrHashtable<nsURIHashKey, mozilla::GenericPromise::Private> mFetchingModules;
- nsRefPtrHashtable<nsURIHashKey, nsModuleScript> mFetchedModules;
+ nsRefPtrHashtable<nsURIHashKey, ModuleScript> mFetchedModules;
nsCOMPtr<nsIConsoleReportCollector> mReporter;
};
-class nsScriptLoadHandler final : public nsIIncrementalStreamLoaderObserver
-{
-public:
- explicit nsScriptLoadHandler(nsScriptLoader* aScriptLoader,
- nsScriptLoadRequest *aRequest,
- mozilla::dom::SRICheckDataVerifier *aSRIDataVerifier);
-
- NS_DECL_ISUPPORTS
- NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
-
-private:
- virtual ~nsScriptLoadHandler();
-
- /*
- * Try to decode some raw data.
- */
- nsresult TryDecodeRawData(const uint8_t* aData, uint32_t aDataLength,
- bool aEndOfStream);
-
- /*
- * Discover the charset by looking at the stream data, the script
- * tag, and other indicators. Returns true if charset has been
- * discovered.
- */
- bool EnsureDecoder(nsIIncrementalStreamLoader *aLoader,
- const uint8_t* aData, uint32_t aDataLength,
- bool aEndOfStream);
-
- // ScriptLoader which will handle the parsed script.
- RefPtr<nsScriptLoader> mScriptLoader;
-
- // The nsScriptLoadRequest for this load.
- RefPtr<nsScriptLoadRequest> mRequest;
-
- // SRI data verifier.
- nsAutoPtr<mozilla::dom::SRICheckDataVerifier> mSRIDataVerifier;
-
- // Status of SRI data operations.
- nsresult mSRIStatus;
-
- // Unicode decoder for charset.
- nsCOMPtr<nsIUnicodeDecoder> mDecoder;
-
- // Accumulated decoded char buffer.
- mozilla::Vector<char16_t> mBuffer;
-};
-
class nsAutoScriptLoaderDisabler
{
public:
@@ -711,7 +660,10 @@ public:
}
bool mWasEnabled;
- RefPtr<nsScriptLoader> mLoader;
+ RefPtr<ScriptLoader> mLoader;
};
-#endif //__nsScriptLoader_h__
+} // namespace dom
+} // namespace mozilla
+
+#endif //mozilla_dom_ScriptLoader_h
diff --git a/dom/base/ScriptSettings.cpp b/dom/script/ScriptSettings.cpp
index d67f2167a..92ab221c9 100644
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/script/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
diff --git a/dom/base/ScriptSettings.h b/dom/script/ScriptSettings.h
index 05e62f55e..05e62f55e 100644
--- a/dom/base/ScriptSettings.h
+++ b/dom/script/ScriptSettings.h
diff --git a/dom/script/moz.build b/dom/script/moz.build
new file mode 100644
index 000000000..280850ed4
--- /dev/null
+++ b/dom/script/moz.build
@@ -0,0 +1,36 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+XPIDL_SOURCES += [
+ 'nsIScriptLoaderObserver.idl',
+]
+
+XPIDL_MODULE = 'dom'
+
+EXPORTS += ['nsIScriptElement.h']
+
+EXPORTS.mozilla.dom += [
+ 'ScriptElement.h',
+ 'ScriptLoader.h',
+ 'ScriptSettings.h',
+]
+
+SOURCES += [
+ 'ModuleLoadRequest.cpp',
+ 'ModuleScript.cpp',
+ 'ScriptElement.cpp',
+ 'ScriptLoader.cpp',
+ 'ScriptLoadHandler.cpp',
+ 'ScriptSettings.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/dom/workers',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/base/nsIScriptElement.h b/dom/script/nsIScriptElement.h
index 470d51c94..470d51c94 100644
--- a/dom/base/nsIScriptElement.h
+++ b/dom/script/nsIScriptElement.h
diff --git a/dom/base/nsIScriptLoaderObserver.idl b/dom/script/nsIScriptLoaderObserver.idl
index ed7196525..ed7196525 100644
--- a/dom/base/nsIScriptLoaderObserver.idl
+++ b/dom/script/nsIScriptLoaderObserver.idl
diff --git a/dom/svg/SVGScriptElement.cpp b/dom/svg/SVGScriptElement.cpp
index ffc049c21..f2fb3ff5c 100644
--- a/dom/svg/SVGScriptElement.cpp
+++ b/dom/svg/SVGScriptElement.cpp
@@ -42,7 +42,7 @@ NS_IMPL_ISUPPORTS_INHERITED(SVGScriptElement, SVGScriptElementBase,
SVGScriptElement::SVGScriptElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
FromParser aFromParser)
: SVGScriptElementBase(aNodeInfo)
- , nsScriptElement(aFromParser)
+ , ScriptElement(aFromParser)
{
AddMutationObserver(this);
}
@@ -168,7 +168,7 @@ SVGScriptElement::FreezeUriAsyncDefer()
}
//----------------------------------------------------------------------
-// nsScriptElement methods
+// ScriptElement methods
bool
SVGScriptElement::HasScriptContent()
diff --git a/dom/svg/SVGScriptElement.h b/dom/svg/SVGScriptElement.h
index 620a1bcde..9f098e047 100644
--- a/dom/svg/SVGScriptElement.h
+++ b/dom/svg/SVGScriptElement.h
@@ -10,7 +10,7 @@
#include "nsSVGElement.h"
#include "nsCOMPtr.h"
#include "nsSVGString.h"
-#include "nsScriptElement.h"
+#include "mozilla/dom/ScriptElement.h"
class nsIDocument;
@@ -24,7 +24,7 @@ namespace dom {
typedef nsSVGElement SVGScriptElementBase;
class SVGScriptElement final : public SVGScriptElementBase,
- public nsScriptElement
+ public ScriptElement
{
protected:
friend nsresult (::NS_NewSVGScriptElement(nsIContent **aResult,
@@ -47,7 +47,7 @@ public:
virtual void FreezeUriAsyncDefer() override;
virtual CORSMode GetCORSMode() const override;
- // nsScriptElement
+ // ScriptElement
virtual bool HasScriptContent() override;
// nsIContent specializations:
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..668e609ec
--- /dev/null
+++ b/dom/tests/mochitest/fetch/file_fetch_observer.html
@@ -0,0 +1,146 @@
+<script>
+function ok(a, msg) {
+ parent.postMessage({ type: "check", status: !!a, message: msg }, "*");
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+function testObserver() {
+ ok("FetchObserver" in self, "We have a FetchObserver prototype");
+
+ 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 AbortController();
+
+ 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 AbortController();
+
+ 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 AbortController();
+
+ 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 AbortController();
+
+ 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() {
+ if (!steps.length) {
+ parent.postMessage({ type: "finish" }, "*");
+ return;
+ }
+
+ var step = steps.shift();
+ step();
+}
+
+next();
+
+</script>
diff --git a/dom/tests/mochitest/fetch/mochitest.ini b/dom/tests/mochitest/fetch/mochitest.ini
index cf4477463..a9447d0d9 100644
--- a/dom/tests/mochitest/fetch/mochitest.ini
+++ b/dom/tests/mochitest/fetch/mochitest.ini
@@ -4,6 +4,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
@@ -15,6 +16,7 @@ support-files =
reroute.html
reroute.js
reroute.js^headers^
+ slow.sjs
sw_reroute.js
empty.js
empty.js^headers^
@@ -47,6 +49,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/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
new file mode 100644
index 000000000..2af86977c
--- /dev/null
+++ b/dom/tests/mochitest/fetch/test_fetch_observer.html
@@ -0,0 +1,41 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test FetchObserver</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+
+SpecialPowers.pushPrefEnv({"set": [["dom.fetchObserver.enabled", true ],
+ ["dom.fetchController.enabled", true ]]}, () => {
+ let ifr = document.createElement('iframe');
+ ifr.src = "file_fetch_observer.html";
+ document.body.appendChild(ifr);
+
+ onmessage = function(e) {
+ if (e.data.type == "finish") {
+ SimpleTest.finish();
+ return;
+ }
+
+ if (e.data.type == "check") {
+ ok(e.data.status, e.data.message);
+ return;
+ }
+
+ ok(false, "Something when wrong.");
+ }
+});
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
+
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1269155
+-->
+<head>
+ <title>Test for Bug 1269155</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1269155">Mozilla Bug 1269155</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1269155 **/
+var host = document.querySelector('#content');
+ var root = host.createShadowRoot();
+
+ var header1 = document.createElement('h1');
+ header1.textContent = 'Shadow Header1';
+
+ var paragraph1 = document.createElement('p');
+ paragraph1.textContent = 'shadow text paragraph1';
+
+ root.appendChild(header1);
+ root.appendChild(paragraph1);
+
+var root2 = paragraph1.createShadowRoot();
+var header2 = document.createElement('h2');
+header2.textContent = 'Shadow Header2';
+
+var paragraph2 = document.createElement('p');
+paragraph2.textContent = 'shadow text paragraph2';
+root2.appendChild(header2);
+root2.appendChild(paragraph2);
+
+
+var frag = document.createDocumentFragment();
+var paragraph3 = document.createElement('p');
+paragraph3.textContent = 'fragment paragraph3';
+frag.appendChild(paragraph3);
+
+var root3 = paragraph3.createShadowRoot();
+var header4 = document.createElement('h2');
+header4.textContent = 'Shadow Header3';
+
+var paragraph4 = document.createElement('p');
+paragraph4.textContent = 'shadow text paragraph4';
+
+root3.appendChild(header4);
+root3.appendChild(paragraph4);
+
+//shadow dom without compose
+is(root.getRootNode(), root, "root.getRootNode() should be root.");
+is(root2.getRootNode(), root2, "root2.getRootNode() should be root.");
+is(root3.getRootNode(), root3, "root3.getRootNode() should be root.");
+is(header1.getRootNode(), root, "header1.getRootNode() should be root.");
+is(header2.getRootNode(), root2, "header1.getRootNode() should be root2.");
+is(header4.getRootNode(), root3, "header1.getRootNode() should be root3.");
+//shadow dom with compose
+is(root.getRootNode({ composed: true }), document, "root.getRootNode() with composed flag should be document.");
+is(root2.getRootNode({ composed: true }), document, "root2.getRootNode() with composed flag should be document.");
+is(root3.getRootNode({ composed: true }), frag, "root3.getRootNode() with composed flag should be frag.");
+is(header1.getRootNode({ composed: true }) , document, "header1.getRootNode() with composed flag should be document.");
+is(header2.getRootNode({ composed: true }) , document, "header2.getRootNode() with composed flag should be document.");
+is(header4.getRootNode({ composed: true }) , frag, "head4.getRootNode() with composed flag should be frag.");
+//dom without compose
+is(host.getRootNode(), document, "host.getRootNode() should be document.");
+is(header1.getRootNode(), root, "header1.getRootNode() should be root.");
+is(paragraph1.getRootNode(), root, "paragraph1.getRootNode() should be root.");
+is(frag.getRootNode(), frag, "frag.getRootNode() should be frag.");
+//dom with compose
+is(host.getRootNode({ composed: true }) , document, "host.getRootNode() with composed flag should be document.");
+is(header1.getRootNode({ composed: true }) , document, "header1.getRootNode() with composed flag should be document.");
+is(paragraph1.getRootNode({ composed: true }) , document, "paragraph1.getRootNode() with composed flag should be document.");
+is(frag.getRootNode({ composed: true }) , frag, "frag.getRootNode() with composed flag should be frag.");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/webidl/AbortController.webidl b/dom/webidl/AbortController.webidl
new file mode 100644
index 000000000..4e9124075
--- /dev/null
+++ b/dom/webidl/AbortController.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/.
+ */
+
+[Constructor(), Exposed=(Window,Worker),
+ Func="AbortController::IsEnabled"]
+interface AbortController {
+ readonly attribute AbortSignal signal;
+
+ void abort();
+};
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/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/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/dom/webidl/Request.webidl b/dom/webidl/Request.webidl
index e29c084d0..fe6a63ec0 100644
--- a/dom/webidl/Request.webidl
+++ b/dom/webidl/Request.webidl
@@ -47,6 +47,12 @@ dictionary RequestInit {
RequestCache cache;
RequestRedirect redirect;
DOMString integrity;
+
+ [Func="AbortController::IsEnabled"]
+ AbortSignal 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 5e913585e..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',
@@ -142,6 +144,7 @@ WEBIDL_FILES = [
'FakePluginTagInit.webidl',
'Fetch.webidl',
'FetchEvent.webidl',
+ 'FetchObserver.webidl',
'File.webidl',
'FileList.webidl',
'FileMode.webidl',
diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp
index 56b18441e..bcec94dcb 100644
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -34,7 +34,6 @@
#include "nsIPipe.h"
#include "nsIOutputStream.h"
#include "nsPrintfCString.h"
-#include "nsScriptLoader.h"
#include "nsString.h"
#include "nsStreamUtils.h"
#include "nsTArray.h"
@@ -58,6 +57,7 @@
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/Response.h"
+#include "mozilla/dom/ScriptLoader.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/SRILogHelper.h"
#include "mozilla/UniquePtr.h"
@@ -1075,14 +1075,14 @@ private:
// May be null.
nsIDocument* parentDoc = mWorkerPrivate->GetDocument();
- // Use the regular nsScriptLoader for this grunt work! Should be just fine
+ // Use the regular ScriptLoader for this grunt work! Should be just fine
// because we're running on the main thread.
// Unlike <script> tags, Worker scripts are always decoded as UTF-8,
// per spec. So we explicitly pass in the charset hint.
- rv = nsScriptLoader::ConvertToUTF16(aLoadInfo.mChannel, aString, aStringLen,
- NS_LITERAL_STRING("UTF-8"), parentDoc,
- aLoadInfo.mScriptTextBuf,
- aLoadInfo.mScriptTextLength);
+ rv = ScriptLoader::ConvertToUTF16(aLoadInfo.mChannel, aString, aStringLen,
+ NS_LITERAL_STRING("UTF-8"), parentDoc,
+ aLoadInfo.mScriptTextBuf,
+ aLoadInfo.mScriptTextLength);
if (NS_FAILED(rv)) {
return rv;
}
@@ -1289,10 +1289,10 @@ private:
MOZ_ASSERT(!loadInfo.mScriptTextBuf);
nsresult rv =
- nsScriptLoader::ConvertToUTF16(nullptr, aString, aStringLen,
- NS_LITERAL_STRING("UTF-8"), parentDoc,
- loadInfo.mScriptTextBuf,
- loadInfo.mScriptTextLength);
+ ScriptLoader::ConvertToUTF16(nullptr, aString, aStringLen,
+ NS_LITERAL_STRING("UTF-8"), parentDoc,
+ loadInfo.mScriptTextBuf,
+ loadInfo.mScriptTextLength);
if (NS_SUCCEEDED(rv) && IsMainWorkerScript()) {
nsCOMPtr<nsIURI> finalURI;
rv = NS_NewURI(getter_AddRefs(finalURI), loadInfo.mFullURL, nullptr, nullptr);
diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
index a8f191f2e..306ef6b23 100644
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -22,7 +22,6 @@
#include "nsITimer.h"
#include "nsIUploadChannel2.h"
#include "nsPIDOMWindow.h"
-#include "nsScriptLoader.h"
#include "nsServiceManagerUtils.h"
#include "nsDebug.h"
#include "nsISupportsPrimitives.h"
@@ -44,6 +43,7 @@
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/Request.h"
#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ScriptLoader.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
diff --git a/dom/workers/ServiceWorkerScriptCache.cpp b/dom/workers/ServiceWorkerScriptCache.cpp
index f44bb673c..707b689e8 100644
--- a/dom/workers/ServiceWorkerScriptCache.cpp
+++ b/dom/workers/ServiceWorkerScriptCache.cpp
@@ -11,6 +11,7 @@
#include "mozilla/dom/cache/Cache.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseWorkerProxy.h"
+#include "mozilla/dom/ScriptLoader.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "nsICacheInfoChannel.h"
@@ -23,7 +24,6 @@
#include "nsIScriptError.h"
#include "nsContentUtils.h"
#include "nsNetUtil.h"
-#include "nsScriptLoader.h"
#include "ServiceWorkerManager.h"
#include "Workers.h"
#include "nsStringStream.h"
@@ -801,9 +801,9 @@ CompareNetwork::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext
char16_t* buffer = nullptr;
size_t len = 0;
- rv = nsScriptLoader::ConvertToUTF16(httpChannel, aString, aLen,
- NS_LITERAL_STRING("UTF-8"), nullptr,
- buffer, len);
+ rv = ScriptLoader::ConvertToUTF16(httpChannel, aString, aLen,
+ NS_LITERAL_STRING("UTF-8"), nullptr,
+ buffer, len);
if (NS_WARN_IF(NS_FAILED(rv))) {
mManager->NetworkFinished(rv);
return rv;
@@ -855,9 +855,9 @@ CompareCache::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
char16_t* buffer = nullptr;
size_t len = 0;
- nsresult rv = nsScriptLoader::ConvertToUTF16(nullptr, aString, aLen,
- NS_LITERAL_STRING("UTF-8"),
- nullptr, buffer, len);
+ nsresult rv = ScriptLoader::ConvertToUTF16(nullptr, aString, aLen,
+ NS_LITERAL_STRING("UTF-8"),
+ nullptr, buffer, len);
if (NS_WARN_IF(NS_FAILED(rv))) {
mManager->CacheFinished(rv, false);
return rv;
diff --git a/dom/workers/WorkerPrefs.h b/dom/workers/WorkerPrefs.h
index 9a1be4801..b552d8956 100644
--- a/dom/workers/WorkerPrefs.h
+++ b/dom/workers/WorkerPrefs.h
@@ -39,6 +39,8 @@ 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.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)
WORKER_PREF("general.appname.override", AppNameOverrideChanged)
diff --git a/dom/worklet/Worklet.cpp b/dom/worklet/Worklet.cpp
index 19a877ea8..1b71916ab 100644
--- a/dom/worklet/Worklet.cpp
+++ b/dom/worklet/Worklet.cpp
@@ -12,11 +12,11 @@
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/RegisterWorkletBindings.h"
#include "mozilla/dom/Response.h"
+#include "mozilla/dom/ScriptLoader.h"
#include "mozilla/dom/ScriptSettings.h"
#include "nsIInputStreamPump.h"
#include "nsIThreadRetargetableRequest.h"
#include "nsNetUtil.h"
-#include "nsScriptLoader.h"
#include "xpcprivate.h"
namespace mozilla {
@@ -171,9 +171,9 @@ public:
char16_t* scriptTextBuf;
size_t scriptTextLength;
nsresult rv =
- nsScriptLoader::ConvertToUTF16(nullptr, aString, aStringLen,
- NS_LITERAL_STRING("UTF-8"), nullptr,
- scriptTextBuf, scriptTextLength);
+ ScriptLoader::ConvertToUTF16(nullptr, aString, aStringLen,
+ NS_LITERAL_STRING("UTF-8"), nullptr,
+ scriptTextBuf, scriptTextLength);
if (NS_WARN_IF(NS_FAILED(rv))) {
RejectPromises(rv);
return NS_OK;
diff --git a/dom/xml/nsXMLContentSink.cpp b/dom/xml/nsXMLContentSink.cpp
index 7c9d308fd..daf1dbf27 100644
--- a/dom/xml/nsXMLContentSink.cpp
+++ b/dom/xml/nsXMLContentSink.cpp
@@ -35,7 +35,6 @@
#include "nsRect.h"
#include "nsIWebNavigation.h"
#include "nsIScriptElement.h"
-#include "nsScriptLoader.h"
#include "nsStyleLinkElement.h"
#include "nsReadableUtils.h"
#include "nsUnicharUtils.h"
@@ -62,6 +61,7 @@
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLTemplateElement.h"
#include "mozilla/dom/ProcessingInstruction.h"
+#include "mozilla/dom/ScriptLoader.h"
using namespace mozilla;
using namespace mozilla::dom;
diff --git a/dom/xml/nsXMLFragmentContentSink.cpp b/dom/xml/nsXMLFragmentContentSink.cpp
index 7fa815c84..7d9f86987 100644
--- a/dom/xml/nsXMLFragmentContentSink.cpp
+++ b/dom/xml/nsXMLFragmentContentSink.cpp
@@ -24,10 +24,10 @@
#include "nsTArray.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIDocShell.h"
-#include "nsScriptLoader.h"
#include "mozilla/css/Loader.h"
#include "mozilla/dom/DocumentFragment.h"
#include "mozilla/dom/ProcessingInstruction.h"
+#include "mozilla/dom/ScriptLoader.h"
using namespace mozilla::dom;
diff --git a/dom/xslt/xslt/txMozillaXMLOutput.cpp b/dom/xslt/xslt/txMozillaXMLOutput.cpp
index 069413d97..704d8ac11 100644
--- a/dom/xslt/xslt/txMozillaXMLOutput.cpp
+++ b/dom/xslt/xslt/txMozillaXMLOutput.cpp
@@ -7,7 +7,6 @@
#include "nsIDocument.h"
#include "nsIDocShell.h"
-#include "nsScriptLoader.h"
#include "nsIDOMDocument.h"
#include "nsIDOMDocumentType.h"
#include "nsIScriptElement.h"
@@ -31,6 +30,7 @@
#include "mozilla/css/Loader.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/ScriptLoader.h"
#include "nsContentUtils.h"
#include "txXMLUtils.h"
#include "nsContentSink.h"
@@ -230,7 +230,7 @@ txMozillaXMLOutput::endDocument(nsresult aResult)
MOZ_ASSERT(mDocument->GetReadyStateEnum() ==
nsIDocument::READYSTATE_LOADING, "Bad readyState");
mDocument->SetReadyStateInternal(nsIDocument::READYSTATE_INTERACTIVE);
- nsScriptLoader* loader = mDocument->ScriptLoader();
+ ScriptLoader* loader = mDocument->ScriptLoader();
if (loader) {
loader->ParsingComplete(false);
}
@@ -416,7 +416,7 @@ txMozillaXMLOutput::startDocument()
}
if (mCreatingNewDocument) {
- nsScriptLoader* loader = mDocument->ScriptLoader();
+ ScriptLoader* loader = mDocument->ScriptLoader();
if (loader) {
loader->BeginDeferringScripts();
}
@@ -857,7 +857,7 @@ txMozillaXMLOutput::createResultDocument(const nsSubstring& aName, int32_t aNsID
}
// Set up script loader of the result document.
- nsScriptLoader *loader = mDocument->ScriptLoader();
+ ScriptLoader *loader = mDocument->ScriptLoader();
if (mNotifier) {
loader->AddObserver(mNotifier);
}
diff --git a/dom/xul/XULDocument.cpp b/dom/xul/XULDocument.cpp
index 1dcb55aee..36481f989 100644
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -3337,10 +3337,10 @@ XULDocument::OnStreamComplete(nsIStreamLoader* aLoader,
!mOffThreadCompileStringBuf),
"XULDocument can't load multiple scripts at once");
- rv = nsScriptLoader::ConvertToUTF16(channel, string, stringLen,
- EmptyString(), this,
- mOffThreadCompileStringBuf,
- mOffThreadCompileStringLength);
+ rv = ScriptLoader::ConvertToUTF16(channel, string, stringLen,
+ EmptyString(), this,
+ mOffThreadCompileStringBuf,
+ mOffThreadCompileStringLength);
if (NS_SUCCEEDED(rv)) {
// Attempt to give ownership of the buffer to the JS engine. If
// we hit offthread compilation, however, we will have to take it
diff --git a/dom/xul/XULDocument.h b/dom/xul/XULDocument.h
index 06abb797f..e72edfeed 100644
--- a/dom/xul/XULDocument.h
+++ b/dom/xul/XULDocument.h
@@ -21,13 +21,13 @@
#include "nsCOMArray.h"
#include "nsIURI.h"
#include "nsIXULDocument.h"
-#include "nsScriptLoader.h"
#include "nsIStreamListener.h"
#include "nsIStreamLoader.h"
#include "nsICSSLoaderObserver.h"
#include "nsIXULStore.h"
#include "mozilla/Attributes.h"
+#include "mozilla/dom/ScriptLoader.h"
#include "js/TracingAPI.h"
#include "js/TypeDecls.h"
diff --git a/dom/xul/nsXULContentSink.cpp b/dom/xul/nsXULContentSink.cpp
index edeee8728..94560e70d 100644
--- a/dom/xul/nsXULContentSink.cpp
+++ b/dom/xul/nsXULContentSink.cpp
@@ -881,7 +881,7 @@ XULContentSinkImpl::OpenScript(const char16_t** aAttributes,
isJavaScript = false;
}
} else if (key.EqualsLiteral("language")) {
- // Language is deprecated, and the impl in nsScriptLoader ignores the
+ // Language is deprecated, and the impl in ScriptLoader ignores the
// various version strings anyway. So we make no attempt to support
// languages other than JS for language=
nsAutoString lang(aAttributes[1]);
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
@@ -31,6 +31,119 @@ 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 545e22f70..d3cbb8775 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,11 +2196,28 @@ HTMLEditRules::WillDeleteSelection(Selection* aSelection,
address_of(selPointNode), &selPointOffset);
NS_ENSURE_STATE(leftNode && leftNode->IsContent() &&
rightNode && rightNode->IsContent());
- *aHandled = true;
- rv = JoinBlocks(*leftNode->AsContent(), *rightNode->AsContent(),
- aCancel);
- 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,
+ // 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<int32_t>(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;
}
@@ -2247,10 +2264,16 @@ HTMLEditRules::WillDeleteSelection(Selection* aSelection,
AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater,
address_of(selPointNode), &selPointOffset);
NS_ENSURE_STATE(leftNode->IsContent() && rightNode->IsContent());
+ 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;
- rv = JoinBlocks(*leftNode->AsContent(), *rightNode->AsContent(),
- aCancel);
- NS_ENSURE_SUCCESS(rv, rv);
+ *aCancel |= ret.Canceled();
+ if (NS_WARN_IF(ret.Failed())) {
+ return ret.Rv();
+ }
}
aSelection->Collapse(selPointNode, selPointOffset);
return NS_OK;
@@ -2421,8 +2444,12 @@ HTMLEditRules::WillDeleteSelection(Selection* aSelection,
}
if (join) {
- rv = JoinBlocks(*leftParent, *rightParent, aCancel);
- NS_ENSURE_SUCCESS(rv, rv);
+ EditActionResult ret = TryToJoinBlocks(*leftParent, *rightParent);
+ MOZ_ASSERT(*aHandled);
+ *aCancel |= ret.Canceled();
+ if (NS_WARN_IF(ret.Failed())) {
+ return ret.Rv();
+ }
}
}
}
@@ -2571,60 +2598,58 @@ 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)
+EditActionResult
+HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode,
+ nsIContent& aRightNode)
{
- MOZ_ASSERT(aCanceled);
+ if (NS_WARN_IF(!mHTMLEditor)) {
+ return EditActionIgnored(NS_ERROR_UNEXPECTED);
+ }
- NS_ENSURE_STATE(mHTMLEditor);
RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
nsCOMPtr<Element> leftBlock = htmlEditor->GetBlock(aLeftNode);
nsCOMPtr<Element> 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;
- 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;
- 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) {
- return NS_OK;
+ return EditActionHandled();
}
// Special rule here: if we are trying to join list items, and they are in
@@ -2665,7 +2690,9 @@ HTMLEditRules::JoinBlocks(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.
@@ -2675,40 +2702,61 @@ HTMLEditRules::JoinBlocks(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();
}
}
// Do br adjustment.
nsCOMPtr<Element> 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.
for (nsCOMPtr<nsIContent> 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?
+ ret.MarkAsHandled();
} else {
- MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset);
+ // XXX Why do we ignore the result of MoveBlock()?
+ EditActionResult retMoveBlock =
+ MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset);
+ if (retMoveBlock.Handled()) {
+ ret.MarkAsHandled();
+ }
}
- if (brNode) {
- htmlEditor->DeleteNode(brNode);
+ 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,
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.
@@ -2718,19 +2766,30 @@ HTMLEditRules::JoinBlocks(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();
}
}
// Do br adjustment.
nsCOMPtr<Element> brNode =
CheckForInvisibleBR(*leftBlock, BRLocation::beforeBlock, leftOffset);
+ EditActionResult ret(NS_OK);
if (mergeLists) {
- MoveContents(*rightList, *leftList, &leftOffset);
+ // 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
@@ -2775,7 +2834,9 @@ HTMLEditRules::JoinBlocks(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();
@@ -2784,58 +2845,67 @@ HTMLEditRules::JoinBlocks(nsIContent& aLeftNode,
}
}
- NS_ENSURE_TRUE(previousContentParent, NS_ERROR_NULL_POINTER);
+ if (NS_WARN_IF(!previousContentParent)) {
+ return EditActionIgnored(NS_ERROR_NULL_POINTER);
+ }
- rv = MoveBlock(*previousContentParent->AsElement(), *rightBlock,
- previousContentOffset, rightOffset);
- NS_ENSURE_SUCCESS(rv, rv);
+ ret |= MoveBlock(*previousContentParent->AsElement(), *rightBlock,
+ previousContentOffset, rightOffset);
+ if (NS_WARN_IF(ret.Failed())) {
+ return ret;
+ }
}
- if (brNode) {
- htmlEditor->DeleteNode(brNode);
+ 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 <p>paragraph</p><ul><li>one<li>two<li>three</ul>. 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);
- NS_ENSURE_SUCCESS(rv, rv);
- // Do br adjustment.
- nsCOMPtr<Element> 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<Element> newBlock;
- ConvertListType(rightBlock, getter_AddRefs(newBlock),
- existingList, nsGkAtoms::li);
- }
- } else {
- // Nodes are dissimilar types.
- rv = MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset);
- NS_ENSURE_SUCCESS(rv, rv);
+ // Normal case. Blocks are siblings, or at least close enough. An example
+ // of the latter is <p>paragraph</p><ul><li>one<li>two<li>three</ul>. 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<Element> 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<Element> 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);
- NS_ENSURE_SUCCESS(rv, rv);
+ }
+ 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 NS_OK;
+ return ret;
}
-
-/**
- * Moves the content from aRightBlock starting from aRightOffset into
- * aLeftBlock at aLeftOffset. Note that the "block" might merely be inline
- * nodes between <br>s, or between blocks, etc. DTD containment rules are
- * followed throughout.
- */
-nsresult
+EditActionResult
HTMLEditRules::MoveBlock(Element& aLeftBlock,
Element& aRightBlock,
int32_t aLeftOffset,
@@ -2846,41 +2916,51 @@ HTMLEditRules::MoveBlock(Element& aLeftBlock,
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.
- rv = MoveContents(*arrayOfNodes[i]->AsElement(), aLeftBlock,
- &aLeftOffset);
- 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]);
+ ret.MarkAsHandled();
} else {
// Otherwise move the content as is, checking against the DTD.
- rv = MoveNodeSmart(*arrayOfNodes[i]->AsContent(), aLeftBlock,
- &aLeftOffset);
+ 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;
}
-/**
- * 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
+EditActionResult
HTMLEditRules::MoveNodeSmart(nsIContent& aNode,
Element& aDestElement,
int32_t* aInOutDestOffset)
{
MOZ_ASSERT(aInOutDestOffset);
- NS_ENSURE_STATE(mHTMLEditor);
+ if (NS_WARN_IF(!mHTMLEditor)) {
+ return EditActionIgnored(NS_ERROR_UNEXPECTED);
+ }
+
RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
// Check if this node can go into the destination node
@@ -2888,44 +2968,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)++;
}
- } else {
- // If it can't, move its children (if any), and then delete it.
- if (aNode.IsElement()) {
- nsresult rv =
- MoveContents(*aNode.AsElement(), aDestElement, aInOutDestOffset);
- NS_ENSURE_SUCCESS(rv, rv);
+ // XXX Should we check if the node is actually moved in this case?
+ return EditActionHandled();
+ }
+
+ // 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);
- NS_ENSURE_SUCCESS(rv, rv);
+ nsresult rv = htmlEditor->DeleteNode(&aNode);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return ret.SetResult(rv);
}
- return NS_OK;
+ return ret.MarkAsHandled();
}
-/**
- * Moves the _contents_ of aElement to (aDestElement, aInOutDestOffset). DTD
- * containment rules are followed throughout. aInOutDestOffset is updated to
- * point _after_ inserted content.
- */
-nsresult
+EditActionResult
HTMLEditRules::MoveContents(Element& aElement,
Element& aDestElement,
int32_t* aInOutDestOffset)
{
MOZ_ASSERT(aInOutDestOffset);
- 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()) {
- nsresult rv = MoveNodeSmart(*aElement.GetFirstChild(), aDestElement,
- aInOutDestOffset);
- 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 40c5e2afd..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;
@@ -163,14 +164,63 @@ protected:
nsresult InsertBRIfNeeded(Selection* aSelection);
mozilla::EditorDOMPoint GetGoodSelPointForNode(nsINode& aNode,
nsIEditor::EDirection aAction);
- nsresult JoinBlocks(nsIContent& aLeftNode, nsIContent& aRightNode,
- bool* aCanceled);
- nsresult MoveBlock(Element& aLeftBlock, Element& aRightBlock,
- int32_t aLeftOffset, int32_t aRightOffset);
- nsresult MoveNodeSmart(nsIContent& aNode, Element& aDestElement,
- int32_t* aOffset);
- nsresult MoveContents(Element& aElement, Element& aDestElement,
- int32_t* aOffset);
+
+ /**
+ * 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.
+ *
+ * @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.
+ */
+ EditActionResult TryToJoinBlocks(nsIContent& aLeftNode,
+ nsIContent& aRightNode);
+
+ /**
+ * MoveBlock() moves the content from aRightBlock starting from aRightOffset
+ * into aLeftBlock at aLeftOffset. Note that the "block" can be inline nodes
+ * between <br>s, or between blocks, etc. DTD containment rules are followed
+ * throughout.
+ *
+ * @return Sets handled to true if this actually joins the nodes.
+ * canceled is always false.
+ */
+ 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.
+ * @return Sets true to handled if this actually moves
+ * the nodes.
+ * canceled is always false.
+ */
+ 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.
+ * @return Sets true to handled if this actually moves
+ * the nodes.
+ * canceled is always false.
+ */
+ EditActionResult MoveContents(Element& aElement, Element& aDestElement,
+ int32_t* aInOutDestOffset);
+
nsresult DeleteNonTableElements(nsINode* aNode);
nsresult WillMakeList(Selection* aSelection,
const nsAString* aListType,
diff --git a/image/encoders/jpeg/nsJPEGEncoder.cpp b/image/encoders/jpeg/nsJPEGEncoder.cpp
index 04cfef07b..e5835c295 100644
--- a/image/encoders/jpeg/nsJPEGEncoder.cpp
+++ b/image/encoders/jpeg/nsJPEGEncoder.cpp
@@ -8,6 +8,7 @@
#include "nsString.h"
#include "nsStreamUtils.h"
#include "gfxColor.h"
+#include "mozilla/CheckedInt.h"
#include <setjmp.h>
#include "jerror.h"
@@ -430,10 +431,14 @@ nsJPEGEncoder::emptyOutputBuffer(jpeg_compress_struct* cinfo)
that->mImageBufferUsed = that->mImageBufferSize;
// expand buffer, just double size each time
- that->mImageBufferSize *= 2;
+ uint8_t* newBuf = nullptr;
+ CheckedInt<uint32_t> bufSize =
+ CheckedInt<uint32_t>(that->mImageBufferSize) * 2;
+ if (bufSize.isValid()) {
+ that->mImageBufferSize = bufSize.value();
+ newBuf = (uint8_t*)realloc(that->mImageBuffer, that->mImageBufferSize);
+ }
- uint8_t* newBuf = (uint8_t*)realloc(that->mImageBuffer,
- that->mImageBufferSize);
if (!newBuf) {
// can't resize, just zero (this will keep us from writing more)
free(that->mImageBuffer);
diff --git a/js/src/builtin/Module.js b/js/src/builtin/Module.js
index 5c3d5e147..64d62d216 100644
--- a/js/src/builtin/Module.js
+++ b/js/src/builtin/Module.js
@@ -2,11 +2,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/. */
-function CallModuleResolveHook(module, specifier, expectedMinimumState)
+function CallModuleResolveHook(module, specifier, expectedMinimumStatus)
{
let requestedModule = HostResolveImportedModule(module, specifier);
- if (requestedModule.state < expectedMinimumState)
- ThrowInternalError(JSMSG_BAD_MODULE_STATE);
+ if (requestedModule.state < expectedMinimumStatus)
+ ThrowInternalError(JSMSG_BAD_MODULE_STATUS);
return requestedModule;
}
@@ -65,7 +65,36 @@ function ModuleGetExportedNames(exportStarSet = [])
return exportedNames;
}
+function ModuleSetStatus(module, newStatus)
+{
+ assert(newStatus >= MODULE_STATUS_ERRORED && newStatus <= MODULE_STATUS_EVALUATED,
+ "Bad new module status in ModuleSetStatus");
+ if (newStatus !== MODULE_STATUS_ERRORED)
+ assert(newStatus > module.status, "New module status inconsistent with current status");
+
+ UnsafeSetReservedSlot(module, MODULE_OBJECT_STATUS_SLOT, newStatus);
+}
+
// 15.2.1.16.3 ResolveExport(exportName, resolveSet)
+//
+// Returns an object describing the location of the resolved export or
+// indicating a failure.
+//
+// On success this returns: { resolved: true, module, bindingName }
+//
+// There are three failure cases:
+//
+// - The resolution failure can be blamed on a particular module.
+// Returns: { resolved: false, module, ambiguous: false }
+//
+// - No culprit can be determined and the resolution failure was due to star
+// export ambiguity.
+// Returns: { resolved: false, module: null, ambiguous: true }
+//
+// - No culprit can be determined and the resolution failure was not due to
+// star export ambiguity.
+// Returns: { resolved: false, module: null, ambiguous: false }
+//
function ModuleResolveExport(exportName, resolveSet = [])
{
if (!IsObject(this) || !IsModule(this)) {
@@ -77,88 +106,104 @@ function ModuleResolveExport(exportName, resolveSet = [])
let module = this;
// Step 2
+ assert(module.status !== MODULE_STATUS_ERRORED, "Bad module status in ResolveExport");
+
+ // Step 3
for (let i = 0; i < resolveSet.length; i++) {
let r = resolveSet[i];
- if (r.module === module && r.exportName === exportName)
- return null;
+ if (r.module === module && r.exportName === exportName) {
+ // This is a circular import request.
+ return {resolved: false, module: null, ambiguous: false};
+ }
}
- // Step 3
+ // Step 4
_DefineDataProperty(resolveSet, resolveSet.length, {module: module, exportName: exportName});
- // Step 4
+ // Step 5
let localExportEntries = module.localExportEntries;
for (let i = 0; i < localExportEntries.length; i++) {
let e = localExportEntries[i];
if (exportName === e.exportName)
- return {module: module, bindingName: e.localName};
+ return {resolved: true, module, bindingName: e.localName};
}
- // Step 5
+ // Step 6
let indirectExportEntries = module.indirectExportEntries;
for (let i = 0; i < indirectExportEntries.length; i++) {
let e = indirectExportEntries[i];
if (exportName === e.exportName) {
let importedModule = CallModuleResolveHook(module, e.moduleRequest,
- MODULE_STATE_PARSED);
- return callFunction(importedModule.resolveExport, importedModule, e.importName,
- resolveSet);
+ MODULE_STATUS_UNINSTANTIATED);
+ let resolution = callFunction(importedModule.resolveExport, importedModule, e.importName,
+ resolveSet);
+ if (!resolution.resolved && !resolution.module)
+ resolution.module = module;
+ return resolution;
}
}
- // Step 6
+ // Step 7
if (exportName === "default") {
// A default export cannot be provided by an export *.
- return null;
+ return {resolved: false, module: null, ambiguous: false};
}
- // Step 7
+ // Step 8
let starResolution = null;
- // Step 8
+ // Step 9
let starExportEntries = module.starExportEntries;
for (let i = 0; i < starExportEntries.length; i++) {
let e = starExportEntries[i];
let importedModule = CallModuleResolveHook(module, e.moduleRequest,
- MODULE_STATE_PARSED);
- let resolution = callFunction(importedModule.resolveExport, importedModule,
- exportName, resolveSet);
- if (resolution === "ambiguous")
+ MODULE_STATUS_UNINSTANTIATED);
+ let resolution = callFunction(importedModule.resolveExport, importedModule, exportName,
+ resolveSet);
+ if (!resolution.resolved && (resolution.module || resolution.ambiguous))
return resolution;
- if (resolution !== null) {
+ if (resolution.resolved) {
if (starResolution === null) {
starResolution = resolution;
} else {
if (resolution.module !== starResolution.module ||
- resolution.exportName !== starResolution.exportName)
+ resolution.bindingName !== starResolution.bindingName)
{
- return "ambiguous";
+ return {resolved: false, module: null, ambiguous: true};
}
}
}
}
- // Step 9
- return starResolution;
+ // Step 10
+ if (starResolution !== null)
+ return starResolution;
+
+ return {resolved: false, module: null, ambiguous: false};
}
// 15.2.1.18 GetModuleNamespace(module)
function GetModuleNamespace(module)
{
+ // Step 1
+ assert(IsModule(module), "GetModuleNamespace called with non-module");
+
// Step 2
- let namespace = module.namespace;
+ assert(module.status !== MODULE_STATUS_UNINSTANTIATED &&
+ module.status !== MODULE_STATUS_ERRORED,
+ "Bad module status in GetModuleNamespace");
// Step 3
+ let namespace = module.namespace;
+
if (typeof namespace === "undefined") {
let exportedNames = callFunction(module.getExportedNames, module);
let unambiguousNames = [];
for (let i = 0; i < exportedNames.length; i++) {
let name = exportedNames[i];
let resolution = callFunction(module.resolveExport, module, name);
- if (resolution === null)
- ThrowSyntaxError(JSMSG_MISSING_NAMESPACE_EXPORT);
- if (resolution !== "ambiguous")
+ if (resolution.resolved)
_DefineDataProperty(unambiguousNames, unambiguousNames.length, name);
}
namespace = ModuleNamespaceCreate(module, unambiguousNames);
@@ -180,7 +225,7 @@ function ModuleNamespaceCreate(module, exports)
for (let i = 0; i < exports.length; i++) {
let name = exports[i];
let binding = callFunction(module.resolveExport, module, name);
- assert(binding !== null && binding !== "ambiguous", "Failed to resolve binding");
+ assert(binding.resolved, "Failed to resolve binding");
AddModuleNamespaceBinding(ns, name, binding.module, binding.bindingName);
}
@@ -193,8 +238,8 @@ function GetModuleEnvironment(module)
// Check for a previous failed attempt to instantiate this module. This can
// only happen due to a bug in the module loader.
- if (module.state == MODULE_STATE_FAILED)
- ThrowInternalError(JSMSG_MODULE_INSTANTIATE_FAILED);
+ if (module.status === MODULE_STATUS_ERRORED)
+ ThrowInternalError(JSMSG_MODULE_INSTANTIATE_FAILED, module.status);
let env = UnsafeGetReservedSlot(module, MODULE_OBJECT_ENVIRONMENT_SLOT);
assert(env === undefined || IsModuleEnvironment(env),
@@ -203,112 +248,401 @@ function GetModuleEnvironment(module)
return env;
}
-function RecordInstantationFailure(module)
+function RecordModuleError(module, error)
{
- // Set the module's state to 'failed' to indicate a failed module
- // instantiation and reset the environment slot to 'undefined'.
- assert(IsModule(module), "Non-module passed to RecordInstantationFailure");
- SetModuleState(module, MODULE_STATE_FAILED);
+ // Set the module's status to 'errored' to indicate a failed module
+ // instantiation and record the exception. The environment slot is also
+ // reset to 'undefined'.
+
+ assert(IsObject(module) && IsModule(module), "Non-module passed to RecordModuleError");
+
+ ModuleSetStatus(module, MODULE_STATUS_ERRORED);
+ UnsafeSetReservedSlot(module, MODULE_OBJECT_ERROR_SLOT, error);
UnsafeSetReservedSlot(module, MODULE_OBJECT_ENVIRONMENT_SLOT, undefined);
}
-// 15.2.1.16.4 ModuleDeclarationInstantiation()
-function ModuleDeclarationInstantiation()
+function CountArrayValues(array, value)
+{
+ let count = 0;
+ for (let i = 0; i < array.length; i++) {
+ if (array[i] === value)
+ count++;
+ }
+ return count;
+}
+
+function ArrayContains(array, value)
+{
+ for (let i = 0; i < array.length; i++) {
+ if (array[i] === value)
+ return true;
+ }
+ return false;
+}
+
+// 15.2.1.16.4 ModuleInstantiate()
+function ModuleInstantiate()
{
if (!IsObject(this) || !IsModule(this))
- return callFunction(CallModuleMethodIfWrapped, this, "ModuleDeclarationInstantiation");
+ return callFunction(CallModuleMethodIfWrapped, this, "ModuleInstantiate");
// Step 1
let module = this;
- // Step 5
- if (GetModuleEnvironment(module) !== undefined)
- return;
+ // Step 2
+ if (module.status === MODULE_STATUS_INSTANTIATING ||
+ module.status === MODULE_STATUS_EVALUATING)
+ {
+ ThrowInternalError(JSMSG_BAD_MODULE_STATUS);
+ }
+
+ // Step 3
+ let stack = [];
+
+ // Steps 4-5
+ try {
+ InnerModuleDeclarationInstantiation(module, stack, 0);
+ } catch (error) {
+ for (let i = 0; i < stack.length; i++) {
+ let m = stack[i];
+
+ assert(m.status === MODULE_STATUS_INSTANTIATING ||
+ m.status === MODULE_STATUS_ERRORED,
+ "Bad module status after failed instantiation");
+
+ RecordModuleError(m, error);
+ }
+
+ if (stack.length === 0 &&
+ typeof(UnsafeGetReservedSlot(module, MODULE_OBJECT_ERROR_SLOT)) === "undefined")
+ {
+ // This can happen due to OOM when appending to the stack.
+ assert(error === "out of memory",
+ "Stack must contain module unless we hit OOM");
+ RecordModuleError(module, error);
+ }
+
+ assert(module.status === MODULE_STATUS_ERRORED,
+ "Bad module status after failed instantiation");
+ assert(SameValue(UnsafeGetReservedSlot(module, MODULE_OBJECT_ERROR_SLOT), error),
+ "Module has different error set after failed instantiation");
+
+ throw error;
+ }
+
+ // Step 6
+ assert(module.status == MODULE_STATUS_INSTANTIATED ||
+ module.status == MODULE_STATUS_EVALUATED,
+ "Bad module status after successful instantiation");
// Step 7
- CreateModuleEnvironment(module);
- let env = GetModuleEnvironment(module);
+ assert(stack.length === 0,
+ "Stack should be empty after successful instantiation");
- SetModuleState(this, MODULE_STATE_INSTANTIATED);
+ // Step 8
+ return undefined;
+}
+_SetCanonicalName(ModuleInstantiate, "ModuleInstantiate");
- try {
- // Step 8
- let requestedModules = module.requestedModules;
- for (let i = 0; i < requestedModules.length; i++) {
- let required = requestedModules[i];
- let requiredModule = CallModuleResolveHook(module, required, MODULE_STATE_PARSED);
- callFunction(requiredModule.declarationInstantiation, requiredModule);
+// 15.2.1.16.4.1 InnerModuleDeclarationInstantiation(module, stack, index)
+function InnerModuleDeclarationInstantiation(module, stack, index)
+{
+ // Step 1
+ // TODO: Support module records other than source text module records.
+
+ // Step 2
+ if (module.status === MODULE_STATUS_INSTANTIATING ||
+ module.status === MODULE_STATUS_INSTANTIATED ||
+ module.status === MODULE_STATUS_EVALUATED)
+ {
+ return index;
+ }
+
+ // Step 3
+ if (module.status === MODULE_STATUS_ERRORED)
+ throw module.error;
+
+ // Step 4
+ assert(module.status === MODULE_STATUS_UNINSTANTIATED,
+ "Bad module status in ModuleDeclarationInstantiation");
+
+ // Steps 5
+ ModuleSetStatus(module, MODULE_STATUS_INSTANTIATING);
+
+ // Step 6-8
+ UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_INDEX_SLOT, index);
+ UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_ANCESTOR_INDEX_SLOT, index);
+ index++;
+
+ // Step 9
+ _DefineDataProperty(stack, stack.length, module);
+
+ // Step 10
+ let requestedModules = module.requestedModules;
+ for (let i = 0; i < requestedModules.length; i++) {
+ let required = requestedModules[i];
+ let requiredModule = CallModuleResolveHook(module, required, MODULE_STATUS_ERRORED);
+
+ index = InnerModuleDeclarationInstantiation(requiredModule, stack, index);
+
+ assert(requiredModule.status === MODULE_STATUS_INSTANTIATING ||
+ requiredModule.status === MODULE_STATUS_INSTANTIATED ||
+ requiredModule.status === MODULE_STATUS_EVALUATED,
+ "Bad required module status after InnerModuleDeclarationInstantiation");
+
+ assert((requiredModule.status === MODULE_STATUS_INSTANTIATING) ===
+ ArrayContains(stack, requiredModule),
+ "Required module should be in the stack iff it is currently being instantiated");
+
+ assert(typeof requiredModule.dfsIndex === "number", "Bad dfsIndex");
+ assert(typeof requiredModule.dfsAncestorIndex === "number", "Bad dfsAncestorIndex");
+
+ if (requiredModule.status === MODULE_STATUS_INSTANTIATING) {
+ UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_ANCESTOR_INDEX_SLOT,
+ std_Math_min(module.dfsAncestorIndex,
+ requiredModule.dfsAncestorIndex));
}
+ }
+
+ // Step 11
+ ModuleDeclarationEnvironmentSetup(module);
+
+ // Steps 12-13
+ assert(CountArrayValues(stack, module) === 1,
+ "Current module should appear exactly once in the stack");
+ assert(module.dfsAncestorIndex <= module.dfsIndex,
+ "Bad DFS ancestor index");
+
+ // Step 14
+ if (module.dfsAncestorIndex === module.dfsIndex) {
+ let requiredModule;
+ do {
+ requiredModule = callFunction(std_Array_pop, stack);
+ ModuleSetStatus(requiredModule, MODULE_STATUS_INSTANTIATED);
+ } while (requiredModule !== module);
+ }
+
+ // Step 15
+ return index;
+}
- // Step 9
- let indirectExportEntries = module.indirectExportEntries;
- for (let i = 0; i < indirectExportEntries.length; i++) {
- let e = indirectExportEntries[i];
- let resolution = callFunction(module.resolveExport, module, e.exportName);
- if (resolution === null)
- ThrowSyntaxError(JSMSG_MISSING_INDIRECT_EXPORT, e.exportName);
- if (resolution === "ambiguous")
- ThrowSyntaxError(JSMSG_AMBIGUOUS_INDIRECT_EXPORT, e.exportName);
+// 15.2.1.16.4.2 ModuleDeclarationEnvironmentSetup(module)
+function ModuleDeclarationEnvironmentSetup(module)
+{
+ // Step 1
+ let indirectExportEntries = module.indirectExportEntries;
+ for (let i = 0; i < indirectExportEntries.length; i++) {
+ let e = indirectExportEntries[i];
+ let resolution = callFunction(module.resolveExport, module, e.exportName);
+ assert(resolution.resolved || resolution.module,
+ "Unexpected failure to resolve export in ModuleDeclarationEnvironmentSetup");
+ if (!resolution.resolved) {
+ return ResolutionError(resolution, "indirectExport", e.exportName,
+ e.lineNumber, e.columnNumber)
}
+ }
- // Step 12
- let importEntries = module.importEntries;
- for (let i = 0; i < importEntries.length; i++) {
- let imp = importEntries[i];
- let importedModule = CallModuleResolveHook(module, imp.moduleRequest,
- MODULE_STATE_INSTANTIATED);
- if (imp.importName === "*") {
- let namespace = GetModuleNamespace(importedModule);
- CreateNamespaceBinding(env, imp.localName, namespace);
- } else {
- let resolution = callFunction(importedModule.resolveExport, importedModule,
- imp.importName);
- if (resolution === null)
- ThrowSyntaxError(JSMSG_MISSING_IMPORT, imp.importName);
- if (resolution === "ambiguous")
- ThrowSyntaxError(JSMSG_AMBIGUOUS_IMPORT, imp.importName);
- if (resolution.module.state < MODULE_STATE_INSTANTIATED)
- ThrowInternalError(JSMSG_BAD_MODULE_STATE);
- CreateImportBinding(env, imp.localName, resolution.module, resolution.bindingName);
+ // Steps 5-6
+ CreateModuleEnvironment(module);
+ let env = GetModuleEnvironment(module);
+
+ // Step 8
+ let importEntries = module.importEntries;
+ for (let i = 0; i < importEntries.length; i++) {
+ let imp = importEntries[i];
+ let importedModule = CallModuleResolveHook(module, imp.moduleRequest,
+ MODULE_STATUS_INSTANTIATING);
+ if (imp.importName === "*") {
+ let namespace = GetModuleNamespace(importedModule);
+ CreateNamespaceBinding(env, imp.localName, namespace);
+ } else {
+ let resolution = callFunction(importedModule.resolveExport, importedModule,
+ imp.importName);
+ if (!resolution.resolved && !resolution.module)
+ resolution.module = module;
+
+ if (!resolution.resolved) {
+ return ResolutionError(resolution, "import", imp.importName,
+ imp.lineNumber, imp.columnNumber);
}
+
+ CreateImportBinding(env, imp.localName, resolution.module, resolution.bindingName);
}
+ }
- // Step 17.a.iii
- InstantiateModuleFunctionDeclarations(module);
- } catch (e) {
- RecordInstantationFailure(module);
- throw e;
+ InstantiateModuleFunctionDeclarations(module);
+}
+
+// 15.2.1.16.4.3 ResolutionError(module)
+function ResolutionError(resolution, kind, name, line, column)
+{
+ let module = resolution.module;
+ assert(module !== null,
+ "Null module passed to ResolutionError");
+
+ assert(module.status === MODULE_STATUS_UNINSTANTIATED ||
+ module.status === MODULE_STATUS_INSTANTIATING,
+ "Unexpected module status in ResolutionError");
+
+ assert(kind === "import" || kind === "indirectExport",
+ "Unexpected kind in ResolutionError");
+
+ assert(line > 0,
+ "Line number should be present for all imports and indirect exports");
+
+ let errorNumber;
+ if (kind === "import") {
+ errorNumber = resolution.ambiguous ? JSMSG_AMBIGUOUS_IMPORT
+ : JSMSG_MISSING_IMPORT;
+ } else {
+ errorNumber = resolution.ambiguous ? JSMSG_AMBIGUOUS_INDIRECT_EXPORT
+ : JSMSG_MISSING_INDIRECT_EXPORT;
}
+
+ let message = GetErrorMessage(errorNumber) + ": " + name;
+ let error = CreateModuleSyntaxError(module, line, column, message);
+ RecordModuleError(module, error);
+ throw error;
}
-_SetCanonicalName(ModuleDeclarationInstantiation, "ModuleDeclarationInstantiation");
-// 15.2.1.16.5 ModuleEvaluation()
-function ModuleEvaluation()
+// 15.2.1.16.5 ModuleEvaluate()
+function ModuleEvaluate()
{
if (!IsObject(this) || !IsModule(this))
- return callFunction(CallModuleMethodIfWrapped, this, "ModuleEvaluation");
+ return callFunction(CallModuleMethodIfWrapped, this, "ModuleEvaluate");
// Step 1
let module = this;
- if (module.state < MODULE_STATE_INSTANTIATED)
- ThrowInternalError(JSMSG_BAD_MODULE_STATE);
+ // Step 2
+ if (module.status !== MODULE_STATUS_ERRORED &&
+ module.status !== MODULE_STATUS_INSTANTIATED &&
+ module.status !== MODULE_STATUS_EVALUATED)
+ {
+ ThrowInternalError(JSMSG_BAD_MODULE_STATUS);
+ }
+
+ // Step 3
+ let stack = [];
+
+ // Steps 4-5
+ try {
+ InnerModuleEvaluation(module, stack, 0);
+ } catch (error) {
+ for (let i = 0; i < stack.length; i++) {
+ let m = stack[i];
+
+ assert(m.status === MODULE_STATUS_EVALUATING,
+ "Bad module status after failed evaluation");
+
+ RecordModuleError(m, error);
+ }
+
+ if (stack.length === 0 &&
+ typeof(UnsafeGetReservedSlot(module, MODULE_OBJECT_ERROR_SLOT)) === "undefined")
+ {
+ // This can happen due to OOM when appending to the stack.
+ assert(error === "out of memory",
+ "Stack must contain module unless we hit OOM");
+ RecordModuleError(module, error);
+ }
+
+ assert(module.status === MODULE_STATUS_ERRORED,
+ "Bad module status after failed evaluation");
+ assert(SameValue(UnsafeGetReservedSlot(module, MODULE_OBJECT_ERROR_SLOT), error),
+ "Module has different error set after failed evaluation");
+
+ throw error;
+ }
+
+ assert(module.status == MODULE_STATUS_EVALUATED,
+ "Bad module status after successful evaluation");
+ assert(stack.length === 0,
+ "Stack should be empty after successful evaluation");
+
+ return undefined;
+}
+_SetCanonicalName(ModuleEvaluate, "ModuleEvaluate");
+
+// 15.2.1.16.5.1 InnerModuleEvaluation(module, stack, index)
+function InnerModuleEvaluation(module, stack, index)
+{
+ // Step 1
+ // TODO: Support module records other than source text module records.
+
+ // Step 2
+ if (module.status === MODULE_STATUS_EVALUATING ||
+ module.status === MODULE_STATUS_EVALUATED)
+ {
+ return index;
+ }
+
+ // Step 3
+ if (module.status === MODULE_STATUS_ERRORED)
+ throw module.error;
// Step 4
- if (module.state == MODULE_STATE_EVALUATED)
- return undefined;
+ assert(module.status === MODULE_STATUS_INSTANTIATED,
+ "Bad module status in ModuleEvaluation");
// Step 5
- SetModuleState(this, MODULE_STATE_EVALUATED);
+ ModuleSetStatus(module, MODULE_STATUS_EVALUATING);
- // Step 6
+ // Steps 6-8
+ UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_INDEX_SLOT, index);
+ UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_ANCESTOR_INDEX_SLOT, index);
+ index++;
+
+ // Step 9
+ _DefineDataProperty(stack, stack.length, module);
+
+ // Step 10
let requestedModules = module.requestedModules;
for (let i = 0; i < requestedModules.length; i++) {
let required = requestedModules[i];
- let requiredModule = CallModuleResolveHook(module, required, MODULE_STATE_INSTANTIATED);
- callFunction(requiredModule.evaluation, requiredModule);
+ let requiredModule =
+ CallModuleResolveHook(module, required, MODULE_STATUS_INSTANTIATED);
+
+ index = InnerModuleEvaluation(requiredModule, stack, index);
+
+ assert(requiredModule.status == MODULE_STATUS_EVALUATING ||
+ requiredModule.status == MODULE_STATUS_EVALUATED,
+ "Bad module status after InnerModuleEvaluation");
+
+ assert((requiredModule.status === MODULE_STATUS_EVALUATING) ===
+ ArrayContains(stack, requiredModule),
+ "Required module should be in the stack iff it is currently being evaluated");
+
+ assert(typeof requiredModule.dfsIndex === "number", "Bad dfsIndex");
+ assert(typeof requiredModule.dfsAncestorIndex === "number", "Bad dfsAncestorIndex");
+
+ if (requiredModule.status === MODULE_STATUS_EVALUATING) {
+ UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_ANCESTOR_INDEX_SLOT,
+ std_Math_min(module.dfsAncestorIndex,
+ requiredModule.dfsAncestorIndex));
+ }
+ }
+
+ // Step 11
+ ExecuteModule(module);
+
+ // Step 12
+ assert(CountArrayValues(stack, module) === 1,
+ "Current module should appear exactly once in the stack");
+
+ // Step 13
+ assert(module.dfsAncestorIndex <= module.dfsIndex,
+ "Bad DFS ancestor index");
+
+ // Step 14
+ if (module.dfsAncestorIndex === module.dfsIndex) {
+ let requiredModule;
+ do {
+ requiredModule = callFunction(std_Array_pop, stack);
+ ModuleSetStatus(requiredModule, MODULE_STATUS_EVALUATED);
+ } while (requiredModule !== module);
}
- return EvaluateModule(module);
+ // Step 15
+ return index;
}
-_SetCanonicalName(ModuleEvaluation, "ModuleEvaluation");
diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp
index b275cb968..30e7120c0 100644
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -18,10 +18,11 @@
using namespace js;
using namespace js::frontend;
-static_assert(MODULE_STATE_FAILED < MODULE_STATE_PARSED &&
- MODULE_STATE_PARSED < MODULE_STATE_INSTANTIATED &&
- MODULE_STATE_INSTANTIATED < MODULE_STATE_EVALUATED,
- "Module states are ordered incorrectly");
+static_assert(MODULE_STATUS_ERRORED < MODULE_STATUS_UNINSTANTIATED &&
+ MODULE_STATUS_UNINSTANTIATED < MODULE_STATUS_INSTANTIATING &&
+ MODULE_STATUS_INSTANTIATING < MODULE_STATUS_INSTANTIATED &&
+ MODULE_STATUS_INSTANTIATED < MODULE_STATUS_EVALUATED,
+ "Module statuses are ordered incorrectly");
template<typename T, Value ValueGetter(const T* obj)>
static bool
@@ -42,7 +43,7 @@ ModuleValueGetter(JSContext* cx, unsigned argc, Value* vp)
#define DEFINE_GETTER_FUNCTIONS(cls, name, slot) \
static Value \
cls##_##name##Value(const cls* obj) { \
- return obj->getFixedSlot(cls::slot); \
+ return obj->getReservedSlot(cls::slot); \
} \
\
static bool \
@@ -69,6 +70,15 @@ ModuleValueGetter(JSContext* cx, unsigned argc, Value* vp)
return &value.toString()->asAtom(); \
}
+#define DEFINE_UINT32_ACCESSOR_METHOD(cls, name) \
+ uint32_t \
+ cls::name() const \
+ { \
+ Value value = cls##_##name##Value(this); \
+ MOZ_ASSERT(value.toInt32() >= 0); \
+ return value.toInt32(); \
+ }
+
///////////////////////////////////////////////////////////////////////////
// ImportEntryObject
@@ -82,10 +92,14 @@ ImportEntryObject::class_ = {
DEFINE_GETTER_FUNCTIONS(ImportEntryObject, moduleRequest, ModuleRequestSlot)
DEFINE_GETTER_FUNCTIONS(ImportEntryObject, importName, ImportNameSlot)
DEFINE_GETTER_FUNCTIONS(ImportEntryObject, localName, LocalNameSlot)
+DEFINE_GETTER_FUNCTIONS(ImportEntryObject, lineNumber, LineNumberSlot)
+DEFINE_GETTER_FUNCTIONS(ImportEntryObject, columnNumber, ColumnNumberSlot)
DEFINE_ATOM_ACCESSOR_METHOD(ImportEntryObject, moduleRequest)
DEFINE_ATOM_ACCESSOR_METHOD(ImportEntryObject, importName)
DEFINE_ATOM_ACCESSOR_METHOD(ImportEntryObject, localName)
+DEFINE_UINT32_ACCESSOR_METHOD(ImportEntryObject, lineNumber)
+DEFINE_UINT32_ACCESSOR_METHOD(ImportEntryObject, columnNumber)
/* static */ bool
ImportEntryObject::isInstance(HandleValue value)
@@ -100,6 +114,8 @@ GlobalObject::initImportEntryProto(JSContext* cx, Handle<GlobalObject*> global)
JS_PSG("moduleRequest", ImportEntryObject_moduleRequestGetter, 0),
JS_PSG("importName", ImportEntryObject_importNameGetter, 0),
JS_PSG("localName", ImportEntryObject_localNameGetter, 0),
+ JS_PSG("lineNumber", ImportEntryObject_lineNumberGetter, 0),
+ JS_PSG("columnNumber", ImportEntryObject_columnNumberGetter, 0),
JS_PS_END
};
@@ -118,8 +134,12 @@ GlobalObject::initImportEntryProto(JSContext* cx, Handle<GlobalObject*> global)
ImportEntryObject::create(ExclusiveContext* cx,
HandleAtom moduleRequest,
HandleAtom importName,
- HandleAtom localName)
+ HandleAtom localName,
+ uint32_t lineNumber,
+ uint32_t columnNumber)
{
+ MOZ_ASSERT(lineNumber > 0);
+
RootedObject proto(cx, cx->global()->getImportEntryPrototype());
RootedObject obj(cx, NewObjectWithGivenProto(cx, &class_, proto));
if (!obj)
@@ -129,6 +149,8 @@ ImportEntryObject::create(ExclusiveContext* cx,
self->initReservedSlot(ModuleRequestSlot, StringValue(moduleRequest));
self->initReservedSlot(ImportNameSlot, StringValue(importName));
self->initReservedSlot(LocalNameSlot, StringValue(localName));
+ self->initReservedSlot(LineNumberSlot, Int32Value(lineNumber));
+ self->initReservedSlot(ColumnNumberSlot, Int32Value(columnNumber));
return self;
}
@@ -146,11 +168,15 @@ DEFINE_GETTER_FUNCTIONS(ExportEntryObject, exportName, ExportNameSlot)
DEFINE_GETTER_FUNCTIONS(ExportEntryObject, moduleRequest, ModuleRequestSlot)
DEFINE_GETTER_FUNCTIONS(ExportEntryObject, importName, ImportNameSlot)
DEFINE_GETTER_FUNCTIONS(ExportEntryObject, localName, LocalNameSlot)
+DEFINE_GETTER_FUNCTIONS(ExportEntryObject, lineNumber, LineNumberSlot)
+DEFINE_GETTER_FUNCTIONS(ExportEntryObject, columnNumber, ColumnNumberSlot)
DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(ExportEntryObject, exportName)
DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(ExportEntryObject, moduleRequest)
DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(ExportEntryObject, importName)
DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(ExportEntryObject, localName)
+DEFINE_UINT32_ACCESSOR_METHOD(ExportEntryObject, lineNumber)
+DEFINE_UINT32_ACCESSOR_METHOD(ExportEntryObject, columnNumber)
/* static */ bool
ExportEntryObject::isInstance(HandleValue value)
@@ -166,6 +192,8 @@ GlobalObject::initExportEntryProto(JSContext* cx, Handle<GlobalObject*> global)
JS_PSG("moduleRequest", ExportEntryObject_moduleRequestGetter, 0),
JS_PSG("importName", ExportEntryObject_importNameGetter, 0),
JS_PSG("localName", ExportEntryObject_localNameGetter, 0),
+ JS_PSG("lineNumber", ExportEntryObject_lineNumberGetter, 0),
+ JS_PSG("columnNumber", ExportEntryObject_columnNumberGetter, 0),
JS_PS_END
};
@@ -191,8 +219,13 @@ ExportEntryObject::create(ExclusiveContext* cx,
HandleAtom maybeExportName,
HandleAtom maybeModuleRequest,
HandleAtom maybeImportName,
- HandleAtom maybeLocalName)
+ HandleAtom maybeLocalName,
+ uint32_t lineNumber,
+ uint32_t columnNumber)
{
+ // Line and column numbers are optional for export entries since direct
+ // entries are checked at parse time.
+
RootedObject proto(cx, cx->global()->getExportEntryPrototype());
RootedObject obj(cx, NewObjectWithGivenProto(cx, &class_, proto));
if (!obj)
@@ -203,6 +236,8 @@ ExportEntryObject::create(ExclusiveContext* cx,
self->initReservedSlot(ModuleRequestSlot, StringOrNullValue(maybeModuleRequest));
self->initReservedSlot(ImportNameSlot, StringOrNullValue(maybeImportName));
self->initReservedSlot(LocalNameSlot, StringOrNullValue(maybeLocalName));
+ self->initReservedSlot(LineNumberSlot, Int32Value(lineNumber));
+ self->initReservedSlot(ColumnNumberSlot, Int32Value(columnNumber));
return self;
}
@@ -564,7 +599,7 @@ ModuleObject::class_ = {
ArrayObject& \
cls::name() const \
{ \
- return getFixedSlot(cls::slot).toObject().as<ArrayObject>(); \
+ return getReservedSlot(cls::slot).toObject().as<ArrayObject>(); \
}
DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, requestedModules, RequestedModulesSlot)
@@ -688,7 +723,7 @@ void
ModuleObject::init(HandleScript script)
{
initReservedSlot(ScriptSlot, PrivateValue(script));
- initReservedSlot(StateSlot, Int32Value(MODULE_STATE_FAILED));
+ initReservedSlot(StatusSlot, Int32Value(MODULE_STATUS_ERRORED));
}
void
@@ -709,7 +744,7 @@ ModuleObject::initImportExportData(HandleArrayObject requestedModules,
initReservedSlot(LocalExportEntriesSlot, ObjectValue(*localExportEntries));
initReservedSlot(IndirectExportEntriesSlot, ObjectValue(*indirectExportEntries));
initReservedSlot(StarExportEntriesSlot, ObjectValue(*starExportEntries));
- setReservedSlot(StateSlot, Int32Value(MODULE_STATE_PARSED));
+ setReservedSlot(StatusSlot, Int32Value(MODULE_STATUS_UNINSTANTIATED));
}
static bool
@@ -790,17 +825,24 @@ ModuleObject::script() const
}
static inline void
-AssertValidModuleState(ModuleState state)
+AssertValidModuleStatus(ModuleStatus status)
{
- MOZ_ASSERT(state >= MODULE_STATE_FAILED && state <= MODULE_STATE_EVALUATED);
+ MOZ_ASSERT(status >= MODULE_STATUS_ERRORED && status <= MODULE_STATUS_EVALUATED);
}
-ModuleState
-ModuleObject::state() const
+ModuleStatus
+ModuleObject::status() const
{
- ModuleState state = getReservedSlot(StateSlot).toInt32();
- AssertValidModuleState(state);
- return state;
+ ModuleStatus status = getReservedSlot(StatusSlot).toInt32();
+ AssertValidModuleStatus(status);
+ return status;
+}
+
+Value
+ModuleObject::error() const
+{
+ MOZ_ASSERT(status() == MODULE_STATUS_ERRORED);
+ return getReservedSlot(ErrorSlot);
}
Value
@@ -899,17 +941,8 @@ ModuleObject::instantiateFunctionDeclarations(JSContext* cx, HandleModuleObject
return true;
}
-void
-ModuleObject::setState(int32_t newState)
-{
- AssertValidModuleState(newState);
- MOZ_ASSERT(state() != MODULE_STATE_FAILED);
- MOZ_ASSERT(newState == MODULE_STATE_FAILED || newState > state());
- setReservedSlot(StateSlot, Int32Value(newState));
-}
-
/* static */ bool
-ModuleObject::evaluate(JSContext* cx, HandleModuleObject self, MutableHandleValue rval)
+ModuleObject::execute(JSContext* cx, HandleModuleObject self, MutableHandleValue rval)
{
MOZ_ASSERT(IsFrozen(cx, self));
@@ -959,44 +992,50 @@ InvokeSelfHostedMethod(JSContext* cx, HandleModuleObject self, HandlePropertyNam
}
/* static */ bool
-ModuleObject::DeclarationInstantiation(JSContext* cx, HandleModuleObject self)
+ModuleObject::Instantiate(JSContext* cx, HandleModuleObject self)
{
- return InvokeSelfHostedMethod(cx, self, cx->names().ModuleDeclarationInstantiation);
+ return InvokeSelfHostedMethod(cx, self, cx->names().ModuleInstantiate);
}
/* static */ bool
-ModuleObject::Evaluation(JSContext* cx, HandleModuleObject self)
+ModuleObject::Evaluate(JSContext* cx, HandleModuleObject self)
{
- return InvokeSelfHostedMethod(cx, self, cx->names().ModuleEvaluation);
+ return InvokeSelfHostedMethod(cx, self, cx->names().ModuleEvaluate);
}
DEFINE_GETTER_FUNCTIONS(ModuleObject, namespace_, NamespaceSlot)
-DEFINE_GETTER_FUNCTIONS(ModuleObject, state, StateSlot)
+DEFINE_GETTER_FUNCTIONS(ModuleObject, status, StatusSlot)
+DEFINE_GETTER_FUNCTIONS(ModuleObject, error, ErrorSlot)
DEFINE_GETTER_FUNCTIONS(ModuleObject, requestedModules, RequestedModulesSlot)
DEFINE_GETTER_FUNCTIONS(ModuleObject, importEntries, ImportEntriesSlot)
DEFINE_GETTER_FUNCTIONS(ModuleObject, localExportEntries, LocalExportEntriesSlot)
DEFINE_GETTER_FUNCTIONS(ModuleObject, indirectExportEntries, IndirectExportEntriesSlot)
DEFINE_GETTER_FUNCTIONS(ModuleObject, starExportEntries, StarExportEntriesSlot)
+DEFINE_GETTER_FUNCTIONS(ModuleObject, dfsIndex, DFSIndexSlot)
+DEFINE_GETTER_FUNCTIONS(ModuleObject, dfsAncestorIndex, DFSAncestorIndexSlot)
/* static */ bool
GlobalObject::initModuleProto(JSContext* cx, Handle<GlobalObject*> global)
{
static const JSPropertySpec protoAccessors[] = {
JS_PSG("namespace", ModuleObject_namespace_Getter, 0),
- JS_PSG("state", ModuleObject_stateGetter, 0),
+ JS_PSG("status", ModuleObject_statusGetter, 0),
+ JS_PSG("error", ModuleObject_errorGetter, 0),
JS_PSG("requestedModules", ModuleObject_requestedModulesGetter, 0),
JS_PSG("importEntries", ModuleObject_importEntriesGetter, 0),
JS_PSG("localExportEntries", ModuleObject_localExportEntriesGetter, 0),
JS_PSG("indirectExportEntries", ModuleObject_indirectExportEntriesGetter, 0),
JS_PSG("starExportEntries", ModuleObject_starExportEntriesGetter, 0),
+ JS_PSG("dfsIndex", ModuleObject_dfsIndexGetter, 0),
+ JS_PSG("dfsAncestorIndex", ModuleObject_dfsAncestorIndexGetter, 0),
JS_PS_END
};
static const JSFunctionSpec protoFunctions[] = {
JS_SELF_HOSTED_FN("getExportedNames", "ModuleGetExportedNames", 1, 0),
JS_SELF_HOSTED_FN("resolveExport", "ModuleResolveExport", 2, 0),
- JS_SELF_HOSTED_FN("declarationInstantiation", "ModuleDeclarationInstantiation", 0, 0),
- JS_SELF_HOSTED_FN("evaluation", "ModuleEvaluation", 0, 0),
+ JS_SELF_HOSTED_FN("declarationInstantiation", "ModuleInstantiate", 0, 0),
+ JS_SELF_HOSTED_FN("evaluation", "ModuleEvaluate", 0, 0),
JS_FS_END
};
@@ -1018,9 +1057,11 @@ GlobalObject::initModuleProto(JSContext* cx, Handle<GlobalObject*> global)
///////////////////////////////////////////////////////////////////////////
// ModuleBuilder
-ModuleBuilder::ModuleBuilder(ExclusiveContext* cx, HandleModuleObject module)
+ModuleBuilder::ModuleBuilder(ExclusiveContext* cx, HandleModuleObject module,
+ const frontend::TokenStream& tokenStream)
: cx_(cx),
module_(cx, module),
+ tokenStream_(tokenStream),
requestedModules_(cx, AtomVector(cx)),
importedBoundNames_(cx, AtomVector(cx)),
importEntries_(cx, ImportEntryVector(cx)),
@@ -1045,6 +1086,7 @@ ModuleBuilder::buildTables()
if (!localExportEntries_.append(exp))
return false;
} else {
+ MOZ_ASSERT(exp->lineNumber());
RootedAtom exportName(cx_, exp->exportName());
RootedAtom moduleRequest(cx_, importEntry->moduleRequest());
RootedAtom importName(cx_, importEntry->importName());
@@ -1053,7 +1095,9 @@ ModuleBuilder::buildTables()
exportName,
moduleRequest,
importName,
- nullptr);
+ nullptr,
+ exp->lineNumber(),
+ exp->columnNumber());
if (!exportEntry || !indirectExportEntries_.append(exportEntry))
return false;
}
@@ -1062,6 +1106,7 @@ ModuleBuilder::buildTables()
if (!starExportEntries_.append(exp))
return false;
} else {
+ MOZ_ASSERT(exp->lineNumber());
if (!indirectExportEntries_.append(exp))
return false;
}
@@ -1126,8 +1171,12 @@ ModuleBuilder::processImport(frontend::ParseNode* pn)
if (!importedBoundNames_.append(localName))
return false;
+ uint32_t line;
+ uint32_t column;
+ tokenStream_.srcCoords.lineNumAndColumnIndex(spec->pn_left->pn_pos.begin, &line, &column);
+
RootedImportEntryObject importEntry(cx_);
- importEntry = ImportEntryObject::create(cx_, module, importName, localName);
+ importEntry = ImportEntryObject::create(cx_, module, importName, localName, line, column);
if (!importEntry || !importEntries_.append(importEntry))
return false;
}
@@ -1158,7 +1207,7 @@ ModuleBuilder::processExport(frontend::ParseNode* pn)
MOZ_ASSERT(spec->isKind(PNK_EXPORT_SPEC));
RootedAtom localName(cx_, spec->pn_left->pn_atom);
RootedAtom exportName(cx_, spec->pn_right->pn_atom);
- if (!appendExportEntry(exportName, localName))
+ if (!appendExportEntry(exportName, localName, spec))
return false;
}
break;
@@ -1223,12 +1272,12 @@ ModuleBuilder::processExportFrom(frontend::ParseNode* pn)
if (spec->isKind(PNK_EXPORT_SPEC)) {
RootedAtom bindingName(cx_, spec->pn_left->pn_atom);
RootedAtom exportName(cx_, spec->pn_right->pn_atom);
- if (!appendExportFromEntry(exportName, module, bindingName))
+ if (!appendExportFromEntry(exportName, module, bindingName, spec->pn_left))
return false;
} else {
MOZ_ASSERT(spec->isKind(PNK_EXPORT_BATCH_SPEC));
RootedAtom importName(cx_, cx_->names().star);
- if (!appendExportFromEntry(nullptr, module, importName))
+ if (!appendExportFromEntry(nullptr, module, importName, spec))
return false;
}
}
@@ -1257,19 +1306,30 @@ ModuleBuilder::hasExportedName(JSAtom* name) const
}
bool
-ModuleBuilder::appendExportEntry(HandleAtom exportName, HandleAtom localName)
+ModuleBuilder::appendExportEntry(HandleAtom exportName, HandleAtom localName, ParseNode* node)
{
+ uint32_t line = 0;
+ uint32_t column = 0;
+ if (node)
+ tokenStream_.srcCoords.lineNumAndColumnIndex(node->pn_pos.begin, &line, &column);
+
Rooted<ExportEntryObject*> exportEntry(cx_);
- exportEntry = ExportEntryObject::create(cx_, exportName, nullptr, nullptr, localName);
+ exportEntry = ExportEntryObject::create(cx_, exportName, nullptr, nullptr, localName,
+ line, column);
return exportEntry && exportEntries_.append(exportEntry);
}
bool
ModuleBuilder::appendExportFromEntry(HandleAtom exportName, HandleAtom moduleRequest,
- HandleAtom importName)
+ HandleAtom importName, ParseNode* node)
{
+ uint32_t line;
+ uint32_t column;
+ tokenStream_.srcCoords.lineNumAndColumnIndex(node->pn_pos.begin, &line, &column);
+
Rooted<ExportEntryObject*> exportEntry(cx_);
- exportEntry = ExportEntryObject::create(cx_, exportName, moduleRequest, importName, nullptr);
+ exportEntry = ExportEntryObject::create(cx_, exportName, moduleRequest, importName, nullptr,
+ line, column);
return exportEntry && exportEntries_.append(exportEntry);
}
diff --git a/js/src/builtin/ModuleObject.h b/js/src/builtin/ModuleObject.h
index 22db762ac..be0315215 100644
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -24,6 +24,7 @@ class ModuleObject;
namespace frontend {
class ParseNode;
+class TokenStream;
} /* namespace frontend */
typedef Rooted<ModuleObject*> RootedModuleObject;
@@ -39,6 +40,8 @@ class ImportEntryObject : public NativeObject
ModuleRequestSlot = 0,
ImportNameSlot,
LocalNameSlot,
+ LineNumberSlot,
+ ColumnNumberSlot,
SlotCount
};
@@ -48,10 +51,14 @@ class ImportEntryObject : public NativeObject
static ImportEntryObject* create(ExclusiveContext* cx,
HandleAtom moduleRequest,
HandleAtom importName,
- HandleAtom localName);
+ HandleAtom localName,
+ uint32_t lineNumber,
+ uint32_t columnNumber);
JSAtom* moduleRequest() const;
JSAtom* importName() const;
JSAtom* localName() const;
+ uint32_t lineNumber() const;
+ uint32_t columnNumber() const;
};
typedef Rooted<ImportEntryObject*> RootedImportEntryObject;
@@ -66,6 +73,8 @@ class ExportEntryObject : public NativeObject
ModuleRequestSlot,
ImportNameSlot,
LocalNameSlot,
+ LineNumberSlot,
+ ColumnNumberSlot,
SlotCount
};
@@ -76,11 +85,15 @@ class ExportEntryObject : public NativeObject
HandleAtom maybeExportName,
HandleAtom maybeModuleRequest,
HandleAtom maybeImportName,
- HandleAtom maybeLocalName);
+ HandleAtom maybeLocalName,
+ uint32_t lineNumber,
+ uint32_t columnNumber);
JSAtom* exportName() const;
JSAtom* moduleRequest() const;
JSAtom* importName() const;
JSAtom* localName() const;
+ uint32_t lineNumber() const;
+ uint32_t columnNumber() const;
};
typedef Rooted<ExportEntryObject*> RootedExportEntryObject;
@@ -192,8 +205,8 @@ struct FunctionDeclaration
using FunctionDeclarationVector = GCVector<FunctionDeclaration, 0, ZoneAllocPolicy>;
-// Possible values for ModuleState are defined in SelfHostingDefines.h.
-using ModuleState = int32_t;
+// Possible values for ModuleStatus are defined in SelfHostingDefines.h.
+using ModuleStatus = int32_t;
class ModuleObject : public NativeObject
{
@@ -204,7 +217,8 @@ class ModuleObject : public NativeObject
InitialEnvironmentSlot,
EnvironmentSlot,
NamespaceSlot,
- StateSlot,
+ StatusSlot,
+ ErrorSlot,
HostDefinedSlot,
RequestedModulesSlot,
ImportEntriesSlot,
@@ -215,11 +229,21 @@ class ModuleObject : public NativeObject
NamespaceExportsSlot,
NamespaceBindingsSlot,
FunctionDeclarationsSlot,
+ DFSIndexSlot,
+ DFSAncestorIndexSlot,
SlotCount
};
static_assert(EnvironmentSlot == MODULE_OBJECT_ENVIRONMENT_SLOT,
"EnvironmentSlot must match self-hosting define");
+ static_assert(StatusSlot == MODULE_OBJECT_STATUS_SLOT,
+ "StatusSlot must match self-hosting define");
+ static_assert(ErrorSlot == MODULE_OBJECT_ERROR_SLOT,
+ "ErrorSlot must match self-hosting define");
+ static_assert(DFSIndexSlot == MODULE_OBJECT_DFS_INDEX_SLOT,
+ "DFSIndexSlot must match self-hosting define");
+ static_assert(DFSAncestorIndexSlot == MODULE_OBJECT_DFS_ANCESTOR_INDEX_SLOT,
+ "DFSAncestorIndexSlot must match self-hosting define");
static const Class class_;
@@ -244,7 +268,8 @@ class ModuleObject : public NativeObject
ModuleEnvironmentObject& initialEnvironment() const;
ModuleEnvironmentObject* environment() const;
ModuleNamespaceObject* namespace_();
- ModuleState state() const;
+ ModuleStatus status() const;
+ Value error() const;
Value hostDefinedField() const;
ArrayObject& requestedModules() const;
ArrayObject& importEntries() const;
@@ -255,8 +280,8 @@ class ModuleObject : public NativeObject
JSObject* namespaceExports();
IndirectBindingMap* namespaceBindings();
- static bool DeclarationInstantiation(JSContext* cx, HandleModuleObject self);
- static bool Evaluation(JSContext* cx, HandleModuleObject self);
+ static bool Instantiate(JSContext* cx, HandleModuleObject self);
+ static bool Evaluate(JSContext* cx, HandleModuleObject self);
void setHostDefinedField(const JS::Value& value);
@@ -269,10 +294,8 @@ class ModuleObject : public NativeObject
// For intrinsic_InstantiateModuleFunctionDeclarations.
static bool instantiateFunctionDeclarations(JSContext* cx, HandleModuleObject self);
- void setState(ModuleState newState);
-
- // For intrinsic_EvaluateModule.
- static bool evaluate(JSContext* cx, HandleModuleObject self, MutableHandleValue rval);
+ // For intrinsic_ExecuteModule.
+ static bool execute(JSContext* cx, HandleModuleObject self, MutableHandleValue rval);
// For intrinsic_NewModuleNamespace.
static ModuleNamespaceObject* createNamespace(JSContext* cx, HandleModuleObject self,
@@ -294,7 +317,8 @@ class ModuleObject : public NativeObject
class MOZ_STACK_CLASS ModuleBuilder
{
public:
- explicit ModuleBuilder(ExclusiveContext* cx, HandleModuleObject module);
+ explicit ModuleBuilder(ExclusiveContext* cx, HandleModuleObject module,
+ const frontend::TokenStream& tokenStream);
bool processImport(frontend::ParseNode* pn);
bool processExport(frontend::ParseNode* pn);
@@ -319,6 +343,7 @@ class MOZ_STACK_CLASS ModuleBuilder
ExclusiveContext* cx_;
RootedModuleObject module_;
+ const frontend::TokenStream& tokenStream_;
RootedAtomVector requestedModules_;
RootedAtomVector importedBoundNames_;
RootedImportEntryVector importEntries_;
@@ -329,9 +354,10 @@ class MOZ_STACK_CLASS ModuleBuilder
ImportEntryObject* importEntryFor(JSAtom* localName) const;
- bool appendExportEntry(HandleAtom exportName, HandleAtom localName);
+ bool appendExportEntry(HandleAtom exportName, HandleAtom localName,
+ frontend::ParseNode* node = nullptr);
bool appendExportFromEntry(HandleAtom exportName, HandleAtom moduleRequest,
- HandleAtom importName);
+ HandleAtom importName, frontend::ParseNode* node);
bool maybeAppendRequestedModule(HandleAtom module);
diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp
index a8065d6d1..5cb81355f 100644
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -3741,7 +3741,7 @@ reflect_parse(JSContext* cx, uint32_t argc, Value* vp)
if (!module)
return false;
- ModuleBuilder builder(cx, module);
+ ModuleBuilder builder(cx, module, parser.tokenStream);
ModuleSharedContext modulesc(cx, module, &cx->global()->emptyGlobalScope(), builder);
pn = parser.moduleBody(&modulesc);
if (!pn)
diff --git a/js/src/builtin/SelfHostingDefines.h b/js/src/builtin/SelfHostingDefines.h
index 117ac7ffd..a06c2aa62 100644
--- a/js/src/builtin/SelfHostingDefines.h
+++ b/js/src/builtin/SelfHostingDefines.h
@@ -97,11 +97,17 @@
#define REGEXP_STRING_ITERATOR_FLAGS_SLOT 2
#define REGEXP_STRING_ITERATOR_DONE_SLOT 3
-#define MODULE_OBJECT_ENVIRONMENT_SLOT 2
-
-#define MODULE_STATE_FAILED 0
-#define MODULE_STATE_PARSED 1
-#define MODULE_STATE_INSTANTIATED 2
-#define MODULE_STATE_EVALUATED 3
+#define MODULE_OBJECT_ENVIRONMENT_SLOT 2
+#define MODULE_OBJECT_STATUS_SLOT 4
+#define MODULE_OBJECT_ERROR_SLOT 5
+#define MODULE_OBJECT_DFS_INDEX_SLOT 16
+#define MODULE_OBJECT_DFS_ANCESTOR_INDEX_SLOT 17
+
+#define MODULE_STATUS_ERRORED 0
+#define MODULE_STATUS_UNINSTANTIATED 1
+#define MODULE_STATUS_INSTANTIATING 2
+#define MODULE_STATUS_INSTANTIATED 3
+#define MODULE_STATUS_EVALUATING 4
+#define MODULE_STATUS_EVALUATED 5
#endif
diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js
index d5f233d05..3916311db 100644
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -27,13 +27,20 @@
// Assertions and debug printing, defined here instead of in the header above
// to make `assert` invisible to C++.
#ifdef DEBUG
-#define assert(b, info) if (!(b)) AssertionFailed(__FILE__ + ":" + __LINE__ + ": " + info)
-#define dbg(msg) DumpMessage(callFunction(std_Array_pop, \
- StringSplitString(__FILE__, '/')) \
- + '#' + __LINE__ + ': ' + msg)
+#define assert(b, info) \
+ do { \
+ if (!(b)) \
+ AssertionFailed(__FILE__ + ":" + __LINE__ + ": " + info) \
+ } while (false)
+#define dbg(msg) \
+ do { \
+ DumpMessage(callFunction(std_Array_pop, \
+ StringSplitString(__FILE__, '/')) + \
+ '#' + __LINE__ + ': ' + msg) \
+ } while (false)
#else
-#define assert(b, info) // Elided assertion.
-#define dbg(msg) // Elided debugging output.
+#define assert(b, info) do {} while (false) // Elided assertion.
+#define dbg(msg) do {} while (false) // Elided debugging output.
#endif
// All C++-implemented standard builtins library functions used in self-hosted
diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp
index ccfe3cd2e..3c2bcc1ed 100644
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -405,7 +405,7 @@ BytecodeCompiler::compileModule()
module->init(script);
- ModuleBuilder builder(cx, module);
+ ModuleBuilder builder(cx, module, parser->tokenStream);
ModuleSharedContext modulesc(cx, module, enclosingScope, builder);
ParseNode* pn = parser->moduleBody(&modulesc);
if (!pn)
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp
index 18cc7d954..654336a64 100644
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -8261,7 +8261,7 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
if (topLevelFunction) {
if (sc->isModuleContext()) {
// For modules, we record the function and instantiate the binding
- // during ModuleDeclarationInstantiation(), before the script is run.
+ // during ModuleInstantiate(), before the script is run.
RootedModuleObject module(cx, sc->asModuleContext()->module());
if (!module->noteFunctionDeclaration(cx, name, fun))
diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp
index 810d589be..59783a759 100644
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -5038,7 +5038,7 @@ Parser<FullParseHandler>::namedImportsOrNamespaceImport(TokenKind tt, Node impor
// Namespace imports are are not indirect bindings but lexical
// definitions that hold a module namespace object. They are treated
// as const variables which are initialized during the
- // ModuleDeclarationInstantiation step.
+ // ModuleInstantiate step.
RootedPropertyName bindingName(context, importedBinding());
if (!bindingName)
return false;
diff --git a/js/src/jit-test/tests/modules/ambiguous-star-export.js b/js/src/jit-test/tests/modules/ambiguous-star-export.js
index b8c91445c..94aa7ac4a 100644
--- a/js/src/jit-test/tests/modules/ambiguous-star-export.js
+++ b/js/src/jit-test/tests/modules/ambiguous-star-export.js
@@ -5,10 +5,11 @@
load(libdir + "asserts.js");
load(libdir + "dummyModuleResolveHook.js");
-function checkModuleEval(source, result) {
+function checkModuleEval(source) {
let m = parseModule(source);
m.declarationInstantiation();
- assertEq(m.evaluation(), result);
+ m.evaluation();
+ return m;
}
function checkModuleSyntaxError(source) {
@@ -23,17 +24,19 @@ c.declarationInstantiation();
c.evaluation();
// Check importing/exporting non-ambiguous name works.
-checkModuleEval("import { a } from 'c'; a;", 1);
-checkModuleEval("export { a } from 'c';", undefined);
+let d = checkModuleEval("import { a } from 'c';");
+assertEq(getModuleEnvironmentValue(d, "a"), 1);
+checkModuleEval("export { a } from 'c';");
// Check importing/exporting ambiguous name is a syntax error.
checkModuleSyntaxError("import { b } from 'c';");
checkModuleSyntaxError("export { b } from 'c';");
// Check that namespace objects include only non-ambiguous names.
-let m = parseModule("import * as ns from 'c'; ns;");
+let m = parseModule("import * as ns from 'c';");
m.declarationInstantiation();
-let ns = m.evaluation();
+m.evaluation();
+let ns = c.namespace;
let names = Object.keys(ns);
assertEq(names.length, 2);
assertEq('a' in ns, true);
diff --git a/js/src/jit-test/tests/modules/bad-namespace-created.js b/js/src/jit-test/tests/modules/bad-namespace-created.js
new file mode 100644
index 000000000..127892d6e
--- /dev/null
+++ b/js/src/jit-test/tests/modules/bad-namespace-created.js
@@ -0,0 +1,17 @@
+// Prior to https://github.com/tc39/ecma262/pull/916 it was possible for a
+// module namespace object to be successfully created that was later found to be
+// erroneous. Test that this is no longer the case.
+
+"use strict";
+
+load(libdir + "asserts.js");
+load(libdir + "dummyModuleResolveHook.js");
+
+moduleRepo['A'] = parseModule('import "B"; export {x} from "C"');
+moduleRepo['B'] = parseModule('import * as a from "A"');
+moduleRepo['C'] = parseModule('export * from "D"; export * from "E"');
+moduleRepo['D'] = parseModule('export let x');
+moduleRepo['E'] = parseModule('export let x');
+
+let m = moduleRepo['A'];
+assertThrowsInstanceOf(() => m.declarationInstantiation(), SyntaxError);
diff --git a/js/src/jit-test/tests/modules/bug-1284486.js b/js/src/jit-test/tests/modules/bug-1284486.js
index 9a3244ec3..08286393a 100644
--- a/js/src/jit-test/tests/modules/bug-1284486.js
+++ b/js/src/jit-test/tests/modules/bug-1284486.js
@@ -1,23 +1,36 @@
-// |jit-test| error: InternalError
-
// This tests that attempting to perform ModuleDeclarationInstantation a second
-// time after a failure throws an error. Doing this would be a bug in the module
-// loader, which is expected to throw away modules if there is an error
-// instantiating them.
+// time after a failure re-throws the same error.
//
// The first attempt fails becuase module 'a' is not available. The second
// attempt fails because of the previous failure (it would otherwise succeed as
// 'a' is now available).
-let moduleRepo = {};
-setModuleResolveHook(function(module, specifier) {
- return moduleRepo[specifier];
-});
+load(libdir + "dummyModuleResolveHook.js");
+
+let b = moduleRepo['b'] = parseModule("export var b = 3; export var c = 4;");
+let c = moduleRepo['c'] = parseModule("export * from 'a'; export * from 'b';");
+
+let e1;
+let threw = false;
try {
- let b = moduleRepo['b'] = parseModule("export var b = 3; export var c = 4;");
- let c = moduleRepo['c'] = parseModule("export * from 'a'; export * from 'b';");
c.declarationInstantiation();
-} catch (exc) {}
+} catch (exc) {
+ threw = true;
+ e1 = exc;
+}
+assertEq(threw, true);
+assertEq(typeof e1 === "undefined", false);
+
let a = moduleRepo['a'] = parseModule("export var a = 1; export var b = 2;");
let d = moduleRepo['d'] = parseModule("import { a } from 'c'; a;");
-d.declarationInstantiation();
+
+threw = false;
+let e2;
+try {
+ d.declarationInstantiation();
+} catch (exc) {
+ threw = true;
+ e2 = exc;
+}
+assertEq(threw, true);
+assertEq(e1, e2);
diff --git a/js/src/jit-test/tests/modules/bug-1287410.js b/js/src/jit-test/tests/modules/bug-1287410.js
index 8a891372a..7df5621a5 100644
--- a/js/src/jit-test/tests/modules/bug-1287410.js
+++ b/js/src/jit-test/tests/modules/bug-1287410.js
@@ -20,3 +20,5 @@ let d = moduleRepo['d'] = parseModule("import { a } from 'c'; a;");
// Attempting to instantiate 'd' throws an error because depdency 'a' of
// instantiated module 'c' is not instantiated.
d.declarationInstantiation();
+d.evaluation();
+
diff --git a/js/src/jit-test/tests/modules/bug-1394492.js b/js/src/jit-test/tests/modules/bug-1394492.js
new file mode 100644
index 000000000..a0e5d2ac3
--- /dev/null
+++ b/js/src/jit-test/tests/modules/bug-1394492.js
@@ -0,0 +1,6 @@
+// |jit-test| error: NaN
+let m = parseModule(`
+ throw i => { return 5; }, m-1;
+`);
+m.declarationInstantiation();
+m.evaluation();
diff --git a/js/src/jit-test/tests/modules/global-scope.js b/js/src/jit-test/tests/modules/global-scope.js
index 90a9f7026..b99019fa8 100644
--- a/js/src/jit-test/tests/modules/global-scope.js
+++ b/js/src/jit-test/tests/modules/global-scope.js
@@ -1,32 +1,34 @@
// Test interaction with global object and global lexical scope.
-function parseAndEvaluate(source) {
+function evalModuleAndCheck(source, expected) {
let m = parseModule(source);
m.declarationInstantiation();
- return m.evaluation();
+ m.evaluation();
+ assertEq(getModuleEnvironmentValue(m, "r"), expected);
}
var x = 1;
-assertEq(parseAndEvaluate("let r = x; x = 2; r"), 1);
+evalModuleAndCheck("export let r = x; x = 2;", 1);
assertEq(x, 2);
let y = 3;
-assertEq(parseAndEvaluate("let r = y; y = 4; r"), 3);
+evalModuleAndCheck("export let r = y; y = 4;", 3);
assertEq(y, 4);
if (helperThreadCount() == 0)
quit();
-function offThreadParseAndEvaluate(source) {
+function offThreadEvalModuleAndCheck(source, expected) {
offThreadCompileModule(source);
let m = finishOffThreadModule();
print("compiled");
m.declarationInstantiation();
- return m.evaluation();
+ m.evaluation();
+ assertEq(getModuleEnvironmentValue(m, "r"), expected);
}
-assertEq(offThreadParseAndEvaluate("let r = x; x = 5; r"), 2);
+offThreadEvalModuleAndCheck("export let r = x; x = 5;", 2);
assertEq(x, 5);
-assertEq(offThreadParseAndEvaluate("let r = y; y = 6; r"), 4);
+offThreadEvalModuleAndCheck("export let r = y; y = 6;", 4);
assertEq(y, 6);
diff --git a/js/src/jit-test/tests/modules/module-evaluation.js b/js/src/jit-test/tests/modules/module-evaluation.js
index eec13c040..84d88f19c 100644
--- a/js/src/jit-test/tests/modules/module-evaluation.js
+++ b/js/src/jit-test/tests/modules/module-evaluation.js
@@ -6,16 +6,17 @@ load(libdir + "dummyModuleResolveHook.js");
function parseAndEvaluate(source) {
let m = parseModule(source);
m.declarationInstantiation();
- return m.evaluation();
+ m.evaluation();
+ return m;
}
// Check the evaluation of an empty module succeeds.
-assertEq(typeof parseAndEvaluate(""), "undefined");
+parseAndEvaluate("");
// Check evaluation returns evaluation result the first time, then undefined.
let m = parseModule("1");
m.declarationInstantiation();
-assertEq(m.evaluation(), 1);
+assertEq(m.evaluation(), undefined);
assertEq(typeof m.evaluation(), "undefined");
// Check top level variables are initialized by evaluation.
@@ -60,31 +61,35 @@ parseAndEvaluate("export default class { constructor() {} };");
parseAndEvaluate("export default class foo { constructor() {} };");
// Test default import
-m = parseModule("import a from 'a'; a;")
+m = parseModule("import a from 'a'; export { a };")
m.declarationInstantiation();
-assertEq(m.evaluation(), 2);
+m.evaluation();
+assertEq(getModuleEnvironmentValue(m, "a"), 2);
// Test named import
-m = parseModule("import { x as y } from 'a'; y;")
+m = parseModule("import { x as y } from 'a'; export { y };")
m.declarationInstantiation();
-assertEq(m.evaluation(), 1);
+m.evaluation();
+assertEq(getModuleEnvironmentValue(m, "y"), 1);
// Call exported function
-m = parseModule("import { f } from 'a'; f(3);")
+m = parseModule("import { f } from 'a'; export let x = f(3);")
m.declarationInstantiation();
-assertEq(m.evaluation(), 4);
+m.evaluation();
+assertEq(getModuleEnvironmentValue(m, "x"), 4);
// Test importing an indirect export
moduleRepo['b'] = parseModule("export { x as z } from 'a';");
-assertEq(parseAndEvaluate("import { z } from 'b'; z"), 1);
+m = parseAndEvaluate("import { z } from 'b'; export { z }");
+assertEq(getModuleEnvironmentValue(m, "z"), 1);
// Test cyclic dependencies
moduleRepo['c1'] = parseModule("export var x = 1; export {y} from 'c2'");
moduleRepo['c2'] = parseModule("export var y = 2; export {x} from 'c1'");
-assertDeepEq(parseAndEvaluate(`import { x as x1, y as y1 } from 'c1';
- import { x as x2, y as y2 } from 'c2';
- [x1, y1, x2, y2]`),
- [1, 2, 1, 2]);
+m = parseAndEvaluate(`import { x as x1, y as y1 } from 'c1';
+ import { x as x2, y as y2 } from 'c2';
+ export let z = [x1, y1, x2, y2]`),
+assertDeepEq(getModuleEnvironmentValue(m, "z"), [1, 2, 1, 2]);
// Import access in functions
m = parseModule("import { x } from 'a'; function f() { return x; }")
diff --git a/js/src/js.msg b/js/src/js.msg
index 9dc5f4e9f..9c508ebbd 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -573,14 +573,14 @@ MSG_DEF(JSMSG_REINIT_THIS, 0, JSEXN_REFERENCEERR, "super() called twice in
// Modules
MSG_DEF(JSMSG_BAD_DEFAULT_EXPORT, 0, JSEXN_SYNTAXERR, "default export cannot be provided by export *")
-MSG_DEF(JSMSG_MISSING_INDIRECT_EXPORT, 1, JSEXN_SYNTAXERR, "indirect export '{0}' not found")
-MSG_DEF(JSMSG_AMBIGUOUS_INDIRECT_EXPORT, 1, JSEXN_SYNTAXERR, "ambiguous indirect export '{0}'")
-MSG_DEF(JSMSG_MISSING_IMPORT, 1, JSEXN_SYNTAXERR, "import '{0}' not found")
-MSG_DEF(JSMSG_AMBIGUOUS_IMPORT, 1, JSEXN_SYNTAXERR, "ambiguous import '{0}'")
+MSG_DEF(JSMSG_MISSING_INDIRECT_EXPORT, 0, JSEXN_SYNTAXERR, "indirect export not found")
+MSG_DEF(JSMSG_AMBIGUOUS_INDIRECT_EXPORT, 0, JSEXN_SYNTAXERR, "ambiguous indirect export")
+MSG_DEF(JSMSG_MISSING_IMPORT, 0, JSEXN_SYNTAXERR, "import not found")
+MSG_DEF(JSMSG_AMBIGUOUS_IMPORT, 0, JSEXN_SYNTAXERR, "ambiguous import")
MSG_DEF(JSMSG_MISSING_NAMESPACE_EXPORT, 0, JSEXN_SYNTAXERR, "export not found for namespace")
MSG_DEF(JSMSG_MISSING_EXPORT, 1, JSEXN_SYNTAXERR, "local binding for export '{0}' not found")
MSG_DEF(JSMSG_MODULE_INSTANTIATE_FAILED, 0, JSEXN_INTERNALERR, "attempt to re-instantiate module after failure")
-MSG_DEF(JSMSG_BAD_MODULE_STATE, 0, JSEXN_INTERNALERR, "module record in unexpected state")
+MSG_DEF(JSMSG_BAD_MODULE_STATUS, 0, JSEXN_INTERNALERR, "module record has unexpected status")
// Promise
MSG_DEF(JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF, 0, JSEXN_TYPEERR, "A promise cannot be resolved with itself.")
diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp
index d75a3c33a..cf5880e03 100644
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4704,21 +4704,21 @@ JS::GetModuleHostDefinedField(JSObject* module)
}
JS_PUBLIC_API(bool)
-JS::ModuleDeclarationInstantiation(JSContext* cx, JS::HandleObject moduleArg)
+JS::ModuleInstantiate(JSContext* cx, JS::HandleObject moduleArg)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, moduleArg);
- return ModuleObject::DeclarationInstantiation(cx, moduleArg.as<ModuleObject>());
+ return ModuleObject::Instantiate(cx, moduleArg.as<ModuleObject>());
}
JS_PUBLIC_API(bool)
-JS::ModuleEvaluation(JSContext* cx, JS::HandleObject moduleArg)
+JS::ModuleEvaluate(JSContext* cx, JS::HandleObject moduleArg)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, moduleArg);
- return ModuleObject::Evaluation(cx, moduleArg.as<ModuleObject>());
+ return ModuleObject::Evaluate(cx, moduleArg.as<ModuleObject>());
}
JS_PUBLIC_API(JSObject*)
@@ -4739,6 +4739,18 @@ JS::GetModuleScript(JSContext* cx, JS::HandleObject moduleArg)
return moduleArg->as<ModuleObject>().script();
}
+JS_PUBLIC_API(bool)
+JS::IsModuleErrored(JSObject* moduleArg)
+{
+ return moduleArg->as<ModuleObject>().status() == MODULE_STATUS_ERRORED;
+}
+
+JS_PUBLIC_API(JS::Value)
+JS::GetModuleError(JSObject* moduleArg)
+{
+ return moduleArg->as<ModuleObject>().error();
+}
+
JS_PUBLIC_API(JSObject*)
JS_New(JSContext* cx, HandleObject ctor, const JS::HandleValueArray& inputArgs)
{
diff --git a/js/src/jsapi.h b/js/src/jsapi.h
index 9138a4a92..9c3bf8151 100644
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4356,28 +4356,27 @@ extern JS_PUBLIC_API(JS::Value)
GetModuleHostDefinedField(JSObject* module);
/*
- * Perform the ModuleDeclarationInstantiation operation on on the give source
- * text module record.
+ * Perform the ModuleInstantiate operation on the given source text module
+ * record.
*
* This transitively resolves all module dependencies (calling the
* HostResolveImportedModule hook) and initializes the environment record for
* the module.
*/
extern JS_PUBLIC_API(bool)
-ModuleDeclarationInstantiation(JSContext* cx, JS::HandleObject moduleRecord);
+ModuleInstantiate(JSContext* cx, JS::HandleObject moduleRecord);
/*
- * Perform the ModuleEvaluation operation on on the give source text module
- * record.
+ * Perform the ModuleEvaluate operation on the given source text module record.
*
* This does nothing if this module has already been evaluated. Otherwise, it
* transitively evaluates all dependences of this module and then evaluates this
* module.
*
- * ModuleDeclarationInstantiation must have completed prior to calling this.
+ * ModuleInstantiate must have completed prior to calling this.
*/
extern JS_PUBLIC_API(bool)
-ModuleEvaluation(JSContext* cx, JS::HandleObject moduleRecord);
+ModuleEvaluate(JSContext* cx, JS::HandleObject moduleRecord);
/*
* Get a list of the module specifiers used by a source text module
@@ -4396,6 +4395,12 @@ GetRequestedModules(JSContext* cx, JS::HandleObject moduleRecord);
extern JS_PUBLIC_API(JSScript*)
GetModuleScript(JSContext* cx, JS::HandleObject moduleRecord);
+extern JS_PUBLIC_API(bool)
+IsModuleErrored(JSObject* moduleRecord);
+
+extern JS_PUBLIC_API(JS::Value)
+GetModuleError(JSObject* moduleRecord);
+
} /* namespace JS */
extern JS_PUBLIC_API(bool)
diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp
index c6a369e2d..41722ffa9 100755
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -21,6 +21,8 @@
#include "mozilla/FloatingPoint.h"
#include "mozilla/Sprintf.h"
+#include "nsCRT.h"
+
#include <ctype.h>
#include <math.h>
#include <string.h>
@@ -958,11 +960,20 @@ ParseDate(const CharT* s, size_t length, ClippedTime* result)
while (i < length) {
int c = s[i];
i++;
- if (c <= ' ' || c == ',' || c == '-') {
- if (c == '-' && '0' <= s[i] && s[i] <= '9')
+
+ // Spaces, ASCII control characters, and commas are ignored.
+ if (c <= ' ' || c == ',')
+ continue;
+
+ // Dashes are delimiters if they're immediately followed by a number field.
+ // If they're not followed by a number field, they're simply ignored.
+ if (c == '-') {
+ if (i < length && nsCRT::IsAsciiDigit(s[i])) {
prevc = c;
+ }
continue;
}
+
if (c == '(') { /* comments) */
int depth = 1;
while (i < length) {
@@ -977,7 +988,9 @@ ParseDate(const CharT* s, size_t length, ClippedTime* result)
}
continue;
}
- if ('0' <= c && c <= '9') {
+
+ // Parse a number field.
+ if (nsCRT::IsAsciiDigit(c)) {
int n = c - '0';
while (i < length && '0' <= (c = s[i]) && c <= '9') {
n = n * 10 + c - '0';
diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h
index b4a2de6f3..aa555886e 100644
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -227,8 +227,8 @@
macro(missingArguments, missingArguments, "missingArguments") \
macro(module, module, "module") \
macro(Module, Module, "Module") \
- macro(ModuleDeclarationInstantiation, ModuleDeclarationInstantiation, "ModuleDeclarationInstantiation") \
- macro(ModuleEvaluation, ModuleEvaluation, "ModuleEvaluation") \
+ macro(ModuleInstantiate, ModuleInstantiate, "ModuleInstantiate") \
+ macro(ModuleEvaluate, ModuleEvaluate, "ModuleEvaluate") \
macro(month, month, "month") \
macro(multiline, multiline, "multiline") \
macro(name, name, "name") \
diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h
index 3a3e50244..e9c59ff7c 100644
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -646,7 +646,10 @@ class NativeObject : public ShapedObject
uint32_t slotSpan() const {
if (inDictionaryMode())
return lastProperty()->base()->slotSpan();
- return lastProperty()->slotSpan();
+
+ // Get the class from the object group rather than the base shape to avoid a
+ // race between Shape::ensureOwnBaseShape and background sweeping.
+ return lastProperty()->slotSpan(getClass());
}
/* Whether a slot is at a fixed offset from this object. */
diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp
index 2216bf91e..0dfeffc36 100644
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -345,6 +345,50 @@ intrinsic_ThrowInternalError(JSContext* cx, unsigned argc, Value* vp)
return false;
}
+static bool
+intrinsic_GetErrorMessage(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ MOZ_ASSERT(args[0].isInt32());
+
+ const JSErrorFormatString* errorString = GetErrorMessage(nullptr, args[0].toInt32());
+ MOZ_ASSERT(errorString);
+
+ MOZ_ASSERT(errorString->argCount == 0);
+ RootedString message(cx, JS_NewStringCopyZ(cx, errorString->format));
+ if (!message)
+ return false;
+
+ args.rval().setString(message);
+ return true;
+}
+
+static bool
+intrinsic_CreateModuleSyntaxError(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 4);
+ MOZ_ASSERT(args[0].isObject());
+ MOZ_ASSERT(args[1].isInt32());
+ MOZ_ASSERT(args[2].isInt32());
+ MOZ_ASSERT(args[3].isString());
+
+ RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
+ RootedString filename(cx, JS_NewStringCopyZ(cx, module->script()->filename()));
+ RootedString message(cx, args[3].toString());
+
+ RootedValue error(cx);
+ if (!JS::CreateError(cx, JSEXN_SYNTAXERR, nullptr, filename, args[1].toInt32(),
+ args[2].toInt32(), nullptr, message, &error))
+ {
+ return false;
+ }
+
+ args.rval().set(error);
+ return true;
+}
+
/**
* Handles an assertion failure in self-hosted code just like an assertion
* failure in C++ code. Information about the failure can be provided in args[0].
@@ -2060,24 +2104,12 @@ intrinsic_InstantiateModuleFunctionDeclarations(JSContext* cx, unsigned argc, Va
}
static bool
-intrinsic_SetModuleState(JSContext* cx, unsigned argc, Value* vp)
-{
- CallArgs args = CallArgsFromVp(argc, vp);
- MOZ_ASSERT(args.length() == 2);
- RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
- ModuleState newState = args[1].toInt32();
- module->setState(newState);
- args.rval().setUndefined();
- return true;
-}
-
-static bool
-intrinsic_EvaluateModule(JSContext* cx, unsigned argc, Value* vp)
+intrinsic_ExecuteModule(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
- return ModuleObject::evaluate(cx, module, args.rval());
+ return ModuleObject::execute(cx, module, args.rval());
}
static bool
@@ -2351,6 +2383,8 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("ThrowTypeError", intrinsic_ThrowTypeError, 4,0),
JS_FN("ThrowSyntaxError", intrinsic_ThrowSyntaxError, 4,0),
JS_FN("ThrowInternalError", intrinsic_ThrowInternalError, 4,0),
+ JS_FN("GetErrorMessage", intrinsic_GetErrorMessage, 1,0),
+ JS_FN("CreateModuleSyntaxError", intrinsic_CreateModuleSyntaxError, 4,0),
JS_FN("AssertionFailed", intrinsic_AssertionFailed, 1,0),
JS_FN("DumpMessage", intrinsic_DumpMessage, 1,0),
JS_FN("OwnPropertyKeys", intrinsic_OwnPropertyKeys, 1,0),
@@ -2630,8 +2664,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("CreateNamespaceBinding", intrinsic_CreateNamespaceBinding, 3, 0),
JS_FN("InstantiateModuleFunctionDeclarations",
intrinsic_InstantiateModuleFunctionDeclarations, 1, 0),
- JS_FN("SetModuleState", intrinsic_SetModuleState, 1, 0),
- JS_FN("EvaluateModule", intrinsic_EvaluateModule, 1, 0),
+ JS_FN("ExecuteModule", intrinsic_ExecuteModule, 1, 0),
JS_FN("NewModuleNamespace", intrinsic_NewModuleNamespace, 2, 0),
JS_FN("AddModuleNamespaceBinding", intrinsic_AddModuleNamespaceBinding, 4, 0),
JS_FN("ModuleNamespaceExports", intrinsic_ModuleNamespaceExports, 1, 0),
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);
diff --git a/js/xpconnect/loader/mozJSSubScriptLoader.cpp b/js/xpconnect/loader/mozJSSubScriptLoader.cpp
index f23e5833a..baf7a9392 100644
--- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp
@@ -15,7 +15,6 @@
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsIFileURL.h"
-#include "nsScriptLoader.h"
#include "nsIScriptSecurityManager.h"
#include "nsThreadUtils.h"
@@ -26,6 +25,7 @@
#include "jswrapper.h"
#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ScriptLoader.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/scache/StartupCache.h"
@@ -139,8 +139,8 @@ PrepareScript(nsIURI* uri,
size_t scriptLength = 0;
nsresult rv =
- nsScriptLoader::ConvertToUTF16(nullptr, reinterpret_cast<const uint8_t*>(buf), len,
- charset, nullptr, scriptBuf, scriptLength);
+ ScriptLoader::ConvertToUTF16(nullptr, reinterpret_cast<const uint8_t*>(buf), len,
+ charset, nullptr, scriptBuf, scriptLength);
JS::SourceBufferHolder srcBuf(scriptBuf, scriptLength,
JS::SourceBufferHolder::GiveOwnership);
@@ -826,9 +826,9 @@ ScriptPrecompiler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
// Convert data to char16_t* and prepare to call CompileOffThread.
nsAutoString hintCharset;
nsresult rv =
- nsScriptLoader::ConvertToUTF16(mChannel, aString, aLength,
- hintCharset, nullptr,
- mScriptBuf, mScriptLength);
+ ScriptLoader::ConvertToUTF16(mChannel, aString, aLength,
+ hintCharset, nullptr,
+ mScriptBuf, mScriptLength);
NS_ENSURE_SUCCESS(rv, NS_OK);
diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp
index bde949a96..511bc8a98 100644
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -35,13 +35,13 @@
#include "nsCCUncollectableMarker.h"
#include "nsCycleCollectionNoteRootCallback.h"
#include "nsCycleCollector.h"
-#include "nsScriptLoader.h"
#include "jsapi.h"
#include "jsprf.h"
#include "js/MemoryMetrics.h"
#include "mozilla/dom/GeneratedAtomList.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScriptLoader.h"
#include "mozilla/dom/WindowBinding.h"
#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
#include "mozilla/Atomics.h"
@@ -3035,8 +3035,8 @@ ReadSourceFromFilename(JSContext* cx, const char* filename, char16_t** src, size
ptr += bytesRead;
}
- rv = nsScriptLoader::ConvertToUTF16(scriptChannel, buf.get(), rawLen, EmptyString(),
- nullptr, *src, *len);
+ rv = ScriptLoader::ConvertToUTF16(scriptChannel, buf.get(), rawLen, EmptyString(),
+ nullptr, *src, *len);
NS_ENSURE_SUCCESS(rv, rv);
if (!*src)
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/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
}
}
diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.c b/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.c
index 6c07ae2a6..f08a20a1f 100644
--- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.c
@@ -418,8 +418,7 @@ static int nr_ice_candidate_copy_for_triggered_check(nr_ice_cand_pair *pair)
copy->nominated = pair->nominated;
r_log(LOG_ICE,LOG_INFO,"CAND-PAIR(%s): Adding pair to check list and trigger check queue: %s",pair->codeword,pair->as_string);
- if(r=nr_ice_candidate_pair_insert(&pair->remote->stream->check_list,copy))
- ABORT(r);
+ nr_ice_candidate_pair_insert(&pair->remote->stream->check_list,copy);
nr_ice_candidate_pair_trigger_check_append(&pair->remote->stream->trigger_check_queue,copy);
copy->triggered = 1;
@@ -601,7 +600,7 @@ int nr_ice_candidate_pair_trigger_check_append(nr_ice_cand_pair_head *head,nr_ic
return(0);
}
-int nr_ice_candidate_pair_insert(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair)
+void nr_ice_candidate_pair_insert(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair)
{
nr_ice_cand_pair *c1;
@@ -615,8 +614,6 @@ int nr_ice_candidate_pair_insert(nr_ice_cand_pair_head *head,nr_ice_cand_pair *p
c1=TAILQ_NEXT(c1,check_queue_entry);
}
if(!c1) TAILQ_INSERT_TAIL(head,pair,check_queue_entry);
-
- return(0);
}
void nr_ice_candidate_pair_restart_stun_nominated_cb(NR_SOCKET s, int how, void *cb_arg)
diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.h b/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.h
index 171ded4a0..cb3d61eca 100644
--- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.h
@@ -82,7 +82,7 @@ int nr_ice_candidate_pair_dump_state(nr_ice_cand_pair *pair, FILE *out);
int nr_ice_candidate_pair_cancel(nr_ice_peer_ctx *pctx,nr_ice_cand_pair *pair, int move_to_wait_state);
int nr_ice_candidate_pair_select(nr_ice_cand_pair *pair);
int nr_ice_candidate_pair_do_triggered_check(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair);
-int nr_ice_candidate_pair_insert(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair);
+void nr_ice_candidate_pair_insert(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair);
int nr_ice_candidate_pair_trigger_check_append(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair);
void nr_ice_candidate_pair_restart_stun_nominated_cb(NR_SOCKET s, int how, void *cb_arg);
int nr_ice_candidate_pair_destroy(nr_ice_cand_pair **pairp);
diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_component.c b/media/mtransport/third_party/nICEr/src/ice/ice_component.c
index 11b4fcbc1..7a42eb208 100644
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c
@@ -1613,8 +1613,7 @@ int nr_ice_component_finalize(nr_ice_component *lcomp, nr_ice_component *rcomp)
int nr_ice_component_insert_pair(nr_ice_component *pcomp, nr_ice_cand_pair *pair)
{
- int r,_status;
- int pair_inserted=0;
+ int _status;
/* Pairs for peer reflexive are marked SUCCEEDED immediately */
if (pair->state != NR_ICE_PAIR_STATE_FROZEN &&
@@ -1623,10 +1622,8 @@ int nr_ice_component_insert_pair(nr_ice_component *pcomp, nr_ice_cand_pair *pair
ABORT(R_BAD_ARGS);
}
- if(r=nr_ice_candidate_pair_insert(&pair->remote->stream->check_list,pair))
- ABORT(r);
-
- pair_inserted=1;
+ /* We do not throw an error after this, because we've inserted the pair. */
+ nr_ice_candidate_pair_insert(&pair->remote->stream->check_list,pair);
/* Make sure the check timer is running, if the stream was previously
* started. We will not start streams just because a pair was created,
@@ -1638,13 +1635,12 @@ int nr_ice_component_insert_pair(nr_ice_component *pcomp, nr_ice_cand_pair *pair
!pair->remote->stream->pctx->checks_started)){
if(nr_ice_media_stream_start_checks(pair->remote->stream->pctx, pair->remote->stream)) {
r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s)/CAND-PAIR(%s): Could not restart checks for new pair %s.",pair->remote->stream->pctx->label, pair->codeword, pair->as_string);
- ABORT(R_INTERNAL);
}
}
_status=0;
abort:
- if (_status && !pair_inserted) {
+ if (_status) {
nr_ice_candidate_pair_destroy(&pair);
}
return(_status);
diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
index 0193c5ef1..6edd41e70 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);
@@ -1248,8 +1252,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
@@ -4735,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);
@@ -5377,13 +5386,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/netwerk/base/nsStandardURL.cpp b/netwerk/base/nsStandardURL.cpp
index 81b485502..1866c1037 100644
--- a/netwerk/base/nsStandardURL.cpp
+++ b/netwerk/base/nsStandardURL.cpp
@@ -2747,12 +2747,16 @@ nsStandardURL::SetFilePath(const nsACString &input)
return SetSpec(spec);
}
- else if (mPath.mLen > 1) {
+
+ if (mPath.mLen > 1) {
mSpec.Cut(mPath.mPos + 1, mFilepath.mLen - 1);
// left shift query, and ref
ShiftFromQuery(1 - mFilepath.mLen);
+ // One character for '/', and if we have a query or ref we add their
+ // length and one extra for each '?' or '#' characters
+ mPath.mLen = 1 + (mQuery.mLen >= 0 ? (mQuery.mLen + 1) : 0) +
+ (mRef.mLen >= 0 ? (mRef.mLen + 1) : 0);
// these contain only a '/'
- mPath.mLen = 1;
mDirectory.mLen = 1;
mFilepath.mLen = 1;
// these are no longer defined
diff --git a/parser/html/nsHtml5DocumentBuilder.cpp b/parser/html/nsHtml5DocumentBuilder.cpp
index ba8a333c4..aff199a45 100644
--- a/parser/html/nsHtml5DocumentBuilder.cpp
+++ b/parser/html/nsHtml5DocumentBuilder.cpp
@@ -8,8 +8,8 @@
#include "nsIStyleSheetLinkingElement.h"
#include "nsStyleLinkElement.h"
-#include "nsScriptLoader.h"
#include "nsIHTMLDocument.h"
+#include "mozilla/dom/ScriptLoader.h"
NS_IMPL_CYCLE_COLLECTION_INHERITED(nsHtml5DocumentBuilder, nsContentSink,
mOwnedElements)
diff --git a/parser/html/nsHtml5OplessBuilder.cpp b/parser/html/nsHtml5OplessBuilder.cpp
index ac1c03f10..65b97ffc7 100644
--- a/parser/html/nsHtml5OplessBuilder.cpp
+++ b/parser/html/nsHtml5OplessBuilder.cpp
@@ -6,8 +6,8 @@
#include "nsHtml5OplessBuilder.h"
-#include "nsScriptLoader.h"
#include "mozilla/css/Loader.h"
+#include "mozilla/dom/ScriptLoader.h"
#include "nsIDocShell.h"
#include "nsIHTMLDocument.h"
diff --git a/parser/html/nsHtml5TreeOpExecutor.cpp b/parser/html/nsHtml5TreeOpExecutor.cpp
index 95f177376..3ed634d0c 100644
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -7,10 +7,10 @@
#include "mozilla/DebugOnly.h"
#include "mozilla/Likely.h"
#include "mozilla/dom/nsCSPService.h"
+#include "mozilla/dom/ScriptLoader.h"
#include "nsError.h"
#include "nsHtml5TreeOpExecutor.h"
-#include "nsScriptLoader.h"
#include "nsIContentViewer.h"
#include "nsIContentSecurityPolicy.h"
#include "nsIDocShellTreeItem.h"
diff --git a/parser/html/nsParserUtils.cpp b/parser/html/nsParserUtils.cpp
index 9e0bb8c9e..2085cd149 100644
--- a/parser/html/nsParserUtils.cpp
+++ b/parser/html/nsParserUtils.cpp
@@ -9,7 +9,6 @@
#include "nsXPCOM.h"
#include "nsISupportsPrimitives.h"
#include "nsXPIDLString.h"
-#include "nsScriptLoader.h"
#include "nsEscape.h"
#include "nsIParser.h"
#include "nsIDTD.h"
@@ -36,6 +35,7 @@
#include "nsTreeSanitizer.h"
#include "nsHtml5Module.h"
#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/ScriptLoader.h"
#include "nsNullPrincipal.h"
#define XHTML_DIV_TAG "div xmlns=\"http://www.w3.org/1999/xhtml\""
@@ -148,7 +148,7 @@ nsParserUtils::ParseFragment(const nsAString& aFragment,
nsAutoScriptBlockerSuppressNodeRemoved autoBlocker;
// stop scripts
- RefPtr<nsScriptLoader> loader;
+ RefPtr<ScriptLoader> loader;
bool scripts_enabled = false;
if (document) {
loader = document->ScriptLoader();
diff --git a/parser/htmlparser/nsParser.cpp b/parser/htmlparser/nsParser.cpp
index dd140c553..d1e521750 100644
--- a/parser/htmlparser/nsParser.cpp
+++ b/parser/htmlparser/nsParser.cpp
@@ -28,7 +28,6 @@
#include "nsIFragmentContentSink.h"
#include "nsStreamUtils.h"
#include "nsHTMLTokenizer.h"
-#include "nsScriptLoader.h"
#include "nsDataHashtable.h"
#include "nsXPCOMCIDInternal.h"
#include "nsMimeTypes.h"
@@ -41,6 +40,7 @@
#include "nsIHTMLContentSink.h"
#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/ScriptLoader.h"
#include "mozilla/BinarySearch.h"
using namespace mozilla;
diff --git a/security/nss/coreconf/coreconf.dep b/security/nss/coreconf/coreconf.dep
index 590d1bfae..5182f7555 100644
--- a/security/nss/coreconf/coreconf.dep
+++ b/security/nss/coreconf/coreconf.dep
@@ -10,4 +10,3 @@
*/
#error "Do not include this header file."
-
diff --git a/security/nss/lib/freebl/mpi/mpi.c b/security/nss/lib/freebl/mpi/mpi.c
index 7e96e51ff..1b7b171e7 100644
--- a/security/nss/lib/freebl/mpi/mpi.c
+++ b/security/nss/lib/freebl/mpi/mpi.c
@@ -8,6 +8,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mpi-priv.h"
+#include "mplogic.h"
#if defined(OSF1)
#include <c_asm.h>
#endif
@@ -1688,98 +1689,112 @@ mp_iseven(const mp_int *a)
/* {{{ mp_gcd(a, b, c) */
/*
- Like the old mp_gcd() function, except computes the GCD using the
- binary algorithm due to Josef Stein in 1961 (via Knuth).
+ Computes the GCD using the constant-time algorithm
+ by Bernstein and Yang (https://eprint.iacr.org/2019/266)
+ "Fast constant-time gcd computation and modular inversion"
*/
mp_err
mp_gcd(mp_int *a, mp_int *b, mp_int *c)
{
mp_err res;
- mp_int u, v, t;
- mp_size k = 0;
+ mp_digit cond = 0, mask = 0;
+ mp_int g, temp, f;
+ int i, j, m, bit = 1, delta = 1, shifts = 0, last = -1;
+ mp_size top, flen, glen;
+ mp_int *clear[3];
ARGCHK(a != NULL && b != NULL && c != NULL, MP_BADARG);
-
- if (mp_cmp_z(a) == MP_EQ && mp_cmp_z(b) == MP_EQ)
- return MP_RANGE;
+ /*
+ Early exit if either of the inputs is zero.
+ Caller is responsible for the proper handling of inputs.
+ */
if (mp_cmp_z(a) == MP_EQ) {
- return mp_copy(b, c);
+ res = mp_copy(b, c);
+ SIGN(c) = ZPOS;
+ return res;
} else if (mp_cmp_z(b) == MP_EQ) {
- return mp_copy(a, c);
- }
-
- if ((res = mp_init(&t)) != MP_OKAY)
+ res = mp_copy(a, c);
+ SIGN(c) = ZPOS;
return res;
- if ((res = mp_init_copy(&u, a)) != MP_OKAY)
- goto U;
- if ((res = mp_init_copy(&v, b)) != MP_OKAY)
- goto V;
-
- SIGN(&u) = ZPOS;
- SIGN(&v) = ZPOS;
-
- /* Divide out common factors of 2 until at least 1 of a, b is even */
- while (mp_iseven(&u) && mp_iseven(&v)) {
- s_mp_div_2(&u);
- s_mp_div_2(&v);
- ++k;
}
- /* Initialize t */
- if (mp_isodd(&u)) {
- if ((res = mp_copy(&v, &t)) != MP_OKAY)
- goto CLEANUP;
-
- /* t = -v */
- if (SIGN(&v) == ZPOS)
- SIGN(&t) = NEG;
- else
- SIGN(&t) = ZPOS;
+ MP_CHECKOK(mp_init(&temp));
+ clear[++last] = &temp;
+ MP_CHECKOK(mp_init_copy(&g, a));
+ clear[++last] = &g;
+ MP_CHECKOK(mp_init_copy(&f, b));
+ clear[++last] = &f;
- } else {
- if ((res = mp_copy(&u, &t)) != MP_OKAY)
- goto CLEANUP;
+ /*
+ For even case compute the number of
+ shared powers of 2 in f and g.
+ */
+ for (i = 0; i < USED(&f) && i < USED(&g); i++) {
+ mask = ~(DIGIT(&f, i) | DIGIT(&g, i));
+ for (j = 0; j < MP_DIGIT_BIT; j++) {
+ bit &= mask;
+ shifts += bit;
+ mask >>= 1;
+ }
}
+ /* Reduce to the odd case by removing the powers of 2. */
+ s_mp_div_2d(&f, shifts);
+ s_mp_div_2d(&g, shifts);
- for (;;) {
- while (mp_iseven(&t)) {
- s_mp_div_2(&t);
- }
+ /* Allocate to the size of largest mp_int. */
+ top = (mp_size)1 + ((USED(&f) >= USED(&g)) ? USED(&f) : USED(&g));
+ MP_CHECKOK(s_mp_grow(&f, top));
+ MP_CHECKOK(s_mp_grow(&g, top));
+ MP_CHECKOK(s_mp_grow(&temp, top));
- if (mp_cmp_z(&t) == MP_GT) {
- if ((res = mp_copy(&t, &u)) != MP_OKAY)
- goto CLEANUP;
+ /* Make sure f contains the odd value. */
+ MP_CHECKOK(mp_cswap((~DIGIT(&f, 0) & 1), &f, &g, top));
- } else {
- if ((res = mp_copy(&t, &v)) != MP_OKAY)
- goto CLEANUP;
+ /* Upper bound for the total iterations. */
+ flen = mpl_significant_bits(&f);
+ glen = mpl_significant_bits(&g);
+ m = 4 + 3 * ((flen >= glen) ? flen : glen);
- /* v = -t */
- if (SIGN(&t) == ZPOS)
- SIGN(&v) = NEG;
- else
- SIGN(&v) = ZPOS;
- }
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable : 4146) // Thanks MSVC, we know what we're negating an unsigned mp_digit
+#endif
- if ((res = mp_sub(&u, &v, &t)) != MP_OKAY)
- goto CLEANUP;
+ for (i = 0; i < m; i++) {
+ /* Step 1: conditional swap. */
+ /* Set cond if delta > 0 and g is odd. */
+ cond = (-delta >> (8 * sizeof(delta) - 1)) & DIGIT(&g, 0) & 1;
+ /* If cond is set replace (delta,f) with (-delta,-f). */
+ delta = (-cond & -delta) | ((cond - 1) & delta);
+ SIGN(&f) ^= cond;
+ /* If cond is set swap f with g. */
+ MP_CHECKOK(mp_cswap(cond, &f, &g, top));
+
+ /* Step 2: elemination. */
+ /* Update delta. */
+ delta++;
+ /* If g is odd, right shift (g+f) else right shift g. */
+ MP_CHECKOK(mp_add(&g, &f, &temp));
+ MP_CHECKOK(mp_cswap((DIGIT(&g, 0) & 1), &g, &temp, top));
+ s_mp_div_2(&g);
+ }
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
- if (s_mp_cmp_d(&t, 0) == MP_EQ)
- break;
- }
+ /* GCD is in f, take the absolute value. */
+ SIGN(&f) = ZPOS;
- s_mp_2expt(&v, k); /* v = 2^k */
- res = mp_mul(&u, &v, c); /* c = u * v */
+ /* Add back the removed powers of 2. */
+ MP_CHECKOK(s_mp_mul_2d(&f, shifts));
-CLEANUP:
- mp_clear(&v);
-V:
- mp_clear(&u);
-U:
- mp_clear(&t);
+ MP_CHECKOK(mp_copy(&f, c));
+CLEANUP:
+ while (last >= 0)
+ mp_clear(clear[last--]);
return res;
-
} /* end mp_gcd() */
/* }}} */
@@ -2131,42 +2146,114 @@ CLEANUP:
return res;
}
-/* compute mod inverse using Schroeppel's method, only if m is odd */
+/*
+ Computes the modular inverse using the constant-time algorithm
+ by Bernstein and Yang (https://eprint.iacr.org/2019/266)
+ "Fast constant-time gcd computation and modular inversion"
+ */
mp_err
s_mp_invmod_odd_m(const mp_int *a, const mp_int *m, mp_int *c)
{
- int k;
mp_err res;
- mp_int x;
+ mp_digit cond = 0;
+ mp_int g, f, v, r, temp;
+ int i, its, delta = 1, last = -1;
+ mp_size top, flen, glen;
+ mp_int *clear[6];
ARGCHK(a != NULL && m != NULL && c != NULL, MP_BADARG);
-
- if (mp_cmp_z(a) == 0 || mp_cmp_z(m) == 0)
+ /* Check for invalid inputs. */
+ if (mp_cmp_z(a) == MP_EQ || mp_cmp_d(m, 2) == MP_LT)
return MP_RANGE;
- if (mp_iseven(m))
+
+ if (a == m || mp_iseven(m))
return MP_UNDEF;
- MP_DIGITS(&x) = 0;
+ MP_CHECKOK(mp_init(&temp));
+ clear[++last] = &temp;
+ MP_CHECKOK(mp_init(&v));
+ clear[++last] = &v;
+ MP_CHECKOK(mp_init(&r));
+ clear[++last] = &r;
+ MP_CHECKOK(mp_init_copy(&g, a));
+ clear[++last] = &g;
+ MP_CHECKOK(mp_init_copy(&f, m));
+ clear[++last] = &f;
+
+ mp_set(&v, 0);
+ mp_set(&r, 1);
+
+ /* Allocate to the size of largest mp_int. */
+ top = (mp_size)1 + ((USED(&f) >= USED(&g)) ? USED(&f) : USED(&g));
+ MP_CHECKOK(s_mp_grow(&f, top));
+ MP_CHECKOK(s_mp_grow(&g, top));
+ MP_CHECKOK(s_mp_grow(&temp, top));
+ MP_CHECKOK(s_mp_grow(&v, top));
+ MP_CHECKOK(s_mp_grow(&r, top));
+
+ /* Upper bound for the total iterations. */
+ flen = mpl_significant_bits(&f);
+ glen = mpl_significant_bits(&g);
+ its = 4 + 3 * ((flen >= glen) ? flen : glen);
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable : 4146) // Thanks MSVC, we know what we're negating an unsigned mp_digit
+#endif
- if (a == c) {
- if ((res = mp_init_copy(&x, a)) != MP_OKAY)
- return res;
- if (a == m)
- m = &x;
- a = &x;
- } else if (m == c) {
- if ((res = mp_init_copy(&x, m)) != MP_OKAY)
- return res;
- m = &x;
- } else {
- MP_DIGITS(&x) = 0;
+ for (i = 0; i < its; i++) {
+ /* Step 1: conditional swap. */
+ /* Set cond if delta > 0 and g is odd. */
+ cond = (-delta >> (8 * sizeof(delta) - 1)) & DIGIT(&g, 0) & 1;
+ /* If cond is set replace (delta,f,v) with (-delta,-f,-v). */
+ delta = (-cond & -delta) | ((cond - 1) & delta);
+ SIGN(&f) ^= cond;
+ SIGN(&v) ^= cond;
+ /* If cond is set swap (f,v) with (g,r). */
+ MP_CHECKOK(mp_cswap(cond, &f, &g, top));
+ MP_CHECKOK(mp_cswap(cond, &v, &r, top));
+
+ /* Step 2: elemination. */
+ /* Update delta */
+ delta++;
+ /* If g is odd replace r with (r+v). */
+ MP_CHECKOK(mp_add(&r, &v, &temp));
+ MP_CHECKOK(mp_cswap((DIGIT(&g, 0) & 1), &r, &temp, top));
+ /* If g is odd, right shift (g+f) else right shift g. */
+ MP_CHECKOK(mp_add(&g, &f, &temp));
+ MP_CHECKOK(mp_cswap((DIGIT(&g, 0) & 1), &g, &temp, top));
+ s_mp_div_2(&g);
+ /*
+ If r is even, right shift it.
+ If r is odd, right shift (r+m) which is even because m is odd.
+ We want the result modulo m so adding in multiples of m here vanish.
+ */
+ MP_CHECKOK(mp_add(&r, m, &temp));
+ MP_CHECKOK(mp_cswap((DIGIT(&r, 0) & 1), &r, &temp, top));
+ s_mp_div_2(&r);
}
- MP_CHECKOK(s_mp_almost_inverse(a, m, c));
- k = res;
- MP_CHECKOK(s_mp_fixup_reciprocal(c, m, k, c));
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+ /* We have the inverse in v, propagate sign from f. */
+ SIGN(&v) ^= SIGN(&f);
+ /* GCD is in f, take the absolute value. */
+ SIGN(&f) = ZPOS;
+
+ /* If gcd != 1, not invertible. */
+ if (mp_cmp_d(&f, 1) != MP_EQ) {
+ res = MP_UNDEF;
+ goto CLEANUP;
+ }
+
+ /* Return inverse modulo m. */
+ MP_CHECKOK(mp_mod(&v, m, c));
+
CLEANUP:
- mp_clear(&x);
+ while (last >= 0)
+ mp_clear(clear[last--]);
return res;
}
@@ -2218,13 +2305,24 @@ s_mp_invmod_2d(const mp_int *a, mp_size k, mp_int *c)
if (mp_iseven(a))
return MP_UNDEF;
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable : 4146) // Thanks MSVC, we know what we're negating an unsigned mp_digit
+#endif
if (k <= MP_DIGIT_BIT) {
mp_digit i = s_mp_invmod_radix(MP_DIGIT(a, 0));
+ /* propagate the sign from mp_int */
+ i = (i ^ -(mp_digit)SIGN(a)) + (mp_digit)SIGN(a);
if (k < MP_DIGIT_BIT)
i &= ((mp_digit)1 << k) - (mp_digit)1;
mp_set(c, i);
return MP_OKAY;
}
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
MP_DIGITS(&t0) = 0;
MP_DIGITS(&t1) = 0;
MP_DIGITS(&val) = 0;
@@ -2831,6 +2929,8 @@ s_mp_clamp(mp_int *mp)
while (used > 1 && DIGIT(mp, used - 1) == 0)
--used;
MP_USED(mp) = used;
+ if (used == 1 && DIGIT(mp, 0) == 0)
+ MP_SIGN(mp) = ZPOS;
} /* end s_mp_clamp() */
/* }}} */
@@ -2908,37 +3008,36 @@ mp_err
s_mp_mul_2d(mp_int *mp, mp_digit d)
{
mp_err res;
- mp_digit dshift, bshift;
- mp_digit mask;
+ mp_digit dshift, rshift, mask, x, prev = 0;
+ mp_digit *pa = NULL;
+ int i;
ARGCHK(mp != NULL, MP_BADARG);
dshift = d / MP_DIGIT_BIT;
- bshift = d % MP_DIGIT_BIT;
+ d %= MP_DIGIT_BIT;
+ /* mp_digit >> rshift is undefined behavior for rshift >= MP_DIGIT_BIT */
+ /* mod and corresponding mask logic avoid that when d = 0 */
+ rshift = MP_DIGIT_BIT - d;
+ rshift %= MP_DIGIT_BIT;
+ /* mask = (2**d - 1) * 2**(w-d) mod 2**w */
+ mask = (DIGIT_MAX << rshift) + 1;
+ mask &= DIGIT_MAX - 1;
/* bits to be shifted out of the top word */
- if (bshift) {
- mask = (mp_digit)~0 << (MP_DIGIT_BIT - bshift);
- mask &= MP_DIGIT(mp, MP_USED(mp) - 1);
- } else {
- mask = 0;
- }
+ x = MP_DIGIT(mp, MP_USED(mp) - 1) & mask;
- if (MP_OKAY != (res = s_mp_pad(mp, MP_USED(mp) + dshift + (mask != 0))))
+ if (MP_OKAY != (res = s_mp_pad(mp, MP_USED(mp) + dshift + (x != 0))))
return res;
if (dshift && MP_OKAY != (res = s_mp_lshd(mp, dshift)))
return res;
- if (bshift) {
- mp_digit *pa = MP_DIGITS(mp);
- mp_digit *alim = pa + MP_USED(mp);
- mp_digit prev = 0;
+ pa = MP_DIGITS(mp) + dshift;
- for (pa += dshift; pa < alim;) {
- mp_digit x = *pa;
- *pa++ = (x << bshift) | prev;
- prev = x >> (DIGIT_BIT - bshift);
- }
+ for (i = MP_USED(mp) - dshift; i > 0; i--) {
+ x = *pa;
+ *pa++ = (x << d) | prev;
+ prev = (x & mask) >> rshift;
}
s_mp_clamp(mp);
@@ -3077,18 +3176,20 @@ void
s_mp_div_2d(mp_int *mp, mp_digit d)
{
int ix;
- mp_digit save, next, mask;
+ mp_digit save, next, mask, lshift;
s_mp_rshd(mp, d / DIGIT_BIT);
d %= DIGIT_BIT;
- if (d) {
- mask = ((mp_digit)1 << d) - 1;
- save = 0;
- for (ix = USED(mp) - 1; ix >= 0; ix--) {
- next = DIGIT(mp, ix) & mask;
- DIGIT(mp, ix) = (DIGIT(mp, ix) >> d) | (save << (DIGIT_BIT - d));
- save = next;
- }
+ /* mp_digit << lshift is undefined behavior for lshift >= MP_DIGIT_BIT */
+ /* mod and corresponding mask logic avoid that when d = 0 */
+ lshift = DIGIT_BIT - d;
+ lshift %= DIGIT_BIT;
+ mask = ((mp_digit)1 << d) - 1;
+ save = 0;
+ for (ix = USED(mp) - 1; ix >= 0; ix--) {
+ next = DIGIT(mp, ix) & mask;
+ DIGIT(mp, ix) = (save << lshift) | (DIGIT(mp, ix) >> d);
+ save = next;
}
s_mp_clamp(mp);
@@ -4841,5 +4942,44 @@ mp_to_fixlen_octets(const mp_int *mp, unsigned char *str, mp_size length)
} /* end mp_to_fixlen_octets() */
/* }}} */
+/* {{{ mp_cswap(condition, a, b, numdigits) */
+/* performs a conditional swap between mp_int. */
+mp_err
+mp_cswap(mp_digit condition, mp_int *a, mp_int *b, mp_size numdigits)
+{
+ mp_digit x;
+ unsigned int i;
+ mp_err res = 0;
+
+ /* if pointers are equal return */
+ if (a == b)
+ return res;
+
+ if (MP_ALLOC(a) < numdigits || MP_ALLOC(b) < numdigits) {
+ MP_CHECKOK(s_mp_grow(a, numdigits));
+ MP_CHECKOK(s_mp_grow(b, numdigits));
+ }
+
+ condition = ((~condition & ((condition - 1))) >> (MP_DIGIT_BIT - 1)) - 1;
+
+ x = (USED(a) ^ USED(b)) & condition;
+ USED(a) ^= x;
+ USED(b) ^= x;
+
+ x = (SIGN(a) ^ SIGN(b)) & condition;
+ SIGN(a) ^= x;
+ SIGN(b) ^= x;
+
+ for (i = 0; i < numdigits; i++) {
+ x = (DIGIT(a, i) ^ DIGIT(b, i)) & condition;
+ DIGIT(a, i) ^= x;
+ DIGIT(b, i) ^= x;
+ }
+
+CLEANUP:
+ return res;
+} /* end mp_cswap() */
+/* }}} */
+
/*------------------------------------------------------------------------*/
/* HERE THERE BE DRAGONS */
diff --git a/security/nss/lib/freebl/mpi/mpi.h b/security/nss/lib/freebl/mpi/mpi.h
index af608b43d..b1a07a61d 100644
--- a/security/nss/lib/freebl/mpi/mpi.h
+++ b/security/nss/lib/freebl/mpi/mpi.h
@@ -267,6 +267,7 @@ mp_size mp_trailing_zeros(const mp_int *mp);
void freebl_cpuid(unsigned long op, unsigned long *eax,
unsigned long *ebx, unsigned long *ecx,
unsigned long *edx);
+mp_err mp_cswap(mp_digit condition, mp_int *a, mp_int *b, mp_size numdigits);
#define MP_CHECKOK(x) \
if (MP_OKAY > (res = (x))) \
diff --git a/security/nss/lib/freebl/mpi/mplogic.c b/security/nss/lib/freebl/mpi/mplogic.c
index 89fd03ae8..23ddfec1a 100644
--- a/security/nss/lib/freebl/mpi/mplogic.c
+++ b/security/nss/lib/freebl/mpi/mplogic.c
@@ -409,35 +409,54 @@ mpl_get_bits(const mp_int *a, mp_size lsbNum, mp_size numBits)
return (mp_err)mask;
}
+#define LZCNTLOOP(i) \
+ do { \
+ x = d >> (i); \
+ mask = (0 - x); \
+ mask = (0 - (mask >> (MP_DIGIT_BIT - 1))); \
+ bits += (i)&mask; \
+ d ^= (x ^ d) & mask; \
+ } while (0)
+
/*
mpl_significant_bits
- returns number of significnant bits in abs(a).
+ returns number of significant bits in abs(a).
+ In other words: floor(lg(abs(a))) + 1.
returns 1 if value is zero.
*/
mp_size
mpl_significant_bits(const mp_int *a)
{
- mp_size bits = 0;
+ /*
+ start bits at 1.
+ lg(0) = 0 => bits = 1 by function semantics.
+ below does a binary search for the _position_ of the top bit set,
+ which is floor(lg(abs(a))) for a != 0.
+ */
+ mp_size bits = 1;
int ix;
ARGCHK(a != NULL, MP_BADARG);
for (ix = MP_USED(a); ix > 0;) {
- mp_digit d;
- d = MP_DIGIT(a, --ix);
- if (d) {
- while (d) {
- ++bits;
- d >>= 1;
- }
- break;
- }
+ mp_digit d, x, mask;
+ if ((d = MP_DIGIT(a, --ix)) == 0)
+ continue;
+#if !defined(MP_USE_UINT_DIGIT)
+ LZCNTLOOP(32);
+#endif
+ LZCNTLOOP(16);
+ LZCNTLOOP(8);
+ LZCNTLOOP(4);
+ LZCNTLOOP(2);
+ LZCNTLOOP(1);
+ break;
}
bits += ix * MP_DIGIT_BIT;
- if (!bits)
- bits = 1;
return bits;
}
+#undef LZCNTLOOP
+
/*------------------------------------------------------------------------*/
/* HERE THERE BE DRAGONS */
diff --git a/security/nss/lib/nss/nss.h b/security/nss/lib/nss/nss.h
index 2701a1ea1..850e9306a 100644
--- a/security/nss/lib/nss/nss.h
+++ b/security/nss/lib/nss/nss.h
@@ -22,10 +22,10 @@
* The format of the version string should be
* "<major version>.<minor version>[.<patch level>[.<build number>]][ <ECC>][ <Beta>]"
*/
-#define NSS_VERSION "3.48.2" _NSS_CUSTOMIZED
+#define NSS_VERSION "3.48.3" _NSS_CUSTOMIZED
#define NSS_VMAJOR 3
#define NSS_VMINOR 48
-#define NSS_VPATCH 2
+#define NSS_VPATCH 3
#define NSS_VBUILD 0
#define NSS_BETA PR_FALSE
diff --git a/security/nss/lib/softoken/softkver.h b/security/nss/lib/softoken/softkver.h
index a1c8f8c5c..d6c8087b5 100644
--- a/security/nss/lib/softoken/softkver.h
+++ b/security/nss/lib/softoken/softkver.h
@@ -17,10 +17,10 @@
* The format of the version string should be
* "<major version>.<minor version>[.<patch level>[.<build number>]][ <ECC>][ <Beta>]"
*/
-#define SOFTOKEN_VERSION "3.48.2" SOFTOKEN_ECC_STRING
+#define SOFTOKEN_VERSION "3.48.3" SOFTOKEN_ECC_STRING
#define SOFTOKEN_VMAJOR 3
#define SOFTOKEN_VMINOR 48
-#define SOFTOKEN_VPATCH 2
+#define SOFTOKEN_VPATCH 3
#define SOFTOKEN_VBUILD 0
#define SOFTOKEN_BETA PR_FALSE
diff --git a/security/nss/lib/util/nssutil.h b/security/nss/lib/util/nssutil.h
index f067465c8..8a4378fe6 100644
--- a/security/nss/lib/util/nssutil.h
+++ b/security/nss/lib/util/nssutil.h
@@ -19,10 +19,10 @@
* The format of the version string should be
* "<major version>.<minor version>[.<patch level>[.<build number>]][ <Beta>]"
*/
-#define NSSUTIL_VERSION "3.48.2"
+#define NSSUTIL_VERSION "3.48.3"
#define NSSUTIL_VMAJOR 3
#define NSSUTIL_VMINOR 48
-#define NSSUTIL_VPATCH 2
+#define NSSUTIL_VPATCH 3
#define NSSUTIL_VBUILD 0
#define NSSUTIL_BETA PR_FALSE
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
-
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 bb1a5eeee..c8a395c40 100644
--- a/toolkit/content/widgets/popup.xml
+++ b/toolkit/content/widgets/popup.xml
@@ -25,8 +25,21 @@
</getter>
</property>
+#ifdef MOZ_WIDGET_GTK
+ <property name="state" readonly="true">
+ <getter>
+ <![CDATA[
+ if (this.hasAttribute('_moz-nativemenupopupstate'))
+ return this.getAttribute('_moz-nativemenupopupstate');
+ else
+ return this.popupBoxObject.popupState;
+ ]]>
+ </getter>
+ </property>
+#else
<property name="state" readonly="true"
onget="return this.popupBoxObject.popupState"/>
+#endif
<property name="triggerNode" readonly="true"
onget="return this.popupBoxObject.triggerNode"/>
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/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
diff --git a/uriloader/prefetch/nsOfflineCacheUpdate.cpp b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
index 4b6cd4d0c..8a4183429 100644
--- a/uriloader/prefetch/nsOfflineCacheUpdate.cpp
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
@@ -948,6 +948,14 @@ nsOfflineManifestItem::HandleManifestLine(const nsCString::const_iterator &aBegi
mStrictFileOriginPolicy))
break;
+ // Check fallback path for disallowed encoded path separators
+ nsAutoCString path;
+ fallbackURI->GetFilePath(path);
+ if (path.Find("%2f") != kNotFound || path.Find("%2F") != kNotFound) {
+ LogToConsole("Offline cache manifest bad fallback path", this);
+ break;
+ }
+
mFallbackURIs.AppendObject(fallbackURI);
AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_FALLBACK,
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 <glib.h>
+#include <gdk/gdk.h>
+
+#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 <glib-object.h>
+
+#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<nsMenu *>(mWeakMenu.get())->HandleContentInserted(mContainer,
+ mChild,
+ mPrevSibling);
+ return NS_OK;
+ }
+
+private:
+ nsWeakMenuObject mWeakMenu;
+
+ nsCOMPtr<nsIContent> mContainer;
+ nsCOMPtr<nsIContent> mChild;
+ nsCOMPtr<nsIContent> 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<nsMenu *>(mWeakMenu.get())->HandleContentRemoved(mContainer,
+ mChild);
+ return NS_OK;
+ }
+
+private:
+ nsWeakMenuObject mWeakMenu;
+
+ nsCOMPtr<nsIContent> mContainer;
+ nsCOMPtr<nsIContent> 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<nsStyleContext> 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<nsXBLBinding> 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<nsMenu *>(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<nsMenu *>(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<nsIContent> 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<nsMenuObject> child = CreateChild(childContent);
+
+ if (!child) {
+ continue;
+ }
+
+ AppendChild(Move(child));
+ }
+}
+
+void
+nsMenu::InitializePopup() {
+ nsCOMPtr<nsIContent> oldPopupContent;
+ oldPopupContent.swap(mPopupContent);
+
+ for (uint32_t i = 0; i < ContentNode()->GetChildCount(); ++i) {
+ nsIContent* child = ContentNode()->GetChildAt(i);
+
+ int32_t dummy;
+ nsCOMPtr<nsIAtom> 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<nsMenuObject> aChild,
+ nsIContent* aPrevSibling) {
+ if (!IsInBatchedUpdate()) {
+ EnsureNoPlaceholderItem();
+ }
+
+ nsMenuContainer::InsertChildAfter(Move(aChild), aPrevSibling,
+ !IsInBatchedUpdate());
+ StructureMutated();
+}
+
+void
+nsMenu::AppendChild(UniquePtr<nsMenuObject> 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<nsMenuObject> 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::PropertyFlags>(
+ 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<nsStyleContext> sc = GetStyleContext();
+ UpdateVisibility(sc);
+ } else if (aAttribute == nsGkAtoms::image) {
+ RefPtr<nsStyleContext> 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<DbusmenuMenuitem *>(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<DbusmenuMenuitem *>(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<DbusmenuMenuitem *>(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<nsMenu *>(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 <glib.h>
+
+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<nsMenuObject> aChild,
+ nsIContent* aPrevSibling);
+ void AppendChild(mozilla::UniquePtr<nsMenuObject> 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<nsIContent> mPopupContent;
+
+ nsCOMPtr<nsITimer> 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 <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <glib.h>
+#include <glib-object.h>
+
+#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<nsMenuBar* >(mWeakMenuBar.get())->HandleContentInserted(mChild,
+ mPrevSibling);
+ return NS_OK;
+ }
+
+private:
+ nsWeakMenuObject mWeakMenuBar;
+
+ nsCOMPtr<nsIContent> mChild;
+ nsCOMPtr<nsIContent> 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<nsMenuBar* >(mWeakMenuBar.get())->HandleContentRemoved(mChild);
+ return NS_OK;
+ }
+
+private:
+ nsWeakMenuObject mWeakMenuBar;
+
+ nsCOMPtr<nsIContent> 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<GdkWindow* >(
+ 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<uint32_t>(
+ 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<nsMenuObject> 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<ModifierFlags>(0);
+ bool modifier;
+
+ aEvent->GetAltKey(&modifier);
+ if (modifier) {
+ modifiers = static_cast<ModifierFlags>(modifiers | eModifierAlt);
+ }
+
+ aEvent->GetShiftKey(&modifier);
+ if (modifier) {
+ modifiers = static_cast<ModifierFlags>(modifiers | eModifierShift);
+ }
+
+ aEvent->GetCtrlKey(&modifier);
+ if (modifier) {
+ modifiers = static_cast<ModifierFlags>(modifiers | eModifierCtrl);
+ }
+
+ aEvent->GetMetaKey(&modifier);
+ if (modifier) {
+ modifiers = static_cast<ModifierFlags>(modifiers | eModifierMeta);
+ }
+
+ return modifiers;
+}
+
+nsresult
+nsMenuBar::Keypress(nsIDOMEvent* aEvent) {
+ if (!ShouldHandleKeyEvent(aEvent)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMKeyEvent> 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<nsMenu* >(found)->OpenMenu();
+
+ aEvent->StopPropagation();
+ aEvent->PreventDefault();
+
+ return NS_OK;
+}
+
+nsresult
+nsMenuBar::KeyDown(nsIDOMEvent* aEvent) {
+ if (!ShouldHandleKeyEvent(aEvent)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMKeyEvent> 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<nsIDOMKeyEvent> 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<nsMenuObject> 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>
+nsMenuBar::Create(nsIWidget* aParent, nsIContent* aMenuBarNode) {
+ UniquePtr<nsMenuBar> 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<uint32_t>(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 <gtk/gtk.h>
+
+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<nsMenuBar> 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<nsIDOMEventTarget> mDocument;
+ RefPtr<DocEventListener> 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<nsMenuObject> (*nsMenuObjectConstructor)(nsMenuContainer*,
+ nsIContent*);
+
+template<class T>
+static UniquePtr<nsMenuObject> CreateMenuObject(nsMenuContainer* aContainer,
+ nsIContent* aContent) {
+ return UniquePtr<T>(new T(aContainer, aContent));
+}
+
+static nsMenuObjectConstructor
+GetMenuObjectConstructor(nsIContent* aContent) {
+ if (aContent->IsXULElement(nsGkAtoms::menuitem)) {
+ return CreateMenuObject<nsMenuItem>;
+ } else if (aContent->IsXULElement(nsGkAtoms::menu)) {
+ return CreateMenuObject<nsMenu>;
+ } else if (aContent->IsXULElement(nsGkAtoms::menuseparator)) {
+ return CreateMenuObject<nsMenuSeparator>;
+ }
+
+ 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<nsMenuObject>
+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<nsMenuObject> 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<nsMenuObject> 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<nsMenuObject> 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<mozilla::UniquePtr<nsMenuObject> > 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<nsMenuObject> 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<nsMenuObject> aChild,
+ nsIContent* aPrevSibling,
+ bool aUpdateNative = true);
+
+ void AppendChild(mozilla::UniquePtr<nsMenuObject> 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 <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#if (MOZ_WIDGET_GTK == 3)
+#include <gdk/gdkkeysyms-compat.h>
+#endif
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#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 <gdk/gdkkeysyms.h>
+//
+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<nsMenuItem* >(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<nsMenuItem* >(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<nsIDOMEventTarget> target = do_QueryInterface(ContentNode());
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc);
+ if (domDoc && target) {
+ nsCOMPtr<nsIDOMEvent> event;
+ domDoc->CreateEvent(NS_LITERAL_STRING("xulcommandevent"),
+ getter_AddRefs(event));
+ nsCOMPtr<nsIDOMXULCommandEvent> 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<nsMenu* >(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<nsIContent> 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<GdkModifierType>(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<nsMenuBar* >(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<nsIContent> 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::PropertyFlags>(
+ 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<nsStyleContext> sc = GetStyleContext();
+ UpdateVisibility(sc);
+ } else if (aAttribute == nsGkAtoms::image) {
+ RefPtr<nsStyleContext> 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 <glib.h>
+
+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<nsIContent> 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 <gdk/gdk.h>
+#include <glib-object.h>
+#include <pango/pango.h>
+
+#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<imgRequestProxy> mImageRequest;
+ nsCOMPtr<nsIURI> 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<imgIContainer> 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<imgIContainer> 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<nsIURI> 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<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
+ RefPtr<imgLoader> 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<nsStyleContext>
+nsMenuObject::GetStyleContext() {
+ nsIPresShell* shell = mContent->OwnerDoc()->GetShell();
+ if (!shell) {
+ return nullptr;
+ }
+
+ RefPtr<nsStyleContext> sc =
+ nsComputedDOMStyle::GetStyleContextForElementNoFlush(
+ mContent->AsElement(), nullptr, shell);
+
+ return sc.forget();
+}
+
+void
+nsMenuObject::InitializeNativeData() {
+}
+
+nsMenuObject::PropertyFlags
+nsMenuObject::SupportedProperties() const {
+ return static_cast<nsMenuObject::PropertyFlags>(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<PropertyFlags>(1);
+
+ for (uint32_t i = 0; gPropertyStrings[i]; ++i) {
+ if (!(mask & supported)) {
+ dbusmenu_menuitem_property_remove(mNativeData, gPropertyStrings[i]);
+ }
+ mask = static_cast<PropertyFlags>(mask << 1);
+ }
+
+ InitializeNativeData();
+ if (mParent && mParent->IsBeingDisplayed()) {
+ ContainerIsOpening();
+ }
+
+ mListener->RegisterForContentChanges(mContent, this);
+
+ return NS_OK;
+}
+
+void
+nsMenuObject::ContainerIsOpening() {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ UpdateContentAttributes();
+
+ RefPtr<nsStyleContext> 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<nsStyleContext> 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<nsIContent> 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<nsNativeMenuDocListener> mListener;
+ nsMenuContainer* mParent; // [weak]
+ DbusmenuMenuitem* mNativeData; // [strong]
+ RefPtr<nsMenuObjectIconLoader> 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::PropertyFlags>(
+ 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<nsStyleContext> 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<nsNativeMenuChangeObserver* > 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<nsIContent> mTarget;
+ nsCOMPtr<nsIContent> mChild;
+ nsCOMPtr<nsIContent> mPrevSibling;
+ nsCOMPtr<nsIAtom> 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<nsIContent> mRootNode;
+ nsIDocument* mDocument;
+ nsIContent* mLastSource;
+ nsNativeMenuChangeObserver* mLastTarget;
+ nsTArray<nsAutoPtr<MutationRecord> > mPendingMutations;
+ nsDataHashtable<nsPtrHashKey<nsIContent>, nsNativeMenuChangeObserver* > mContentToObserverTable;
+
+ static uint32_t sUpdateBlockersCount;
+};
+
+typedef nsTArray<RefPtr<nsNativeMenuDocListener> > 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..7b92e73e8
--- /dev/null
+++ b/widget/gtk/nsNativeMenuService.cpp
@@ -0,0 +1,484 @@
+/* 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 <glib-object.h>
+#include <pango/pango.h>
+#include <stdlib.h>
+
+#include "nsNativeMenuService.h"
+
+using namespace mozilla;
+
+nsNativeMenuService* nsNativeMenuService::sService = nullptr;
+
+extern PangoLayout* gPangoLayout;
+extern nsNativeMenuDocListenerTArray* gPendingListeners;
+
+static const nsTArray<nsMenuBar* >::index_type NoIndex = nsTArray<nsMenuBar* >::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;
+ }
+
+ 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<GDBusProxyFlags>(
+ 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<nsINativeMenuService> 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<nsMenuBar* >(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<nsMenuBar* >(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<nsMenuBar> 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<nsWindow* >(aParent)->SetMenuBar(Move(menubar));
+
+ return NS_OK;
+}
+
+/* static */ already_AddRefed<nsNativeMenuService>
+nsNativeMenuService::GetInstanceForServiceManager() {
+ RefPtr<nsNativeMenuService> 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 <gdk/gdk.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+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<nsNativeMenuService> 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<nsMenuBar* > mMenuBars;
+ nsDataHashtable<nsPtrHashKey<nsMenuBar>, 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<nsMenuBar> 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<nsMenuBar> 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<mozilla::widget::IMContextWrapper> mIMContext;
mozilla::UniquePtr<mozilla::CurrentX11TimeGetter> mCurrentTimeGetter;
+
+ mozilla::UniquePtr<nsMenuBar> 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