Files
tubestation/toolkit/components/extensions/webidl-api/ExtensionAPIRequestForwarder.cpp
Luca Greco 4900365b51 Bug 1724183 - Add ExtensionTest::AssertMatchInternal to provide the logic to be shared between AssertThrows and AssertRejects. r=baku,sfink
This patch introduce a new AssertMatchInternal to ExtensionTest, this method isn't exposed to
js callers through the webidl definition, it is meant to be used internally by the
AssertThrows and AssertRejects methods (which are instead both methods part of the ExtensionTest
webidl).

This patch also introduces small changes to ExtensionAPIRequestForwarder and RequestWorkerRunnable,
to allow AssertMatchInternal to accept as an optional parameter a serialized caller stack trace
(to be sent as part of the APIRequest forwarded from the worker thread to the main thread),
which is needed for AssertRejects (to include the strack trace retrieved when AssertRejects is
called, and then after the promise parameter has been rejected or resolved to send it as part
of the forwarded aPI request).

Differential Revision: https://phabricator.services.mozilla.com/D122033
2021-10-12 18:32:27 +00:00

630 lines
20 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 "ExtensionAPIRequestForwarder.h"
#include "ExtensionEventListener.h"
#include "js/Promise.h"
#include "js/PropertyAndElement.h" // JS_GetElement
#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::SetSerializedCallerStack(
UniquePtr<dom::SerializedStackHolder> aCallerStack) {
MOZ_ASSERT(dom::IsCurrentThreadRunningWorker());
MOZ_ASSERT(mStackHolder.isNothing());
mStackHolder = Some(std::move(aCallerStack));
}
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);
if (mStackHolder.isSome()) {
runnable->SetSerializedCallerStack(mStackHolder.extract());
}
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;
}
if (!mStackHolder.isSome()) {
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::SetSerializedCallerStack(
UniquePtr<dom::SerializedStackHolder> aCallerStack) {
MOZ_ASSERT(dom::IsCurrentThreadRunningWorker());
MOZ_ASSERT(mStackHolder.isNothing());
mStackHolder = Some(std::move(aCallerStack));
}
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));
MOZ_ASSERT(savedFrame);
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);
RefPtr<dom::Promise> retPromise =
dom::Promise::Resolve(glob, aCx, aRetval, rv);
if (rv.Failed()) {
return false;
}
retPromise->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