tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit ef13c67d27be90daf9ba6482ab8f66c02684cc72
parent 18449793c18bd1414b0ee991b2d5126c351dc30d
Author: Nika Layzell <nika@thelayzells.com>
Date:   Tue, 16 Dec 2025 04:53:44 +0000

Bug 1908693 - Part 3: Delay EventDispatcher messages until listeners are ready on iOS, r=glandium,owlish,geckoview-reviewers

Before this change, calls through `EventDispatcher` could be delivered before
the geckoview.js script had begun executing, which would mean that early calls
to the Gecko backend would not be handled. This patch ensures that these events
are queued up until the listeners are ready, which is more in line with the
behaviour on Android.

Differential Revision: https://phabricator.services.mozilla.com/D217133

Diffstat:
Mmobile/ios/GeckoTestBrowser/GeckoView/EventDispatcher.swift | 20++++++++++++++++++++
Mwidget/uikit/EventDispatcher.h | 2++
Mwidget/uikit/EventDispatcher.mm | 6++++++
Mwidget/uikit/GeckoViewSwiftSupport.h | 2++
Mwidget/uikit/IOSBridge.mm | 3+++
Mwidget/uikit/nsAppShell.mm | 14++++++++++++++
6 files changed, 47 insertions(+), 0 deletions(-)

diff --git a/mobile/ios/GeckoTestBrowser/GeckoView/EventDispatcher.swift b/mobile/ios/GeckoTestBrowser/GeckoView/EventDispatcher.swift @@ -38,7 +38,14 @@ class EventDispatcher: NSObject, SwiftEventDispatcher { static var runtimeInstance = EventDispatcher() static var dispatchers: [String: EventDispatcher] = [:] + struct QueuedMessage { + let type: String + let message: [String: Any?]? + let callback: EventCallback? + } + var gecko: GeckoEventDispatcher? + var queue: [QueuedMessage]? = [] var listeners: [String: [EventListener]] = [:] var name: String? @@ -68,6 +75,8 @@ class EventDispatcher: NSObject, SwiftEventDispatcher { for listener in eventListeners { listener.handleMessage(type: type, message: message, callback: callback) } + } else if queue != nil { + queue!.append(QueuedMessage(type: type, message: message, callback: callback)) } else { gecko?.dispatch(toGecko: type, message: message, callback: callback) } @@ -111,6 +120,17 @@ class EventDispatcher: NSObject, SwiftEventDispatcher { } } + func activate() { + // Drain the queue, then clear it out so future messages are dispatched + // directly. + if let queue = self.queue { + self.queue = nil + for event in queue { + gecko?.dispatch(toGecko: event.type, message: event.message, callback: event.callback) + } + } + } + func hasListener(_ type: String) -> Bool { listeners.keys.contains(type) } diff --git a/widget/uikit/EventDispatcher.h b/widget/uikit/EventDispatcher.h @@ -24,6 +24,8 @@ class EventDispatcher final : public EventDispatcherBase { void Attach(id aDispatcher); void Detach(); + void Activate() MOZ_REQUIRES(sMainThreadCapability); + bool HasEmbedderListener(const nsAString& aEvent) override MOZ_REQUIRES(sMainThreadCapability); nsresult DispatchToEmbedder(JSContext* aCx, const nsAString& aEvent, diff --git a/widget/uikit/EventDispatcher.mm b/widget/uikit/EventDispatcher.mm @@ -567,6 +567,12 @@ void EventDispatcher::Detach() { Shutdown(); } +void EventDispatcher::Activate() { + if (mDispatcher) { + [(id<SwiftEventDispatcher>)mDispatcher activate]; + } +} + EventDispatcher::~EventDispatcher() { if (mDispatcher) { [mDispatcher release]; diff --git a/widget/uikit/GeckoViewSwiftSupport.h b/widget/uikit/GeckoViewSwiftSupport.h @@ -52,6 +52,8 @@ message:(id)message callback:(id<EventCallback>)callback; - (BOOL)hasListener:(NSString*)type; +// Called when GeckoView is ready to receive dispatched events from Swift. +- (void)activate; @end @protocol GeckoViewWindow <NSObject> diff --git a/widget/uikit/IOSBridge.mm b/widget/uikit/IOSBridge.mm @@ -18,15 +18,18 @@ nsIOSBridge::nsIOSBridge() { RefPtr<mozilla::widget::EventDispatcher> dispatcher = new mozilla::widget::EventDispatcher(); dispatcher->Attach([GetSwiftRuntime() runtimeDispatcher]); + dispatcher->Activate(); mEventDispatcher = dispatcher; } NS_IMETHODIMP nsIOSBridge::GetDispatcherByName(const char* aName, nsIGeckoViewEventDispatcher** aResult) { + mozilla::AssertIsOnMainThread(); RefPtr<mozilla::widget::EventDispatcher> dispatcher = new mozilla::widget::EventDispatcher(); dispatcher->Attach([GetSwiftRuntime() dispatcherByName:aName]); + dispatcher->Activate(); dispatcher.forget(aResult); return NS_OK; } diff --git a/widget/uikit/nsAppShell.mm b/widget/uikit/nsAppShell.mm @@ -8,6 +8,7 @@ #import <UIKit/UIWindow.h> #include "mozilla/Components.h" +#include "mozilla/dom/Document.h" #include "nsIObserverService.h" #include "gfxPlatform.h" #include "nsAppShell.h" @@ -21,6 +22,7 @@ #include "nsThreadUtils.h" #include "nsMemoryPressure.h" #include "nsServiceManagerUtils.h" +#include "mozilla/widget/EventDispatcher.h" #include "mozilla/widget/ScreenManager.h" #include "ScreenHelperUIKit.h" #include "mozilla/Hal.h" @@ -149,6 +151,7 @@ nsresult nsAppShell::Init() { mozilla::services::GetObserverService(); if (obsServ) { obsServ->AddObserver(this, "profile-after-change", false); + obsServ->AddObserver(this, "chrome-document-loaded", false); } return rv; @@ -156,6 +159,8 @@ nsresult nsAppShell::Init() { NS_IMETHODIMP nsAppShell::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { + AssertIsOnMainThread(); + bool removeObserver = false; if (!strcmp(aTopic, "profile-after-change")) { // Gecko on iOS follows the iOS app model where it never stops until it is @@ -167,6 +172,15 @@ NS_IMETHODIMP nsAppShell::Observe(nsISupports* aSubject, const char* aTopic, appStartup->EnterLastWindowClosingSurvivalArea(); } removeObserver = true; + } else if (!strcmp(aTopic, "chrome-document-loaded")) { + // Set the global ready state and enable the window event dispatcher + // for this particular GeckoView. + nsCOMPtr<dom::Document> doc = do_QueryInterface(aSubject); + MOZ_ASSERT(doc); + if (const RefPtr<nsWindow> window = nsWindow::From(doc->GetWindow())) { + RefPtr<EventDispatcher> dispatcher = window->GetEventDispatcher(); + dispatcher->Activate(); + } } else { return nsBaseAppShell::Observe(aSubject, aTopic, aData); }