forked from mirrors/gecko-dev
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
166 lines
5 KiB
C++
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
|