зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 14 changesets (bug 1682632
) for causing hazard bustages in ExtensionEventManager.cpp
Backed out changeset c5acc19db606 (bug1682632
) Backed out changeset 61380029a38b (bug1682632
) Backed out changeset d3a153070b38 (bug1682632
) Backed out changeset 8b8bd2385503 (bug1682632
) Backed out changeset 7fa45afd83a0 (bug1682632
) Backed out changeset 57652a2152ac (bug1682632
) Backed out changeset 9195b13525d0 (bug1682632
) Backed out changeset a647c0cb85e4 (bug1682632
) Backed out changeset 55553e0dc6ca (bug1682632
) Backed out changeset c85363089c29 (bug1682632
) Backed out changeset 6c1f4efb4975 (bug1682632
) Backed out changeset 9452456d249f (bug1682632
) Backed out changeset 7b8016e5f3fb (bug1682632
) Backed out changeset ba742f7e256f (bug1682632
)
This commit is contained in:
Родитель
4a0c903748
Коммит
be06efd457
|
@ -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)
|
||||
# ==============================================================
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче