Async loading of image resources
authoryoav@yoav.ws <yoav@yoav.ws@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 1 Apr 2015 19:02:35 +0000 (19:02 +0000)
committeryoav@yoav.ws <yoav@yoav.ws@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 1 Apr 2015 19:02:35 +0000 (19:02 +0000)
https://bugs.webkit.org/show_bug.cgi?id=134488

Reviewed by Dean Jackson.

Source/WebCore:

This patch changes image loading to be asynchronous (per spec), in order
for it to be agnostic to property setting order when it comes to src, srcset and crossorigin,
as well as to enable future hooking of the <picture>-based selection logic on top of it.

Tests: fast/dom/HTMLImageElement/image-load-post-onload.html
fast/dom/HTMLImageElement/sizes/image-sizes-js-change-reverse.html

* WebCore.xcodeproj/project.pbxproj: Change MicroTask.h to be private.
* bindings/js/JSEventListener.cpp:
(WebCore::JSEventListener::handleEvent): Add a microtask checkpoint after event handling.
* bindings/js/ScheduledAction.cpp:
(WebCore::ScheduledAction::execute): Add a microtask checkpoint after running of a scheduled action.
* html/HTMLImageElement.cpp:
(WebCore::HTMLImageElement::didMoveToNewDocument): Add the old document to call of elementDidMoveToNewDocument.
* html/HTMLImageLoader.cpp:
(WebCore::HTMLImageLoader::notifyFinished): Avoid a crash when notifyFinished is called and there's no CachedImage.
* html/HTMLInputElement.cpp:
(WebCore::HTMLInputElement::didMoveToNewDocument): Add the old document to call of elementDidMoveToNewDocument.
* html/HTMLPlugInImageElement.cpp:
(WebCore::HTMLPlugInImageElement::didMoveToNewDocument): Add the old document to call of elementDidMoveToNewDocument.
* html/HTMLVideoElement.cpp:
(WebCore::HTMLVideoElement::didMoveToNewDocument): Add the old document to call of elementDidMoveToNewDocument.
* html/parser/HTMLDocumentParser.cpp:
(WebCore::HTMLDocumentParser::attemptToRunDeferredScriptsAndEnd): Add a microtask checkpoint.
* loader/ImageLoader.cpp: Move image resource loading to be asynchronous.
(WebCore::ImageLoader::ImageLoaderTask::create):
(WebCore::ImageLoader::ImageLoaderTask::run): Run the image loading microtask and called doUpdateFromElement.
(WebCore::ImageLoader::ImageLoaderTask::ImageLoaderTask):
(WebCore::ImageLoader::ImageLoader):
(WebCore::ImageLoader::doUpdateFromElement): This is split from the previous updateFromElement, and performs the actual resource loading.
(WebCore::ImageLoader::updateFromElement): Now only prepares the stage for the actual image resource loading.
(WebCore::ImageLoader::shouldLoadImmediately): If this returns true, the image resource is loaded immediately, without queueing a microtask.
(WebCore::ImageLoader::notifyFinished): Add asserts.
(WebCore::ImageLoader::elementDidMoveToNewDocument): Handle document load event counters decrementing and incrementing.
* loader/ImageLoader.h:
(WebCore::ImageLoader::imageComplete): Make sure that the image is complete only if there aren't any pending tasks.
(WebCore::ImageLoader::hasPendingActivity): Make sure that pending activity takes pending tasks into account.
(WebCore::ImageLoader::hasPendingTask): Getter to know if an ImageLoader has a pending task. (Used by the image loading microtask)
(WebCore::ImageLoader::createWeakPtr): Create a weakPtr to be used by the microtask, so it can called back the loader if it's still alive.
* loader/cache/CachedResourceLoader.cpp:
(WebCore::CachedResourceLoader::canRequest): Add a 'shouldBypassMainWorldContentSecurityPolicy' flag and act upon it.
(WebCore::CachedResourceLoader::requestImage): Add a 'shouldBypassMainWorldContentSecurityPolicy' flag.
(WebCore::CachedResourceLoader::requestResource): Add a 'shouldBypassMainWorldContentSecurityPolicy' flag.
* loader/cache/CachedResourceLoader.h:
(WebCore::CachedResourceLoader::canRequest): Add a 'shouldBypassMainWorldContentSecurityPolicy' flag and act upon it.
(WebCore::CachedResourceLoader::requestImage): Add a 'shouldBypassMainWorldContentSecurityPolicy' flag.
(WebCore::CachedResourceLoader::requestResource): Add a 'shouldBypassMainWorldContentSecurityPolicy' flag.
* svg/SVGImageElement.cpp:
(WebCore::SVGImageElement::didMoveToNewDocument): Add the old document to call of elementDidMoveToNewDocument.
* xml/parser/XMLDocumentParser.cpp:
(WebCore::XMLDocumentParser::end): Add a microtask checkpoint after XML finishes parsing.

LayoutTests:

* fast/dom/HTMLImageElement/image-load-post-onload-expected.txt: Added.
* fast/dom/HTMLImageElement/image-load-post-onload.html: Added.
* fast/dom/HTMLImageElement/image-loading-gc.html: Cosmetic changes.
* fast/dom/HTMLImageElement/sizes/image-sizes-js-change-reverse-expected.txt: Added.
* fast/dom/HTMLImageElement/sizes/image-sizes-js-change-reverse.html: Added.
* fast/dom/image-object.html: Cosmetic changes.
* http/tests/misc/image-blocked-src-change-expected.txt: Removed line numbers from console.
* http/tests/misc/image-blocked-src-no-change-expected.txt: Removed line numbers from console.
* http/tests/security/contentSecurityPolicy/report-blocked-file-uri-expected.txt: Removed line numbers from console.
* http/tests/security/frame-loading-via-document-write-expected.txt: Removed line numbers from console.
* http/tests/security/local-image-from-remote-expected.txt: Removed line numbers from console.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@182247 268f45cc-cd09-0410-ab3c-d52691b4dbfc

28 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/dom/HTMLImageElement/image-load-post-onload-expected.txt [new file with mode: 0644]
LayoutTests/fast/dom/HTMLImageElement/image-load-post-onload.html [new file with mode: 0644]
LayoutTests/fast/dom/HTMLImageElement/image-loading-gc.html
LayoutTests/fast/dom/HTMLImageElement/sizes/image-sizes-js-change-reverse-expected.txt [new file with mode: 0644]
LayoutTests/fast/dom/HTMLImageElement/sizes/image-sizes-js-change-reverse.html [new file with mode: 0644]
LayoutTests/fast/dom/image-object.html
LayoutTests/http/tests/misc/image-blocked-src-change-expected.txt
LayoutTests/http/tests/misc/image-blocked-src-no-change-expected.txt
LayoutTests/http/tests/security/contentSecurityPolicy/report-blocked-file-uri-expected.txt
LayoutTests/http/tests/security/frame-loading-via-document-write-expected.txt
LayoutTests/http/tests/security/local-image-from-remote-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/bindings/js/JSEventListener.cpp
Source/WebCore/bindings/js/ScheduledAction.cpp
Source/WebCore/html/HTMLImageElement.cpp
Source/WebCore/html/HTMLImageLoader.cpp
Source/WebCore/html/HTMLInputElement.cpp
Source/WebCore/html/HTMLPlugInImageElement.cpp
Source/WebCore/html/HTMLVideoElement.cpp
Source/WebCore/html/parser/HTMLDocumentParser.cpp
Source/WebCore/loader/ImageLoader.cpp
Source/WebCore/loader/ImageLoader.h
Source/WebCore/loader/cache/CachedResourceLoader.cpp
Source/WebCore/loader/cache/CachedResourceLoader.h
Source/WebCore/svg/SVGImageElement.cpp
Source/WebCore/xml/parser/XMLDocumentParser.cpp

index 3af250f..4b7c86e 100644 (file)
@@ -1,3 +1,22 @@
+2015-04-01  Yoav Weiss  <yoav@yoav.ws>
+
+        Async loading of image resources
+        https://bugs.webkit.org/show_bug.cgi?id=134488
+
+        Reviewed by Dean Jackson.
+
+        * fast/dom/HTMLImageElement/image-load-post-onload-expected.txt: Added.
+        * fast/dom/HTMLImageElement/image-load-post-onload.html: Added.
+        * fast/dom/HTMLImageElement/image-loading-gc.html: Cosmetic changes.
+        * fast/dom/HTMLImageElement/sizes/image-sizes-js-change-reverse-expected.txt: Added.
+        * fast/dom/HTMLImageElement/sizes/image-sizes-js-change-reverse.html: Added.
+        * fast/dom/image-object.html: Cosmetic changes.
+        * http/tests/misc/image-blocked-src-change-expected.txt: Removed line numbers from console.
+        * http/tests/misc/image-blocked-src-no-change-expected.txt: Removed line numbers from console.
+        * http/tests/security/contentSecurityPolicy/report-blocked-file-uri-expected.txt: Removed line numbers from console.
+        * http/tests/security/frame-loading-via-document-write-expected.txt: Removed line numbers from console.
+        * http/tests/security/local-image-from-remote-expected.txt: Removed line numbers from console.
+
 2015-03-31  Simon Fraser  <simon.fraser@apple.com>
 
         Make it possible to compute a region for elements on the page that have wheel event handlers
diff --git a/LayoutTests/fast/dom/HTMLImageElement/image-load-post-onload-expected.txt b/LayoutTests/fast/dom/HTMLImageElement/image-load-post-onload-expected.txt
new file mode 100644 (file)
index 0000000..484c1d5
--- /dev/null
@@ -0,0 +1,2 @@
+This tests that an image element is loading even if its src was changed after the page's onload event.
+SUCCESS
diff --git a/LayoutTests/fast/dom/HTMLImageElement/image-load-post-onload.html b/LayoutTests/fast/dom/HTMLImageElement/image-load-post-onload.html
new file mode 100644 (file)
index 0000000..95ae520
--- /dev/null
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<script>
+function runTest() {
+    if (window.testRunner) {
+        testRunner.dumpAsText();
+        testRunner.waitUntilDone();
+    }
+
+    var image = new Image();
+    var first = true;
+    image.onload = function () {
+        if (!first) {
+            document.getElementById('result').innerHTML = 'SUCCESS';
+            if (window.testRunner)
+                testRunner.notifyDone();
+        }
+        image.src = 'resources/image-set-4x.png';
+        first = false;
+    }
+
+    image.src = 'resources/blue_rect.jpg'
+}
+</script>
+<body onload="runTest()">
+    <div>This tests that an image element is loading even if its src was changed after the page's onload event.</div>
+<div id="result">FAILURE</div>
+</body>
+</html>
index 7203500..869dad3 100644 (file)
@@ -32,7 +32,7 @@ function runTest() {
 }
 </script>
 <body onload="runTest()">
-<div>This tests that an image element won't be garbage collected when it's loading, even if it doesn't have any references.
+    <div>This tests that an image element won't be garbage collected when it's loading, even if it doesn't have any references.</div>
 <div id="result">FAILURE</div>
 </body>
 </html>
diff --git a/LayoutTests/fast/dom/HTMLImageElement/sizes/image-sizes-js-change-reverse-expected.txt b/LayoutTests/fast/dom/HTMLImageElement/sizes/image-sizes-js-change-reverse-expected.txt
new file mode 100644 (file)
index 0000000..1599929
--- /dev/null
@@ -0,0 +1,6 @@
+image-set-4x.png has MIME type image/png
+PASS currentSrcFileName("foo") is "image-set-4x.png"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/dom/HTMLImageElement/sizes/image-sizes-js-change-reverse.html b/LayoutTests/fast/dom/HTMLImageElement/sizes/image-sizes-js-change-reverse.html
new file mode 100644 (file)
index 0000000..a17e4fe
--- /dev/null
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<script src="../../../../resources/srcset-helper.js"></script>
+<script src="../../../../resources/js-test.js"></script>
+<script src="../resources/currentSrcHelper.js"></script>
+<script>
+    if (window.testRunner)
+        testRunner.dumpAsText();
+
+    function updateSrc() {
+        var img = document.getElementById("foo");
+        // Here we're setting sizes last, and srcset after src to make sure image loading is done is "stable-state"
+        img.src = "../resources/blue-100-px-square.png"
+        img.srcset = "../../../hidpi/resources/image-set-1x.png 200w, ../../../hidpi/resources/image-set-2x.png 400w, ../resources/image-set-4x.png 800w";
+        img.sizes = "400px";
+    }
+
+    addEventListener("DOMContentLoaded", function() {
+        if (sessionStorage.pageReloaded)
+            testRunner.dumpResourceResponseMIMETypes();
+        updateSrc();
+    }, false);
+
+    function runTest()
+    {
+        shouldBe('currentSrcFileName("foo")', '"image-set-4x.png"');
+    }
+</script>
+<img id="foo">
index 9472933..0b7512f 100644 (file)
@@ -46,8 +46,7 @@ function runTests() {
         debug('FAILURE: Empty image should have complete set to true.');
         numErrs++;
     }
-    
-    i2 = new Image()
+    i2 = new Image();
     i2.onload = imageLoaded;
     i2.src = 'resources/apple.gif';
 }
index 65ab53f..6b53db7 100644 (file)
@@ -1,4 +1,4 @@
-CONSOLE MESSAGE: line 44: Not allowed to load local resource: compass.jpg
+CONSOLE MESSAGE: Not allowed to load local resource: compass.jpg
 Test case for bug 17897 : Not Rendering Images Imported from XHTML Document
 
 You should see PASSED once and 250 x 50 green box. The console should also show a blocked attempt to load a local resource.
index 552f00a..7ec9097 100644 (file)
@@ -1,5 +1,5 @@
-CONSOLE MESSAGE: line 27: Not allowed to load local resource: compass.jpg
-CONSOLE MESSAGE: line 21: Not allowed to load local resource: compass.jpg
+CONSOLE MESSAGE: Not allowed to load local resource: compass.jpg
+CONSOLE MESSAGE: Not allowed to load local resource: compass.jpg
 Test case for bug 17897 : Not Rendering Images Imported from XHTML Document
 
 This test checks that setting an image url should lead to load even if the image was in error and the url is not changed.
index 2ca2b09..43b76e4 100644 (file)
@@ -5,4 +5,4 @@ CONTENT_TYPE: application/json
 HTTP_REFERER: http://127.0.0.1:8000/security/contentSecurityPolicy/report-blocked-file-uri.html
 REQUEST_METHOD: POST
 === POST DATA ===
-{"csp-report":{"document-uri":"http://127.0.0.1:8000/security/contentSecurityPolicy/report-blocked-file-uri.html","referrer":"","violated-directive":"img-src 'none'","original-policy":"img-src 'none'; report-uri resources/save-report.php","blocked-uri":"file","source-file":"http://127.0.0.1:8000/security/contentSecurityPolicy/report-blocked-file-uri.html","line-number":12}}
+{"csp-report":{"document-uri":"http://127.0.0.1:8000/security/contentSecurityPolicy/report-blocked-file-uri.html","referrer":"","violated-directive":"img-src 'none'","original-policy":"img-src 'none'; report-uri resources/save-report.php","blocked-uri":"file"}}
index d4b9f90..78e8746 100644 (file)
@@ -1,4 +1,4 @@
-CONSOLE MESSAGE: line 1: Not allowed to load local resource: abe.png
+CONSOLE MESSAGE: Not allowed to load local resource: abe.png
 
 
 --------
index 17ea23b..3fcc3ec 100644 (file)
@@ -1,4 +1,4 @@
-CONSOLE MESSAGE: line 14: Not allowed to load local resource: compass.jpg
+CONSOLE MESSAGE: Not allowed to load local resource: compass.jpg
 This test is to see if a remote file can include a local image. 
 Currently this test cannot be run manually on Windows because we do not have a function like pathToLocalResource() outside of DRT. 
 
index afb9107..bfac56f 100644 (file)
@@ -1,3 +1,62 @@
+2015-04-01  Yoav Weiss  <yoav@yoav.ws>
+
+        Async loading of image resources
+        https://bugs.webkit.org/show_bug.cgi?id=134488
+
+        Reviewed by Dean Jackson.
+
+        This patch changes image loading to be asynchronous (per spec), in order
+        for it to be agnostic to property setting order when it comes to src, srcset and crossorigin,
+        as well as to enable future hooking of the <picture>-based selection logic on top of it.
+
+        Tests: fast/dom/HTMLImageElement/image-load-post-onload.html
+        fast/dom/HTMLImageElement/sizes/image-sizes-js-change-reverse.html
+
+        * WebCore.xcodeproj/project.pbxproj: Change MicroTask.h to be private.
+        * bindings/js/JSEventListener.cpp:
+        (WebCore::JSEventListener::handleEvent): Add a microtask checkpoint after event handling.
+        * bindings/js/ScheduledAction.cpp:
+        (WebCore::ScheduledAction::execute): Add a microtask checkpoint after running of a scheduled action.
+        * html/HTMLImageElement.cpp:
+        (WebCore::HTMLImageElement::didMoveToNewDocument): Add the old document to call of elementDidMoveToNewDocument.
+        * html/HTMLImageLoader.cpp:
+        (WebCore::HTMLImageLoader::notifyFinished): Avoid a crash when notifyFinished is called and there's no CachedImage.
+        * html/HTMLInputElement.cpp:
+        (WebCore::HTMLInputElement::didMoveToNewDocument): Add the old document to call of elementDidMoveToNewDocument.
+        * html/HTMLPlugInImageElement.cpp:
+        (WebCore::HTMLPlugInImageElement::didMoveToNewDocument): Add the old document to call of elementDidMoveToNewDocument.
+        * html/HTMLVideoElement.cpp:
+        (WebCore::HTMLVideoElement::didMoveToNewDocument): Add the old document to call of elementDidMoveToNewDocument.
+        * html/parser/HTMLDocumentParser.cpp:
+        (WebCore::HTMLDocumentParser::attemptToRunDeferredScriptsAndEnd): Add a microtask checkpoint.
+        * loader/ImageLoader.cpp: Move image resource loading to be asynchronous.
+        (WebCore::ImageLoader::ImageLoaderTask::create):
+        (WebCore::ImageLoader::ImageLoaderTask::run): Run the image loading microtask and called doUpdateFromElement.
+        (WebCore::ImageLoader::ImageLoaderTask::ImageLoaderTask):
+        (WebCore::ImageLoader::ImageLoader):
+        (WebCore::ImageLoader::doUpdateFromElement): This is split from the previous updateFromElement, and performs the actual resource loading.
+        (WebCore::ImageLoader::updateFromElement): Now only prepares the stage for the actual image resource loading.
+        (WebCore::ImageLoader::shouldLoadImmediately): If this returns true, the image resource is loaded immediately, without queueing a microtask.
+        (WebCore::ImageLoader::notifyFinished): Add asserts.
+        (WebCore::ImageLoader::elementDidMoveToNewDocument): Handle document load event counters decrementing and incrementing.
+        * loader/ImageLoader.h:
+        (WebCore::ImageLoader::imageComplete): Make sure that the image is complete only if there aren't any pending tasks.
+        (WebCore::ImageLoader::hasPendingActivity): Make sure that pending activity takes pending tasks into account.
+        (WebCore::ImageLoader::hasPendingTask): Getter to know if an ImageLoader has a pending task. (Used by the image loading microtask)
+        (WebCore::ImageLoader::createWeakPtr): Create a weakPtr to be used by the microtask, so it can called back the loader if it's still alive.
+        * loader/cache/CachedResourceLoader.cpp:
+        (WebCore::CachedResourceLoader::canRequest): Add a 'shouldBypassMainWorldContentSecurityPolicy' flag and act upon it.
+        (WebCore::CachedResourceLoader::requestImage): Add a 'shouldBypassMainWorldContentSecurityPolicy' flag.
+        (WebCore::CachedResourceLoader::requestResource): Add a 'shouldBypassMainWorldContentSecurityPolicy' flag.
+        * loader/cache/CachedResourceLoader.h:
+        (WebCore::CachedResourceLoader::canRequest): Add a 'shouldBypassMainWorldContentSecurityPolicy' flag and act upon it.
+        (WebCore::CachedResourceLoader::requestImage): Add a 'shouldBypassMainWorldContentSecurityPolicy' flag.
+        (WebCore::CachedResourceLoader::requestResource): Add a 'shouldBypassMainWorldContentSecurityPolicy' flag.
+        * svg/SVGImageElement.cpp:
+        (WebCore::SVGImageElement::didMoveToNewDocument): Add the old document to call of elementDidMoveToNewDocument.
+        * xml/parser/XMLDocumentParser.cpp:
+        (WebCore::XMLDocumentParser::end): Add a microtask checkpoint after XML finishes parsing.
+
 2015-04-01  Alex Christensen  <achristensen@webkit.org>
 
         Unreviewed build fix when using content extensions performance reporting.
index 82a253a..168ce51 100644 (file)
                536D5A23193E8E0C00CE4CAB /* ParsingUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 536D5A22193E8E0C00CE4CAB /* ParsingUtilities.h */; };
                536D5A25193F40FC00CE4CAB /* SourceSizeList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 536D5A24193F40FC00CE4CAB /* SourceSizeList.cpp */; };
                536D5A27193F410B00CE4CAB /* SourceSizeList.h in Headers */ = {isa = PBXBuildFile; fileRef = 536D5A26193F410B00CE4CAB /* SourceSizeList.h */; settings = {ATTRIBUTES = (Private, ); }; };
-               53B895AF19DC7ED9009CAA93 /* MicroTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 53B895AD19DC7C37009CAA93 /* MicroTask.h */; settings = {ATTRIBUTES = (Public, ); }; };
+               53B895AF19DC7ED9009CAA93 /* MicroTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 53B895AD19DC7C37009CAA93 /* MicroTask.h */; settings = {ATTRIBUTES = (Private, ); }; };
                53C8298D13D8D92700DE2DEB /* RenderFlexibleBox.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 53C8298B13D8D92700DE2DEB /* RenderFlexibleBox.cpp */; };
                53C8298E13D8D92700DE2DEB /* RenderFlexibleBox.h in Headers */ = {isa = PBXBuildFile; fileRef = 53C8298C13D8D92700DE2DEB /* RenderFlexibleBox.h */; settings = {ATTRIBUTES = (Private, ); }; };
                53E29E5E167A8A1900586D3D /* InternalSettingsGenerated.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 53E29E5C167A8A1900586D3D /* InternalSettingsGenerated.cpp */; };
index ffb0580..9d2ce6b 100644 (file)
@@ -29,6 +29,7 @@
 #include "JSEventTarget.h"
 #include "JSMainThreadExecState.h"
 #include "JSMainThreadExecStateInstrumentation.h"
+#include "MicroTask.h"
 #include "ScriptController.h"
 #include "WorkerGlobalScope.h"
 #include <runtime/ExceptionHelpers.h>
@@ -151,6 +152,8 @@ void JSEventListener::handleEvent(ScriptExecutionContext* scriptExecutionContext
             }
         }
     }
+    if (scriptExecutionContext->isDocument())
+        MicroTaskQueue::singleton().runMicroTasks();
 }
 
 bool JSEventListener::virtualisAttribute() const
index 060e6fa..b42fffb 100644 (file)
@@ -34,6 +34,7 @@
 #include "JSMainThreadExecState.h"
 #include "JSMainThreadExecStateInstrumentation.h"
 #include "JSWorkerGlobalScope.h"
+#include "MicroTask.h"
 #include "ScriptController.h"
 #include "ScriptExecutionContext.h"
 #include "ScriptSourceCode.h"
@@ -125,6 +126,8 @@ void ScheduledAction::execute(Document& document)
         executeFunctionInContext(window, window->shell(), document);
     else
         frame->script().executeScriptInWorld(*m_isolatedWorld, m_code);
+
+    MicroTaskQueue::singleton().runMicroTasks();
 }
 
 void ScheduledAction::execute(WorkerGlobalScope& workerGlobalScope)
index 89954cf..1b3f783 100644 (file)
@@ -452,7 +452,7 @@ void HTMLImageElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
 
 void HTMLImageElement::didMoveToNewDocument(Document* oldDocument)
 {
-    m_imageLoader.elementDidMoveToNewDocument();
+    m_imageLoader.elementDidMoveToNewDocument(oldDocument);
     HTMLElement::didMoveToNewDocument(oldDocument);
 }
 
index 3e5f81c..b81de31 100644 (file)
@@ -76,6 +76,8 @@ String HTMLImageLoader::sourceURI(const AtomicString& attr) const
 void HTMLImageLoader::notifyFinished(CachedResource*)
 {
     CachedImage* cachedImage = image();
+    if (!cachedImage)
+        return;
 
     Ref<Element> protect(element());
     ImageLoader::notifyFinished(cachedImage);
index ec247ce..29fb23e 100644 (file)
@@ -1495,7 +1495,7 @@ void HTMLInputElement::removedFrom(ContainerNode& insertionPoint)
 void HTMLInputElement::didMoveToNewDocument(Document* oldDocument)
 {
     if (imageLoader())
-        imageLoader()->elementDidMoveToNewDocument();
+        imageLoader()->elementDidMoveToNewDocument(oldDocument);
 
     bool needsSuspensionCallback = this->needsSuspensionCallback();
     if (oldDocument) {
index 83f1b7e..0cf9d59 100644 (file)
@@ -306,7 +306,7 @@ void HTMLPlugInImageElement::didMoveToNewDocument(Document* oldDocument)
     }
 
     if (m_imageLoader)
-        m_imageLoader->elementDidMoveToNewDocument();
+        m_imageLoader->elementDidMoveToNewDocument(oldDocument);
 
     HTMLPlugInElement::didMoveToNewDocument(oldDocument);
 }
index a94b1d9..8d434f9 100644 (file)
@@ -309,7 +309,7 @@ void HTMLVideoElement::setWebkitWirelessVideoPlaybackDisabled(bool disabled)
 void HTMLVideoElement::didMoveToNewDocument(Document* oldDocument)
 {
     if (m_imageLoader)
-        m_imageLoader->elementDidMoveToNewDocument();
+        m_imageLoader->elementDidMoveToNewDocument(oldDocument);
     HTMLMediaElement::didMoveToNewDocument(oldDocument);
 }
 
index 93c5f93..a5dddf8 100644 (file)
@@ -34,6 +34,7 @@
 #include "HTMLTreeBuilder.h"
 #include "HTMLDocument.h"
 #include "InspectorInstrumentation.h"
+#include "MicroTask.h"
 
 namespace WebCore {
 
@@ -409,6 +410,8 @@ void HTMLDocumentParser::attemptToRunDeferredScriptsAndEnd()
     if (m_scriptRunner && !m_scriptRunner->executeScriptsWaitingForParsing())
         return;
     end();
+    if (!isExecutingScript())
+        MicroTaskQueue::singleton().runMicroTasks();
 }
 
 void HTMLDocumentParser::attemptToEnd()
index 0919212..627b7b6 100644 (file)
@@ -2,6 +2,8 @@
  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
  * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010 Apple Inc. All rights reserved.
+ * Copyright 2014 The Chromium Authors. All rights reserved.
+ * Copyright (C) 2015 Akamai Technologies Inc. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
 #include "HTMLNames.h"
 #include "HTMLObjectElement.h"
 #include "HTMLParserIdioms.h"
+#include "MemoryCache.h"
 #include "Page.h"
 #include "RenderImage.h"
 #include "RenderSVGImage.h"
+#include "ScriptController.h"
 #include "SecurityOrigin.h"
 #include <wtf/NeverDestroyed.h>
 
@@ -63,6 +67,25 @@ template<> struct ValueCheck<WebCore::ImageLoader*> {
 
 namespace WebCore {
 
+class ImageLoader::ImageLoaderTask : public MicroTask {
+public:
+    ImageLoaderTask(WeakPtr<ImageLoader> loader, CachedResourceLoader::ShouldBypassMainWorldContentSecurityPolicy shouldBypassMainWorldContentSecurityPolicy)
+        : m_loader(loader)
+        , m_shouldBypassMainWorldContentSecurityPolicy(shouldBypassMainWorldContentSecurityPolicy)
+    {
+    }
+
+private:
+    virtual void run() override
+    {
+        if (m_loader && m_loader->hasPendingTask())
+            m_loader->doUpdateFromElement(m_shouldBypassMainWorldContentSecurityPolicy);
+    }
+
+    WeakPtr<ImageLoader> m_loader;
+    CachedResourceLoader::ShouldBypassMainWorldContentSecurityPolicy m_shouldBypassMainWorldContentSecurityPolicy;
+};
+
 static ImageEventSender& beforeLoadEventSender()
 {
     static NeverDestroyed<ImageEventSender> sender(eventNames().beforeloadEvent);
@@ -91,12 +114,14 @@ ImageLoader::ImageLoader(Element& element)
     : m_element(element)
     , m_image(0)
     , m_derefElementTimer(*this, &ImageLoader::timerFired)
+    , m_weakFactory(this)
     , m_hasPendingBeforeLoadEvent(false)
     , m_hasPendingLoadEvent(false)
     , m_hasPendingErrorEvent(false)
     , m_imageComplete(true)
     , m_loadManually(false)
     , m_elementIsProtected(false)
+    , m_hasPendingTask(false)
 {
 }
 
@@ -159,28 +184,26 @@ void ImageLoader::clearImageWithoutConsideringPendingLoadEvent()
         imageResource->resetAnimation();
 }
 
-void ImageLoader::updateFromElement()
+void ImageLoader::doUpdateFromElement(CachedResourceLoader::ShouldBypassMainWorldContentSecurityPolicy shouldBypassMainWorldContentSecurityPolicy)
 {
-    // If we're not making renderers for the page, then don't load images.  We don't want to slow
-    // down the raw HTML parsing case by loading images we don't intend to display.
-    Document& document = element().document();
-    if (!document.hasLivingRenderTree())
-        return;
-
-    AtomicString attr = element().imageSourceURL();
-
-    // Avoid loading a URL we already failed to load.
-    if (!m_failedLoadURL.isEmpty() && attr == m_failedLoadURL)
-        return;
-
+    m_hasPendingTask = false;
+    Document& document = m_element.document();
+    AtomicString attr = m_element.imageSourceURL();
+
+    String srcURI = sourceURI(attr);
+    URL url;
+    // Set url value only if srcURI is not empty. Otherwise, url will be the URL for the document itself.
+    if (!srcURI.isEmpty())
+        url = document.completeURL(srcURI);
     // Do not load any image if the 'src' attribute is missing or if it is
     // an empty string.
     CachedResourceHandle<CachedImage> newImage = 0;
-    if (!attr.isNull() && !stripLeadingAndTrailingHTMLSpaces(attr).isEmpty()) {
-        CachedResourceRequest request(ResourceRequest(document.completeURL(sourceURI(attr))));
+    if (!url.isNull()) {
+        ResourceRequest resourceRequest(url);
+        CachedResourceRequest request(resourceRequest);
         request.setInitiator(&element());
 
-        String crossOriginMode = element().fastGetAttribute(HTMLNames::crossoriginAttr);
+        AtomicString crossOriginMode = m_element.fastGetAttribute(HTMLNames::crossoriginAttr);
         if (!crossOriginMode.isNull()) {
             StoredCredentials allowCredentials = equalIgnoringCase(crossOriginMode, "use-credentials") ? AllowStoredCredentials : DoNotAllowStoredCredentials;
             updateRequestForAccessControl(request.mutableResourceRequest(), document.securityOrigin(), allowCredentials);
@@ -195,7 +218,7 @@ void ImageLoader::updateFromElement()
             document.cachedResourceLoader().m_documentResources.set(newImage->url(), newImage.get());
             document.cachedResourceLoader().setAutoLoadImages(autoLoadOtherImages);
         } else
-            newImage = document.cachedResourceLoader().requestImage(request);
+            newImage = document.cachedResourceLoader().requestImage(request, shouldBypassMainWorldContentSecurityPolicy);
 
         // If we do not have an image here, it means that a cross-site
         // violation occurred, or that the image was blocked via Content
@@ -213,7 +236,7 @@ void ImageLoader::updateFromElement()
         m_hasPendingErrorEvent = true;
         errorEventSender().dispatchEventSoon(*this);
     }
-    
+
     CachedImage* oldImage = m_image.get();
     if (newImage != oldImage) {
         if (m_hasPendingBeforeLoadEvent) {
@@ -265,6 +288,47 @@ void ImageLoader::updateFromElement()
     // Only consider updating the protection ref-count of the Element immediately before returning
     // from this function as doing so might result in the destruction of this ImageLoader.
     updatedHasPendingEvent();
+    document.decrementLoadEventDelayCount();
+}
+
+void ImageLoader::updateFromElement()
+{
+    AtomicString attribute = m_element.imageSourceURL();
+
+    // Avoid loading a URL we already failed to load.
+    if (!m_failedLoadURL.isEmpty() && attribute == m_failedLoadURL)
+        return;
+
+    // If we're not making renderers for the page, then don't load images. We don't want to slow
+    // down the raw HTML parsing case by loading images we don't intend to display.
+    Document& document = element().document();
+    if (!document.hasLivingRenderTree())
+        return;
+
+    CachedResourceLoader::ShouldBypassMainWorldContentSecurityPolicy shouldBypassMainWorldContentSecurityPolicy = CachedResourceLoader::ShouldBypassMainWorldContentSecurityPolicy::No;
+    if (document.frame() && document.frame()->script().shouldBypassMainWorldContentSecurityPolicy())
+        shouldBypassMainWorldContentSecurityPolicy = CachedResourceLoader::ShouldBypassMainWorldContentSecurityPolicy::Yes;
+
+    if (!m_hasPendingTask) {
+        m_hasPendingTask = true;
+        document.incrementLoadEventDelayCount();
+        if (shouldLoadImmediately(attribute))
+            doUpdateFromElement(CachedResourceLoader::ShouldBypassMainWorldContentSecurityPolicy::No);
+        else
+            MicroTaskQueue::singleton().queueMicroTask(std::make_unique<ImageLoaderTask>(createWeakPtr(), shouldBypassMainWorldContentSecurityPolicy));
+    }
+}
+
+bool ImageLoader::shouldLoadImmediately(const AtomicString& attribute) const
+{
+    String srcURI = sourceURI(attribute);
+    URL url = element().document().completeURL(srcURI);
+    return (srcURI.isEmpty()
+        || url.isEmpty()
+        || m_loadManually
+        || !is<HTMLImageElement>(m_element)
+        || url.protocolIsData()
+        || MemoryCache::singleton().resourceForURL(url));
 }
 
 void ImageLoader::updateFromElementIgnoringPreviousError()
@@ -276,6 +340,7 @@ void ImageLoader::updateFromElementIgnoringPreviousError()
 void ImageLoader::notifyFinished(CachedResource* resource)
 {
     ASSERT(m_failedLoadURL.isEmpty());
+    ASSERT(resource);
     ASSERT(resource == m_image.get());
 
     m_imageComplete = true;
@@ -285,6 +350,9 @@ void ImageLoader::notifyFinished(CachedResource* resource)
     if (!m_hasPendingLoadEvent)
         return;
 
+    ASSERT(image());
+    ASSERT(element().document().securityOrigin());
+
     if (element().fastHasAttribute(HTMLNames::crossoriginAttr)
         && !element().document().securityOrigin()->canRequest(image()->response().url())
         && !resource->passesAccessControlCheck(element().document().securityOrigin())) {
@@ -464,8 +532,13 @@ void ImageLoader::dispatchPendingErrorEvents()
     errorEventSender().dispatchPendingEvents();
 }
 
-void ImageLoader::elementDidMoveToNewDocument()
+void ImageLoader::elementDidMoveToNewDocument(Document* oldDocument)
 {
+    if (m_hasPendingTask) {
+        if (oldDocument)
+            oldDocument->decrementLoadEventDelayCount();
+        m_element.document().incrementLoadEventDelayCount();
+    }
     clearFailedLoadURL();
     clearImage();
 }
index 8449f11..7f740b2 100644 (file)
 
 #include "CachedImageClient.h"
 #include "CachedResourceHandle.h"
+#include "CachedResourceLoader.h"
+#include "MicroTask.h"
 #include "Timer.h"
+#include <wtf/RefCounted.h>
+#include <wtf/WeakPtr.h>
 #include <wtf/text/AtomicString.h>
 
 namespace WebCore {
 
+class Document;
 class Element;
 class ImageLoader;
 class RenderImageResource;
+class URL;
+class Task;
 
 template<typename T> class EventSender;
 typedef EventSender<ImageLoader> ImageEventSender;
@@ -50,12 +57,12 @@ public:
     // doesn't change; starts new load unconditionally (matches Firefox and Opera behavior).
     void updateFromElementIgnoringPreviousError();
 
-    void elementDidMoveToNewDocument();
+    void elementDidMoveToNewDocument(Document* oldDocument);
 
     Element& element() { return m_element; }
     const Element& element() const { return m_element; }
 
-    bool imageComplete() const { return m_imageComplete; }
+    bool imageComplete() const { return m_imageComplete && !m_hasPendingTask; }
 
     CachedImage* image() const { return m_image.get(); }
     void clearImage(); // Cancels pending beforeload and load events, and doesn't dispatch new ones.
@@ -63,7 +70,7 @@ public:
     void setLoadManually(bool loadManually) { m_loadManually = loadManually; }
 
     bool hasPendingBeforeLoadEvent() const { return m_hasPendingBeforeLoadEvent; }
-    bool hasPendingActivity() const { return m_hasPendingLoadEvent || m_hasPendingErrorEvent; }
+    bool hasPendingActivity() const { return m_hasPendingLoadEvent || m_hasPendingErrorEvent || m_hasPendingTask; }
 
     void dispatchPendingEvent(ImageEventSender*);
 
@@ -71,11 +78,17 @@ public:
     static void dispatchPendingLoadEvents();
     static void dispatchPendingErrorEvents();
 
+    bool hasPendingTask() const { return m_hasPendingTask; }
+
 protected:
     explicit ImageLoader(Element&);
     virtual void notifyFinished(CachedResource*) override;
 
 private:
+    class ImageLoaderTask;
+
+    // Called from the task or from updateFromElement to initiate the load.
+    void doUpdateFromElement(CachedResourceLoader::ShouldBypassMainWorldContentSecurityPolicy);
     virtual void dispatchLoadEvent() = 0;
     virtual String sourceURI(const AtomicString&) const = 0;
 
@@ -93,9 +106,14 @@ private:
 
     void timerFired();
 
+    // Determine whether to initiate a synchronous load or to schedule a microtask.
+    bool shouldLoadImmediately(const AtomicString& attribtue) const;
+    WeakPtr<ImageLoader> createWeakPtr() { return m_weakFactory.createWeakPtr(); }
+
     Element& m_element;
     CachedResourceHandle<CachedImage> m_image;
     Timer m_derefElementTimer;
+    WeakPtrFactory<ImageLoader> m_weakFactory;
     AtomicString m_failedLoadURL;
     bool m_hasPendingBeforeLoadEvent : 1;
     bool m_hasPendingLoadEvent : 1;
@@ -103,6 +121,7 @@ private:
     bool m_imageComplete : 1;
     bool m_loadManually : 1;
     bool m_elementIsProtected : 1;
+    bool m_hasPendingTask : 1;
 };
 
 }
index f3c2af0..94f380a 100644 (file)
@@ -173,7 +173,7 @@ SessionID CachedResourceLoader::sessionID() const
     return sessionID;
 }
 
-CachedResourceHandle<CachedImage> CachedResourceLoader::requestImage(CachedResourceRequest& request)
+CachedResourceHandle<CachedImage> CachedResourceLoader::requestImage(CachedResourceRequest& request, ShouldBypassMainWorldContentSecurityPolicy shouldBypassMainWorldContentSecurityPolicy)
 {
     if (Frame* frame = this->frame()) {
         if (frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::NoDismissal) {
@@ -185,7 +185,7 @@ CachedResourceHandle<CachedImage> CachedResourceLoader::requestImage(CachedResou
     }
     
     request.setDefer(clientDefersImage(request.resourceRequest().url()) ? CachedResourceRequest::DeferredByClient : CachedResourceRequest::NoDefer);
-    return downcast<CachedImage>(requestResource(CachedResource::ImageResource, request).get());
+    return downcast<CachedImage>(requestResource(CachedResource::ImageResource, request, shouldBypassMainWorldContentSecurityPolicy).get());
 }
 
 CachedResourceHandle<CachedFont> CachedResourceLoader::requestFont(CachedResourceRequest& request, bool isSVG)
@@ -357,7 +357,7 @@ bool CachedResourceLoader::checkInsecureContent(CachedResource::Type type, const
     return true;
 }
 
-bool CachedResourceLoader::canRequest(CachedResource::Type type, const URL& url, const ResourceLoaderOptions& options, bool forPreload)
+bool CachedResourceLoader::canRequest(CachedResource::Type type, const URL& url, const ResourceLoaderOptions& options, bool forPreload, ShouldBypassMainWorldContentSecurityPolicy bypassMainWorldContentSecurityPolicy)
 {
     if (document() && !document()->securityOrigin()->canDisplay(url)) {
         if (!forPreload)
@@ -366,8 +366,10 @@ bool CachedResourceLoader::canRequest(CachedResource::Type type, const URL& url,
         return 0;
     }
 
+
     // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved.
-    bool shouldBypassMainWorldContentSecurityPolicy = (frame() && frame()->script().shouldBypassMainWorldContentSecurityPolicy());
+    bool shouldBypassMainWorldContentSecurityPolicy = (bypassMainWorldContentSecurityPolicy == ShouldBypassMainWorldContentSecurityPolicy::Yes)
+        || (frame() && frame()->script().shouldBypassMainWorldContentSecurityPolicy());
 
     // Some types of resources can be loaded only from the same origin.  Other
     // types of resources, like Images, Scripts, and CSS, can be loaded from
@@ -492,7 +494,7 @@ static inline void logMemoryCacheResourceRequest(Frame* frame, const String& des
         frame->mainFrame().diagnosticLoggingClient().logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceRequestKey(), description, value, ShouldSample::Yes);
 }
 
-CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest& request)
+CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest& request, ShouldBypassMainWorldContentSecurityPolicy shouldBypassMainWorldContentSecurityPolicy)
 {
     URL url = request.resourceRequest().url();
     
@@ -504,7 +506,7 @@ CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(Cache
     if (!url.isValid())
         return nullptr;
 
-    if (!canRequest(type, url, request.options(), request.forPreload()))
+    if (!canRequest(type, url, request.options(), request.forPreload(), shouldBypassMainWorldContentSecurityPolicy))
         return nullptr;
 
 #if ENABLE(CONTENT_EXTENSIONS)
index 5447d4f..26b260a 100644 (file)
@@ -71,7 +71,9 @@ public:
     static Ref<CachedResourceLoader> create(DocumentLoader* documentLoader) { return adoptRef(*new CachedResourceLoader(documentLoader)); }
     ~CachedResourceLoader();
 
-    CachedResourceHandle<CachedImage> requestImage(CachedResourceRequest&);
+    enum class ShouldBypassMainWorldContentSecurityPolicy { No, Yes };
+
+    CachedResourceHandle<CachedImage> requestImage(CachedResourceRequest&, ShouldBypassMainWorldContentSecurityPolicy = ShouldBypassMainWorldContentSecurityPolicy::No);
     CachedResourceHandle<CachedCSSStyleSheet> requestCSSStyleSheet(CachedResourceRequest&);
     CachedResourceHandle<CachedCSSStyleSheet> requestUserCSSStyleSheet(CachedResourceRequest&);
     CachedResourceHandle<CachedScript> requestScript(CachedResourceRequest&);
@@ -131,7 +133,7 @@ public:
     void preload(CachedResource::Type, CachedResourceRequest&, const String& charset);
     void checkForPendingPreloads();
     void printPreloadStats();
-    bool canRequest(CachedResource::Type, const URL&, const ResourceLoaderOptions&, bool forPreload = false);
+    bool canRequest(CachedResource::Type, const URL&, const ResourceLoaderOptions&, bool forPreload = false, ShouldBypassMainWorldContentSecurityPolicy = ShouldBypassMainWorldContentSecurityPolicy::No);
 
     static const ResourceLoaderOptions& defaultCachedResourceOptions();
 
@@ -140,7 +142,7 @@ public:
 private:
     explicit CachedResourceLoader(DocumentLoader*);
 
-    CachedResourceHandle<CachedResource> requestResource(CachedResource::Type, CachedResourceRequest&);
+    CachedResourceHandle<CachedResource> requestResource(CachedResource::Type, CachedResourceRequest&, ShouldBypassMainWorldContentSecurityPolicy = ShouldBypassMainWorldContentSecurityPolicy::No);
     CachedResourceHandle<CachedResource> revalidateResource(const CachedResourceRequest&, CachedResource*);
     CachedResourceHandle<CachedResource> loadResource(CachedResource::Type, CachedResourceRequest&);
 #if ENABLE(RESOURCE_TIMING)
index b366812..9aa90a2 100644 (file)
@@ -205,7 +205,7 @@ void SVGImageElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
 
 void SVGImageElement::didMoveToNewDocument(Document* oldDocument)
 {
-    m_imageLoader.elementDidMoveToNewDocument();
+    m_imageLoader.elementDidMoveToNewDocument(oldDocument);
     SVGGraphicsElement::didMoveToNewDocument(oldDocument);
 }
 
index f55a615..521e749 100644 (file)
@@ -40,6 +40,7 @@
 #include "HTMLNames.h"
 #include "HTMLStyleElement.h"
 #include "ImageLoader.h"
+#include "MicroTask.h"
 #include "ProcessingInstruction.h"
 #include "ResourceError.h"
 #include "ResourceRequest.h"
@@ -200,6 +201,7 @@ void XMLDocumentParser::end()
     document()->setReadyState(Document::Interactive);
     clearCurrentNodeStack();
     document()->finishedParsing();
+    MicroTaskQueue::singleton().runMicroTasks();
 }
 
 void XMLDocumentParser::finish()