diff options
179 files changed, 9276 insertions, 1827 deletions
@@ -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 |