gecko-dev/toolkit/components/extensions/webidl-api/ExtensionEventManager.cpp
Luca Greco dd82a248de Bug 1682632 - ExtensionEventManager should use a JS::Rooted<JSObject*> as key in the mListeners JS::GCHashMap. r=sfink,baku
This change is meant to handle the `mach hazards` failure that triggered a backout of this stack of patches,
ExtensionEventManager::AddListener/RemoveListener should use a JS::Rooted object as a key in the GCHashMap.

The GCHashMap has been added into ExtensionEventManager as part of D80610 ("part2.2: ExtensionEventListener xpcom interface
base implementation"), this patch adds the necessary changes on top of that patch.

(I've also double-checked locally that these changes should not be triggering the `mach hazards` failure anymore
and confirmed that all the unit tests part of this stack of patches are completing successfully).

Differential Revision: https://phabricator.services.mozilla.com/D117538
2021-06-11 18:58:12 +00:00

154 lines
4.6 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_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_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,
const nsAString& aNamespace,
const nsAString& aEventName,
const nsAString& aObjectType,
const nsAString& aObjectId)
: mGlobal(aGlobal),
mAPINamespace(aNamespace),
mEventName(aEventName),
mAPIObjectType(aObjectType),
mAPIObjectId(aObjectId) {
MOZ_DIAGNOSTIC_ASSERT(mGlobal);
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, &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);
}
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;
}
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