fune/toolkit/components/extensions/webidl-api/ExtensionEventManager.cpp
Luca Greco 1d595d732c Bug 1728327 - Track ExtensionEventListener a background service worker registered by the time it is fully loaded. r=baku
This patch includes a proposed approach to keep track of the WebExtensions API event listeners
subscribed synchronously while the background service worker script was being loaded and executed,
because this are the listeners that we can assume to be available right after we spawn a worker
and be able to handle the API event that triggered the worker to be spawned.

The information about the "listeners subscribed synchronously while the worker script is being
loaded and executed" is then used by the ExtensionBrowser::HasWakeupEventListener method,
which will be called as part of handling the nsIServiceWorkerManager.wakeForExtensionAPIEvent
call.

Differential Revision: https://phabricator.services.mozilla.com/D130758
2021-12-15 18:29:37 +00:00

166 lines
5 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 "ExtensionEventManager.h"
#include "mozilla/dom/ExtensionEventManagerBinding.h"
#include "nsIGlobalObject.h"
#include "ExtensionEventListener.h"
namespace mozilla {
namespace extensions {
NS_IMPL_CYCLE_COLLECTION_CLASS(ExtensionEventManager);
NS_IMPL_CYCLE_COLLECTING_ADDREF(ExtensionEventManager);
NS_IMPL_CYCLE_COLLECTING_RELEASE(ExtensionEventManager)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ExtensionEventManager)
tmp->mListeners.clear();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mExtensionBrowser)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ExtensionEventManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExtensionBrowser)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ExtensionEventManager)
for (auto iter = tmp->mListeners.iter(); !iter.done(); iter.next()) {
aCallbacks.Trace(&iter.get().mutableKey(), "mListeners key", aClosure);
}
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionEventManager)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
ExtensionEventManager::ExtensionEventManager(
nsIGlobalObject* aGlobal, ExtensionBrowser* aExtensionBrowser,
const nsAString& aNamespace, const nsAString& aEventName,
const nsAString& aObjectType, const nsAString& aObjectId)
: mGlobal(aGlobal),
mExtensionBrowser(aExtensionBrowser),
mAPINamespace(aNamespace),
mEventName(aEventName),
mAPIObjectType(aObjectType),
mAPIObjectId(aObjectId) {
MOZ_DIAGNOSTIC_ASSERT(mGlobal);
MOZ_DIAGNOSTIC_ASSERT(mExtensionBrowser);
RefPtr<ExtensionEventManager> self = this;
mozilla::HoldJSObjects(this);
}
ExtensionEventManager::~ExtensionEventManager() {
ReleaseListeners();
mozilla::DropJSObjects(this);
};
void ExtensionEventManager::ReleaseListeners() {
if (mListeners.empty()) {
return;
}
for (auto iter = mListeners.iter(); !iter.done(); iter.next()) {
iter.get().value()->Cleanup();
}
mListeners.clear();
}
JSObject* ExtensionEventManager::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return dom::ExtensionEventManager_Binding::Wrap(aCx, this, aGivenProto);
}
nsIGlobalObject* ExtensionEventManager::GetParentObject() const {
return mGlobal;
}
void ExtensionEventManager::AddListener(
JSContext* aCx, dom::Function& aCallback,
const dom::Optional<JS::Handle<JSObject*>>& aOptions, ErrorResult& aRv) {
JS::Rooted<JSObject*> cb(aCx, aCallback.CallbackOrNull());
if (cb == nullptr) {
ThrowUnexpectedError(aCx, aRv);
return;
}
RefPtr<ExtensionEventManager> self = this;
IgnoredErrorResult rv;
RefPtr<ExtensionEventListener> wrappedCb = ExtensionEventListener::Create(
mGlobal, mExtensionBrowser, &aCallback,
[self = std::move(self)]() { self->ReleaseListeners(); }, rv);
if (NS_WARN_IF(rv.Failed())) {
ThrowUnexpectedError(aCx, aRv);
return;
}
RefPtr<ExtensionEventListener> storedWrapper = wrappedCb;
if (!mListeners.put(cb, std::move(storedWrapper))) {
ThrowUnexpectedError(aCx, aRv);
return;
}
auto request = SendAddListener(mEventName);
request->Run(mGlobal, aCx, {}, wrappedCb, aRv);
if (!aRv.Failed() && mAPIObjectType.IsEmpty()) {
mExtensionBrowser->TrackWakeupEventListener(aCx, mAPINamespace, mEventName);
}
}
void ExtensionEventManager::RemoveListener(dom::Function& aCallback,
ErrorResult& aRv) {
dom::AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JSObject*> cb(cx, aCallback.CallbackOrNull());
const auto& ptr = mListeners.lookup(cb);
// Return earlier if the listener wasn't found
if (!ptr) {
return;
}
RefPtr<ExtensionEventListener> wrappedCb = ptr->value();
auto request = SendRemoveListener(mEventName);
request->Run(mGlobal, cx, {}, wrappedCb, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
if (mAPIObjectType.IsEmpty()) {
mExtensionBrowser->UntrackWakeupEventListener(cx, mAPINamespace,
mEventName);
}
mListeners.remove(cb);
wrappedCb->Cleanup();
}
bool ExtensionEventManager::HasListener(dom::Function& aCallback,
ErrorResult& aRv) const {
return mListeners.has(aCallback.CallbackOrNull());
}
bool ExtensionEventManager::HasListeners(ErrorResult& aRv) const {
return !mListeners.empty();
}
} // namespace extensions
} // namespace mozilla