Backed out 14 changesets (bug 1682632) for causing hazard bustages in ExtensionEventManager.cpp

Backed out changeset c5acc19db606 (bug 1682632)
Backed out changeset 61380029a38b (bug 1682632)
Backed out changeset d3a153070b38 (bug 1682632)
Backed out changeset 8b8bd2385503 (bug 1682632)
Backed out changeset 7fa45afd83a0 (bug 1682632)
Backed out changeset 57652a2152ac (bug 1682632)
Backed out changeset 9195b13525d0 (bug 1682632)
Backed out changeset a647c0cb85e4 (bug 1682632)
Backed out changeset 55553e0dc6ca (bug 1682632)
Backed out changeset c85363089c29 (bug 1682632)
Backed out changeset 6c1f4efb4975 (bug 1682632)
Backed out changeset 9452456d249f (bug 1682632)
Backed out changeset 7b8016e5f3fb (bug 1682632)
Backed out changeset ba742f7e256f (bug 1682632)
This commit is contained in:
Alexandru Michis 2021-06-10 17:28:23 +03:00
Родитель 4a0c903748
Коммит be06efd457
52 изменённых файлов: 8 добавлений и 5347 удалений

Просмотреть файл

@ -91,10 +91,6 @@ UniquePtr<SerializedStackHolder> GetCurrentStackForNetMonitor(JSContext* aCx) {
MOZ_ASSERT_IF(!NS_IsMainThread(),
GetCurrentThreadWorkerPrivate()->IsWatchedByDevTools());
return GetCurrentStack(aCx);
}
UniquePtr<SerializedStackHolder> GetCurrentStack(JSContext* aCx) {
UniquePtr<SerializedStackHolder> stack = MakeUnique<SerializedStackHolder>();
stack->SerializeCurrentStack(aCx);
return stack;

Просмотреть файл

@ -55,9 +55,6 @@ class SerializedStackHolder {
// be checked before creating the stack.
UniquePtr<SerializedStackHolder> GetCurrentStackForNetMonitor(JSContext* aCx);
// Construct a stack for the current thread.
UniquePtr<SerializedStackHolder> GetCurrentStack(JSContext* aCx);
// If aStackHolder is non-null, this notifies the net monitor that aStackHolder
// is the stack from which aChannel originates. This must be called on the main
// thread. This call is synchronous, and aChannel and aStackHolder will not be

Просмотреть файл

@ -1570,28 +1570,6 @@ DOMInterfaces = {
'nativeType': 'nsXULElement',
},
# WebExtension API
'ExtensionBrowser': {
'headerFile': 'mozilla/extensions/ExtensionBrowser.h',
'nativeType': 'mozilla::extensions::ExtensionBrowser',
},
'ExtensionMockAPI': {
'headerFile': 'mozilla/extensions/ExtensionMockAPI.h',
'nativeType': 'mozilla::extensions::ExtensionMockAPI',
},
'ExtensionEventManager': {
'headerFile': 'mozilla/extensions/ExtensionEventManager.h',
'nativeType': 'mozilla::extensions::ExtensionEventManager',
},
'ExtensionPort': {
'headerFile': 'mozilla/extensions/ExtensionPort.h',
'nativeType': 'mozilla::extensions::ExtensionPort',
},
####################################
# Test Interfaces of various sorts #
####################################

Просмотреть файл

@ -9485,22 +9485,12 @@ class CGPerSignatureCall(CGThing):
# Callee expects a quoted string for the context if
# there's a context.
context = '"%s"' % context
if idlNode.isMethod() and idlNode.getExtendedAttribute("WebExtensionStub"):
[
nativeMethodName,
argsPre,
args,
] = self.processWebExtensionStubAttribute(idlNode, cgThings)
else:
args = self.getArguments()
cgThings.append(
CGCallGenerator(
self.needsErrorResult(),
needsCallerType(idlNode),
isChromeOnly(idlNode),
args,
self.getArguments(),
argsPre,
returnType,
self.extendedAttributes,
@ -9557,95 +9547,6 @@ class CGPerSignatureCall(CGThing):
def getArguments(self):
return [(a, "arg" + str(i)) for i, a in enumerate(self.arguments)]
def processWebExtensionStubAttribute(self, idlNode, cgThings):
nativeMethodName = "CallWebExtMethod"
stubNameSuffix = idlNode.getExtendedAttribute("WebExtensionStub")
if isinstance(stubNameSuffix, list):
nativeMethodName += stubNameSuffix[0]
argsLength = len(self.getArguments())
singleVariadicArg = argsLength == 1 and self.getArguments()[0][0].variadic
# If the method signature does only include a single variadic arguments,
# then `arg0` is already a Sequence of JS values and we can pass that
# to the WebExtensions Stub method as is.
if singleVariadicArg:
argsPre = [
"cx",
'u"%s"_ns' % idlNode.identifier.name,
"Constify(%s)" % "arg0",
]
args = []
return [nativeMethodName, argsPre, args]
argsPre = [
"cx",
'u"%s"_ns' % idlNode.identifier.name,
"Constify(%s)" % "args_sequence",
]
args = []
# Determine the maximum number of elements of the js values sequence argument,
# skipping the last optional callback argument if any:
#
# if this WebExtensions API method does expect a last optional callback argument,
# then it is the callback parameter supported for chrome-compatibility
# reasons, and we want it as a separate argument passed to the WebExtension
# stub method and skip it from the js values sequence including all other
# arguments.
maxArgsSequenceLen = argsLength
if argsLength > 0:
lastArg = self.getArguments()[argsLength - 1]
isCallback = lastArg[0].type.tag() == IDLType.Tags.callback
if isCallback and lastArg[0].optional:
argsPre.append(
"MOZ_KnownLive(NonNullHelper(Constify(%s)))" % lastArg[1]
)
maxArgsSequenceLen = argsLength - 1
cgThings.append(
CGGeneric(
dedent(
fill(
"""
// Collecting all args js values into the single sequence argument
// passed to the webextensions stub method.
//
// NOTE: The stub method will receive the original non-normalized js values,
// but those arguments will still be normalized on the main thread by the
// WebExtensions API request handler using the same JSONSchema defnition
// used by the non-webIDL webextensions API bindings.
AutoSequence<JS::Value> args_sequence;
SequenceRooter<JS::Value> args_sequence_holder(cx, &args_sequence);
// maximum number of arguments expected by the WebExtensions API method
// excluding the last optional chrome-compatible callback argument (which
// is being passed to the stub method as a separate additional argument).
uint32_t maxArgsSequenceLen = ${maxArgsSequenceLen};
uint32_t sequenceArgsLen = args.length() <= maxArgsSequenceLen ?
args.length() : maxArgsSequenceLen;
if (sequenceArgsLen > 0) {
if (!args_sequence.SetCapacity(sequenceArgsLen, mozilla::fallible)) {
JS_ReportOutOfMemory(cx);
return false;
}
for (uint32_t argIdx = 0; argIdx < sequenceArgsLen; ++argIdx) {
// OK to do infallible append here, since we ensured capacity already.
JS::Value& slot = *args_sequence.AppendElement();
slot = args[argIdx];
}
}
""",
maxArgsSequenceLen=maxArgsSequenceLen,
)
)
)
)
return [nativeMethodName, argsPre, args]
def needsErrorResult(self):
return "needsErrorResult" in self.extendedAttributes

Просмотреть файл

@ -6394,7 +6394,6 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
or identifier == "StaticClassOverride"
or identifier == "NonEnumerable"
or identifier == "Unexposed"
or identifier == "WebExtensionStub"
):
# Known attributes that we don't need to do anything with here
pass

Просмотреть файл

@ -1,32 +0,0 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This IDL file is related to interface mixin for the additional globals that should be
* available in windows and service workers allowed to access the WebExtensions API and
* the WebExtensions browser API namespace.
*
* You are granted a license to use, reproduce and create derivative works of
* this document.
*/
// WebExtensions API interface mixin (used to include ExtensionBrowser interface
// in the ServiceWorkerGlobalScope and Window).
[Exposed=(ServiceWorker)]
interface mixin ExtensionGlobalsMixin {
[Replaceable, SameObject, BinaryName="AcquireExtensionBrowser",
Func="extensions::ExtensionAPIAllowed"]
readonly attribute ExtensionBrowser browser;
};
[Exposed=(ServiceWorker), LegacyNoInterfaceObject]
interface ExtensionBrowser {
// A mock API only exposed in tests to unit test the internals
// meant to be reused by the real WebExtensions API bindings
// in xpcshell tests.
[Replaceable, SameObject, BinaryName="GetExtensionMockAPI",
Func="mozilla::extensions::ExtensionMockAPI::IsAllowed",
Pref="extensions.webidl-api.expose_mock_interface"]
readonly attribute ExtensionMockAPI mockExtensionAPI;
};

Просмотреть файл

@ -1,25 +0,0 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This IDL file is related to the WebExtensions API object.
*
* You are granted a license to use, reproduce and create derivative works of
* this document.
*/
[Exposed=(ServiceWorker), LegacyNoInterfaceObject]
interface ExtensionEventManager {
[Throws]
void addListener(Function callback, optional object listenerOptions);
[Throws]
void removeListener(Function callback);
[Throws]
boolean hasListener(Function callback);
[Throws]
boolean hasListeners();
};

Просмотреть файл

@ -1,43 +0,0 @@
/* 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/.
*
* You are granted a license to use, reproduce and create derivative works of
* this document.
*/
// WebIDL definition for the "mockExtensionAPI" WebExtensions API,
// only available in tests and locked behind an about:config preference
// ("extensions.webidl-api.expose_mock_interface").
[Exposed=(ServiceWorker), LegacyNoInterfaceObject]
interface ExtensionMockAPI {
// Test API methods scenarios.
[Throws, WebExtensionStub]
any methodSyncWithReturn(any... args);
[Throws, WebExtensionStub="NoReturn"]
void methodNoReturn(any... args);
[Throws, WebExtensionStub="Async"]
any methodAsync(any arg0, optional Function cb);
[Throws, WebExtensionStub="AsyncAmbiguous"]
any methodAmbiguousArgsAsync(any... args);
[Throws, WebExtensionStub="ReturnsPort"]
ExtensionPort methodReturnsPort(DOMString testName);
// Test API properties.
[Replaceable]
readonly attribute any propertyAsErrorObject;
[Replaceable]
readonly attribute DOMString propertyAsString;
// Test API events.
[Replaceable, SameObject]
readonly attribute ExtensionEventManager onTestEvent;
};

Просмотреть файл

@ -1,39 +0,0 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This IDL file is related to the WebExtensions browser.runtime's Port.
*
* You are granted a license to use, reproduce and create derivative works of
* this document.
*/
[Exposed=(ServiceWorker), LegacyNoInterfaceObject]
interface ExtensionPort {
[Replaceable]
readonly attribute DOMString name;
[Replaceable]
readonly attribute object sender;
[Replaceable]
readonly attribute object? error;
[Throws, WebExtensionStub="NoReturn"]
void disconnect();
[Throws, WebExtensionStub="NoReturn"]
void postMessage(any message);
[Replaceable, SameObject]
readonly attribute ExtensionEventManager onDisconnect;
[Replaceable, SameObject]
readonly attribute ExtensionEventManager onMessage;
};
// Dictionary used by ExtensionAPIRequestForwarder and ExtensionCallabck to receive from the
// mozIExtensionAPIRequestHandler an internal description of a runtime.Port (and then used in
// the webidl implementation to create an ExtensionPort instance).
[GenerateInit]
dictionary ExtensionPortDescriptor {
required DOMString portId;
DOMString name = "";
};

Просмотреть файл

@ -43,7 +43,3 @@ partial interface ServiceWorkerGlobalScope {
attribute EventHandler onnotificationclick;
attribute EventHandler onnotificationclose;
};
// Mixin the WebExtensions API globals (the actual properties are only available to
// extension service workers, locked behind a Func="extensions::ExtensionAPIAllowed" annotation).
ServiceWorkerGlobalScope includes ExtensionGlobalsMixin;

Просмотреть файл

@ -379,9 +379,6 @@ with Files("WindowOrWorkerGlobalScope.webidl"):
with Files("Worker*"):
BUG_COMPONENT = ("Core", "DOM: Workers")
with Files("Extension*"):
BUG_COMPONENT = ("WebExtensions", "General")
GENERATED_WEBIDL_FILES = [
"CSS2Properties.webidl",
]
@ -1051,15 +1048,6 @@ WEBIDL_FILES += [
"StyleSheetApplicableStateChangeEvent.webidl",
]
# WebExtensions API.
WEBIDL_FILES += [
"ExtensionBrowser.webidl",
"ExtensionEventManager.webidl",
# ExtensionMockAPI is not a real WebExtensions API, and it is only enabled in tests.
"ExtensionMockAPI.webidl",
"ExtensionPort.webidl",
]
# We only expose our prefable test interfaces in debug builds, just to be on
# the safe side.
if CONFIG["MOZ_DEBUG"] and CONFIG["ENABLE_TESTS"]:

Просмотреть файл

@ -2217,7 +2217,6 @@ WorkerPrivate::WorkerPrivate(
IsNewWorkerSecureContext(mParent, mWorkerKind, mLoadInfo)),
mDebuggerRegistered(false),
mDebuggerReady(true),
mExtensionAPIAllowed(false),
mIsInAutomation(false),
mId(std::move(aId)),
mAgentClusterOpenerPolicy(aAgentClusterOpenerPolicy),
@ -2289,16 +2288,6 @@ WorkerPrivate::WorkerPrivate(
// content processes.
mIsPrivilegedAddonGlobal = true;
}
if (StaticPrefs::
extensions_backgroundServiceWorker_enabled_AtStartup() &&
mWorkerKind == WorkerKindService &&
policy->IsManifestBackgroundWorker(mScriptURL)) {
// Only allows ExtensionAPI for extension service workers
// that are declared in the extension manifest json as
// the background service worker.
mExtensionAPIAllowed = true;
}
}
}

Просмотреть файл

@ -35,7 +35,6 @@
#include "mozilla/dom/WorkerStatus.h"
#include "mozilla/dom/workerinternals/JSSettings.h"
#include "mozilla/dom/workerinternals/Queue.h"
#include "mozilla/StaticPrefs_extensions.h"
#include "nsContentUtils.h"
#include "nsIChannel.h"
#include "nsIContentSecurityPolicy.h"
@ -169,14 +168,6 @@ class WorkerPrivate final : public RelativeTimeline {
return mDebuggerRegistered;
}
bool ExtensionAPIAllowed() {
// This method should never be actually called if the extension background
// service worker is disabled by pref.
MOZ_ASSERT(
StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup());
return mExtensionAPIAllowed;
}
void SetIsDebuggerRegistered(bool aDebuggerRegistered) {
AssertIsOnMainThread();
@ -1341,14 +1332,6 @@ class WorkerPrivate final : public RelativeTimeline {
bool mDebuggerReady;
nsTArray<RefPtr<WorkerRunnable>> mDelayedDebuggeeRunnables;
// Whether this worker should have access to the WebExtension API bindings
// (currently only the Extension Background ServiceWorker declared in the
// extension manifest is allowed to access any WebExtension API bindings).
// This default to false, and it is eventually set to true by
// RemoteWorkerChild::ExecWorkerOnMainThread if the needed conditions
// are met.
bool mExtensionAPIAllowed;
// mIsInAutomation is true when we're running in test automation.
// We expose some extra testing functions in that case.
bool mIsInAutomation;

Просмотреть файл

@ -81,7 +81,6 @@
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/dom/cache/CacheStorage.h"
#include "mozilla/dom/cache/Types.h"
#include "mozilla/extensions/ExtensionBrowser.h"
#include "mozilla/fallible.h"
#include "mozilla/gfx/Rect.h"
#include "nsAtom.h"
@ -796,7 +795,7 @@ void SharedWorkerGlobalScope::Close() {
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope,
mClients, mExtensionBrowser, mRegistration)
mClients, mRegistration)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerGlobalScope)
NS_INTERFACE_MAP_END_INHERITING(WorkerGlobalScope)
@ -938,15 +937,6 @@ already_AddRefed<Promise> ServiceWorkerGlobalScope::SkipWaiting(
return promise.forget();
}
SafeRefPtr<extensions::ExtensionBrowser>
ServiceWorkerGlobalScope::AcquireExtensionBrowser() {
if (!mExtensionBrowser) {
mExtensionBrowser = MakeSafeRefPtr<extensions::ExtensionBrowser>(this);
}
return mExtensionBrowser.clonePtr();
}
bool WorkerDebuggerGlobalScope::WrapGlobalObject(
JSContext* aCx, JS::MutableHandle<JSObject*> aReflector) {
mWorkerPrivate->AssertIsOnWorkerThread();

Просмотреть файл

@ -16,7 +16,6 @@
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/dom/ImageBitmapSource.h"
#include "mozilla/dom/SafeRefPtr.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIGlobalObject.h"
@ -33,12 +32,6 @@ class nsISerialEventTarget;
namespace mozilla {
class ErrorResult;
namespace extensions {
class ExtensionBrowser;
} // namespace extensions
namespace dom {
class AnyCallback;
@ -374,8 +367,6 @@ class ServiceWorkerGlobalScope final : public WorkerGlobalScope {
already_AddRefed<Promise> SkipWaiting(ErrorResult& aRv);
SafeRefPtr<extensions::ExtensionBrowser> AcquireExtensionBrowser();
IMPL_EVENT_HANDLER(install)
IMPL_EVENT_HANDLER(activate)
@ -402,7 +393,6 @@ class ServiceWorkerGlobalScope final : public WorkerGlobalScope {
RefPtr<Clients> mClients;
const nsString mScope;
RefPtr<ServiceWorkerRegistration> mRegistration;
SafeRefPtr<extensions::ExtensionBrowser> mExtensionBrowser;
};
class WorkerDebuggerGlobalScope final : public WorkerGlobalScopeBase {

Просмотреть файл

@ -3955,16 +3955,6 @@
value: false
mirror: always
# Whether to expose the MockExtensionAPI test interface in tests.
# The interface MockExtensionAPI doesn't represent a real extension API,
# it is only available in test and does include a series of cases useful
# to test the API request handling without tying the unit test to a
# specific WebExtensions API.
- name: extensions.webidl-api.expose_mock_interface
type: RelaxedAtomicBool
value: false
mirror: always
#---------------------------------------------------------------------------
# Prefs starting with "findbar."
#---------------------------------------------------------------------------

Просмотреть файл

@ -54,16 +54,9 @@ using dom::Promise;
static const char kDocElementInserted[] = "initial-document-element-inserted";
/*****************************************************************************
* ExtensionPolicyService
*****************************************************************************/
/* static */
mozIExtensionProcessScript& ExtensionPolicyService::ProcessScript() {
static mozIExtensionProcessScript& ProcessScript() {
static nsCOMPtr<mozIExtensionProcessScript> sProcessScript;
MOZ_ASSERT(NS_IsMainThread());
if (MOZ_UNLIKELY(!sProcessScript)) {
sProcessScript =
do_ImportModule("resource://gre/modules/ExtensionProcessScript.jsm",
@ -74,6 +67,10 @@ mozIExtensionProcessScript& ExtensionPolicyService::ProcessScript() {
return *sProcessScript;
}
/*****************************************************************************
* ExtensionPolicyService
*****************************************************************************/
/* static */ ExtensionPolicyService& ExtensionPolicyService::GetSingleton() {
static RefPtr<ExtensionPolicyService> sExtensionPolicyService;

Просмотреть файл

@ -8,7 +8,6 @@
#include "mozilla/MemoryReporting.h"
#include "mozilla/extensions/WebExtensionPolicy.h"
#include "mozIExtensionProcessScript.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsHashKeys.h"
@ -52,8 +51,6 @@ class ExtensionPolicyService final : public nsIAddonPolicyService,
NS_DECL_NSIOBSERVER
NS_DECL_NSIMEMORYREPORTER
static mozIExtensionProcessScript& ProcessScript();
static ExtensionPolicyService& GetSingleton();
static already_AddRefed<ExtensionPolicyService> GetInstance() {

Просмотреть файл

@ -10,7 +10,7 @@
* after startup, in *every* browser process live outside of this file.
*/
var EXPORTED_SYMBOLS = ["ExtensionProcessScript", "ExtensionAPIRequestHandler"];
var EXPORTED_SYMBOLS = ["ExtensionProcessScript"];
const { MessageChannel } = ChromeUtils.import(
"resource://gre/modules/MessageChannel.jsm"
@ -417,14 +417,4 @@ var ExtensionProcessScript = {
},
};
var ExtensionAPIRequestHandler = {
handleAPIRequest(policy, request) {
// TODO: to be actually implemented in the "part3" patches that follows,
// this patch does only contain a placeholder method, which is
// replaced with a mock in the set of unit tests defined in this
// patch.
throw new Error("Not implemented");
},
};
ExtensionManager.init();

Просмотреть файл

@ -65,7 +65,6 @@ TESTING_JS_MODULES += [
DIRS += [
"schemas",
"storage",
"webidl-api",
"webrequest",
]
@ -75,7 +74,6 @@ IPDL_SOURCES += [
XPIDL_SOURCES += [
"extIWebNavigation.idl",
"mozIExtensionAPIRequestHandling.idl",
"mozIExtensionProcessScript.idl",
]
@ -130,11 +128,6 @@ XPCSHELL_TESTS_MANIFESTS += [
"test/xpcshell/xpcshell.ini",
]
# Only include tests that requires the WebExtensions WebIDL API bindings
# in builds where they are enabled (currently only on Nightly builds).
if CONFIG["MOZ_WEBEXT_WEBIDL_ENABLED"]:
XPCSHELL_TESTS_MANIFESTS += ["test/xpcshell/webidl-api/xpcshell.ini"]
SPHINX_TREES["webextensions"] = "docs"
with Files("docs/**"):

Просмотреть файл

@ -1,151 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIPrincipal;
[scriptable, builtinclass, uuid(e6862533-8844-4207-a6ab-04748a29d859)]
interface mozIExtensionServiceWorkerInfo : nsISupports
{
readonly attribute nsIPrincipal principal;
readonly attribute AString scriptURL;
readonly attribute AString clientInfoId;
};
[scriptable, uuid(876d45db-5c1b-4c9b-9148-1c86b33d120b)]
interface mozIExtensionListenerCallOptions : nsISupports
{
cenum APIObjectType: 8 {
// Default: no api object is prepended to the event listener call arguments.
NONE,
// A runtime.Port instance is prepended to the event listener call arguments.
RUNTIME_PORT,
};
readonly attribute mozIExtensionListenerCallOptions_APIObjectType apiObjectType;
// An optional javascript object that should provide the attributes expected
// by related apiObjectType webidl dictionary type (e.g. ExtensionPortDescriptor
// if apiObjectType is RUNTIME_PORT).
readonly attribute jsval apiObjectDescriptor;
cenum CallbackType: 8 {
// Default: no callback argument is passed to the call to the event listener.
CALLBACK_NONE,
// The event listener will be called with a function as the last call parameter
// that behaves like the runtime.onMessage's sendResponse parameter:
//
// - if the event listener already returned false, sendResponse calls are ignored
// (if it has not been called already)
// - if the event listener already returned true, the first sendResponse call
// resolves the promise returned by the mozIExtensionCallback method call
// - if the event listener already returned a Promise, sendResponse calls
// are ignored
CALLBACK_SEND_RESPONSE,
};
attribute mozIExtensionListenerCallOptions_CallbackType callbackType;
};
[scriptable, builtinclass, uuid(e68e3c19-1b35-4112-8faa-5c5b84086a5b)]
interface mozIExtensionEventListener : nsISupports
{
[implicit_jscontext, can_run_script]
Promise callListener(
in Array<jsval> args,
[optional] in mozIExtensionListenerCallOptions listenerCallOptions);
};
[scriptable, builtinclass, uuid(0fee1c8f-e363-46a6-bd0c-d3c3338e2534)]
interface mozIExtensionAPIRequest : nsISupports
{
AUTF8String toString();
// Determine what the caller and receiver should expect, e.g. what should
// be provided to the API request handler and what to expect it to return.
cenum RequestType: 8 {
CALL_FUNCTION,
CALL_FUNCTION_NO_RETURN,
CALL_FUNCTION_ASYNC,
ADD_LISTENER,
REMOVE_LISTENER,
GET_PROPERTY,
};
// The type of the request.
readonly attribute AUTF8String requestType;
// WebExtension API namespace (e.g. tabs, runtime, webRequest, ...)
readonly attribute AUTF8String apiNamespace;
// method or event name
readonly attribute AUTF8String apiName;
// API object type (e.g. Port, Panel, ...) and its unique id, this
// properties are used by API requests originated by an API object
// instance (like a runtime Port returned by browser.runtime.connect).
readonly attribute AUTF8String apiObjectType;
readonly attribute AUTF8String apiObjectId;
// An array of API call arguments.
[implicit_jscontext] readonly attribute jsval args;
// The caller SavedFrame (only set for calls originated off of the main thread
// from a service worker).
[implicit_jscontext] readonly attribute jsval callerSavedFrame;
// Set for requests coming from an extension service worker.
readonly attribute mozIExtensionServiceWorkerInfo serviceWorkerInfo;
// Set for `addListener`/`removeListener` API requests.
readonly attribute mozIExtensionEventListener eventListener;
};
[scriptable, uuid(59fd4097-d88e-40fd-8664-fedd8ab67ab6)]
interface mozIExtensionAPIRequestResult : nsISupports
{
cenum ResultType: 8 {
// The result is a value to be returned as a result for the API request.
// The value attribute can be set to:
// - a structured clonable result value (which may be the result of the
// API call itself, or be used by the API method webidl implementation
// to initialize a webidl object to return to the caller, e.g.
// ExtensionPort returned by a call to browser.runtime.connect())
// - an error object (which also include internally converted to and from
// ClonedErrorHolder to make it structured clonable).
// - a Promise (which can be resolved to a structured clonable value or
// an error object).
RETURN_VALUE,
// The result is an error object that should be thrown as an extension error
// to the caller on the thread that originated the request.
EXTENSION_ERROR,
};
readonly attribute mozIExtensionAPIRequestResult_ResultType type;
readonly attribute jsval value;
};
[scriptable, uuid(0c61bd33-0557-43a2-9497-96c449f39e33)]
interface mozIExtensionAPIRequestHandler : nsISupports
{
/**
* Handle an API request originated from the WebExtensions webidl API
* bindings.
*
* @param extension An instance of the WebExtensionPolicy webidl interface.
* @param apiRequest An instance of the mozIExtensionAPIRequest xpcom interface.
*
* @return mozIExtensionAPIRequestResult
* JS value returned by the API request handler, may contain a value
* (the result of the API call or a WebIDL dictionary that is used to
* initialize WebIDL-based API object, e.g. ExtensionPort) or
* an Error to be throw on the thread that originated the request.
*/
void handleAPIRequest(in nsISupports extension,
in mozIExtensionAPIRequest apiRequest,
[optional, retval] out mozIExtensionAPIRequestResult apiRequestResult);
};

Просмотреть файл

@ -1,9 +0,0 @@
"use strict";
module.exports = {
env: {
// The tests in this folder are testing based on WebExtensions, so lets
// just define the webextensions environment here.
webextensions: true,
},
};

Просмотреть файл

@ -1,309 +0,0 @@
/* import-globals-from ../head.js */
/* exported getBackgroundServiceWorkerRegistration, waitForTerminatedWorkers,
* runExtensionAPITest */
"use strict";
XPCOMUtils.defineLazyModuleGetters(this, {
TestUtils: "resource://testing-common/TestUtils.jsm",
ExtensionTestCommon: "resource://testing-common/ExtensionTestCommon.jsm",
});
add_task(function checkExtensionsWebIDLEnabled() {
equal(
AppConstants.MOZ_WEBEXT_WEBIDL_ENABLED,
true,
"WebExtensions WebIDL bindings build time flag should be enabled"
);
});
function getBackgroundServiceWorkerRegistration(extension) {
const swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
Ci.nsIServiceWorkerManager
);
const swRegs = swm.getAllRegistrations();
const scope = `moz-extension://${extension.uuid}/`;
for (let i = 0; i < swRegs.length; i++) {
let regInfo = swRegs.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
if (regInfo.scope === scope) {
return regInfo;
}
}
}
function waitForTerminatedWorkers(swRegInfo) {
info(`Wait all ${swRegInfo.scope} workers to be terminated`);
return TestUtils.waitForCondition(() => {
const {
evaluatingWorker,
installingWorker,
waitingWorker,
activeWorker,
} = swRegInfo;
return !(
evaluatingWorker ||
installingWorker ||
waitingWorker ||
activeWorker
);
}, `wait workers for scope ${swRegInfo.scope} to be terminated`);
}
function unmockHandleAPIRequest(extPage) {
return extPage.spawn([], () => {
const { ExtensionAPIRequestHandler } = ChromeUtils.import(
"resource://gre/modules/ExtensionProcessScript.jsm"
);
// Unmock ExtensionAPIRequestHandler.
if (ExtensionAPIRequestHandler._handleAPIRequest_orig) {
ExtensionAPIRequestHandler.handleAPIRequest =
ExtensionAPIRequestHandler._handleAPIRequest_orig;
delete ExtensionAPIRequestHandler._handleAPIRequest_orig;
}
});
}
function mockHandleAPIRequest(extPage, mockHandleAPIRequest) {
mockHandleAPIRequest =
mockHandleAPIRequest ||
((policy, request) => {
const ExtError = request.window?.Error || Error;
return {
type: Ci.mozIExtensionAPIRequestResult.EXTENSION_ERROR,
value: new ExtError(
"mockHandleAPIRequest not defined by this test case"
),
};
});
return extPage.spawn(
[ExtensionTestCommon.serializeFunction(mockHandleAPIRequest)],
mockFnText => {
const { ExtensionAPIRequestHandler } = ChromeUtils.import(
"resource://gre/modules/ExtensionProcessScript.jsm"
);
mockFnText = `(() => {
return (${mockFnText});
})();`;
// eslint-disable-next-line no-eval
const mockFn = eval(mockFnText);
// Mock ExtensionAPIRequestHandler.
if (!ExtensionAPIRequestHandler._handleAPIRequest_orig) {
ExtensionAPIRequestHandler._handleAPIRequest_orig =
ExtensionAPIRequestHandler.handleAPIRequest;
}
ExtensionAPIRequestHandler.handleAPIRequest = function(policy, request) {
if (request.apiNamespace === "test") {
return this._handleAPIRequest_orig(policy, request);
}
return mockFn.call(this, policy, request);
};
}
);
}
/**
* An helper function used to run unit test that are meant to test the
* Extension API webidl bindings helpers shared by all the webextensions
* API namespaces.
*
* @param {string} testDescription
* Brief description of the test.
* @param {Object} [options]
* @param {Function} backgroundScript
* Test function running in the extension global. This function
* does receive a parameter of type object with the following
* properties:
* - testLog(message): log a message on the terminal
* - testAsserts:
* - isErrorInstance(err): throw if err is not an Error instance
* - isInstanceOf(value, globalContructorName): throws if value
* is not an instance of global[globalConstructorName]
* - equal(val, exp, msg): throw an error including msg if
* val is not strictly equal to exp.
* @param {Function} assertResults
* Function to be provided to assert the result returned by
* `backgroundScript`, or assert the error if it did throw.
* This function does receive a parameter of type object with
* the following properties:
* - testResult: the result returned (and resolved if the return
* value was a promise) from the call to `backgroundScript`
* - testError: the error raised (or rejected if the return value
* value was a promise) from the call to `backgroundScript`
* @param {Function} mockAPIRequestHandler
* Function to be used to mock mozIExtensionAPIRequestHandler.handleAPIRequest
* for the purpose of the test.
* This function received the same parameter that are listed in the idl
* definition (mozIExtensionAPIRequestHandling.webidl).
* @param {string} [options.extensionId]
* Optional extension id for the test extension.
*/
async function runExtensionAPITest(
testDescription,
{
backgroundScript,
assertResults,
mockAPIRequestHandler,
extensionId = "test-ext-api-request-forward@mochitest",
}
) {
// Wraps the `backgroundScript` function to be execute in the target
// extension global (currently only in a background service worker,
// in follow-ups the same function should also be execute in
// other supported extension globals, e.g. an extension page and
// a content script).
//
// The test wrapper does also provide to `backgroundScript` some
// helpers to be used as part of the test, these tests are meant to
// only cover internals shared by all webidl API bindings through a
// mock API namespace only available in tests (and so none of the tests
// written with this helpers should be using the browser.test API namespace).
function backgroundScriptWrapper(testParams, testFn) {
const testLog = msg => {
// console messages emitted by workers are not visible in the test logs if not
// explicitly collected, and so this testLog helper method does use dump for now
// (this way the logs will be visibile as part of the test logs).
dump(`"${testParams.extensionId}": ${msg}\n`);
};
const testAsserts = {
isErrorInstance(err) {
if (!(err instanceof Error)) {
throw new Error("Unexpected error: not an instance of Error");
}
return true;
},
isInstanceOf(value, globalConstructorName) {
if (!(value instanceof self[globalConstructorName])) {
throw new Error(
`Unexpected error: expected instance of ${globalConstructorName}`
);
}
return true;
},
equal(val, exp, msg) {
if (val !== exp) {
throw new Error(
`Unexpected error: expected ${exp} but got ${val}. ${msg}`
);
}
},
};
testLog(`Evaluating - test case "${testParams.testDescription}"`);
self.onmessage = async evt => {
testLog(`Running test case "${testParams.testDescription}"`);
let testError = null;
let testResult;
try {
testResult = await testFn({ testLog, testAsserts });
} catch (err) {
testError = { message: err.message, stack: err.stack };
testLog(`Unexpected test error: ${err} :: ${err.stack}\n`);
}
evt.ports[0].postMessage({ success: !testError, testError, testResult });
testLog(`Test case "${testParams.testDescription}" executed`);
};
testLog(`Wait onmessage event - test case "${testParams.testDescription}"`);
}
async function assertTestResult(result) {
if (assertResults) {
await assertResults(result);
} else {
equal(result.testError, undefined, "Expect no errors");
ok(result.success, "Test completed successfully");
}
}
async function runTestCaseInWorker(page) {
info(`*** Run test case in an extension service worker`);
const result = await page.spawn([], async () => {
const { active } = await content.navigator.serviceWorker.ready;
const { port1, port2 } = new MessageChannel();
return new Promise(resolve => {
port1.onmessage = evt => resolve(evt.data);
active.postMessage("run-test", [port2]);
});
});
info(`*** Assert test case results got from extension service worker`);
await assertTestResult(result);
}
// NOTE: prefixing this with `function ` is needed because backgroundScript
// is an object property and so it is going to be stringified as
// `backgroundScript() { ... }` (which would be detected as a syntax error
// on the worker script evaluation phase).
const scriptFnParam = ExtensionTestCommon.serializeFunction(backgroundScript);
const testOptsParam = `${JSON.stringify({ testDescription, extensionId })}`;
const testExtData = {
useAddonManager: "temporary",
manifest: {
version: "1",
background: {
service_worker: "test-sw.js",
},
applications: {
gecko: { id: extensionId },
},
},
files: {
"page.html": `<!DOCTYPE html>
<head><meta charset="utf-8"></head>
<body>
<script src="test-sw.js"></script>
</body>`,
"test-sw.js": `
(${backgroundScriptWrapper})(${testOptsParam}, ${scriptFnParam});
`,
},
};
let cleanupCalled = false;
let extension;
let page;
let swReg;
async function testCleanup() {
if (cleanupCalled) {
return;
}
cleanupCalled = true;
await unmockHandleAPIRequest(page);
await page.close();
await extension.unload();
await waitForTerminatedWorkers(swReg);
}
info(`Start test case "${testDescription}"`);
extension = ExtensionTestUtils.loadExtension(testExtData);
await extension.startup();
swReg = getBackgroundServiceWorkerRegistration(extension);
ok(swReg, "Extension background.service_worker should be registered");
page = await ExtensionTestUtils.loadContentPage(
`moz-extension://${extension.uuid}/page.html`,
{ extension }
);
registerCleanupFunction(testCleanup);
await mockHandleAPIRequest(page, mockAPIRequestHandler);
await runTestCaseInWorker(page);
await testCleanup();
info(`End test case "${testDescription}"`);
}

Просмотреть файл

@ -1,489 +0,0 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
AddonTestUtils.init(this);
AddonTestUtils.createAppInfo(
"xpcshell@tests.mozilla.org",
"XPCShell",
"1",
"42"
);
add_task(async function setup() {
await AddonTestUtils.promiseStartupManager();
// Ensure that the profile-after-change message has been notified,
// so that ServiceWokerRegistrar is going to be initialized.
Services.obs.notifyObservers(
null,
"profile-after-change",
"force-serviceworkerrestart-init"
);
});
add_task(async function test_ext_context_does_have_webidl_bindings() {
await runExtensionAPITest("should have a browser global object", {
backgroundScript() {
const { browser } = self;
return {
hasExtensionAPI: !!browser,
hasExtensionMockAPI: !!browser?.mockExtensionAPI,
};
},
assertResults({ testResult, testError }) {
Assert.deepEqual(testError, undefined);
Assert.deepEqual(
testResult,
{
hasExtensionAPI: true,
hasExtensionMockAPI: true,
},
"browser and browser.test WebIDL API bindings found"
);
},
});
});
add_task(async function test_propagated_extension_error() {
await runExtensionAPITest(
"should throw an extension error on ResultType::EXTENSION_ERROR",
{
backgroundScript({ testAsserts }) {
try {
const api = self.browser.mockExtensionAPI;
api.methodSyncWithReturn("arg0", 1, { value: "arg2" });
} catch (err) {
testAsserts.isErrorInstance(err);
throw err;
}
},
mockAPIRequestHandler(policy, request) {
return {
type: Ci.mozIExtensionAPIRequestResult.EXTENSION_ERROR,
value: new Error("Fake Extension Error"),
};
},
assertResults({ testError }) {
Assert.deepEqual(testError?.message, "Fake Extension Error");
},
}
);
});
add_task(async function test_system_errors_donot_leak() {
function assertResults({ testError }) {
ok(
testError?.message?.match(/An unexpected error occurred/),
`Got the general unexpected error as expected: ${testError?.message}`
);
}
function mockAPIRequestHandler(policy, request) {
throw new Error("Fake handleAPIRequest exception");
}
const msg =
"should throw an unexpected error occurred if handleAPIRequest throws";
await runExtensionAPITest(`sync method ${msg}`, {
backgroundScript({ testAsserts }) {
try {
self.browser.mockExtensionAPI.methodSyncWithReturn("arg0");
} catch (err) {
testAsserts.isErrorInstance(err);
throw err;
}
},
mockAPIRequestHandler,
assertResults,
});
await runExtensionAPITest(`async method ${msg}`, {
backgroundScript({ testAsserts }) {
try {
self.browser.mockExtensionAPI.methodAsync("arg0");
} catch (err) {
testAsserts.isErrorInstance(err);
throw err;
}
},
mockAPIRequestHandler,
assertResults,
});
await runExtensionAPITest(`no return method ${msg}`, {
backgroundScript({ testAsserts }) {
try {
self.browser.mockExtensionAPI.methodNoReturn("arg0");
} catch (err) {
testAsserts.isErrorInstance(err);
throw err;
}
},
mockAPIRequestHandler,
assertResults,
});
});
add_task(async function test_call_sync_function_result() {
await runExtensionAPITest(
"sync API methods should support structured clonable return values",
{
backgroundScript({ testAsserts }) {
const api = self.browser.mockExtensionAPI;
const results = {
string: api.methodSyncWithReturn("string-result"),
nested_prop: api.methodSyncWithReturn({
string: "123",
number: 123,
date: new Date("2020-09-20"),
map: new Map([
["a", 1],
["b", 2],
]),
}),
};
testAsserts.isInstanceOf(results.nested_prop.date, "Date");
testAsserts.isInstanceOf(results.nested_prop.map, "Map");
return results;
},
mockAPIRequestHandler(policy, request) {
if (request.apiName === "methodSyncWithReturn") {
// Return the first argument unmodified, which will be checked in the
// resultAssertFn above.
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: request.args[0],
};
}
throw new Error("Unexpected API method");
},
assertResults({ testResult, testError }) {
Assert.deepEqual(testError, null, "Got no error as expected");
Assert.deepEqual(testResult, {
string: "string-result",
nested_prop: {
string: "123",
number: 123,
date: new Date("2020-09-20"),
map: new Map([
["a", 1],
["b", 2],
]),
},
});
},
}
);
});
add_task(async function test_call_sync_fn_missing_return() {
await runExtensionAPITest(
"should throw an unexpected error occurred on missing return value",
{
backgroundScript() {
self.browser.mockExtensionAPI.methodSyncWithReturn("arg0");
},
mockAPIRequestHandler(policy, request) {
return undefined;
},
assertResults({ testError }) {
ok(
testError?.message?.match(/An unexpected error occurred/),
`Got the general unexpected error as expected: ${testError?.message}`
);
},
}
);
});
add_task(async function test_call_async_throw_extension_error() {
await runExtensionAPITest(
"an async function can throw an error occurred for param validation errors",
{
backgroundScript({ testAsserts }) {
try {
self.browser.mockExtensionAPI.methodAsync("arg0");
} catch (err) {
testAsserts.isErrorInstance(err);
throw err;
}
},
mockAPIRequestHandler(policy, request) {
return {
type: Ci.mozIExtensionAPIRequestResult.EXTENSION_ERROR,
value: new Error("Fake Param Validation Error"),
};
},
assertResults({ testError }) {
Assert.deepEqual(testError?.message, "Fake Param Validation Error");
},
}
);
});
add_task(async function test_call_async_reject_error() {
await runExtensionAPITest(
"an async function rejected promise should propagate extension errors",
{
async backgroundScript({ testAsserts }) {
try {
await self.browser.mockExtensionAPI.methodAsync("arg0");
} catch (err) {
testAsserts.isErrorInstance(err);
throw err;
}
},
mockAPIRequestHandler(policy, request) {
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: Promise.reject(new Error("Fake API rejected error object")),
};
},
assertResults({ testError }) {
Assert.deepEqual(testError?.message, "Fake API rejected error object");
},
}
);
});
add_task(async function test_call_async_function_result() {
await runExtensionAPITest(
"async API methods should support structured clonable resolved values",
{
async backgroundScript({ testAsserts }) {
const api = self.browser.mockExtensionAPI;
const results = {
string: await api.methodAsync("string-result"),
nested_prop: await api.methodAsync({
string: "123",
number: 123,
date: new Date("2020-09-20"),
map: new Map([
["a", 1],
["b", 2],
]),
}),
};
testAsserts.isInstanceOf(results.nested_prop.date, "Date");
testAsserts.isInstanceOf(results.nested_prop.map, "Map");
return results;
},
mockAPIRequestHandler(policy, request) {
if (request.apiName === "methodAsync") {
// Return the first argument unmodified, which will be checked in the
// resultAssertFn above.
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: Promise.resolve(request.args[0]),
};
}
throw new Error("Unexpected API method");
},
assertResults({ testResult, testError }) {
Assert.deepEqual(testError, null, "Got no error as expected");
Assert.deepEqual(testResult, {
string: "string-result",
nested_prop: {
string: "123",
number: 123,
date: new Date("2020-09-20"),
map: new Map([
["a", 1],
["b", 2],
]),
},
});
},
}
);
});
add_task(async function test_call_no_return_throw_extension_error() {
await runExtensionAPITest(
"no return function call throw an error occurred for param validation errors",
{
backgroundScript({ testAsserts }) {
try {
self.browser.mockExtensionAPI.methodNoReturn("arg0");
} catch (err) {
testAsserts.isErrorInstance(err);
throw err;
}
},
mockAPIRequestHandler(policy, request) {
return {
type: Ci.mozIExtensionAPIRequestResult.EXTENSION_ERROR,
value: new Error("Fake Param Validation Error"),
};
},
assertResults({ testError }) {
Assert.deepEqual(testError?.message, "Fake Param Validation Error");
},
}
);
});
add_task(async function test_call_no_return_without_errors() {
await runExtensionAPITest(
"handleAPIHandler can return undefined on api calls to methods with no return",
{
backgroundScript() {
self.browser.mockExtensionAPI.methodNoReturn("arg0");
},
mockAPIRequestHandler(policy, request) {
return undefined;
},
assertResults({ testError }) {
Assert.deepEqual(testError, null, "Got no error as expected");
},
}
);
});
add_task(async function test_async_method_chrome_compatible_callback() {
function mockAPIRequestHandler(policy, request) {
if (request.args[0] === "fake-async-method-failure") {
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: Promise.reject("this-should-not-be-passed-to-cb-as-parameter"),
};
}
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: Promise.resolve(request.args),
};
}
await runExtensionAPITest(
"async method should support an optional chrome-compatible callback",
{
mockAPIRequestHandler,
async backgroundScript({ testAsserts }) {
const api = self.browser.mockExtensionAPI;
const success_cb_params = await new Promise(resolve => {
const res = api.methodAsync(
{ prop: "fake-async-method-success" },
(...results) => {
resolve(results);
}
);
testAsserts.equal(res, undefined, "no promise should be returned");
});
const error_cb_params = await new Promise(resolve => {
const res = api.methodAsync(
"fake-async-method-failure",
(...results) => {
resolve(results);
}
);
testAsserts.equal(res, undefined, "no promise should be returned");
});
return { success_cb_params, error_cb_params };
},
assertResults({ testError, testResult }) {
Assert.deepEqual(testError, null, "Got no error as expected");
Assert.deepEqual(
testResult,
{
success_cb_params: [[{ prop: "fake-async-method-success" }]],
error_cb_params: [],
},
"Got the expected results from the chrome compatible callbacks"
);
},
}
);
await runExtensionAPITest(
"async method with ambiguous args called with a chrome-compatible callback",
{
mockAPIRequestHandler,
async backgroundScript({ testAsserts }) {
const api = self.browser.mockExtensionAPI;
const success_cb_params = await new Promise(resolve => {
const res = api.methodAmbiguousArgsAsync(
"arg0",
{ prop: "arg1" },
3,
(...results) => {
resolve(results);
}
);
testAsserts.equal(res, undefined, "no promise should be returned");
});
const error_cb_params = await new Promise(resolve => {
const res = api.methodAmbiguousArgsAsync(
"fake-async-method-failure",
(...results) => {
resolve(results);
}
);
testAsserts.equal(res, undefined, "no promise should be returned");
});
return { success_cb_params, error_cb_params };
},
assertResults({ testError, testResult }) {
Assert.deepEqual(testError, null, "Got no error as expected");
Assert.deepEqual(
testResult,
{
success_cb_params: [["arg0", { prop: "arg1" }, 3]],
error_cb_params: [],
},
"Got the expected results from the chrome compatible callbacks"
);
},
}
);
});
add_task(async function test_get_property() {
await runExtensionAPITest(
"getProperty API request does return a value synchrously",
{
backgroundScript() {
return self.browser.mockExtensionAPI.propertyAsString;
},
mockAPIRequestHandler(policy, request) {
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: "property-value",
};
},
assertResults({ testError, testResult }) {
Assert.deepEqual(testError, null, "Got no error as expected");
Assert.deepEqual(
testResult,
"property-value",
"Got the expected result"
);
},
}
);
await runExtensionAPITest(
"getProperty API request can return an error object",
{
backgroundScript({ testAsserts }) {
const errObj = self.browser.mockExtensionAPI.propertyAsErrorObject;
testAsserts.isErrorInstance(errObj);
testAsserts.equal(errObj.message, "fake extension error");
},
mockAPIRequestHandler(policy, request) {
let savedFrame = request.calledSavedFrame;
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: ChromeUtils.createError("fake extension error", savedFrame),
};
},
assertResults({ testError, testResult }) {
Assert.deepEqual(testError, null, "Got no error as expected");
},
}
);
});

Просмотреть файл

@ -1,421 +0,0 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
AddonTestUtils.init(this);
AddonTestUtils.createAppInfo(
"xpcshell@tests.mozilla.org",
"XPCShell",
"1",
"42"
);
add_task(async function setup() {
await AddonTestUtils.promiseStartupManager();
// Ensure that the profile-after-change message has been notified,
// so that ServiceWokerRegistrar is going to be initialized.
Services.obs.notifyObservers(
null,
"profile-after-change",
"force-serviceworkerrestart-init"
);
});
add_task(async function test_api_event_manager_methods() {
await runExtensionAPITest("extension event manager methods", {
backgroundScript({ testAsserts, testLog }) {
const api = browser.mockExtensionAPI;
const listener = () => {};
function assertHasListener(expect) {
testAsserts.equal(
api.onTestEvent.hasListeners(),
expect,
`onTestEvent.hasListeners should return {expect}`
);
testAsserts.equal(
api.onTestEvent.hasListener(listener),
expect,
`onTestEvent.hasListeners should return {expect}`
);
}
assertHasListener(false);
api.onTestEvent.addListener(listener);
assertHasListener(true);
api.onTestEvent.removeListener(listener);
assertHasListener(false);
},
mockAPIRequestHandler(policy, request) {
if (!request.eventListener) {
throw new Error(
"Unexpected Error: missing ExtensionAPIRequest.eventListener"
);
}
},
assertResults({ testError, testResult }) {
Assert.deepEqual(testError, null, "Got no error as expected");
},
});
});
add_task(async function test_api_event_eventListener_call() {
await runExtensionAPITest(
"extension event eventListener wrapper does forward calls parameters",
{
backgroundScript({ testAsserts, testLog }) {
const api = browser.mockExtensionAPI;
let listener;
return new Promise((resolve, reject) => {
testLog("addListener and wait for event to be fired");
listener = (...args) => {
testLog("onTestEvent");
// Make sure the extension code can access the arguments.
try {
testAsserts.equal(args[1], "arg1");
resolve(args);
} catch (err) {
reject(err);
}
};
api.onTestEvent.addListener(listener);
});
},
mockAPIRequestHandler(policy, request) {
if (!request.eventListener) {
throw new Error(
"Unexpected Error: missing ExtensionAPIRequest.eventListener"
);
}
if (request.requestType === "addListener") {
let args = [{ arg: 0 }, "arg1"];
request.eventListener.callListener(args);
}
},
assertResults({ testError, testResult }) {
Assert.deepEqual(testError, null, "Got no error as expected");
Assert.deepEqual(
testResult,
[{ arg: 0 }, "arg1"],
"Got the expected result"
);
},
}
);
});
add_task(async function test_api_event_eventListener_call_with_result() {
await runExtensionAPITest(
"extension event eventListener wrapper forwarded call result",
{
backgroundScript({ testAsserts, testLog }) {
const api = browser.mockExtensionAPI;
let listener;
return new Promise((resolve, reject) => {
testLog("addListener and wait for event to be fired");
listener = (msg, value) => {
testLog(`onTestEvent received: ${msg}`);
switch (msg) {
case "test-result-value":
return value;
case "test-promise-resolve":
return Promise.resolve(value);
case "test-promise-reject":
return Promise.reject(new Error("test-reject"));
case "test-done":
resolve(value);
break;
default:
reject(new Error(`Unexpected onTestEvent message: ${msg}`));
}
};
api.onTestEvent.addListener(listener);
});
},
assertResults({ testError, testResult }) {
Assert.deepEqual(testError, null, "Got no error as expected");
Assert.deepEqual(
testResult?.resSync,
{ prop: "retval" },
"Got result from eventListener returning a plain return value"
);
Assert.deepEqual(
testResult?.resAsync,
{ prop: "promise" },
"Got result from eventListener returning a resolved promise"
);
Assert.deepEqual(
testResult?.resAsyncReject,
{
isInstanceOfError: true,
errorMessage: "test-reject",
},
"got result from eventListener returning a rejected promise"
);
},
mockAPIRequestHandler(policy, request) {
if (!request.eventListener) {
throw new Error(
"Unexpected Error: missing ExtensionAPIRequest.eventListener"
);
}
if (request.requestType === "addListener") {
Promise.resolve().then(async () => {
try {
dump(`calling listener, expect a plain return value\n`);
const resSync = await request.eventListener.callListener([
"test-result-value",
{ prop: "retval" },
]);
dump(
`calling listener, expect a resolved promise return value\n`
);
const resAsync = await request.eventListener.callListener([
"test-promise-resolve",
{ prop: "promise" },
]);
dump(
`calling listener, expect a rejected promise return value\n`
);
const resAsyncReject = await request.eventListener
.callListener(["test-promise-reject"])
.catch(err => err);
// call API listeners once more to complete the test
let args = {
resSync,
resAsync,
resAsyncReject: {
isInstanceOfError: resAsyncReject instanceof Error,
errorMessage: resAsyncReject?.message,
},
};
request.eventListener.callListener(["test-done", args]);
} catch (err) {
dump(`Unexpected error: ${err} :: ${err.stack}\n`);
throw err;
}
});
}
},
}
);
});
add_task(async function test_api_event_eventListener_result_rejected() {
await runExtensionAPITest(
"extension event eventListener throws (mozIExtensionCallback.call)",
{
backgroundScript({ testAsserts, testLog }) {
const api = browser.mockExtensionAPI;
let listener;
return new Promise((resolve, reject) => {
testLog("addListener and wait for event to be fired");
listener = (msg, arg1) => {
if (msg === "test-done") {
testLog(`Resolving result: ${JSON.stringify(arg1)}`);
resolve(arg1);
return;
}
throw new Error("FAKE eventListener exception");
};
api.onTestEvent.addListener(listener);
});
},
assertResults({ testError, testResult }) {
Assert.deepEqual(testError, null, "Got no error as expected");
Assert.deepEqual(
testResult,
{
isPromise: true,
rejectIsError: true,
errorMessage: "FAKE eventListener exception",
},
"Got the expected rejected promise"
);
},
mockAPIRequestHandler(policy, request) {
if (!request.eventListener) {
throw new Error(
"Unexpected Error: missing ExtensionAPIRequest.eventListener"
);
}
if (request.requestType === "addListener") {
Promise.resolve().then(async () => {
const promiseResult = request.eventListener.callListener([]);
const isPromise = promiseResult instanceof Promise;
const err = await promiseResult.catch(e => e);
const rejectIsError = err instanceof Error;
request.eventListener.callListener([
"test-done",
{ isPromise, rejectIsError, errorMessage: err?.message },
]);
});
}
},
}
);
});
add_task(async function test_api_event_eventListener_throws_on_call() {
await runExtensionAPITest(
"extension event eventListener throws (mozIExtensionCallback.call)",
{
backgroundScript({ testAsserts, testLog }) {
const api = browser.mockExtensionAPI;
let listener;
return new Promise(resolve => {
testLog("addListener and wait for event to be fired");
listener = (msg, arg1) => {
if (msg === "test-done") {
testLog(`Resolving result: ${JSON.stringify(arg1)}`);
resolve();
return;
}
throw new Error("FAKE eventListener exception");
};
api.onTestEvent.addListener(listener);
});
},
assertResults({ testError, testResult }) {
Assert.deepEqual(testError, null, "Got no error as expected");
},
mockAPIRequestHandler(policy, request) {
if (!request.eventListener) {
throw new Error(
"Unexpected Error: missing ExtensionAPIRequest.eventListener"
);
}
if (request.requestType === "addListener") {
Promise.resolve().then(async () => {
request.eventListener.callListener([]);
request.eventListener.callListener(["test-done"]);
});
}
},
}
);
});
add_task(async function test_send_response_eventListener() {
await runExtensionAPITest(
"extension event eventListener sendResponse eventListener argument",
{
backgroundScript({ testAsserts, testLog }) {
const api = browser.mockExtensionAPI;
let listener;
return new Promise(resolve => {
testLog("addListener and wait for event to be fired");
listener = (msg, sendResponse) => {
if (msg === "call-sendResponse") {
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
setTimeout(() => sendResponse("sendResponse-value"), 20);
return true;
}
resolve(msg);
};
api.onTestEvent.addListener(listener);
});
},
assertResults({ testError, testResult }) {
Assert.deepEqual(testError, null, "Got no error as expected");
Assert.equal(testResult, "sendResponse-value", "Got expected value");
},
mockAPIRequestHandler(policy, request) {
if (!request.eventListener) {
throw new Error(
"Unexpected Error: missing ExtensionAPIRequest.eventListener"
);
}
if (request.requestType === "addListener") {
Promise.resolve().then(async () => {
const res = await request.eventListener.callListener(
["call-sendResponse"],
{
callbackType:
Ci.mozIExtensionListenerCallOptions.CALLBACK_SEND_RESPONSE,
}
);
request.eventListener.callListener([res]);
});
}
},
}
);
});
add_task(async function test_send_response_multiple_eventListener() {
await runExtensionAPITest("multiple extension event eventListeners", {
backgroundScript({ testAsserts, testLog }) {
const api = browser.mockExtensionAPI;
let listenerNoReply;
let listenerSendResponseReply;
return new Promise(resolve => {
testLog("addListener and wait for event to be fired");
listenerNoReply = (msg, sendResponse) => {
return false;
};
listenerSendResponseReply = (msg, sendResponse) => {
if (msg === "call-sendResponse") {
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
setTimeout(() => sendResponse("sendResponse-value"), 20);
return true;
}
resolve(msg);
};
api.onTestEvent.addListener(listenerNoReply);
api.onTestEvent.addListener(listenerSendResponseReply);
});
},
assertResults({ testError, testResult }) {
Assert.deepEqual(testError, null, "Got no error as expected");
Assert.equal(testResult, "sendResponse-value", "Got expected value");
},
mockAPIRequestHandler(policy, request) {
if (!request.eventListener) {
throw new Error(
"Unexpected Error: missing ExtensionAPIRequest.eventListener"
);
}
if (request.requestType === "addListener") {
this._listeners = this._listeners || [];
this._listeners.push(request.eventListener);
if (this._listeners.length === 2) {
Promise.resolve().then(async () => {
const { _listeners } = this;
this._listeners = undefined;
// Reference to the listener to which we should send the
// final message to complete the test.
const replyListener = _listeners[1];
const res = await Promise.race(
_listeners.map(l =>
l.callListener(["call-sendResponse"], {
callbackType:
Ci.mozIExtensionListenerCallOptions.CALLBACK_SEND_RESPONSE,
})
)
);
replyListener.callListener([res]);
});
}
}
},
});
});

Просмотреть файл

@ -1,204 +0,0 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
AddonTestUtils.init(this);
AddonTestUtils.createAppInfo(
"xpcshell@tests.mozilla.org",
"XPCShell",
"1",
"42"
);
add_task(async function setup() {
await AddonTestUtils.promiseStartupManager();
// Ensure that the profile-after-change message has been notified,
// so that ServiceWokerRegistrar is going to be initialized.
Services.obs.notifyObservers(
null,
"profile-after-change",
"force-serviceworkerrestart-init"
);
});
add_task(async function test_method_return_runtime_port() {
await runExtensionAPITest("API method returns an ExtensionPort instance", {
backgroundScript({ testAsserts, testLog }) {
try {
browser.mockExtensionAPI.methodReturnsPort("port-create-error");
throw new Error("methodReturnsPort should have raised an exception");
} catch (err) {
testAsserts.equal(
err?.message,
"An unexpected error occurred",
"Got the expected error"
);
}
const port = browser.mockExtensionAPI.methodReturnsPort(
"port-create-success"
);
testAsserts.equal(!!port, true, "Got a port");
testAsserts.equal(
typeof port.name,
"string",
"port.name should be a string"
);
testAsserts.equal(
typeof port.sender,
"object",
"port.sender should be an object"
);
testAsserts.equal(
typeof port.disconnect,
"function",
"port.disconnect method"
);
testAsserts.equal(
typeof port.postMessage,
"function",
"port.postMessage method"
);
testAsserts.equal(
typeof port.onDisconnect?.addListener,
"function",
"port.onDisconnect.addListener method"
);
testAsserts.equal(
typeof port.onMessage?.addListener,
"function",
"port.onDisconnect.addListener method"
);
return new Promise(resolve => {
let messages = [];
port.onDisconnect.addListener(() => resolve(messages));
port.onMessage.addListener((...args) => {
messages.push(args);
});
});
},
assertResults({ testError, testResult }) {
Assert.deepEqual(testError, null, "Got no error as expected");
Assert.deepEqual(
testResult,
[
[1, 2],
[3, 4],
[5, 6],
],
"Got the expected results"
);
},
mockAPIRequestHandler(policy, request) {
if (request.apiName == "methodReturnsPort") {
if (request.args[0] == "port-create-error") {
return {
type: Ci.mozIExtensionAPIRequestResult.RESULT_VALUE,
value: "not-a-valid-port",
};
}
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: {
portId: "port-id-1",
name: "a-port-name",
},
};
} else if (request.requestType == "addListener") {
if (request.apiObjectType !== "Port") {
throw new Error(`Unexpected objectType ${request}`);
}
switch (request.apiName) {
case "onDisconnect":
this._onDisconnectCb = request.eventListener;
return;
case "onMessage":
Promise.resolve().then(async () => {
await request.eventListener.callListener([1, 2]);
await request.eventListener.callListener([3, 4]);
await request.eventListener.callListener([5, 6]);
this._onDisconnectCb.callListener([]);
});
return;
}
}
throw new Error(`Unexpected request: ${request}`);
},
});
});
add_task(async function test_port_as_event_listener_eventListener_param() {
await runExtensionAPITest(
"API event eventListener received an ExtensionPort parameter",
{
backgroundScript({ testAsserts, testLog }) {
const api = browser.mockExtensionAPI;
let listener;
return new Promise((resolve, reject) => {
testLog("addListener and wait for event to be fired");
listener = port => {
try {
testAsserts.equal(!!port, true, "Got a port parameter");
testAsserts.equal(
port.name,
"a-port-name-2",
"Got expected port.name value"
);
testAsserts.equal(
typeof port.disconnect,
"function",
"port.disconnect method"
);
testAsserts.equal(
typeof port.postMessage,
"function",
"port.disconnect method"
);
port.onMessage.addListener(msg => {
if (msg === "test-done") {
testLog("Got a port.onMessage event");
resolve();
} else {
reject(
new Error(
`port.onMessage got an unexpected message: ${msg}`
)
);
}
});
} catch (err) {
reject(err);
}
};
api.onTestEvent.addListener(listener);
});
},
assertResults({ testError }) {
Assert.deepEqual(testError, null, "Got no error as expected");
},
mockAPIRequestHandler(policy, request) {
if (
request.requestType == "addListener" &&
request.apiName == "onTestEvent"
) {
request.eventListener.callListener([], {
apiObjectType: Ci.mozIExtensionListenerCallOptions.RUNTIME_PORT,
apiObjectDescriptor: { portId: "port-id-2", name: "a-port-name-2" },
});
return;
} else if (
request.requestType == "addListener" &&
request.apiObjectType == "Port" &&
request.apiObjectId == "port-id-2"
) {
request.eventListener.callListener(["test-done"]);
return;
}
throw new Error(`Unexpected request: ${request}`);
},
}
);
});

Просмотреть файл

@ -1,29 +0,0 @@
[DEFAULT]
head = ../head.js ../head_remote.js head_webidl_api.js
firefox-appdir = browser
tags = webextensions webextensions-webidl-api
prefs =
# Enable support for the extension background service worker.
extensions.backgroundServiceWorker.enabled=true
# Enable Extensions API WebIDL bindings for extension windows.
extensions.webidl-api.enabled=true
# Enable ExtensionMockAPI WebIDL bindings used for unit tests
# related to the API request forwarding and not tied to a particular
# extension API.
extensions.webidl-api.expose_mock_interface=true
# services.settings.server/default_bucket:
# Make sure that loading the default settings for url-classifier-skip-urls
# doesn't interfere with running our tests while IDB operations are in
# flight by overriding the default remote settings bucket pref name to
# ensure that the IDB database isn't created in the first place.
services.settings.server=http://localhost:7777/remote-settings-dummy/v1
services.settings.default_bucket=nonexistent-bucket-foo
# NOTE: these tests seems to be timing out because it takes too much time to
# run all tests and then fully exiting the test.
skip-if = os == "android" && verify
[test_ext_webidl_api.js]
[test_ext_webidl_api_event_callback.js]
[test_ext_webidl_runtime_port.js]

Просмотреть файл

@ -1,36 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_extensions_ExtensionAPIAddRemoveListener_h
#define mozilla_extensions_ExtensionAPIAddRemoveListener_h
#include "ExtensionAPIRequestForwarder.h"
namespace mozilla {
namespace extensions {
class ExtensionAPIAddRemoveListener : public ExtensionAPIRequestForwarder {
public:
enum class EType {
eAddListener,
eRemoveListener,
};
ExtensionAPIAddRemoveListener(const EType type,
const nsAString& aApiNamespace,
const nsAString& aApiEvent,
const nsAString& aApiObjectType,
const nsAString& aApiObjectId)
: ExtensionAPIRequestForwarder(
type == EType::eAddListener ? APIRequestType::ADD_LISTENER
: APIRequestType::REMOVE_LISTENER,
aApiNamespace, aApiEvent, aApiObjectType, aApiObjectId) {}
};
} // namespace extensions
} // namespace mozilla
#endif // mozilla_extensions_ExtensionAPIAddRemoveListener_h

Просмотреть файл

@ -1,222 +0,0 @@
/* 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 "ExtensionAPIBase.h"
#include "ExtensionAPIRequestForwarder.h"
#include "ExtensionAPIAddRemoveListener.h"
#include "ExtensionAPICallAsyncFunction.h"
#include "ExtensionAPICallFunctionNoReturn.h"
#include "ExtensionAPICallSyncFunction.h"
#include "ExtensionAPIGetProperty.h"
#include "ExtensionEventManager.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/SerializedStackHolder.h"
#include "mozilla/dom/FunctionBinding.h"
namespace mozilla {
namespace extensions {
// ChromeCompatCallbackHandler
NS_IMPL_ISUPPORTS0(ChromeCompatCallbackHandler)
// static
void ChromeCompatCallbackHandler::Create(
dom::Promise* aPromise, const RefPtr<dom::Function>& aCallback) {
MOZ_ASSERT(aPromise);
MOZ_ASSERT(aCallback);
RefPtr<ChromeCompatCallbackHandler> handler =
new ChromeCompatCallbackHandler(aCallback);
aPromise->AppendNativeHandler(handler);
}
void ChromeCompatCallbackHandler::ResolvedCallback(
JSContext* aCx, JS::Handle<JS::Value> aValue) {
JS::RootedValue retval(aCx);
IgnoredErrorResult rv;
MOZ_KnownLive(mCallback)->Call({aValue}, &retval, rv);
}
void ChromeCompatCallbackHandler::RejectedCallback(
JSContext* aCx, JS::Handle<JS::Value> aValue) {
JS::RootedValue retval(aCx);
IgnoredErrorResult rv;
// Call the chrome-compatible callback without any parameter, the errors
// isn't passed to the callback as a parameter but the extension will be
// able to retrieve it from chrome.runtime.lastError.
MOZ_KnownLive(mCallback)->Call({}, &retval, rv);
}
// WebExtensionStub methods shared between multiple API namespaces.
void ExtensionAPIBase::CallWebExtMethodNotImplementedNoReturn(
JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs, ErrorResult& aRv) {
aRv.ThrowNotSupportedError("Not implemented");
}
void ExtensionAPIBase::CallWebExtMethodNotImplementedAsync(
JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs,
const dom::Optional<OwningNonNull<dom::Function>>& aCallback,
JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
CallWebExtMethodNotImplementedNoReturn(aCx, aApiMethod, aArgs, aRv);
}
void ExtensionAPIBase::CallWebExtMethodNotImplemented(
JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs, JS::MutableHandle<JS::Value> aRetval,
ErrorResult& aRv) {
CallWebExtMethodNotImplementedNoReturn(aCx, aApiMethod, aArgs, aRv);
}
void ExtensionAPIBase::CallWebExtMethodNoReturn(
JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs, ErrorResult& aRv) {
auto request = CallFunctionNoReturn(aApiMethod);
request->Run(GetGlobalObject(), aCx, aArgs, aRv);
if (aRv.Failed()) {
return;
}
}
void ExtensionAPIBase::CallWebExtMethod(JSContext* aCx,
const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs,
JS::MutableHandle<JS::Value> aRetVal,
ErrorResult& aRv) {
auto request = CallSyncFunction(aApiMethod);
request->Run(GetGlobalObject(), aCx, aArgs, aRetVal, aRv);
if (aRv.Failed()) {
return;
}
}
void ExtensionAPIBase::CallWebExtMethodAsyncInternal(
JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs,
const RefPtr<dom::Function>& aCallback,
JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
auto* global = GetGlobalObject();
IgnoredErrorResult erv;
RefPtr<dom::Promise> domPromise = dom::Promise::Create(global, erv);
if (NS_WARN_IF(erv.Failed())) {
ThrowUnexpectedError(aCx, aRv);
return;
}
MOZ_ASSERT(domPromise);
auto request = CallAsyncFunction(aApiMethod);
request->Run(global, aCx, aArgs, domPromise, aRv);
if (aRv.Failed()) {
return;
}
// The async method has been called with the chrome-compatible callback
// convention.
if (aCallback) {
ChromeCompatCallbackHandler::Create(domPromise, aCallback);
return;
}
if (NS_WARN_IF(!ToJSValue(aCx, domPromise, aRetval))) {
ThrowUnexpectedError(aCx, aRv);
return;
}
}
void ExtensionAPIBase::CallWebExtMethodAsync(
JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs,
const dom::Optional<OwningNonNull<dom::Function>>& aCallback,
JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
RefPtr<dom::Function> callback = nullptr;
if (aCallback.WasPassed()) {
callback = &aCallback.Value();
}
CallWebExtMethodAsyncInternal(aCx, aApiMethod, aArgs, callback, aRetval, aRv);
}
void ExtensionAPIBase::CallWebExtMethodAsyncAmbiguous(
JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs, JS::MutableHandle<JS::Value> aRetval,
ErrorResult& aRv) {
RefPtr<dom::Function> chromeCompatCb;
auto lastElement =
aArgs.IsEmpty() ? JS::UndefinedValue() : aArgs.LastElement();
dom::Sequence<JS::Value> callArgs(aArgs);
if (lastElement.isObject() && JS::IsCallable(&lastElement.toObject())) {
JS::Rooted<JSObject*> tempRoot(aCx, &lastElement.toObject());
JS::Rooted<JSObject*> tempGlobalRoot(aCx, JS::CurrentGlobalOrNull(aCx));
chromeCompatCb = new dom::Function(aCx, tempRoot, tempGlobalRoot,
dom::GetIncumbentGlobal());
Unused << callArgs.PopLastElement();
}
CallWebExtMethodAsyncInternal(aCx, aApiMethod, callArgs, chromeCompatCb,
aRetval, aRv);
}
// ExtensionAPIBase - API Request helpers
already_AddRefed<ExtensionEventManager> ExtensionAPIBase::CreateEventManager(
const nsAString& aEventName) {
RefPtr<ExtensionEventManager> eventMgr = new ExtensionEventManager(
GetGlobalObject(), GetAPINamespace(), aEventName, GetAPIObjectType(),
GetAPIObjectId());
return eventMgr.forget();
}
RefPtr<ExtensionAPICallFunctionNoReturn> ExtensionAPIBase::CallFunctionNoReturn(
const nsAString& aApiMethod) {
return new ExtensionAPICallFunctionNoReturn(
GetAPINamespace(), aApiMethod, GetAPIObjectType(), GetAPIObjectId());
}
RefPtr<ExtensionAPICallSyncFunction> ExtensionAPIBase::CallSyncFunction(
const nsAString& aApiMethod) {
return new ExtensionAPICallSyncFunction(GetAPINamespace(), aApiMethod,
GetAPIObjectType(), GetAPIObjectId());
}
RefPtr<ExtensionAPICallAsyncFunction> ExtensionAPIBase::CallAsyncFunction(
const nsAString& aApiMethod) {
return new ExtensionAPICallAsyncFunction(
GetAPINamespace(), aApiMethod, GetAPIObjectType(), GetAPIObjectId());
}
RefPtr<ExtensionAPIGetProperty> ExtensionAPIBase::GetProperty(
const nsAString& aApiProperty) {
return new ExtensionAPIGetProperty(GetAPINamespace(), aApiProperty,
GetAPIObjectType(), GetAPIObjectId());
}
RefPtr<ExtensionAPIAddRemoveListener> ExtensionAPIBase::SendAddListener(
const nsAString& aEventName) {
using EType = ExtensionAPIAddRemoveListener::EType;
return new ExtensionAPIAddRemoveListener(
EType::eAddListener, GetAPINamespace(), aEventName, GetAPIObjectType(),
GetAPIObjectId());
}
RefPtr<ExtensionAPIAddRemoveListener> ExtensionAPIBase::SendRemoveListener(
const nsAString& aEventName) {
using EType = ExtensionAPIAddRemoveListener::EType;
return new ExtensionAPIAddRemoveListener(
EType::eRemoveListener, GetAPINamespace(), aEventName, GetAPIObjectType(),
GetAPIObjectId());
}
// static
void ExtensionAPIBase::ThrowUnexpectedError(JSContext* aCx, ErrorResult& aRv) {
ExtensionAPIRequestForwarder::ThrowUnexpectedError(aCx, aRv);
}
} // namespace extensions
} // namespace mozilla

Просмотреть файл

@ -1,141 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_extensions_ExtensionAPIBase_h
#define mozilla_extensions_ExtensionAPIBase_h
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/ErrorResult.h"
class nsIGlobalObject;
namespace mozilla {
namespace dom {
class Function;
}
namespace extensions {
class ExtensionAPIAddRemoveListener;
class ExtensionAPICallFunctionNoReturn;
class ExtensionAPICallSyncFunction;
class ExtensionAPICallAsyncFunction;
class ExtensionAPIGetProperty;
class ExtensionEventManager;
class ExtensionAPIBase {
protected:
virtual nsIGlobalObject* GetGlobalObject() const = 0;
virtual nsString GetAPINamespace() const = 0;
virtual nsString GetAPIObjectType() const = 0;
virtual nsString GetAPIObjectId() const = 0;
private:
void CallWebExtMethodAsyncInternal(JSContext* aCx,
const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs,
const RefPtr<dom::Function>& aCallback,
JS::MutableHandle<JS::Value> aRetval,
ErrorResult& aRv);
public:
// WebExtensionStub methods shared between multiple API namespaces.
virtual void CallWebExtMethodNotImplementedNoReturn(
JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs, ErrorResult& aRv);
virtual void CallWebExtMethodNotImplementedAsync(
JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs,
const dom::Optional<OwningNonNull<dom::Function>>& aCallback,
JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv);
virtual void CallWebExtMethodNotImplemented(
JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs,
JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv);
virtual void CallWebExtMethodNoReturn(JSContext* aCx,
const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs,
ErrorResult& aRv);
virtual void CallWebExtMethod(JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs,
JS::MutableHandle<JS::Value> aRetVal,
ErrorResult& aRv);
virtual void CallWebExtMethodAsync(
JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs,
const dom::Optional<OwningNonNull<dom::Function>>& aCallback,
JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv);
virtual void CallWebExtMethodAsyncAmbiguous(
JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs,
JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv);
// API Requests helpers.
already_AddRefed<ExtensionEventManager> CreateEventManager(
const nsAString& aEventName);
RefPtr<ExtensionAPICallFunctionNoReturn> CallFunctionNoReturn(
const nsAString& aApiMethod);
RefPtr<ExtensionAPICallSyncFunction> CallSyncFunction(
const nsAString& aApiMethod);
RefPtr<ExtensionAPICallAsyncFunction> CallAsyncFunction(
const nsAString& aApiMethod);
RefPtr<ExtensionAPIGetProperty> GetProperty(const nsAString& aApiProperty);
RefPtr<ExtensionAPIAddRemoveListener> SendAddListener(
const nsAString& aEventName);
RefPtr<ExtensionAPIAddRemoveListener> SendRemoveListener(
const nsAString& aEventName);
static void ThrowUnexpectedError(JSContext* aCx, ErrorResult& aRv);
};
class ExtensionAPINamespace : public ExtensionAPIBase {
protected:
nsString GetAPIObjectType() const override { return VoidString(); }
nsString GetAPIObjectId() const override { return VoidString(); };
};
class ChromeCompatCallbackHandler final : public dom::PromiseNativeHandler {
public:
NS_DECL_THREADSAFE_ISUPPORTS
static void Create(dom::Promise* aPromise,
const RefPtr<dom::Function>& aCallback);
MOZ_CAN_RUN_SCRIPT void ResolvedCallback(
JSContext* aCx, JS::Handle<JS::Value> aValue) override;
MOZ_CAN_RUN_SCRIPT void RejectedCallback(
JSContext* aCx, JS::Handle<JS::Value> aValue) override;
private:
explicit ChromeCompatCallbackHandler(const RefPtr<dom::Function>& aCallback)
: mCallback(aCallback) {
MOZ_ASSERT(aCallback);
}
~ChromeCompatCallbackHandler() = default;
RefPtr<dom::Function> mCallback;
};
} // namespace extensions
} // namespace mozilla
#endif // mozilla_extensions_ExtensionAPIBase_h

Просмотреть файл

@ -1,29 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_extensions_ExtensionAPICallAsyncFunction_h
#define mozilla_extensions_ExtensionAPICallAsyncFunction_h
#include "ExtensionAPIRequestForwarder.h"
namespace mozilla {
namespace extensions {
class ExtensionAPICallAsyncFunction : public ExtensionAPIRequestForwarder {
public:
ExtensionAPICallAsyncFunction(const nsAString& aApiNamespace,
const nsAString& aApiMethod,
const nsAString& aApiObjectType = u""_ns,
const nsAString& aApiObjectId = u""_ns)
: ExtensionAPIRequestForwarder(
mozIExtensionAPIRequest::RequestType::CALL_FUNCTION_ASYNC,
aApiNamespace, aApiMethod, aApiObjectType, aApiObjectId) {}
};
} // namespace extensions
} // namespace mozilla
#endif // mozilla_extensions_ExtensionAPICallAsyncFunction_h

Просмотреть файл

@ -1,29 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_extensions_ExtensionAPICallFunctionNoReturn_h
#define mozilla_extensions_ExtensionAPICallFunctionNoReturn_h
#include "ExtensionAPIRequestForwarder.h"
namespace mozilla {
namespace extensions {
class ExtensionAPICallFunctionNoReturn : public ExtensionAPIRequestForwarder {
public:
ExtensionAPICallFunctionNoReturn(const nsAString& aApiNamespace,
const nsAString& aApiMethod,
const nsAString& aApiObjectType = u""_ns,
const nsAString& aApiObjectId = u""_ns)
: ExtensionAPIRequestForwarder(
mozIExtensionAPIRequest::RequestType::CALL_FUNCTION_NO_RETURN,
aApiNamespace, aApiMethod, aApiObjectType, aApiObjectId) {}
};
} // namespace extensions
} // namespace mozilla
#endif // mozilla_extensions_ExtensionAPICallFunctionNoReturn_h

Просмотреть файл

@ -1,29 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_extensions_ExtensionAPICallSyncFunction_h
#define mozilla_extensions_ExtensionAPICallSyncFunction_h
#include "ExtensionAPIRequestForwarder.h"
namespace mozilla {
namespace extensions {
class ExtensionAPICallSyncFunction : public ExtensionAPIRequestForwarder {
public:
ExtensionAPICallSyncFunction(const nsAString& aApiNamespace,
const nsAString& aApiMethod,
const nsAString& aApiObjectType = u""_ns,
const nsAString& aApiObjectId = u""_ns)
: ExtensionAPIRequestForwarder(
mozIExtensionAPIRequest::RequestType::CALL_FUNCTION, aApiNamespace,
aApiMethod, aApiObjectType, aApiObjectId) {}
};
} // namespace extensions
} // namespace mozilla
#endif // mozilla_extensions_ExtensionAPICallSyncFunction_h

Просмотреть файл

@ -1,29 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_extensions_ExtensionAPIGetProperty_h
#define mozilla_extensions_ExtensionAPIGetProperty_h
#include "ExtensionAPIRequestForwarder.h"
namespace mozilla {
namespace extensions {
class ExtensionAPIGetProperty : public ExtensionAPIRequestForwarder {
public:
ExtensionAPIGetProperty(const nsAString& aApiNamespace,
const nsAString& aApiProperty,
const nsAString& aApiObjectType = u""_ns,
const nsAString& aApiObjectId = u""_ns)
: ExtensionAPIRequestForwarder(
mozIExtensionAPIRequest::RequestType::GET_PROPERTY, aApiNamespace,
aApiProperty, aApiObjectType, aApiObjectId) {}
};
} // namespace extensions
} // namespace mozilla
#endif // mozilla_extensions_ExtensionAPICallSyncFunction_h

Просмотреть файл

@ -1,214 +0,0 @@
/* 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 "ExtensionAPIRequest.h"
#include "mozilla/dom/ClientInfo.h"
#include "mozilla/extensions/WebExtensionPolicy.h"
#include "mozilla/ipc/BackgroundUtils.h" // PrincipalInfoToPrincipal
namespace mozilla {
namespace extensions {
// mozIExtensionServiceWorkerInfo
NS_IMPL_ISUPPORTS(ExtensionServiceWorkerInfo, mozIExtensionServiceWorkerInfo)
NS_IMETHODIMP
ExtensionServiceWorkerInfo::GetPrincipal(nsIPrincipal** aPrincipal) {
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG_POINTER(aPrincipal);
auto principalOrErr = PrincipalInfoToPrincipal(mClientInfo.PrincipalInfo());
if (NS_WARN_IF(principalOrErr.isErr())) {
return NS_ERROR_UNEXPECTED;
}
nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
principal.forget(aPrincipal);
return NS_OK;
}
NS_IMETHODIMP
ExtensionServiceWorkerInfo::GetScriptURL(nsAString& aScriptURL) {
MOZ_ASSERT(NS_IsMainThread());
aScriptURL = NS_ConvertUTF8toUTF16(mClientInfo.URL());
return NS_OK;
}
NS_IMETHODIMP
ExtensionServiceWorkerInfo::GetClientInfoId(nsAString& aClientInfoId) {
MOZ_ASSERT(NS_IsMainThread());
aClientInfoId = NS_ConvertUTF8toUTF16(mClientInfo.Id().ToString());
return NS_OK;
}
// mozIExtensionAPIRequest
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionAPIRequest)
NS_INTERFACE_MAP_ENTRY(mozIExtensionAPIRequest)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_CLASS(ExtensionAPIRequest)
NS_IMPL_CYCLE_COLLECTING_ADDREF(ExtensionAPIRequest)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ExtensionAPIRequest)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ExtensionAPIRequest)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventListener)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSWInfo)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ExtensionAPIRequest)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArgs)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mStack)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ExtensionAPIRequest)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mEventListener)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSWInfo)
tmp->mStack.setUndefined();
tmp->mArgs.setUndefined();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
ExtensionAPIRequest::ExtensionAPIRequest(
const mozIExtensionAPIRequest::RequestType aRequestType,
const ExtensionAPIRequestTarget& aRequestTarget) {
MOZ_ASSERT(NS_IsMainThread());
mRequestType = aRequestType;
mRequestTarget = aRequestTarget;
mozilla::HoldJSObjects(this);
}
void ExtensionAPIRequest::Init(Maybe<dom::ClientInfo>& aSWClientInfo,
JS::HandleValue aRequestArgs,
JS::HandleValue aCallerStack) {
MOZ_ASSERT(NS_IsMainThread());
mSWClientInfo = aSWClientInfo;
mArgs.set(aRequestArgs);
mStack.set(aCallerStack);
}
NS_IMETHODIMP
ExtensionAPIRequest::ToString(nsACString& aResult) {
aResult.Truncate();
nsAutoCString requestType;
nsAutoCString apiNamespace;
nsAutoCString apiName;
GetRequestType(requestType);
GetApiNamespace(apiNamespace);
GetApiName(apiName);
if (mRequestTarget.mObjectType.IsEmpty()) {
aResult.AppendPrintf("[ExtensionAPIRequest %s %s.%s]", requestType.get(),
apiNamespace.get(), apiName.get());
} else {
nsAutoCString objectType;
nsAutoCString objectId;
GetApiObjectType(objectType);
GetApiObjectId(objectId);
aResult.AppendPrintf("[ExtensionAPIRequest %s %s.%s.%s (%s)]",
requestType.get(), apiNamespace.get(),
objectType.get(), apiName.get(), objectId.get());
}
return NS_OK;
}
NS_IMETHODIMP
ExtensionAPIRequest::GetRequestType(nsACString& aRequestTypeName) {
MOZ_ASSERT(NS_IsMainThread());
switch (mRequestType) {
case mozIExtensionAPIRequest::RequestType::CALL_FUNCTION:
aRequestTypeName = "callFunction"_ns;
break;
case mozIExtensionAPIRequest::RequestType::CALL_FUNCTION_NO_RETURN:
aRequestTypeName = "callFunctionNoReturn"_ns;
break;
case mozIExtensionAPIRequest::RequestType::CALL_FUNCTION_ASYNC:
aRequestTypeName = "callAsyncFunction"_ns;
break;
case mozIExtensionAPIRequest::RequestType::ADD_LISTENER:
aRequestTypeName = "addListener"_ns;
break;
case mozIExtensionAPIRequest::RequestType::REMOVE_LISTENER:
aRequestTypeName = "removeListener"_ns;
break;
case mozIExtensionAPIRequest::RequestType::GET_PROPERTY:
aRequestTypeName = "getProperty"_ns;
break;
default:
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
NS_IMETHODIMP
ExtensionAPIRequest::GetApiNamespace(nsACString& aApiNamespace) {
MOZ_ASSERT(NS_IsMainThread());
aApiNamespace.Assign(NS_ConvertUTF16toUTF8(mRequestTarget.mNamespace));
return NS_OK;
}
NS_IMETHODIMP
ExtensionAPIRequest::GetApiName(nsACString& aApiName) {
MOZ_ASSERT(NS_IsMainThread());
aApiName.Assign(NS_ConvertUTF16toUTF8(mRequestTarget.mMethod));
return NS_OK;
}
NS_IMETHODIMP
ExtensionAPIRequest::GetApiObjectType(nsACString& aApiObjectType) {
MOZ_ASSERT(NS_IsMainThread());
aApiObjectType.Assign(NS_ConvertUTF16toUTF8(mRequestTarget.mObjectType));
return NS_OK;
}
NS_IMETHODIMP
ExtensionAPIRequest::GetApiObjectId(nsACString& aApiObjectId) {
MOZ_ASSERT(NS_IsMainThread());
aApiObjectId.Assign(NS_ConvertUTF16toUTF8(mRequestTarget.mObjectId));
return NS_OK;
}
NS_IMETHODIMP
ExtensionAPIRequest::GetArgs(JSContext* aCx,
JS::MutableHandle<JS::Value> aRetval) {
MOZ_ASSERT(NS_IsMainThread());
aRetval.set(mArgs);
return NS_OK;
}
NS_IMETHODIMP
ExtensionAPIRequest::GetCallerSavedFrame(
JSContext* aCx, JS::MutableHandle<JS::Value> aSavedFrame) {
MOZ_ASSERT(NS_IsMainThread());
aSavedFrame.set(mStack);
return NS_OK;
}
NS_IMETHODIMP
ExtensionAPIRequest::GetServiceWorkerInfo(
mozIExtensionServiceWorkerInfo** aSWInfo) {
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG_POINTER(aSWInfo);
if (mSWClientInfo.isSome() && !mSWInfo) {
mSWInfo = new ExtensionServiceWorkerInfo(*mSWClientInfo);
}
NS_IF_ADDREF(*aSWInfo = mSWInfo);
return NS_OK;
}
NS_IMETHODIMP
ExtensionAPIRequest::GetEventListener(mozIExtensionEventListener** aListener) {
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG_POINTER(aListener);
NS_IF_ADDREF(*aListener = mEventListener);
return NS_OK;
}
} // namespace extensions
} // namespace mozilla

Просмотреть файл

@ -1,115 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_extensions_ExtensionAPIRequest_h
#define mozilla_extensions_ExtensionAPIRequest_h
#include "ExtensionEventListener.h"
#include "mozIExtensionAPIRequestHandling.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/dom/ClientInfo.h"
#include "mozilla/extensions/WebExtensionPolicy.h"
#include "nsCycleCollectionParticipant.h"
namespace mozilla {
namespace extensions {
class ExtensionAPIRequestForwarder;
class RequestWorkerRunnable;
// Represent the target of the API request forwarded, mObjectType and mObjectId
// are only expected to be polulated when the API request is originated from API
// object (like an ExtensionPort returned by a call to browser.runtime.connect).
struct ExtensionAPIRequestTarget {
nsString mNamespace;
nsString mMethod;
nsString mObjectType;
nsString mObjectId;
};
// A class that represents the service worker that has originated the API
// request.
class ExtensionServiceWorkerInfo : public mozIExtensionServiceWorkerInfo {
public:
NS_DECL_MOZIEXTENSIONSERVICEWORKERINFO
NS_DECL_ISUPPORTS
explicit ExtensionServiceWorkerInfo(const dom::ClientInfo& aClientInfo)
: mClientInfo(aClientInfo) {}
private:
virtual ~ExtensionServiceWorkerInfo() = default;
dom::ClientInfo mClientInfo;
};
// A class that represents a WebExtensions API request (a method call,
// add/remote listener or accessing a property getter) forwarded by the
// WebIDL bindings to the mozIExtensionAPIRequestHandler.
class ExtensionAPIRequest : public mozIExtensionAPIRequest {
public:
using APIRequestType = mozIExtensionAPIRequest::RequestType;
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ExtensionAPIRequest)
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_MOZIEXTENSIONAPIREQUEST
explicit ExtensionAPIRequest(
const mozIExtensionAPIRequest::RequestType aRequestType,
const ExtensionAPIRequestTarget& aRequestTarget);
void Init(Maybe<dom::ClientInfo>& aSWClientInfo, JS::HandleValue aRequestArgs,
JS::HandleValue aCallerStack);
static bool ShouldHaveResult(const APIRequestType& aRequestType) {
switch (aRequestType) {
case APIRequestType::GET_PROPERTY:
case APIRequestType::CALL_FUNCTION:
case APIRequestType::CALL_FUNCTION_ASYNC:
return true;
case APIRequestType::CALL_FUNCTION_NO_RETURN:
case APIRequestType::ADD_LISTENER:
case APIRequestType::REMOVE_LISTENER:
break;
default:
MOZ_DIAGNOSTIC_ASSERT(false, "Unexpected APIRequestType");
}
return false;
}
bool ShouldHaveResult() const { return ShouldHaveResult(mRequestType); }
void SetEventListener(const RefPtr<ExtensionEventListener>& aListener) {
MOZ_ASSERT(!mEventListener);
mEventListener = aListener;
}
private:
virtual ~ExtensionAPIRequest() {
mSWClientInfo = Nothing();
mArgs.setUndefined();
mStack.setUndefined();
mEventListener = nullptr;
mozilla::DropJSObjects(this);
};
APIRequestType mRequestType;
ExtensionAPIRequestTarget mRequestTarget;
JS::Heap<JS::Value> mStack;
JS::Heap<JS::Value> mArgs;
Maybe<dom::ClientInfo> mSWClientInfo;
RefPtr<ExtensionServiceWorkerInfo> mSWInfo;
// Only set for addListener/removeListener API requests.
RefPtr<ExtensionEventListener> mEventListener;
};
} // namespace extensions
} // namespace mozilla
#endif // mozilla_extensions_ExtensionAPIRequest_h

Просмотреть файл

@ -1,606 +0,0 @@
/* 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 "ExtensionAPIRequestForwarder.h"
#include "ExtensionEventListener.h"
#include "js/Promise.h"
#include "mozilla/dom/Client.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/ClonedErrorHolder.h"
#include "mozilla/dom/ClonedErrorHolderBinding.h"
#include "mozilla/dom/ExtensionBrowserBinding.h"
#include "mozilla/dom/FunctionBinding.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/SerializedStackHolder.h"
#include "mozilla/dom/ServiceWorkerInfo.h"
#include "mozilla/dom/ServiceWorkerManager.h"
#include "mozilla/dom/ServiceWorkerRegistrationInfo.h"
#include "mozilla/dom/StructuredCloneTags.h"
#include "mozilla/ExtensionPolicyService.h"
#include "nsIGlobalObject.h"
#include "nsImportModule.h"
#include "nsIXPConnect.h"
namespace mozilla {
namespace extensions {
// ExtensionAPIRequestForwarder
// static
void ExtensionAPIRequestForwarder::ThrowUnexpectedError(JSContext* aCx,
ErrorResult& aRv) {
aRv.MightThrowJSException();
JS_ReportErrorASCII(aCx, "An unexpected error occurred");
aRv.StealExceptionFromJSContext(aCx);
}
ExtensionAPIRequestForwarder::ExtensionAPIRequestForwarder(
const mozIExtensionAPIRequest::RequestType aRequestType,
const nsAString& aApiNamespace, const nsAString& aApiMethod,
const nsAString& aApiObjectType, const nsAString& aApiObjectId) {
mRequestType = aRequestType;
mRequestTarget.mNamespace = aApiNamespace;
mRequestTarget.mMethod = aApiMethod;
mRequestTarget.mObjectType = aApiObjectType;
mRequestTarget.mObjectId = aApiObjectId;
}
// static
nsresult ExtensionAPIRequestForwarder::JSArrayToSequence(
JSContext* aCx, JS::HandleValue aJSValue,
dom::Sequence<JS::Value>& aResult) {
bool isArray;
JS::Rooted<JSObject*> obj(aCx, aJSValue.toObjectOrNull());
if (NS_WARN_IF(!obj || !JS::IsArrayObject(aCx, obj, &isArray))) {
return NS_ERROR_UNEXPECTED;
}
if (isArray) {
uint32_t len;
if (NS_WARN_IF(!JS::GetArrayLength(aCx, obj, &len))) {
return NS_ERROR_UNEXPECTED;
}
for (uint32_t i = 0; i < len; i++) {
JS::RootedValue v(aCx);
JS_GetElement(aCx, obj, i, &v);
if (NS_WARN_IF(!aResult.AppendElement(v, fallible))) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
} else if (NS_WARN_IF(!aResult.AppendElement(aJSValue, fallible))) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
/* static */
mozIExtensionAPIRequestHandler&
ExtensionAPIRequestForwarder::APIRequestHandler() {
static nsCOMPtr<mozIExtensionAPIRequestHandler> sAPIRequestHandler;
MOZ_ASSERT(NS_IsMainThread());
if (MOZ_UNLIKELY(!sAPIRequestHandler)) {
sAPIRequestHandler =
do_ImportModule("resource://gre/modules/ExtensionProcessScript.jsm",
"ExtensionAPIRequestHandler");
MOZ_RELEASE_ASSERT(sAPIRequestHandler);
ClearOnShutdown(&sAPIRequestHandler);
}
return *sAPIRequestHandler;
}
void ExtensionAPIRequestForwarder::Run(nsIGlobalObject* aGlobal, JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs,
ExtensionEventListener* aListener,
JS::MutableHandleValue aRetVal,
ErrorResult& aRv) {
MOZ_ASSERT(dom::IsCurrentThreadRunningWorker());
dom::WorkerPrivate* workerPrivate = dom::GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);
RefPtr<RequestWorkerRunnable> runnable =
new RequestWorkerRunnable(workerPrivate, this);
RefPtr<dom::Promise> domPromise;
IgnoredErrorResult rv;
switch (mRequestType) {
case APIRequestType::CALL_FUNCTION_ASYNC:
domPromise = dom::Promise::Create(aGlobal, rv);
if (NS_WARN_IF(rv.Failed())) {
ThrowUnexpectedError(aCx, aRv);
return;
}
runnable->Init(aGlobal, aCx, aArgs, domPromise, rv);
break;
case APIRequestType::ADD_LISTENER:
[[fallthrough]];
case APIRequestType::REMOVE_LISTENER:
runnable->Init(aGlobal, aCx, aArgs, aListener, aRv);
break;
default:
runnable->Init(aGlobal, aCx, aArgs, rv);
}
if (NS_WARN_IF(rv.Failed())) {
ThrowUnexpectedError(aCx, aRv);
return;
}
runnable->Dispatch(dom::WorkerStatus::Canceling, rv);
if (NS_WARN_IF(rv.Failed())) {
ThrowUnexpectedError(aCx, aRv);
return;
}
auto resultType = runnable->GetResultType();
if (resultType.isNothing()) {
if (NS_WARN_IF(ExtensionAPIRequest::ShouldHaveResult(mRequestType))) {
ThrowUnexpectedError(aCx, aRv);
}
return;
}
// Read and throw the extension error if needed.
if (resultType.isSome() && *resultType == APIResultType::EXTENSION_ERROR) {
JS::Rooted<JS::Value> ignoredResultValue(aCx);
runnable->ReadResult(aCx, &ignoredResultValue, aRv);
// When the result type is an error aRv is expected to be
// failed, if it is not throw the generic
// "An unexpected error occurred".
if (NS_WARN_IF(!aRv.Failed())) {
ThrowUnexpectedError(aCx, aRv);
}
return;
}
if (mRequestType == APIRequestType::CALL_FUNCTION_ASYNC) {
MOZ_ASSERT(domPromise);
if (NS_WARN_IF(!ToJSValue(aCx, domPromise, aRetVal))) {
ThrowUnexpectedError(aCx, aRv);
}
return;
}
JS::Rooted<JS::Value> resultValue(aCx);
runnable->ReadResult(aCx, &resultValue, rv);
if (NS_WARN_IF(rv.Failed())) {
ThrowUnexpectedError(aCx, aRv);
return;
}
aRetVal.set(resultValue);
}
void ExtensionAPIRequestForwarder::Run(nsIGlobalObject* aGlobal, JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs,
JS::MutableHandleValue aRetVal,
ErrorResult& aRv) {
Run(aGlobal, aCx, aArgs, nullptr, aRetVal, aRv);
}
void ExtensionAPIRequestForwarder::Run(nsIGlobalObject* aGlobal, JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs,
ErrorResult& aRv) {
JS::Rooted<JS::Value> ignoredRetval(aCx);
Run(aGlobal, aCx, aArgs, nullptr, &ignoredRetval, aRv);
}
void ExtensionAPIRequestForwarder::Run(nsIGlobalObject* aGlobal, JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs,
ExtensionEventListener* aListener,
ErrorResult& aRv) {
MOZ_ASSERT(aListener);
JS::Rooted<JS::Value> ignoredRetval(aCx);
Run(aGlobal, aCx, aArgs, aListener, &ignoredRetval, aRv);
}
void ExtensionAPIRequestForwarder::Run(
nsIGlobalObject* aGlobal, JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs,
const RefPtr<dom::Promise>& aPromiseRetval, ErrorResult& aRv) {
MOZ_ASSERT(aPromiseRetval);
JS::Rooted<JS::Value> promisedRetval(aCx);
Run(aGlobal, aCx, aArgs, &promisedRetval, aRv);
if (aRv.Failed()) {
return;
}
aPromiseRetval->MaybeResolve(promisedRetval);
}
void ExtensionAPIRequestForwarder::Run(nsIGlobalObject* aGlobal, JSContext* aCx,
JS::MutableHandleValue aRetVal,
ErrorResult& aRv) {
Run(aGlobal, aCx, {}, aRetVal, aRv);
}
namespace {
// Custom PromiseWorkerProxy callback to deserialize error objects
// from ClonedErrorHolder structured clone data.
JSObject* ExtensionAPIRequestStructuredCloneRead(
JSContext* aCx, JSStructuredCloneReader* aReader,
const dom::PromiseWorkerProxy* aProxy, uint32_t aTag, uint32_t aData) {
// Deserialize ClonedErrorHolder that may have been structured cloned
// as a result of a resolved/rejected promise.
if (aTag == dom::SCTAG_DOM_CLONED_ERROR_OBJECT) {
return dom::ClonedErrorHolder::ReadStructuredClone(aCx, aReader, nullptr);
}
return nullptr;
}
// Custom PromiseWorkerProxy callback to serialize error objects into
// ClonedErrorHolder structured clone data.
bool ExtensionAPIRequestStructuredCloneWrite(JSContext* aCx,
JSStructuredCloneWriter* aWriter,
dom::PromiseWorkerProxy* aProxy,
JS::HandleObject aObj) {
// Try to serialize the object as a CloneErrorHolder, if it fails then
// the object wasn't an error.
IgnoredErrorResult rv;
RefPtr<dom::ClonedErrorHolder> ceh =
dom::ClonedErrorHolder::Create(aCx, aObj, rv);
if (NS_WARN_IF(rv.Failed()) || !ceh) {
return false;
}
return ceh->WriteStructuredClone(aCx, aWriter, nullptr);
}
} // namespace
RequestWorkerRunnable::RequestWorkerRunnable(
dom::WorkerPrivate* aWorkerPrivate,
ExtensionAPIRequestForwarder* aOuterAPIRequest)
: WorkerMainThreadRunnable(aWorkerPrivate,
"ExtensionAPIRequest :: WorkerRunnable"_ns) {
MOZ_ASSERT(dom::IsCurrentThreadRunningWorker());
MOZ_ASSERT(aOuterAPIRequest);
mOuterRequest = aOuterAPIRequest;
}
void RequestWorkerRunnable::Init(nsIGlobalObject* aGlobal, JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs,
ExtensionEventListener* aListener,
ErrorResult& aRv) {
MOZ_ASSERT(dom::IsCurrentThreadRunningWorker());
auto* workerScope = mWorkerPrivate->GlobalScope();
if (NS_WARN_IF(!workerScope)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
mClientInfo = workerScope->GetClientInfo();
if (mClientInfo.isNothing()) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
IgnoredErrorResult rv;
SerializeArgs(aCx, aArgs, rv);
if (NS_WARN_IF(rv.Failed())) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
SerializeCallerStack(aCx);
mEventListener = aListener;
}
void RequestWorkerRunnable::Init(nsIGlobalObject* aGlobal, JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs,
const RefPtr<dom::Promise>& aPromiseRetval,
ErrorResult& aRv) {
// Custom callbacks needed to make the PromiseWorkerProxy instance to
// be able to write and read errors using CloneErrorHolder.
static const dom::PromiseWorkerProxy::
PromiseWorkerProxyStructuredCloneCallbacks
kExtensionAPIRequestStructuredCloneCallbacks = {
ExtensionAPIRequestStructuredCloneRead,
ExtensionAPIRequestStructuredCloneWrite,
};
Init(aGlobal, aCx, aArgs, /* aListener */ nullptr, aRv);
if (aRv.Failed()) {
return;
}
mPromiseProxy = dom::PromiseWorkerProxy::Create(
mWorkerPrivate, aPromiseRetval,
&kExtensionAPIRequestStructuredCloneCallbacks);
}
void RequestWorkerRunnable::SerializeCallerStack(JSContext* aCx) {
MOZ_ASSERT(dom::IsCurrentThreadRunningWorker());
MOZ_ASSERT(mStackHolder.isNothing());
mStackHolder = Some(dom::GetCurrentStack(aCx));
}
void RequestWorkerRunnable::DeserializeCallerStack(
JSContext* aCx, JS::MutableHandleValue aRetval) {
MOZ_ASSERT(NS_IsMainThread());
if (mStackHolder.isSome()) {
JS::RootedObject savedFrame(aCx, mStackHolder->get()->ReadStack(aCx));
aRetval.set(JS::ObjectValue(*savedFrame));
mStackHolder = Nothing();
}
}
void RequestWorkerRunnable::SerializeArgs(JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs,
ErrorResult& aRv) {
MOZ_ASSERT(dom::IsCurrentThreadRunningWorker());
MOZ_ASSERT(!mArgsHolder);
JS::Rooted<JS::Value> jsval(aCx);
if (NS_WARN_IF(!ToJSValue(aCx, aArgs, &jsval))) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
mArgsHolder = Some(MakeUnique<dom::StructuredCloneHolder>(
dom::StructuredCloneHolder::CloningSupported,
dom::StructuredCloneHolder::TransferringNotSupported,
JS::StructuredCloneScope::SameProcess));
mArgsHolder->get()->Write(aCx, jsval, aRv);
}
nsresult RequestWorkerRunnable::DeserializeArgs(
JSContext* aCx, JS::MutableHandle<JS::Value> aArgs) {
MOZ_ASSERT(NS_IsMainThread());
if (mArgsHolder.isSome() && mArgsHolder->get()->HasData()) {
IgnoredErrorResult rv;
JS::Rooted<JS::Value> jsvalue(aCx);
mArgsHolder->get()->Read(xpc::CurrentNativeGlobal(aCx), aCx, &jsvalue, rv);
if (NS_WARN_IF(rv.Failed())) {
return NS_ERROR_UNEXPECTED;
}
aArgs.set(jsvalue);
}
return NS_OK;
}
bool RequestWorkerRunnable::MainThreadRun() {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<mozIExtensionAPIRequestHandler> handler =
&ExtensionAPIRequestForwarder::APIRequestHandler();
nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(handler);
dom::AutoJSAPI jsapi;
if (!jsapi.Init(wrapped->GetJSObjectGlobal())) {
return false;
}
auto* cx = jsapi.cx();
JS::Rooted<JS::Value> retval(cx);
return HandleAPIRequest(cx, &retval);
}
already_AddRefed<ExtensionAPIRequest> RequestWorkerRunnable::CreateAPIRequest(
JSContext* aCx) {
JS::Rooted<JS::Value> callArgs(aCx);
JS::Rooted<JS::Value> callerStackValue(aCx);
DeserializeArgs(aCx, &callArgs);
DeserializeCallerStack(aCx, &callerStackValue);
RefPtr<ExtensionAPIRequest> request = new ExtensionAPIRequest(
mOuterRequest->GetRequestType(), *mOuterRequest->GetRequestTarget());
request->Init(mClientInfo, callArgs, callerStackValue);
if (mEventListener) {
request->SetEventListener(mEventListener.forget());
}
return request.forget();
}
already_AddRefed<WebExtensionPolicy>
RequestWorkerRunnable::GetWebExtensionPolicy() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mWorkerPrivate);
auto* baseURI = mWorkerPrivate->GetBaseURI();
RefPtr<WebExtensionPolicy> policy =
ExtensionPolicyService::GetSingleton().GetByURL(baseURI);
return policy.forget();
}
bool RequestWorkerRunnable::HandleAPIRequest(JSContext* aCx,
JS::MutableHandleValue aRetval) {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<WebExtensionPolicy> policy = GetWebExtensionPolicy();
if (NS_WARN_IF(!policy || !policy->Active())) {
// Fails if no extension policy object has been found, or if the
// extension is not active.
return false;
}
nsresult rv;
RefPtr<ExtensionAPIRequest> request = CreateAPIRequest(aCx);
nsCOMPtr<mozIExtensionAPIRequestHandler> handler =
&ExtensionAPIRequestForwarder::APIRequestHandler();
RefPtr<mozIExtensionAPIRequestResult> apiResult;
rv = handler->HandleAPIRequest(policy, request, getter_AddRefs(apiResult));
if (NS_FAILED(rv)) {
return false;
}
// A missing apiResult is expected for some request types
// (e.g. CALL_FUNCTION_NO_RETURN/ADD_LISTENER/REMOVE_LISTENER).
// If the apiResult is missing for a request type that expects
// to have one, consider the request as failed with an unknown error.
if (!apiResult) {
return !request->ShouldHaveResult();
}
mozIExtensionAPIRequestResult::ResultType resultType;
apiResult->GetType(&resultType);
apiResult->GetValue(aRetval);
mResultType = Some(resultType);
bool isExtensionError =
resultType == mozIExtensionAPIRequestResult::ResultType::EXTENSION_ERROR;
bool okSerializedError = false;
if (aRetval.isObject()) {
// Try to serialize the result as an ClonedErrorHolder
// (because all API requests could receive one for EXTENSION_ERROR
// result types, and some also as a RETURN_VALUE result, e.g.
// runtime.lastError).
JS::Rooted<JSObject*> errObj(aCx, &aRetval.toObject());
IgnoredErrorResult rv;
RefPtr<dom::ClonedErrorHolder> ceh =
dom::ClonedErrorHolder::Create(aCx, errObj, rv);
if (!rv.Failed() && ceh) {
JS::RootedObject obj(aCx);
// Note: `ToJSValue` cannot be used because ClonedErrorHolder isn't
// wrapper cached.
okSerializedError = ceh->WrapObject(aCx, nullptr, &obj);
aRetval.setObject(*obj);
} else {
okSerializedError = false;
}
}
if (isExtensionError && !okSerializedError) {
NS_WARNING("Failed to wrap ClonedErrorHolder");
MOZ_DIAGNOSTIC_ASSERT(false, "Failed to wrap ClonedErrorHolder");
return false;
}
if (isExtensionError && !aRetval.isObject()) {
NS_WARNING("Unexpected non-object error");
return false;
}
switch (resultType) {
case mozIExtensionAPIRequestResult::ResultType::RETURN_VALUE:
return ProcessHandlerResult(aCx, aRetval);
case mozIExtensionAPIRequestResult::ResultType::EXTENSION_ERROR:
if (!aRetval.isObject()) {
return false;
}
return ProcessHandlerResult(aCx, aRetval);
}
MOZ_DIAGNOSTIC_ASSERT(false, "Unexpected API request ResultType");
return false;
}
bool RequestWorkerRunnable::ProcessHandlerResult(
JSContext* aCx, JS::MutableHandleValue aRetval) {
MOZ_ASSERT(NS_IsMainThread());
if (mOuterRequest->GetRequestType() == APIRequestType::CALL_FUNCTION_ASYNC) {
if (NS_WARN_IF(mResultType.isNothing())) {
return false;
}
if (*mResultType == APIResultType::RETURN_VALUE) {
// For an Async API method we expect a promise object to be set
// as the value to return, if it is not we return earlier here
// (and then throw a generic unexpected error to the caller).
if (NS_WARN_IF(!aRetval.isObject())) {
return false;
}
JS::Rooted<JSObject*> obj(aCx, &aRetval.toObject());
if (NS_WARN_IF(!JS::IsPromiseObject(obj))) {
return false;
}
ErrorResult rv;
nsIGlobalObject* glob = xpc::CurrentNativeGlobal(aCx);
already_AddRefed<dom::Promise> promise =
dom::Promise::Resolve(glob, aCx, aRetval, rv);
if (rv.Failed()) {
return false;
}
promise.take()->AppendNativeHandler(mPromiseProxy);
return true;
}
}
switch (*mResultType) {
case APIResultType::RETURN_VALUE:
[[fallthrough]];
case APIResultType::EXTENSION_ERROR: {
// In all other case we expect the result to be:
// - a structured clonable result
// - an extension error (e.g. due to the API call params validation
// errors),
// previously converted into a CloneErrorHolder
IgnoredErrorResult rv;
mResultHolder = Some(MakeUnique<dom::StructuredCloneHolder>(
dom::StructuredCloneHolder::CloningSupported,
dom::StructuredCloneHolder::TransferringNotSupported,
JS::StructuredCloneScope::SameProcess));
mResultHolder->get()->Write(aCx, aRetval, rv);
return !rv.Failed();
}
}
MOZ_DIAGNOSTIC_ASSERT(false, "Unexpected API request ResultType");
return false;
}
void RequestWorkerRunnable::ReadResult(JSContext* aCx,
JS::MutableHandleValue aResult,
ErrorResult& aRv) {
MOZ_ASSERT(mWorkerPrivate->IsOnCurrentThread());
if (mResultHolder.isNothing() || !mResultHolder->get()->HasData()) {
return;
}
if (NS_WARN_IF(mResultType.isNothing())) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
switch (*mResultType) {
case mozIExtensionAPIRequestResult::ResultType::RETURN_VALUE:
mResultHolder->get()->Read(xpc::CurrentNativeGlobal(aCx), aCx, aResult,
aRv);
return;
case mozIExtensionAPIRequestResult::ResultType::EXTENSION_ERROR:
JS::RootedValue exn(aCx);
IgnoredErrorResult rv;
mResultHolder->get()->Read(xpc::CurrentNativeGlobal(aCx), aCx, &exn, rv);
if (rv.Failed()) {
NS_WARNING("Failed to deserialize extension error");
ExtensionAPIBase::ThrowUnexpectedError(aCx, aRv);
return;
}
aRv.MightThrowJSException();
aRv.ThrowJSException(aCx, exn);
return;
}
MOZ_DIAGNOSTIC_ASSERT(false, "Unexpected API request ResultType");
aRv.Throw(NS_ERROR_UNEXPECTED);
}
} // namespace extensions
} // namespace mozilla

Просмотреть файл

@ -1,194 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_extensions_ExtensionAPIRequestForwarder_h
#define mozilla_extensions_ExtensionAPIRequestForwarder_h
#include "ExtensionAPIRequest.h"
#include "mozilla/dom/PromiseWorkerProxy.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/dom/StructuredCloneHolder.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/ToJSValue.h"
namespace mozilla {
namespace dom {
class ClientInfoAndState;
class Function;
class SerializedStackHolder;
} // namespace dom
namespace extensions {
class ExtensionAPIRequestForwarder;
// A class used to forward an API request (a method call, add/remote listener or
// a property getter) originated from a WebExtensions global (a window, a
// content script sandbox or a service worker) to the JS privileged API request
// handler available on the main thread (mozIExtensionAPIRequestHandler).
//
// Instances of this class are meant to be short-living, and destroyed when the
// caller function is exiting.
class ExtensionAPIRequestForwarder {
friend class ExtensionAPIRequest;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ExtensionAPIRequestForwarder)
public:
using APIRequestType = mozIExtensionAPIRequest::RequestType;
using APIResultType = mozIExtensionAPIRequestResult::ResultType;
static nsresult JSArrayToSequence(JSContext* aCx, JS::HandleValue aJSValue,
dom::Sequence<JS::Value>& aResult);
static void ThrowUnexpectedError(JSContext* aCx, ErrorResult& aRv);
static mozIExtensionAPIRequestHandler& APIRequestHandler();
ExtensionAPIRequestForwarder(const APIRequestType aRequestType,
const nsAString& aApiNamespace,
const nsAString& aApiMethod,
const nsAString& aApiObjectType = u""_ns,
const nsAString& aApiObjectId = u""_ns);
mozIExtensionAPIRequest::RequestType GetRequestType() const {
return mRequestType;
}
const ExtensionAPIRequestTarget* GetRequestTarget() {
return &mRequestTarget;
}
void Run(nsIGlobalObject* aGlobal, JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs, ErrorResult& aRv);
void Run(nsIGlobalObject* aGlobal, JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs,
ExtensionEventListener* aListener, ErrorResult& aRv);
void Run(nsIGlobalObject* aGlobal, JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs,
JS::MutableHandleValue aRetVal, ErrorResult& aRv);
void Run(nsIGlobalObject* aGlobal, JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs,
ExtensionEventListener* aListener, JS::MutableHandleValue aRetVal,
ErrorResult& aRv);
void Run(nsIGlobalObject* aGlobal, JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs,
const RefPtr<dom::Promise>& aPromiseRetval, ErrorResult& aRv);
void Run(nsIGlobalObject* aGlobal, JSContext* aCx,
JS::MutableHandleValue aRetVal, ErrorResult& aRv);
protected:
virtual ~ExtensionAPIRequestForwarder() = default;
private:
already_AddRefed<ExtensionAPIRequest> CreateAPIRequest(
nsIGlobalObject* aGlobal, JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs, ExtensionEventListener* aListener,
ErrorResult& aRv);
APIRequestType mRequestType;
ExtensionAPIRequestTarget mRequestTarget;
};
/*
* This runnable is used internally by ExtensionAPIRequestForwader class
* to call the JS privileged code that handle the API requests originated
* from the WebIDL bindings instantiated in a worker thread.
*
* The runnable is meant to block the worker thread until we get a result
* from the JS privileged code that handles the API request.
*
* For async API calls we still need to block the worker thread until
* we get a promise (which we link to the worker thread promise and
* at that point we unblock the worker thread), because the JS privileged
* code handling the API request may need to throw some errors synchonously
* (e.g. in case of additional validations based on the API schema definition
* for the parameter, like strings that has to pass additional validation
* or normalizations).
*/
class RequestWorkerRunnable : public dom::WorkerMainThreadRunnable {
public:
using APIRequestType = mozIExtensionAPIRequest::RequestType;
using APIResultType = mozIExtensionAPIRequestResult::ResultType;
RequestWorkerRunnable(dom::WorkerPrivate* aWorkerPrivate,
ExtensionAPIRequestForwarder* aOuterAPIRequest);
/**
* Init a request runnable for AddListener and RemoveListener API requests
* (which do have an event callback callback and do not expect any return
* value).
*/
void Init(nsIGlobalObject* aGlobal, JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs,
ExtensionEventListener* aListener, ErrorResult& aRv);
/**
* Init a request runnable for CallFunctionNoReturn API requests (which do
* do not expect any return value).
*/
void Init(nsIGlobalObject* aGlobal, JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs, ErrorResult& aRv) {
Init(aGlobal, aCx, aArgs, nullptr, aRv);
}
/**
* Init a request runnable for CallAsyncFunction API requests (which do
* expect a promise as return value).
*/
void Init(nsIGlobalObject* aGlobal, JSContext* aCx,
const dom::Sequence<JS::Value>& aArgs,
const RefPtr<dom::Promise>& aPromiseRetval, ErrorResult& aRv);
bool MainThreadRun() override;
void ReadResult(JSContext* aCx, JS::MutableHandleValue aResult,
ErrorResult& aRv);
Maybe<mozIExtensionAPIRequestResult::ResultType> GetResultType() {
return mResultType;
}
protected:
virtual bool ProcessHandlerResult(JSContext* aCx,
JS::MutableHandleValue aRetval);
already_AddRefed<WebExtensionPolicy> GetWebExtensionPolicy();
already_AddRefed<ExtensionAPIRequest> CreateAPIRequest(JSContext* aCx);
void SerializeCallerStack(JSContext* aCx);
void DeserializeCallerStack(JSContext* aCx, JS::MutableHandleValue aRetval);
void SerializeArgs(JSContext* aCx, const dom::Sequence<JS::Value>& aArgs,
ErrorResult& aRv);
nsresult DeserializeArgs(JSContext* aCx, JS::MutableHandle<JS::Value> aArgs);
bool HandleAPIRequest(JSContext* aCx, JS::MutableHandleValue aRetval);
Maybe<mozIExtensionAPIRequestResult::ResultType> mResultType;
Maybe<UniquePtr<dom::StructuredCloneHolder>> mResultHolder;
RefPtr<dom::PromiseWorkerProxy> mPromiseProxy;
Maybe<UniquePtr<dom::StructuredCloneHolder>> mArgsHolder;
Maybe<UniquePtr<dom::SerializedStackHolder>> mStackHolder;
Maybe<dom::ClientInfo> mClientInfo;
// Only set for addListener/removeListener API requests.
RefPtr<ExtensionEventListener> mEventListener;
// The outer request object is kept alive by the caller for the
// entire life of the inner worker runnable.
ExtensionAPIRequestForwarder* mOuterRequest;
};
} // namespace extensions
} // namespace mozilla
#endif // mozilla_extensions_ExtensionAPIRequestForwarder_h

Просмотреть файл

@ -1,81 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ExtensionBrowser.h"
#include "mozilla/dom/ExtensionBrowserBinding.h"
#include "mozilla/dom/WorkerPrivate.h" // GetWorkerPrivateFromContext
#include "mozilla/extensions/ExtensionMockAPI.h"
#include "mozilla/extensions/WebExtensionPolicy.h"
namespace mozilla {
namespace extensions {
NS_IMPL_CYCLE_COLLECTING_ADDREF(ExtensionBrowser);
NS_IMPL_CYCLE_COLLECTING_RELEASE(ExtensionBrowser)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ExtensionBrowser, mGlobal,
mExtensionMockAPI);
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionBrowser)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
ExtensionBrowser::ExtensionBrowser(nsIGlobalObject* aGlobal)
: mGlobal(aGlobal) {
MOZ_DIAGNOSTIC_ASSERT(mGlobal);
}
JSObject* ExtensionBrowser::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return dom::ExtensionBrowser_Binding::Wrap(aCx, this, aGivenProto);
}
nsIGlobalObject* ExtensionBrowser::GetParentObject() const { return mGlobal; }
bool ExtensionAPIAllowed(JSContext* aCx, JSObject* aGlobal) {
#ifdef MOZ_WEBEXT_WEBIDL_ENABLED
// Only expose the Extension API bindings if:
// - the context is related to a worker where the Extension API are allowed
// (currently only the extension service worker declared in the extension
// manifest met this condition)
// - the global is an extension window or an extension content script sandbox
// TODO:
// - the support for the extension window is deferred to a followup.
// - support for the content script sandboxes is also deferred to follow-ups
// - lock native Extension API in an extension window or sandbox behind a
// separate pref.
MOZ_DIAGNOSTIC_ASSERT(
!NS_IsMainThread(),
"ExtensionAPI webidl bindings does not yet support main thread globals");
// Verify if the Extensions API should be allowed on a worker thread.
if (!StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup()) {
return false;
}
auto* workerPrivate = mozilla::dom::GetWorkerPrivateFromContext(aCx);
MOZ_ASSERT(workerPrivate);
MOZ_ASSERT(workerPrivate->IsServiceWorker());
return workerPrivate->ExtensionAPIAllowed();
#else
// Always return false on build where MOZ_WEBEXT_WEBIDL_ENABLED is set to
// false (currently on all channels but nightly).
return false;
#endif
}
ExtensionMockAPI* ExtensionBrowser::GetExtensionMockAPI() {
if (!mExtensionMockAPI) {
mExtensionMockAPI = new ExtensionMockAPI(mGlobal, this);
}
return mExtensionMockAPI;
}
} // namespace extensions
} // namespace mozilla

Просмотреть файл

@ -1,52 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_extensions_ExtensionBrowser_h
#define mozilla_extensions_ExtensionBrowser_h
#include "nsCOMPtr.h"
#include "nsISupports.h"
#include "nsWrapperCache.h"
class nsIGlobalObject;
namespace mozilla {
class ErrorResult;
namespace extensions {
class ExtensionMockAPI;
bool ExtensionAPIAllowed(JSContext* aCx, JSObject* aGlobal);
class ExtensionBrowser final : public nsISupports, public nsWrapperCache {
nsCOMPtr<nsIGlobalObject> mGlobal;
RefPtr<ExtensionMockAPI> mExtensionMockAPI;
~ExtensionBrowser() = default;
public:
explicit ExtensionBrowser(nsIGlobalObject* aGlobal);
// nsWrapperCache interface methods
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
// DOM bindings methods
nsIGlobalObject* GetParentObject() const;
ExtensionMockAPI* GetExtensionMockAPI();
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ExtensionBrowser)
};
} // namespace extensions
} // namespace mozilla
#endif // mozilla_extensions_ExtensionBrowser_h

Просмотреть файл

@ -1,676 +0,0 @@
/* 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 "ExtensionEventListener.h"
#include "ExtensionPort.h"
#include "mozilla/dom/FunctionBinding.h"
#include "nsJSPrincipals.h" // nsJSPrincipals::AutoSetActiveWorkerPrincipal
#include "nsThreadManager.h" // NS_IsMainThread
namespace mozilla {
namespace extensions {
namespace {
class SendResponseCallback final : public nsISupports {
public:
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(SendResponseCallback)
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
static RefPtr<SendResponseCallback> Create(
nsIGlobalObject* aGlobalObject, const RefPtr<dom::Promise>& aPromise,
JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
MOZ_ASSERT(dom::IsCurrentThreadRunningWorker());
RefPtr<SendResponseCallback> responseCallback =
new SendResponseCallback(aPromise, aValue);
auto cleanupCb = [responseCallback]() { responseCallback->Cleanup(); };
// Create a StrongWorkerRef to the worker thread, the cleanup callback
// associated to the StongerWorkerRef will release the reference and resolve
// the promise returned to the ExtensionEventListener caller with undefined
// if the worker global is being destroyed.
auto* workerPrivate = dom::GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);
workerPrivate->AssertIsOnWorkerThread();
RefPtr<dom::StrongWorkerRef> workerRef = dom::StrongWorkerRef::Create(
workerPrivate, "SendResponseCallback", cleanupCb);
if (NS_WARN_IF(!workerRef)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
responseCallback->mWorkerRef = workerRef;
return responseCallback;
}
SendResponseCallback(const RefPtr<dom::Promise>& aPromise,
JS::Handle<JS::Value> aValue)
: mPromise(aPromise), mValue(aValue) {
MOZ_ASSERT(mPromise);
mozilla::HoldJSObjects(this);
// Create a promise monitor that invalidates the sendResponse
// callback if the promise has been already resolved or rejected.
mPromiseListener = new dom::DomPromiseListener(
mPromise,
[self = RefPtr{this}](JSContext* aCx, JS::Handle<JS::Value> aValue) {
self->Cleanup();
},
[self = RefPtr{this}](nsresult aError) { self->Cleanup(); });
}
void Cleanup(bool aIsDestroying = false) {
// Return earlier if the instance was already been cleaned up.
if (!mPromiseListener) {
return;
}
NS_WARNING("SendResponseCallback::Cleanup");
// Override the promise listener's resolvers to release the
// RefPtr captured by the ones initially set.
mPromiseListener->SetResolvers(
[](JSContext* aCx, JS::Handle<JS::Value> aValue) {},
[](nsresult aError) {});
mPromiseListener = nullptr;
if (mPromise) {
mPromise->MaybeResolveWithUndefined();
}
mPromise = nullptr;
// Skipped if called from the destructor.
if (!aIsDestroying && mValue.isObject()) {
// Release the reference to the SendResponseCallback.
js::SetFunctionNativeReserved(&mValue.toObject(),
SLOT_SEND_RESPONSE_CALLBACK_INSTANCE,
JS::PrivateValue(nullptr));
}
if (mWorkerRef) {
mWorkerRef = nullptr;
}
}
static bool Call(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
JS::Rooted<JSObject*> callee(aCx, &args.callee());
JS::Value v = js::GetFunctionNativeReserved(
callee, SLOT_SEND_RESPONSE_CALLBACK_INSTANCE);
SendResponseCallback* sendResponse =
reinterpret_cast<SendResponseCallback*>(v.toPrivate());
if (!sendResponse || !sendResponse->mPromise ||
!sendResponse->mPromise->PromiseObj()) {
NS_WARNING("SendResponseCallback called after being invalidated");
return true;
}
sendResponse->mPromise->MaybeResolve(args.get(0));
sendResponse->Cleanup();
return true;
}
private:
~SendResponseCallback() {
mozilla::DropJSObjects(this);
this->Cleanup(true);
};
RefPtr<dom::Promise> mPromise;
RefPtr<dom::DomPromiseListener> mPromiseListener;
JS::Heap<JS::Value> mValue;
RefPtr<dom::StrongWorkerRef> mWorkerRef;
};
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SendResponseCallback)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_CLASS(SendResponseCallback)
NS_IMPL_CYCLE_COLLECTING_ADDREF(SendResponseCallback)
NS_IMPL_CYCLE_COLLECTING_RELEASE(SendResponseCallback)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SendResponseCallback)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(SendResponseCallback)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mValue)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SendResponseCallback)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromiseListener);
tmp->mValue.setUndefined();
// Resolve the promise with undefined (as "unhandled") before unlinking it.
if (tmp->mPromise) {
tmp->mPromise->MaybeResolveWithUndefined();
}
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
} // anonymous namespace
// ExtensionEventListener
NS_IMPL_ISUPPORTS(ExtensionEventListener, mozIExtensionEventListener)
// static
already_AddRefed<ExtensionEventListener> ExtensionEventListener::Create(
nsIGlobalObject* aGlobal, dom::Function* aCallback,
CleanupCallback&& aCleanupCallback, ErrorResult& aRv) {
MOZ_ASSERT(dom::IsCurrentThreadRunningWorker());
RefPtr<ExtensionEventListener> extCb =
new ExtensionEventListener(aGlobal, aCallback);
auto* workerPrivate = dom::GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);
workerPrivate->AssertIsOnWorkerThread();
RefPtr<dom::StrongWorkerRef> workerRef = dom::StrongWorkerRef::Create(
workerPrivate, "ExtensionEventListener", std::move(aCleanupCallback));
if (!workerRef) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
extCb->mWorkerRef = new dom::ThreadSafeWorkerRef(workerRef);
return extCb.forget();
}
// static
UniquePtr<dom::StructuredCloneHolder>
ExtensionEventListener::SerializeCallArguments(const nsTArray<JS::Value>& aArgs,
JSContext* aCx,
ErrorResult& aRv) {
JS::Rooted<JS::Value> jsval(aCx);
if (NS_WARN_IF(!dom::ToJSValue(aCx, aArgs, &jsval))) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
UniquePtr<dom::StructuredCloneHolder> argsHolder =
MakeUnique<dom::StructuredCloneHolder>(
dom::StructuredCloneHolder::CloningSupported,
dom::StructuredCloneHolder::TransferringNotSupported,
JS::StructuredCloneScope::SameProcess);
argsHolder->Write(aCx, jsval, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return argsHolder;
}
NS_IMETHODIMP ExtensionEventListener::CallListener(
const nsTArray<JS::Value>& aArgs, ListenerCallOptions* aCallOptions,
JSContext* aCx, dom::Promise** aPromiseResult) {
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG_POINTER(aPromiseResult);
// Process and validate call options.
APIObjectType apiObjectType = APIObjectType::NONE;
JS::Rooted<JS::Value> apiObjectDescriptor(aCx);
if (aCallOptions) {
aCallOptions->GetApiObjectType(&apiObjectType);
aCallOptions->GetApiObjectDescriptor(&apiObjectDescriptor);
// Explicitly check that the APIObjectType is one of expected ones,
// raise to the caller an explicit error if it is not.
//
// This is using a switch to also get a warning if a new value is added to
// the APIObjectType enum and it is not yet handled.
switch (apiObjectType) {
case APIObjectType::NONE:
if (NS_WARN_IF(!apiObjectDescriptor.isNullOrUndefined())) {
JS_ReportErrorASCII(
aCx,
"Unexpected non-null apiObjectDescriptor on apiObjectType=NONE");
return NS_ERROR_UNEXPECTED;
}
break;
case APIObjectType::RUNTIME_PORT:
if (NS_WARN_IF(apiObjectDescriptor.isNullOrUndefined())) {
JS_ReportErrorASCII(aCx,
"Unexpected null apiObjectDescriptor on "
"apiObjectType=RUNTIME_PORT");
return NS_ERROR_UNEXPECTED;
}
break;
default:
MOZ_CRASH("Unexpected APIObjectType");
return NS_ERROR_UNEXPECTED;
}
}
// Create promise to be returned.
IgnoredErrorResult rv;
RefPtr<dom::Promise> retPromise;
nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
if (NS_WARN_IF(!global)) {
return NS_ERROR_FAILURE;
}
retPromise = dom::Promise::Create(global, rv);
if (NS_WARN_IF(rv.Failed())) {
return rv.StealNSResult();
}
// Convert args into a non-const sequence.
dom::Sequence<JS::Value> args;
if (!args.AppendElements(aArgs, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Execute the listener call.
MutexAutoLock lock(mMutex);
if (NS_WARN_IF(!mWorkerRef)) {
return NS_ERROR_ABORT;
}
if (apiObjectType != APIObjectType::NONE) {
// Prepend the apiObjectDescriptor data to the call arguments,
// the worker runnable will convert that into an API object
// instance on the worker thread.
if (!args.InsertElementAt(0, std::move(apiObjectDescriptor), fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
UniquePtr<dom::StructuredCloneHolder> argsHolder =
SerializeCallArguments(args, aCx, rv);
if (NS_WARN_IF(rv.Failed())) {
return rv.StealNSResult();
}
RefPtr<ExtensionListenerCallWorkerRunnable> runnable =
new ExtensionListenerCallWorkerRunnable(this, std::move(argsHolder),
aCallOptions, retPromise);
runnable->Dispatch();
retPromise.forget(aPromiseResult);
return NS_OK;
}
dom::WorkerPrivate* ExtensionEventListener::GetWorkerPrivate() const {
MOZ_ASSERT(mWorkerRef);
return mWorkerRef->Private();
}
// ExtensionListenerCallWorkerRunnable
void ExtensionListenerCallWorkerRunnable::DeserializeCallArguments(
JSContext* aCx, dom::Sequence<JS::Value>& aArgs, ErrorResult& aRv) {
JS::Rooted<JS::Value> jsvalue(aCx);
mArgsHolder->Read(xpc::CurrentNativeGlobal(aCx), aCx, &jsvalue, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
nsresult rv2 =
ExtensionAPIRequestForwarder::JSArrayToSequence(aCx, jsvalue, aArgs);
if (NS_FAILED(rv2)) {
aRv.Throw(rv2);
}
}
bool ExtensionListenerCallWorkerRunnable::WorkerRun(
JSContext* aCx, dom::WorkerPrivate* aWorkerPrivate) {
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate);
auto global = mListener->GetGlobalObject();
if (NS_WARN_IF(!global)) {
return true;
}
auto fn = mListener->GetCallback();
if (NS_WARN_IF(!fn)) {
return true;
}
IgnoredErrorResult rv;
dom::Sequence<JS::Value> argsSequence;
dom::SequenceRooter<JS::Value> arguments(aCx, &argsSequence);
DeserializeCallArguments(aCx, argsSequence, rv);
if (NS_WARN_IF(rv.Failed())) {
return true;
}
RefPtr<dom::Promise> retPromise;
RefPtr<dom::StrongWorkerRef> workerRef;
retPromise = dom::Promise::Create(global, rv);
if (retPromise) {
workerRef = dom::StrongWorkerRef::Create(
aWorkerPrivate, "ExtensionListenerCallWorkerRunnable", []() {});
}
if (NS_WARN_IF(rv.Failed() || !workerRef)) {
auto rejectMainThreadPromise =
[error = rv.Failed() ? rv.StealNSResult() : NS_ERROR_UNEXPECTED,
promiseResult = std::move(mPromiseResult)]() {
// TODO(rpl): this seems to be currently rejecting an error object
// without a stack trace, its a corner case but we may look into
// improve this error.
promiseResult->MaybeReject(error);
};
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableFunction(__func__, std::move(rejectMainThreadPromise));
NS_DispatchToMainThread(runnable);
JS_ClearPendingException(aCx);
return true;
}
ExtensionListenerCallPromiseResultHandler::Create(
retPromise, this, new dom::ThreadSafeWorkerRef(workerRef));
// Translate the first parameter into the API object type (e.g. an
// ExtensionPort), the content of the original argument value is expected to
// be a dictionary that is valid as an internal descriptor for that API object
// type.
if (mAPIObjectType != APIObjectType::NONE) {
IgnoredErrorResult rv;
// The api object descriptor is expected to have been prepended to the
// other arguments, assert here that the argsSequence does contain at least
// one element.
MOZ_ASSERT(!argsSequence.IsEmpty());
JS::Rooted<JS::Value> apiObjectDescriptor(aCx, argsSequence.ElementAt(0));
JS::Rooted<JS::Value> apiObjectValue(aCx);
// We only expect the object type to be RUNTIME_PORT at the moment,
// until we will need to expect it to support other object types that
// some specific API may need.
MOZ_ASSERT(mAPIObjectType == APIObjectType::RUNTIME_PORT);
RefPtr<ExtensionPort> port =
ExtensionPort::Create(global, apiObjectDescriptor, rv);
if (NS_WARN_IF(rv.Failed())) {
retPromise->MaybeReject(rv.StealNSResult());
return true;
}
if (NS_WARN_IF(!dom::ToJSValue(aCx, port, &apiObjectValue))) {
retPromise->MaybeReject(NS_ERROR_UNEXPECTED);
return true;
}
argsSequence.ReplaceElementAt(0, apiObjectValue);
}
// Create callback argument and append it to the call arguments.
JS::Rooted<JSObject*> sendResponseObj(aCx);
switch (mCallbackArgType) {
case CallbackType::CALLBACK_NONE:
break;
case CallbackType::CALLBACK_SEND_RESPONSE: {
JS::Rooted<JSFunction*> sendResponseFn(
aCx, js::NewFunctionWithReserved(aCx, SendResponseCallback::Call,
/* nargs */ 1, 0, "sendResponse"));
sendResponseObj = JS_GetFunctionObject(sendResponseFn);
JS::RootedValue sendResponseValue(aCx, JS::ObjectValue(*sendResponseObj));
// Create a SendResponseCallback instance that keeps a reference
// to the promise to resolve when the static SendReponseCallback::Call
// is being called.
// the SendReponseCallback instance from the resolved slot to resolve
// the promise and invalidated the sendResponse callback (any new call
// becomes a noop).
RefPtr<SendResponseCallback> sendResponsePtr =
SendResponseCallback::Create(global, retPromise, sendResponseValue,
rv);
if (NS_WARN_IF(rv.Failed())) {
retPromise->MaybeReject(NS_ERROR_UNEXPECTED);
return true;
}
// Store the SendResponseCallback instance in a private value set on the
// function object reserved slot, where ehe SendResponseCallback::Call
// static function will get it back to resolve the related promise
// and then invalidate the sendResponse callback (any new call
// becomes a noop).
js::SetFunctionNativeReserved(sendResponseObj,
SLOT_SEND_RESPONSE_CALLBACK_INSTANCE,
JS::PrivateValue(sendResponsePtr));
if (NS_WARN_IF(
!argsSequence.AppendElement(sendResponseValue, fallible))) {
retPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
return true;
}
break;
}
default:
MOZ_ASSERT_UNREACHABLE("Unexpected callbackType");
break;
}
// TODO: should `nsAutoMicroTask mt;` be used here?
dom::AutoEntryScript aes(global, "WebExtensionAPIEvent");
JS::Rooted<JS::Value> retval(aCx);
ErrorResult erv;
erv.MightThrowJSException();
MOZ_KnownLive(fn)->Call(argsSequence, &retval, erv, "WebExtensionAPIEvent",
dom::Function::eRethrowExceptions);
// Calling the callback may have thrown an exception.
// TODO: add a ListenerCallOptions to optionally report the exception
// instead of forwarding it to the caller.
erv.WouldReportJSException();
if (erv.Failed()) {
retPromise->MaybeReject(std::move(erv));
return true;
}
// Custom return value handling logic for events that do pass a
// sendResponse callback parameter (see expected behavior
// for the runtime.onMessage sendResponse parameter on MDN:
// https://mzl.la/3dokpMi):
//
// - listener returns Boolean true => the extension listener is
// expected to call sendResponse callback parameter asynchronosuly
// - listener return a Promise object => the promise is the listener
// response
// - listener return any other value => the listener didn't handle the
// event and the return value is ignored
//
if (mCallbackArgType == CallbackType::CALLBACK_SEND_RESPONSE) {
if (retval.isBoolean() && retval.isTrue()) {
// The listener returned `true` and so the promise relate to the
// listener call will be resolved once the extension will call
// the sendResponce function passed as a callback argument.
return true;
}
// If the retval isn't true and it is not a Promise object,
// the listener isn't handling the event, and we resolve the
// promise with undefined (if the listener didn't reply already
// by calling sendResponse synchronsouly).
// undefined (
if (!ExtensionEventListener::IsPromise(aCx, retval)) {
// Mark this listener call as cancelled, ExtensionListenerCallPromiseResult
// will check to know that it should release the main thread promise without
// resolving it.
//
// TODO: double-check if we should also cancel rejecting the promise returned by
// mozIExtensionEventListener.callListener when the listener call throws (by
// comparing it with the behavior on the current privileged-based API implementation).
mIsCallResultCancelled = true;
retPromise->MaybeResolveWithUndefined();
// Invalidate the sendResponse function by setting the private
// value where the SendResponseCallback instance was stored
// to a nullptr.
js::SetFunctionNativeReserved(sendResponseObj,
SLOT_SEND_RESPONSE_CALLBACK_INSTANCE,
JS::PrivateValue(nullptr));
return true;
}
}
retPromise->MaybeResolve(retval);
return true;
}
// ExtensionListenerCallPromiseResultHandler
NS_IMPL_ISUPPORTS0(ExtensionListenerCallPromiseResultHandler)
// static
void ExtensionListenerCallPromiseResultHandler::Create(
const RefPtr<dom::Promise>& aPromise,
const RefPtr<ExtensionListenerCallWorkerRunnable>& aWorkerRunnable,
dom::ThreadSafeWorkerRef* aWorkerRef) {
MOZ_ASSERT(aPromise);
MOZ_ASSERT(aWorkerRef);
MOZ_ASSERT(aWorkerRef->Private()->IsOnCurrentThread());
RefPtr<ExtensionListenerCallPromiseResultHandler> handler =
new ExtensionListenerCallPromiseResultHandler(aWorkerRef,
aWorkerRunnable);
aPromise->AppendNativeHandler(handler);
}
void ExtensionListenerCallPromiseResultHandler::WorkerRunCallback(
JSContext* aCx, JS::Handle<JS::Value> aValue,
PromiseCallbackType aCallbackType) {
MOZ_ASSERT(mWorkerRef);
mWorkerRef->Private()->AssertIsOnWorkerThread();
// The listener call was cancelled (e.g. when a runtime.onMessage listener
// returned false), release resources associated with this promise handler
// on the main thread without resolving the promise associated to the
// extension event listener call.
if (mWorkerRunnable->IsCallResultCancelled()) {
auto releaseMainThreadPromise = [runnable = std::move(mWorkerRunnable),
workerRef = std::move(mWorkerRef)]() {};
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableFunction(__func__, std::move(releaseMainThreadPromise));
NS_DispatchToMainThread(runnable);
return;
}
JS::RootedValue retval(aCx, aValue);
if (retval.isObject()) {
// Try to serialize the result as an ClonedErrorHolder,
// in case the value is an Error object.
IgnoredErrorResult rv;
JS::Rooted<JSObject*> errObj(aCx, &retval.toObject());
RefPtr<dom::ClonedErrorHolder> ceh =
dom::ClonedErrorHolder::Create(aCx, errObj, rv);
if (!rv.Failed() && ceh) {
JS::RootedObject obj(aCx);
// Note: `ToJSValue` cannot be used because ClonedErrorHolder isn't
// wrapped cached.
Unused << NS_WARN_IF(!ceh->WrapObject(aCx, nullptr, &obj));
retval.setObject(*obj);
}
}
UniquePtr<dom::StructuredCloneHolder> resHolder =
MakeUnique<dom::StructuredCloneHolder>(
dom::StructuredCloneHolder::CloningSupported,
dom::StructuredCloneHolder::TransferringNotSupported,
JS::StructuredCloneScope::SameProcess);
IgnoredErrorResult erv;
resHolder->Write(aCx, retval, erv);
// Failed to serialize the result, dispatch a runnable to reject
// the promise returned to the caller of the mozIExtensionCallback
// callWithPromiseResult method.
if (NS_WARN_IF(erv.Failed())) {
auto rejectMainThreadPromise = [error = erv.StealNSResult(),
runnable = std::move(mWorkerRunnable),
resHolder = std::move(resHolder)]() {
RefPtr<dom::Promise> promiseResult = std::move(runnable->mPromiseResult);
promiseResult->MaybeReject(error);
};
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableFunction(__func__, std::move(rejectMainThreadPromise));
NS_DispatchToMainThread(runnable);
JS_ClearPendingException(aCx);
return;
}
auto resolveMainThreadPromise = [callbackType = aCallbackType,
resHolder = std::move(resHolder),
runnable = std::move(mWorkerRunnable),
workerRef = std::move(mWorkerRef)]() {
RefPtr<dom::Promise> promiseResult = std::move(runnable->mPromiseResult);
auto* global = promiseResult->GetGlobalObject();
dom::AutoEntryScript aes(global,
"ExtensionListenerCallWorkerRunnable::WorkerRun");
JSContext* cx = aes.cx();
JS::Rooted<JS::Value> jsvalue(cx);
IgnoredErrorResult rv;
{
// Set the active worker principal while reading the result,
// needed to be sure to be able to successfully deserialize the
// SavedFrame part of a ClonedErrorHolder (in case that was the
// result stored in the StructuredCloneHolder).
Maybe<nsJSPrincipals::AutoSetActiveWorkerPrincipal> set;
if (workerRef) {
set.emplace(workerRef->Private()->GetPrincipal());
}
resHolder->Read(global, cx, &jsvalue, rv);
}
if (NS_WARN_IF(rv.Failed())) {
promiseResult->MaybeReject(rv.StealNSResult());
JS_ClearPendingException(cx);
} else {
switch (callbackType) {
case PromiseCallbackType::Resolve:
promiseResult->MaybeResolve(jsvalue);
break;
case PromiseCallbackType::Reject:
promiseResult->MaybeReject(jsvalue);
break;
}
}
};
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableFunction(__func__, std::move(resolveMainThreadPromise));
NS_DispatchToMainThread(runnable);
}
void ExtensionListenerCallPromiseResultHandler::ResolvedCallback(
JSContext* aCx, JS::Handle<JS::Value> aValue) {
WorkerRunCallback(aCx, aValue, PromiseCallbackType::Resolve);
}
void ExtensionListenerCallPromiseResultHandler::RejectedCallback(
JSContext* aCx, JS::Handle<JS::Value> aValue) {
WorkerRunCallback(aCx, aValue, PromiseCallbackType::Reject);
}
} // namespace extensions
} // namespace mozilla

Просмотреть файл

@ -1,217 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_extensions_ExtensionEventListener_h
#define mozilla_extensions_ExtensionEventListener_h
#include "js/Promise.h" // JS::IsPromiseObject
#include "mozIExtensionAPIRequestHandling.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/StructuredCloneHolder.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/dom/WorkerPrivate.h"
class nsIGlobalObject;
namespace mozilla {
namespace dom {
class Function;
} // namespace dom
namespace extensions {
#define SLOT_SEND_RESPONSE_CALLBACK_INSTANCE 0
// A class that represents a callback parameter passed to WebExtensions API
// addListener / removeListener methods.
//
// Instances of this class are sent to the mozIExtensionAPIRequestHandler as
// a property of the mozIExtensionAPIRequest.
//
// The mozIExtensionEventListener xpcom interface provides methods that allow
// the mozIExtensionAPIRequestHandler running in the Main Thread to call the
// underlying callback Function on its owning thread.
class ExtensionEventListener final : public mozIExtensionEventListener {
public:
NS_DECL_MOZIEXTENSIONEVENTLISTENER
NS_DECL_THREADSAFE_ISUPPORTS
using CleanupCallback = std::function<void()>;
using ListenerCallOptions = mozIExtensionListenerCallOptions;
using APIObjectType = ListenerCallOptions::APIObjectType;
using CallbackType = ListenerCallOptions::CallbackType;
static already_AddRefed<ExtensionEventListener> Create(
nsIGlobalObject* aGlobal, dom::Function* aCallback,
CleanupCallback&& aCleanupCallback, ErrorResult& aRv);
static bool IsPromise(JSContext* aCx, JS::Handle<JS::Value> aValue) {
if (!aValue.isObject()) {
return false;
}
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
return JS::IsPromiseObject(obj);
}
dom::WorkerPrivate* GetWorkerPrivate() const;
RefPtr<dom::Function> GetCallback() const { return mCallback; }
nsCOMPtr<nsIGlobalObject> GetGlobalObject() const {
nsCOMPtr<nsIGlobalObject> global = do_QueryReferent(mGlobal);
return global;
}
void Cleanup() {
if (mWorkerRef) {
MutexAutoLock lock(mMutex);
mWorkerRef->Private()->AssertIsOnWorkerThread();
mWorkerRef = nullptr;
}
mGlobal = nullptr;
mCallback = nullptr;
}
private:
ExtensionEventListener(nsIGlobalObject* aGlobal, dom::Function* aCallback)
: mGlobal(do_GetWeakReference(aGlobal)),
mCallback(aCallback),
mMutex("ExtensionEventListener::mMutex"){};
static UniquePtr<dom::StructuredCloneHolder> SerializeCallArguments(
const nsTArray<JS::Value>& aArgs, JSContext* aCx, ErrorResult& aRv);
~ExtensionEventListener() { Cleanup(); };
// Accessed on the main and on the owning threads.
RefPtr<dom::ThreadSafeWorkerRef> mWorkerRef;
// Accessed only on the owning thread.
nsWeakPtr mGlobal;
RefPtr<dom::Function> mCallback;
// Used to make sure we are not going to release the
// instance on the worker thread, while we are in the
// process of forwarding a call from the main thread.
Mutex mMutex;
};
// A WorkerRunnable subclass used to call an ExtensionEventListener
// in the thread that owns the dom::Function wrapped by the
// ExtensionEventListener class.
class ExtensionListenerCallWorkerRunnable : public dom::WorkerRunnable {
friend class ExtensionListenerCallPromiseResultHandler;
public:
using ListenerCallOptions = mozIExtensionListenerCallOptions;
using APIObjectType = ListenerCallOptions::APIObjectType;
using CallbackType = ListenerCallOptions::CallbackType;
ExtensionListenerCallWorkerRunnable(
const RefPtr<ExtensionEventListener>& aExtensionEventListener,
UniquePtr<dom::StructuredCloneHolder> aArgsHolder,
ListenerCallOptions* aCallOptions,
RefPtr<dom::Promise> aPromiseRetval = nullptr)
: WorkerRunnable(aExtensionEventListener->GetWorkerPrivate(),
WorkerThreadUnchangedBusyCount),
mListener(aExtensionEventListener),
mArgsHolder(std::move(aArgsHolder)),
mPromiseResult(std::move(aPromiseRetval)),
mAPIObjectType(APIObjectType::NONE),
mCallbackArgType(CallbackType::CALLBACK_NONE) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aExtensionEventListener);
if (aCallOptions) {
aCallOptions->GetApiObjectType(&mAPIObjectType);
aCallOptions->GetCallbackType(&mCallbackArgType);
}
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY
bool WorkerRun(JSContext* aCx, dom::WorkerPrivate* aWorkerPrivate) override;
bool IsCallResultCancelled() {
return mIsCallResultCancelled;
}
private:
~ExtensionListenerCallWorkerRunnable() {
NS_ReleaseOnMainThread(mPromiseResult.forget());
ReleaseArgsHolder();
mListener = nullptr;
}
void ReleaseArgsHolder() {
if (NS_IsMainThread()) {
mArgsHolder = nullptr;
} else {
auto releaseArgsHolder = [argsHolder = std::move(mArgsHolder)]() {};
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableFunction(__func__, std::move(releaseArgsHolder));
NS_DispatchToMainThread(runnable);
}
}
void DeserializeCallArguments(JSContext* aCx, dom::Sequence<JS::Value>& aArg,
ErrorResult& aRv);
RefPtr<ExtensionEventListener> mListener;
UniquePtr<dom::StructuredCloneHolder> mArgsHolder;
RefPtr<dom::Promise> mPromiseResult;
bool mIsCallResultCancelled = false;
// Call Options.
APIObjectType mAPIObjectType;
CallbackType mCallbackArgType;
};
// A class attached to the promise that should be resolved once the extension
// event listener call has been handled, responsible for serializing resolved
// values or rejected errors on the listener's owning thread and sending them to
// the extension event listener caller running on the main thread.
class ExtensionListenerCallPromiseResultHandler
: public dom::PromiseNativeHandler {
public:
NS_DECL_THREADSAFE_ISUPPORTS
static void Create(
const RefPtr<dom::Promise>& aPromise,
const RefPtr<ExtensionListenerCallWorkerRunnable>& aWorkerRunnable,
dom::ThreadSafeWorkerRef* aWorkerRef);
// PromiseNativeHandler
void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
enum class PromiseCallbackType { Resolve, Reject };
private:
ExtensionListenerCallPromiseResultHandler(
dom::ThreadSafeWorkerRef* aWorkerRef,
RefPtr<ExtensionListenerCallWorkerRunnable> aWorkerRunnable)
: mWorkerRef(aWorkerRef), mWorkerRunnable(std::move(aWorkerRunnable)) {}
~ExtensionListenerCallPromiseResultHandler() = default;
void WorkerRunCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
PromiseCallbackType aCallbackType);
// Set and accessed only on the owning worker thread.
RefPtr<dom::ThreadSafeWorkerRef> mWorkerRef;
// Reference to the runnable created on and owned by the main thread,
// accessed on the worker thread and released on the owning thread.
RefPtr<ExtensionListenerCallWorkerRunnable> mWorkerRunnable;
};
} // namespace extensions
} // namespace mozilla
#endif // mozilla_extensions_ExtensionEventListener_h

Просмотреть файл

@ -1,155 +0,0 @@
/* 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 "ExtensionEventManager.h"
#include "mozilla/dom/ExtensionEventManagerBinding.h"
#include "nsIGlobalObject.h"
#include "ExtensionEventListener.h"
namespace mozilla {
namespace extensions {
NS_IMPL_CYCLE_COLLECTION_CLASS(ExtensionEventManager);
NS_IMPL_CYCLE_COLLECTING_ADDREF(ExtensionEventManager);
NS_IMPL_CYCLE_COLLECTING_RELEASE(ExtensionEventManager)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ExtensionEventManager)
tmp->mListeners.clear();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ExtensionEventManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ExtensionEventManager)
for (auto iter = tmp->mListeners.iter(); !iter.done(); iter.next()) {
aCallbacks.Trace(&iter.get().mutableKey(), "mListeners key", aClosure);
}
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionEventManager)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
ExtensionEventManager::ExtensionEventManager(nsIGlobalObject* aGlobal,
const nsAString& aNamespace,
const nsAString& aEventName,
const nsAString& aObjectType,
const nsAString& aObjectId)
: mGlobal(aGlobal),
mAPINamespace(aNamespace),
mEventName(aEventName),
mAPIObjectType(aObjectType),
mAPIObjectId(aObjectId) {
MOZ_DIAGNOSTIC_ASSERT(mGlobal);
RefPtr<ExtensionEventManager> self = this;
mozilla::HoldJSObjects(this);
}
ExtensionEventManager::~ExtensionEventManager() {
ReleaseListeners();
mozilla::DropJSObjects(this);
};
void ExtensionEventManager::ReleaseListeners() {
if (mListeners.empty()) {
return;
}
for (auto iter = mListeners.iter(); !iter.done(); iter.next()) {
iter.get().value()->Cleanup();
}
mListeners.clear();
}
JSObject* ExtensionEventManager::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return dom::ExtensionEventManager_Binding::Wrap(aCx, this, aGivenProto);
}
nsIGlobalObject* ExtensionEventManager::GetParentObject() const {
return mGlobal;
}
void ExtensionEventManager::AddListener(
JSContext* aCx, dom::Function& aCallback,
const dom::Optional<JS::Handle<JSObject*>>& aOptions, ErrorResult& aRv) {
auto* cb = aCallback.CallbackOrNull();
if (cb == nullptr) {
ThrowUnexpectedError(aCx, aRv);
return;
}
RefPtr<ExtensionEventManager> self = this;
IgnoredErrorResult rv;
RefPtr<ExtensionEventListener> wrappedCb = ExtensionEventListener::Create(
mGlobal, &aCallback,
[self = std::move(self)]() { self->ReleaseListeners(); }, rv);
if (NS_WARN_IF(rv.Failed())) {
ThrowUnexpectedError(aCx, aRv);
return;
}
RefPtr<ExtensionEventListener> storedWrapper = wrappedCb;
if (!mListeners.put(cb, std::move(storedWrapper))) {
ThrowUnexpectedError(aCx, aRv);
return;
}
auto request = SendAddListener(mEventName);
request->Run(mGlobal, aCx, {}, wrappedCb, aRv);
}
void ExtensionEventManager::RemoveListener(dom::Function& aCallback,
ErrorResult& aRv) {
auto* cb = aCallback.CallbackOrNull();
const auto& ptr = mListeners.lookup(cb);
// Return earlier if the listener wasn't found
if (!ptr) {
return;
}
RefPtr<ExtensionEventListener> wrappedCb = ptr->value();
dom::AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
JSContext* cx = jsapi.cx();
auto request = SendRemoveListener(mEventName);
request->Run(mGlobal, cx, {}, wrappedCb, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
mListeners.remove(cb);
wrappedCb->Cleanup();
}
bool ExtensionEventManager::HasListener(dom::Function& aCallback,
ErrorResult& aRv) const {
return mListeners.has(aCallback.CallbackOrNull());
}
bool ExtensionEventManager::HasListeners(ErrorResult& aRv) const {
return !mListeners.empty();
}
} // namespace extensions
} // namespace mozilla

Просмотреть файл

@ -1,93 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_extensions_ExtensionEventManager_h
#define mozilla_extensions_ExtensionEventManager_h
#include "js/GCHashTable.h" // for JS::GCHashMap
#include "js/TypeDecls.h" // for JS::Handle, JSContext, JSObject, ...
#include "mozilla/Attributes.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/ErrorResult.h"
#include "nsCycleCollectionParticipant.h"
#include "nsCOMPtr.h"
#include "nsISupports.h"
#include "nsPointerHashKeys.h"
#include "nsRefPtrHashtable.h"
#include "nsWrapperCache.h"
#include "ExtensionAPIBase.h"
class nsIGlobalObject;
namespace mozilla {
namespace dom {
class Function;
} // namespace dom
namespace extensions {
class ExtensionBrowser;
class ExtensionEventListener;
class ExtensionEventManager final : public nsISupports,
public nsWrapperCache,
public ExtensionAPIBase {
nsCOMPtr<nsIGlobalObject> mGlobal;
nsString mAPINamespace;
nsString mEventName;
nsString mAPIObjectType;
nsString mAPIObjectId;
using ListenerWrappersMap =
JS::GCHashMap<JS::Heap<JSObject*>, RefPtr<ExtensionEventListener>,
js::MovableCellHasher<JS::Heap<JSObject*>>,
js::SystemAllocPolicy>;
ListenerWrappersMap mListeners;
~ExtensionEventManager();
void ReleaseListeners();
protected:
// ExtensionAPIBase methods
nsIGlobalObject* GetGlobalObject() const override { return mGlobal; }
nsString GetAPINamespace() const override { return mAPINamespace; }
nsString GetAPIObjectType() const override { return mAPIObjectType; }
nsString GetAPIObjectId() const override { return mAPIObjectId; }
public:
ExtensionEventManager(nsIGlobalObject* aGlobal, const nsAString& aNamespace,
const nsAString& aEventName,
const nsAString& aObjectType = VoidString(),
const nsAString& aObjectId = VoidString());
// nsWrapperCache interface methods
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
nsIGlobalObject* GetParentObject() const;
bool HasListener(dom::Function& aCallback, ErrorResult& aRv) const;
bool HasListeners(ErrorResult& aRv) const;
void AddListener(JSContext* aCx, dom::Function& aCallback,
const dom::Optional<JS::Handle<JSObject*>>& aOptions,
ErrorResult& aRv);
void RemoveListener(dom::Function& aCallback, ErrorResult& aRv);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ExtensionEventManager)
};
} // namespace extensions
} // namespace mozilla
#endif // mozilla_extensions_ExtensionEventManager_h

Просмотреть файл

@ -1,113 +0,0 @@
/* 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 "ExtensionMockAPI.h"
#include "ExtensionEventManager.h"
#include "mozilla/dom/ExtensionMockAPIBinding.h"
#include "mozilla/extensions/ExtensionPort.h"
#include "nsIGlobalObject.h"
namespace mozilla {
namespace extensions {
NS_IMPL_CYCLE_COLLECTING_ADDREF(ExtensionMockAPI);
NS_IMPL_CYCLE_COLLECTING_RELEASE(ExtensionMockAPI)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ExtensionMockAPI, mGlobal,
mOnTestEventMgr);
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionMockAPI)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
ExtensionMockAPI::ExtensionMockAPI(nsIGlobalObject* aGlobal,
ExtensionBrowser* aExtensionBrowser)
: mGlobal(aGlobal) {
MOZ_DIAGNOSTIC_ASSERT(mGlobal);
}
/* static */
bool ExtensionMockAPI::IsAllowed(JSContext* aCx, JSObject* aGlobal) {
return true;
}
JSObject* ExtensionMockAPI::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return dom::ExtensionMockAPI_Binding::Wrap(aCx, this, aGivenProto);
}
nsIGlobalObject* ExtensionMockAPI::GetParentObject() const { return mGlobal; }
void ExtensionMockAPI::GetPropertyAsErrorObject(
JSContext* aCx, JS::MutableHandle<JS::Value> aRetval) {
IgnoredErrorResult rv;
RefPtr<ExtensionAPIGetProperty> request =
GetProperty(u"propertyAsErrorObject"_ns);
request->Run(mGlobal, aCx, aRetval, rv);
if (rv.Failed()) {
NS_WARNING("ExtensionMockAPI::GetPropertyAsErrorObject failure");
return;
}
}
void ExtensionMockAPI::GetPropertyAsString(DOMString& aRetval) {
IgnoredErrorResult rv;
dom::AutoJSAPI jsapi;
if (!jsapi.Init(GetParentObject())) {
NS_WARNING("ExtensionMockAPI::GetPropertyAsId fail to init jsapi");
}
JSContext* cx = jsapi.cx();
JS::RootedValue retval(cx);
RefPtr<ExtensionAPIGetProperty> request =
GetProperty(u"getPropertyAsString"_ns);
request->Run(mGlobal, cx, &retval, rv);
if (rv.Failed()) {
NS_WARNING("ExtensionMockAPI::GetPropertyAsString failure");
return;
}
nsAutoJSString strRetval;
if (!retval.isString() || !strRetval.init(cx, retval)) {
NS_WARNING("ExtensionMockAPI::GetPropertyAsString got a non string result");
return;
}
aRetval.SetKnownLiveString(strRetval);
}
ExtensionEventManager* ExtensionMockAPI::OnTestEvent() {
if (!mOnTestEventMgr) {
mOnTestEventMgr = CreateEventManager(u"onTestEvent"_ns);
}
return mOnTestEventMgr;
}
already_AddRefed<ExtensionPort> ExtensionMockAPI::CallWebExtMethodReturnsPort(
JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs, ErrorResult& aRv) {
JS::Rooted<JS::Value> apiResult(aCx);
auto request = CallSyncFunction(u"methodReturnsPort"_ns);
request->Run(mGlobal, aCx, aArgs, &apiResult, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
IgnoredErrorResult rv;
RefPtr<ExtensionPort> port = ExtensionPort::Create(mGlobal, apiResult, rv);
if (NS_WARN_IF(rv.Failed())) {
// ExtensionPort::Create doesn't throw the js exception with the generic
// error message as the "api request forwarding" helper classes.
ThrowUnexpectedError(aCx, aRv);
return nullptr;
}
return port.forget();
}
} // namespace extensions
} // namespace mozilla

Просмотреть файл

@ -1,76 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_extensions_ExtensionMockAPI_h
#define mozilla_extensions_ExtensionMockAPI_h
#include "js/TypeDecls.h"
#include "mozilla/Attributes.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "nsCycleCollectionParticipant.h"
#include "nsCOMPtr.h"
#include "nsISupports.h"
#include "nsWrapperCache.h"
#include "ExtensionAPIBase.h"
#include "ExtensionBrowser.h"
class nsIGlobalObject;
namespace mozilla {
namespace extensions {
using dom::DOMString;
class ExtensionEventManager;
class ExtensionPort;
class ExtensionMockAPI final : public nsISupports,
public nsWrapperCache,
public ExtensionAPINamespace {
nsCOMPtr<nsIGlobalObject> mGlobal;
RefPtr<ExtensionEventManager> mOnTestEventMgr;
~ExtensionMockAPI() = default;
protected:
// ExtensionAPIBase methods
nsIGlobalObject* GetGlobalObject() const override { return mGlobal; }
nsString GetAPINamespace() const override { return u"mockExtensionAPI"_ns; }
public:
ExtensionMockAPI(nsIGlobalObject* aGlobal,
ExtensionBrowser* aExtensionBrowser);
// nsWrapperCache interface methods
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
// DOM bindings methods
static bool IsAllowed(JSContext* aCx, JSObject* aGlobal);
nsIGlobalObject* GetParentObject() const;
void GetPropertyAsErrorObject(JSContext* aCx,
JS::MutableHandle<JS::Value> aRetval);
void GetPropertyAsString(DOMString& aRetval);
already_AddRefed<ExtensionPort> CallWebExtMethodReturnsPort(
JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs, ErrorResult& aRv);
ExtensionEventManager* OnTestEvent();
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ExtensionMockAPI)
};
} // namespace extensions
} // namespace mozilla
#endif // mozilla_extensions_ExtensionMockAPI_h

Просмотреть файл

@ -1,89 +0,0 @@
/* 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 "ExtensionPort.h"
#include "ExtensionEventManager.h"
#include "mozilla/dom/BindingUtils.h" // SequenceRooter
#include "mozilla/dom/ExtensionPortBinding.h"
#include "mozilla/dom/ScriptSettings.h" // AutoEntryScript
#include "nsIGlobalObject.h"
namespace mozilla {
namespace extensions {
NS_IMPL_CYCLE_COLLECTING_ADDREF(ExtensionPort);
NS_IMPL_CYCLE_COLLECTING_RELEASE(ExtensionPort)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ExtensionPort, mGlobal,
mOnDisconnectEventMgr,
mOnMessageEventMgr);
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionPort)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
// static
already_AddRefed<ExtensionPort> ExtensionPort::Create(
nsIGlobalObject* aGlobal, JS::Handle<JS::Value> aDescriptorValue,
ErrorResult& aRv) {
if (!aDescriptorValue.isObject()) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
dom::AutoEntryScript aes(&aDescriptorValue.toObject(), __func__);
JSContext* acx = aes.cx();
auto portDescriptor = MakeUnique<dom::ExtensionPortDescriptor>();
if (!portDescriptor->Init(acx, aDescriptorValue, __func__)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
RefPtr<ExtensionPort> port =
new ExtensionPort(aGlobal, std::move(portDescriptor));
return port.forget();
}
ExtensionPort::ExtensionPort(
nsIGlobalObject* aGlobal,
UniquePtr<dom::ExtensionPortDescriptor> aPortDescriptor)
: mGlobal(aGlobal), mPortDescriptor(std::move(aPortDescriptor)) {
MOZ_DIAGNOSTIC_ASSERT(mGlobal);
}
nsString ExtensionPort::GetAPIObjectId() const {
return mPortDescriptor->mPortId;
}
JSObject* ExtensionPort::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return dom::ExtensionPort_Binding::Wrap(aCx, this, aGivenProto);
}
nsIGlobalObject* ExtensionPort::GetParentObject() const { return mGlobal; }
ExtensionEventManager* ExtensionPort::OnMessage() {
if (!mOnMessageEventMgr) {
mOnMessageEventMgr = CreateEventManager(u"onMessage"_ns);
}
return mOnMessageEventMgr;
}
ExtensionEventManager* ExtensionPort::OnDisconnect() {
if (!mOnDisconnectEventMgr) {
mOnDisconnectEventMgr = CreateEventManager(u"onDisconnect"_ns);
}
return mOnDisconnectEventMgr;
}
void ExtensionPort::GetName(nsAString& aString) {
aString.Assign(mPortDescriptor->mName);
}
} // namespace extensions
} // namespace mozilla

Просмотреть файл

@ -1,87 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_extensions_ExtensionPort_h
#define mozilla_extensions_ExtensionPort_h
#include "js/TypeDecls.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "nsWrapperCache.h"
#include "ExtensionAPIBase.h"
class nsIGlobalObject;
namespace mozilla {
class ErrorResult;
namespace dom {
struct ExtensionPortDescriptor;
}
namespace extensions {
class ExtensionEventManager;
class ExtensionPort final : public nsISupports,
public nsWrapperCache,
public ExtensionAPIBase {
nsCOMPtr<nsIGlobalObject> mGlobal;
RefPtr<ExtensionEventManager> mOnDisconnectEventMgr;
RefPtr<ExtensionEventManager> mOnMessageEventMgr;
UniquePtr<dom::ExtensionPortDescriptor> mPortDescriptor;
~ExtensionPort() = default;
ExtensionPort(nsIGlobalObject* aGlobal,
UniquePtr<dom::ExtensionPortDescriptor> aPortDescriptor);
protected:
// ExtensionAPIBase methods
nsIGlobalObject* GetGlobalObject() const override { return mGlobal; }
nsString GetAPINamespace() const override { return u"runtime"_ns; }
nsString GetAPIObjectType() const override { return u"Port"_ns; }
nsString GetAPIObjectId() const override;
public:
static already_AddRefed<ExtensionPort> Create(
nsIGlobalObject* aGlobal, JS::Handle<JS::Value> aDescriptorValue,
ErrorResult& aRv);
// nsWrapperCache interface methods
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
nsIGlobalObject* GetParentObject() const;
ExtensionEventManager* OnDisconnect();
ExtensionEventManager* OnMessage();
void GetName(nsAString& aString);
void GetError(JSContext* aCx, JS::MutableHandle<JSObject*> aResult) {
// TODO: this is currently just a placeholder, should be filled in
// with the actual implementation (which may send to the API request
// handler an API request to get the property value from the port object
// representation that lives on the main thread).
}
void GetSender(JSContext* aCx, JS::MutableHandle<JSObject*> aResult) {
// TODO: this is currently just a placeholder, needed to please the
// webidl binding which excepts this property to always return
// an object.
aResult.set(JS_NewPlainObject(aCx));
};
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ExtensionPort)
};
} // namespace extensions
} // namespace mozilla
#endif // mozilla_extensions_ExtensionPort_h

Просмотреть файл

@ -1,46 +0,0 @@
# -*- 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 = ("WebExtensions", "General")
UNIFIED_SOURCES += [
"ExtensionAPIBase.cpp",
"ExtensionAPIRequest.cpp",
"ExtensionAPIRequestForwarder.cpp",
"ExtensionBrowser.cpp",
"ExtensionEventListener.cpp",
"ExtensionEventManager.cpp",
"ExtensionPort.cpp",
]
EXPORTS.mozilla.extensions += [
"ExtensionAPIBase.h",
"ExtensionBrowser.h",
"ExtensionEventManager.h",
"ExtensionPort.h",
]
# The following is not a real WebExtensions API, it is a test WebIDL
# interface that includes a collection of the cases useful to unit
# test the API request forwarding mechanism without tying it to
# a specific WebExtensions API.
UNIFIED_SOURCES += ["ExtensionMockAPI.cpp"]
EXPORTS.mozilla.extensions += ["ExtensionMockAPI.h"]
# Propagate the build config to be able to use it in souce code preprocessing
# (used in mozilla::extensions::ExtensionAPIAllowed to disable the webidl
# bindings in non-nightly builds).
if CONFIG["MOZ_WEBEXT_WEBIDL_ENABLED"]:
DEFINES["MOZ_WEBEXT_WEBIDL_ENABLED"] = True
include("/ipc/chromium/chromium-config.mozbuild")
LOCAL_INCLUDES += [
"/js/xpconnect/src",
]
FINAL_LIBRARY = "xul"

Просмотреть файл

@ -300,13 +300,6 @@ this.AppConstants = Object.freeze({
false,
#endif
MOZ_WEBEXT_WEBIDL_ENABLED:
#ifdef MOZ_WEBEXT_WEBIDL_ENABLED
true,
#else
false,
#endif
MENUBAR_CAN_AUTOHIDE:
#ifdef MENUBAR_CAN_AUTOHIDE
true,

Просмотреть файл

@ -301,7 +301,6 @@ for var in (
"MOZ_UNSIGNED_SYSTEM_SCOPE",
"MOZ_UPDATE_AGENT",
"MOZ_UPDATER",
"MOZ_WEBEXT_WEBIDL_ENABLED",
):
if CONFIG[var]:
DEFINES[var] = True

Просмотреть файл

@ -1486,31 +1486,6 @@ def addon_sideload_allowed(value):
set_config("MOZ_ALLOW_ADDON_SIDELOAD", addon_sideload_allowed)
# WebExtensions API WebIDL bindings
# ==============================================================
@depends(milestone)
def extensions_webidl_bindings_default(milestone):
# Only enable the webidl bindings for the WebExtensions APIs
# in Nightly.
return milestone.is_nightly
option(
"--enable-extensions-webidl-bindings",
default=extensions_webidl_bindings_default,
help="{Enable|Disable} building experimental WebExtensions WebIDL bindings",
)
@depends("--enable-extensions-webidl-bindings")
def extensions_webidl_enabled(value):
return bool(value)
set_config("MOZ_WEBEXT_WEBIDL_ENABLED", extensions_webidl_enabled)
# Launcher process (Windows only)
# ==============================================================