mirror of
https://github.com/mozilla-firefox/firefox
synced 2026-08-12 20:45:22 +00:00
replaced by .editorconfig # ignore-this-changeset Differential Revision: https://phabricator.services.mozilla.com/D287877
257 lines
8.8 KiB
C++
257 lines
8.8 KiB
C++
/* 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 "EventDispatcherBase.h"
|
|
|
|
#include "js/Array.h"
|
|
#include "mozilla/ProfilerMarkers.h"
|
|
#include "mozilla/dom/ScriptSettings.h"
|
|
#include "nsJSUtils.h"
|
|
|
|
namespace mozilla::widget {
|
|
|
|
namespace {
|
|
// Helper type used internally to transform a pair of
|
|
// `nsIGeckoViewEventCallback` and `nsIGeckoViewEventFinalizer` into a single
|
|
// `nsIGeckoViewEventCallback` which invokes the finalizer in its destructor.
|
|
class FinalizingCallbackDelegate final : public nsIGeckoViewEventCallback {
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_FORWARD_NSIGECKOVIEWEVENTCALLBACK(mCallback->);
|
|
|
|
FinalizingCallbackDelegate(nsIGeckoViewEventCallback* aCallback,
|
|
nsIGeckoViewEventFinalizer* aFinalizer)
|
|
: mCallback(aCallback), mFinalizer(aFinalizer) {}
|
|
|
|
nsIGeckoViewEventCallback* WrappedCallback() { return mCallback; }
|
|
|
|
private:
|
|
virtual ~FinalizingCallbackDelegate() {
|
|
if (mFinalizer) {
|
|
mFinalizer->OnFinalize();
|
|
}
|
|
}
|
|
|
|
const nsCOMPtr<nsIGeckoViewEventCallback> mCallback;
|
|
const nsCOMPtr<nsIGeckoViewEventFinalizer> mFinalizer;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(FinalizingCallbackDelegate, nsIGeckoViewEventCallback)
|
|
} // namespace
|
|
|
|
// This type is threadsafe refcounted, as it could theoretically be accessed
|
|
// from off-main-thread, but must only be destroyed on the main thread (due to
|
|
// holding main-thread only references to JS objects).
|
|
NS_IMPL_ADDREF(EventDispatcherBase)
|
|
NS_IMPL_RELEASE_WITH_DESTROY(EventDispatcherBase, Destroy())
|
|
NS_IMPL_QUERY_INTERFACE(EventDispatcherBase, nsIGeckoViewEventDispatcher)
|
|
|
|
void EventDispatcherBase::Destroy() {
|
|
NS_PROXY_DELETE_TO_EVENT_TARGET(EventDispatcherBase,
|
|
GetMainThreadSerialEventTarget());
|
|
}
|
|
|
|
nsresult EventDispatcherBase::DispatchToGeckoInternal(
|
|
ListenersList* list, const nsAString& aEvent, JS::Handle<JS::Value> aData,
|
|
nsIGeckoViewEventCallback* aCallback) {
|
|
mLock.NoteOnMainThread();
|
|
|
|
dom::AutoNoJSAPI nojsapi;
|
|
|
|
for (const auto& ent : list->EndLimitedRange()) {
|
|
// NOTE: Hold a strong reference to the listener, as the observer array can
|
|
// be mutated during this call.
|
|
nsCOMPtr<nsIGeckoViewEventListener> listener = ent;
|
|
nsresult rv = listener->OnEvent(aEvent, aData, aCallback);
|
|
|
|
// Discard any errors encountered while dispatching so we don't miss
|
|
// listeners.
|
|
(void)NS_WARN_IF(NS_FAILED(rv));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
EventDispatcherBase::Dispatch(JS::Handle<JS::Value> aEvent,
|
|
JS::Handle<JS::Value> aData,
|
|
nsIGeckoViewEventCallback* aCallback,
|
|
nsIGeckoViewEventFinalizer* aFinalizer,
|
|
JSContext* aCx) {
|
|
AssertIsOnMainThread();
|
|
mLock.NoteOnMainThread();
|
|
|
|
// Manually convert the event string from JS.
|
|
// See bug 1334728 for why AString is not used here.
|
|
if (!aEvent.isString()) {
|
|
NS_WARNING("Invalid event name");
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
nsAutoJSString event;
|
|
if (!event.init(aCx, aEvent.toString())) {
|
|
JS_ClearPendingException(aCx);
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// If a finalizer is provided, use FinalizingCallbackDelegate to wrap the
|
|
// type.
|
|
nsCOMPtr<nsIGeckoViewEventCallback> callback =
|
|
(aCallback && aFinalizer)
|
|
? new FinalizingCallbackDelegate(aCallback, aFinalizer)
|
|
: aCallback;
|
|
|
|
return DispatchInternal(aCx, event, aData, callback);
|
|
}
|
|
|
|
nsresult EventDispatcherBase::DispatchInternal(
|
|
JSContext* aCx, const nsAString& aEvent, JS::Handle<JS::Value> aData,
|
|
nsIGeckoViewEventCallback* aCallback) {
|
|
mLock.NoteOnMainThread();
|
|
|
|
// Don't need to lock here because we're on the main thread, and we can't
|
|
// race against Register/UnregisterListener.
|
|
|
|
if (ListenersList* list = mListenersMap.Get(aEvent)) {
|
|
return DispatchToGeckoInternal(list, aEvent, aData, aCallback);
|
|
}
|
|
|
|
return DispatchToEmbedder(aCx, aEvent, aData, aCallback);
|
|
}
|
|
|
|
// Given a JS value which is either a string or an array of strings, call the
|
|
// given `aCallback` method for each string with the mutex held.
|
|
nsresult EventDispatcherBase::IterateEvents(
|
|
JSContext* aCx, JS::Handle<JS::Value> aEvents,
|
|
IterateEventsCallback aCallback, nsIGeckoViewEventListener* aListener) {
|
|
MutexAutoLock lock(mLock.Lock());
|
|
mLock.NoteExclusiveAccess();
|
|
|
|
auto processEvent = [&](JS::Handle<JS::Value> event) -> nsresult {
|
|
nsAutoJSString str;
|
|
if (!str.init(aCx, event.toString())) {
|
|
JS_ClearPendingException(aCx);
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
(this->*aCallback)(str, aListener);
|
|
return NS_OK;
|
|
};
|
|
|
|
// NOTE: This does manual jsapi processing, rather than using something like
|
|
// WebIDL for simplicity for historical reasons.
|
|
// It may be related to wanting to avoid invalid values being passed in and
|
|
// coerced to strings.
|
|
if (aEvents.isString()) {
|
|
return processEvent(aEvents);
|
|
}
|
|
|
|
bool isArray = false;
|
|
NS_ENSURE_TRUE(aEvents.isObject(), NS_ERROR_INVALID_ARG);
|
|
if (!JS::IsArrayObject(aCx, aEvents, &isArray)) {
|
|
JS_ClearPendingException(aCx);
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
NS_ENSURE_TRUE(isArray, NS_ERROR_INVALID_ARG);
|
|
|
|
JS::Rooted<JSObject*> events(aCx, &aEvents.toObject());
|
|
uint32_t length = 0;
|
|
if (!JS::GetArrayLength(aCx, events, &length)) {
|
|
JS_ClearPendingException(aCx);
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
NS_ENSURE_TRUE(length, NS_ERROR_INVALID_ARG);
|
|
|
|
for (size_t i = 0; i < length; i++) {
|
|
JS::Rooted<JS::Value> event(aCx);
|
|
if (!JS_GetElement(aCx, events, i, &event)) {
|
|
JS_ClearPendingException(aCx);
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
NS_ENSURE_TRUE(event.isString(), NS_ERROR_INVALID_ARG);
|
|
|
|
nsresult rv = processEvent(event);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
void EventDispatcherBase::RegisterEventLocked(
|
|
const nsAString& aEvent, nsIGeckoViewEventListener* aListener) {
|
|
ListenersList* list = mListenersMap.GetOrInsertNew(aEvent);
|
|
|
|
// NOTE: Previously this code would return an error if the entry already
|
|
// existed, but only in debug builds. This has been upgraded to a debug
|
|
// assert, making the code infallible for more consistency between debug &
|
|
// release builds in terms of runtime behaviour.
|
|
if (NS_WARN_IF(list->Contains(aListener))) {
|
|
MOZ_ASSERT_UNREACHABLE("Attempt to register the same listener twice");
|
|
return;
|
|
}
|
|
|
|
list->AppendElement(aListener);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
EventDispatcherBase::RegisterListener(nsIGeckoViewEventListener* aListener,
|
|
JS::Handle<JS::Value> aEvents,
|
|
JSContext* aCx) {
|
|
AssertIsOnMainThread();
|
|
return IterateEvents(aCx, aEvents, &EventDispatcherBase::RegisterEventLocked,
|
|
aListener);
|
|
}
|
|
|
|
void EventDispatcherBase::UnregisterEventLocked(
|
|
const nsAString& aEvent, nsIGeckoViewEventListener* aListener) {
|
|
// NOTE: Previously this code would return an error if the entry didn't exist
|
|
// but only in debug builds. This has been upgraded to a debug assert, making
|
|
// the code infallible for more consistency between debug & release builds in
|
|
// terms of runtime behaviour.
|
|
ListenersList* list = mListenersMap.Get(aEvent);
|
|
MOZ_ASSERT(list);
|
|
NS_ENSURE_TRUE_VOID(list);
|
|
|
|
DebugOnly<bool> found = list->RemoveElement(aListener);
|
|
MOZ_ASSERT(found);
|
|
|
|
// NOTE: We intentionally do not remove the entry from `mListenersMap` here,
|
|
// as other code higher up the stack could be holding a reference to this
|
|
// nsTObserverArray through an iterator.
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
EventDispatcherBase::UnregisterListener(nsIGeckoViewEventListener* aListener,
|
|
JS::Handle<JS::Value> aEvents,
|
|
JSContext* aCx) {
|
|
AssertIsOnMainThread();
|
|
return IterateEvents(aCx, aEvents,
|
|
&EventDispatcherBase::UnregisterEventLocked, aListener);
|
|
}
|
|
|
|
bool EventDispatcherBase::HasGeckoListener(const nsAString& aEvent) {
|
|
// NOTE: This can be called on any thread, so must hold the mutex.
|
|
MutexAutoLock lock(mLock.Lock());
|
|
mLock.NoteLockHeld();
|
|
|
|
ListenersList* list = mListenersMap.Get(aEvent);
|
|
return list && !list->IsEmpty();
|
|
}
|
|
|
|
nsresult EventDispatcherBase::DispatchToGecko(
|
|
JSContext* aCx, const nsAString& aEvent, JS::Handle<JS::Value> aData,
|
|
nsIGeckoViewEventCallback* aCallback) {
|
|
mLock.NoteOnMainThread();
|
|
|
|
// If there are no Gecko listeners for this event, abort early.
|
|
ListenersList* list = mListenersMap.Get(aEvent);
|
|
if (!list || list->IsEmpty()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
AUTO_PROFILER_MARKER_TEXT("DispatchToGecko", OTHER, {}, aEvent);
|
|
|
|
// Actually call the Gecko listeners.
|
|
return DispatchToGeckoInternal(list, aEvent, aData, aCallback);
|
|
}
|
|
|
|
} // namespace mozilla::widget
|