Title: [233915] trunk
Revision
233915
Author
wenson_hs...@apple.com
Date
2018-07-18 10:08:46 -0700 (Wed, 18 Jul 2018)

Log Message

Add SPI to defer running async script until after document load
https://bugs.webkit.org/show_bug.cgi?id=187748
<rdar://problem/42317378>

Reviewed by Ryosuke Niwa and Tim Horton.

Source/WebCore:

On watchOS, we currently observe that time-consuming async scripts can block the first paint of Reader, leaving
the user with a blank screen for tens of seconds. One way to mitigate this is to defer async script execution
until after document load (i.e. the same timing as DOMContentLoaded).

This patch introduces an SPI configuration allowing internal clients to defer execution of asynchronous script
until after document load; this, in combination with the parser yielding token introduced in r233891, allows
Safari on watchOS to avoid being blocked on slow script execution before the first paint of the Reader page on
most article-like pages. See below for more details.

Test: RunScriptAfterDocumentLoad.ExecutionOrderOfScriptsInDocument

* dom/Document.cpp:
(WebCore::Document::shouldDeferAsynchronousScriptsUntilParsingFinishes const):
(WebCore::Document::finishedParsing):

Notify ScriptRunner when the Document has finished parsing, and is about to fire DOMContentLoaded.

* dom/Document.h:
* dom/ScriptRunner.cpp:
(WebCore::ScriptRunner::documentFinishedParsing):

When the document is finished parsing, kick off the script execution timer if needed to run any async script
that has been deferred.

(WebCore::ScriptRunner::notifyFinished):
(WebCore::ScriptRunner::timerFired):

Instead of always taking from the list of async scripts to execute, check our document to see whether we should
defer this until after document load. If so, ignore `m_scriptsToExecuteSoon`.

* dom/ScriptRunner.h:
* page/Settings.yaml:

Add a WebCore setting for this behavior.

Source/WebKit:

Add plumbing for a new ShouldDeferAsynchronousScriptsUntilAfterDocumentLoad configuration that determines
whether async script execution should be deferred until document load (i.e. DOMContentLoaded). This
configuration defaults to NO on all platforms. See WebCore ChangeLog for more detail.

* Shared/WebPreferences.yaml:
* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _initializeWithConfiguration:]):
* UIProcess/API/Cocoa/WKWebViewConfiguration.mm:
(-[WKWebViewConfiguration init]):
(-[WKWebViewConfiguration copyWithZone:]):
(-[WKWebViewConfiguration _shouldDeferAsynchronousScriptsUntilAfterDocumentLoad]):
(-[WKWebViewConfiguration _setShouldDeferAsynchronousScriptsUntilAfterDocumentLoad:]):
* UIProcess/API/Cocoa/WKWebViewConfigurationPrivate.h:

Tools:

Add an API test to verify that when the deferred async script configuration is set, async scripts will be
executed after the DOMContentLoaded event.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/RunScriptAfterDocumentLoad.mm: Added.
(TEST):

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (233914 => 233915)


--- trunk/Source/WebCore/ChangeLog	2018-07-18 16:04:33 UTC (rev 233914)
+++ trunk/Source/WebCore/ChangeLog	2018-07-18 17:08:46 UTC (rev 233915)
@@ -1,3 +1,46 @@
+2018-07-18  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Add SPI to defer running async script until after document load
+        https://bugs.webkit.org/show_bug.cgi?id=187748
+        <rdar://problem/42317378>
+
+        Reviewed by Ryosuke Niwa and Tim Horton.
+
+        On watchOS, we currently observe that time-consuming async scripts can block the first paint of Reader, leaving
+        the user with a blank screen for tens of seconds. One way to mitigate this is to defer async script execution
+        until after document load (i.e. the same timing as DOMContentLoaded).
+
+        This patch introduces an SPI configuration allowing internal clients to defer execution of asynchronous script
+        until after document load; this, in combination with the parser yielding token introduced in r233891, allows
+        Safari on watchOS to avoid being blocked on slow script execution before the first paint of the Reader page on
+        most article-like pages. See below for more details.
+
+        Test: RunScriptAfterDocumentLoad.ExecutionOrderOfScriptsInDocument
+
+        * dom/Document.cpp:
+        (WebCore::Document::shouldDeferAsynchronousScriptsUntilParsingFinishes const):
+        (WebCore::Document::finishedParsing):
+
+        Notify ScriptRunner when the Document has finished parsing, and is about to fire DOMContentLoaded.
+
+        * dom/Document.h:
+        * dom/ScriptRunner.cpp:
+        (WebCore::ScriptRunner::documentFinishedParsing):
+
+        When the document is finished parsing, kick off the script execution timer if needed to run any async script
+        that has been deferred.
+
+        (WebCore::ScriptRunner::notifyFinished):
+        (WebCore::ScriptRunner::timerFired):
+
+        Instead of always taking from the list of async scripts to execute, check our document to see whether we should
+        defer this until after document load. If so, ignore `m_scriptsToExecuteSoon`.
+
+        * dom/ScriptRunner.h:
+        * page/Settings.yaml:
+
+        Add a WebCore setting for this behavior.
+
 2018-07-18  Zan Dobersek  <zdober...@igalia.com>
 
         [Nicosia] Add debug border, repaint counter state tracking to Nicosia::CompositionLayer

Modified: trunk/Source/WebCore/dom/Document.cpp (233914 => 233915)


--- trunk/Source/WebCore/dom/Document.cpp	2018-07-18 16:04:33 UTC (rev 233914)
+++ trunk/Source/WebCore/dom/Document.cpp	2018-07-18 17:08:46 UTC (rev 233915)
@@ -5193,6 +5193,11 @@
     m_currentScriptStack.removeLast();
 }
 
+bool Document::shouldDeferAsynchronousScriptsUntilParsingFinishes() const
+{
+    return parsing() && settings().shouldDeferAsynchronousScriptsUntilAfterDocumentLoad();
+}
+
 #if ENABLE(XSLT)
 
 void Document::scheduleToApplyXSLTransforms()
@@ -5429,6 +5434,8 @@
 
     Ref<Document> protectedThis(*this);
 
+    scriptRunner()->documentFinishedParsing();
+
     if (!m_documentTiming.domContentLoadedEventStart)
         m_documentTiming.domContentLoadedEventStart = MonotonicTime::now();
 

Modified: trunk/Source/WebCore/dom/Document.h (233914 => 233915)


--- trunk/Source/WebCore/dom/Document.h	2018-07-18 16:04:33 UTC (rev 233914)
+++ trunk/Source/WebCore/dom/Document.h	2018-07-18 17:08:46 UTC (rev 233915)
@@ -991,6 +991,8 @@
     void pushCurrentScript(HTMLScriptElement*);
     void popCurrentScript();
 
+    bool shouldDeferAsynchronousScriptsUntilParsingFinishes() const;
+
 #if ENABLE(XSLT)
     void scheduleToApplyXSLTransforms();
     void applyPendingXSLTransformsNowIfScheduled();

Modified: trunk/Source/WebCore/dom/ScriptRunner.cpp (233914 => 233915)


--- trunk/Source/WebCore/dom/ScriptRunner.cpp	2018-07-18 16:04:33 UTC (rev 233914)
+++ trunk/Source/WebCore/dom/ScriptRunner.cpp	2018-07-18 17:08:46 UTC (rev 233915)
@@ -86,6 +86,12 @@
         m_timer.startOneShot(0_s);
 }
 
+void ScriptRunner::documentFinishedParsing()
+{
+    if (!m_scriptsToExecuteSoon.isEmpty() && !m_timer.isActive())
+        resume();
+}
+
 void ScriptRunner::notifyFinished(PendingScript& pendingScript)
 {
     if (pendingScript.element().willExecuteInOrder())
@@ -105,8 +111,10 @@
     Ref<Document> protect(m_document);
 
     Vector<RefPtr<PendingScript>> scripts;
-    scripts.swap(m_scriptsToExecuteSoon);
 
+    if (!m_document.shouldDeferAsynchronousScriptsUntilParsingFinishes())
+        scripts.swap(m_scriptsToExecuteSoon);
+
     size_t numInOrderScriptsToExecute = 0;
     for (; numInOrderScriptsToExecute < m_scriptsToExecuteInOrder.size() && m_scriptsToExecuteInOrder[numInOrderScriptsToExecute]->isLoaded(); ++numInOrderScriptsToExecute)
         scripts.append(m_scriptsToExecuteInOrder[numInOrderScriptsToExecute].ptr());

Modified: trunk/Source/WebCore/dom/ScriptRunner.h (233914 => 233915)


--- trunk/Source/WebCore/dom/ScriptRunner.h	2018-07-18 16:04:33 UTC (rev 233914)
+++ trunk/Source/WebCore/dom/ScriptRunner.h	2018-07-18 17:08:46 UTC (rev 233915)
@@ -53,6 +53,8 @@
     void didBeginYieldingParser() { suspend(); }
     void didEndYieldingParser() { resume(); }
 
+    void documentFinishedParsing();
+
 private:
     void timerFired();
 

Modified: trunk/Source/WebCore/page/Settings.yaml (233914 => 233915)


--- trunk/Source/WebCore/page/Settings.yaml	2018-07-18 16:04:33 UTC (rev 233914)
+++ trunk/Source/WebCore/page/Settings.yaml	2018-07-18 17:08:46 UTC (rev 233915)
@@ -758,3 +758,6 @@
 
 incompleteImageBorderEnabled:
   initial: false
+
+shouldDeferAsynchronousScriptsUntilAfterDocumentLoad:
+  initial: false

Modified: trunk/Source/WebKit/ChangeLog (233914 => 233915)


--- trunk/Source/WebKit/ChangeLog	2018-07-18 16:04:33 UTC (rev 233914)
+++ trunk/Source/WebKit/ChangeLog	2018-07-18 17:08:46 UTC (rev 233915)
@@ -1,3 +1,25 @@
+2018-07-18  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Add SPI to defer running async script until after document load
+        https://bugs.webkit.org/show_bug.cgi?id=187748
+        <rdar://problem/42317378>
+
+        Reviewed by Ryosuke Niwa and Tim Horton.
+
+        Add plumbing for a new ShouldDeferAsynchronousScriptsUntilAfterDocumentLoad configuration that determines
+        whether async script execution should be deferred until document load (i.e. DOMContentLoaded). This
+        configuration defaults to NO on all platforms. See WebCore ChangeLog for more detail.
+
+        * Shared/WebPreferences.yaml:
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _initializeWithConfiguration:]):
+        * UIProcess/API/Cocoa/WKWebViewConfiguration.mm:
+        (-[WKWebViewConfiguration init]):
+        (-[WKWebViewConfiguration copyWithZone:]):
+        (-[WKWebViewConfiguration _shouldDeferAsynchronousScriptsUntilAfterDocumentLoad]):
+        (-[WKWebViewConfiguration _setShouldDeferAsynchronousScriptsUntilAfterDocumentLoad:]):
+        * UIProcess/API/Cocoa/WKWebViewConfigurationPrivate.h:
+
 2018-07-18  Zan Dobersek  <zdober...@igalia.com>
 
         [CoordGraphics] Start tracking Nicosia layers in CoordinatedGraphicsState

Modified: trunk/Source/WebKit/Shared/WebPreferences.yaml (233914 => 233915)


--- trunk/Source/WebKit/Shared/WebPreferences.yaml	2018-07-18 16:04:33 UTC (rev 233914)
+++ trunk/Source/WebKit/Shared/WebPreferences.yaml	2018-07-18 17:08:46 UTC (rev 233915)
@@ -1092,6 +1092,10 @@
   type: bool
   defaultValue: false
 
+ShouldDeferAsynchronousScriptsUntilAfterDocumentLoad:
+  type: bool
+  defaultValue: false
+
 StorageAccessAPIEnabled:
   type: bool
   defaultValue: true

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm (233914 => 233915)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm	2018-07-18 16:04:33 UTC (rev 233914)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm	2018-07-18 17:08:46 UTC (rev 233915)
@@ -547,6 +547,7 @@
     pageConfiguration->setDrawsBackground([_configuration _drawsBackground]);
     pageConfiguration->setControlledByAutomation([_configuration _isControlledByAutomation]);
     pageConfiguration->preferenceValues().set(WebKit::WebPreferencesKey::incompleteImageBorderEnabledKey(), WebKit::WebPreferencesStore::Value(!![_configuration _incompleteImageBorderEnabled]));
+    pageConfiguration->preferenceValues().set(WebKit::WebPreferencesKey::shouldDeferAsynchronousScriptsUntilAfterDocumentLoadKey(), WebKit::WebPreferencesStore::Value(!![_configuration _shouldDeferAsynchronousScriptsUntilAfterDocumentLoad]));
 
 #if ENABLE(APPLICATION_MANIFEST)
     pageConfiguration->setApplicationManifest([_configuration _applicationManifest] ? [configuration _applicationManifest]->_applicationManifest.get() : nullptr);

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewConfiguration.mm (233914 => 233915)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewConfiguration.mm	2018-07-18 16:04:33 UTC (rev 233914)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewConfiguration.mm	2018-07-18 17:08:46 UTC (rev 233915)
@@ -164,6 +164,7 @@
     BOOL _allowMediaContentTypesRequiringHardwareSupportAsFallback;
     BOOL _colorFilterEnabled;
     BOOL _incompleteImageBorderEnabled;
+    BOOL _shouldDeferAsynchronousScriptsUntilAfterDocumentLoad;
     BOOL _drawsBackground;
 
     RetainPtr<NSString> _overrideContentSecurityPolicy;
@@ -250,6 +251,7 @@
 
     _colorFilterEnabled = NO;
     _incompleteImageBorderEnabled = NO;
+    _shouldDeferAsynchronousScriptsUntilAfterDocumentLoad = NO;
     _drawsBackground = YES;
 
     return self;
@@ -414,6 +416,7 @@
     configuration->_groupIdentifier = adoptNS([self->_groupIdentifier copyWithZone:zone]);
     configuration->_colorFilterEnabled = self->_colorFilterEnabled;
     configuration->_incompleteImageBorderEnabled = self->_incompleteImageBorderEnabled;
+    configuration->_shouldDeferAsynchronousScriptsUntilAfterDocumentLoad = self->_shouldDeferAsynchronousScriptsUntilAfterDocumentLoad;
     configuration->_drawsBackground = self->_drawsBackground;
 
     return configuration;
@@ -784,6 +787,16 @@
     _incompleteImageBorderEnabled = incompleteImageBorderEnabled;
 }
 
+- (BOOL)_shouldDeferAsynchronousScriptsUntilAfterDocumentLoad
+{
+    return _shouldDeferAsynchronousScriptsUntilAfterDocumentLoad;
+}
+
+- (void)_setShouldDeferAsynchronousScriptsUntilAfterDocumentLoad:(BOOL)shouldDeferAsynchronousScriptsUntilAfterDocumentLoad
+{
+    _shouldDeferAsynchronousScriptsUntilAfterDocumentLoad = shouldDeferAsynchronousScriptsUntilAfterDocumentLoad;
+}
+
 - (BOOL)_drawsBackground
 {
     return _drawsBackground;

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewConfigurationPrivate.h (233914 => 233915)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewConfigurationPrivate.h	2018-07-18 16:04:33 UTC (rev 233914)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewConfigurationPrivate.h	2018-07-18 17:08:46 UTC (rev 233915)
@@ -73,6 +73,7 @@
 @property (nonatomic, setter=_setColorFilterEnabled:) BOOL _colorFilterEnabled WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 @property (nonatomic, setter=_setIncompleteImageBorderEnabled:) BOOL _incompleteImageBorderEnabled WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 @property (nonatomic, setter=_setDrawsBackground:) BOOL _drawsBackground WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+@property (nonatomic, setter=_setShouldDeferAsynchronousScriptsUntilAfterDocumentLoad:) BOOL _shouldDeferAsynchronousScriptsUntilAfterDocumentLoad WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 #if TARGET_OS_IPHONE
 @property (nonatomic, setter=_setAlwaysRunsAtForegroundPriority:) BOOL _alwaysRunsAtForegroundPriority WK_API_AVAILABLE(ios(9_0));

Modified: trunk/Tools/ChangeLog (233914 => 233915)


--- trunk/Tools/ChangeLog	2018-07-18 16:04:33 UTC (rev 233914)
+++ trunk/Tools/ChangeLog	2018-07-18 17:08:46 UTC (rev 233915)
@@ -1,3 +1,18 @@
+2018-07-18  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Add SPI to defer running async script until after document load
+        https://bugs.webkit.org/show_bug.cgi?id=187748
+        <rdar://problem/42317378>
+
+        Reviewed by Ryosuke Niwa and Tim Horton.
+
+        Add an API test to verify that when the deferred async script configuration is set, async scripts will be
+        executed after the DOMContentLoaded event.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/RunScriptAfterDocumentLoad.mm: Added.
+        (TEST):
+
 2018-07-18  Charlie Turner  <ctur...@igalia.com>
 
         [WPE] Update WPEBackend in flatpak

Modified: trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj (233914 => 233915)


--- trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj	2018-07-18 16:04:33 UTC (rev 233914)
+++ trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj	2018-07-18 17:08:46 UTC (rev 233915)
@@ -816,6 +816,7 @@
 		F4C8797F2059D8D3009CD00B /* ScrollViewInsetTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4C8797E2059D8D3009CD00B /* ScrollViewInsetTests.mm */; };
 		F4CD74C620FDACFA00DE3794 /* text-with-async-script.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4CD74C520FDACF500DE3794 /* text-with-async-script.html */; };
 		F4CD74C920FDB49600DE3794 /* TestURLSchemeHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4CD74C820FDB49600DE3794 /* TestURLSchemeHandler.mm */; };
+		F4D2986E20FEE7370092D636 /* RunScriptAfterDocumentLoad.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4D2986D20FEE7370092D636 /* RunScriptAfterDocumentLoad.mm */; };
 		F4D4F3B61E4E2BCB00BB2767 /* DataInteractionSimulator.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4D4F3B41E4E2BCB00BB2767 /* DataInteractionSimulator.mm */; };
 		F4D4F3B91E4E36E400BB2767 /* DataInteractionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4D4F3B71E4E36E400BB2767 /* DataInteractionTests.mm */; };
 		F4D5E4E81F0C5D38008C1A49 /* dragstart-clear-selection.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4D5E4E71F0C5D27008C1A49 /* dragstart-clear-selection.html */; };
@@ -2024,6 +2025,7 @@
 		F4CD74C520FDACF500DE3794 /* text-with-async-script.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "text-with-async-script.html"; sourceTree = "<group>"; };
 		F4CD74C720FDB49600DE3794 /* TestURLSchemeHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestURLSchemeHandler.h; sourceTree = "<group>"; };
 		F4CD74C820FDB49600DE3794 /* TestURLSchemeHandler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TestURLSchemeHandler.mm; sourceTree = "<group>"; };
+		F4D2986D20FEE7370092D636 /* RunScriptAfterDocumentLoad.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RunScriptAfterDocumentLoad.mm; sourceTree = "<group>"; };
 		F4D4F3B41E4E2BCB00BB2767 /* DataInteractionSimulator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DataInteractionSimulator.mm; sourceTree = "<group>"; };
 		F4D4F3B51E4E2BCB00BB2767 /* DataInteractionSimulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataInteractionSimulator.h; sourceTree = "<group>"; };
 		F4D4F3B71E4E36E400BB2767 /* DataInteractionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DataInteractionTests.mm; sourceTree = "<group>"; };
@@ -2312,6 +2314,7 @@
 				CD9E292B1C90A71F000BB800 /* RequiresUserActionForPlayback.mm */,
 				51C8E1A41F26AC5400BF731B /* ResourceLoadStatistics.mm */,
 				A180C0F91EE67DF000468F47 /* RunOpenPanel.mm */,
+				F4D2986D20FEE7370092D636 /* RunScriptAfterDocumentLoad.mm */,
 				CE0947362063223B003C9BA0 /* SchemeRegistry.mm */,
 				51EB12931FDF050500A5A1BD /* ServiceWorkerBasic.mm */,
 				37BCA61B1B596BA9002012CA /* ShouldOpenExternalURLsInNewWindowActions.mm */,
@@ -3824,6 +3827,7 @@
 				46E816F81E79E29C00375ADC /* RestoreStateAfterTermination.mm in Sources */,
 				F418BE151F71B7DC001970E6 /* RoundedRectTests.cpp in Sources */,
 				A180C0FA1EE67DF000468F47 /* RunOpenPanel.mm in Sources */,
+				F4D2986E20FEE7370092D636 /* RunScriptAfterDocumentLoad.mm in Sources */,
 				CDCFA7AA1E45183200C2433D /* SampleMap.cpp in Sources */,
 				CE0947372063223B003C9BA0 /* SchemeRegistry.mm in Sources */,
 				7CCE7F121A411AE600447C4C /* ScrollPinningBehaviors.cpp in Sources */,

Added: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/RunScriptAfterDocumentLoad.mm (0 => 233915)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/RunScriptAfterDocumentLoad.mm	                        (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/RunScriptAfterDocumentLoad.mm	2018-07-18 17:08:46 UTC (rev 233915)
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#import "config.h"
+
+#if WK_API_ENABLED
+
+#import "PlatformUtilities.h"
+#import "TestNavigationDelegate.h"
+#import "TestURLSchemeHandler.h"
+#import "TestWKWebView.h"
+#import <WebKit/WKWebViewConfigurationPrivate.h>
+#import <wtf/RetainPtr.h>
+#import <wtf/Seconds.h>
+
+static const char* markup =
+    "<head>"
+    "<script>function scriptLoaded(element) { webkit.messageHandlers.testHandler.postMessage(`${element.getAttribute('src')} loaded`) }</script>"
+    "<script _onload_='scriptLoaded(this)' src='' async></script>"
+    "</head>"
+    "<body>"
+    "<script>document.addEventListener('DOMContentLoaded', () => webkit.messageHandlers.testHandler.postMessage('DOMContentLoaded'))</script>"
+    "<script _onload_='scriptLoaded(this)' src='' defer></script>"
+    "<script _onload_='scriptLoaded(this)' src=''></script>"
+    "</body>";
+
+TEST(RunScriptAfterDocumentLoad, ExecutionOrderOfScriptsInDocument)
+{
+    auto schemeHandler = adoptNS([[TestURLSchemeHandler alloc] init]);
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    [configuration setURLSchemeHandler:schemeHandler.get() forURLScheme:@"custom"];
+    [configuration _setShouldDeferAsynchronousScriptsUntilAfterDocumentLoad:YES];
+    [schemeHandler setStartURLSchemeTaskHandler:^(WKWebView *, id<WKURLSchemeTask> task) {
+        auto responseBlock = [task = retainPtr(task)] {
+            NSURL *requestURL = [task request].URL;
+            NSString *script = [NSString stringWithFormat:@"webkit.messageHandlers.testHandler.postMessage('Running %@')", requestURL.absoluteString.lastPathComponent];
+            auto response = adoptNS([[NSURLResponse alloc] initWithURL:requestURL MIMEType:@"text/_javascript_" expectedContentLength:[script length] textEncodingName:nil]);
+            [task didReceiveResponse:response.get()];
+            [task didReceiveData:[script dataUsingEncoding:NSUTF8StringEncoding]];
+            [task didFinish];
+        };
+
+        if ([[task request].URL.absoluteString containsString:@"async.js"]) {
+            responseBlock();
+            return;
+        }
+
+        // Delay request responses for the other two scripts for a short duration to ensure that in the absence
+        // of the deferred asynchronous scripts configuration flag, we will normally execute the asynchronous script
+        // before the other scripts.
+        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (0.25_s).nanoseconds()), dispatch_get_main_queue(), responseBlock);
+    }];
+
+    auto messages = adoptNS([NSMutableArray new]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 200, 200) configuration:configuration.get()]);
+    for (NSString *message in @[ @"Running async.js", @"Running defer.js", @"Running sync.js", @"DOMContentLoaded", @"async.js loaded", @"defer.js loaded", @"sync.js loaded" ]) {
+        [webView performAfterReceivingMessage:message action:[message = adoptNS(message.copy), messages] {
+            [messages addObject:message.get()];
+        }];
+    }
+
+    [webView loadHTMLString:@(markup) baseURL:[NSURL URLWithString:@"custom://"]];
+    [webView _test_waitForDidFinishNavigation];
+
+    // Verify that the asynchronous script is executed after document load, the synchronous script is executed before the
+    // deferred script, and the deferred script is executed prior to document load.
+    EXPECT_EQ(7U, [messages count]);
+    EXPECT_WK_STREQ("Running sync.js", [messages objectAtIndex:0]);
+    EXPECT_WK_STREQ("sync.js loaded", [messages objectAtIndex:1]);
+    EXPECT_WK_STREQ("Running defer.js", [messages objectAtIndex:2]);
+    EXPECT_WK_STREQ("defer.js loaded", [messages objectAtIndex:3]);
+    EXPECT_WK_STREQ("DOMContentLoaded", [messages objectAtIndex:4]);
+    EXPECT_WK_STREQ("Running async.js", [messages objectAtIndex:5]);
+    EXPECT_WK_STREQ("async.js loaded", [messages objectAtIndex:6]);
+}
+
+#endif // WK_API_ENABLED
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to