diff --git a/Source/WebCore/automation/AutomationInstrumentation.cpp b/Source/WebCore/automation/AutomationInstrumentation.cpp index 95f57de872f3..6ba4f8d6c1d4 100644 --- a/Source/WebCore/automation/AutomationInstrumentation.cpp +++ b/Source/WebCore/automation/AutomationInstrumentation.cpp @@ -63,6 +63,11 @@ void AutomationInstrumentation::clearClient() automationClient().clear(); } +bool AutomationInstrumentation::hasClient() +{ + return !!automationClient(); +} + void AutomationInstrumentation::addMessageToConsole(const std::unique_ptr& message) { if (!automationClient()) [[likely]] @@ -74,6 +79,28 @@ void AutomationInstrumentation::addMessageToConsole(const std::unique_ptrscriptRealmCreated(frameID, origin); + }); +} + +void AutomationInstrumentation::scriptRealmDestroyed(FrameIdentifier frameID) +{ + if (!automationClient()) [[likely]] + return; + + WTF::ensureOnMainThread([frameID] { + if (RefPtr client = automationClient().get()) + client->scriptRealmDestroyed(frameID); + }); +} + } // namespace WebCore #endif diff --git a/Source/WebCore/automation/AutomationInstrumentation.h b/Source/WebCore/automation/AutomationInstrumentation.h index 0c0a7d772e87..b0c7fdd2b644 100644 --- a/Source/WebCore/automation/AutomationInstrumentation.h +++ b/Source/WebCore/automation/AutomationInstrumentation.h @@ -32,6 +32,7 @@ #if ENABLE(WEBDRIVER_BIDI) +#include "FrameIdentifier.h" #include #include #include @@ -53,6 +54,8 @@ class WEBCORE_EXPORT AutomationInstrumentationClient : public AbstractRefCounted virtual ~AutomationInstrumentationClient() = default; virtual void addMessageToConsole(const JSC::MessageSource&, const JSC::MessageLevel&, const String&, const JSC::MessageType&, const WallTime&) = 0; + virtual void scriptRealmCreated(FrameIdentifier, const String& origin) = 0; + virtual void scriptRealmDestroyed(FrameIdentifier) = 0; }; @@ -60,8 +63,11 @@ class WEBCORE_EXPORT AutomationInstrumentation { public: static void setClient(const AutomationInstrumentationClient&); static void clearClient(); + static bool hasClient(); static void addMessageToConsole(const std::unique_ptr&); + static void scriptRealmCreated(FrameIdentifier, const String& origin); + static void scriptRealmDestroyed(FrameIdentifier); }; } // namespace WebCore diff --git a/Source/WebCore/bindings/js/WindowProxy.cpp b/Source/WebCore/bindings/js/WindowProxy.cpp index b8aa3c2f6fd1..8559e204cc32 100644 --- a/Source/WebCore/bindings/js/WindowProxy.cpp +++ b/Source/WebCore/bindings/js/WindowProxy.cpp @@ -23,16 +23,20 @@ #include "CommonVM.h" #include "DOMWrapperWorld.h" +#include "DocumentLoader.h" #include "DocumentPage.h" #include "FrameConsoleClient.h" +#include "FrameLoader.h" #include "GarbageCollectionController.h" #include "JSDOMWindowBase.h" #include "JSWindowProxy.h" #include "LocalFrame.h" +#include "LocalFrameInlines.h" #include "Page.h" #include "PageGroup.h" #include "RemoteFrame.h" #include "ScriptController.h" +#include "SecurityOrigin.h" #include "runtime_root.h" #include #include @@ -40,10 +44,27 @@ #include #include +#if ENABLE(WEBDRIVER_BIDI) +#include "AutomationInstrumentation.h" +#endif + namespace WebCore { using namespace JSC; +#if ENABLE(WEBDRIVER_BIDI) +static String resolveOriginForRealm(LocalFrame& localFrame) +{ + if (auto* document = localFrame.document()) + return document->securityOrigin().toString(); + + if (auto* loader = localFrame.loader().activeDocumentLoader(); loader && !loader->url().isEmpty()) + return SecurityOrigin::create(loader->url())->toString(); + + return "null"_s; +} +#endif + static void collectGarbageAfterWindowProxyDestruction() { // Make sure to GC Extra Soon(tm) during memory pressure conditions @@ -107,6 +128,15 @@ void WindowProxy::destroyJSWindowProxy(DOMWrapperWorld& world) { ASSERT(m_jsWindowProxies.contains(&world)); m_jsWindowProxies.remove(&world); + +#if ENABLE(WEBDRIVER_BIDI) + // Notify about realm destruction for automation + // Only notify for normal world (main page execution), not user/internal worlds + if (world.isNormal()) { + if (RefPtr localFrame = dynamicDowncast(*m_frame)) + AutomationInstrumentation::scriptRealmDestroyed(localFrame->frameID()); + } +#endif world.didDestroyWindowProxy(this); } @@ -122,6 +152,16 @@ JSWindowProxy& WindowProxy::createJSWindowProxy(DOMWrapperWorld& world) Strong jsWindowProxy(vm, &JSWindowProxy::create(vm, *m_frame->protectedWindow().get(), world)); m_jsWindowProxies.add(&world, jsWindowProxy); world.didCreateWindowProxy(this); + +#if ENABLE(WEBDRIVER_BIDI) + // Notify about realm creation for automation. + // Only notify for normal world (main page execution), not user/internal worlds. + if (world.isNormal()) { + if (RefPtr localFrame = dynamicDowncast(*m_frame)) + AutomationInstrumentation::scriptRealmCreated(localFrame->frameID(), resolveOriginForRealm(*localFrame)); + } +#endif + return *jsWindowProxy.get(); } @@ -175,12 +215,11 @@ void WindowProxy::clearJSWindowProxiesNotMatchingDOMWindow(DOMWindow* newDOMWind void WindowProxy::setDOMWindow(DOMWindow* newDOMWindow) { ASSERT(newDOMWindow); + ASSERT(m_frame); if (m_jsWindowProxies.isEmpty()) return; - ASSERT(m_frame); - JSLockHolder lock(commonVM()); for (auto& windowProxy : jsWindowProxiesAsVector()) { @@ -189,6 +228,16 @@ void WindowProxy::setDOMWindow(DOMWindow* newDOMWindow) windowProxy->setWindow(*newDOMWindow); +#if ENABLE(WEBDRIVER_BIDI) + // Navigations reuse the JSWindowProxy with a new DOMWindow, which means a new realm. + if (windowProxy->world().isNormal()) { + if (RefPtr localFrame = dynamicDowncast(m_frame.get())) { + AutomationInstrumentation::scriptRealmDestroyed(localFrame->frameID()); + AutomationInstrumentation::scriptRealmCreated(localFrame->frameID(), resolveOriginForRealm(*localFrame)); + } + } +#endif + if (RefPtr localFrame = dynamicDowncast(m_frame.get())) { CheckedRef scriptController = localFrame->script(); diff --git a/Source/WebCore/loader/FrameLoader.cpp b/Source/WebCore/loader/FrameLoader.cpp index 0f3016dc546e..e2339d927ab1 100644 --- a/Source/WebCore/loader/FrameLoader.cpp +++ b/Source/WebCore/loader/FrameLoader.cpp @@ -727,7 +727,7 @@ void FrameLoader::clear(RefPtr&& newDocument, bool clearWindowProperti if (!neededClear) return; - + // Do this after detaching the document so that the unload event works. if (clearWindowProperties) { InspectorInstrumentation::frameWindowDiscarded(frame, document->protectedWindow().get()); diff --git a/Source/WebKit/UIProcess/Automation/BidiScriptAgent.cpp b/Source/WebKit/UIProcess/Automation/BidiScriptAgent.cpp index 42bd0f095990..97139c8bbe6b 100644 --- a/Source/WebKit/UIProcess/Automation/BidiScriptAgent.cpp +++ b/Source/WebKit/UIProcess/Automation/BidiScriptAgent.cpp @@ -255,66 +255,29 @@ RefPtr BidiScriptAgent::createRealmI String BidiScriptAgent::generateRealmIdForFrame(const FrameInfoData& frameInfo) { - String currentURL = frameInfo.request.url().string(); - std::optional currentDocumentID = frameInfo.documentID ? std::optional(frameInfo.documentID->toString()) : std::nullopt; - - if (auto it = m_frameRealmCache.find(frameInfo.frameID); it != m_frameRealmCache.end()) { - const auto& cachedEntry = it->value; - - if (cachedEntry.url == currentURL && cachedEntry.documentID == currentDocumentID) - return cachedEntry.realmId; - - // FIXME: This is a workaround until realm.created/realm.destroyed events are implemented. - // https://bugs.webkit.org/show_bug.cgi?id=304062 - // If only the documentID changed but URL is the same, reuse the cached realm ID to keep - // realm IDs stable between getRealms() and evaluate()/callFunction() calls on the same document. - // Once realm lifecycle events are implemented, they will handle cache updates properly. - if (cachedEntry.url == currentURL && currentURL != "about:blank"_s) { - m_frameRealmCache.set(frameInfo.frameID, FrameRealmCacheEntry { currentURL, currentDocumentID, cachedEntry.realmId }); - return cachedEntry.realmId; - } - - // Special case: Transitioning to/from about:blank is typically not a navigation, - // it's either the initial page load or a new test/session starting. - // Don't treat this as a state change that increments the counter. - bool transitioningToOrFromBlank = (cachedEntry.url == "about:blank"_s) != (currentURL == "about:blank"_s); - - if (transitioningToOrFromBlank) { - m_frameRealmCache.remove(frameInfo.frameID); - m_frameRealmCounters.remove(frameInfo.frameID); - } - } - - // Generate a new realm ID - the state has changed or this is a new frame + // Get the browsing context handle for this frame auto contextHandle = contextHandleForFrame(frameInfo); - - String newRealmId; - if (!contextHandle) { // Fallback to frame-based ID if we can't get context handle - newRealmId = makeString("realm-frame-"_s, String::number(frameInfo.frameID.toUInt64())); - } else { - // Use the contextHandle directly - it's already unique for both main frames and iframes - // For the first load of a context, use just the context handle - // For subsequent navigations/reloads, append a counter to make it unique - auto counterIt = m_frameRealmCounters.find(frameInfo.frameID); - if (counterIt == m_frameRealmCounters.end()) { - // First realm for this frame - no counter suffix - newRealmId = makeString("realm-"_s, *contextHandle); - // Start counter at 1 so the NEXT navigation will use "-1" suffix - m_frameRealmCounters.set(frameInfo.frameID, 1); - } else { - // Subsequent realm (reload/navigation) - use and increment counter - uint64_t counter = counterIt->value; - newRealmId = makeString("realm-"_s, *contextHandle, "-"_s, String::number(counter)); - counterIt->value = counter + 1; - } + return makeString("realm-frame-"_s, String::number(frameInfo.frameID.toUInt64())); } - // Update the cache with the new realm ID - m_frameRealmCache.set(frameInfo.frameID, FrameRealmCacheEntry { currentURL, currentDocumentID, newRealmId }); + // Use the shared m_realmNavigationCounters to ensure consistency with notifyRealmCreated/Destroyed + auto counterIt = m_realmNavigationCounters.find(*contextHandle); + if (counterIt == m_realmNavigationCounters.end()) { + // No realm has been created yet for this context - return the base realm ID + // Note: This should not normally happen since notifyRealmCreated should be called + // before getRealms due to the sendWithAsyncReply barrier + return makeString("realm-"_s, *contextHandle); + } + + // Generate realm ID based on the current counter value + uint64_t counter = counterIt->value; + String realmId = counter <= 1 + ? makeString("realm-"_s, *contextHandle) + : makeString("realm-"_s, *contextHandle, "-"_s, String::number(counter - 1)); - return newRealmId; + return realmId; } String BidiScriptAgent::generateRealmIdForBrowsingContext(const String& browsingContext) @@ -443,6 +406,93 @@ void BidiScriptAgent::collectExecutionReadyFrameRealms(const FrameTreeNodeData& } } +void BidiScriptAgent::notifyRealmCreated(const String& browsingContext, const String& origin) +{ + RefPtr session = m_session.get(); + if (!session) + return; + + // Generate a realm ID consistent with generateRealmIdForFrame() semantics: + // first realm -> realm-{context}, subsequent -> realm-{context}-{counter} + auto counterIt = m_realmNavigationCounters.find(browsingContext); + String realmId; + if (counterIt == m_realmNavigationCounters.end()) { + realmId = makeString("realm-"_s, browsingContext); + m_realmNavigationCounters.set(browsingContext, 1); + } else { + uint64_t counter = counterIt->value; + realmId = makeString("realm-"_s, browsingContext, "-"_s, String::number(counter)); + counterIt->value = counter + 1; + } + + // Store realm info for getRealms queries + RealmInfo info; + info.realmId = realmId; + info.origin = origin; + info.type = "window"_s; + info.context = browsingContext; + m_activeRealms.set(realmId, WTF::move(info)); + + // FIXME: Only emit events to subscribers based on context-specific subscriptions. + // https://bugs.webkit.org/show_bug.cgi?id=282981 + // Build script.realmCreated event per W3C BiDi spec + auto event = JSON::Object::create(); + event->setString("method"_s, "script.realmCreated"_s); + event->setString("type"_s, "event"_s); + + auto params = JSON::Object::create(); + params->setString("realm"_s, realmId); + params->setString("origin"_s, origin); + params->setString("type"_s, "window"_s); + params->setString("context"_s, browsingContext); + event->setObject("params"_s, WTF::move(params)); + + // Send event immediately + session->sendBidiMessage(event->toJSONString()); +} + +void BidiScriptAgent::notifyRealmDestroyed(const String& browsingContext) +{ + RefPtr session = m_session.get(); + if (!session) + return; + + auto counterIt = m_realmNavigationCounters.find(browsingContext); + if (counterIt == m_realmNavigationCounters.end()) + return; + + uint64_t counter = counterIt->value; + String realmId = counter == 1 + ? makeString("realm-"_s, browsingContext) + : makeString("realm-"_s, browsingContext, "-"_s, String::number(counter - 1)); + + auto activeIt = m_activeRealms.find(realmId); + if (activeIt != m_activeRealms.end()) + m_activeRealms.remove(activeIt); + + // FIXME: Only emit events to subscribers based on context-specific subscriptions. + // https://bugs.webkit.org/show_bug.cgi?id=282981 + // Build script.realmDestroyed event per W3C BiDi spec + auto event = JSON::Object::create(); + event->setString("method"_s, "script.realmDestroyed"_s); + event->setString("type"_s, "event"_s); + + auto params = JSON::Object::create(); + params->setString("realm"_s, realmId); + event->setObject("params"_s, WTF::move(params)); + + session->sendBidiMessage(event->toJSONString()); +} + +bool BidiScriptAgent::hasRealmForContext(const String& browsingContext) const +{ + for (const auto& entry : m_activeRealms) { + if (entry.value.context == browsingContext) + return true; + } + return false; +} + } // namespace WebKit #endif // ENABLE(WEBDRIVER_BIDI) diff --git a/Source/WebKit/UIProcess/Automation/BidiScriptAgent.h b/Source/WebKit/UIProcess/Automation/BidiScriptAgent.h index 524e8bc9d2d1..3ba79f1a5172 100644 --- a/Source/WebKit/UIProcess/Automation/BidiScriptAgent.h +++ b/Source/WebKit/UIProcess/Automation/BidiScriptAgent.h @@ -53,12 +53,6 @@ class BidiScriptAgent final : public Inspector::BidiScriptBackendDispatcherHandl WTF_MAKE_TZONE_ALLOCATED(BidiScriptAgent); WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(BidiScriptAgent); - struct FrameRealmCacheEntry { - String url; - std::optional documentID; - String realmId; - }; - public: BidiScriptAgent(WebAutomationSession&, Inspector::BackendDispatcher&); ~BidiScriptAgent() override; @@ -68,7 +62,19 @@ class BidiScriptAgent final : public Inspector::BidiScriptBackendDispatcherHandl void evaluate(const String& expression, bool awaitPromise, Ref&& target, std::optional&&, RefPtr&& optionalSerializationOptions, std::optional&& optionalUserActivation, Inspector::CommandCallbackOf, RefPtr>&&) override; void getRealms(const Inspector::Protocol::BidiBrowsingContext::BrowsingContext& optionalBrowsingContext , std::optional&& optionalRealmType, Inspector::CommandCallback>>&&) override; + // Realm lifecycle events + void notifyRealmCreated(const String& browsingContext, const String& origin); + void notifyRealmDestroyed(const String& browsingContext); + bool hasRealmForContext(const String& browsingContext) const; + private: + struct RealmInfo { + String realmId; + String origin; + String type; // "window", "dedicated-worker", etc. + String context; + }; + void processRealmsForPagesAsync(Deque>&& pagesToProcess, std::optional&& optionalRealmType, std::optional&& contextHandleFilter, Vector>&& accumulated, Inspector::CommandCallback>>&&); void collectExecutionReadyFrameRealms(const FrameTreeNodeData&, Vector>& realms, const std::optional& contextHandleFilter, bool recurseSubframes = true); bool isFrameExecutionReady(const FrameInfoData&); @@ -81,11 +87,11 @@ class BidiScriptAgent final : public Inspector::BidiScriptBackendDispatcherHandl WeakPtr m_session; Ref m_scriptDomainDispatcher; - // Track realm IDs to ensure they change when realms are recreated - HashMap m_frameRealmCache; // frame ID -> (state signature, realm ID) + // Track navigation counter for unique realm IDs (shared between events and getRealms) + HashMap m_realmNavigationCounters; - // Track realm counters for navigation detection: frame ID -> counter - HashMap m_frameRealmCounters; + // Store active realms (key: realm ID, value: realm info) + HashMap m_activeRealms; }; } // namespace WebKit diff --git a/Source/WebKit/UIProcess/Automation/WebAutomationSession.cpp b/Source/WebKit/UIProcess/Automation/WebAutomationSession.cpp index 460be329763a..4f6f19765a7c 100644 --- a/Source/WebKit/UIProcess/Automation/WebAutomationSession.cpp +++ b/Source/WebKit/UIProcess/Automation/WebAutomationSession.cpp @@ -71,6 +71,7 @@ #if ENABLE(WEBDRIVER_BIDI) #include "BidiBrowserAgent.h" +#include "BidiScriptAgent.h" #include "WebDriverBidiProcessor.h" #endif @@ -470,6 +471,10 @@ void WebAutomationSession::createBrowsingContext(std::optionallaunchInitialProcessIfNecessary(); + +#if ENABLE(WEBDRIVER_BIDI) + page->legacyMainFrameProcess().send(Messages::WebAutomationSessionProxy::EnsureRealmForInitialEmptyDocument(page->webPageIDInMainFrameProcess()), 0); +#endif callback({ { protectedThis->handleForWebPageProxy(*page), toProtocol(protectedThis->m_client->currentPresentationOfPage(protectedThis.get(), *page)) } }); }); } @@ -572,7 +577,7 @@ void WebAutomationSession::setWindowFrameOfBrowsingContext(const Inspector::Prot WebCore::FloatRect newFrame = WebCore::FloatRect(WebCore::FloatPoint(x.value_or(originalFrame.location().x()), y.value_or(originalFrame.location().y())), WebCore::FloatSize(width.value_or(originalFrame.size().width()), height.value_or(originalFrame.size().height()))); if (newFrame != originalFrame) page->setWindowFrame(newFrame); - + callback({ }); }); }); @@ -1196,6 +1201,9 @@ void WebAutomationSession::contextDestroyedForPage(const WebPageProxy& page) auto [clientWindow, userContext] = getClientWindowAndUserContext(page); + // Ensure the active realm is destroyed even if the WebProcess terminates first. + m_bidiProcessor->scriptAgent().notifyRealmDestroyed(contextHandle); + m_bidiProcessor->browsingContextDomainNotifier().contextDestroyed(contextHandle, url, originalOpenerHandle, parentContext, JSON::ArrayOf::create(), clientWindow, userContext); m_handleWebPageMap.remove(contextHandle); diff --git a/Source/WebKit/UIProcess/Automation/WebAutomationSession.h b/Source/WebKit/UIProcess/Automation/WebAutomationSession.h index 54577cd94e2e..76b1397cc0ff 100644 --- a/Source/WebKit/UIProcess/Automation/WebAutomationSession.h +++ b/Source/WebKit/UIProcess/Automation/WebAutomationSession.h @@ -290,6 +290,7 @@ friend class WebDriverBidiProcessor; #if ENABLE(WEBDRIVER_BIDI) Inspector::CommandResult processBidiMessage(const String&) override; void sendBidiMessage(const String&); + WebDriverBidiProcessor& bidiProcessor() const { return m_bidiProcessor; } #endif #if PLATFORM(MAC) diff --git a/Source/WebKit/UIProcess/Automation/WebDriverBidiProcessor.h b/Source/WebKit/UIProcess/Automation/WebDriverBidiProcessor.h index ee70429999f0..2909aa099fc7 100644 --- a/Source/WebKit/UIProcess/Automation/WebDriverBidiProcessor.h +++ b/Source/WebKit/UIProcess/Automation/WebDriverBidiProcessor.h @@ -54,6 +54,7 @@ class WebDriverBidiProcessor final : public Inspector::FrontendChannel { void sendBidiMessage(const String&); BidiBrowserAgent& browserAgent() const { return m_browserAgent; } + BidiScriptAgent& scriptAgent() const { return m_scriptAgent; } // Inspector::FrontendChannel methods. Domain events sent via WebDriverBidi domain notifiers are packaged up // by FrontendRouter and are then sent back out-of-process via WebAutomationSession::sendBidiMessage(). diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp index 588a67a50d11..df09ba5308ad 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.cpp +++ b/Source/WebKit/UIProcess/WebPageProxy.cpp @@ -145,6 +145,10 @@ #include "WKContextPrivate.h" #include "WebAutomationSession.h" #include "WebAutomationSessionProxyMessages.h" +#if ENABLE(WEBDRIVER_BIDI) +#include "BidiScriptAgent.h" +#include "WebDriverBidiProcessor.h" +#endif #include "WebBackForwardCache.h" #include "WebBackForwardList.h" #include "WebBackForwardListCounts.h" @@ -7946,6 +7950,53 @@ void WebPageProxy::didFinishLoadForFrame(IPC::Connection& connection, FrameIdent m_isLoadingAlternateHTMLStringForFailingProvisionalLoad = false; } +#if ENABLE(WEBDRIVER_BIDI) +void WebPageProxy::scriptRealmWasCreated(WebCore::FrameIdentifier frameID, String&& origin, CompletionHandler&& completionHandler) +{ + RefPtr automationSession = activeAutomationSession(); + if (!automationSession) { + completionHandler(); + return; + } + + // Convert frameID to browsing context handle + String browsingContext; + if (RefPtr frame = WebFrameProxy::webFrame(frameID)) { + if (frame->isMainFrame()) + browsingContext = automationSession->handleForWebPageProxy(*this); + else + browsingContext = automationSession->handleForWebFrameID(frameID); + } + + if (!browsingContext.isEmpty()) + automationSession->bidiProcessor().scriptAgent().notifyRealmCreated(browsingContext, origin); + + completionHandler(); +} + +void WebPageProxy::scriptRealmWasDestroyed(WebCore::FrameIdentifier frameID, CompletionHandler&& completionHandler) +{ + RefPtr automationSession = activeAutomationSession(); + if (!automationSession) { + completionHandler(); + return; + } + + String browsingContext; + if (RefPtr frame = WebFrameProxy::webFrame(frameID)) { + if (frame->isMainFrame()) + browsingContext = automationSession->handleForWebPageProxy(*this); + else + browsingContext = automationSession->handleForWebFrameID(frameID); + } + + if (!browsingContext.isEmpty()) + automationSession->bidiProcessor().scriptAgent().notifyRealmDestroyed(browsingContext); + + completionHandler(); +} +#endif + void WebPageProxy::didFailLoadForFrame(IPC::Connection& connection, FrameIdentifier frameID, FrameInfoData&& frameInfo, ResourceRequest&& request, std::optional navigationID, const ResourceError& error, const UserData& userData) { RefPtr protectedPageClient { pageClient() }; diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h index 2cfbe39c416c..2514de321c16 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.h +++ b/Source/WebKit/UIProcess/WebPageProxy.h @@ -2954,6 +2954,10 @@ class WebPageProxy final : public API::ObjectImpl, publ void didFinishDocumentLoadForFrame(IPC::Connection&, WebCore::FrameIdentifier, std::optional, const UserData&, WallTime); void didFinishLoadForFrame(IPC::Connection&, WebCore::FrameIdentifier, FrameInfoData&&, WebCore::ResourceRequest&&, std::optional, const UserData&, WallTime); void didFailLoadForFrame(IPC::Connection&, WebCore::FrameIdentifier, FrameInfoData&&, WebCore::ResourceRequest&&, std::optional, const WebCore::ResourceError&, const UserData&); +#if ENABLE(WEBDRIVER_BIDI) + void scriptRealmWasCreated(WebCore::FrameIdentifier, String&& origin, CompletionHandler&&); + void scriptRealmWasDestroyed(WebCore::FrameIdentifier, CompletionHandler&&); +#endif void didSameDocumentNavigationForFrame(IPC::Connection&, WebCore::FrameIdentifier, std::optional, SameDocumentNavigationType, URL&&, const UserData&); void didSameDocumentNavigationForFrameViaJS(IPC::Connection&, SameDocumentNavigationType, URL&&, NavigationActionData&&, const UserData&); void didChangeMainDocument(IPC::Connection&, WebCore::FrameIdentifier, std::optional); diff --git a/Source/WebKit/UIProcess/WebPageProxy.messages.in b/Source/WebKit/UIProcess/WebPageProxy.messages.in index ae31fb707bd0..a9d79f12ac9a 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.messages.in +++ b/Source/WebKit/UIProcess/WebPageProxy.messages.in @@ -149,6 +149,12 @@ messages -> WebPageProxy { DidFinishDocumentLoadForFrame(WebCore::FrameIdentifier frameID, std::optional navigationID, WebKit::UserData userData, WallTime timestamp) DidFinishLoadForFrame(WebCore::FrameIdentifier frameID, struct WebKit::FrameInfoData frameInfo, WebCore::ResourceRequest request, std::optional navigationID, WebKit::UserData userData, WallTime timestamp) DidFirstLayoutForFrame(WebCore::FrameIdentifier frameID, WebKit::UserData userData) + +#if ENABLE(WEBDRIVER_BIDI) + # WebDriver BiDi realm lifecycle events + ScriptRealmWasCreated(WebCore::FrameIdentifier frameID, String origin) -> () + ScriptRealmWasDestroyed(WebCore::FrameIdentifier frameID) -> () +#endif DidFirstVisuallyNonEmptyLayoutForFrame(WebCore::FrameIdentifier frameID, WebKit::UserData userData, WallTime timestamp) DidReachLayoutMilestone(OptionSet layoutMilestones, WallTime timestamp) DidReceiveTitleForFrame(WebCore::FrameIdentifier frameID, String title, WebKit::UserData userData) diff --git a/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.cpp b/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.cpp index 4b487da0376d..2459a214ff95 100644 --- a/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.cpp +++ b/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.cpp @@ -33,6 +33,11 @@ #include "WebAutomationSessionMessages.h" #include "WebAutomationSessionProxyMessages.h" #include "WebAutomationSessionProxyScriptSource.h" +#if ENABLE(WEBDRIVER_BIDI) +#include "WebPageProxyMessages.h" +#include +#include +#endif #include "WebFrame.h" #include "WebImage.h" #include "WebPage.h" @@ -1094,6 +1099,55 @@ void WebAutomationSessionProxy::addMessageToConsole(const JSC::MessageSource& so { WebProcess::singleton().protectedParentProcessConnection()->send(Messages::WebAutomationSession::LogEntryAdded(source, level, messageText, type, timestamp), 0); } + +void WebAutomationSessionProxy::scriptRealmCreated(WebCore::FrameIdentifier frameID, const String& origin) +{ + WeakPtr frame = WebProcess::singleton().webFrame(frameID); + if (!frame) + return; + + RefPtr page = frame->page(); + if (!page) + return; + + // Send to the specific WebPageProxy instance and wait for the UIProcess to handle it. + WebProcess::singleton().protectedParentProcessConnection()->sendWithAsyncReply( + Messages::WebPageProxy::ScriptRealmWasCreated(frameID, origin), + [] { }, + page->identifier().toUInt64()); +} + +void WebAutomationSessionProxy::scriptRealmDestroyed(WebCore::FrameIdentifier frameID) +{ + // Get the page that owns this frame + WeakPtr frame = WebProcess::singleton().webFrame(frameID); + if (!frame) + return; + + RefPtr page = frame->page(); + if (!page) + return; + + WebProcess::singleton().protectedParentProcessConnection()->sendWithAsyncReply( + Messages::WebPageProxy::ScriptRealmWasDestroyed(frameID), + [] { }, + page->identifier().toUInt64()); +} + +void WebAutomationSessionProxy::ensureRealmForInitialEmptyDocument(WebCore::PageIdentifier pageID) +{ + RefPtr page = WebProcess::singleton().webPage(pageID); + if (!page) + return; + + RefPtr frame = &page->mainWebFrame(); + RefPtr coreFrame = frame->coreLocalFrame(); + if (!coreFrame) + return; + + // Force creation of JSWindowProxy for the normal world, which will trigger realm creation. + coreFrame->protectedWindowProxy()->jsWindowProxy(WebCore::mainThreadNormalWorldSingleton()); +} #endif } // namespace WebKit diff --git a/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.h b/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.h index d4f56f72e284..2821a47d6ee6 100644 --- a/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.h +++ b/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.h @@ -110,6 +110,9 @@ class WebAutomationSessionProxy : public IPC::MessageReceiver #if ENABLE(WEBDRIVER_BIDI) void addMessageToConsole(const JSC::MessageSource&, const JSC::MessageLevel&, const String&, const JSC::MessageType&, const WallTime&) override; + void scriptRealmCreated(WebCore::FrameIdentifier, const String& origin) override; + void scriptRealmDestroyed(WebCore::FrameIdentifier) override; + void ensureRealmForInitialEmptyDocument(WebCore::PageIdentifier); #endif String m_sessionIdentifier; diff --git a/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.messages.in b/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.messages.in index f39b8c65b094..a0c285d728a7 100644 --- a/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.messages.in +++ b/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.messages.in @@ -47,4 +47,8 @@ messages -> WebAutomationSessionProxy { GetCookiesForFrame(WebCore::PageIdentifier pageID, std::optional frameID) -> (std::optional errorType, Vector cookies) DeleteCookie(WebCore::PageIdentifier pageID, std::optional frameID, String cookieName) -> (std::optional errorType) + +#if ENABLE(WEBDRIVER_BIDI) + EnsureRealmForInitialEmptyDocument(WebCore::PageIdentifier pageID) +#endif } diff --git a/WebDriverTests/TestExpectations.json b/WebDriverTests/TestExpectations.json index 1ea25a5e9a41..8ede03e3d75c 100644 --- a/WebDriverTests/TestExpectations.json +++ b/WebDriverTests/TestExpectations.json @@ -3918,10 +3918,59 @@ "expected": { "all": { "status": ["SKIP"], "bug": "webkit.org/b/288065"}} }, "imported/w3c/webdriver/tests/bidi/script/realm_created/realm_created.py": { - "expected": { "all": { "status": ["SKIP"], "bug": "webkit.org/b/288066"}} + "expected": { "all": { "status": ["FAIL"], "bug": "webkit.org/b/288066"}}, + "subtests": { + "test_unsubscribe": { + "expected": { "all": { "status": ["PASS"]}} + }, + "test_create_context[window]": { + "expected": { "all": { "status": ["PASS"]}} + }, + "test_create_context[tab]": { + "expected": { "all": { "status": ["PASS"]}} + }, + "test_navigate": { + "expected": { "all": { "status": ["PASS"]}} + }, + "test_reload": { + "expected": { "all": { "status": ["PASS"]}} + }, + "test_existing_realm[window]": { + "expected": { "all": { "status": ["PASS", "FAIL"]}} + }, + "test_existing_realm[tab]": { + "expected": { "all": { "status": ["PASS", "FAIL"]}} + } + } }, "imported/w3c/webdriver/tests/bidi/script/realm_destroyed/realm_destroyed.py": { - "expected": { "all": { "status": ["SKIP"], "bug": "webkit.org/b/281937"}} + "expected": { "all": { "status": ["PASS"]}}, + "subtests": { + "test_sandbox[evaluate]": { + "expected": { "all": { "status": ["FAIL"], "bug": "webkit.org/b/288067"}} + }, + "test_sandbox[call_function]": { + "expected": { "all": { "status": ["FAIL"], "bug": "webkit.org/b/288067"}} + }, + "test_iframe[same_origin]": { + "expected": { "all": { "status": ["FAIL"], "bug": "webkit.org/b/288067"}} + }, + "test_iframe[cross_origin]": { + "expected": { "all": { "status": ["FAIL"], "bug": "webkit.org/b/288067"}} + }, + "test_iframe_destroy_parent": { + "expected": { "all": { "status": ["FAIL"], "bug": "webkit.org/b/288067"}} + }, + "test_subscribe_to_one_context": { + "expected": { "all": { "status": ["FAIL"], "bug": "webkit.org/b/282981"}} + }, + "test_dedicated_worker": { + "expected": { "all": { "status": ["FAIL"], "bug": "webkit.org/b/288067"}} + }, + "test_shared_worker": { + "expected": { "all": { "status": ["FAIL"], "bug": "webkit.org/b/288067"}} + } + } }, "imported/w3c/webdriver/tests/bidi/script/remove_preload_script/invalid.py": { "expected": { "all": { "status": ["SKIP"], "bug": "webkit.org/b/288062"}}