Skip to content
Permalink
Browse files
SVG load external file via <use> ignores ServiceWorker when offline
https://bugs.webkit.org/show_bug.cgi?id=194948
rdar://problem/96318522

Reviewed by Alex Christensen.

Change destination from document to image for SVGUseElement loads.
This will make sure we reuse the document service worker for these loads.
image is the destination used by Chrome as well (Firefox is using the empty string),
as the SVG spec is not integrated with fetch spec yet.

* LayoutTests/http/wpt/service-workers/use-element-worker.js: Added.
* LayoutTests/http/wpt/service-workers/use-element.https-expected.txt: Added.
* LayoutTests/http/wpt/service-workers/use-element.https.html: Added.
* Source/WebCore/svg/SVGUseElement.cpp:

Canonical link: https://commits.webkit.org/252132@main
  • Loading branch information
youennf committed Jul 5, 2022
1 parent 5e8ab8c commit c923ed26acc47d4ba7a5a5563169db0b4b3c7a0d
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 0 deletions.
@@ -0,0 +1,18 @@
addEventListener('activate', function(event) {
event.waitUntil(self.clients.claim());
});

addEventListener("message", (e) => {
self.port = e.data;
self.port.postMessage("ok");
});

addEventListener("fetch", async (e) => {
if (e.request.url.indexOf("image1") !== -1) {
if (self.port)
self.port.postMessage({url: e.request.url, destination: e.request.destination});
const blob = new Blob(["<svg xmlns='http://www.w3.org/2000/svg' version='1.0'><text x='20' y='20' font-size='20' fill='black'>OK</text></svg>"], { "type" : "image/svg+xml" });
e.respondWith(new Response(blob));
return;
}
});
@@ -0,0 +1,8 @@


PASS register service workers
PASS image1 load
PASS image2 load
PASS svg1 load
PASS svg2 load

@@ -0,0 +1,96 @@
<html>
<head>
<title>Service Worker Fetch Event for use element</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="myDiv">
<img id="image1"></img>
<img id="image2"></img>
<br>
<div id="svg1"></div>
<div id="svg2"></div>
</div>
<script>
var scope1 = "";
var scope2 = "resources/";
let registration1, registration2;
let port1, port2;

async function waitForActive(registration)
{
var activeWorker = registration.active;
if (!activeWorker) {
activeWorker = registration.installing;
await new Promise(resolve => {
activeWorker.addEventListener('statechange', () => {
if (activeWorker.state === "activated")
resolve();
});
});
}

const channel = new MessageChannel();
activeWorker.postMessage(channel.port1, [channel.port1]);
return new Promise(resolve => channel.port2.onmessage = (event) => {
resolve(channel.port2);
});
}

function createSVG(url)
{
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute("width", 300);
svg.setAttribute("height", 300);
svg.setAttribute("viewBox", "0 0 88 88");
svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
use.setAttribute("x", 10);
use.setAttribute("y", 10);
use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', url);
svg.appendChild(use);

return svg;
}

function waitForMessage(port)
{
return new Promise(resolve => port.onmessage = (event) => resolve(event.data));
}

promise_test(async () => {
// registration controlling this document.
registration1 = await navigator.serviceWorker.register("use-element-worker.js", { scope : scope1 });
port1 = await waitForActive(registration1);

// registration not controlling this document.
registration2 = await navigator.serviceWorker.register("use-element-worker.js", { scope : scope2 });
port2 = await waitForActive(registration2);
}, "register service workers");

promise_test(async (test) => {
image1.src = 'image1.svg?img';
return waitForMessage(port1);
}, "image1 load");

promise_test(async (test) => {
image2.src = 'resources/image1.svg?img';
return waitForMessage(port1);
}, "image2 load");

promise_test(async (test) => {
myDiv.appendChild(createSVG('image1.svg#all'));
const result = await waitForMessage(port1);
assert_equals(result.destination, "image");
}, "svg1 load");

promise_test(async (test) => {
// We do not want the load to match registration2.
myDiv.appendChild(createSVG('resources/image1.svg#all'));
const result = await waitForMessage(port1);
assert_equals(result.destination, "image");
}, "svg2 load");
</script>
</body>
</html>
@@ -581,6 +581,7 @@ void SVGUseElement::updateExternalDocument()
ResourceLoaderOptions options = CachedResourceLoader::defaultCachedResourceOptions();
options.contentSecurityPolicyImposition = isInUserAgentShadowTree() ? ContentSecurityPolicyImposition::SkipPolicyCheck : ContentSecurityPolicyImposition::DoPolicyCheck;
options.mode = FetchOptions::Mode::SameOrigin;
options.destination = FetchOptions::Destination::Image;
CachedResourceRequest request { ResourceRequest { externalDocumentURL }, options };
request.setInitiator(*this);
m_externalDocument = document().cachedResourceLoader().requestSVGDocument(WTFMove(request)).value_or(nullptr);

0 comments on commit c923ed2

Please sign in to comment.