Files
tubestation/toolkit/components/extensions/webidl-api/ExtensionAPIBase.cpp
Luca Greco 1c908a6177 Bug 1688040 - part10.2: Return existing ExtensionPort instance based on the ExtensionPortDescriptor portId. r=baku
The extensions expect that ExtensionPort instances they get in the calls to the
port event listeners to always be the same, and to be also strictly equal to the
object port got from browser.runtime.connect or browser.runtime.onConnect:

```
const port = browser.runtime.connect();
port.onDisconnect.addListener(disconnectedPort => {
  // port === disconnectedPort => true
});
```

This patch does add an extension port lookup map in the ExtensionBrowser
class and a new ExtensionBrowser::GetPort method which is responsible of
providing the expected behavior (returning an existing istance if one is
found in the lookup map).

Differential Revision: https://phabricator.services.mozilla.com/D107554
2021-10-06 12:28:24 +00:00

351 lines
12 KiB
C++

/* 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 "ExtensionBrowser.h"
#include "ExtensionEventManager.h"
#include "ExtensionPort.h"
#include "mozilla/ConsoleReportCollector.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/SerializedStackHolder.h"
#include "mozilla/dom/FunctionBinding.h"
#include "js/CallAndConstruct.h" // JS::IsCallable
namespace mozilla {
namespace extensions {
// ChromeCompatCallbackHandler
NS_IMPL_ISUPPORTS0(ChromeCompatCallbackHandler)
// static
void ChromeCompatCallbackHandler::Create(
ExtensionBrowser* aExtensionBrowser, dom::Promise* aPromise,
const RefPtr<dom::Function>& aCallback) {
MOZ_ASSERT(aPromise);
MOZ_ASSERT(aExtensionBrowser);
MOZ_ASSERT(aCallback);
RefPtr<ChromeCompatCallbackHandler> handler =
new ChromeCompatCallbackHandler(aExtensionBrowser, 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.
mExtensionBrowser->SetLastError(aValue);
MOZ_KnownLive(mCallback)->Call({}, &retval, rv);
if (mExtensionBrowser->ClearLastError()) {
ReportUncheckedLastError(aCx, aValue);
}
}
void ChromeCompatCallbackHandler::ReportUncheckedLastError(
JSContext* aCx, JS::Handle<JS::Value> aValue) {
nsCString sourceSpec;
uint32_t line = 0;
uint32_t column = 0;
nsString valueString;
nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
valueString);
nsTArray<nsString> params;
params.AppendElement(valueString);
RefPtr<ConsoleReportCollector> reporter = new ConsoleReportCollector();
reporter->AddConsoleReport(nsIScriptError::errorFlag, "content javascript"_ns,
nsContentUtils::eDOM_PROPERTIES, sourceSpec, line,
column, "WebExtensionUncheckedLastError"_ns,
params);
dom::WorkerPrivate* workerPrivate = dom::GetWorkerPrivateFromContext(aCx);
RefPtr<Runnable> r = NS_NewRunnableFunction(
"ChromeCompatCallbackHandler::ReportUncheckedLastError",
[reporter]() { reporter->FlushReportsToConsole(0); });
workerPrivate->DispatchToMainThread(r.forget());
}
// 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::CallWebExtMethodReturnsString(
JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs, nsAString& aRetVal,
ErrorResult& aRv) {
JS::Rooted<JS::Value> retval(aCx);
auto request = CallSyncFunction(aApiMethod);
request->Run(GetGlobalObject(), aCx, aArgs, &retval, aRv);
if (aRv.Failed()) {
return;
}
if (NS_WARN_IF(!retval.isString())) {
ThrowUnexpectedError(aCx, aRv);
return;
}
nsAutoJSString str;
if (!str.init(aCx, retval.toString())) {
JS_ClearPendingException(aCx);
ThrowUnexpectedError(aCx, aRv);
return;
}
aRetVal = str;
}
already_AddRefed<ExtensionPort> ExtensionAPIBase::CallWebExtMethodReturnsPort(
JSContext* aCx, const nsAString& aApiMethod,
const dom::Sequence<JS::Value>& aArgs, ErrorResult& aRv) {
JS::Rooted<JS::Value> apiResult(aCx);
auto request = CallSyncFunction(aApiMethod);
request->Run(GetGlobalObject(), aCx, aArgs, &apiResult, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
IgnoredErrorResult rv;
auto* extensionBrowser = GetExtensionBrowser();
RefPtr<ExtensionPort> port = extensionBrowser->GetPort(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();
}
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(GetExtensionBrowser(), 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
void ExtensionAPIBase::GetWebExtPropertyAsString(const nsString& aPropertyName,
dom::DOMString& aRetval) {
IgnoredErrorResult rv;
dom::AutoJSAPI jsapi;
auto* global = GetGlobalObject();
if (!jsapi.Init(global)) {
NS_WARNING("GetWebExtPropertyAsString fail to init jsapi");
return;
}
JSContext* cx = jsapi.cx();
JS::RootedValue retval(cx);
RefPtr<ExtensionAPIGetProperty> request = GetProperty(aPropertyName);
request->Run(global, cx, &retval, rv);
if (rv.Failed()) {
NS_WARNING("GetWebExtPropertyAsString failure");
return;
}
nsAutoJSString strRetval;
if (!retval.isString() || !strRetval.init(cx, retval)) {
NS_WARNING("GetWebExtPropertyAsString got a non string result");
return;
}
aRetval.SetKnownLiveString(strRetval);
}
void ExtensionAPIBase::GetWebExtPropertyAsJSValue(
JSContext* aCx, const nsAString& aPropertyName,
JS::MutableHandle<JS::Value> aRetval) {
IgnoredErrorResult rv;
RefPtr<ExtensionAPIGetProperty> request = GetProperty(aPropertyName);
request->Run(GetGlobalObject(), aCx, aRetval, rv);
if (rv.Failed()) {
NS_WARNING("GetWebExtPropertyAsJSValue failure");
return;
}
}
already_AddRefed<ExtensionEventManager> ExtensionAPIBase::CreateEventManager(
const nsAString& aEventName) {
RefPtr<ExtensionEventManager> eventMgr = new ExtensionEventManager(
GetGlobalObject(), GetExtensionBrowser(), 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