Title: [266699] trunk
Revision
266699
Author
[email protected]
Date
2020-09-07 08:51:25 -0700 (Mon, 07 Sep 2020)

Log Message

Safari takes too long to fetch images from memory cache
https://bugs.webkit.org/show_bug.cgi?id=216048
<rdar://problem/68260952>

Reviewed by Antti Koivisto.

LayoutTests/imported/w3c:

* web-platform-tests/fetch/stale-while-revalidate/stale-image.html:
* web-platform-tests/html/dom/elements/images/bypass-cache-revalidation-expected.txt: Added.
* web-platform-tests/html/dom/elements/images/bypass-cache-revalidation.html: Added.
* web-platform-tests/html/dom/elements/images/image.py: Added.
(main):

Source/WebCore:

In case of image resource, we skip revalidation if the stored image is from the same document.
This is inline with https://html.spec.whatwg.org/#updating-the-image-data:list-of-available-images
which defines a list of available images for each Document.
In case fetch mode is different, we do not skip revalidation as we might otherwise bypass security checks.

Tests: http/wpt/html/dom/elements/images/bypass-cache-redirection-revalidation.html
       http/wpt/html/dom/elements/images/hover-image-change.html
       imported/w3c/web-platform-tests/html/dom/elements/images/bypass-cache-revalidation.html

* loader/cache/CachedImage.cpp:
(WebCore::CachedImage::load):
(WebCore::CachedImage::canSkipRevalidation const):
* loader/cache/CachedImage.h:
* loader/cache/CachedResourceLoader.cpp:
(WebCore::CachedResourceLoader::determineRevalidationPolicy const):

LayoutTests:

* http/wpt/html/dom/elements/images/bypass-cache-redirection-revalidation-expected.txt: Added.
* http/wpt/html/dom/elements/images/bypass-cache-redirection-revalidation.html: Added.
* http/wpt/html/dom/elements/images/hover-image-change-expected.html: Added.
* http/wpt/html/dom/elements/images/hover-image-change.html: Added.
* http/wpt/html/dom/elements/images/redirect.py: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (266698 => 266699)


--- trunk/LayoutTests/ChangeLog	2020-09-07 12:37:37 UTC (rev 266698)
+++ trunk/LayoutTests/ChangeLog	2020-09-07 15:51:25 UTC (rev 266699)
@@ -1,5 +1,19 @@
 2020-09-07  Youenn Fablet  <[email protected]>
 
+        Safari takes too long to fetch images from memory cache
+        https://bugs.webkit.org/show_bug.cgi?id=216048
+        <rdar://problem/68260952>
+
+        Reviewed by Antti Koivisto.
+
+        * http/wpt/html/dom/elements/images/bypass-cache-redirection-revalidation-expected.txt: Added.
+        * http/wpt/html/dom/elements/images/bypass-cache-redirection-revalidation.html: Added.
+        * http/wpt/html/dom/elements/images/hover-image-change-expected.html: Added.
+        * http/wpt/html/dom/elements/images/hover-image-change.html: Added.
+        * http/wpt/html/dom/elements/images/redirect.py: Added.
+
+2020-09-07  Youenn Fablet  <[email protected]>
+
         Add missing members to RTCIceCandidate
         https://bugs.webkit.org/show_bug.cgi?id=216075
 

Added: trunk/LayoutTests/http/wpt/html/dom/elements/images/bypass-cache-redirection-revalidation-expected.txt (0 => 266699)


--- trunk/LayoutTests/http/wpt/html/dom/elements/images/bypass-cache-redirection-revalidation-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/wpt/html/dom/elements/images/bypass-cache-redirection-revalidation-expected.txt	2020-09-07 15:51:25 UTC (rev 266699)
@@ -0,0 +1,5 @@
+
+
+
+PASS Images can bypass no-store redirections 
+

Added: trunk/LayoutTests/http/wpt/html/dom/elements/images/bypass-cache-redirection-revalidation.html (0 => 266699)


--- trunk/LayoutTests/http/wpt/html/dom/elements/images/bypass-cache-redirection-revalidation.html	                        (rev 0)
+++ trunk/LayoutTests/http/wpt/html/dom/elements/images/bypass-cache-redirection-revalidation.html	2020-09-07 15:51:25 UTC (rev 266699)
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>Cached images can bypass revalidation, including redirections</title>
+<script src=""
+<script src=""
+<script src=""
+<div id="imageDiv1"></div>
+<div id="imageDiv2"></div>
+<div id="imageDiv3"></div>
+<canvas id="canvas"></canvas>
+<script>
+
+function getImagePixel(image)
+{
+    canvas.getContext("2d").drawImage(image, 0, 0, 10, 10);
+    return canvas.getContext("2d").getImageData(0, 0, 1, 1).data;
+}
+
+function computeRedirectionURL(finalURL)
+{
+   return "redirect.py?status=302&location="+ encodeURIComponent(finalURL);
+}
+
+let resolve;
+promise_test(async (t) => {
+   const url = "" + token());
+
+   let promise = new Promise(r => resolve = r);
+   imageDiv1.innerHTML = `<img src="" _onload_="resolve()"></img>`;
+   await promise;
+
+   const url2 = computeRedirectionURL("/html/dom/elements/images/image.py?id=" + token());
+   promise = new Promise(r => resolve = r);
+   imageDiv1.innerHTML = `<img src="" _onload_="resolve()"></img>`;
+   await promise;
+
+   promise = new Promise(r => resolve = r);
+   imageDiv3.innerHTML = `<img id="image3" src="" _onload_="resolve()"></img>`;
+   await promise;
+
+   assert_array_equals(getImagePixel(image3), [0, 255, 0, 255]);
+}, "Images can bypass no-store redirections");
+</script>
+

Added: trunk/LayoutTests/http/wpt/html/dom/elements/images/hover-image-change-expected.html (0 => 266699)


--- trunk/LayoutTests/http/wpt/html/dom/elements/images/hover-image-change-expected.html	                        (rev 0)
+++ trunk/LayoutTests/http/wpt/html/dom/elements/images/hover-image-change-expected.html	2020-09-07 15:51:25 UTC (rev 266699)
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+    <head>
+        <style>
+        #testDiv {
+            height: 100px;
+            border: 1rem solid blue;
+            background-color: rgba(0, 255, 0, 255);
+        }
+        </style>
+    </head>
+    <body>
+        <div id="testDiv">This is a test</div>
+    </body>
+</html>

Added: trunk/LayoutTests/http/wpt/html/dom/elements/images/hover-image-change.html (0 => 266699)


--- trunk/LayoutTests/http/wpt/html/dom/elements/images/hover-image-change.html	                        (rev 0)
+++ trunk/LayoutTests/http/wpt/html/dom/elements/images/hover-image-change.html	2020-09-07 15:51:25 UTC (rev 266699)
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+    <head>
+        <link rel="match" href=""
+        <style id="style">
+        #testDiv {
+            height: 100px;
+            background: var(--background);
+        }
+
+        #testDiv:hover {
+            border: 1rem solid blue;
+        }
+        </style>
+    </head>
+    <body>
+        <div id="testDiv" _onmouseover_="checkTestResult()">This is a test</div>
+        <script src=""
+        <script>
+        if (window.testRunner)
+            testRunner.waitUntilDone();
+
+        style.sheet.insertRule('div { --background: url(/html/dom/elements/images/image.py?id='+ token() + '); }');
+
+        async function checkTestResult()
+        {
+            if (window.testRunner)
+                testRunner.notifyDone();
+        }
+
+        window._onload_ = () => {
+            const y = document.getElementById("testDiv").getBoundingClientRect().top;
+            if (window.testRunner)
+                eventSender.mouseMoveTo(10, y + 10);
+        }
+        </script>
+    </body>
+</html>

Added: trunk/LayoutTests/http/wpt/html/dom/elements/images/redirect.py (0 => 266699)


--- trunk/LayoutTests/http/wpt/html/dom/elements/images/redirect.py	                        (rev 0)
+++ trunk/LayoutTests/http/wpt/html/dom/elements/images/redirect.py	2020-09-07 15:51:25 UTC (rev 266699)
@@ -0,0 +1,20 @@
+def main(request, response):
+    """Simple handler that causes not-cacheable redirection.
+
+    The request should typically have two query parameters:
+    status - The status to use for the redirection. Defaults to 302.
+    location - The resource to redirect to.
+    """
+    status = 302
+    if b"status" in request.GET:
+        try:
+            status = int(request.GET.first(b"status"))
+        except ValueError:
+            pass
+
+    response.status = status
+
+    location = request.GET.first(b"location")
+
+    response.headers.set(b"Location", location)
+    response.headers.set(b"Cache-Control", "no-store")

Modified: trunk/LayoutTests/imported/w3c/ChangeLog (266698 => 266699)


--- trunk/LayoutTests/imported/w3c/ChangeLog	2020-09-07 12:37:37 UTC (rev 266698)
+++ trunk/LayoutTests/imported/w3c/ChangeLog	2020-09-07 15:51:25 UTC (rev 266699)
@@ -1,5 +1,19 @@
 2020-09-07  Youenn Fablet  <[email protected]>
 
+        Safari takes too long to fetch images from memory cache
+        https://bugs.webkit.org/show_bug.cgi?id=216048
+        <rdar://problem/68260952>
+
+        Reviewed by Antti Koivisto.
+
+        * web-platform-tests/fetch/stale-while-revalidate/stale-image.html:
+        * web-platform-tests/html/dom/elements/images/bypass-cache-revalidation-expected.txt: Added.
+        * web-platform-tests/html/dom/elements/images/bypass-cache-revalidation.html: Added.
+        * web-platform-tests/html/dom/elements/images/image.py: Added.
+        (main):
+
+2020-09-07  Youenn Fablet  <[email protected]>
+
         Add missing members to RTCIceCandidate
         https://bugs.webkit.org/show_bug.cgi?id=216075
 

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-image.html (266698 => 266699)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-image.html	2020-09-07 12:37:37 UTC (rev 266698)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/stale-while-revalidate/stale-image.html	2020-09-07 15:51:25 UTC (rev 266699)
@@ -10,13 +10,17 @@
 an image loaded into the same document will skip cache-control headers.
 See: https://html.spec.whatwg.org/#the-list-of-available-images
 -->
-<iframe id="child" srcdoc=""></iframe>
+<div id="frames">
+<iframe id="child1" srcdoc=""></iframe>
+<iframe id="child2" srcdoc=""></iframe>
+<iframe id="child3" srcdoc=""></iframe>
+</div>
 <script>
 
 var request_token = token();
 let image_src = "resources/stale-image.py?token=" + request_token;
 
-let loadImage = async () => {
+let loadImage = async (document) => {
   let img = document.createElement("img");
   img.src = ""
   let loaded = new Promise(r => img._onload_ = r);
@@ -28,10 +32,10 @@
 promise_test(async t => {
   await new Promise(r => window._onload_ = r);
 
-  let img1 = await loadImage();
+  let img1 = await loadImage(child1.contentDocument);
   assert_equals(img1.width, 16, "(initial version loaded)");
 
-  let img2 = await loadImage();
+  let img2 = await loadImage(child2.contentDocument);
   assert_equals(img2.width, 16, "(stale version loaded)");
 
   // Query the server again and again. At some point it must have received the
@@ -45,9 +49,10 @@
       break;
   }
 
-  let img3 = await loadImage();
+  let img3 = await loadImage(child3.contentDocument);
   assert_equals(img3.width, 256, "(revalidated version loaded)");
 
+  frames.innerHTML = "";
 }, 'Cache returns stale resource');
 
 </script>

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/html/dom/elements/images/bypass-cache-revalidation-expected.txt (0 => 266699)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/html/dom/elements/images/bypass-cache-revalidation-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/html/dom/elements/images/bypass-cache-revalidation-expected.txt	2020-09-07 15:51:25 UTC (rev 266699)
@@ -0,0 +1,5 @@
+
+
+
+PASS Images can bypass no-cache 
+

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/html/dom/elements/images/bypass-cache-revalidation.html (0 => 266699)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/html/dom/elements/images/bypass-cache-revalidation.html	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/html/dom/elements/images/bypass-cache-revalidation.html	2020-09-07 15:51:25 UTC (rev 266699)
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Cached images can bypass revalidation</title>
+<script src=""
+<script src=""
+<script src=""
+<div id="imageDiv1"></div>
+<div id="imageDiv2"></div>
+<canvas id="canvas"></canvas>
+<script>
+
+function getImagePixel(image)
+{
+    canvas.getContext("2d").drawImage(image, 0, 0, 10, 10);
+    return canvas.getContext("2d").getImageData(0, 0, 1, 1).data;
+}
+
+let resolve;
+promise_test(async (t) => {
+   const url = "" + token();
+
+   let promise = new Promise(r => resolve = r);
+   imageDiv1.innerHTML = `<img src="" _onload_="resolve()"></img>`;
+   await promise;
+
+   const url2 = "image.py?id=" + token();
+   promise = new Promise(r => resolve = r);
+   imageDiv1.innerHTML = `<img src="" _onload_="resolve()"></img>`;
+   await promise;
+
+   promise = new Promise(r => resolve = r);
+   imageDiv2.innerHTML = `<img id="image2" src="" _onload_="resolve()"></img>`;
+   await promise;
+
+   assert_array_equals(getImagePixel(image2), [0, 255, 0, 255]);
+}, "Images can bypass no-cache");
+</script>
+

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/html/dom/elements/images/image.py (0 => 266699)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/html/dom/elements/images/image.py	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/html/dom/elements/images/image.py	2020-09-07 15:51:25 UTC (rev 266699)
@@ -0,0 +1,28 @@
+import os.path
+
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+
+    key = request.GET['id']
+    alreadyServedRequest = False
+    try:
+      alreadyServedRequest = request.server.stash.take(key)
+    except (KeyError, ValueError) as e:
+      pass
+
+    if alreadyServedRequest:
+      body = open(os.path.join(os.path.dirname(isomorphic_decode(__file__)), u"../../../../images/red.png"), u"rb").read()
+    else:
+      request.server.stash.put(key, True);
+      body = open(os.path.join(os.path.dirname(isomorphic_decode(__file__)), u"../../../../images/green.png"), u"rb").read()
+      pass
+
+    response.writer.write_status(200)
+    response.writer.write_header(b"etag", "abcdef")
+    response.writer.write_header(b"content-length", len(body))
+    response.writer.write_header(b"content-type", "image/png")
+    response.writer.write_header(b"cache-control", "public, max-age=31536000, no-cache")
+    response.writer.end_headers()
+
+    response.writer.write(body)

Modified: trunk/Source/WebCore/ChangeLog (266698 => 266699)


--- trunk/Source/WebCore/ChangeLog	2020-09-07 12:37:37 UTC (rev 266698)
+++ trunk/Source/WebCore/ChangeLog	2020-09-07 15:51:25 UTC (rev 266699)
@@ -1,5 +1,29 @@
 2020-09-07  Youenn Fablet  <[email protected]>
 
+        Safari takes too long to fetch images from memory cache
+        https://bugs.webkit.org/show_bug.cgi?id=216048
+        <rdar://problem/68260952>
+
+        Reviewed by Antti Koivisto.
+
+        In case of image resource, we skip revalidation if the stored image is from the same document.
+        This is inline with https://html.spec.whatwg.org/#updating-the-image-data:list-of-available-images
+        which defines a list of available images for each Document.
+        In case fetch mode is different, we do not skip revalidation as we might otherwise bypass security checks.
+
+        Tests: http/wpt/html/dom/elements/images/bypass-cache-redirection-revalidation.html
+               http/wpt/html/dom/elements/images/hover-image-change.html
+               imported/w3c/web-platform-tests/html/dom/elements/images/bypass-cache-revalidation.html
+
+        * loader/cache/CachedImage.cpp:
+        (WebCore::CachedImage::load):
+        (WebCore::CachedImage::canSkipRevalidation const):
+        * loader/cache/CachedImage.h:
+        * loader/cache/CachedResourceLoader.cpp:
+        (WebCore::CachedResourceLoader::determineRevalidationPolicy const):
+
+2020-09-07  Youenn Fablet  <[email protected]>
+
         Add missing members to RTCIceCandidate
         https://bugs.webkit.org/show_bug.cgi?id=216075
 

Modified: trunk/Source/WebCore/loader/cache/CachedImage.cpp (266698 => 266699)


--- trunk/Source/WebCore/loader/cache/CachedImage.cpp	2020-09-07 12:37:37 UTC (rev 266698)
+++ trunk/Source/WebCore/loader/cache/CachedImage.cpp	2020-09-07 15:51:25 UTC (rev 266699)
@@ -97,6 +97,7 @@
 
 void CachedImage::load(CachedResourceLoader& loader)
 {
+    m_skippingRevalidationDocument = makeWeakPtr(loader.document());
     if (loader.shouldPerformImageLoad(url()))
         CachedResource::load(loader);
     else
@@ -732,4 +733,14 @@
     return CachedResource::makeRevalidationDecision(cachePolicy);
 }
 
+bool CachedImage::canSkipRevalidation(const CachedResourceLoader& loader, const CachedResourceRequest& request) const
+{
+    if (options().mode != request.options().mode || options().credentials != request.options().credentials || resourceRequest().allowCookies() != request.resourceRequest().allowCookies())
+        return false;
+
+    // Skip revalidation as per https://html.spec.whatwg.org/#ignore-higher-layer-caching which defines a per-document image list.
+    // This rule is loosely implemented by other browsers, we could relax it and should update it once memory cache is properly specified.
+    return m_skippingRevalidationDocument && loader.document() == m_skippingRevalidationDocument;
+}
+
 } // namespace WebCore

Modified: trunk/Source/WebCore/loader/cache/CachedImage.h (266698 => 266699)


--- trunk/Source/WebCore/loader/cache/CachedImage.h	2020-09-07 12:37:37 UTC (rev 266698)
+++ trunk/Source/WebCore/loader/cache/CachedImage.h	2020-09-07 15:51:25 UTC (rev 266699)
@@ -95,6 +95,7 @@
     void setForceUpdateImageDataEnabledForTesting(bool enabled) { m_forceUpdateImageDataEnabledForTesting =  enabled; }
 
     bool stillNeedsLoad() const override { return !errorOccurred() && status() == Unknown && !isLoading(); }
+    bool canSkipRevalidation(const CachedResourceLoader&, const CachedResourceRequest&) const;
 
 private:
     void clear();
@@ -187,6 +188,8 @@
 
     MonotonicTime m_lastUpdateImageDataTime;
 
+    WeakPtr<Document> m_skippingRevalidationDocument;
+
     static constexpr unsigned maxUpdateImageDataCount = 4;
     unsigned m_updateImageDataCount : 3;
     bool m_isManuallyCached : 1;

Modified: trunk/Source/WebCore/loader/cache/CachedResourceLoader.cpp (266698 => 266699)


--- trunk/Source/WebCore/loader/cache/CachedResourceLoader.cpp	2020-09-07 12:37:37 UTC (rev 266698)
+++ trunk/Source/WebCore/loader/cache/CachedResourceLoader.cpp	2020-09-07 15:51:25 UTC (rev 266699)
@@ -1169,6 +1169,9 @@
         return Use;
     ASSERT(!existingResource->validationInProgress());
 
+    if (is<CachedImage>(*existingResource) && downcast<CachedImage>(*existingResource).canSkipRevalidation(*this, cachedResourceRequest))
+        return Use;
+
     auto cachePolicy = this->cachePolicy(type, request.url());
 
     // Validate the redirect chain.
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to