364 lines
13 KiB
C++
364 lines
13 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 "ExtensionSetting.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,
|
|
ErrorResult& aRv) {
|
|
JS::Rooted<JS::Value> retval(aCx);
|
|
IgnoredErrorResult rv;
|
|
MOZ_KnownLive(mCallback)->Call({aValue}, &retval, rv);
|
|
}
|
|
|
|
void ChromeCompatCallbackHandler::RejectedCallback(JSContext* aCx,
|
|
JS::Handle<JS::Value> aValue,
|
|
ErrorResult& aRv) {
|
|
JS::Rooted<JS::Value> 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::Rooted<JS::Value> 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();
|
|
}
|
|
|
|
already_AddRefed<ExtensionSetting> ExtensionAPIBase::CreateSetting(
|
|
const nsAString& aSettingName) {
|
|
nsAutoString settingAPIPath;
|
|
settingAPIPath.Append(GetAPINamespace());
|
|
settingAPIPath.AppendLiteral(".");
|
|
settingAPIPath.Append(aSettingName);
|
|
RefPtr<ExtensionSetting> settingAPI = new ExtensionSetting(
|
|
GetGlobalObject(), GetExtensionBrowser(), settingAPIPath);
|
|
return settingAPI.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
|