Title: [251637] trunk
Revision
251637
Author
[email protected]
Date
2019-10-26 10:17:54 -0700 (Sat, 26 Oct 2019)

Log Message

Main implementation for lazy image loading
https://bugs.webkit.org/show_bug.cgi?id=200764

Patch by Rob Buis <[email protected]> on 2019-10-26
Reviewed by Simon Fraser.

LayoutTests/imported/w3c:

Set correct test option.

* web-platform-tests/loading/lazyload/image-loading-lazy.tentative-expected.txt: Added.
* web-platform-tests/loading/lazyload/image-loading-lazy.tentative.html:

Source/WebCore:

Implement lazy image loading as specified here [1]. Lazy image loading
is controlled by the loading attribute on <img>. When the loading attribute is
auto or not specified, the behavior is like before this patch, i.e. loading is
eager.

Not all loading=lazy requests will turn into actual lazy image loads, when
scripting is turned off or images are not http(s), they will not be deferred.

This implementation relies on Intersection Observer and hence works on WK2 only.

Deferred images are painted using a simple outline until fully loaded.

[1] https://github.com/whatwg/html/pull/3752/files

Tests: http/tests/lazyload/attribute.html
       http/tests/lazyload/invisible-image.html
       http/tests/lazyload/js-image.html
       http/tests/lazyload/lazy-image-load-in-iframes-scripting-disabled.html
       http/tests/lazyload/lazy-image-load-in-iframes-scripting-enabled.html
       http/tests/lazyload/lazy.html
       http/tests/lazyload/scroll-element-moved-from-document.html
       http/tests/lazyload/scroll-element-removed-from-document.html
       http/tests/lazyload/scroll.html

* Sources.txt:
* WebCore.xcodeproj/project.pbxproj:
* dom/Document.cpp:
(WebCore::Document::lazyLoadImageObserver):
* dom/Document.h:
* html/HTMLImageElement.cpp:
(WebCore::HTMLImageElement::parseAttribute):
(WebCore::HTMLImageElement::loadDeferredImage):
(WebCore::HTMLImageElement::didMoveToNewDocument):
(WebCore::HTMLImageElement::loadingForBindings const):
(WebCore::HTMLImageElement::setLoadingForBindings):
(WebCore::HTMLImageElement::isDeferred const):
(WebCore::HTMLImageElement::isLazyLoadable const):
* html/HTMLImageElement.h:
* html/HTMLImageElement.idl:
* html/LazyLoadImageObserver.cpp: Added.
(WebCore::LazyLoadImageObserver::observe):
(WebCore::LazyLoadImageObserver::unobserve):
(WebCore::LazyLoadImageObserver::intersectionObserver):
(WebCore::LazyLoadImageObserver::isObserved const):
* html/LazyLoadImageObserver.h: Added.
* html/parser/HTMLPreloadScanner.cpp:
(WebCore::TokenPreloadScanner::StartTagScanner::createPreloadRequest):
(WebCore::TokenPreloadScanner::StartTagScanner::processAttribute):
* loader/ImageLoader.cpp:
(WebCore::ImageLoader::updateFromElement):
(WebCore::ImageLoader::notifyFinished):
(WebCore::ImageLoader::loadDeferredImage):
* loader/ImageLoader.h:
(WebCore::ImageLoader::isDeferred const):
* loader/cache/CachedImage.h:
* loader/cache/CachedResourceLoader.cpp:
(WebCore::CachedResourceLoader::requestImage):
(WebCore::CachedResourceLoader::requestResource):
(WebCore::CachedResourceLoader::determineRevalidationPolicy const):
(WebCore::CachedResourceLoader::clientDefersImage const):
(WebCore::CachedResourceLoader::shouldDeferImageLoad const):
(WebCore::CachedResourceLoader::reloadImagesIfNotDeferred):
* loader/cache/CachedResourceLoader.h:
* rendering/RenderImage.cpp:
(WebCore::isDeferredImage):
(WebCore::RenderImage::paintReplaced):

LayoutTests:

Import relevant tests into http/tests/lazyload.
Skip lazy image load tests for WK1 and windows, which
has IntersectionObserver turned off by default.

* TestExpectations:
* http/tests/lazyload/attribute-expected.txt: Added.
* http/tests/lazyload/attribute.html: Added.
* http/tests/lazyload/invisible-image-expected.txt: Added.
* http/tests/lazyload/invisible-image.html: Added.
* http/tests/lazyload/js-image-expected.txt: Added.
* http/tests/lazyload/js-image.html: Added.
* http/tests/lazyload/lazy-expected.txt: Added.
* http/tests/lazyload/lazy-image-load-in-iframes-scripting-disabled-expected.txt: Added.
* http/tests/lazyload/lazy-image-load-in-iframes-scripting-disabled.html: Added.
* http/tests/lazyload/lazy-image-load-in-iframes-scripting-enabled-expected.txt: Added.
* http/tests/lazyload/lazy-image-load-in-iframes-scripting-enabled.html: Added.
* http/tests/lazyload/lazy.html: Added.
* http/tests/lazyload/lazy2-expected.txt: Added.
* http/tests/lazyload/placeholder.js: Added.
(is_image_fully_loaded):
* http/tests/lazyload/resources/lazy-load-in-iframe.html: Added.
* http/tests/lazyload/scroll-element-moved-from-document-expected.txt: Added.
* http/tests/lazyload/scroll-element-moved-from-document.html: Added.
* http/tests/lazyload/scroll-element-removed-from-document-expected.txt: Added.
* http/tests/lazyload/scroll-element-removed-from-document.html: Added.
* http/tests/lazyload/scroll-expected.txt: Added.
* http/tests/lazyload/scroll.html: Added.
* platform/mac-wk1/TestExpectations:
* platform/win/TestExpectations:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (251636 => 251637)


--- trunk/LayoutTests/ChangeLog	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/LayoutTests/ChangeLog	2019-10-26 17:17:54 UTC (rev 251637)
@@ -1,3 +1,40 @@
+2019-10-26  Rob Buis  <[email protected]>
+
+        Main implementation for lazy image loading
+        https://bugs.webkit.org/show_bug.cgi?id=200764
+
+        Reviewed by Simon Fraser.
+
+        Import relevant tests into http/tests/lazyload.
+        Skip lazy image load tests for WK1 and windows, which
+        has IntersectionObserver turned off by default.
+
+        * TestExpectations:
+        * http/tests/lazyload/attribute-expected.txt: Added.
+        * http/tests/lazyload/attribute.html: Added.
+        * http/tests/lazyload/invisible-image-expected.txt: Added.
+        * http/tests/lazyload/invisible-image.html: Added.
+        * http/tests/lazyload/js-image-expected.txt: Added.
+        * http/tests/lazyload/js-image.html: Added.
+        * http/tests/lazyload/lazy-expected.txt: Added.
+        * http/tests/lazyload/lazy-image-load-in-iframes-scripting-disabled-expected.txt: Added.
+        * http/tests/lazyload/lazy-image-load-in-iframes-scripting-disabled.html: Added.
+        * http/tests/lazyload/lazy-image-load-in-iframes-scripting-enabled-expected.txt: Added.
+        * http/tests/lazyload/lazy-image-load-in-iframes-scripting-enabled.html: Added.
+        * http/tests/lazyload/lazy.html: Added.
+        * http/tests/lazyload/lazy2-expected.txt: Added.
+        * http/tests/lazyload/placeholder.js: Added.
+        (is_image_fully_loaded):
+        * http/tests/lazyload/resources/lazy-load-in-iframe.html: Added.
+        * http/tests/lazyload/scroll-element-moved-from-document-expected.txt: Added.
+        * http/tests/lazyload/scroll-element-moved-from-document.html: Added.
+        * http/tests/lazyload/scroll-element-removed-from-document-expected.txt: Added.
+        * http/tests/lazyload/scroll-element-removed-from-document.html: Added.
+        * http/tests/lazyload/scroll-expected.txt: Added.
+        * http/tests/lazyload/scroll.html: Added.
+        * platform/mac-wk1/TestExpectations:
+        * platform/win/TestExpectations:
+
 2019-10-26  Yury Semikhatsky  <[email protected]>
 
         [GTK] LayoutTests/inspector tests timeout

Modified: trunk/LayoutTests/TestExpectations (251636 => 251637)


--- trunk/LayoutTests/TestExpectations	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/LayoutTests/TestExpectations	2019-10-26 17:17:54 UTC (rev 251637)
@@ -3854,7 +3854,6 @@
 fast/text/design-system-ui-15.html [ ImageOnlyFailure ]
 fast/text/design-system-ui-16.html [ ImageOnlyFailure ]
 
-webkit.org/b/196698 imported/w3c/web-platform-tests/loading/lazyload/image-loading-lazy.tentative.html
 webkit.org/b/196698 imported/w3c/web-platform-tests/loading/lazyload/iframe-loading-lazy.tentative.html
 
 webkit.org/b/202517 imported/w3c/web-platform-tests/2dcontext/text-styles/2d.text.draw.baseline.ideographic.html [ Skip ]

Added: trunk/LayoutTests/http/tests/lazyload/attribute-expected.txt (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/attribute-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/attribute-expected.txt	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,8 @@
+       
+
+PASS Test that document load event is fired 
+PASS Test that values for loading are case insensitive 
+PASS Test that <img> with loading=lazy or auto or no attribute or invalid value are loaded as a placeholder 
+PASS Test that <img> with loading=eager is fully loaded, and not a placeholder 
+PASS Test that deferred <img> are fully loaded when lazyload is turned off 
+

Added: trunk/LayoutTests/http/tests/lazyload/attribute.html (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/attribute.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/attribute.html	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,94 @@
+<!DOCTYPE html><!-- webkit-test-runner [ experimental:enableLazyImageLoading=true ] -->
+<script src=""
+<script src=""
+<script src=""
+
+<body>
+  <div style="height:10000px;"></div>
+  <img id="no_attribute_img" src=''>
+  <img id="auto_attribute_img" src='' loading="auto">
+  <img id="invalid_attribute_img" src='' loading="invalid-value-default">
+  <img id="lazy_attribute_img" src='' loading="lazy">
+  <img id="eager_attribute_img" src='' loading="eager">
+  <img id="auto_attribute_ci_img" src='' loading="Auto">
+  <img id="lazy_attribute_ci_img" src='' loading="Lazy">
+  <img id="eager_attribute_ci_img" src='' loading="Eager">
+</body>
+
+<script>
+  var no_attribute_img = document.getElementById("no_attribute_img");
+  var auto_attribute_img = document.getElementById("auto_attribute_img");
+  var invalid_attribute_img = document.getElementById("invalid_attribute_img");
+  var lazy_attribute_img = document.getElementById("lazy_attribute_img");
+  var eager_attribute_img = document.getElementById("eager_attribute_img");
+  var auto_attribute_ci_img = document.getElementById("auto_attribute_ci_img");
+  var lazy_attribute_ci_img = document.getElementById("lazy_attribute_ci_img");
+  var eager_attribute_ci_img = document.getElementById("eager_attribute_ci_img");
+
+  async_test(function(t) {
+    window.addEventListener("load", t.step_func_done());
+  }, "Test that document load event is fired");
+
+  async_test(function(t) {
+    auto_attribute_ci_img.addEventListener("load",
+      t.step_func_done(function() {
+        assert_true(is_image_fully_loaded(auto_attribute_ci_img));
+      }));
+    window.addEventListener("load", t.step_func_done(function() {
+      assert_false(is_image_fully_loaded(lazy_attribute_ci_img));
+    }));
+    eager_attribute_ci_img.addEventListener("load",
+      t.step_func_done(function() {
+        assert_true(is_image_fully_loaded(eager_attribute_ci_img));
+      }));
+  }, "Test that values for loading are case insensitive");
+
+  async_test(function(t) {
+    window.addEventListener("load", t.step_func_done(function() {
+      assert_false(is_image_fully_loaded(lazy_attribute_img));
+      assert_true(is_image_fully_loaded(no_attribute_img));
+    }));
+    lazy_attribute_img.addEventListener("load",
+      t.unreached_func("Load event should not be fired for below viewport image with loading=lazy"));
+    auto_attribute_img.addEventListener("load",
+      t.step_func_done(function() {
+        assert_true(is_image_fully_loaded(auto_attribute_img));
+      }));
+    no_attribute_img.addEventListener("load",
+      t.step_func_done(function() {
+        assert_true(is_image_fully_loaded(no_attribute_img));
+      }));
+    invalid_attribute_img.addEventListener("load",
+      t.step_func_done(function() {
+        assert_true(is_image_fully_loaded(invalid_attribute_img));
+      }));
+  }, "Test that <img> with loading=lazy or auto or no attribute or invalid value are loaded as a placeholder");
+
+  async_test(function(t) {
+    eager_attribute_img.addEventListener("load",
+      t.step_func_done(function() {
+        assert_true(is_image_fully_loaded(eager_attribute_img));
+      }));
+  }, "Test that <img> with loading=eager is fully loaded, and not a placeholder");
+
+  async_test(function(t) {
+    var complete = 0;
+    var _onload_callback_ = function() {
+      if (++complete == 2) {
+        // The two images with loading=lazy,Lazy attribute are loaded.
+        assert_true(is_image_fully_loaded(lazy_attribute_img));
+        assert_true(is_image_fully_loaded(lazy_attribute_ci_img));
+        t.done();
+      }
+      assert_equals(this.getAttribute('loading'), "eager");
+    };
+    lazy_attribute_img.addEventListener("load", onload_callback);
+    lazy_attribute_ci_img.addEventListener("load", onload_callback);
+    window.addEventListener("load", t.step_func(function() {
+      assert_equals(lazy_attribute_img.getAttribute('loading'), "lazy");
+      assert_equals(lazy_attribute_ci_img.getAttribute('loading'), "Lazy");
+      lazy_attribute_img.loading = 'eager';
+      lazy_attribute_ci_img.loading = 'eager';
+    }));
+  }, "Test that deferred <img> are fully loaded when lazyload is turned off");
+</script>

Added: trunk/LayoutTests/http/tests/lazyload/invisible-image-expected.txt (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/invisible-image-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/invisible-image-expected.txt	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,4 @@
+
+PASS Test that document load event is fired 
+PASS Test that invisible images are loaded 
+

Added: trunk/LayoutTests/http/tests/lazyload/invisible-image.html (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/invisible-image.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/invisible-image.html	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<script src=""
+<script src=""
+<script src=""
+
+<body>
+  <div style="height:10000px;"></div>
+  <img id="visibility_hidden" style="visibility:hidden;" src=''>
+  <img id="display_none" style="display:none;" src=''>
+  <img id="attribute_hidden" hidden src=''>
+  <img id="js_hidden" src=''>
+  <script>
+    document.getElementById("js_hidden").style = 'display:none;';
+  </script>
+</body>
+
+<script>
+  var visibility_hidden_element = document.getElementById("visibility_hidden");
+  var display_none_element = document.getElementById("display_none");
+  var attribute_hidden_element = document.getElementById("attribute_hidden");
+  var js_hidden_element = document.getElementById("js_hidden");
+  async_test(function(t) {
+    window.addEventListener("load", t.step_func_done());
+  }, "Test that document load event is fired");
+  async_test(function(t) {
+    let image_fully_loaded_promise = (element) => {
+      return new Promise(resolve => {
+        element.addEventListener("load",
+          t.step_func(() => {
+            assert_true(is_image_fully_loaded(element));
+            resolve();
+        }));
+      });
+    }
+    Promise.all([image_fully_loaded_promise(visibility_hidden_element),
+      image_fully_loaded_promise(display_none_element),
+      image_fully_loaded_promise(attribute_hidden_element),
+      image_fully_loaded_promise(js_hidden_element)]).then(() => {
+        t.done();
+    });
+  }, "Test that invisible images are loaded");
+</script>
+

Added: trunk/LayoutTests/http/tests/lazyload/js-image-expected.txt (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/js-image-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/js-image-expected.txt	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,7 @@
+ 
+
+PASS Test that load event is fired for JS Image() fetches 
+PASS Test that load event is fired for <img> created via JS and attached below viewport 
+PASS Test that load event is fired for adopted <img> that is appended below the viewport 
+PASS Test that load event is fired for JS Image() decode even with loading set to lazy 
+

Added: trunk/LayoutTests/http/tests/lazyload/js-image.html (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/js-image.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/js-image.html	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,55 @@
+<!DOCTYPE html><!-- webkit-test-runner [ experimental:enableLazyImageLoading=true ] -->
+<script src=""
+<script src=""
+<script src=""
+
+<body>
+  <div style="height:10000px;"></div>
+</body>
+
+<script>
+  async_test(function(t) {
+    const img = new Image();
+    img._onload_ = t.step_func_done(function() {
+      assert_true(is_image_fully_loaded(img));
+    });
+    img.src = '';
+  }, "Test that load event is fired for JS Image() fetches");
+
+  async_test(function(t) {
+    const attached_img = document.createElement("IMG");
+    document.body.appendChild(attached_img);
+    attached_img._onload_ = t.step_func_done(function() {
+      assert_true(is_image_fully_loaded(attached_img));
+    });
+    attached_img.src = ""
+  }, "Test that load event is fired for <img> created via JS and attached below viewport");
+
+  async_test(function(t) {
+    var iframe = document.createElement('iframe');
+    iframe.srcdoc = "<img loading='lazy' style='width:100px;height:100px'></img>";
+    iframe._onload_ = function() {
+      var iframe_img = iframe.contentDocument.getElementsByTagName('img')[0];
+      var adopted_img = document.adoptNode(iframe_img);
+      document.body.appendChild(adopted_img);
+      adopted_img._onload_ = t.step_func_done(function() {
+        assert_true(is_image_fully_loaded(adopted_img));
+      });
+      adopted_img.src = ""
+    };
+    document.body.appendChild(iframe);
+  }, "Test that load event is fired for adopted <img> that is appended below the viewport");
+
+  async_test(function(t) {
+    const img = new Image();
+    img.loading = "lazy";
+    img.src = '';
+    img.decode().then(() => {
+      assert_true(is_image_fully_loaded(img));
+      t.done();
+    }).catch((encodingError) => {
+      assert_unreached();
+    })
+  }, "Test that load event is fired for JS Image() decode even with loading set to lazy");
+
+</script>

Added: trunk/LayoutTests/http/tests/lazyload/lazy-expected.txt (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/lazy-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/lazy-expected.txt	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,7 @@
+
+
+
+PASS Test that document load event is fired 
+PASS Test that <img> in viewport is loaded, and not a placeholder 
+PASS Test that <img> below viewport is a placeholder, with lazyimage enabled 
+

Added: trunk/LayoutTests/http/tests/lazyload/lazy-image-load-in-iframes-scripting-disabled-expected.txt (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/lazy-image-load-in-iframes-scripting-disabled-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/lazy-image-load-in-iframes-scripting-disabled-expected.txt	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,7 @@
+CONSOLE MESSAGE: Blocked script execution in 'http://127.0.0.1:8000/lazyload/resources/lazy-load-in-iframe.html' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.
+CONSOLE MESSAGE: Blocked script execution in 'http://127.0.0.1:8000/lazyload/resources/lazy-load-in-iframe.html' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.
+CONSOLE MESSAGE: Blocked script execution in 'http://127.0.0.1:8000/lazyload/resources/lazy-load-in-iframe.html' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.
+CONSOLE MESSAGE: Blocked script execution in 'http://127.0.0.1:8000/lazyload/resources/lazy-load-in-iframe.html' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.
+
+PASS Verify that iframe's with scripting off disallow lazy image loading. 
+

Added: trunk/LayoutTests/http/tests/lazyload/lazy-image-load-in-iframes-scripting-disabled.html (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/lazy-image-load-in-iframes-scripting-disabled.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/lazy-image-load-in-iframes-scripting-disabled.html	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,17 @@
+<!DOCTYPE html><!-- webkit-test-runner [ experimental:enableLazyImageLoading=true ] -->
+<script src=""
+<script src=""
+<script src=""
+<script>
+    var t = async_test("Verify that iframe's with scripting off disallow lazy image loading.");
+    function finish() {
+        assert_equals(is_image_fully_loaded(document.getElementById('sandboxedframe').contentWindow.document.getElementById('below_viewport')), true);
+        t.done();
+    }
+</script>
+
+<body>
+    <div style="height:10000px;"></div>
+    <iframe id="sandboxedframe" style="visibility:hidden;display:none" sandbox="allow-same-origin" src="" _onload_="finish()"></iframe>
+    <div id="log"></div>
+</body>

Added: trunk/LayoutTests/http/tests/lazyload/lazy-image-load-in-iframes-scripting-enabled-expected.txt (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/lazy-image-load-in-iframes-scripting-enabled-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/lazy-image-load-in-iframes-scripting-enabled-expected.txt	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,3 @@
+
+PASS Verify that iframe's with sandbox='allow-scripts' allow lazy image loading. 
+

Added: trunk/LayoutTests/http/tests/lazyload/lazy-image-load-in-iframes-scripting-enabled.html (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/lazy-image-load-in-iframes-scripting-enabled.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/lazy-image-load-in-iframes-scripting-enabled.html	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,17 @@
+<!DOCTYPE html><!-- webkit-test-runner [ experimental:enableLazyImageLoading=true ] -->
+<script src=""
+<script src=""
+<script src=""
+<script>
+    var t = async_test("Verify that iframe's with sandbox='allow-scripts' allow lazy image loading.");
+    function finish() {
+        assert_equals(is_image_fully_loaded(document.getElementById('sandboxedframe').contentWindow.document.getElementById('below_viewport')), false);
+        t.done();
+    }
+</script>
+
+<body>
+    <div style="height:10000px;"></div>
+    <iframe id="sandboxedframe" style="visibility:hidden;display:none" sandbox="allow-same-origin allow-scripts" src="" _onload_="finish()"></iframe>
+    <div id="log"></div>
+</body>

Added: trunk/LayoutTests/http/tests/lazyload/lazy.html (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/lazy.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/lazy.html	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,41 @@
+<!DOCTYPE html><!-- webkit-test-runner [ experimental:enableLazyImageLoading=true ] -->
+<script src=""
+<script src=""
+<script src=""
+
+<body>
+  <img id="in_viewport" src=''>
+  <div style="height:10000px;"></div>
+  <img id="below_viewport" src='' loading="lazy">
+</body>
+
+<script>
+  var in_viewport_element = document.getElementById("in_viewport");
+  var below_viewport_element = document.getElementById("below_viewport");
+  async_test(function(t) {
+    window.addEventListener("load", t.step_func_done());
+  }, "Test that document load event is fired");
+
+  async_test(function(t) {
+    in_viewport_element.addEventListener("load",
+      t.step_func_done(function() {
+        assert_true(is_image_fully_loaded(in_viewport_element));
+      }));
+  }, "Test that <img> in viewport is loaded, and not a placeholder");
+
+  async_test(function(t) {
+    var complete = 0;
+    var _onload_callback_ = function() {
+      if (++complete == 2) {
+        // Document and the above viewport image has loaded.
+        assert_false(is_image_fully_loaded(below_viewport_element));
+        t.done();
+      }
+    };
+    window.addEventListener("load", t.step_func(onload_callback));
+    in_viewport_element.addEventListener("load",
+      t.step_func(onload_callback));
+    below_viewport_element.addEventListener("load",
+      t.unreached_func("Load event should not be fired for below viewport image"));
+  }, "Test that <img> below viewport is a placeholder, with lazyimage enabled");
+</script>

Added: trunk/LayoutTests/http/tests/lazyload/lazy2-expected.txt (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/lazy2-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/lazy2-expected.txt	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,7 @@
+
+
+
+PASS Test that document load event is fired 
+PASS Test that <img> in viewport is loaded, and not a placeholder 
+PASS Test that <img> below viewport is a placeholder, with lazyimage enabled 
+

Added: trunk/LayoutTests/http/tests/lazyload/placeholder.js (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/placeholder.js	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/placeholder.js	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,16 @@
+// Returns if the image is complete and fully loaded as a non-placeholder image.
+function is_image_fully_loaded(image) {
+  if (!image.complete) {
+    return false;
+  }
+
+  let canvas = document.createElement('canvas');
+  canvas.width = canvas.height = 1;
+  let canvasContext = canvas.getContext("2d");
+  canvasContext.drawImage(image, 0, 0);
+  let data = "" 0, canvas.width, canvas.height).data;
+
+  // Fully loaded image should not be a placeholder which is drawn as a
+  // translucent gray rectangle in placeholder_image.cc
+  return data[0] != 0xd9 || data[1] != 0xd9 || data[2] != 0xd9;
+}

Added: trunk/LayoutTests/http/tests/lazyload/resources/lazy-load-in-iframe.html (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/resources/lazy-load-in-iframe.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/resources/lazy-load-in-iframe.html	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,32 @@
+<!DOCTYPE html><!-- webkit-test-runner [ experimental:enableLazyImageLoading=true ] -->
+<script src=""
+<script src=""
+<script src=""
+
+<body>
+  <img id="in_viewport" src=''>
+  <div style="height:10000px;"></div>
+  <img id="below_viewport" src='' loading="lazy">
+</body>
+
+<script>
+  var in_viewport_element = document.getElementById("in_viewport");
+  var below_viewport_element = document.getElementById("below_viewport");
+  async_test(function(t) {
+    window.addEventListener("load", t.step_func_done());
+  }, "Test that document load event is fired");
+
+  async_test(function(t) {
+    in_viewport_element.addEventListener("load",
+      t.step_func_done(function() {
+        assert_true(is_image_fully_loaded(in_viewport_element));
+      }));
+  }, "Test that <img> in viewport is loaded, and not a placeholder");
+
+  async_test(function(t) {
+    below_viewport_element.addEventListener("load",
+      t.step_func_done(function() {
+        assert_true(is_image_fully_loaded(in_viewport_element));
+      }));
+  }, "Test that <img> below viewport is loaded, and not a placeholder");
+</script>

Added: trunk/LayoutTests/http/tests/lazyload/scroll-element-moved-from-document-expected.txt (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/scroll-element-moved-from-document-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/scroll-element-moved-from-document-expected.txt	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,4 @@
+
+
+PASS Test that <img> below viewport is not loaded when moved to another document and then scrolled to 
+

Added: trunk/LayoutTests/http/tests/lazyload/scroll-element-moved-from-document.html (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/scroll-element-moved-from-document.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/scroll-element-moved-from-document.html	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,27 @@
+<!DOCTYPE html><!-- webkit-test-runner [ experimental:enableLazyImageLoading=true ] -->
+<script src=""
+<script src=""
+<script src=""
+
+<body>
+  <img id="in_viewport" src=''>
+  <div style="height:10000px;"></div>
+  <img id="below_viewport" src='' loading="lazy">
+</body>
+
+<script>
+  var in_viewport_element = document.getElementById("in_viewport");
+  var below_viewport_element = document.getElementById("below_viewport");
+
+  async_test(function(t) {
+    var iframe = document.createElement('iframe');
+    iframe.srcdoc = "<body></body>";
+    iframe._onload_ = function() {
+      var adopted_img = iframe.contentDocument.adoptNode(below_viewport_element);
+      iframe.contentDocument.body.appendChild(adopted_img);
+      window.scrollTo(0, 10000);
+      t.step_timeout(function() { assert_false(is_image_fully_loaded(below_viewport_element)); t.done(); }, 1000);
+    };
+    document.body.insertBefore(iframe, in_viewport_element,);
+  }, "Test that <img> below viewport is not loaded when moved to another document and then scrolled to");
+</script>

Added: trunk/LayoutTests/http/tests/lazyload/scroll-element-removed-from-document-expected.txt (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/scroll-element-removed-from-document-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/scroll-element-removed-from-document-expected.txt	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,4 @@
+
+
+PASS Test that <img> below viewport is not loaded when removed from the document and then scrolled to 
+

Added: trunk/LayoutTests/http/tests/lazyload/scroll-element-removed-from-document.html (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/scroll-element-removed-from-document.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/scroll-element-removed-from-document.html	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,24 @@
+<!DOCTYPE html><!-- webkit-test-runner [ experimental:enableLazyImageLoading=true ] -->
+<script src=""
+<script src=""
+<script src=""
+
+<body>
+  <img id="in_viewport" src=''>
+  <div style="height:10000px;"></div>
+  <img id="below_viewport" src='' loading="lazy">
+</body>
+
+<script>
+  var in_viewport_element = document.getElementById("in_viewport");
+  var below_viewport_element = document.getElementById("below_viewport");
+
+  async_test(function(t) {
+    below_viewport_element.remove();
+    assert_false(is_image_fully_loaded(below_viewport_element));
+    in_viewport_element.addEventListener("load", t.step_func(function() {
+      window.scrollTo(0, 10000);
+      t.step_timeout(function() { assert_false(is_image_fully_loaded(below_viewport_element)); t.done(); }, 1000);
+    }));
+  }, "Test that <img> below viewport is not loaded when removed from the document and then scrolled to");
+</script>

Added: trunk/LayoutTests/http/tests/lazyload/scroll-expected.txt (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/scroll-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/scroll-expected.txt	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,7 @@
+
+
+
+PASS Test that document load event is fired 
+PASS Test that <img> in viewport is loaded, and not a placeholder 
+PASS Test that <img> below viewport is loaded when scrolled near, and not a placeholder 
+

Added: trunk/LayoutTests/http/tests/lazyload/scroll.html (0 => 251637)


--- trunk/LayoutTests/http/tests/lazyload/scroll.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/lazyload/scroll.html	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,35 @@
+<!DOCTYPE html><!-- webkit-test-runner [ experimental:enableLazyImageLoading=true ] -->
+<script src=""
+<script src=""
+<script src=""
+
+<body>
+  <img id="in_viewport" src=''>
+  <div style="height:10000px;"></div>
+  <img id="below_viewport" src='' loading="lazy">
+</body>
+
+<script>
+  var in_viewport_element = document.getElementById("in_viewport");
+  var below_viewport_element = document.getElementById("below_viewport");
+  async_test(function(t) {
+    window.addEventListener("load", t.step_func_done());
+  }, "Test that document load event is fired");
+
+  async_test(function(t) {
+    in_viewport_element.addEventListener("load",
+      t.step_func_done(function() {
+        assert_true(is_image_fully_loaded(in_viewport_element));
+      }));
+  }, "Test that <img> in viewport is loaded, and not a placeholder");
+
+  async_test(function(t) {
+    in_viewport_element.addEventListener("load", t.step_func(function() {
+      below_viewport_element.scrollIntoView();
+    }));
+    below_viewport_element.addEventListener("load",
+      t.step_func_done(function() {
+        assert_true(is_image_fully_loaded(below_viewport_element));
+      }));
+  }, "Test that <img> below viewport is loaded when scrolled near, and not a placeholder");
+</script>

Modified: trunk/LayoutTests/imported/w3c/ChangeLog (251636 => 251637)


--- trunk/LayoutTests/imported/w3c/ChangeLog	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/LayoutTests/imported/w3c/ChangeLog	2019-10-26 17:17:54 UTC (rev 251637)
@@ -1,3 +1,15 @@
+2019-10-26  Rob Buis  <[email protected]>
+
+        Main implementation for lazy image loading
+        https://bugs.webkit.org/show_bug.cgi?id=200764
+
+        Reviewed by Simon Fraser.
+
+        Set correct test option.
+
+        * web-platform-tests/loading/lazyload/image-loading-lazy.tentative-expected.txt: Added.
+        * web-platform-tests/loading/lazyload/image-loading-lazy.tentative.html:
+
 2019-10-26  Simon Fraser  <[email protected]>
 
         Import css/css-syntax WPT tests

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/loading/lazyload/image-loading-lazy.tentative-expected.txt (0 => 251637)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/loading/lazyload/image-loading-lazy.tentative-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/loading/lazyload/image-loading-lazy.tentative-expected.txt	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,5 @@
+
+
+
+PASS Test that images with loading='lazy' load once they enter the viewport. 
+

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/loading/lazyload/image-loading-lazy.tentative.html (251636 => 251637)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/loading/lazyload/image-loading-lazy.tentative.html	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/loading/lazyload/image-loading-lazy.tentative.html	2019-10-26 17:17:54 UTC (rev 251637)
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!DOCTYPE html><!-- webkit-test-runner [ experimental:enableLazyImageLoading=true ] -->
 <head>
   <title>Images with loading='lazy' load when in the viewport</title>
   <link rel="author" title="Scott Little" href=""

Modified: trunk/LayoutTests/platform/mac-wk1/TestExpectations (251636 => 251637)


--- trunk/LayoutTests/platform/mac-wk1/TestExpectations	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/LayoutTests/platform/mac-wk1/TestExpectations	2019-10-26 17:17:54 UTC (rev 251637)
@@ -152,6 +152,9 @@
 imported/w3c/web-platform-tests/intersection-observer [ Skip ]
 intersection-observer [ Skip ]
 
+http/tests/lazyload [ Skip ]
+imported/w3c/web-platform-tests/loading/lazyload [ Skip ]
+
 # testRunner.queueLoad() does not support loading data URLs in Mac WK1
 http/tests/security/contentSecurityPolicy/navigate-self-to-data-url.html [ Skip ]
 

Modified: trunk/LayoutTests/platform/win/TestExpectations (251636 => 251637)


--- trunk/LayoutTests/platform/win/TestExpectations	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/LayoutTests/platform/win/TestExpectations	2019-10-26 17:17:54 UTC (rev 251637)
@@ -4468,3 +4468,7 @@
 webkit.org/b/202952 http/tests/security/navigate-when-restoring-cached-page.html [ Timeout ]
 
 webkit.org/b/202953 http/tests/websocket/tests/hybi/non-document-mixed-content-blocked-https-with-embedded-http-with-embedded-https.https.html [ Timeout ]
+
+# IntersectionObserver is off by default
+http/tests/lazyload [ Skip ]
+imported/w3c/web-platform-tests/loading/lazyload [ Skip ]

Modified: trunk/Source/WebCore/ChangeLog (251636 => 251637)


--- trunk/Source/WebCore/ChangeLog	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/Source/WebCore/ChangeLog	2019-10-26 17:17:54 UTC (rev 251637)
@@ -1,3 +1,77 @@
+2019-10-26  Rob Buis  <[email protected]>
+
+        Main implementation for lazy image loading
+        https://bugs.webkit.org/show_bug.cgi?id=200764
+
+        Reviewed by Simon Fraser.
+
+        Implement lazy image loading as specified here [1]. Lazy image loading
+        is controlled by the loading attribute on <img>. When the loading attribute is
+        auto or not specified, the behavior is like before this patch, i.e. loading is
+        eager.
+
+        Not all loading=lazy requests will turn into actual lazy image loads, when
+        scripting is turned off or images are not http(s), they will not be deferred.
+
+        This implementation relies on Intersection Observer and hence works on WK2 only.
+
+        Deferred images are painted using a simple outline until fully loaded.
+
+        [1] https://github.com/whatwg/html/pull/3752/files
+
+        Tests: http/tests/lazyload/attribute.html
+               http/tests/lazyload/invisible-image.html
+               http/tests/lazyload/js-image.html
+               http/tests/lazyload/lazy-image-load-in-iframes-scripting-disabled.html
+               http/tests/lazyload/lazy-image-load-in-iframes-scripting-enabled.html
+               http/tests/lazyload/lazy.html
+               http/tests/lazyload/scroll-element-moved-from-document.html
+               http/tests/lazyload/scroll-element-removed-from-document.html
+               http/tests/lazyload/scroll.html
+
+        * Sources.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * dom/Document.cpp:
+        (WebCore::Document::lazyLoadImageObserver):
+        * dom/Document.h:
+        * html/HTMLImageElement.cpp:
+        (WebCore::HTMLImageElement::parseAttribute):
+        (WebCore::HTMLImageElement::loadDeferredImage):
+        (WebCore::HTMLImageElement::didMoveToNewDocument):
+        (WebCore::HTMLImageElement::loadingForBindings const):
+        (WebCore::HTMLImageElement::setLoadingForBindings):
+        (WebCore::HTMLImageElement::isDeferred const):
+        (WebCore::HTMLImageElement::isLazyLoadable const):
+        * html/HTMLImageElement.h:
+        * html/HTMLImageElement.idl:
+        * html/LazyLoadImageObserver.cpp: Added.
+        (WebCore::LazyLoadImageObserver::observe):
+        (WebCore::LazyLoadImageObserver::unobserve):
+        (WebCore::LazyLoadImageObserver::intersectionObserver):
+        (WebCore::LazyLoadImageObserver::isObserved const):
+        * html/LazyLoadImageObserver.h: Added.
+        * html/parser/HTMLPreloadScanner.cpp:
+        (WebCore::TokenPreloadScanner::StartTagScanner::createPreloadRequest):
+        (WebCore::TokenPreloadScanner::StartTagScanner::processAttribute):
+        * loader/ImageLoader.cpp:
+        (WebCore::ImageLoader::updateFromElement):
+        (WebCore::ImageLoader::notifyFinished):
+        (WebCore::ImageLoader::loadDeferredImage):
+        * loader/ImageLoader.h:
+        (WebCore::ImageLoader::isDeferred const):
+        * loader/cache/CachedImage.h:
+        * loader/cache/CachedResourceLoader.cpp:
+        (WebCore::CachedResourceLoader::requestImage):
+        (WebCore::CachedResourceLoader::requestResource):
+        (WebCore::CachedResourceLoader::determineRevalidationPolicy const):
+        (WebCore::CachedResourceLoader::clientDefersImage const):
+        (WebCore::CachedResourceLoader::shouldDeferImageLoad const):
+        (WebCore::CachedResourceLoader::reloadImagesIfNotDeferred):
+        * loader/cache/CachedResourceLoader.h:
+        * rendering/RenderImage.cpp:
+        (WebCore::isDeferredImage):
+        (WebCore::RenderImage::paintReplaced):
+
 2019-10-26  Antti Koivisto  <[email protected]>
 
         Move StyleResolver::applyProperty to PropertyCascade

Modified: trunk/Source/WebCore/Sources.txt (251636 => 251637)


--- trunk/Source/WebCore/Sources.txt	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/Source/WebCore/Sources.txt	2019-10-26 17:17:54 UTC (rev 251637)
@@ -1221,6 +1221,7 @@
 html/InputTypeNames.cpp
 html/LabelableElement.cpp
 html/LabelsNodeList.cpp
+html/LazyLoadImageObserver.cpp
 html/LinkIconCollector.cpp
 html/LinkRelAttribute.cpp
 html/MediaController.cpp

Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (251636 => 251637)


--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2019-10-26 17:17:54 UTC (rev 251637)
@@ -3374,6 +3374,7 @@
 		AA7FEEAD16A4E74B004C0C33 /* JSSpeechSynthesis.h in Headers */ = {isa = PBXBuildFile; fileRef = AA7FEEAC16A4E74B004C0C33 /* JSSpeechSynthesis.h */; };
 		AAA728F716D1D8BC00D3BBC6 /* WebAccessibilityObjectWrapperIOS.h in Headers */ = {isa = PBXBuildFile; fileRef = AAA728F116D1D8BC00D3BBC6 /* WebAccessibilityObjectWrapperIOS.h */; };
 		AAC08CF315F941FD00F1E188 /* AccessibilitySVGRoot.h in Headers */ = {isa = PBXBuildFile; fileRef = AAC08CF115F941FC00F1E188 /* AccessibilitySVGRoot.h */; };
+		AAD9D0B521DFA810001B11C7 /* LazyLoadImageObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = AAD9D0B321DFA80E001B11C7 /* LazyLoadImageObserver.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		AB23A32809BBA7D00067CC53 /* BeforeTextInsertedEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = AB23A32609BBA7D00067CC53 /* BeforeTextInsertedEvent.h */; };
 		AB247A6D0AFD6383003FA5FD /* RenderSlider.h in Headers */ = {isa = PBXBuildFile; fileRef = AB247A6B0AFD6383003FA5FD /* RenderSlider.h */; };
 		AB31C91E10AE1B8E000C7B92 /* LineClampValue.h in Headers */ = {isa = PBXBuildFile; fileRef = AB31C91D10AE1B8E000C7B92 /* LineClampValue.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -9134,6 +9135,8 @@
 		65FEA86809833ADE00BED4AB /* Page.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Page.cpp; sourceTree = "<group>"; };
 		697101071C6BE1550018C7F1 /* AccessibilitySVGElement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AccessibilitySVGElement.cpp; sourceTree = "<group>"; };
 		697101081C6BE1550018C7F1 /* AccessibilitySVGElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AccessibilitySVGElement.h; sourceTree = "<group>"; };
+		AAD9D0B121DFA80C001B11C7 /* LazyLoadImageObserver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LazyLoadImageObserver.cpp; sourceTree = "<group>"; };
+		AAD9D0B321DFA80E001B11C7 /* LazyLoadImageObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LazyLoadImageObserver.h; sourceTree = "<group>"; };
 		6A22E86F1F10418600F546C3 /* InspectorCanvas.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InspectorCanvas.h; sourceTree = "<group>"; };
 		6A22E8721F1042C400F546C3 /* InspectorCanvas.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorCanvas.cpp; sourceTree = "<group>"; };
 		6A7279881F16C29B003F39B8 /* InspectorShaderProgram.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InspectorShaderProgram.h; sourceTree = "<group>"; };
@@ -21904,6 +21907,8 @@
 				450CEBEF15073BBE002BB149 /* LabelableElement.h */,
 				A456FA2411AD4A830020B420 /* LabelsNodeList.cpp */,
 				A456FA2511AD4A830020B420 /* LabelsNodeList.h */,
+				AAD9D0B121DFA80C001B11C7 /* LazyLoadImageObserver.cpp */,
+				AAD9D0B321DFA80E001B11C7 /* LazyLoadImageObserver.h */,
 				1A4DA41F1CDD3A8300F4473C /* LinkIconCollector.cpp */,
 				1A4DA4201CDD3A8300F4473C /* LinkIconCollector.h */,
 				1A250E0C1CDD632000D0BE63 /* LinkIconType.h */,
@@ -30998,6 +31003,7 @@
 				6F7CA3C6208C2957002F29AB /* LayoutState.h in Headers */,
 				11310CF220BA4A320065A8D0 /* LayoutTreeBuilder.h in Headers */,
 				141DC0481648348F00371E5A /* LayoutUnit.h in Headers */,
+				AAD9D0B521DFA810001B11C7 /* LazyLoadImageObserver.h in Headers */,
 				CDE8B5ED1A69777300B4B66A /* LegacyCDMPrivateClearKey.h in Headers */,
 				CDF4B7121E0087AE00E235A2 /* LegacyCDMSession.h in Headers */,
 				CDE8B5F11A69778B00B4B66A /* LegacyCDMSessionClearKey.h in Headers */,

Modified: trunk/Source/WebCore/dom/Document.cpp (251636 => 251637)


--- trunk/Source/WebCore/dom/Document.cpp	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/Source/WebCore/dom/Document.cpp	2019-10-26 17:17:54 UTC (rev 251637)
@@ -119,6 +119,7 @@
 #include "KeyboardEvent.h"
 #include "KeyframeEffect.h"
 #include "LayoutDisallowedScope.h"
+#include "LazyLoadImageObserver.h"
 #include "LegacySchemeRegistry.h"
 #include "LibWebRTCProvider.h"
 #include "LoaderStrategy.h"
@@ -8325,4 +8326,11 @@
     return *m_textManipulationController;
 }
 
+LazyLoadImageObserver& Document::lazyLoadImageObserver()
+{
+    if (!m_lazyLoadImageObserver)
+        m_lazyLoadImageObserver = makeUnique<LazyLoadImageObserver>();
+    return *m_lazyLoadImageObserver;
+}
+
 } // namespace WebCore

Modified: trunk/Source/WebCore/dom/Document.h (251636 => 251637)


--- trunk/Source/WebCore/dom/Document.h	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/Source/WebCore/dom/Document.h	2019-10-26 17:17:54 UTC (rev 251637)
@@ -153,6 +153,7 @@
 class JSNode;
 class LayoutPoint;
 class LayoutRect;
+class LazyLoadImageObserver;
 class LiveNodeList;
 class Locale;
 class Location;
@@ -1556,6 +1557,8 @@
     WEBCORE_EXPORT TextManipulationController& textManipulationController();
     TextManipulationController* textManipulationControllerIfExists() { return m_textManipulationController.get(); }
 
+    LazyLoadImageObserver& lazyLoadImageObserver();
+
 protected:
     enum ConstructionFlags { Synthesized = 1, NonRenderedPlaceholder = 1 << 1 };
     Document(Frame*, const URL&, unsigned = DefaultDocumentClass, unsigned constructionFlags = 0);
@@ -1741,6 +1744,8 @@
 
     Element* m_cssTarget { nullptr };
 
+    std::unique_ptr<LazyLoadImageObserver> m_lazyLoadImageObserver;
+
     RefPtr<SerializedScriptValue> m_pendingStateObject;
     MonotonicTime m_documentCreationTime;
     bool m_overMinimumLayoutThreshold { false };

Modified: trunk/Source/WebCore/html/HTMLImageElement.cpp (251636 => 251637)


--- trunk/Source/WebCore/html/HTMLImageElement.cpp	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/Source/WebCore/html/HTMLImageElement.cpp	2019-10-26 17:17:54 UTC (rev 251637)
@@ -43,6 +43,7 @@
 #include "HTMLMapElement.h"
 #include "HTMLSourceElement.h"
 #include "HTMLSrcsetParser.h"
+#include "LazyLoadImageObserver.h"
 #include "Logging.h"
 #include "MIMETypeRegistry.h"
 #include "MediaList.h"
@@ -53,6 +54,7 @@
 #include "RenderImage.h"
 #include "RenderView.h"
 #include "RuntimeEnabledFeatures.h"
+#include "ScriptController.h"
 #include "Settings.h"
 #include "ShadowRoot.h"
 #include "SizesAttributeParser.h"
@@ -244,7 +246,10 @@
 #endif
     } else if (name == x_apple_editable_imageAttr)
         updateEditableImage();
-    else {
+    else if (name == loadingAttr) {
+        if (!equalLettersIgnoringASCIICase(value, "lazy"))
+            loadDeferredImage();
+    } else {
         if (name == nameAttr) {
             bool willHaveName = !value.isNull();
             if (m_hadNameBeforeAttributeChanged != willHaveName && isConnected() && !isInShadowTree() && is<HTMLDocument>(document())) {
@@ -263,6 +268,11 @@
     }
 }
 
+void HTMLImageElement::loadDeferredImage()
+{
+    m_imageLoader->loadDeferredImage();
+}
+
 const AtomString& HTMLImageElement::altText() const
 {
     // lets figure out the alt text.. magic stuff
@@ -665,6 +675,7 @@
 
 void HTMLImageElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
 {
+    m_createdByParser = false;
     m_imageLoader->elementDidMoveToNewDocument();
     HTMLElement::didMoveToNewDocument(oldDocument, newDocument);
 }
@@ -861,4 +872,35 @@
     return m_imageLoader->pendingDecodePromisesCountForTesting();
 }
 
+const AtomString& HTMLImageElement::loadingForBindings() const
+{
+    static NeverDestroyed<AtomString> autoValue("auto", AtomString::ConstructFromLiteral);
+    static NeverDestroyed<AtomString> eager("eager", AtomString::ConstructFromLiteral);
+    static NeverDestroyed<AtomString> lazy("lazy", AtomString::ConstructFromLiteral);
+    auto attributeValue = attributeWithoutSynchronization(HTMLNames::loadingAttr);
+    if (equalLettersIgnoringASCIICase(attributeValue, "eager"))
+        return eager;
+    if (equalLettersIgnoringASCIICase(attributeValue, "lazy"))
+        return lazy;
+    return autoValue;
 }
+
+void HTMLImageElement::setLoadingForBindings(const AtomString& value)
+{
+    setAttributeWithoutSynchronization(loadingAttr, value);
+}
+
+bool HTMLImageElement::isDeferred() const
+{
+    return m_imageLoader->isDeferred();
+}
+
+bool HTMLImageElement::isLazyLoadable() const
+{
+    if (document().frame() && !document().frame()->script().canExecuteScripts(NotAboutToExecuteScript))
+        return false;
+    // Never do lazy loading for image elements created from _javascript_.
+    return createdByParser() && equalLettersIgnoringASCIICase(attributeWithoutSynchronization(HTMLNames::loadingAttr), "lazy");
+}
+
+}

Modified: trunk/Source/WebCore/html/HTMLImageElement.h (251636 => 251637)


--- trunk/Source/WebCore/html/HTMLImageElement.h	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/Source/WebCore/html/HTMLImageElement.h	2019-10-26 17:17:54 UTC (rev 251637)
@@ -126,8 +126,17 @@
 
     void defaultEventHandler(Event&) final;
 
+    void loadDeferredImage();
+
     bool createdByParser() const { return m_createdByParser; }
 
+    const AtomString& loadingForBindings() const;
+    void setLoadingForBindings(const AtomString&);
+
+    bool isLazyLoadable() const;
+
+    bool isDeferred() const;
+
     bool isDroppedImagePlaceholder() const { return m_isDroppedImagePlaceholder; }
     void setIsDroppedImagePlaceholder() { m_isDroppedImagePlaceholder = true; }
 

Modified: trunk/Source/WebCore/html/HTMLImageElement.idl (251636 => 251637)


--- trunk/Source/WebCore/html/HTMLImageElement.idl	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/Source/WebCore/html/HTMLImageElement.idl	2019-10-26 17:17:54 UTC (rev 251637)
@@ -44,7 +44,7 @@
 
     [Conditional=ATTACHMENT_ELEMENT, EnabledAtRuntime=AttachmentElement] readonly attribute DOMString attachmentIdentifier;
 
-    [EnabledAtRuntime=LazyImageLoading, CEReactions, Reflect] attribute DOMString loading;
+    [CEReactions, EnabledAtRuntime=LazyImageLoading, ImplementedAs=loadingForBindings] attribute DOMString loading;
 
     // Extensions
     readonly attribute boolean complete;

Added: trunk/Source/WebCore/html/LazyLoadImageObserver.cpp (0 => 251637)


--- trunk/Source/WebCore/html/LazyLoadImageObserver.cpp	                        (rev 0)
+++ trunk/Source/WebCore/html/LazyLoadImageObserver.cpp	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "LazyLoadImageObserver.h"
+
+#include "Frame.h"
+#include "HTMLImageElement.h"
+#include "IntersectionObserverCallback.h"
+#include "RenderStyle.h"
+
+#include <limits>
+
+namespace WebCore {
+
+class LazyImageLoadIntersectionObserverCallback final : public IntersectionObserverCallback {
+public:
+    static Ref<LazyImageLoadIntersectionObserverCallback> create(Document& document)
+    {
+        return adoptRef(*new LazyImageLoadIntersectionObserverCallback(document));
+    }
+
+private:
+    CallbackResult<void> handleEvent(const Vector<Ref<IntersectionObserverEntry>>& entries, IntersectionObserver&) final
+    {
+        ASSERT(!entries.isEmpty());
+
+        for (auto& entry : entries) {
+            if (!entry->isIntersecting())
+                continue;
+            Element* element = entry->target();
+            if (is<HTMLImageElement>(element)) {
+                downcast<HTMLImageElement>(*element).loadDeferredImage();
+                element->document().lazyLoadImageObserver().unobserve(*element);
+            }
+        }
+        return { };
+    }
+
+    LazyImageLoadIntersectionObserverCallback(Document& document)
+        : IntersectionObserverCallback(&document)
+    {
+    }
+};
+
+void LazyLoadImageObserver::observe(Element& element)
+{
+    auto& observer = element.document().lazyLoadImageObserver();
+    auto* intersectionObserver = observer.intersectionObserver(element.document());
+    if (!intersectionObserver)
+        return;
+    intersectionObserver->observe(element);
+}
+
+void LazyLoadImageObserver::unobserve(Element& element)
+{
+    auto& observer = element.document().lazyLoadImageObserver();
+    ASSERT(observer.isObserved(element));
+    observer.m_lazyLoadIntersectionObserver->unobserve(element);
+}
+
+IntersectionObserver* LazyLoadImageObserver::intersectionObserver(Document& document)
+{
+    if (!m_lazyLoadIntersectionObserver) {
+        auto callback = LazyImageLoadIntersectionObserverCallback::create(document);
+        auto options = IntersectionObserver::Init { nullptr, emptyString(), { } };
+        auto observer = IntersectionObserver::create(document, WTFMove(callback), WTFMove(options));
+        if (observer.hasException())
+            return nullptr;
+        m_lazyLoadIntersectionObserver = observer.returnValue().ptr();
+    }
+    return m_lazyLoadIntersectionObserver.get();
+}
+
+bool LazyLoadImageObserver::isObserved(Element& element) const
+{
+    return m_lazyLoadIntersectionObserver && m_lazyLoadIntersectionObserver->observationTargets().contains(&element);
+}
+
+}

Added: trunk/Source/WebCore/html/LazyLoadImageObserver.h (0 => 251637)


--- trunk/Source/WebCore/html/LazyLoadImageObserver.h	                        (rev 0)
+++ trunk/Source/WebCore/html/LazyLoadImageObserver.h	2019-10-26 17:17:54 UTC (rev 251637)
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "IntersectionObserver.h"
+
+namespace WebCore {
+
+class Document;
+class Element;
+
+class LazyLoadImageObserver {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    LazyLoadImageObserver() = default;
+
+    static void observe(Element&);
+    static void unobserve(Element&);
+
+private:
+    IntersectionObserver* intersectionObserver(Document&);
+    bool isObserved(Element&) const;
+
+    // The intersection observer responsible for loading the image once it's near
+    // the viewport.
+    RefPtr<IntersectionObserver> m_lazyLoadIntersectionObserver;
+};
+
+} // namespace

Modified: trunk/Source/WebCore/html/parser/HTMLPreloadScanner.cpp (251636 => 251637)


--- trunk/Source/WebCore/html/parser/HTMLPreloadScanner.cpp	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/Source/WebCore/html/parser/HTMLPreloadScanner.cpp	2019-10-26 17:17:54 UTC (rev 251637)
@@ -161,6 +161,10 @@
         if (!LinkLoader::isSupportedType(type.value(), m_typeAttribute))
             return nullptr;
 
+        // Do not preload if lazyload is possible but metadata fetch is disabled.
+        if (equalLettersIgnoringASCIICase(m_lazyloadAttribute, "lazy"))
+            return nullptr;
+
         auto request = makeUnique<PreloadRequest>(initiatorFor(m_tagId), m_urlToLoad, predictedBaseURL, type.value(), m_mediaAttribute, m_moduleScript, m_referrerPolicy);
         request->setCrossOriginMode(m_crossOriginMode);
         request->setNonce(m_nonceAttribute);
@@ -206,6 +210,12 @@
                 m_sizesAttribute = attributeValue;
                 break;
             }
+            if (RuntimeEnabledFeatures::sharedFeatures().lazyImageLoadingEnabled()) {
+                if (match(attributeName, loadingAttr) && m_lazyloadAttribute.isNull()) {
+                    m_lazyloadAttribute = attributeValue;
+                    break;
+                }
+            }
             processImageAndScriptAttribute(attributeName, attributeValue);
             break;
         case TagId::Source:
@@ -370,6 +380,7 @@
     String m_metaContent;
     String m_asAttribute;
     String m_typeAttribute;
+    String m_lazyloadAttribute;
     bool m_metaIsViewport;
     bool m_metaIsDisabledAdaptations;
     bool m_inputIsImage;

Modified: trunk/Source/WebCore/loader/ImageLoader.cpp (251636 => 251637)


--- trunk/Source/WebCore/loader/ImageLoader.cpp	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/Source/WebCore/loader/ImageLoader.cpp	2019-10-26 17:17:54 UTC (rev 251637)
@@ -39,9 +39,11 @@
 #include "HTMLParserIdioms.h"
 #include "InspectorInstrumentation.h"
 #include "JSDOMPromiseDeferred.h"
+#include "LazyLoadImageObserver.h"
 #include "Page.h"
 #include "RenderImage.h"
 #include "RenderSVGImage.h"
+#include "RuntimeEnabledFeatures.h"
 #include <wtf/NeverDestroyed.h>
 
 #if ENABLE(VIDEO)
@@ -197,8 +199,16 @@
             newImage->setLoading(true);
             document.cachedResourceLoader().m_documentResources.set(newImage->url(), newImage.get());
             document.cachedResourceLoader().setAutoLoadImages(autoLoadOtherImages);
-        } else
-            newImage = document.cachedResourceLoader().requestImage(WTFMove(request)).value_or(nullptr);
+        } else {
+            if (m_lazyImageLoadState == LazyImageLoadState::None) {
+                if (is<HTMLImageElement>(element())) {
+                    auto& imageElement = downcast<HTMLImageElement>(element());
+                    if (imageElement.isLazyLoadable() && RuntimeEnabledFeatures::sharedFeatures().lazyImageLoadingEnabled())
+                        m_lazyImageLoadState = LazyImageLoadState::Deferred;
+                }
+            }
+            newImage = document.cachedResourceLoader().requestImage(WTFMove(request), (m_lazyImageLoadState == LazyImageLoadState::Deferred) ? ImageLoading::DeferredUntilVisible : ImageLoading::Immediate).value_or(nullptr);
+        }
 
         // If we do not have an image here, it means that a cross-site
         // violation occurred, or that the image was blocked via Content
@@ -251,6 +261,9 @@
             } else
                 updateRenderer();
 
+            if (m_lazyImageLoadState == LazyImageLoadState::Deferred)
+                LazyLoadImageObserver::observe(element());
+
             // If newImage is cached, addClient() will result in the load event
             // being queued to fire. Ensure this happens after beforeload is
             // dispatched.
@@ -307,6 +320,11 @@
     ASSERT(m_failedLoadURL.isEmpty());
     ASSERT_UNUSED(resource, &resource == m_image.get());
 
+    if (m_lazyImageLoadState == LazyImageLoadState::Deferred) {
+        LazyLoadImageObserver::unobserve(element());
+        m_lazyImageLoadState = LazyImageLoadState::FullImage;
+    }
+
     m_imageComplete = true;
     if (!hasPendingBeforeLoadEvent())
         updateRenderer();
@@ -560,4 +578,12 @@
     m_failedLoadURL = nullAtom();
 }
 
+void ImageLoader::loadDeferredImage()
+{
+    if (m_lazyImageLoadState != LazyImageLoadState::Deferred)
+        return;
+    m_lazyImageLoadState = LazyImageLoadState::FullImage;
+    updateFromElement();
 }
+
+}

Modified: trunk/Source/WebCore/loader/ImageLoader.h (251636 => 251637)


--- trunk/Source/WebCore/loader/ImageLoader.h	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/Source/WebCore/loader/ImageLoader.h	2019-10-26 17:17:54 UTC (rev 251637)
@@ -75,11 +75,17 @@
     static void dispatchPendingLoadEvents();
     static void dispatchPendingErrorEvents();
 
+    void loadDeferredImage();
+
+    bool isDeferred() const { return m_lazyImageLoadState == LazyImageLoadState::Deferred; }
+
 protected:
     explicit ImageLoader(Element&);
     void notifyFinished(CachedResource&) override;
 
 private:
+    enum class LazyImageLoadState { None, Deferred, FullImage };
+
     virtual void dispatchLoadEvent() = 0;
     virtual String sourceURI(const AtomString&) const = 0;
 
@@ -114,6 +120,7 @@
     bool m_imageComplete : 1;
     bool m_loadManually : 1;
     bool m_elementIsProtected : 1;
+    LazyImageLoadState m_lazyImageLoadState { LazyImageLoadState::None };
 };
 
 }

Modified: trunk/Source/WebCore/loader/cache/CachedImage.h (251636 => 251637)


--- trunk/Source/WebCore/loader/cache/CachedImage.h	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/Source/WebCore/loader/cache/CachedImage.h	2019-10-26 17:17:54 UTC (rev 251637)
@@ -91,7 +91,9 @@
     void removeAllClientsWaitingForAsyncDecoding();
 
     void setForceUpdateImageDataEnabledForTesting(bool enabled) { m_forceUpdateImageDataEnabledForTesting =  enabled; }
-    
+
+    bool stillNeedsLoad() const override { return !errorOccurred() && status() == Unknown && !isLoading(); }
+
 private:
     void clear();
 
@@ -128,8 +130,6 @@
     // For compatibility, images keep loading even if there are HTTP errors.
     bool shouldIgnoreHTTPStatusCodeErrors() const override { return true; }
 
-    bool stillNeedsLoad() const override { return !errorOccurred() && status() == Unknown && !isLoading(); }
-
     class CachedImageObserver final : public RefCounted<CachedImageObserver>, public ImageObserver {
     public:
         static Ref<CachedImageObserver> create(CachedImage& image) { return adoptRef(*new CachedImageObserver(image)); }

Modified: trunk/Source/WebCore/loader/cache/CachedResourceLoader.cpp (251636 => 251637)


--- trunk/Source/WebCore/loader/cache/CachedResourceLoader.cpp	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/Source/WebCore/loader/cache/CachedResourceLoader.cpp	2019-10-26 17:17:54 UTC (rev 251637)
@@ -207,7 +207,7 @@
     return m_documentLoader ? m_documentLoader->frame() : nullptr;
 }
 
-ResourceErrorOr<CachedResourceHandle<CachedImage>> CachedResourceLoader::requestImage(CachedResourceRequest&& request)
+ResourceErrorOr<CachedResourceHandle<CachedImage>> CachedResourceLoader::requestImage(CachedResourceRequest&& request, ImageLoading imageLoading)
 {
     if (Frame* frame = this->frame()) {
         if (frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::PageDismissalType::None) {
@@ -220,8 +220,9 @@
         }
     }
 
-    auto defer = clientDefersImage(request.resourceRequest().url()) ? DeferOption::DeferredByClient : DeferOption::NoDefer;
-    return castCachedResourceTo<CachedImage>(requestResource(CachedResource::Type::ImageResource, WTFMove(request), ForPreload::No, defer));
+    if (imageLoading == ImageLoading::Immediate)
+        imageLoading = clientDefersImage(request.resourceRequest().url());
+    return castCachedResourceTo<CachedImage>(requestResource(CachedResource::Type::ImageResource, WTFMove(request), ForPreload::No, imageLoading));
 }
 
 ResourceErrorOr<CachedResourceHandle<CachedFont>> CachedResourceLoader::requestFont(CachedResourceRequest&& request, bool isSVG)
@@ -824,7 +825,7 @@
 }
 #endif
 
-ResourceErrorOr<CachedResourceHandle<CachedResource>> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest&& request, ForPreload forPreload, DeferOption defer)
+ResourceErrorOr<CachedResourceHandle<CachedResource>> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest&& request, ForPreload forPreload, ImageLoading imageLoading)
 {
     if (!frame() || !frame()->page()) {
         RELEASE_LOG_IF_ALLOWED("requestResource: failed because no frame or page");
@@ -948,7 +949,7 @@
 
     auto& cookieJar = page.cookieJar();
 
-    RevalidationPolicy policy = determineRevalidationPolicy(type, request, resource.get(), forPreload, defer);
+    RevalidationPolicy policy = determineRevalidationPolicy(type, request, resource.get(), forPreload, imageLoading);
     switch (policy) {
     case Reload:
         memoryCache.remove(*resource);
@@ -1010,7 +1011,7 @@
         incrementRequestCount(*resource);
     }
 
-    if ((policy != Use || resource->stillNeedsLoad()) && defer == DeferOption::NoDefer) {
+    if ((policy != Use || resource->stillNeedsLoad()) && imageLoading == ImageLoading::Immediate) {
         resource->load(*this);
 
         // We don't support immediate loads, but we do support immediate failure.
@@ -1119,7 +1120,7 @@
     }
 }
 
-CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalidationPolicy(CachedResource::Type type, CachedResourceRequest& cachedResourceRequest, CachedResource* existingResource, ForPreload forPreload, DeferOption defer) const
+CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalidationPolicy(CachedResource::Type type, CachedResourceRequest& cachedResourceRequest, CachedResource* existingResource, ForPreload forPreload, ImageLoading imageLoading) const
 {
     auto& request = cachedResourceRequest.resourceRequest();
 
@@ -1175,7 +1176,7 @@
     ASSERT(!request.isConditional());
 
     // Do not load from cache if images are not enabled. The load for this image will be blocked in CachedImage::load.
-    if (defer == DeferOption::DeferredByClient)
+    if (imageLoading == ImageLoading::DeferredUntilVisible)
         return Reload;
 
     // Don't reload resources while pasting or if cache mode allows stale resources.
@@ -1320,9 +1321,9 @@
     reloadImagesIfNotDeferred();
 }
 
-bool CachedResourceLoader::clientDefersImage(const URL&) const
+ImageLoading CachedResourceLoader::clientDefersImage(const URL&) const
 {
-    return !m_imagesEnabled;
+    return m_imagesEnabled ? ImageLoading::Immediate : ImageLoading::DeferredUntilVisible;
 }
 
 bool CachedResourceLoader::shouldPerformImageLoad(const URL& url) const
@@ -1332,13 +1333,13 @@
 
 bool CachedResourceLoader::shouldDeferImageLoad(const URL& url) const
 {
-    return clientDefersImage(url) || !shouldPerformImageLoad(url);
+    return clientDefersImage(url) == ImageLoading::DeferredUntilVisible || !shouldPerformImageLoad(url);
 }
 
 void CachedResourceLoader::reloadImagesIfNotDeferred()
 {
     for (auto& resource : m_documentResources.values()) {
-        if (is<CachedImage>(*resource) && resource->stillNeedsLoad() && !clientDefersImage(resource->url()))
+        if (is<CachedImage>(*resource) && resource->stillNeedsLoad() && clientDefersImage(resource->url()) == ImageLoading::Immediate)
             downcast<CachedImage>(*resource).load(*this);
     }
 }

Modified: trunk/Source/WebCore/loader/cache/CachedResourceLoader.h (251636 => 251637)


--- trunk/Source/WebCore/loader/cache/CachedResourceLoader.h	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/Source/WebCore/loader/cache/CachedResourceLoader.h	2019-10-26 17:17:54 UTC (rev 251637)
@@ -62,6 +62,8 @@
 template <typename T>
 using ResourceErrorOr = Expected<T, ResourceError>;
 
+enum class ImageLoading : uint8_t { Immediate, DeferredUntilVisible };
+
 // The CachedResourceLoader provides a per-context interface to the MemoryCache
 // and enforces a bunch of security checks and rules for resource revalidation.
 // Its lifetime is roughly per-DocumentLoader, in that it is generally created
@@ -79,7 +81,7 @@
     static Ref<CachedResourceLoader> create(DocumentLoader* documentLoader) { return adoptRef(*new CachedResourceLoader(documentLoader)); }
     ~CachedResourceLoader();
 
-    ResourceErrorOr<CachedResourceHandle<CachedImage>> requestImage(CachedResourceRequest&&);
+    ResourceErrorOr<CachedResourceHandle<CachedImage>> requestImage(CachedResourceRequest&&, ImageLoading = ImageLoading::Immediate);
     ResourceErrorOr<CachedResourceHandle<CachedCSSStyleSheet>> requestCSSStyleSheet(CachedResourceRequest&&);
     CachedResourceHandle<CachedCSSStyleSheet> requestUserCSSStyleSheet(Page&, CachedResourceRequest&&);
     ResourceErrorOr<CachedResourceHandle<CachedScript>> requestScript(CachedResourceRequest&&);
@@ -163,9 +165,8 @@
     explicit CachedResourceLoader(DocumentLoader*);
 
     enum class ForPreload { Yes, No };
-    enum class DeferOption { NoDefer, DeferredByClient };
 
-    ResourceErrorOr<CachedResourceHandle<CachedResource>> requestResource(CachedResource::Type, CachedResourceRequest&&, ForPreload = ForPreload::No, DeferOption = DeferOption::NoDefer);
+    ResourceErrorOr<CachedResourceHandle<CachedResource>> requestResource(CachedResource::Type, CachedResourceRequest&&, ForPreload = ForPreload::No, ImageLoading = ImageLoading::Immediate);
     CachedResourceHandle<CachedResource> revalidateResource(CachedResourceRequest&&, CachedResource&);
     CachedResourceHandle<CachedResource> loadResource(CachedResource::Type, PAL::SessionID, CachedResourceRequest&&, const CookieJar&);
 
@@ -175,7 +176,7 @@
     bool canRequest(CachedResource::Type, const URL&, const CachedResourceRequest&, ForPreload);
 
     enum RevalidationPolicy { Use, Revalidate, Reload, Load };
-    RevalidationPolicy determineRevalidationPolicy(CachedResource::Type, CachedResourceRequest&, CachedResource* existingResource, ForPreload, DeferOption) const;
+    RevalidationPolicy determineRevalidationPolicy(CachedResource::Type, CachedResourceRequest&, CachedResource* existingResource, ForPreload, ImageLoading) const;
 
     bool shouldUpdateCachedResourceWithCurrentRequest(const CachedResource&, const CachedResourceRequest&);
     CachedResourceHandle<CachedResource> updateCachedResourceWithCurrentRequest(const CachedResource&, CachedResourceRequest&&, const PAL::SessionID&, const CookieJar&);
@@ -185,7 +186,7 @@
 
     void performPostLoadActions();
 
-    bool clientDefersImage(const URL&) const;
+    ImageLoading clientDefersImage(const URL&) const;
     void reloadImagesIfNotDeferred();
 
     bool canRequestAfterRedirection(CachedResource::Type, const URL&, const ResourceLoaderOptions&) const;

Modified: trunk/Source/WebCore/rendering/RenderImage.cpp (251636 => 251637)


--- trunk/Source/WebCore/rendering/RenderImage.cpp	2019-10-26 15:15:20 UTC (rev 251636)
+++ trunk/Source/WebCore/rendering/RenderImage.cpp	2019-10-26 17:17:54 UTC (rev 251637)
@@ -423,6 +423,11 @@
     context.drawRect(snapRectToDevicePixels(LayoutRect({ paintOffset.x() + leftBorder + leftPadding, paintOffset.y() + topBorder + topPadding }, contentSize), document().deviceScaleFactor()), borderWidth);
 }
 
+static bool isDeferredImage(Element* element)
+{
+    return is<HTMLImageElement>(element) && downcast<HTMLImageElement>(element)->isDeferred();
+}
+
 void RenderImage::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
 {
     GraphicsContext& context = paintInfo.context();
@@ -436,7 +441,7 @@
     float deviceScaleFactor = document().deviceScaleFactor();
     LayoutUnit missingImageBorderWidth(1 / deviceScaleFactor);
 
-    if (!imageResource().cachedImage() || shouldDisplayBrokenImageIcon()) {
+    if (!imageResource().cachedImage() || isDeferredImage(element()) || shouldDisplayBrokenImageIcon()) {
         if (paintInfo.phase == PaintPhase::Selection)
             return;
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to