diff options
Diffstat (limited to 'toolkit/components/extensions/webidl-api/ExtensionAPIBase.cpp')
-rw-r--r-- | toolkit/components/extensions/webidl-api/ExtensionAPIBase.cpp | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/toolkit/components/extensions/webidl-api/ExtensionAPIBase.cpp b/toolkit/components/extensions/webidl-api/ExtensionAPIBase.cpp new file mode 100644 index 0000000000..503124a39a --- /dev/null +++ b/toolkit/components/extensions/webidl-api/ExtensionAPIBase.cpp @@ -0,0 +1,364 @@ +/* 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 |