/* 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& aCallback) { MOZ_ASSERT(aPromise); MOZ_ASSERT(aExtensionBrowser); MOZ_ASSERT(aCallback); RefPtr handler = new ChromeCompatCallbackHandler(aExtensionBrowser, aCallback); aPromise->AppendNativeHandler(handler); } void ChromeCompatCallbackHandler::ResolvedCallback(JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) { JS::Rooted retval(aCx); IgnoredErrorResult rv; MOZ_KnownLive(mCallback)->Call({aValue}, &retval, rv); } void ChromeCompatCallbackHandler::RejectedCallback(JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) { JS::Rooted 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 aValue) { nsCString sourceSpec; uint32_t line = 0; uint32_t column = 0; nsString valueString; nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column, valueString); nsTArray params; params.AppendElement(valueString); RefPtr 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 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& aArgs, ErrorResult& aRv) { aRv.ThrowNotSupportedError("Not implemented"); } void ExtensionAPIBase::CallWebExtMethodNotImplementedAsync( JSContext* aCx, const nsAString& aApiMethod, const dom::Sequence& aArgs, const dom::Optional>& aCallback, JS::MutableHandle aRetval, ErrorResult& aRv) { CallWebExtMethodNotImplementedNoReturn(aCx, aApiMethod, aArgs, aRv); } void ExtensionAPIBase::CallWebExtMethodNotImplemented( JSContext* aCx, const nsAString& aApiMethod, const dom::Sequence& aArgs, JS::MutableHandle aRetval, ErrorResult& aRv) { CallWebExtMethodNotImplementedNoReturn(aCx, aApiMethod, aArgs, aRv); } void ExtensionAPIBase::CallWebExtMethodNoReturn( JSContext* aCx, const nsAString& aApiMethod, const dom::Sequence& 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& aArgs, JS::MutableHandle 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& aArgs, nsAString& aRetVal, ErrorResult& aRv) { JS::Rooted 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 ExtensionAPIBase::CallWebExtMethodReturnsPort( JSContext* aCx, const nsAString& aApiMethod, const dom::Sequence& aArgs, ErrorResult& aRv) { JS::Rooted 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 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& aArgs, const RefPtr& aCallback, JS::MutableHandle aRetval, ErrorResult& aRv) { auto* global = GetGlobalObject(); IgnoredErrorResult erv; RefPtr 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& aArgs, const dom::Optional>& aCallback, JS::MutableHandle aRetval, ErrorResult& aRv) { RefPtr 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& aArgs, JS::MutableHandle aRetval, ErrorResult& aRv) { RefPtr chromeCompatCb; auto lastElement = aArgs.IsEmpty() ? JS::UndefinedValue() : aArgs.LastElement(); dom::Sequence callArgs(aArgs); if (lastElement.isObject() && JS::IsCallable(&lastElement.toObject())) { JS::Rooted tempRoot(aCx, &lastElement.toObject()); JS::Rooted 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 retval(cx); RefPtr 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 aRetval) { IgnoredErrorResult rv; RefPtr request = GetProperty(aPropertyName); request->Run(GetGlobalObject(), aCx, aRetval, rv); if (rv.Failed()) { NS_WARNING("GetWebExtPropertyAsJSValue failure"); return; } } already_AddRefed ExtensionAPIBase::CreateEventManager( const nsAString& aEventName) { RefPtr eventMgr = new ExtensionEventManager( GetGlobalObject(), GetExtensionBrowser(), GetAPINamespace(), aEventName, GetAPIObjectType(), GetAPIObjectId()); return eventMgr.forget(); } RefPtr ExtensionAPIBase::CallFunctionNoReturn( const nsAString& aApiMethod) { return new ExtensionAPICallFunctionNoReturn( GetAPINamespace(), aApiMethod, GetAPIObjectType(), GetAPIObjectId()); } RefPtr ExtensionAPIBase::CallSyncFunction( const nsAString& aApiMethod) { return new ExtensionAPICallSyncFunction(GetAPINamespace(), aApiMethod, GetAPIObjectType(), GetAPIObjectId()); } RefPtr ExtensionAPIBase::CallAsyncFunction( const nsAString& aApiMethod) { return new ExtensionAPICallAsyncFunction( GetAPINamespace(), aApiMethod, GetAPIObjectType(), GetAPIObjectId()); } RefPtr ExtensionAPIBase::GetProperty( const nsAString& aApiProperty) { return new ExtensionAPIGetProperty(GetAPINamespace(), aApiProperty, GetAPIObjectType(), GetAPIObjectId()); } RefPtr ExtensionAPIBase::SendAddListener( const nsAString& aEventName) { using EType = ExtensionAPIAddRemoveListener::EType; return new ExtensionAPIAddRemoveListener( EType::eAddListener, GetAPINamespace(), aEventName, GetAPIObjectType(), GetAPIObjectId()); } RefPtr 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