Diff
Modified: trunk/LayoutTests/ChangeLog (292458 => 292459)
--- trunk/LayoutTests/ChangeLog 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/LayoutTests/ChangeLog 2022-04-06 11:17:25 UTC (rev 292459)
@@ -1,3 +1,17 @@
+2022-04-06 Youenn Fablet <you...@apple.com>
+
+ Implement ServiceWorkerWindowClient.navigate
+ https://bugs.webkit.org/show_bug.cgi?id=238738
+
+ Reviewed by Chris Dumez.
+
+ * http/wpt/service-workers/navigate-iframes-window-client.https-expected.txt: Added.
+ * http/wpt/service-workers/navigate-iframes-window-client.https.html: Added.
+ * http/wpt/service-workers/navigate-window-client-worker.js: Added.
+ * platform/glib/TestExpectations:
+ * platform/glib/imported/w3c/web-platform-tests/service-workers/service-worker/windowclient-navigate.https-expected.txt: Removed.
+ * platform/mac-wk2/TestExpectations:
+
2022-04-05 Ada Chan <adac...@apple.com>
[WebXR] Add a new enum type to represent session features
Added: trunk/LayoutTests/http/wpt/service-workers/navigate-iframes-window-client.https-expected.txt (0 => 292459)
--- trunk/LayoutTests/http/wpt/service-workers/navigate-iframes-window-client.https-expected.txt (rev 0)
+++ trunk/LayoutTests/http/wpt/service-workers/navigate-iframes-window-client.https-expected.txt 2022-04-06 11:17:25 UTC (rev 292459)
@@ -0,0 +1,8 @@
+
+
+PASS Setup activating worker
+PASS Navigate to same origin
+PASS Navigate to same URL
+PASS Navigate to same document fragment
+PASS Navigate to cross origin
+
Added: trunk/LayoutTests/http/wpt/service-workers/navigate-iframes-window-client.https.html (0 => 292459)
--- trunk/LayoutTests/http/wpt/service-workers/navigate-iframes-window-client.https.html (rev 0)
+++ trunk/LayoutTests/http/wpt/service-workers/navigate-iframes-window-client.https.html 2022-04-06 11:17:25 UTC (rev 292459)
@@ -0,0 +1,82 @@
+<!doctype html>
+<html>
+<head>
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+</head>
+<body>
+<script>
+var url = "" URL("/WebKit/service-workers/resources/", location);
+url = ""
+var activeWorker;
+
+promise_test(async (test) => {
+ let registration = await navigator.serviceWorker.register("/WebKit/service-workers/navigate-window-client-worker.js", { scope : url });
+ if (!registration.installing) {
+ registration.unregister();
+ registration = await navigator.serviceWorker.register("/WebKit/service-workers/navigate-window-client-worker.js", { scope : url });
+ }
+ activeWorker = registration.installing;
+ await waitForState(activeWorker, "activating");
+ activeWorker = registration.active;
+}, "Setup activating worker");
+
+promise_test(async (test) => {
+ const iframe = await withIframe(url);
+
+ activeWorker.postMessage({navigate: url + "?test"});
+ const result = await new Promise(resolve => navigator.serviceWorker._onmessage_ = (event) => resolve(event.data));
+ assert_true(result.url.includes("?test"));
+
+ iframe.remove();
+}, "Navigate to same origin");
+
+promise_test(async (test) => {
+ const iframe = await withIframe(url);
+
+ activeWorker.postMessage("get-client");
+ const client1 = await new Promise(resolve => navigator.serviceWorker._onmessage_ = (event) => resolve(event.data));
+
+ activeWorker.postMessage({navigate: url});
+ const result = await new Promise(resolve => navigator.serviceWorker._onmessage_ = (event) => resolve(event.data));
+ assert_equals(result.url, url);
+
+ activeWorker.postMessage("get-client");
+ const client2 = await new Promise(resolve => navigator.serviceWorker._onmessage_ = (event) => resolve(event.data));
+
+ assert_not_equals(client1.id, client2.id);
+
+ iframe.remove();
+}, "Navigate to same URL");
+
+promise_test(async (test) => {
+ const iframe = await withIframe(url);
+
+ activeWorker.postMessage("get-client");
+ const client1 = await new Promise(resolve => navigator.serviceWorker._onmessage_ = (event) => resolve(event.data));
+
+ activeWorker.postMessage({navigate: url + "#test"});
+ const result = await new Promise(resolve => navigator.serviceWorker._onmessage_ = (event) => resolve(event.data));
+ assert_equals(result.url, url);
+
+ activeWorker.postMessage("get-client");
+ const client2 = await new Promise(resolve => navigator.serviceWorker._onmessage_ = (event) => resolve(event.data));
+
+ assert_equals(client1.id, client2.id);
+
+ iframe.remove();
+}, "Navigate to same document fragment");
+
+promise_test(async (test) => {
+ const iframe = await withIframe(url);
+
+ activeWorker.postMessage({navigate: "https://127.0.0.1:9443" });
+ const result = await new Promise(resolve => navigator.serviceWorker._onmessage_ = (event) => resolve(event.data));
+ assert_equals(result, "none");
+}, "Navigate to cross origin");
+</script>
+</body>
+</html>
Added: trunk/LayoutTests/http/wpt/service-workers/navigate-window-client-worker.js (0 => 292459)
--- trunk/LayoutTests/http/wpt/service-workers/navigate-window-client-worker.js (rev 0)
+++ trunk/LayoutTests/http/wpt/service-workers/navigate-window-client-worker.js 2022-04-06 11:17:25 UTC (rev 292459)
@@ -0,0 +1,25 @@
+_onmessage_ = async (event) => {
+ if (event.data ="" "get-client") {
+ const clients = await self.clients.matchAll();
+ if (!clients.length) {
+ event.source.postMessage("no client");
+ return;
+ }
+ event.source.postMessage({ id: clients[0].id, url: clients[0].url });
+ return;
+ }
+
+ if (event.data && event.data.navigate) {
+ const clients = await self.clients.matchAll();
+ if (!clients.length) {
+ event.source.postMessage("failed, no clients");
+ return;
+ }
+ await clients[0].navigate(event.data.navigate).then((client) => {
+ event.source.postMessage(client ? { id: client.id, url: client.url } : "none");
+ }, (e) => {
+ event.source.postMessage("failed");
+ });
+ return;
+ }
+};
Modified: trunk/LayoutTests/imported/w3c/ChangeLog (292458 => 292459)
--- trunk/LayoutTests/imported/w3c/ChangeLog 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/LayoutTests/imported/w3c/ChangeLog 2022-04-06 11:17:25 UTC (rev 292459)
@@ -1,3 +1,12 @@
+2022-04-06 Youenn Fablet <you...@apple.com>
+
+ Implement ServiceWorkerWindowClient.navigate
+ https://bugs.webkit.org/show_bug.cgi?id=238738
+
+ Reviewed by Chris Dumez.
+
+ * web-platform-tests/service-workers/service-worker/windowclient-navigate.https-expected.txt:
+
2022-04-05 Matt Woodrow <mattwood...@apple.com>
Support transitions/animations on grid-template-columns|rows
Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/windowclient-navigate.https-expected.txt (292458 => 292459)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/windowclient-navigate.https-expected.txt 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/windowclient-navigate.https-expected.txt 2022-04-06 11:17:25 UTC (rev 292459)
@@ -10,29 +10,15 @@
PASS invalid url (file:///)
PASS invalid url (about:blank)
PASS navigate on a top-level window client
-FAIL normal worker side promise_test: Unhandled rejection with value: object "NotSupportedError: windowClient.navigate() is not yet supported"
-FAIL blank url worker side promise_test: Unhandled rejection with value: object "NotSupportedError: windowClient.navigate() is not yet supported"
-FAIL in scope but not controlled test on installing worker worker side promise_rejects_js: function "function () { throw e }" threw object "NotSupportedError: windowClient.navigate() is not yet supported" ("NotSupportedError") expected instance of function "function TypeError() {
- [native code]
-}" ("TypeError")
-FAIL in scope but not controlled test on active worker worker side promise_rejects_js: function "function () { throw e }" threw object "NotSupportedError: windowClient.navigate() is not yet supported" ("NotSupportedError") expected instance of function "function TypeError() {
- [native code]
-}" ("TypeError")
-FAIL out of scope worker side promise_rejects_js: function "function () { throw e }" threw object "NotSupportedError: windowClient.navigate() is not yet supported" ("NotSupportedError") expected instance of function "function TypeError() {
- [native code]
-}" ("TypeError")
-FAIL cross orgin url worker side promise_test: Unhandled rejection with value: object "NotSupportedError: windowClient.navigate() is not yet supported"
-FAIL invalid url (http://[example.com]) worker side promise_rejects_js: function "function () { throw e }" threw object "NotSupportedError: windowClient.navigate() is not yet supported" ("NotSupportedError") expected instance of function "function TypeError() {
- [native code]
-}" ("TypeError")
-FAIL invalid url (view-source://example.com) worker side promise_rejects_js: function "function () { throw e }" threw object "NotSupportedError: windowClient.navigate() is not yet supported" ("NotSupportedError") expected instance of function "function TypeError() {
- [native code]
-}" ("TypeError")
-FAIL invalid url (file:///) worker side promise_rejects_js: function "function () { throw e }" threw object "NotSupportedError: windowClient.navigate() is not yet supported" ("NotSupportedError") expected instance of function "function TypeError() {
- [native code]
-}" ("TypeError")
-FAIL invalid url (about:blank) worker side promise_rejects_js: function "function () { throw e }" threw object "NotSupportedError: windowClient.navigate() is not yet supported" ("NotSupportedError") expected instance of function "function TypeError() {
- [native code]
-}" ("TypeError")
+PASS normal worker side
+PASS blank url worker side
+PASS in scope but not controlled test on installing worker worker side
+PASS in scope but not controlled test on active worker worker side
+PASS out of scope worker side
+PASS cross orgin url worker side
+PASS invalid url (http://[example.com]) worker side
+PASS invalid url (view-source://example.com) worker side
+FAIL invalid url (file:///) worker side assert_equals: expected (string) "TypeError" but got (object) null
+PASS invalid url (about:blank) worker side
FAIL navigate on a top-level window client worker side assert_equals: expected "top-level" but got "auxiliary"
Modified: trunk/LayoutTests/platform/glib/TestExpectations (292458 => 292459)
--- trunk/LayoutTests/platform/glib/TestExpectations 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/LayoutTests/platform/glib/TestExpectations 2022-04-06 11:17:25 UTC (rev 292459)
@@ -1194,6 +1194,9 @@
webkit.org/b/201981 http/wpt/service-workers/server-trust-evaluation.https.html [ Failure ]
webkit.org/b/201981 imported/w3c/web-platform-tests/service-workers/service-worker/fetch-canvas-tainting-image-cache.https.html [ Failure Pass ]
+webkit.org/b/238738 http/wpt/service-workers/navigate-iframes-window-client.https.html [ Failure ]
+webkit.org/b/238738238738 imported/w3c/web-platform-tests/service-workers/service-worker/windowclient-navigate.https.html [ Failure ]
+
webkit.org/b/208369 http/tests/workers/service/basic-timeout.https.html [ Failure Timeout Pass ]
webkit.org/b/215638 http/tests/websocket/tests/hybi/invalid-continuation.html [ Failure ]
Deleted: trunk/LayoutTests/platform/glib/imported/w3c/web-platform-tests/service-workers/service-worker/windowclient-navigate.https-expected.txt (292458 => 292459)
--- trunk/LayoutTests/platform/glib/imported/w3c/web-platform-tests/service-workers/service-worker/windowclient-navigate.https-expected.txt 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/LayoutTests/platform/glib/imported/w3c/web-platform-tests/service-workers/service-worker/windowclient-navigate.https-expected.txt 2022-04-06 11:17:25 UTC (rev 292459)
@@ -1,38 +0,0 @@
-
-PASS normal
-PASS blank url
-PASS in scope but not controlled test on installing worker
-PASS in scope but not controlled test on active worker
-PASS out of scope
-PASS cross orgin url
-PASS invalid url (http://[example.com])
-PASS invalid url (view-source://example.com)
-PASS invalid url (file:///)
-PASS invalid url (about:blank)
-PASS navigate on a top-level window client
-FAIL normal worker side promise_test: Unhandled rejection with value: object "NotSupportedError: windowClient.navigate() is not yet supported"
-FAIL blank url worker side promise_test: Unhandled rejection with value: object "NotSupportedError: windowClient.navigate() is not yet supported"
-FAIL in scope but not controlled test on installing worker worker side promise_rejects_js: function "function () { throw e }" threw object "NotSupportedError: windowClient.navigate() is not yet supported" ("NotSupportedError") expected instance of function "function TypeError() {
- [native code]
-}" ("TypeError")
-FAIL in scope but not controlled test on active worker worker side promise_rejects_js: function "function () { throw e }" threw object "NotSupportedError: windowClient.navigate() is not yet supported" ("NotSupportedError") expected instance of function "function TypeError() {
- [native code]
-}" ("TypeError")
-FAIL out of scope worker side promise_rejects_js: function "function () { throw e }" threw object "NotSupportedError: windowClient.navigate() is not yet supported" ("NotSupportedError") expected instance of function "function TypeError() {
- [native code]
-}" ("TypeError")
-FAIL cross orgin url worker side promise_test: Unhandled rejection with value: object "NotSupportedError: windowClient.navigate() is not yet supported"
-FAIL invalid url (http://[example.com]) worker side promise_rejects_js: function "function () { throw e }" threw object "NotSupportedError: windowClient.navigate() is not yet supported" ("NotSupportedError") expected instance of function "function TypeError() {
- [native code]
-}" ("TypeError")
-FAIL invalid url (view-source://example.com) worker side promise_rejects_js: function "function () { throw e }" threw object "NotSupportedError: windowClient.navigate() is not yet supported" ("NotSupportedError") expected instance of function "function TypeError() {
- [native code]
-}" ("TypeError")
-FAIL invalid url (file:///) worker side promise_rejects_js: function "function () { throw e }" threw object "NotSupportedError: windowClient.navigate() is not yet supported" ("NotSupportedError") expected instance of function "function TypeError() {
- [native code]
-}" ("TypeError")
-FAIL invalid url (about:blank) worker side promise_rejects_js: function "function () { throw e }" threw object "NotSupportedError: windowClient.navigate() is not yet supported" ("NotSupportedError") expected instance of function "function TypeError() {
- [native code]
-}" ("TypeError")
-FAIL navigate on a top-level window client worker side assert_equals: expected "top-level" but got "auxiliary"
-
Modified: trunk/LayoutTests/platform/mac-wk2/TestExpectations (292458 => 292459)
--- trunk/LayoutTests/platform/mac-wk2/TestExpectations 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/LayoutTests/platform/mac-wk2/TestExpectations 2022-04-06 11:17:25 UTC (rev 292459)
@@ -924,7 +924,7 @@
webkit.org/b/230412 imported/w3c/web-platform-tests/service-workers/service-worker/clients-matchall-client-types.https.html [ Pass Failure ]
-webkit.org/b/179352 imported/w3c/web-platform-tests/service-workers/service-worker/windowclient-navigate.https.html [ Pass Failure Slow ]
+webkit.org/b/179352 imported/w3c/web-platform-tests/service-workers/service-worker/windowclient-navigate.https.html [ Slow ]
webkit.org/b/183164 fast/dom/Window/window-focus-self.html [ Pass Failure ]
Modified: trunk/Source/WebCore/ChangeLog (292458 => 292459)
--- trunk/Source/WebCore/ChangeLog 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebCore/ChangeLog 2022-04-06 11:17:25 UTC (rev 292459)
@@ -1,5 +1,30 @@
2022-04-06 Youenn Fablet <you...@apple.com>
+ Implement ServiceWorkerWindowClient.navigate
+ https://bugs.webkit.org/show_bug.cgi?id=238738
+
+ Reviewed by Chris Dumez.
+
+ Introduce a routine to navigate a Document.
+ Send FrameIdentifier as part of ServiceWorkerClientData.
+ Implement ServiceWorkerWindowClient::navigate by sending IPC to network process.
+
+ Test: http/wpt/service-workers/navigate-iframes-window-client.https.html
+
+ * dom/Document.cpp:
+ * dom/Document.h:
+ * loader/FrameLoader.cpp:
+ * workers/service/ServiceWorkerClientData.cpp:
+ * workers/service/ServiceWorkerClientData.h:
+ * workers/service/ServiceWorkerWindowClient.cpp:
+ * workers/service/context/SWContextManager.h:
+ * workers/service/server/SWServer.cpp:
+ * workers/service/server/SWServer.h:
+ * workers/service/server/SWServerWorker.cpp:
+ * workers/service/server/SWServerWorker.h:
+
+2022-04-06 Youenn Fablet <you...@apple.com>
+
ServiceWorkerClients.openWindow should not need to get all clients asynchronously to resolve its promise
https://bugs.webkit.org/show_bug.cgi?id=238503
Modified: trunk/Source/WebCore/dom/Document.cpp (292458 => 292459)
--- trunk/Source/WebCore/dom/Document.cpp 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebCore/dom/Document.cpp 2022-04-06 11:17:25 UTC (rev 292459)
@@ -8414,7 +8414,7 @@
std::optional<FrameIdentifier> Document::frameID() const
{
- return m_frame->loader().frameID();
+ return m_frame ? m_frame->loader().frameID() : std::nullopt;
}
void Document::registerArticleElement(Element& article)
@@ -8780,6 +8780,24 @@
auto controllingServiceWorkerRegistrationIdentifier = activeServiceWorker() ? std::make_optional<ServiceWorkerRegistrationIdentifier>(activeServiceWorker()->registrationIdentifier()) : std::nullopt;
m_serviceWorkerConnection->registerServiceWorkerClient(topOrigin(), ServiceWorkerClientData::from(*this), controllingServiceWorkerRegistrationIdentifier, userAgent(url()));
}
+
+void Document::navigateFromServiceWorker(const URL& url, CompletionHandler<void(bool)>&& callback)
+{
+ if (activeDOMObjectsAreSuspended() || activeDOMObjectsAreStopped()) {
+ callback(false);
+ return;
+ }
+ eventLoop().queueTask(TaskSource::DOMManipulation, [weakThis = WeakPtr { *this }, url, callback = WTFMove(callback)]() mutable {
+ auto* frame = weakThis ? weakThis->frame() : nullptr;
+ if (!frame) {
+ callback(false);
+ return;
+ }
+ frame->navigationScheduler().scheduleLocationChange(*weakThis, weakThis->securityOrigin(), url, frame->loader().outgoingReferrer(), LockHistory::Yes, LockBackForwardList::No, [callback = WTFMove(callback)]() mutable {
+ callback(true);
+ });
+ });
+}
#endif
String Document::signedPublicKeyAndChallengeString(unsigned keySizeIndex, const String& challengeString, const URL& url)
Modified: trunk/Source/WebCore/dom/Document.h (292458 => 292459)
--- trunk/Source/WebCore/dom/Document.h 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebCore/dom/Document.h 2022-04-06 11:17:25 UTC (rev 292459)
@@ -1569,6 +1569,7 @@
#if ENABLE(SERVICE_WORKER)
void setServiceWorkerConnection(SWClientConnection*);
void updateServiceWorkerClientData();
+ WEBCORE_EXPORT void navigateFromServiceWorker(const URL&, CompletionHandler<void(bool)>&&);
#endif
#if ENABLE(VIDEO)
Modified: trunk/Source/WebCore/workers/service/ServiceWorkerClientData.cpp (292458 => 292459)
--- trunk/Source/WebCore/workers/service/ServiceWorkerClientData.cpp 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebCore/workers/service/ServiceWorkerClientData.cpp 2022-04-06 11:17:25 UTC (rev 292459)
@@ -59,12 +59,12 @@
ServiceWorkerClientData ServiceWorkerClientData::isolatedCopy() const &
{
- return { identifier, type, frameType, url.isolatedCopy(), pageIdentifier, lastNavigationWasAppInitiated, isVisible, isFocused, focusOrder, crossThreadCopy(ancestorOrigins) };
+ return { identifier, type, frameType, url.isolatedCopy(), pageIdentifier, frameIdentifier, lastNavigationWasAppInitiated, isVisible, isFocused, focusOrder, crossThreadCopy(ancestorOrigins) };
}
ServiceWorkerClientData ServiceWorkerClientData::isolatedCopy() &&
{
- return { identifier, type, frameType, WTFMove(url).isolatedCopy(), pageIdentifier, lastNavigationWasAppInitiated, isVisible, isFocused, focusOrder, crossThreadCopy(WTFMove(ancestorOrigins)) };
+ return { identifier, type, frameType, WTFMove(url).isolatedCopy(), pageIdentifier, frameIdentifier, lastNavigationWasAppInitiated, isVisible, isFocused, focusOrder, crossThreadCopy(WTFMove(ancestorOrigins)) };
}
ServiceWorkerClientData ServiceWorkerClientData::from(ScriptExecutionContext& context)
@@ -87,6 +87,7 @@
toServiceWorkerClientFrameType(context),
document.creationURL(),
document.pageID(),
+ document.frameID(),
lastNavigationWasAppInitiated,
!document.hidden(),
document.hasFocus(),
Modified: trunk/Source/WebCore/workers/service/ServiceWorkerClientData.h (292458 => 292459)
--- trunk/Source/WebCore/workers/service/ServiceWorkerClientData.h 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebCore/workers/service/ServiceWorkerClientData.h 2022-04-06 11:17:25 UTC (rev 292459)
@@ -27,6 +27,7 @@
#if ENABLE(SERVICE_WORKER)
+#include "FrameIdentifier.h"
#include "PageIdentifier.h"
#include "ProcessQualified.h"
#include "ScriptExecutionContextIdentifier.h"
@@ -49,6 +50,7 @@
ServiceWorkerClientFrameType frameType;
URL url;
std::optional<PageIdentifier> pageIdentifier;
+ std::optional<FrameIdentifier> frameIdentifier;
LastNavigationWasAppInitiated lastNavigationWasAppInitiated;
bool isVisible { false };
bool isFocused { false };
@@ -67,7 +69,7 @@
template<class Encoder>
void ServiceWorkerClientData::encode(Encoder& encoder) const
{
- encoder << identifier << type << frameType << url << pageIdentifier << lastNavigationWasAppInitiated << isVisible << isFocused << focusOrder << ancestorOrigins;
+ encoder << identifier << type << frameType << url << pageIdentifier << frameIdentifier << lastNavigationWasAppInitiated << isVisible << isFocused << focusOrder << ancestorOrigins;
}
template<class Decoder>
@@ -98,6 +100,11 @@
if (!pageIdentifier)
return std::nullopt;
+ std::optional<std::optional<FrameIdentifier>> frameIdentifier;
+ decoder >> frameIdentifier;
+ if (!frameIdentifier)
+ return std::nullopt;
+
std::optional<LastNavigationWasAppInitiated> lastNavigationWasAppInitiated;
decoder >> lastNavigationWasAppInitiated;
if (!lastNavigationWasAppInitiated)
@@ -123,7 +130,7 @@
if (!ancestorOrigins)
return std::nullopt;
- return { { WTFMove(*identifier), WTFMove(*type), WTFMove(*frameType), WTFMove(*url), WTFMove(*pageIdentifier), WTFMove(*lastNavigationWasAppInitiated), WTFMove(*isVisible), WTFMove(*isFocused), WTFMove(*focusOrder), WTFMove(*ancestorOrigins) } };
+ return { { WTFMove(*identifier), WTFMove(*type), WTFMove(*frameType), WTFMove(*url), WTFMove(*pageIdentifier), WTFMove(*frameIdentifier), WTFMove(*lastNavigationWasAppInitiated), WTFMove(*isVisible), WTFMove(*isFocused), WTFMove(*focusOrder), WTFMove(*ancestorOrigins) } };
}
using ServiceWorkerClientsMatchAllCallback = CompletionHandler<void(Vector<ServiceWorkerClientData>&&)>;
Modified: trunk/Source/WebCore/workers/service/ServiceWorkerWindowClient.cpp (292458 => 292459)
--- trunk/Source/WebCore/workers/service/ServiceWorkerWindowClient.cpp 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebCore/workers/service/ServiceWorkerWindowClient.cpp 2022-04-06 11:17:25 UTC (rev 292459)
@@ -71,10 +71,48 @@
});
}
-void ServiceWorkerWindowClient::navigate(ScriptExecutionContext&, const String& url, Ref<DeferredPromise>&& promise)
+void ServiceWorkerWindowClient::navigate(ScriptExecutionContext& context, const String& urlString, Ref<DeferredPromise>&& promise)
{
- UNUSED_PARAM(url);
- promise->reject(Exception { NotSupportedError, "windowClient.navigate() is not yet supported"_s });
+ auto url = ""
+
+ if (!url.isValid()) {
+ promise->reject(Exception { TypeError, makeString("URL string ", urlString, " cannot successfully be parsed") });
+ return;
+ }
+
+ if (url.protocolIsAbout()) {
+ promise->reject(Exception { TypeError, makeString("ServiceWorkerClients.navigate() cannot be called with URL ", url.string()) });
+ return;
+ }
+
+ // We implement step 4 (checking of client's active service worker) in network process as we cannot do it synchronously.
+ auto& serviceWorkerContext = downcast<ServiceWorkerGlobalScope>(context);
+ auto promiseIdentifier = serviceWorkerContext.clients().addPendingPromise(WTFMove(promise));
+ callOnMainThread([clientIdentifier = identifier(), promiseIdentifier, serviceWorkerIdentifier = serviceWorkerContext.thread().identifier(), url = "" mutable {
+ SWContextManager::singleton().connection()->navigate(clientIdentifier, serviceWorkerIdentifier, url, [promiseIdentifier, serviceWorkerIdentifier](auto result) mutable {
+ SWContextManager::singleton().postTaskToServiceWorker(serviceWorkerIdentifier, [promiseIdentifier, result = crossThreadCopy(WTFMove(result))](auto& serviceWorkerContext) mutable {
+ auto promise = serviceWorkerContext.clients().takePendingPromise(promiseIdentifier);
+ if (!promise)
+ return;
+
+ if (result.hasException()) {
+ promise->reject(result.releaseException());
+ return;
+ }
+ auto clientData = result.releaseReturnValue();
+ if (!clientData) {
+ promise->resolveWithJSValue(JSC::jsNull());
+ return;
+ }
+#if ASSERT_ENABLED
+ auto originData = SecurityOriginData::fromURL(clientData->url);
+ ClientOrigin clientOrigin { originData, originData };
+#endif
+ ASSERT(serviceWorkerContext.clientOrigin() == clientOrigin);
+ promise->template resolve<IDLInterface<ServiceWorkerWindowClient>>(ServiceWorkerWindowClient::create(serviceWorkerContext, WTFMove(*clientData)));
+ });
+ });
+ });
}
} // namespace WebCore
Modified: trunk/Source/WebCore/workers/service/context/SWContextManager.h (292458 => 292459)
--- trunk/Source/WebCore/workers/service/context/SWContextManager.h 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebCore/workers/service/context/SWContextManager.h 2022-04-06 11:17:25 UTC (rev 292459)
@@ -72,6 +72,9 @@
using OpenWindowCallback = CompletionHandler<void(ExceptionOr<std::optional<ServiceWorkerClientData>>&&)>;
virtual void openWindow(ServiceWorkerIdentifier, const URL&, OpenWindowCallback&&) = 0;
+ using NavigateCallback = CompletionHandler<void(ExceptionOr<std::optional<WebCore::ServiceWorkerClientData>>&&)>;
+ virtual void navigate(ScriptExecutionContextIdentifier, ServiceWorkerIdentifier, const URL&, NavigateCallback&&) = 0;
+
virtual void didFailHeartBeatCheck(ServiceWorkerIdentifier) = 0;
virtual void setAsInspected(ServiceWorkerIdentifier, bool) = 0;
Modified: trunk/Source/WebCore/workers/service/server/SWServer.cpp (292458 => 292459)
--- trunk/Source/WebCore/workers/service/server/SWServer.cpp 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebCore/workers/service/server/SWServer.cpp 2022-04-06 11:17:25 UTC (rev 292459)
@@ -1091,6 +1091,14 @@
m_clientToControllingRegistration.remove(registrationIterator);
}
+std::optional<ServiceWorkerRegistrationIdentifier> SWServer::clientIdentifierToControllingRegistration(ScriptExecutionContextIdentifier clientIdentifier) const
+{
+ auto registrationIterator = m_clientToControllingRegistration.find(clientIdentifier);
+ if (registrationIterator == m_clientToControllingRegistration.end())
+ return { };
+ return registrationIterator->value;
+}
+
SWServer::ShouldDelayRemoval SWServer::removeContextConnectionIfPossible(const RegistrableDomain& domain)
{
if (m_clientsByRegistrableDomain.contains(domain))
Modified: trunk/Source/WebCore/workers/service/server/SWServer.h (292458 => 292459)
--- trunk/Source/WebCore/workers/service/server/SWServer.h 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebCore/workers/service/server/SWServer.h 2022-04-06 11:17:25 UTC (rev 292459)
@@ -242,6 +242,9 @@
enum class ShouldDelayRemoval : bool { No, Yes };
ShouldDelayRemoval removeContextConnectionIfPossible(const RegistrableDomain&);
+ std::optional<ServiceWorkerRegistrationIdentifier> clientIdentifierToControllingRegistration(ScriptExecutionContextIdentifier) const;
+ WEBCORE_EXPORT void forEachClientForOrigin(const ClientOrigin&, const Function<void(ServiceWorkerClientData&)>&);
+
private:
void validateRegistrationDomain(WebCore::RegistrableDomain, ServiceWorkerJobType, CompletionHandler<void(bool)>&&);
@@ -261,7 +264,6 @@
void installContextData(const ServiceWorkerContextData&);
SWServerRegistration* registrationFromServiceWorkerIdentifier(ServiceWorkerIdentifier);
- void forEachClientForOrigin(const ClientOrigin&, const Function<void(ServiceWorkerClientData&)>&);
void performGetOriginsWithRegistrationsCallbacks();
Modified: trunk/Source/WebCore/workers/service/server/SWServerWorker.cpp (292458 => 292459)
--- trunk/Source/WebCore/workers/service/server/SWServerWorker.cpp 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebCore/workers/service/server/SWServerWorker.cpp 2022-04-06 11:17:25 UTC (rev 292459)
@@ -419,6 +419,14 @@
m_server->removeContextConnectionIfPossible(registrableDomain());
}
+bool SWServerWorker::isClientActiveServiceWorker(ScriptExecutionContextIdentifier clientIdentifier) const
+{
+ if (!m_server)
+ return false;
+ auto registrationIdentifier = m_server->clientIdentifierToControllingRegistration(clientIdentifier);
+ return registrationIdentifier == m_data.registrationIdentifier;
+}
+
} // namespace WebCore
#endif // ENABLE(SERVICE_WORKER)
Modified: trunk/Source/WebCore/workers/service/server/SWServerWorker.h (292458 => 292459)
--- trunk/Source/WebCore/workers/service/server/SWServerWorker.h 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebCore/workers/service/server/SWServerWorker.h 2022-04-06 11:17:25 UTC (rev 292459)
@@ -141,6 +141,8 @@
bool shouldContinue() const { return !!m_functionalEventCounter || m_isInspected; }
+ WEBCORE_EXPORT bool isClientActiveServiceWorker(ScriptExecutionContextIdentifier) const;
+
private:
SWServerWorker(SWServer&, SWServerRegistration&, const URL&, const ScriptBuffer&, const CertificateInfo&, const ContentSecurityPolicyResponseHeaders&, const CrossOriginEmbedderPolicy&, String&& referrerPolicy, WorkerType, ServiceWorkerIdentifier, HashMap<URL, ServiceWorkerContextData::ImportedScript>&&);
Modified: trunk/Source/WebKit/ChangeLog (292458 => 292459)
--- trunk/Source/WebKit/ChangeLog 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/ChangeLog 2022-04-06 11:17:25 UTC (rev 292459)
@@ -1,3 +1,36 @@
+2022-04-06 Youenn Fablet <you...@apple.com>
+
+ Implement ServiceWorkerWindowClient.navigate
+ https://bugs.webkit.org/show_bug.cgi?id=238738
+
+ Reviewed by Chris Dumez.
+
+ When receiving a request to navigate a client, send message to UIProcess.
+ UIProcess locates the WebFrameProxy which will send an IPC message to the WebProcess to do navigation.
+ We keep track of the navigation within WebFrameProxy.
+ In case of process swapping, we make sure to transfer the WebFrameProxy callback.
+ In case of policy decision to stop loads, we resolve the promise with an empty client.
+ A follow-up patch may reject the promise once the expected behavior will be clarified.
+
+ * NetworkProcess/ServiceWorker/WebSWServerConnection.cpp:
+ * NetworkProcess/ServiceWorker/WebSWServerConnection.h:
+ * NetworkProcess/ServiceWorker/WebSWServerToContextConnection.cpp:
+ * NetworkProcess/ServiceWorker/WebSWServerToContextConnection.h:
+ * NetworkProcess/ServiceWorker/WebSWServerToContextConnection.messages.in:
+ * UIProcess/Network/NetworkProcessProxy.cpp:
+ * UIProcess/Network/NetworkProcessProxy.h:
+ * UIProcess/Network/NetworkProcessProxy.messages.in:
+ * UIProcess/ProvisionalPageProxy.cpp:
+ * UIProcess/WebFrameProxy.cpp:
+ * UIProcess/WebFrameProxy.h:
+ * UIProcess/WebPageProxy.cpp:
+ * WebProcess/Storage/WebSWContextManagerConnection.cpp:
+ * WebProcess/Storage/WebSWContextManagerConnection.h:
+ * WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp:
+ * WebProcess/WebPage/WebPage.cpp:
+ * WebProcess/WebPage/WebPage.h:
+ * WebProcess/WebPage/WebPage.messages.in:
+
2022-04-06 Zan Dobersek <zdober...@igalia.com>
[Unix] Adopt UnixFileDescriptor in IPC::Attachment
Modified: trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.cpp (292458 => 292459)
--- trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.cpp 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.cpp 2022-04-06 11:17:25 UTC (rev 292459)
@@ -164,11 +164,12 @@
send(Messages::WebSWClientConnection::UpdateWorkerState(worker, state));
}
-void WebSWServerConnection::controlClient(const Vector<RefPtr<SecurityOrigin>>& frameAncestorOrigins, ScriptExecutionContextIdentifier clientIdentifier, SWServerRegistration& registration, const ResourceRequest& request)
+void WebSWServerConnection::controlClient(const NetworkResourceLoadParameters& parameters, SWServerRegistration& registration, const ResourceRequest& request)
{
+ auto clientIdentifier = *parameters.options.clientIdentifier;
// As per step 12 of https://w3c.github.io/ServiceWorker/#on-fetch-request-algorithm, the active service worker should be controlling the document.
- // We register a temporary service worker client using the identifier provided by DocumentLoader and notify DocumentLoader about it.
- // If notification is successful, DocumentLoader will unregister the temporary service worker client just after the document is created and registered as a client.
+ // We register the service worker client using the identifier provided by DocumentLoader and notify DocumentLoader about it.
+ // If notification is successful, DocumentLoader is responsible to unregister the service worker client as needed.
sendWithAsyncReply(Messages::WebSWClientConnection::SetDocumentIsControlled { clientIdentifier, registration.data() }, [weakThis = WeakPtr { *this }, this, clientIdentifier](bool isSuccess) {
if (!weakThis || isSuccess)
return;
@@ -175,8 +176,8 @@
unregisterServiceWorkerClient(clientIdentifier);
});
- auto ancestorOrigins = map(frameAncestorOrigins, [](auto& origin) { return origin->toString(); });
- ServiceWorkerClientData data { clientIdentifier, ServiceWorkerClientType::Window, ServiceWorkerClientFrameType::None, request.url(), { }, request.isAppInitiated() ? WebCore::LastNavigationWasAppInitiated::Yes : WebCore::LastNavigationWasAppInitiated::No, false, false, 0, WTFMove(ancestorOrigins) };
+ auto ancestorOrigins = map(parameters.frameAncestorOrigins, [](auto& origin) { return origin->toString(); });
+ ServiceWorkerClientData data { clientIdentifier, ServiceWorkerClientType::Window, ServiceWorkerClientFrameType::None, request.url(), parameters.webPageID, parameters.webFrameID, request.isAppInitiated() ? WebCore::LastNavigationWasAppInitiated::Yes : WebCore::LastNavigationWasAppInitiated::No, false, false, 0, WTFMove(ancestorOrigins) };
registerServiceWorkerClient(SecurityOriginData { registration.key().topOrigin() }, WTFMove(data), registration.identifier(), request.httpUserAgent());
}
@@ -201,7 +202,7 @@
return nullptr;
serviceWorkerRegistrationIdentifier = registration->identifier();
- controlClient(loader.parameters().frameAncestorOrigins, *loader.parameters().options.clientIdentifier, *registration, request);
+ controlClient(loader.parameters(), *registration, request);
loader.setResultingClientIdentifier(loader.parameters().options.clientIdentifier->toString());
} else {
if (!loader.parameters().serviceWorkerRegistrationIdentifier)
Modified: trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.h (292458 => 292459)
--- trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.h 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerConnection.h 2022-04-06 11:17:25 UTC (rev 292459)
@@ -61,6 +61,7 @@
namespace WebKit {
class NetworkProcess;
+class NetworkResourceLoadParameters;
class NetworkResourceLoader;
class ServiceWorkerFetchTask;
@@ -83,8 +84,6 @@
std::unique_ptr<ServiceWorkerFetchTask> createFetchTask(NetworkResourceLoader&, const WebCore::ResourceRequest&);
void fetchTaskTimedOut(WebCore::ServiceWorkerIdentifier);
- void focusServiceWorkerClient(WebCore::ScriptExecutionContextIdentifier, CompletionHandler<void(std::optional<WebCore::ServiceWorkerClientData>&&)>&&);
-
private:
// Implement SWServer::Connection (Messages to the client WebProcess)
void rejectJobInClient(WebCore::ServiceWorkerJobIdentifier, const WebCore::ExceptionData&) final;
@@ -97,6 +96,7 @@
void setRegistrationLastUpdateTime(WebCore::ServiceWorkerRegistrationIdentifier, WallTime) final;
void setRegistrationUpdateViaCache(WebCore::ServiceWorkerRegistrationIdentifier, WebCore::ServiceWorkerUpdateViaCache) final;
void notifyClientsOfControllerChange(const HashSet<WebCore::ScriptExecutionContextIdentifier>& contextIdentifiers, const WebCore::ServiceWorkerData& newController);
+ void focusServiceWorkerClient(WebCore::ScriptExecutionContextIdentifier, CompletionHandler<void(std::optional<WebCore::ServiceWorkerClientData>&&)>&&) final;
void scheduleJobInServer(WebCore::ServiceWorkerJobData&&);
@@ -129,7 +129,7 @@
void getPushPermissionState(WebCore::ServiceWorkerRegistrationIdentifier, CompletionHandler<void(Expected<uint8_t, WebCore::ExceptionData>&&)>&&);
void postMessageToServiceWorker(WebCore::ServiceWorkerIdentifier destination, WebCore::MessageWithMessagePorts&&, const WebCore::ServiceWorkerOrClientIdentifier& source);
- void controlClient(const Vector<RefPtr<WebCore::SecurityOrigin>>&, WebCore::ScriptExecutionContextIdentifier, WebCore::SWServerRegistration&, const WebCore::ResourceRequest&);
+ void controlClient(const NetworkResourceLoadParameters&, WebCore::SWServerRegistration&, const WebCore::ResourceRequest&);
using ExceptionOrVoidCallback = CompletionHandler<void(std::optional<WebCore::ExceptionData>&&)>;
void enableNavigationPreload(WebCore::ServiceWorkerRegistrationIdentifier, ExceptionOrVoidCallback&&);
Modified: trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.cpp (292458 => 292459)
--- trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.cpp 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.cpp 2022-04-06 11:17:25 UTC (rev 292459)
@@ -273,6 +273,46 @@
connection->focusServiceWorkerClient(clientIdentifier, WTFMove(callback));
}
+void WebSWServerToContextConnection::navigate(ScriptExecutionContextIdentifier clientIdentifier, ServiceWorkerIdentifier serviceWorkerIdentifier, const URL& url, CompletionHandler<void(Expected<std::optional<ServiceWorkerClientData>, ExceptionData>&&)>&& callback)
+{
+ auto* worker = SWServerWorker::existingWorkerForIdentifier(serviceWorkerIdentifier);
+ if (!worker) {
+ callback(makeUnexpected(ExceptionData { TypeError, "no service worker"_s }));
+ return;
+ }
+
+ if (!worker->isClientActiveServiceWorker(clientIdentifier)) {
+ callback(makeUnexpected(ExceptionData { TypeError, "service worker is not the client active service worker"_s }));
+ return;
+ }
+
+ auto data = ""
+ if (!data || !data->pageIdentifier || !data->frameIdentifier) {
+ callback(makeUnexpected(ExceptionData { TypeError, "cannot navigate service worker client"_s }));
+ return;
+ }
+
+ auto frameIdentifier = *data->frameIdentifier;
+ m_connection.networkProcess().parentProcessConnection()->sendWithAsyncReply(Messages::NetworkProcessProxy::NavigateServiceWorkerClient { frameIdentifier, clientIdentifier, url }, [weakThis = WeakPtr { *this }, frameIdentifier, url, clientOrigin = worker->origin(), callback = WTFMove(callback)](auto pageIdentifier) mutable {
+ if (!weakThis || !weakThis->server()) {
+ callback(makeUnexpected(ExceptionData { TypeError, "service worker is gone"_s }));
+ return;
+ }
+
+ if (!pageIdentifier) {
+ callback(makeUnexpected(ExceptionData { TypeError, "navigate failed"_s }));
+ return;
+ }
+
+ std::optional<ServiceWorkerClientData> clientData;
+ weakThis->server()->forEachClientForOrigin(clientOrigin, [pageIdentifier, frameIdentifier, url, &clientData](auto& data) {
+ if (!clientData && data.pageIdentifier && *data.pageIdentifier == *pageIdentifier && data.frameIdentifier && *data.frameIdentifier == frameIdentifier && equalIgnoringFragmentIdentifier(data.url, url))
+ clientData = data;
+ });
+ callback(WTFMove(clientData));
+ }, 0);
+}
+
} // namespace WebKit
#endif // ENABLE(SERVICE_WORKER)
Modified: trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.h (292458 => 292459)
--- trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.h 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.h 2022-04-06 11:17:25 UTC (rev 292459)
@@ -100,6 +100,7 @@
void fireNotificationEvent(WebCore::ServiceWorkerIdentifier, const WebCore::NotificationData&, WebCore::NotificationEventType, CompletionHandler<void(bool)>&&) final;
void close() final;
void focus(WebCore::ScriptExecutionContextIdentifier, CompletionHandler<void(std::optional<WebCore::ServiceWorkerClientData>&&)>&&);
+ void navigate(WebCore::ScriptExecutionContextIdentifier, WebCore::ServiceWorkerIdentifier, const URL&, CompletionHandler<void(Expected<std::optional<WebCore::ServiceWorkerClientData>, WebCore::ExceptionData>&&)>&&);
void connectionIsNoLongerNeeded() final;
void terminateDueToUnresponsiveness() final;
Modified: trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.messages.in (292458 => 292459)
--- trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.messages.in 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/NetworkProcess/ServiceWorker/WebSWServerToContextConnection.messages.in 2022-04-06 11:17:25 UTC (rev 292459)
@@ -36,6 +36,7 @@
MatchAll(uint64_t matchAllRequestIdentifier, WebCore::ServiceWorkerIdentifier serviceWorkerIdentifier, struct WebCore::ServiceWorkerClientQueryOptions options);
Claim(WebCore::ServiceWorkerIdentifier serviceWorkerIdentifier) -> (std::optional<WebCore::ExceptionData> result)
Focus(WebCore::ScriptExecutionContextIdentifier serviceWorkerClientIdentifier) -> (std::optional<WebCore::ServiceWorkerClientData> result)
+ Navigate(WebCore::ScriptExecutionContextIdentifier clientIdentifier, WebCore::ServiceWorkerIdentifier serviceWorkerIdentifier, URL url) -> (Expected<std::optional<WebCore::ServiceWorkerClientData>, WebCore::ExceptionData> result)
SetScriptResource(WebCore::ServiceWorkerIdentifier identifier, URL scriptURL, WebCore::ServiceWorkerContextData::ImportedScript script)
PostMessageToServiceWorkerClient(WebCore::ScriptExecutionContextIdentifier destination, struct WebCore::MessageWithMessagePorts message, WebCore::ServiceWorkerIdentifier source, String sourceOrigin)
DidFailHeartBeatCheck(WebCore::ServiceWorkerIdentifier identifier)
Modified: trunk/Source/WebKit/UIProcess/Network/NetworkProcessProxy.cpp (292458 => 292459)
--- trunk/Source/WebKit/UIProcess/Network/NetworkProcessProxy.cpp 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/UIProcess/Network/NetworkProcessProxy.cpp 2022-04-06 11:17:25 UTC (rev 292459)
@@ -1805,6 +1805,17 @@
callback(std::nullopt);
}
+void NetworkProcessProxy::navigateServiceWorkerClient(WebCore::FrameIdentifier frameIdentifier, WebCore::ScriptExecutionContextIdentifier documentIdentifier, const URL& url, CompletionHandler<void(std::optional<WebCore::PageIdentifier>)>&& callback)
+{
+ auto* process = WebProcessProxy::processForIdentifier(documentIdentifier.processIdentifier());
+ auto* frame = process ? process->webFrame(frameIdentifier) : nullptr;
+ if (!frame) {
+ callback({ });
+ return;
+ }
+ frame->navigateServiceWorkerClient(documentIdentifier, url, WTFMove(callback));
+}
+
void NetworkProcessProxy::applicationDidEnterBackground()
{
send(Messages::NetworkProcess::ApplicationDidEnterBackground(), 0);
Modified: trunk/Source/WebKit/UIProcess/Network/NetworkProcessProxy.h (292458 => 292459)
--- trunk/Source/WebKit/UIProcess/Network/NetworkProcessProxy.h 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/UIProcess/Network/NetworkProcessProxy.h 2022-04-06 11:17:25 UTC (rev 292459)
@@ -304,6 +304,8 @@
void openWindowFromServiceWorker(PAL::SessionID, const String& urlString, const WebCore::SecurityOriginData& serviceWorkerOrigin, CompletionHandler<void(std::optional<WebCore::PageIdentifier>&&)>&&);
+ void navigateServiceWorkerClient(WebCore::FrameIdentifier, WebCore::ScriptExecutionContextIdentifier, const URL&, CompletionHandler<void(std::optional<WebCore::PageIdentifier>)>&&);
+
private:
explicit NetworkProcessProxy();
Modified: trunk/Source/WebKit/UIProcess/Network/NetworkProcessProxy.messages.in (292458 => 292459)
--- trunk/Source/WebKit/UIProcess/Network/NetworkProcessProxy.messages.in 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/UIProcess/Network/NetworkProcessProxy.messages.in 2022-04-06 11:17:25 UTC (rev 292459)
@@ -93,6 +93,7 @@
DataTaskDidCompleteWithError(WebKit::DataTaskIdentifier identifier, WebCore::ResourceError error)
OpenWindowFromServiceWorker(PAL::SessionID sessionID, String urlString, struct WebCore::SecurityOriginData serviceWorkerOrigin) -> (std::optional<WebCore::PageIdentifier> newPage)
+ NavigateServiceWorkerClient(WebCore::FrameIdentifier frameIdentifier, WebCore::ScriptExecutionContextIdentifier documentIdentifier, URL url) -> (std::optional<WebCore::PageIdentifier> page)
}
}
Modified: trunk/Source/WebKit/UIProcess/ProvisionalPageProxy.cpp (292458 => 292459)
--- trunk/Source/WebKit/UIProcess/ProvisionalPageProxy.cpp 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/UIProcess/ProvisionalPageProxy.cpp 2022-04-06 11:17:25 UTC (rev 292459)
@@ -238,8 +238,10 @@
// Restore the main frame's committed URL as some clients may rely on it until the next load is committed.
RefPtr previousMainFrame = m_page.mainFrame();
- if (previousMainFrame)
+ if (previousMainFrame) {
m_mainFrame->frameLoadState().setURL(previousMainFrame->url());
+ previousMainFrame->transferNavigationCallbackToFrame(*m_mainFrame);
+ }
// Normally, notification of a server redirect comes from the WebContent process.
// If we are process swapping in response to a server redirect then that notification will not come from the new WebContent process.
Modified: trunk/Source/WebKit/UIProcess/WebFrameProxy.cpp (292458 => 292459)
--- trunk/Source/WebKit/UIProcess/WebFrameProxy.cpp 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/UIProcess/WebFrameProxy.cpp 2022-04-06 11:17:25 UTC (rev 292459)
@@ -58,6 +58,9 @@
#if PLATFORM(GTK)
WebPasteboardProxy::singleton().didDestroyFrame(this);
#endif
+
+ if (m_navigateCallback)
+ m_navigateCallback({ });
}
void WebFrameProxy::webProcessWillShutDown()
@@ -68,6 +71,9 @@
m_activeListener->ignore();
m_activeListener = nullptr;
}
+
+ if (m_navigateCallback)
+ m_navigateCallback({ });
}
bool WebFrameProxy::isMainFrame() const
@@ -78,6 +84,38 @@
return this == m_page->mainFrame() || (m_page->provisionalPageProxy() && this == m_page->provisionalPageProxy()->mainFrame());
}
+std::optional<PageIdentifier> WebFrameProxy::pageIdentifier() const
+{
+ if (!m_page)
+ return { };
+ return m_page->webPageID();
+}
+
+void WebFrameProxy::navigateServiceWorkerClient(WebCore::ScriptExecutionContextIdentifier documentIdentifier, const URL& url, CompletionHandler<void(std::optional<PageIdentifier>)>&& callback)
+{
+ if (!m_page) {
+ callback({ });
+ return;
+ }
+
+ m_page->sendWithAsyncReply(Messages::WebPage::NavigateServiceWorkerClient { documentIdentifier, url }, [this, protectedThis = Ref { *this }, url, callback = WTFMove(callback)](bool result) mutable {
+ if (!result) {
+ callback({ });
+ return;
+ }
+
+ if (!m_activeListener) {
+ callback(pageIdentifier());
+ return;
+ }
+
+ if (m_navigateCallback)
+ m_navigateCallback({ });
+
+ m_navigateCallback = WTFMove(callback);
+ });
+}
+
void WebFrameProxy::loadURL(const URL& url, const String& referrer)
{
if (!m_page)
@@ -95,7 +133,7 @@
m_page->send(Messages::WebPage::LoadDataInFrame(data, MIMEType, encodingName, baseURL, m_frameID));
}
-void WebFrameProxy::stopLoading() const
+void WebFrameProxy::stopLoading()
{
if (!m_page)
return;
@@ -104,6 +142,9 @@
return;
m_page->send(Messages::WebPage::StopLoadingFrame(m_frameID));
+
+ if (m_navigateCallback)
+ m_navigateCallback({ });
}
bool WebFrameProxy::canProvideSource() const
@@ -160,6 +201,9 @@
void WebFrameProxy::didFailProvisionalLoad()
{
m_frameLoadState.didFailProvisionalLoad();
+
+ if (m_navigateCallback)
+ m_navigateCallback({ });
}
void WebFrameProxy::didCommitLoad(const String& contentType, WebCertificateInfo& certificateInfo, bool containsPluginDocument)
@@ -175,11 +219,17 @@
void WebFrameProxy::didFinishLoad()
{
m_frameLoadState.didFinishLoad();
+
+ if (m_navigateCallback)
+ m_navigateCallback(pageIdentifier());
}
void WebFrameProxy::didFailLoad()
{
m_frameLoadState.didFailLoad();
+
+ if (m_navigateCallback)
+ m_navigateCallback({ });
}
void WebFrameProxy::didSameDocumentNavigation(const URL& url)
@@ -197,6 +247,9 @@
if (m_activeListener)
m_activeListener->ignore();
m_activeListener = WebFramePolicyListenerProxy::create([this, protectedThis = Ref { *this }, completionHandler = WTFMove(completionHandler)] (PolicyAction action, API::WebsitePolicies* policies, ProcessSwapRequestedByClient processSwapRequestedByClient, RefPtr<SafeBrowsingWarning>&& safeBrowsingWarning, std::optional<NavigatingToAppBoundDomain> isNavigatingToAppBoundDomain) mutable {
+ if (action != PolicyAction::Use && m_navigateCallback)
+ m_navigateCallback(pageIdentifier());
+
completionHandler(action, policies, processSwapRequestedByClient, WTFMove(safeBrowsingWarning), isNavigatingToAppBoundDomain);
m_activeListener = nullptr;
}, expectSafeBrowsingResult, expectAppBoundDomainResult);
Modified: trunk/Source/WebKit/UIProcess/WebFrameProxy.h (292458 => 292459)
--- trunk/Source/WebKit/UIProcess/WebFrameProxy.h 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/UIProcess/WebFrameProxy.h 2022-04-06 11:17:25 UTC (rev 292459)
@@ -77,10 +77,12 @@
FrameLoadState& frameLoadState() { return m_frameLoadState; }
+ void navigateServiceWorkerClient(WebCore::ScriptExecutionContextIdentifier, const URL&, CompletionHandler<void(std::optional<WebCore::PageIdentifier>)>&&);
+
void loadURL(const URL&, const String& referrer = String());
// Sub frames only. For main frames, use WebPageProxy::loadData.
void loadData(const IPC::DataReference&, const String& MIMEType, const String& encodingName, const URL& baseURL);
- void stopLoading() const;
+ void stopLoading();
const URL& url() const { return m_frameLoadState.url(); }
const URL& provisionalURL() const { return m_frameLoadState.provisionalURL(); }
@@ -128,9 +130,13 @@
void collapseSelection();
#endif
+ void transferNavigationCallbackToFrame(WebFrameProxy& frame) { frame.m_navigateCallback = WTFMove(m_navigateCallback); }
+
private:
WebFrameProxy(WebPageProxy&, WebCore::FrameIdentifier);
+ std::optional<WebCore::PageIdentifier> pageIdentifier() const;
+
WeakPtr<WebPageProxy> m_page;
FrameLoadState m_frameLoadState;
@@ -144,6 +150,7 @@
#if ENABLE(CONTENT_FILTERING)
WebCore::ContentFilterUnblockHandler m_contentFilterUnblockHandler;
#endif
+ CompletionHandler<void(std::optional<WebCore::PageIdentifier>)> m_navigateCallback;
};
} // namespace WebKit
Modified: trunk/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.cpp (292458 => 292459)
--- trunk/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.cpp 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.cpp 2022-04-06 11:17:25 UTC (rev 292459)
@@ -355,6 +355,17 @@
});
}
+void WebSWContextManagerConnection::navigate(ScriptExecutionContextIdentifier clientIdentifier, ServiceWorkerIdentifier serviceWorkerIdentifier, const URL& url, NavigateCallback&& callback)
+{
+ m_connectionToNetworkProcess->sendWithAsyncReply(Messages::WebSWServerToContextConnection::Navigate { clientIdentifier, serviceWorkerIdentifier, url }, [callback = WTFMove(callback)](auto&& result) mutable {
+ if (!result.has_value()) {
+ callback(WTFMove(result).error().toException());
+ return;
+ }
+ callback(WTFMove(result).value());
+ });
+}
+
void WebSWContextManagerConnection::focus(ScriptExecutionContextIdentifier clientIdentifier, CompletionHandler<void(std::optional<WebCore::ServiceWorkerClientData>&&)>&& callback)
{
m_connectionToNetworkProcess->sendWithAsyncReply(Messages::WebSWServerToContextConnection::Focus { clientIdentifier }, WTFMove(callback));
Modified: trunk/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.h (292458 => 292459)
--- trunk/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.h 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.h 2022-04-06 11:17:25 UTC (rev 292459)
@@ -82,6 +82,7 @@
void matchAll(WebCore::ServiceWorkerIdentifier, const WebCore::ServiceWorkerClientQueryOptions&, WebCore::ServiceWorkerClientsMatchAllCallback&&) final;
void claim(WebCore::ServiceWorkerIdentifier, CompletionHandler<void(WebCore::ExceptionOr<void>&&)>&&) final;
void focus(WebCore::ScriptExecutionContextIdentifier, CompletionHandler<void(std::optional<WebCore::ServiceWorkerClientData>&&)>&&) final;
+ void navigate(WebCore::ScriptExecutionContextIdentifier, WebCore::ServiceWorkerIdentifier, const URL&, NavigateCallback&&) final;
void skipWaiting(WebCore::ServiceWorkerIdentifier, CompletionHandler<void()>&&) final;
void setScriptResource(WebCore::ServiceWorkerIdentifier, const URL&, const WebCore::ServiceWorkerContextData::ImportedScript&) final;
bool isThrottleable() const final;
Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp (292458 => 292459)
--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp 2022-04-06 11:17:25 UTC (rev 292459)
@@ -6439,6 +6439,22 @@
completionHandler(WebCore::interactionRegions(*m_page, rectInContentCoordinates));
}
+void WebPage::navigateServiceWorkerClient(ScriptExecutionContextIdentifier documentIdentifier, const URL& url, CompletionHandler<void(bool)>&& callback)
+{
+#if ENABLE(SERVICE_WORKER)
+ RefPtr document = Document::allDocumentsMap().get(documentIdentifier);
+ if (!document) {
+ callback(false);
+ return;
+ }
+ document->navigateFromServiceWorker(url, WTFMove(callback));
+#else
+ UNUSED_PARAM(documentIdentifier);
+ UNUSED_PARAM(url);
+ callback(false);
+#endif
+}
+
void WebPage::setAlwaysShowsHorizontalScroller(bool alwaysShowsHorizontalScroller)
{
if (alwaysShowsHorizontalScroller == m_alwaysShowsHorizontalScroller)
Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.h (292458 => 292459)
--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.h 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.h 2022-04-06 11:17:25 UTC (rev 292459)
@@ -945,6 +945,7 @@
void didEndUserTriggeredSelectionChanges();
void interactionRegions(WebCore::FloatRect rectInContentCoordinates, CompletionHandler<void(Vector<WebCore::InteractionRegion>)>&&);
+ void navigateServiceWorkerClient(WebCore::ScriptExecutionContextIdentifier, const URL&, CompletionHandler<void(bool)>&&);
#if PLATFORM(COCOA)
void platformInitializeAccessibility();
Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in (292458 => 292459)
--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in 2022-04-06 11:17:25 UTC (rev 292459)
@@ -683,4 +683,6 @@
ScrollToRect(WebCore::FloatRect targetRect, WebCore::FloatPoint origin)
InteractionRegions(WebCore::FloatRect rectInContentCoordinates) -> (Vector<WebCore::InteractionRegion> regions)
+
+ NavigateServiceWorkerClient(WebCore::ScriptExecutionContextIdentifier documentIdentifier, URL url) -> (bool result)
}
Modified: trunk/Tools/ChangeLog (292458 => 292459)
--- trunk/Tools/ChangeLog 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Tools/ChangeLog 2022-04-06 11:17:25 UTC (rev 292459)
@@ -1,5 +1,14 @@
2022-04-06 Youenn Fablet <you...@apple.com>
+ Implement ServiceWorkerWindowClient.navigate
+ https://bugs.webkit.org/show_bug.cgi?id=238738
+
+ Reviewed by Chris Dumez.
+
+ * TestWebKitAPI/Tests/WebKitCocoa/ServiceWorkerBasic.mm:
+
+2022-04-06 Youenn Fablet <you...@apple.com>
+
ServiceWorkerClients.openWindow should not need to get all clients asynchronously to resolve its promise
https://bugs.webkit.org/show_bug.cgi?id=238503
Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ServiceWorkerBasic.mm (292458 => 292459)
--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ServiceWorkerBasic.mm 2022-04-06 11:16:26 UTC (rev 292458)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ServiceWorkerBasic.mm 2022-04-06 11:17:25 UTC (rev 292459)
@@ -3047,3 +3047,172 @@
[webView evaluateJavaScript:@"openWindowClient()" completionHandler: nil];
TestWebKitAPI::Util::run(&done);
}
+
+static constexpr auto ServiceWorkerWindowClientNavigateMain =
+"<div>test page</div>"
+"<script>"
+"let worker;"
+"async function registerServiceWorker() {"
+" try {"
+" const registration = await navigator.serviceWorker.register('/sw.js');"
+" if (registration.active) {"
+" worker = registration.active;"
+" alert('already active');"
+" return;"
+" }"
+" worker = registration.installing;"
+" worker.addEventListener('statechange', () => {"
+" if (worker.state == 'activated')"
+" alert('successfully registered');"
+" });"
+" } catch(e) {"
+" alert('Exception: ' + e);"
+" }"
+"}"
+"window._onload_ = registerServiceWorker;"
+""
+"function navigateOtherClientToURL(url) {"
+" worker.postMessage({navigateOtherClientToURL: url});"
+" navigator.serviceWorker._onmessage_ = (event) => {"
+" alert(event.data);"
+" };"
+"}"
+"</script>"_s;
+
+static constexpr auto ServiceWorkerWindowClientNavigateJS =
+"self.addEventListener('message', async (event) => {"
+" if (event.data && event.data.navigateOtherClientToURL) {"
+" let otherClient;"
+" let currentClients = await self.clients.matchAll();"
+" for (let client of currentClients) {"
+" if (client.id !== event.source.id)"
+" otherClient = client;"
+" }"
+" if (!otherClient) {"
+" event.source.postMessage('failed, no other client, client number = ' + currentClients.length);"
+" return;"
+" }"
+" await otherClient.navigate(event.data.navigateOtherClientToURL).then((client) => {"
+" event.source.postMessage(client ? 'client' : 'none');"
+" }, (e) => {"
+" event.source.postMessage('failed');"
+" });"
+" return;"
+" }"
+"});"_s;
+
+
+@interface ServiceWorkerPSONNavigationDelegate : NSObject <WKNavigationDelegatePrivate> {
+ @public void (^decidePolicyForNavigationAction)(WKNavigationAction *, void (^)(WKNavigationActionPolicy));
+ @public void (^didStartProvisionalNavigationHandler)();
+ @public void (^didCommitNavigationHandler)();
+}
+@end
+
+@implementation ServiceWorkerPSONNavigationDelegate
+
+- (instancetype) init
+{
+ self = [super init];
+ return self;
+}
+
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
+{
+ decisionHandler(WKNavigationActionPolicyAllow);
+}
+
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
+{
+ decisionHandler(WKNavigationResponsePolicyAllow);
+}
+
+@end
+
+TEST(ServiceWorker, WindowClientNavigate)
+{
+ [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
+
+ // Start with a clean slate data store
+ [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
+ done = true;
+ }];
+ TestWebKitAPI::Util::run(&done);
+ done = false;
+
+ auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+ for (_WKExperimentalFeature *feature in [WKPreferences _experimentalFeatures]) {
+ if ([feature.key isEqualToString:@"CrossOriginOpenerPolicyEnabled"])
+ [[configuration preferences] _setEnabled:YES forExperimentalFeature:feature];
+ else if ([feature.key isEqualToString:@"CrossOriginEmbedderPolicyEnabled"])
+ [[configuration preferences] _setEnabled:YES forExperimentalFeature:feature];
+ }
+
+ auto webView1 = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration.get() addToWindow:YES]);
+ auto webView2 = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration.get() addToWindow:YES]);
+
+ TestWebKitAPI::HTTPServer server({
+ { "/"_s, { ServiceWorkerWindowClientNavigateMain } },
+ { "/?test"_s, { ServiceWorkerWindowClientNavigateMain } },
+ { "/?swap"_s, { {{ "Content-Type"_s, "application/html"_s }, { "Cross-Origin-Opener-Policy"_s, "same-origin"_s } }, ServiceWorkerWindowClientNavigateMain } },
+ { "/sw.js"_s, { {{ "Content-Type"_s, "application/_javascript_"_s }}, ServiceWorkerWindowClientNavigateJS } }
+ }, TestWebKitAPI::HTTPServer::Protocol::Http, nullptr, nullptr, 8091);
+
+ [webView1 loadRequest:server.request()];
+ EXPECT_WK_STREQ([webView1 _test_waitForAlert], "successfully registered");
+ [webView2 loadRequest:server.request()];
+ EXPECT_WK_STREQ([webView2 _test_waitForAlert], "already active");
+
+ auto navigationDelegate = adoptNS([[ServiceWorkerPSONNavigationDelegate alloc] init]);
+ [webView2 setNavigationDelegate:navigationDelegate.get()];
+
+ auto *baseURL = [[server.request() URL] absoluteString];
+
+ [webView1 evaluateJavaScript:[NSString stringWithFormat:@"navigateOtherClientToURL('%@')", baseURL] completionHandler: nil];
+ EXPECT_WK_STREQ([webView1 _test_waitForAlert], "client");
+
+ [webView1 evaluateJavaScript:[NSString stringWithFormat:@"navigateOtherClientToURL('%@#test')", baseURL] completionHandler: nil];
+ EXPECT_WK_STREQ([webView1 _test_waitForAlert], "client");
+
+ [webView1 evaluateJavaScript:[NSString stringWithFormat:@"navigateOtherClientToURL('%@?test')", baseURL] completionHandler: nil];
+ EXPECT_WK_STREQ([webView1 _test_waitForAlert], "client");
+
+ [webView1 evaluateJavaScript:[NSString stringWithFormat:@"navigateOtherClientToURL('%@?swap')", baseURL] completionHandler: nil];
+ EXPECT_WK_STREQ([webView1 _test_waitForAlert], "client");
+}
+
+TEST(ServiceWorker, WindowClientNavigateCrossOrigin)
+{
+ [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
+
+ // Start with a clean slate data store
+ [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
+ done = true;
+ }];
+ TestWebKitAPI::Util::run(&done);
+ done = false;
+
+ auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+
+ auto webView1 = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration.get() addToWindow:YES]);
+ auto webView2 = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration.get() addToWindow:YES]);
+
+ TestWebKitAPI::HTTPServer server1({
+ { "/"_s, { ServiceWorkerWindowClientNavigateMain } },
+ { "/?test"_s, { ServiceWorkerWindowClientNavigateMain } },
+ { "/?swap"_s, { {{ "Content-Type"_s, "application/html"_s }, { "Cross-Origin-Opener-Policy"_s, "same-origin"_s } }, ServiceWorkerWindowClientNavigateMain } },
+ { "/sw.js"_s, { {{ "Content-Type"_s, "application/_javascript_"_s }}, ServiceWorkerWindowClientNavigateJS } }
+ }, TestWebKitAPI::HTTPServer::Protocol::Http, nullptr, nullptr, 8091);
+
+ TestWebKitAPI::HTTPServer server2({
+ { "/"_s, { ServiceWorkerWindowClientNavigateMain } },
+ { "/sw.js"_s, { {{ "Content-Type"_s, "application/_javascript_"_s }}, ServiceWorkerWindowClientNavigateJS } }
+ }, TestWebKitAPI::HTTPServer::Protocol::Http, nullptr, nullptr, 9091);
+
+ [webView1 loadRequest:server1.request()];
+ EXPECT_WK_STREQ([webView1 _test_waitForAlert], "successfully registered");
+ [webView2 loadRequest:server1.request()];
+ EXPECT_WK_STREQ([webView2 _test_waitForAlert], "already active");
+ [webView1 evaluateJavaScript:[NSString stringWithFormat:@"navigateOtherClientToURL('%@')", [[server2.request() URL] absoluteString]] completionHandler: nil];
+ EXPECT_WK_STREQ([webView1 _test_waitForAlert], "none");
+}