Skip to content

Commit

Permalink
Cherry-pick 757903c. rdar://problem/98144044
Browse files Browse the repository at this point in the history
    FetchEvent should not start its navigation preload response load if the preload was already used
    https://bugs.webkit.org/show_bug.cgi?id=245788
    rdar://98144044

    Reviewed by Alex Christensen.

    Creating the navigation preload promise was scheduling a load which is hitting the network if the preload is no longer in network process.
    This case happens if the navigation preload promise is actually used in respondWith.

    To circumvent this issue, we do not trigger the load if the preload is used in respondWith.

    Add logs to capture the fact that a load is not getting its expected preload.

    Drive-by fixes to actually use the current preload request and not the loader original request.
    This might change in case of redirections.

    Covered by newly added test which mirrors what we were testing for non-exposed navigation preloads.

    * LayoutTests/http/wpt/service-workers/fetch-service-worker-navigation-preload.https-expected.txt: Added.
    * LayoutTests/http/wpt/service-workers/fetch-service-worker-navigation-preload.https.html: Added.
    * LayoutTests/http/wpt/service-workers/fetch-service-worker-preload-worker.js:
    (event.event.request.url.includes):
    * LayoutTests/http/wpt/service-workers/resources/fetch-service-worker-preload-script.py:
    (main):
    * Source/WebCore/Modules/fetch/FetchResponse.cpp:
    (WebCore::FetchResponse::markAsUsedForPreload):
    (WebCore::FetchResponse::markAsDisturbed): Deleted.
    * Source/WebCore/Modules/fetch/FetchResponse.h:
    * Source/WebCore/workers/service/FetchEvent.cpp:
    (WebCore::FetchEvent::navigationPreloadIsReady):
    * Source/WebCore/workers/service/context/ServiceWorkerFetch.cpp:
    (WebCore::ServiceWorkerFetch::processResponse):
    * Source/WebKit/NetworkProcess/ServiceWorker/ServiceWorkerFetchTask.cpp:
    (WebKit::ServiceWorkerFetchTask::fromNavigationPreloader):
    (WebKit::ServiceWorkerFetchTask::ServiceWorkerFetchTask):
    (WebKit::ServiceWorkerFetchTask::loadBodyFromPreloader):

    Canonical link: https://commits.webkit.org/254992@main

Canonical link: https://commits.webkit.org/252432.476@safari-7614.2.9.1-branch
  • Loading branch information
youennf authored and alancoon committed Sep 29, 2022
1 parent 937cda6 commit 19f568b
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@


PASS Setup activating worker
PASS Service worker preloadResponse does not trigger an additional load

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<!doctype html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="resources/routines.js"></script>
</head>
<body>
<script>
var activeWorker;
var frame;
const channel = new MessageChannel();
const uuid = token();
const url = "/WebKit/service-workers/resources/fetch-service-worker-preload-script.py?useNavigationPreloadPromise=true&token=" + uuid;

function waitUntilActivating()
{
return new Promise(resolve => {
channel.port2.onmessage = (event) => {
if (event.data === "activating")
resolve();
};
});
}

function triggerActivation()
{
activeWorker.postMessage("activate");
}

promise_test(async (test) => {
if (window.testRunner) {
testRunner.setUseSeparateServiceWorkerProcess(true);
await fetch("").then(() => { }, () => { });
}

let registration = await navigator.serviceWorker.register("/WebKit/service-workers/fetch-service-worker-preload-worker.js", { scope : url });
if (!registration.installing) {
registration.unregister();
registration = await navigator.serviceWorker.register("/WebKit/service-workers/fetch-service-worker-preload-worker.js", { scope : url });
}

activeWorker = registration.installing;
activeWorker.postMessage({ port: channel.port1 }, [channel.port1]);

await waitUntilActivating();

if (registration.navigationPreload)
await registration.navigationPreload.enable();

triggerActivation();
}, "Setup activating worker");

promise_test(async (test) => {
await fetch(url + "&value=use-preload2", { method: 'POST' });


const frame = await withIframe(url);
assert_equals(frame.contentWindow.value, "use-preload2");

// We should have only one GET fetch to url: the service worker preload
const response = await fetch(url + "&count=True");
assert_equals(await response.text(), "1");
}, "Service worker preloadResponse does not trigger an additional load");
</script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,10 @@ onactivate = (event) => {
self.addEventListener('fetch', (event) => {
if (event.request.url.includes("no-fetch-event-handling"))
return;

if (event.request.url.includes("useNavigationPreloadPromise") && event.preloadResponse) {
event.respondWith(event.preloadResponse);
return;
}
event.respondWith(fetch(event.request));
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def main(request, response):
request.server.stash.take(testId)
request.server.stash.put(testId, request.GET[b'value'])
response.headers.set(b"Content-Type", b"text/ascii")
request.server.stash.put(countId, 0)
return b"updated to " + request.GET[b'value']

if request.GET.first(b"count", False):
Expand Down
3 changes: 2 additions & 1 deletion Source/WebCore/Modules/fetch/FetchResponse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -450,10 +450,11 @@ FetchResponse::ResponseData FetchResponse::consumeBody()
return body().take();
}

void FetchResponse::markAsDisturbed()
void FetchResponse::markAsUsedForPreload()
{
ASSERT(!m_isDisturbed);
m_isDisturbed = true;
m_isUsedForPreload = true;
}

void FetchResponse::consumeBodyReceivedByChunk(ConsumeDataByChunkCallback&& callback)
Expand Down
4 changes: 3 additions & 1 deletion Source/WebCore/Modules/fetch/FetchResponse.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ class FetchResponse final : public FetchBodyOwner {

void setIsNavigationPreload(bool isNavigationPreload) { m_isNavigationPreload = isNavigationPreload; }
bool isAvailableNavigationPreload() const { return m_isNavigationPreload && m_bodyLoader && !m_bodyLoader->hasLoader() && !hasReadableStreamBody(); }
void markAsDisturbed();
void markAsUsedForPreload();
bool isUsedForPreload() const { return m_isUsedForPreload; }

private:
FetchResponse(ScriptExecutionContext*, std::optional<FetchBody>&&, Ref<FetchHeaders>&&, ResourceResponse&&);
Expand Down Expand Up @@ -177,6 +178,7 @@ class FetchResponse final : public FetchBodyOwner {
NetworkLoadMetrics m_networkLoadMetrics;
bool m_hasInitializedInternalResponse { false };
bool m_isNavigationPreload { false };
bool m_isUsedForPreload { false };
};

} // namespace WebCore
3 changes: 2 additions & 1 deletion Source/WebCore/workers/service/FetchEvent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,8 @@ void FetchEvent::navigationPreloadIsReady(ResourceResponse&& response)

// We postpone the load to leave some time for the service worker to use the preload before loading it.
context->postTask([fetchResponse = WTFMove(fetchResponse), request = WTFMove(request)](auto& context) {
fetchResponse->startLoader(context, request.get(), cachedResourceRequestInitiators().navigation);
if (!fetchResponse->isUsedForPreload())
fetchResponse->startLoader(context, request.get(), cachedResourceRequestInitiators().navigation);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ static void processResponse(Ref<Client>&& client, Expected<Ref<FetchResponse>, s

if (response->isAvailableNavigationPreload()) {
client->usePreload();
response->markAsDisturbed();
response->markAsUsedForPreload();
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ std::unique_ptr<ServiceWorkerFetchTask> ServiceWorkerFetchTask::fromNavigationPr
return nullptr;

auto task = session ? session->navigationPreloaderTaskFromFetchIdentifier(*loader.parameters().navigationPreloadIdentifier) : nullptr;
if (!task || !task->m_preloader || task->m_isLoadingFromPreloader)
if (!task || !task->m_preloader || task->m_isLoadingFromPreloader) {
RELEASE_LOG_ERROR(ServiceWorker, "Unable to retrieve preloader, load will go to the network");
return nullptr;
}

auto preload = std::exchange(task->m_preloader, { });
return makeUnique<ServiceWorkerFetchTask>(swServerConnection, loader, WTFMove(preload));
Expand Down Expand Up @@ -96,7 +98,8 @@ ServiceWorkerFetchTask::ServiceWorkerFetchTask(WebSWServerConnection& swServerCo
m_timeoutTimer->startOneShot(loader.connectionToWebProcess().networkProcess().serviceWorkerFetchTimeout());
}

bool shouldDoNavigationPreload = session && isNavigationRequest(loader.parameters().options.destination) && loader.originalRequest().httpMethod() == "GET"_s;
bool shouldDoNavigationPreload = session && isNavigationRequest(loader.parameters().options.destination) && m_currentRequest.httpMethod() == "GET"_s;

if (shouldDoNavigationPreload && (!isWorkerReady || registration.navigationPreloadState().enabled)) {
NetworkLoadParameters parameters = loader.parameters();
parameters.request = m_currentRequest;
Expand Down Expand Up @@ -441,7 +444,7 @@ void ServiceWorkerFetchTask::loadBodyFromPreloader()
ASSERT(m_isLoadingFromPreloader);
if (!m_preloader) {
SWFETCH_RELEASE_LOG_ERROR("loadBodyFromPreloader preloader is null");
didFail(ResourceError(errorDomainWebKitInternal, 0, m_loader.originalRequest().url(), "Request canceled from preloader"_s, ResourceError::Type::Cancellation));
didFail(ResourceError(errorDomainWebKitInternal, 0, m_currentRequest.url(), "Request canceled from preloader"_s, ResourceError::Type::Cancellation));
return;
}

Expand Down

0 comments on commit 19f568b

Please sign in to comment.