Title: [268238] trunk
Revision
268238
Author
wei...@apple.com
Date
2020-10-08 17:45:29 -0700 (Thu, 08 Oct 2020)

Log Message

Refactor TestOptions code in WebKitTestRunner to make it easier to rationalize and extend
https://bugs.webkit.org/show_bug.cgi?id=217390

Reviewed by Darin Adler.

Refactors TestOptions code in WebKitTestRunner to make it clear how options coming in at
different levels are treated and what precedence each has. The order is (from least to most):

- Global features passed to WebKitTestRunner via the command line (e.g. --show-touches)
- Hardcoded features based on the path of the test being run (hopefully we can remove all soon)
- Hardcoded platform specific defaults (currently only used by macOS for useThreadedScrolling).
- Features from the test headers within the test itself.
- Finally, a second chance for more platform specific defaults (currenly only used the Cocoa ports
  for setting enableProcessSwapOnNavigation and enableProcessSwapOnWindowOpen based on NSUserDefaults.)

To make this change work, and pave the way for future autogeneration of some of these test options,
the TestOptions struct (now class) was overhauled. Instead of storing the state itself, TestOptions
is now an immutable owner of a TestFeatures object. TestFeatures is a simple struct that contains maps
of option keys to values and can be merged with other TestFeatures objects. TestOptions takes a
TestFeatures at construction, and exposes getters for all keys, as well as defaults for when they are
not in the maps. In future changes, I would like to remove many of these (the ones that correspond
with WebKit preferences anyway) and have the keys and values automatically dealt with by WKPreferences.

* WebKitTestRunner/Options.cpp:
* WebKitTestRunner/Options.h:
Switch to std::unordered_map for consistency and ease of conversion.

* WebKitTestRunner/PlatformWebView.h:
(WTR::PlatformWebView::viewSupportsOptions const):
Update to call new TestOptions functions.

* WebKitTestRunner/StringFunctions.h:
(WTR::toWK):
(WTR::toWTFString):
Add conversions for std::string.

* WebKitTestRunner/TestController.h:
* WebKitTestRunner/TestController.cpp:
(WTR::TestController::initialize):
Replace some bools with a new global TestFeatures instance that forms the base of
all TestFeatures merges.

(WTR::TestController::generateContextConfiguration const):
(WTR::TestController::generatePageConfiguration):
(WTR::TestController::createWebViewWithOptions):
(WTR::TestController::ensureViewSupportsOptionsForTest):
(WTR::TestController::resetPreferencesToConsistentValues):
(WTR::TestController::resetStateToConsistentValues):
Update to call new TestOptions functions.

(WTR::TestController::createTestURL):
Expose as a static member function on TestController for use in other files.

(WTR::TestController::testOptionsForTest const):
Rework to be a sequence of TestFeatures merges.

(WTR::TestController::updateWebViewSizeForTest):
(WTR::TestController::updateWindowScaleForTest):
Update to call new TestOptions functions.

(WTR::createTestURL): Deleted.
(WTR::parseBooleanTestHeaderValue): Deleted.
(WTR::parseStringTestHeaderValueAsRelativePath): Deleted.
(WTR::parseStringTestHeaderValueAsURL): Deleted.
(WTR::updateTestOptionsFromTestHeader): Deleted.
(WTR::TestController::platformAddTestOptions const): Deleted.
Moved TestOptions parsing to TestOptions.cpp

* WebKitTestRunner/TestInvocation.cpp:
(WTR::TestInvocation::createTestSettingsDictionary):
Update to call new TestOptions functions.

* WebKitTestRunner/TestOptions.h:
Reworks TestOptions to be an immutable owner of a TestFeatures struct, which is a set
of maps that map feature string keys, to values of type bool, double, string or string vector.

* WebKitTestRunner/TestOptions.cpp:
(WTR::merge):
Merges two TestFeatures objects. If both contain the same key, the 'additional'
TestFeatures overrides the 'base' TestFeatures.

(WTR::dumpFeatures):
Helper to dump features to stderr, useful for debugging.

(WTR::keyType):
Helper for parser to know which value parser to use.

(WTR::parseBooleanTestHeaderValue):
(WTR::parseStringTestHeaderValueAsRelativePath):
(WTR::parseStringTestHeaderValueAsURL):
Value parsers moved from TestController.cpp

(WTR::parseTestHeader):
Header parser moved from TestController.cpp. Update to now return
a TestFeatures object and use keyType to pick value parser rather
than inlining the if statements.

(WTR::hardcodedFeaturesBasedOnPathForTest):
Moved from the TestOptions contructor and reworked to return a TestFeatures
object to be merged by the caller.

(WTR::featureDefaultsFromTestHeaderForTest):
Creates a TestFeatures for the test headers of the current test.

* WebKitTestRunner/cocoa/TestControllerCocoa.mm:
(WTR::TestController::platformSpecificFeatureOverridesDefaultsForTest const):
Reworked to now return a TestFeatures object that will be merged, rather than updating
a TestOptions. Moves enableInAppBrowserPrivacy work to TestController::platformWillRunTest
where it is more appropriate.

(WTR::TestController::platformInitializeDataStore):
(WTR::TestController::platformCreateWebView):
(WTR::TestController::finishCreatingPlatformWebView):
(WTR::TestController::setApplicationBundleIdentifier):
(WTR::TestController::cocoaResetStateToConsistentValues):
(WTR::contentMode):
Update to call new TestOptions functions.

* WebKitTestRunner/ios/TestControllerIOS.mm:
(WTR::TestController::platformResetStateToConsistentValues):
(WTR::TestController::platformConfigureViewForTest):
Update to call new TestOptions functions.

(WTR::TestController::platformSpecificFeatureDefaultsForTest const):
(WTR::TestController::updatePlatformSpecificTestOptionsForTest const): Deleted.
Moved GeneratedTouchesDebugWindow work to TestController::platformConfigureViewForTest
where it is more appropriate and removed shouldShowTouches setting as that is done
globally now.

* WebKitTestRunner/mac/PlatformWebViewMac.mm:
(WTR::PlatformWebView::PlatformWebView):
Update to call new TestOptions functions.

* WebKitTestRunner/mac/TestControllerMac.mm:
(WTR::TestController::platformSpecificFeatureDefaultsForTest const):
(WTR::TestController::updatePlatformSpecificTestOptionsForTest const): Deleted.
Remove shouldUseRemoteLayerTree and shouldShowWebView, as those are now done globally,
and re-implemented useThreadedScrolling to use boolFeatures.

* WebKitTestRunner/win/TestControllerWin.cpp:
(WTR::TestController::platformSpecificFeatureDefaultsForTest const):
(WTR::TestController::updatePlatformSpecificTestOptionsForTest const): Deleted.
Updated for new signature.

* WebKitTestRunner/wpe/TestControllerWPE.cpp:
(WTR::TestController::platformSpecificFeatureDefaultsForTest const):
(WTR::TestController::updatePlatformSpecificTestOptionsForTest const): Deleted.
Updated for new signature.

* WebKitTestRunner/gtk/TestControllerGTK.cpp:
(WTR::TestController::platformSpecificFeatureDefaultsForTest const):
(WTR::TestController::updatePlatformSpecificTestOptionsForTest const): Deleted.
Updated for new signature.

Modified Paths

Diff

Modified: trunk/LayoutTests/fast/images/image-orientation-none-canvas-expected.html (268237 => 268238)


--- trunk/LayoutTests/fast/images/image-orientation-none-canvas-expected.html	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/LayoutTests/fast/images/image-orientation-none-canvas-expected.html	2020-10-09 00:45:29 UTC (rev 268238)
@@ -17,6 +17,10 @@
         height: 50px;
     }
 </style>
+<script>
+    if (window.internals)
+        internals.settings.setCanvasUsesAcceleratedDrawing(false);
+</script>
 <body>
     <b>CanvasRenderingContext2D.drawImage() should ignore the image's EXIF orientation if its style image-orientation is set to "none".</b>
     <br>
@@ -78,6 +82,9 @@
         <br>Undefined (invalid value)
     </div>
     <script>
+        if (window.internals)
+            internals.settings.setCanvasUsesAcceleratedDrawing(false);
+
         if (window.testRunner)
             window.testRunner.waitUntilDone();
 

Modified: trunk/LayoutTests/fast/images/image-orientation-none-canvas.html (268237 => 268238)


--- trunk/LayoutTests/fast/images/image-orientation-none-canvas.html	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/LayoutTests/fast/images/image-orientation-none-canvas.html	2020-10-09 00:45:29 UTC (rev 268238)
@@ -23,6 +23,10 @@
         height: 50px;
     }
 </style>
+<script>
+    if (window.internals)
+        internals.settings.setCanvasUsesAcceleratedDrawing(false);
+</script>
 <body>
     <b>CanvasRenderingContext2D.drawImage() should ignore the image's EXIF orientation if its style image-orientation is set to "none".</b>
     <br>

Modified: trunk/Tools/ChangeLog (268237 => 268238)


--- trunk/Tools/ChangeLog	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/ChangeLog	2020-10-09 00:45:29 UTC (rev 268238)
@@ -1,3 +1,159 @@
+2020-10-08  Sam Weinig  <wei...@apple.com>
+
+        Refactor TestOptions code in WebKitTestRunner to make it easier to rationalize and extend
+        https://bugs.webkit.org/show_bug.cgi?id=217390
+
+        Reviewed by Darin Adler.
+
+        Refactors TestOptions code in WebKitTestRunner to make it clear how options coming in at 
+        different levels are treated and what precedence each has. The order is (from least to most):
+        
+        - Global features passed to WebKitTestRunner via the command line (e.g. --show-touches)
+        - Hardcoded features based on the path of the test being run (hopefully we can remove all soon)
+        - Hardcoded platform specific defaults (currently only used by macOS for useThreadedScrolling).
+        - Features from the test headers within the test itself.
+        - Finally, a second chance for more platform specific defaults (currenly only used the Cocoa ports
+          for setting enableProcessSwapOnNavigation and enableProcessSwapOnWindowOpen based on NSUserDefaults.)
+
+        To make this change work, and pave the way for future autogeneration of some of these test options,
+        the TestOptions struct (now class) was overhauled. Instead of storing the state itself, TestOptions
+        is now an immutable owner of a TestFeatures object. TestFeatures is a simple struct that contains maps 
+        of option keys to values and can be merged with other TestFeatures objects. TestOptions takes a 
+        TestFeatures at construction, and exposes getters for all keys, as well as defaults for when they are
+        not in the maps. In future changes, I would like to remove many of these (the ones that correspond
+        with WebKit preferences anyway) and have the keys and values automatically dealt with by WKPreferences.
+
+        * WebKitTestRunner/Options.cpp:
+        * WebKitTestRunner/Options.h:
+        Switch to std::unordered_map for consistency and ease of conversion.
+
+        * WebKitTestRunner/PlatformWebView.h:
+        (WTR::PlatformWebView::viewSupportsOptions const):
+        Update to call new TestOptions functions.
+
+        * WebKitTestRunner/StringFunctions.h:
+        (WTR::toWK):
+        (WTR::toWTFString):
+        Add conversions for std::string.
+
+        * WebKitTestRunner/TestController.h:
+        * WebKitTestRunner/TestController.cpp:
+        (WTR::TestController::initialize):
+        Replace some bools with a new global TestFeatures instance that forms the base of
+        all TestFeatures merges.
+        
+        (WTR::TestController::generateContextConfiguration const):
+        (WTR::TestController::generatePageConfiguration):
+        (WTR::TestController::createWebViewWithOptions):
+        (WTR::TestController::ensureViewSupportsOptionsForTest):
+        (WTR::TestController::resetPreferencesToConsistentValues):
+        (WTR::TestController::resetStateToConsistentValues):
+        Update to call new TestOptions functions.
+        
+        (WTR::TestController::createTestURL):
+        Expose as a static member function on TestController for use in other files.
+        
+        (WTR::TestController::testOptionsForTest const):
+        Rework to be a sequence of TestFeatures merges.
+        
+        (WTR::TestController::updateWebViewSizeForTest):
+        (WTR::TestController::updateWindowScaleForTest):
+        Update to call new TestOptions functions.
+        
+        (WTR::createTestURL): Deleted.
+        (WTR::parseBooleanTestHeaderValue): Deleted.
+        (WTR::parseStringTestHeaderValueAsRelativePath): Deleted.
+        (WTR::parseStringTestHeaderValueAsURL): Deleted.
+        (WTR::updateTestOptionsFromTestHeader): Deleted.
+        (WTR::TestController::platformAddTestOptions const): Deleted.
+        Moved TestOptions parsing to TestOptions.cpp
+
+        * WebKitTestRunner/TestInvocation.cpp:
+        (WTR::TestInvocation::createTestSettingsDictionary):
+        Update to call new TestOptions functions.
+
+        * WebKitTestRunner/TestOptions.h:
+        Reworks TestOptions to be an immutable owner of a TestFeatures struct, which is a set
+        of maps that map feature string keys, to values of type bool, double, string or string vector.
+
+        * WebKitTestRunner/TestOptions.cpp:
+        (WTR::merge):
+        Merges two TestFeatures objects. If both contain the same key, the 'additional'
+        TestFeatures overrides the 'base' TestFeatures.
+
+        (WTR::dumpFeatures):
+        Helper to dump features to stderr, useful for debugging.
+
+        (WTR::keyType):
+        Helper for parser to know which value parser to use.
+
+        (WTR::parseBooleanTestHeaderValue):
+        (WTR::parseStringTestHeaderValueAsRelativePath):
+        (WTR::parseStringTestHeaderValueAsURL):
+        Value parsers moved from TestController.cpp
+
+        (WTR::parseTestHeader):
+        Header parser moved from TestController.cpp. Update to now return
+        a TestFeatures object and use keyType to pick value parser rather
+        than inlining the if statements.
+        
+        (WTR::hardcodedFeaturesBasedOnPathForTest):
+        Moved from the TestOptions contructor and reworked to return a TestFeatures
+        object to be merged by the caller.
+
+        (WTR::featureDefaultsFromTestHeaderForTest):
+        Creates a TestFeatures for the test headers of the current test.
+
+        * WebKitTestRunner/cocoa/TestControllerCocoa.mm:
+        (WTR::TestController::platformSpecificFeatureOverridesDefaultsForTest const):
+        Reworked to now return a TestFeatures object that will be merged, rather than updating
+        a TestOptions. Moves enableInAppBrowserPrivacy work to TestController::platformWillRunTest
+        where it is more appropriate.
+
+        (WTR::TestController::platformInitializeDataStore):
+        (WTR::TestController::platformCreateWebView):
+        (WTR::TestController::finishCreatingPlatformWebView):
+        (WTR::TestController::setApplicationBundleIdentifier):
+        (WTR::TestController::cocoaResetStateToConsistentValues):
+        (WTR::contentMode):
+        Update to call new TestOptions functions.
+
+        * WebKitTestRunner/ios/TestControllerIOS.mm:
+        (WTR::TestController::platformResetStateToConsistentValues):
+        (WTR::TestController::platformConfigureViewForTest):
+        Update to call new TestOptions functions.
+        
+        (WTR::TestController::platformSpecificFeatureDefaultsForTest const):
+        (WTR::TestController::updatePlatformSpecificTestOptionsForTest const): Deleted.
+        Moved GeneratedTouchesDebugWindow work to TestController::platformConfigureViewForTest
+        where it is more appropriate and removed shouldShowTouches setting as that is done
+        globally now. 
+        
+        * WebKitTestRunner/mac/PlatformWebViewMac.mm:
+        (WTR::PlatformWebView::PlatformWebView):
+        Update to call new TestOptions functions.
+
+        * WebKitTestRunner/mac/TestControllerMac.mm:
+        (WTR::TestController::platformSpecificFeatureDefaultsForTest const):
+        (WTR::TestController::updatePlatformSpecificTestOptionsForTest const): Deleted.
+        Remove shouldUseRemoteLayerTree and shouldShowWebView, as those are now done globally,
+        and re-implemented useThreadedScrolling to use boolFeatures.
+
+        * WebKitTestRunner/win/TestControllerWin.cpp:
+        (WTR::TestController::platformSpecificFeatureDefaultsForTest const):
+        (WTR::TestController::updatePlatformSpecificTestOptionsForTest const): Deleted.
+        Updated for new signature.
+
+        * WebKitTestRunner/wpe/TestControllerWPE.cpp:
+        (WTR::TestController::platformSpecificFeatureDefaultsForTest const):
+        (WTR::TestController::updatePlatformSpecificTestOptionsForTest const): Deleted.
+        Updated for new signature.
+
+        * WebKitTestRunner/gtk/TestControllerGTK.cpp:
+        (WTR::TestController::platformSpecificFeatureDefaultsForTest const):
+        (WTR::TestController::updatePlatformSpecificTestOptionsForTest const): Deleted.
+        Updated for new signature.
+
 2020-10-08  Alex Christensen  <achristen...@webkit.org>
 
         REGRESSION (r267763): [ iOS wk2 ] http/tests/in-app-browser-privacy/non-app-bound-domain-does-not-get-app-bound-session.html is a constant failure

Modified: trunk/Tools/WebKitTestRunner/Options.cpp (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/Options.cpp	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/Options.cpp	2020-10-09 00:45:29 UTC (rev 268238)
@@ -28,6 +28,7 @@
 #include "config.h"
 #include "Options.h"
 
+#include "StringFunctions.h"
 #include <string.h>
 
 namespace WTR {
@@ -113,16 +114,16 @@
     return true;
 }
 
-static bool parseFeature(String featureString, HashMap<String, bool>& features)
+static bool parseFeature(std::string_view featureString, std::unordered_map<std::string, bool>& features)
 {
-    auto strings = featureString.split('=');
-    if (strings.isEmpty() || strings.size() > 2)
+    auto strings = split(featureString, '=');
+    if (strings.empty() || strings.size() > 2)
         return false;
 
     auto featureName = strings[0];
     bool enabled = strings.size() == 1 || strings[1] == "true";
 
-    features.set(featureName, enabled);
+    features.insert({ std::string { featureName }, enabled });
     return true;
 }
 
@@ -173,7 +174,12 @@
 const char * OptionsHandler::help = "Displays this help.";
 
 Option::Option(const char* name, const char* description, std::function<bool(Options&, const char*, const char*)> parameterHandler, bool hasArgument)
-    : name(name), description(description), parameterHandler(parameterHandler), hasArgument(hasArgument) { };
+    : name(name)
+    , description(description)
+    , parameterHandler(parameterHandler)
+    , hasArgument(hasArgument)
+{
+}
 
 bool Option::matches(const char* option)
 {

Modified: trunk/Tools/WebKitTestRunner/Options.h (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/Options.h	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/Options.h	2020-10-09 00:45:29 UTC (rev 268238)
@@ -31,8 +31,8 @@
 #include <set>
 #include <stdio.h>
 #include <string>
+#include <unordered_map>
 #include <vector>
-#include <wtf/HashMap.h>
 #include <wtf/Vector.h>
 #include <wtf/text/StringHash.h>
 #include <wtf/text/WTFString.h>
@@ -57,8 +57,8 @@
 #endif
     std::vector<std::string> paths;
     std::set<std::string> allowedHosts;
-    HashMap<String, bool> internalFeatures;
-    HashMap<String, bool> experimentalFeatures;
+    std::unordered_map<std::string, bool> internalFeatures;
+    std::unordered_map<std::string, bool> experimentalFeatures;
 };
 
 class Option {

Modified: trunk/Tools/WebKitTestRunner/PlatformWebView.h (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/PlatformWebView.h	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/PlatformWebView.h	2020-10-09 00:45:29 UTC (rev 268238)
@@ -114,7 +114,7 @@
     void removeFromWindow();
     void addToWindow();
 
-    bool viewSupportsOptions(const TestOptions& options) const { return !options.runSingly && m_options.hasSameInitializationOptions(options); }
+    bool viewSupportsOptions(const TestOptions& options) const { return !options.runSingly() && m_options.hasSameInitializationOptions(options); }
 
     PlatformImage windowSnapshotImage();
     const TestOptions& options() const { return m_options; }

Modified: trunk/Tools/WebKitTestRunner/StringFunctions.h (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/StringFunctions.h	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/StringFunctions.h	2020-10-09 00:45:29 UTC (rev 268238)
@@ -32,6 +32,7 @@
 #include <WebKit/WKStringPrivate.h>
 #include <sstream>
 #include <string>
+#include <vector>
 #include <wtf/Platform.h>
 #include <wtf/StdLibExtras.h>
 #include <wtf/UniqueArray.h>
@@ -59,6 +60,11 @@
     return adoptWK(WKStringCreateWithUTF8CString(string));
 }
 
+inline WKRetainPtr<WKStringRef> toWK(const std::string& string)
+{
+    return toWK(string.c_str());
+}
+
 inline WKRetainPtr<WKStringRef> toWK(const WTF::String& string)
 {
     return toWK(string.utf8().data());
@@ -114,6 +120,11 @@
     return toWTFString(string.get());
 }
 
+inline WTF::String toWTFString(const std::string& string)
+{
+    return WTF::String::fromUTF8(string.c_str());
+}
+
 inline WKRetainPtr<WKStringRef> toWKString(JSContextRef context, JSValueRef value)
 {
     return toWK(createJSString(context, value).get());
@@ -125,4 +136,25 @@
     return toWTFString(createJSString(context, value));
 }
 
+template<typename StringType>
+inline std::vector<StringType> split(const StringType& string, char delimiter)
+{
+    std::vector<StringType> result;
+
+    size_t i = 0;
+    while (i < string.size()) {
+        auto foundIndex = string.find_first_of(delimiter, i);
+
+        if (foundIndex != i)
+            result.push_back(string.substr(i, foundIndex - i));
+
+        if (foundIndex == StringType::npos)
+            break;
+
+        i = foundIndex + 1;
+    }
+
+    return result;
+}
+
 } // namespace WTR

Modified: trunk/Tools/WebKitTestRunner/TestController.cpp (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/TestController.cpp	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/TestController.cpp	2020-10-09 00:45:29 UTC (rev 268238)
@@ -109,12 +109,6 @@
 static constexpr auto pathSeparator = '/';
 #endif
 
-const unsigned TestController::viewWidth = 800;
-const unsigned TestController::viewHeight = 600;
-
-const unsigned TestController::w3cSVGViewWidth = 480;
-const unsigned TestController::w3cSVGViewHeight = 360;
-
 const WTF::Seconds TestController::defaultShortTimeout = 5_s;
 const WTF::Seconds TestController::noTimeout = -1_s;
 
@@ -469,15 +463,17 @@
     m_shouldDumpPixelsForAllTests = options.shouldDumpPixelsForAllTests;
     m_forceComplexText = options.forceComplexText;
     m_shouldUseAcceleratedDrawing = options.shouldUseAcceleratedDrawing;
-    m_shouldUseRemoteLayerTree = options.shouldUseRemoteLayerTree;
     m_paths = options.paths;
     m_allowedHosts = options.allowedHosts;
-    m_shouldShowWebView = options.shouldShowWebView;
-    m_shouldShowTouches = options.shouldShowTouches;
     m_checkForWorldLeaks = options.checkForWorldLeaks;
     m_allowAnyHTTPSCertificateForAllowedHosts = options.allowAnyHTTPSCertificateForAllowedHosts;
-    m_internalFeatures = options.internalFeatures;
-    m_experimentalFeatures = options.experimentalFeatures;
+    
+    m_globalFeatures.internalDebugFeatures = options.internalFeatures;
+    m_globalFeatures.experimentalFeatures = options.experimentalFeatures;
+    m_globalFeatures.boolFeatures.insert({ "useRemoteLayerTree", options.shouldUseRemoteLayerTree });
+    m_globalFeatures.boolFeatures.insert({ "shouldShowWebView", options.shouldShowWebView });
+    m_globalFeatures.boolFeatures.insert({ "shouldShowTouches", options.shouldShowTouches });
+
 #if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
     m_accessibilityIsolatedTreeMode = options.accessibilityIsolatedTreeMode;
 #endif
@@ -499,7 +495,7 @@
     m_eventSenderProxy = makeUnique<EventSenderProxy>(this);
 }
 
-WKRetainPtr<WKContextConfigurationRef> TestController::generateContextConfiguration(const TestOptions::ContextOptions& options) const
+WKRetainPtr<WKContextConfigurationRef> TestController::generateContextConfiguration(const ContextOptions& options) const
 {
     auto configuration = adoptWK(WKContextConfigurationCreate());
     WKContextConfigurationSetInjectedBundlePath(configuration.get(), injectedBundlePath());
@@ -562,10 +558,11 @@
 
 WKRetainPtr<WKPageConfigurationRef> TestController::generatePageConfiguration(const TestOptions& options)
 {
-    if (!m_context || !m_contextOptions->hasSameInitializationOptions(options.contextOptions)) {
-        auto contextConfiguration = generateContextConfiguration(options.contextOptions);
+    auto contextOptions = options.contextOptions();
+    if (!m_context || !m_contextOptions->hasSameInitializationOptions(contextOptions)) {
+        auto contextConfiguration = generateContextConfiguration(contextOptions);
         m_context = platformAdjustContext(adoptWK(WKContextCreateWithConfiguration(contextConfiguration.get())).get(), contextConfiguration.get());
-        m_contextOptions = options.contextOptions;
+        m_contextOptions = contextOptions;
 
         m_geolocationProvider = makeUnique<GeolocationProviderMock>(m_context.get());
 
@@ -627,7 +624,7 @@
     WKPageConfigurationSetContext(pageConfiguration.get(), m_context.get());
     WKPageConfigurationSetPageGroup(pageConfiguration.get(), m_pageGroup.get());
     
-    if (options.useEphemeralSession) {
+    if (options.useEphemeralSession()) {
         auto ephemeralDataStore = adoptWK(WKWebsiteDataStoreCreateNonPersistentDataStore());
         WKPageConfigurationSetWebsiteDataStore(pageConfiguration.get(), ephemeralDataStore.get());
     }
@@ -639,11 +636,12 @@
 
 void TestController::createWebViewWithOptions(const TestOptions& options)
 {
+    auto applicationBundleIdentifier = options.applicationBundleIdentifier();
 #if PLATFORM(COCOA)
-    if (!options.applicationBundleIdentifier.isEmpty()) {
+    if (!applicationBundleIdentifier.empty()) {
         // The bundle identifier can only be set once per test, and is cleared between tests.
         RELEASE_ASSERT(!m_hasSetApplicationBundleIdentifier);
-        setApplicationBundleIdentifier(options.applicationBundleIdentifier);
+        setApplicationBundleIdentifier(applicationBundleIdentifier);
         m_hasSetApplicationBundleIdentifier = true;
     }
 #endif
@@ -686,7 +684,7 @@
         0, // didDraw
         0, // pageDidScroll
         0, // exceededDatabaseQuota,
-        options.shouldHandleRunOpenPanel ? runOpenPanel : 0,
+        options.shouldHandleRunOpenPanel() ? runOpenPanel : 0,
         decidePolicyForGeolocationPermissionRequest,
         0, // headerHeight
         0, // footerHeight
@@ -795,9 +793,9 @@
     // something else for specific tests that need to run at a different window scale.
     m_mainWebView->changeWindowScaleIfNeeded(1);
     
-    if (!options.applicationBundleIdentifier.isEmpty()) {
+    if (!applicationBundleIdentifier.empty()) {
         reinitializeAppBoundDomains();
-        updateBundleIdentifierInNetworkProcess(options.applicationBundleIdentifier);
+        updateBundleIdentifierInNetworkProcess(applicationBundleIdentifier);
     }
 }
 
@@ -809,7 +807,7 @@
         // Having created another page (via window.open()) prevents process swapping on navigation and it may therefore
         // cause flakiness to reuse the view. We should also always make a new view if the test is marked as app-bound, because
         // the view configuration must change.
-        if (!m_createdOtherPage && m_mainWebView->viewSupportsOptions(options) && !options.isAppBoundWebView)
+        if (!m_createdOtherPage && m_mainWebView->viewSupportsOptions(options) && !options.isAppBoundWebView())
             return;
 
         willDestroyWebView();
@@ -836,8 +834,8 @@
     WKPreferencesResetTestRunnerOverrides(preferences);
 
     WKPreferencesEnableAllExperimentalFeatures(preferences);
-    for (const auto& experimentalFeature : options.experimentalFeatures)
-        WKPreferencesSetExperimentalFeatureForKey(preferences, experimentalFeature.value, toWK(experimentalFeature.key).get());
+    for (const auto& [key, value] : options.experimentalFeatures())
+        WKPreferencesSetExperimentalFeatureForKey(preferences, value, toWK(key).get());
 
     WKPreferencesResetAllInternalDebugFeatures(preferences);
 
@@ -862,17 +860,17 @@
     WKPreferencesSetInternalDebugFeatureForKey(preferences, true, toWK("InputTypeWeekEnabled").get());
 #endif
 
-    for (const auto& internalDebugFeature : options.internalDebugFeatures)
-        WKPreferencesSetInternalDebugFeatureForKey(preferences, internalDebugFeature.value, toWK(internalDebugFeature.key).get());
+    for (const auto& [key, value]  : options.internalDebugFeatures())
+        WKPreferencesSetInternalDebugFeatureForKey(preferences, value, toWK(key).get());
 
 #if PLATFORM(COCOA)
-    WKPreferencesSetCaptureVideoInUIProcessEnabled(preferences, options.enableCaptureVideoInUIProcess);
-    WKPreferencesSetCaptureVideoInGPUProcessEnabled(preferences, options.enableCaptureVideoInGPUProcess);
-    WKPreferencesSetCaptureAudioInUIProcessEnabled(preferences, options.enableCaptureAudioInUIProcess);
-    WKPreferencesSetCaptureAudioInGPUProcessEnabled(preferences, options.enableCaptureAudioInGPUProcess);
+    WKPreferencesSetCaptureVideoInUIProcessEnabled(preferences, options.enableCaptureVideoInUIProcess());
+    WKPreferencesSetCaptureVideoInGPUProcessEnabled(preferences, options.enableCaptureVideoInGPUProcess());
+    WKPreferencesSetCaptureAudioInUIProcessEnabled(preferences, options.enableCaptureAudioInUIProcess());
+    WKPreferencesSetCaptureAudioInGPUProcessEnabled(preferences, options.enableCaptureAudioInGPUProcess());
 #endif
-    WKPreferencesSetProcessSwapOnNavigationEnabled(preferences, options.contextOptions.shouldEnableProcessSwapOnNavigation());
-    WKPreferencesSetPageVisibilityBasedProcessSuppressionEnabled(preferences, options.enableAppNap);
+    WKPreferencesSetProcessSwapOnNavigationEnabled(preferences, options.contextOptions().shouldEnableProcessSwapOnNavigation());
+    WKPreferencesSetPageVisibilityBasedProcessSuppressionEnabled(preferences, options.enableAppNap());
     WKPreferencesSetOfflineWebApplicationCacheEnabled(preferences, true);
     WKPreferencesSetSubpixelAntialiasedLayerTextEnabled(preferences, false);
     WKPreferencesSetXSSAuditorEnabled(preferences, false);
@@ -883,10 +881,10 @@
     WKPreferencesSetJavaScriptRuntimeFlags(preferences, kWKJavaScriptRuntimeFlagsAllEnabled);
     WKPreferencesSetJavaScriptCanOpenWindowsAutomatically(preferences, true);
     WKPreferencesSetJavaScriptCanAccessClipboard(preferences, true);
-    WKPreferencesSetDOMPasteAllowed(preferences, options.domPasteAllowed);
+    WKPreferencesSetDOMPasteAllowed(preferences, options.domPasteAllowed());
     WKPreferencesSetUniversalAccessFromFileURLsAllowed(preferences, true);
     WKPreferencesSetFileAccessFromFileURLsAllowed(preferences, true);
-    WKPreferencesSetTopNavigationToDataURLsAllowed(preferences, options.allowTopNavigationToDataURLs);
+    WKPreferencesSetTopNavigationToDataURLsAllowed(preferences, options.allowTopNavigationToDataURLs());
 #if ENABLE(FULLSCREEN_API)
     WKPreferencesSetFullScreenEnabled(preferences, true);
 #endif
@@ -899,18 +897,18 @@
     WKPreferencesSetCustomPasteboardDataEnabled(preferences, true);
     WKPreferencesSetDialogElementEnabled(preferences, true);
 
-    WKPreferencesSetMockScrollbarsEnabled(preferences, options.useMockScrollbars);
-    WKPreferencesSetNeedsSiteSpecificQuirks(preferences, options.needsSiteSpecificQuirks);
-    WKPreferencesSetAttachmentElementEnabled(preferences, options.enableAttachmentElement);
-    WKPreferencesSetMenuItemElementEnabled(preferences, options.enableMenuItemElement);
-    WKPreferencesSetKeygenElementEnabled(preferences, options.enableKeygenElement);
-    WKPreferencesSetModernMediaControlsEnabled(preferences, options.enableModernMediaControls);
-    WKPreferencesSetWebAuthenticationEnabled(preferences, options.enableWebAuthentication);
-    WKPreferencesSetWebAuthenticationLocalAuthenticatorEnabled(preferences, options.enableWebAuthenticationLocalAuthenticator);
-    WKPreferencesSetAllowCrossOriginSubresourcesToAskForCredentials(preferences, options.allowCrossOriginSubresourcesToAskForCredentials);
-    WKPreferencesSetColorFilterEnabled(preferences, options.enableColorFilter);
-    WKPreferencesSetPunchOutWhiteBackgroundsInDarkMode(preferences, options.punchOutWhiteBackgroundsInDarkMode);
-    WKPreferencesSetPageCacheEnabled(preferences, options.enableBackForwardCache);
+    WKPreferencesSetMockScrollbarsEnabled(preferences, options.useMockScrollbars());
+    WKPreferencesSetNeedsSiteSpecificQuirks(preferences, options.needsSiteSpecificQuirks());
+    WKPreferencesSetAttachmentElementEnabled(preferences, options.enableAttachmentElement());
+    WKPreferencesSetMenuItemElementEnabled(preferences, options.enableMenuItemElement());
+    WKPreferencesSetKeygenElementEnabled(preferences, options.enableKeygenElement());
+    WKPreferencesSetModernMediaControlsEnabled(preferences, options.enableModernMediaControls());
+    WKPreferencesSetWebAuthenticationEnabled(preferences, options.enableWebAuthentication());
+    WKPreferencesSetWebAuthenticationLocalAuthenticatorEnabled(preferences, options.enableWebAuthenticationLocalAuthenticator());
+    WKPreferencesSetAllowCrossOriginSubresourcesToAskForCredentials(preferences, options.allowCrossOriginSubresourcesToAskForCredentials());
+    WKPreferencesSetColorFilterEnabled(preferences, options.enableColorFilter());
+    WKPreferencesSetPunchOutWhiteBackgroundsInDarkMode(preferences, options.punchOutWhiteBackgroundsInDarkMode());
+    WKPreferencesSetPageCacheEnabled(preferences, options.enableBackForwardCache());
 
     WKPreferencesSetDefaultTextEncodingName(preferences, toWK("ISO-8859-1").get());
 
@@ -932,7 +930,7 @@
     WKPreferencesSetHiddenPageDOMTimerThrottlingEnabled(preferences, false);
     WKPreferencesSetHiddenPageCSSAnimationSuspensionEnabled(preferences, false);
 
-    WKPreferencesSetAcceleratedDrawingEnabled(preferences, m_shouldUseAcceleratedDrawing || options.useAcceleratedDrawing);
+    WKPreferencesSetAcceleratedDrawingEnabled(preferences, m_shouldUseAcceleratedDrawing || options.useAcceleratedDrawing());
     // FIXME: We should be testing the default.
     WKPreferencesSetStorageBlockingPolicy(preferences, kWKAllowAllStorage);
 
@@ -953,7 +951,7 @@
     
     WKPreferencesSetLargeImageAsyncDecodingEnabled(preferences, false);
 
-    WKPreferencesSetInspectorAdditionsEnabled(preferences, options.enableInspectorAdditions);
+    WKPreferencesSetInspectorAdditionsEnabled(preferences, options.enableInspectorAdditions());
 
     WKPreferencesSetStorageAccessAPIEnabled(preferences, true);
     
@@ -972,7 +970,7 @@
     WKPreferencesSetAudioPlaybackRequiresUserGesture(preferences, false);
     WKPreferencesSetInternalDebugFeatureForKey(preferences, false, WKStringCreateWithUTF8CString("SpeakerSelectionRequiresUserGesture"));
 
-    WKPreferencesSetShouldUseServiceWorkerShortTimeout(preferences, options.contextOptions.useServiceWorkerShortTimeout);
+    WKPreferencesSetShouldUseServiceWorkerShortTimeout(preferences, options.useServiceWorkerShortTimeout());
 
 #if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
     WKPreferencesSetIsAccessibilityIsolatedTreeEnabled(preferences, accessibilityIsolatedTreeMode());
@@ -1006,11 +1004,12 @@
     setValue(resetMessageBody, "AccessibilityIsolatedTree", m_accessibilityIsolatedTreeMode);
 #endif
 
-    if (options.jscOptions.length())
-        setValue(resetMessageBody, "JSCOptions", options.jscOptions.c_str());
+    auto jscOptions = options.jscOptions();
+    if (!jscOptions.empty())
+        setValue(resetMessageBody, "JSCOptions", jscOptions.c_str());
 
 #if PLATFORM(COCOA)
-    WebCoreTestSupport::setAdditionalSupportedImageTypesForTesting(options.additionalSupportedImageTypes.c_str());
+    WebCoreTestSupport::setAdditionalSupportedImageTypesForTesting(options.additionalSupportedImageTypes().c_str());
 #endif
 
     WKPagePostMessageToInjectedBundle(TestController::singleton().mainWebView()->page(), toWK("Reset").get(), resetMessageBody.get());
@@ -1118,7 +1117,7 @@
 
     setNavigationGesturesEnabled(false);
     
-    setIgnoresViewportScaleLimits(options.ignoresViewportScaleLimits);
+    setIgnoresViewportScaleLimits(options.ignoresViewportScaleLimits());
 
     m_openPanelFileURLs = nullptr;
 #if PLATFORM(IOS_FAMILY)
@@ -1318,7 +1317,7 @@
     return std::string();
 }
 
-static WKURLRef createTestURL(const char* pathOrURL)
+WKURLRef TestController::createTestURL(const char* pathOrURL)
 {
     if (strstr(pathOrURL, "http://") || strstr(pathOrURL, "https://") || strstr(pathOrURL, "file://"))
         return WKURLCreateWithUTF8CString(pathOrURL);
@@ -1356,221 +1355,25 @@
     return WKURLCreateWithUTF8CString(buffer.get());
 }
 
-static bool parseBooleanTestHeaderValue(const std::string& value)
-{
-    if (value == "true")
-        return true;
-    if (value == "false")
-        return false;
-
-    LOG_ERROR("Found unexpected value '%s' for boolean option. Expected 'true' or 'false'.", value.c_str());
-    return false;
-}
-
-static std::string parseStringTestHeaderValueAsRelativePath(const std::string& value, const std::string& pathOrURL)
-{
-    auto baseURL = adoptWK(createTestURL(pathOrURL.c_str()));
-    auto relativeURL = adoptWK(WKURLCreateWithBaseURL(baseURL.get(), value.c_str()));
-    return toSTD(adoptWK(WKURLCopyPath(relativeURL.get())));
-}
-
-static std::string parseStringTestHeaderValueAsURL(const std::string& value)
-{
-    return toSTD(adoptWK(WKURLCopyString(createTestURL(value.c_str()))));
-}
-
-static void updateTestOptionsFromTestHeader(TestOptions& testOptions, const std::string& pathOrURL, const std::string& absolutePath)
-{
-    std::string filename = absolutePath;
-    if (filename.empty()) {
-        // Gross. Need to reduce conversions between all the string types and URLs.
-        filename = testPath(adoptWK(createTestURL(pathOrURL.c_str())).get());
-    }
-
-    if (filename.empty())
-        return;
-
-    std::string options;
-    std::ifstream testFile(filename.data());
-    if (!testFile.good())
-        return;
-    getline(testFile, options);
-    std::string beginString("webkit-test-runner [ ");
-    std::string endString(" ]");
-    size_t beginLocation = options.find(beginString);
-    if (beginLocation == std::string::npos)
-        return;
-    size_t endLocation = options.find(endString, beginLocation);
-    if (endLocation == std::string::npos) {
-        LOG_ERROR("Could not find end of test header in %s", filename.c_str());
-        return;
-    }
-    std::string pairString = options.substr(beginLocation + beginString.size(), endLocation - (beginLocation + beginString.size()));
-    size_t pairStart = 0;
-    while (pairStart < pairString.size()) {
-        size_t pairEnd = pairString.find(" ", pairStart);
-        if (pairEnd == std::string::npos)
-            pairEnd = pairString.size();
-        size_t equalsLocation = pairString.find("=", pairStart);
-        if (equalsLocation == std::string::npos) {
-            LOG_ERROR("Malformed option in test header (could not find '=' character) in %s", filename.c_str());
-            break;
-        }
-        auto key = pairString.substr(pairStart, equalsLocation - pairStart);
-        auto value = pairString.substr(equalsLocation + 1, pairEnd - (equalsLocation + 1));
-
-        if (!key.rfind("experimental:")) {
-            key = key.substr(13);
-            testOptions.experimentalFeatures.add(String(key.c_str()), parseBooleanTestHeaderValue(value));
-        }
-
-        if (!key.rfind("internal:")) {
-            key = key.substr(9);
-            testOptions.internalDebugFeatures.add(String(key.c_str()), parseBooleanTestHeaderValue(value));
-        }
-
-        if (key == "language")
-            testOptions.contextOptions.overrideLanguages = String(value.c_str()).split(',');
-        else if (key == "useThreadedScrolling")
-            testOptions.useThreadedScrolling = parseBooleanTestHeaderValue(value);
-        else if (key == "useAcceleratedDrawing")
-            testOptions.useAcceleratedDrawing = parseBooleanTestHeaderValue(value);
-        else if (key == "useFlexibleViewport")
-            testOptions.useFlexibleViewport = parseBooleanTestHeaderValue(value);
-        else if (key == "useDataDetection")
-            testOptions.useDataDetection = parseBooleanTestHeaderValue(value);
-        else if (key == "useMockScrollbars")
-            testOptions.useMockScrollbars = parseBooleanTestHeaderValue(value);
-        else if (key == "needsSiteSpecificQuirks")
-            testOptions.needsSiteSpecificQuirks = parseBooleanTestHeaderValue(value);
-        else if (key == "ignoresViewportScaleLimits")
-            testOptions.ignoresViewportScaleLimits = parseBooleanTestHeaderValue(value);
-        else if (key == "useCharacterSelectionGranularity")
-            testOptions.useCharacterSelectionGranularity = parseBooleanTestHeaderValue(value);
-        else if (key == "enableAttachmentElement")
-            testOptions.enableAttachmentElement = parseBooleanTestHeaderValue(value);
-        else if (key == "enableIntersectionObserver")
-            testOptions.enableIntersectionObserver = parseBooleanTestHeaderValue(value);
-        else if (key == "useEphemeralSession")
-            testOptions.useEphemeralSession = parseBooleanTestHeaderValue(value);
-        else if (key == "enableMenuItemElement")
-            testOptions.enableMenuItemElement = parseBooleanTestHeaderValue(value);
-        else if (key == "enableKeygenElement")
-            testOptions.enableKeygenElement = parseBooleanTestHeaderValue(value);
-        else if (key == "enableModernMediaControls")
-            testOptions.enableModernMediaControls = parseBooleanTestHeaderValue(value);
-        else if (key == "enablePointerLock")
-            testOptions.enablePointerLock = parseBooleanTestHeaderValue(value);
-        else if (key == "enableWebAuthentication")
-            testOptions.enableWebAuthentication = parseBooleanTestHeaderValue(value);
-        else if (key == "enableWebAuthenticationLocalAuthenticator")
-            testOptions.enableWebAuthenticationLocalAuthenticator = parseBooleanTestHeaderValue(value);
-        else if (key == "enableInspectorAdditions")
-            testOptions.enableInspectorAdditions = parseBooleanTestHeaderValue(value);
-        else if (key == "dumpJSConsoleLogInStdErr")
-            testOptions.dumpJSConsoleLogInStdErr = parseBooleanTestHeaderValue(value);
-        else if (key == "applicationManifest")
-            testOptions.applicationManifest = parseStringTestHeaderValueAsRelativePath(value, pathOrURL);
-        else if (key == "allowCrossOriginSubresourcesToAskForCredentials")
-            testOptions.allowCrossOriginSubresourcesToAskForCredentials = parseBooleanTestHeaderValue(value);
-        else if (key == "domPasteAllowed")
-            testOptions.domPasteAllowed = parseBooleanTestHeaderValue(value);
-        else if (key == "enableProcessSwapOnNavigation")
-            testOptions.contextOptions.enableProcessSwapOnNavigation = parseBooleanTestHeaderValue(value);
-        else if (key == "enableProcessSwapOnWindowOpen")
-            testOptions.contextOptions.enableProcessSwapOnWindowOpen = parseBooleanTestHeaderValue(value);
-        else if (key == "useServiceWorkerShortTimeout")
-            testOptions.contextOptions.useServiceWorkerShortTimeout = parseBooleanTestHeaderValue(value);
-        else if (key == "enableColorFilter")
-            testOptions.enableColorFilter = parseBooleanTestHeaderValue(value);
-        else if (key == "punchOutWhiteBackgroundsInDarkMode")
-            testOptions.punchOutWhiteBackgroundsInDarkMode = parseBooleanTestHeaderValue(value);
-        else if (key == "jscOptions")
-            testOptions.jscOptions = value;
-        else if (key == "additionalSupportedImageTypes")
-            testOptions.additionalSupportedImageTypes = value;
-        else if (key == "runSingly")
-            testOptions.runSingly = parseBooleanTestHeaderValue(value);
-        else if (key == "shouldIgnoreMetaViewport")
-            testOptions.shouldIgnoreMetaViewport = parseBooleanTestHeaderValue(value);
-        else if (key == "spellCheckingDots")
-            testOptions.shouldShowSpellCheckingDots = parseBooleanTestHeaderValue(value);
-        else if (key == "enableServiceControls")
-            testOptions.enableServiceControls = parseBooleanTestHeaderValue(value);
-        else if (key == "editable")
-            testOptions.editable = parseBooleanTestHeaderValue(value);
-        else if (key == "shouldHandleRunOpenPanel")
-            testOptions.shouldHandleRunOpenPanel = parseBooleanTestHeaderValue(value);
-        else if (key == "shouldPresentPopovers")
-            testOptions.shouldPresentPopovers = parseBooleanTestHeaderValue(value);
-        else if (key == "contentInset.top")
-            testOptions.contentInsetTop = std::stod(value);
-        else if (key == "ignoreSynchronousMessagingTimeouts")
-            testOptions.contextOptions.ignoreSynchronousMessagingTimeouts = parseBooleanTestHeaderValue(value);
-        else if (key == "contentMode")
-            testOptions.contentMode = { value.c_str() };
-        else if (key == "applicationBundleIdentifier")
-            testOptions.applicationBundleIdentifier = { value.c_str() };
-        else if (key == "enableAppNap")
-            testOptions.enableAppNap = parseBooleanTestHeaderValue(value);
-        else if (key == "enableBackForwardCache")
-            testOptions.enableBackForwardCache = parseBooleanTestHeaderValue(value);
-        else if (key == "allowsLinkPreview")
-            testOptions.allowsLinkPreview = parseBooleanTestHeaderValue(value);
-        else if (key == "enableCaptureVideoInUIProcess")
-            testOptions.enableCaptureVideoInUIProcess = parseBooleanTestHeaderValue(value);
-        else if (key == "enableCaptureVideoInGPUProcess")
-            testOptions.enableCaptureVideoInGPUProcess = parseBooleanTestHeaderValue(value);
-        else if (key == "enableCaptureAudioInUIProcess")
-            testOptions.enableCaptureAudioInUIProcess = parseBooleanTestHeaderValue(value);
-        else if (key == "enableCaptureAudioInGPUProcess")
-            testOptions.enableCaptureAudioInGPUProcess = parseBooleanTestHeaderValue(value);
-        else if (key == "allowTopNavigationToDataURLs")
-            testOptions.allowTopNavigationToDataURLs = parseBooleanTestHeaderValue(value);
-        else if (key == "enableInAppBrowserPrivacy")
-            testOptions.enableInAppBrowserPrivacy = parseBooleanTestHeaderValue(value);
-        else if (key == "standaloneWebApplicationURL")
-            testOptions.standaloneWebApplicationURL = parseStringTestHeaderValueAsURL(value);
-        else if (key == "isAppBoundWebView")
-            testOptions.isAppBoundWebView = parseBooleanTestHeaderValue(value);
-        pairStart = pairEnd + 1;
-    }
-}
-
 TestOptions TestController::testOptionsForTest(const TestCommand& command) const
 {
-    TestOptions options(command.pathOrURL);
+    TestFeatures features = m_globalFeatures;
+    merge(features, hardcodedFeaturesBasedOnPathForTest(command));
+    merge(features, platformSpecificFeatureDefaultsForTest(command));
+    merge(features, featureDefaultsFromTestHeaderForTest(command));
+    merge(features, platformSpecificFeatureOverridesDefaultsForTest(command));
 
-    options.useRemoteLayerTree = m_shouldUseRemoteLayerTree;
-    options.shouldShowWebView = m_shouldShowWebView;
-
-    for (auto& feature : m_internalFeatures)
-        options.internalDebugFeatures.add(feature.key, feature.value);
-    for (auto& feature : m_experimentalFeatures)
-        options.experimentalFeatures.add(feature.key, feature.value);
-
-    updatePlatformSpecificTestOptionsForTest(options, command.pathOrURL);
-    updateTestOptionsFromTestHeader(options, command.pathOrURL, command.absolutePath);
-    platformAddTestOptions(options);
-
-    return options;
+    return TestOptions { features };
 }
 
 void TestController::updateWebViewSizeForTest(const TestInvocation& test)
 {
-    unsigned width = viewWidth;
-    unsigned height = viewHeight;
-    if (test.options().isSVGTest) {
-        width = w3cSVGViewWidth;
-        height = w3cSVGViewHeight;
-    }
-
-    mainWebView()->resizeTo(width, height);
+    mainWebView()->resizeTo(test.options().viewWidth(), test.options().viewHeight());
 }
 
 void TestController::updateWindowScaleForTest(PlatformWebView* view, const TestInvocation& test)
 {
-    view->changeWindowScaleIfNeeded(test.options().deviceScaleFactor);
+    view->changeWindowScaleIfNeeded(test.options().deviceScaleFactor());
 }
 
 void TestController::configureViewForTest(const TestInvocation& test)
@@ -1766,6 +1569,12 @@
         else
             die(inputLine);
     }
+    
+    if (result.absolutePath.empty()) {
+        // Gross. Need to reduce conversions between all the string types and URLs.
+        result.absolutePath = testPath(adoptWK(TestController::createTestURL(result.pathOrURL.c_str())).get());
+    }
+
     return result;
 }
 
@@ -1788,7 +1597,7 @@
     if (command.timeout > 0_s)
         m_currentInvocation->setCustomTimeout(command.timeout);
 
-    m_currentInvocation->setDumpJSConsoleLogInStdErr(command.dumpJSConsoleLogInStdErr || options.dumpJSConsoleLogInStdErr);
+    m_currentInvocation->setDumpJSConsoleLogInStdErr(command.dumpJSConsoleLogInStdErr || options.dumpJSConsoleLogInStdErr());
 
     platformWillRunTest(*m_currentInvocation);
 
@@ -3003,7 +2812,7 @@
 
 void TestController::platformInitializeDataStore(WKPageConfigurationRef configuration, const TestOptions& options)
 {
-    if (!options.useEphemeralSession)
+    if (!options.useEphemeralSession())
         WKPageConfigurationSetWebsiteDataStore(configuration, defaultWebsiteDataStore());
 
     m_websiteDataStore = WKPageConfigurationGetWebsiteDataStore(configuration);
@@ -3818,7 +3627,7 @@
     WKWebsiteDataStoreReinitializeAppBoundDomains(TestController::websiteDataStore());
 }
 
-void TestController::updateBundleIdentifierInNetworkProcess(const String& bundleIdentifier)
+void TestController::updateBundleIdentifierInNetworkProcess(const std::string& bundleIdentifier)
 {
     InAppBrowserPrivacyCallbackContext context(*this);
     WKWebsiteDataStoreUpdateBundleIdentifierInNetworkProcess(TestController::websiteDataStore(), toWK(bundleIdentifier).get(), &context, inAppBrowserPrivacyVoidResultCallback);
@@ -3833,8 +3642,9 @@
 }
 
 #if !PLATFORM(COCOA)
-void TestController::platformAddTestOptions(TestOptions&) const
+TestFeatures TestController::platformSpecificFeatureOverridesDefaultsForTest(const TestCommand&) const
 {
+    return { };
 }
 
 void TestController::injectUserScript(WKStringRef)

Modified: trunk/Tools/WebKitTestRunner/TestController.h (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/TestController.h	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/TestController.h	2020-10-09 00:45:29 UTC (rev 268238)
@@ -32,6 +32,7 @@
 #include <WebKit/WKRetainPtr.h>
 #include <set>
 #include <string>
+#include <unordered_map>
 #include <vector>
 #include <wtf/HashMap.h>
 #include <wtf/Noncopyable.h>
@@ -50,12 +51,12 @@
 
 namespace WTR {
 
-class TestInvocation;
+class EventSenderProxy;
 class OriginSettings;
 class PlatformWebView;
-class EventSenderProxy;
+class TestInvocation;
+class TestOptions;
 struct TestCommand;
-struct TestOptions;
 
 class AsyncTask {
 public:
@@ -91,12 +92,8 @@
     static void configureWebsiteDataStoreTemporaryDirectories(WKWebsiteDataStoreConfigurationRef);
     static WKWebsiteDataStoreRef defaultWebsiteDataStore();
 
-    static const unsigned viewWidth;
-    static const unsigned viewHeight;
+    static WKURLRef createTestURL(const char* pathOrURL);
 
-    static const unsigned w3cSVGViewWidth;
-    static const unsigned w3cSVGViewHeight;
-
     static const WTF::Seconds defaultShortTimeout;
     static const WTF::Seconds noTimeout;
 
@@ -115,8 +112,6 @@
     WKWebsiteDataStoreRef websiteDataStore();
 
     EventSenderProxy* eventSenderProxy() { return m_eventSenderProxy.get(); }
-
-    bool shouldUseRemoteLayerTree() const { return m_shouldUseRemoteLayerTree; }
     
     // Runs the run loop until `done` is true or the timeout elapses.
     bool useWaitToDumpWatchdogTimer() { return m_useWaitToDumpWatchdogTimer; }
@@ -123,12 +118,9 @@
     void runUntil(bool& done, WTF::Seconds timeout);
     void notifyDone();
 
-    bool shouldShowWebView() const { return m_shouldShowWebView; }
     bool usingServerMode() const { return m_usingServerMode; }
     void configureViewForTest(const TestInvocation&);
-    
-    bool shouldShowTouches() const { return m_shouldShowTouches; }
-    
+
     bool beforeUnloadReturnValue() const { return m_beforeUnloadReturnValue; }
     void setBeforeUnloadReturnValue(bool value) { m_beforeUnloadReturnValue = value; }
 
@@ -275,7 +267,7 @@
     void clearLoadedSubresourceDomains();
     void clearAppBoundSession();
     void reinitializeAppBoundDomains();
-    void updateBundleIdentifierInNetworkProcess(const String& bundleIdentifier);
+    void updateBundleIdentifierInNetworkProcess(const std::string& bundleIdentifier);
     void clearBundleIdentifierInNetworkProcess();
 
     WKArrayRef openPanelFileURLs() const { return m_openPanelFileURLs.get(); }
@@ -362,7 +354,7 @@
 
 private:
     WKRetainPtr<WKPageConfigurationRef> generatePageConfiguration(const TestOptions&);
-    WKRetainPtr<WKContextConfigurationRef> generateContextConfiguration(const TestOptions::ContextOptions&) const;
+    WKRetainPtr<WKContextConfigurationRef> generateContextConfiguration(const ContextOptions&) const;
     void initialize(int argc, const char* argv[]);
     void createWebViewWithOptions(const TestOptions&);
     void run();
@@ -380,7 +372,6 @@
     void platformDestroy();
     WKContextRef platformAdjustContext(WKContextRef, WKContextConfigurationRef);
     void platformInitializeContext();
-    void platformAddTestOptions(TestOptions&) const;
     void platformCreateWebView(WKPageConfigurationRef, const TestOptions&);
     static PlatformWebView* platformCreateOtherPage(PlatformWebView* parentView, WKPageConfigurationRef, const TestOptions&);
     void platformResetPreferencesToConsistentValues();
@@ -389,7 +380,7 @@
 #if PLATFORM(COCOA)
     void cocoaPlatformInitialize();
     void cocoaResetStateToConsistentValues(const TestOptions&);
-    void setApplicationBundleIdentifier(const String&);
+    void setApplicationBundleIdentifier(const std::string&);
     void clearApplicationBundleIdentifierTestingOverride();
 #endif
     void platformConfigureViewForTest(const TestInvocation&);
@@ -403,7 +394,9 @@
 
     void ensureViewSupportsOptionsForTest(const TestInvocation&);
     TestOptions testOptionsForTest(const TestCommand&) const;
-    void updatePlatformSpecificTestOptionsForTest(TestOptions&, const std::string& pathOrURL) const;
+    TestFeatures globalFeatureDefaultsForTest(const TestCommand&) const;
+    TestFeatures platformSpecificFeatureDefaultsForTest(const TestCommand&) const;
+    TestFeatures platformSpecificFeatureOverridesDefaultsForTest(const TestCommand&) const;
 
     void updateWebViewSizeForTest(const TestInvocation&);
     void updateWindowScaleForTest(PlatformWebView*, const TestInvocation&);
@@ -538,8 +531,7 @@
     bool m_createdOtherPage { false };
     std::vector<std::string> m_paths;
     std::set<std::string> m_allowedHosts;
-    HashMap<String, bool> m_internalFeatures;
-    HashMap<String, bool> m_experimentalFeatures;
+    TestFeatures m_globalFeatures;
 
     WKRetainPtr<WKStringRef> m_injectedBundlePath;
     WKRetainPtr<WKStringRef> m_testPluginDirectory;
@@ -548,7 +540,7 @@
 
     std::unique_ptr<PlatformWebView> m_mainWebView;
     WKRetainPtr<WKContextRef> m_context;
-    Optional<TestOptions::ContextOptions> m_contextOptions;
+    Optional<ContextOptions> m_contextOptions;
     WKRetainPtr<WKPageGroupRef> m_pageGroup;
     WKRetainPtr<WKUserContentControllerRef> m_userContentController;
 
@@ -605,14 +597,10 @@
 
     bool m_forceComplexText { false };
     bool m_shouldUseAcceleratedDrawing { false };
-    bool m_shouldUseRemoteLayerTree { false };
-
+    
     bool m_shouldLogCanAuthenticateAgainstProtectionSpace { false };
     bool m_shouldLogDownloadCallbacks { false };
     bool m_shouldLogHistoryClientCallbacks { false };
-    bool m_shouldShowWebView { false };
-    
-    bool m_shouldShowTouches { false };
     bool m_checkForWorldLeaks { false };
 
     bool m_allowAnyHTTPSCertificateForAllowedHosts { false };

Modified: trunk/Tools/WebKitTestRunner/TestInvocation.cpp (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/TestInvocation.cpp	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/TestInvocation.cpp	2020-10-09 00:45:29 UTC (rev 268238)
@@ -131,11 +131,11 @@
 WKRetainPtr<WKMutableDictionaryRef> TestInvocation::createTestSettingsDictionary()
 {
     auto beginTestMessageBody = adoptWK(WKMutableDictionaryCreate());
-    setValue(beginTestMessageBody, "UseFlexibleViewport", options().useFlexibleViewport);
+    setValue(beginTestMessageBody, "UseFlexibleViewport", options().useFlexibleViewport());
     setValue(beginTestMessageBody, "DumpPixels", m_dumpPixels);
     setValue(beginTestMessageBody, "Timeout", static_cast<uint64_t>(m_timeout.milliseconds()));
     setValue(beginTestMessageBody, "DumpJSConsoleLogInStdErr", m_dumpJSConsoleLogInStdErr);
-    setValue(beginTestMessageBody, "additionalSupportedImageTypes", options().additionalSupportedImageTypes.c_str());
+    setValue(beginTestMessageBody, "additionalSupportedImageTypes", options().additionalSupportedImageTypes().c_str());
     return beginTestMessageBody;
 }
 

Modified: trunk/Tools/WebKitTestRunner/TestOptions.cpp (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/TestOptions.cpp	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/TestOptions.cpp	2020-10-09 00:45:29 UTC (rev 268238)
@@ -26,48 +26,383 @@
 #include "config.h"
 #include "TestOptions.h"
 
+#include "StringFunctions.h"
+#include "TestController.h"
+#include <fstream>
+#include <iostream>
 #include <string>
+#include <wtf/Optional.h>
+#include <wtf/StdFilesystem.h>
 #include <wtf/text/WTFString.h>
 
+#define DUMP_FEATURES 0
+
 namespace WTR {
 
-static bool pathContains(const std::string& pathOrURL, const char* substring)
+#if DUMP_FEATURES
+static void dumpFeatures(const TestFeatures& features)
 {
-    String path(pathOrURL.c_str());
-    return path.contains(substring); // Case-insensitive.
+    if (features.experimentalFeatures.empty() && features.internalDebugFeatures.empty() && features.boolFeatures.empty() && features.doubleFeatures.empty() && features.stringFeatures.empty() && features.stringVectorFeatures.empty()) {
+        std::cerr << "  [EMPTY]\n";
+        return;
+    }
+    
+    if (!features.experimentalFeatures.empty()) {
+        std::cerr << "  Experimental Features: \n";
+        for (auto [key, value] : features.experimentalFeatures)
+            std::cerr << "    " << key << ": " << value << '\n';
+    }
+    if (!features.internalDebugFeatures.empty()) {
+        std::cerr << "  Internal Features: \n";
+        for (auto [key, value] : features.internalDebugFeatures)
+            std::cerr << "    " << key << ": " << value << '\n';
+    }
+    if (!features.boolFeatures.empty()) {
+        std::cerr << "  Bool Features: \n";
+        for (auto [key, value] : features.boolFeatures)
+            std::cerr << "    " << key << ": " << value << '\n';
+    }
+    if (!features.doubleFeatures.empty()) {
+        std::cerr << "  Double Features: \n";
+        for (auto [key, value] : features.doubleFeatures)
+            std::cerr << "    " << key << ": " << value << '\n';
+    }
+    if (!features.stringFeatures.empty()) {
+        std::cerr << "  String Features: \n";
+        for (auto [key, value] : features.stringFeatures)
+            std::cerr << "    " << key << ": " << value << '\n';
+    }
+    if (!features.stringVectorFeatures.empty()) {
+        std::cerr << "  String Vector Features: \n";
+        for (auto [key, value] : features.stringVectorFeatures)
+            std::cerr << "    " << key << ": Number of strings " << value.size() << '\n';
+    }
 }
+#endif
 
-static bool shouldMakeViewportFlexible(const std::string& pathOrURL)
+void merge(TestFeatures& base, TestFeatures additional)
 {
-    return pathContains(pathOrURL, "viewport/") && !pathContains(pathOrURL, "visual-viewport/");
+    // FIXME: This should use std::unordered_map::merge when it is available for all ports.
+
+    for (auto [key, value] : additional.experimentalFeatures)
+        base.experimentalFeatures.insert_or_assign(key, value);
+    for (auto [key, value] : additional.internalDebugFeatures)
+        base.internalDebugFeatures.insert_or_assign(key, value);
+    for (auto [key, value] : additional.boolFeatures)
+        base.boolFeatures.insert_or_assign(key, value);
+    for (auto [key, value] : additional.doubleFeatures)
+        base.doubleFeatures.insert_or_assign(key, value);
+    for (auto [key, value] : additional.stringFeatures)
+        base.stringFeatures.insert_or_assign(key, value);
+    for (auto [key, value] : additional.stringVectorFeatures)
+        base.stringVectorFeatures.insert_or_assign(key, value);
 }
 
-static bool shouldUseFixedLayout(const std::string& pathOrURL)
+static const std::unordered_map<std::string, bool>& boolDefaultsMap()
 {
-#if ENABLE(CSS_DEVICE_ADAPTATION)
-    if (pathContains(pathOrURL, "device-adapt/") || pathContains(pathOrURL, "device-adapt\\"))
+    static std::unordered_map<std::string, bool> map {
+        { "useThreadedScrolling", false },
+        { "useAcceleratedDrawing", false },
+        { "useRemoteLayerTree", false },
+        { "shouldShowWebView", false },
+        { "useFlexibleViewport", false },
+        { "useDataDetection", false },
+        { "useMockScrollbars", true },
+        { "needsSiteSpecificQuirks", false },
+        { "ignoresViewportScaleLimits", false },
+        { "useCharacterSelectionGranularity", false },
+        { "enableAttachmentElement", false },
+        { "enableIntersectionObserver", false },
+        { "useEphemeralSession", false },
+        { "enableMenuItemElement", false },
+        { "enableKeygenElement", false },
+        { "enableModernMediaControls", true },
+        { "enablePointerLock", false },
+        { "enableWebAuthentication", true },
+        { "enableWebAuthenticationLocalAuthenticator", true },
+        { "enableInspectorAdditions", false },
+        { "shouldShowTouches", false },
+        { "dumpJSConsoleLogInStdErr", false },
+        { "allowCrossOriginSubresourcesToAskForCredentials", false },
+        { "domPasteAllowed", true },
+        { "enableColorFilter", false },
+        { "punchOutWhiteBackgroundsInDarkMode", false },
+        { "runSingly", false },
+        { "checkForWorldLeaks", false },
+        { "shouldIgnoreMetaViewport", false },
+        { "spellCheckingDots", false },
+        { "enableServiceControls", false },
+        { "editable", false },
+        { "shouldHandleRunOpenPanel", true },
+        { "shouldPresentPopovers", true },
+        { "enableAppNap", false },
+        { "enableBackForwardCache", false },
+        { "allowsLinkPreview", true },
+        { "enableCaptureVideoInUIProcess", false },
+        { "enableCaptureVideoInGPUProcess", false },
+        { "enableCaptureAudioInUIProcess", false },
+        { "enableCaptureAudioInGPUProcess", false },
+        { "allowTopNavigationToDataURLs", true },
+        { "enableInAppBrowserPrivacy", false },
+        { "isAppBoundWebView", false },
+        { "ignoreSynchronousMessagingTimeouts", false },
+        { "enableProcessSwapOnNavigation", true },
+        { "enableProcessSwapOnWindowOpen", false },
+        { "useServiceWorkerShortTimeout", false }
+    };
+    return map;
+}
+
+static const std::unordered_map<std::string, double>& doubleDefaultsMap()
+{
+    static std::unordered_map<std::string, double> map {
+        { "contentInset.top", 0 },
+        { "deviceScaleFactor", 1 },
+        { "viewWidth", 800 },
+        { "viewHeight", 600 }
+    };
+    return map;
+}
+
+static const std::unordered_map<std::string, std::string>& stringDefaultsMap()
+{
+    static std::unordered_map<std::string, std::string> map {
+        { "applicationManifest", { } },
+        { "jscOptions", { } },
+        { "additionalSupportedImageTypes", { } },
+        { "standaloneWebApplicationURL", { } },
+        { "contentMode", { } },
+        { "applicationBundleIdentifier", { } }
+    };
+    return map;
+}
+
+static const std::unordered_map<std::string, std::vector<std::string>>& stringVectorDefaultsMap()
+{
+    static std::unordered_map<std::string, std::vector<std::string>> map {
+        { "language", { } }
+    };
+    return map;
+}
+
+enum class KeyType : uint8_t {
+    Bool,
+    Double,
+    String,
+    StringRelativePath,
+    StringURL,
+    StringVector,
+    Unknown
+};
+
+static const std::unordered_map<std::string, KeyType>& keyTypeMap()
+{
+    static std::unordered_map<std::string, KeyType> map {
+        { "useThreadedScrolling", KeyType::Bool },
+        { "useAcceleratedDrawing", KeyType::Bool },
+        { "useRemoteLayerTree", KeyType::Bool },
+        { "shouldShowWebView", KeyType::Bool },
+        { "useFlexibleViewport", KeyType::Bool },
+        { "useDataDetection", KeyType::Bool },
+        { "useMockScrollbars", KeyType::Bool },
+        { "needsSiteSpecificQuirks", KeyType::Bool },
+        { "ignoresViewportScaleLimits", KeyType::Bool },
+        { "useCharacterSelectionGranularity", KeyType::Bool },
+        { "enableAttachmentElement", KeyType::Bool },
+        { "enableIntersectionObserver", KeyType::Bool },
+        { "useEphemeralSession", KeyType::Bool },
+        { "enableMenuItemElement", KeyType::Bool },
+        { "enableKeygenElement", KeyType::Bool },
+        { "enableModernMediaControls", KeyType::Bool },
+        { "enablePointerLock", KeyType::Bool },
+        { "enableWebAuthentication", KeyType::Bool },
+        { "enableWebAuthenticationLocalAuthenticator", KeyType::Bool },
+        { "enableInspectorAdditions", KeyType::Bool },
+        { "shouldShowTouches", KeyType::Bool },
+        { "dumpJSConsoleLogInStdErr", KeyType::Bool },
+        { "allowCrossOriginSubresourcesToAskForCredentials", KeyType::Bool },
+        { "domPasteAllowed", KeyType::Bool },
+        { "enableColorFilter", KeyType::Bool },
+        { "punchOutWhiteBackgroundsInDarkMode", KeyType::Bool },
+        { "runSingly", KeyType::Bool },
+        { "checkForWorldLeaks", KeyType::Bool },
+        { "shouldIgnoreMetaViewport", KeyType::Bool },
+        { "spellCheckingDots", KeyType::Bool },
+        { "enableServiceControls", KeyType::Bool },
+        { "editable", KeyType::Bool },
+        { "shouldHandleRunOpenPanel", KeyType::Bool },
+        { "shouldPresentPopovers", KeyType::Bool },
+        { "enableAppNap", KeyType::Bool },
+        { "enableBackForwardCache", KeyType::Bool },
+        { "allowsLinkPreview", KeyType::Bool },
+        { "enableCaptureVideoInUIProcess", KeyType::Bool },
+        { "enableCaptureVideoInGPUProcess", KeyType::Bool },
+        { "enableCaptureAudioInUIProcess", KeyType::Bool },
+        { "enableCaptureAudioInGPUProcess", KeyType::Bool },
+        { "allowTopNavigationToDataURLs", KeyType::Bool },
+        { "enableInAppBrowserPrivacy", KeyType::Bool },
+        { "isAppBoundWebView", KeyType::Bool },
+        { "ignoreSynchronousMessagingTimeouts", KeyType::Bool },
+        { "enableProcessSwapOnNavigation", KeyType::Bool },
+        { "enableProcessSwapOnWindowOpen", KeyType::Bool },
+        { "useServiceWorkerShortTimeout", KeyType::Bool },
+    
+        { "contentInset.top", KeyType::Double },
+        { "deviceScaleFactor", KeyType::Double },
+        { "viewWidth", KeyType::Double },
+        { "viewHeight", KeyType::Double },
+
+        { "jscOptions", KeyType::String },
+        { "additionalSupportedImageTypes", KeyType::String },
+        { "contentMode", KeyType::String },
+        { "applicationBundleIdentifier", KeyType::String },
+        { "applicationManifest", KeyType::StringRelativePath },
+        { "standaloneWebApplicationURL", KeyType::StringURL },
+
+        { "language", KeyType::StringVector },
+    };
+
+    return map;
+}
+
+static KeyType keyType(std::string key)
+{
+    auto map = keyTypeMap();
+    auto it = map.find(key);
+    if (it == map.end())
+        return KeyType::Unknown;
+    return it->second;
+}
+
+static bool parseBooleanTestHeaderValue(const std::string& value)
+{
+    if (value == "true")
         return true;
-#endif
+    if (value == "false")
+        return false;
+
+    LOG_ERROR("Found unexpected value '%s' for boolean option. Expected 'true' or 'false'.", value.c_str());
     return false;
 }
 
-static bool isSVGTestPath(const std::string& pathOrURL)
+static std::string parseStringTestHeaderValueAsRelativePath(const std::string& value, const std::string& pathOrURL)
 {
-    return pathContains(pathOrURL, "svg/W3C-SVG-1.1") || pathContains(pathOrURL, "svg\\W3C-SVG-1.1");
+    auto baseURL = adoptWK(TestController::createTestURL(pathOrURL.c_str()));
+    auto relativeURL = adoptWK(WKURLCreateWithBaseURL(baseURL.get(), value.c_str()));
+    return toSTD(adoptWK(WKURLCopyPath(relativeURL.get())));
 }
 
+static std::string parseStringTestHeaderValueAsURL(const std::string& value)
+{
+    return toSTD(adoptWK(WKURLCopyString(TestController::createTestURL(value.c_str()))));
+}
+
+static TestFeatures parseTestHeader(std::filesystem::path path, const std::string& pathOrURL)
+{
+    TestFeatures features;
+    if (!std::filesystem::exists(path))
+        return features;
+
+    std::ifstream file(path);
+    if (!file.good()) {
+        LOG_ERROR("Could not open file to inspect test headers in %s", path.c_str());
+        return features;
+    }
+
+    std::string options;
+    getline(file, options);
+    std::string beginString("webkit-test-runner [ ");
+    std::string endString(" ]");
+    size_t beginLocation = options.find(beginString);
+    if (beginLocation == std::string::npos)
+        return features;
+    size_t endLocation = options.find(endString, beginLocation);
+    if (endLocation == std::string::npos) {
+        LOG_ERROR("Could not find end of test header in %s", path.c_str());
+        return features;
+    }
+    std::string pairString = options.substr(beginLocation + beginString.size(), endLocation - (beginLocation + beginString.size()));
+    size_t pairStart = 0;
+    while (pairStart < pairString.size()) {
+        size_t pairEnd = pairString.find(" ", pairStart);
+        if (pairEnd == std::string::npos)
+            pairEnd = pairString.size();
+        size_t equalsLocation = pairString.find("=", pairStart);
+        if (equalsLocation == std::string::npos) {
+            LOG_ERROR("Malformed option in test header (could not find '=' character) in %s", path.c_str());
+            break;
+        }
+        auto key = pairString.substr(pairStart, equalsLocation - pairStart);
+        auto value = pairString.substr(equalsLocation + 1, pairEnd - (equalsLocation + 1));
+
+        if (key.rfind("experimental:") == 0) {
+            key = key.substr(13);
+            features.experimentalFeatures.insert({ key, parseBooleanTestHeaderValue(value) });
+        } else if (key.rfind("internal:") == 0) {
+            key = key.substr(9);
+            features.internalDebugFeatures.insert({ key, parseBooleanTestHeaderValue(value) });
+        }
+
+        switch (keyType(key)) {
+        case KeyType::Bool:
+            features.boolFeatures.insert({ key, parseBooleanTestHeaderValue(value) });
+            break;
+        case KeyType::Double:
+            features.doubleFeatures.insert({ key, std::stod(value) });
+            break;
+        case KeyType::String:
+            features.stringFeatures.insert({ key, value });
+            break;
+        case KeyType::StringRelativePath:
+            features.stringFeatures.insert({ key, parseStringTestHeaderValueAsRelativePath(value, pathOrURL) });
+            break;
+        case KeyType::StringURL:
+            features.stringFeatures.insert({ key, parseStringTestHeaderValueAsURL(value) });
+            break;
+        case KeyType::StringVector:
+            features.stringVectorFeatures.insert({ key, split(value, ',') });
+            break;
+        case KeyType::Unknown:
+            LOG_ERROR("Unknown key, '%s, in test header in %s", key.c_str(), path.c_str());
+            break;
+        }
+
+        pairStart = pairEnd + 1;
+    }
+
+    return features;
+}
+
+static bool pathContains(const std::string& pathOrURL, const char* substring)
+{
+    String path(pathOrURL.c_str());
+    return path.contains(substring); // Case-insensitive.
+}
+
+static bool shouldMakeViewportFlexible(const std::string& pathOrURL)
+{
+    return pathContains(pathOrURL, "viewport/") && !pathContains(pathOrURL, "visual-viewport/");
+}
+
 static bool shouldUseEphemeralSession(const std::string& pathOrURL)
 {
     return pathContains(pathOrURL, "w3c/IndexedDB-private-browsing") || pathContains(pathOrURL, "w3c\\IndexedDB-private-browsing");
 }
 
-static float deviceScaleFactorForTest(const std::string& pathOrURL)
+static Optional<std::pair<double, double>> overrideViewWidthAndHeightForTest(const std::string& pathOrURL)
 {
+    if (pathContains(pathOrURL, "svg/W3C-SVG-1.1") || pathContains(pathOrURL, "svg\\W3C-SVG-1.1"))
+        return { { 480, 360 } };
+    return WTF::nullopt;
+}
+
+static Optional<double> overrideDeviceScaleFactorForTest(const std::string& pathOrURL)
+{
     if (pathContains(pathOrURL, "/hidpi-3x-"))
         return 3;
     if (pathContains(pathOrURL, "/hidpi-"))
         return 2;
-    return 1;
+    return WTF::nullopt;
 }
 
 static bool shouldDumpJSConsoleLogInStdErr(const std::string& pathOrURL)
@@ -83,14 +418,101 @@
         || pathContains(pathOrURL, "localhost:8800/websockets") || pathContains(pathOrURL, "localhost:9443/websockets");
 }
 
-TestOptions::TestOptions(const std::string& pathOrURL)
-    : useFlexibleViewport(shouldMakeViewportFlexible(pathOrURL))
-    , useFixedLayout(shouldUseFixedLayout(pathOrURL))
-    , isSVGTest(isSVGTestPath(pathOrURL))
-    , useEphemeralSession(shouldUseEphemeralSession(pathOrURL))
-    , dumpJSConsoleLogInStdErr(shouldDumpJSConsoleLogInStdErr(pathOrURL))
-    , deviceScaleFactor(deviceScaleFactorForTest(pathOrURL))
+TestFeatures hardcodedFeaturesBasedOnPathForTest(const TestCommand& command)
 {
+    TestFeatures features;
+
+    if (shouldMakeViewportFlexible(command.pathOrURL))
+        features.boolFeatures.insert({ "useFlexibleViewport", true });
+    if (shouldUseEphemeralSession(command.pathOrURL))
+        features.boolFeatures.insert({ "useEphemeralSession", true });
+    if (shouldDumpJSConsoleLogInStdErr(command.pathOrURL))
+        features.boolFeatures.insert({ "dumpJSConsoleLogInStdErr", true });
+    if (auto deviceScaleFactor = overrideDeviceScaleFactorForTest(command.pathOrURL); deviceScaleFactor != WTF::nullopt)
+        features.doubleFeatures.insert({ "deviceScaleFactor", deviceScaleFactor.value() });
+    if (auto viewWidthAndHeight = overrideViewWidthAndHeightForTest(command.pathOrURL); viewWidthAndHeight != WTF::nullopt) {
+        features.doubleFeatures.insert({ "viewWidth", viewWidthAndHeight->first });
+        features.doubleFeatures.insert({ "viewHeight", viewWidthAndHeight->second });
+    }
+
+    return features;
 }
 
+TestFeatures featureDefaultsFromTestHeaderForTest(const TestCommand& command)
+{
+    return parseTestHeader(command.absolutePath, command.pathOrURL);
 }
+
+TestOptions::TestOptions(TestFeatures features)
+    : m_features { features }
+{
+#if DUMP_FEATURES
+    std::cerr << "DUMPING FEATURES\n";
+    dumpFeatures(m_features);
+#endif
+}
+
+bool TestOptions::hasSameInitializationOptions(const TestOptions& options) const
+{
+    if (m_features.experimentalFeatures != options.m_features.experimentalFeatures)
+        return false;
+    if (m_features.internalDebugFeatures != options.m_features.internalDebugFeatures)
+        return false;
+
+    for (auto [key, keyType] : keyTypeMap()) {
+        switch (keyType) {
+        case KeyType::Bool:
+            if (boolFeatureValue(key) != options.boolFeatureValue(key))
+                return false;
+            break;
+        case KeyType::Double:
+            if (doubleFeatureValue(key) != options.doubleFeatureValue(key))
+                return false;
+            break;
+        case KeyType::String:
+        case KeyType::StringRelativePath:
+        case KeyType::StringURL:
+            if (stringFeatureValue(key) != options.stringFeatureValue(key))
+                return false;
+            break;
+        case KeyType::StringVector:
+            if (stringVectorFeatureValue(key) != options.stringVectorFeatureValue(key))
+                return false;
+            break;
+        case KeyType::Unknown:
+            ASSERT_NOT_REACHED();
+        }
+    }
+    return true;
+}
+
+template<typename T>
+T featureValue(std::string key, const std::unordered_map<std::string, T>& map, const std::unordered_map<std::string, T>& defaultsMap)
+{
+    auto it = map.find(key);
+    if (it != map.end())
+        return it->second;
+    
+    auto defaultsMapIt = defaultsMap.find(key);
+    ASSERT(defaultsMapIt != defaultsMap.end());
+    return defaultsMapIt->second;
+}
+
+bool TestOptions::boolFeatureValue(std::string key) const
+{
+    return featureValue(key, m_features.boolFeatures, boolDefaultsMap());
+}
+double TestOptions::doubleFeatureValue(std::string key) const
+{
+    return featureValue(key, m_features.doubleFeatures, doubleDefaultsMap());
+}
+std::string TestOptions::stringFeatureValue(std::string key) const
+{
+    return featureValue(key, m_features.stringFeatures, stringDefaultsMap());
+}
+std::vector<std::string> TestOptions::stringVectorFeatureValue(std::string key) const
+{
+    return featureValue(key, m_features.stringVectorFeatures, stringVectorDefaultsMap());
+}
+
+}

Modified: trunk/Tools/WebKitTestRunner/TestOptions.h (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/TestOptions.h	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/TestOptions.h	2020-10-09 00:45:29 UTC (rev 268238)
@@ -25,165 +25,140 @@
 
 #pragma once
 
-#include <wtf/HashMap.h>
-#include <wtf/Vector.h>
-#include <wtf/text/StringHash.h>
-#include <wtf/text/WTFString.h>
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <vector>
 
 namespace WTR {
 
-struct TestOptions {
-    struct ContextOptions {
-        Vector<String> overrideLanguages;
-        bool ignoreSynchronousMessagingTimeouts { false };
-        bool enableProcessSwapOnNavigation { true };
-        bool enableProcessSwapOnWindowOpen { false };
-        bool useServiceWorkerShortTimeout { false };
+struct TestCommand;
 
-        bool hasSameInitializationOptions(const ContextOptions& options) const
-        {
-            if (ignoreSynchronousMessagingTimeouts != options.ignoreSynchronousMessagingTimeouts
-                || overrideLanguages != options.overrideLanguages
-                || enableProcessSwapOnNavigation != options.enableProcessSwapOnNavigation
-                || enableProcessSwapOnWindowOpen != options.enableProcessSwapOnWindowOpen
-                || useServiceWorkerShortTimeout != options.useServiceWorkerShortTimeout)
-                return false;
-            return true;
-        }
+struct TestFeatures {
+    std::unordered_map<std::string, bool> experimentalFeatures;
+    std::unordered_map<std::string, bool> internalDebugFeatures;
+    std::unordered_map<std::string, bool> boolFeatures;
+    std::unordered_map<std::string, double> doubleFeatures;
+    std::unordered_map<std::string, std::string> stringFeatures;
+    std::unordered_map<std::string, std::vector<std::string>> stringVectorFeatures;
+};
 
-        bool shouldEnableProcessSwapOnNavigation() const
-        {
-            return enableProcessSwapOnNavigation || enableProcessSwapOnWindowOpen;
-        }
-    };
+void merge(TestFeatures& base, TestFeatures additional);
 
-    bool useThreadedScrolling { false };
-    bool useAcceleratedDrawing { false };
-    bool useRemoteLayerTree { false };
-    bool shouldShowWebView { false };
-    bool useFlexibleViewport { false };
-    bool useFixedLayout { false };
-    bool isSVGTest { false };
-    bool useDataDetection { false };
-    bool useMockScrollbars { true };
-    bool needsSiteSpecificQuirks { false };
-    bool ignoresViewportScaleLimits { false };
-    bool useCharacterSelectionGranularity { false };
-    bool enableAttachmentElement { false };
-    bool enableIntersectionObserver { false };
-    bool useEphemeralSession { false };
-    bool enableMenuItemElement { false };
-    bool enableKeygenElement { false };
-    bool enableModernMediaControls { true };
-    bool enablePointerLock { false };
-    bool enableWebAuthentication { true };
-    bool enableWebAuthenticationLocalAuthenticator { true };
-    bool enableInspectorAdditions { false };
-    bool shouldShowTouches { false };
-    bool dumpJSConsoleLogInStdErr { false };
-    bool allowCrossOriginSubresourcesToAskForCredentials { false };
-    bool domPasteAllowed { true };
-    bool enableColorFilter { false };
-    bool punchOutWhiteBackgroundsInDarkMode { false };
-    bool runSingly { false };
-    bool checkForWorldLeaks { false };
-    bool shouldIgnoreMetaViewport { false };
-    bool shouldShowSpellCheckingDots { false };
-    bool enableServiceControls { false };
-    bool editable { false };
-    bool shouldHandleRunOpenPanel { true };
-    bool shouldPresentPopovers { true };
-    bool enableAppNap { false };
-    bool enableBackForwardCache { false };
-    bool allowsLinkPreview { true };
-    bool enableCaptureVideoInUIProcess { false };
-    bool enableCaptureVideoInGPUProcess { false };
-    bool enableCaptureAudioInUIProcess { false };
-    bool enableCaptureAudioInGPUProcess { false };
-    bool allowTopNavigationToDataURLs { true };
-    bool enableInAppBrowserPrivacy { false };
-    bool isAppBoundWebView { false };
+TestFeatures hardcodedFeaturesBasedOnPathForTest(const TestCommand&);
+TestFeatures featureDefaultsFromTestHeaderForTest(const TestCommand&);
 
-    double contentInsetTop { 0 };
+struct ContextOptions {
+    std::vector<std::string> overrideLanguages;
+    bool ignoreSynchronousMessagingTimeouts;
+    bool enableProcessSwapOnNavigation;
+    bool enableProcessSwapOnWindowOpen;
+    bool useServiceWorkerShortTimeout;
 
-    float deviceScaleFactor { 1 };
-    std::string applicationManifest;
-    std::string jscOptions;
-    std::string additionalSupportedImageTypes;
-    std::string standaloneWebApplicationURL;
-    HashMap<String, bool> experimentalFeatures;
-    HashMap<String, bool> internalDebugFeatures;
-    String contentMode;
-    String applicationBundleIdentifier;
-    
-    ContextOptions contextOptions;
+    bool hasSameInitializationOptions(const ContextOptions& options) const
+    {
+        if (ignoreSynchronousMessagingTimeouts != options.ignoreSynchronousMessagingTimeouts
+            || overrideLanguages != options.overrideLanguages
+            || enableProcessSwapOnNavigation != options.enableProcessSwapOnNavigation
+            || enableProcessSwapOnWindowOpen != options.enableProcessSwapOnWindowOpen
+            || useServiceWorkerShortTimeout != options.useServiceWorkerShortTimeout)
+            return false;
+        return true;
+    }
 
-    TestOptions(const std::string& pathOrURL);
+    bool shouldEnableProcessSwapOnNavigation() const
+    {
+        return enableProcessSwapOnNavigation || enableProcessSwapOnWindowOpen;
+    }
+};
 
-    // Add here options that can only be set upon PlatformWebView
-    // initialization and make sure it's up to date when adding new
-    // options to this struct. Otherwise, tests using those options
-    // might fail if WTR is reusing an existing PlatformWebView.
-    bool hasSameInitializationOptions(const TestOptions& options) const
+class TestOptions {
+public:
+    explicit TestOptions(TestFeatures);
+
+    ContextOptions contextOptions() const
     {
-        if (useThreadedScrolling != options.useThreadedScrolling
-            || useAcceleratedDrawing != options.useAcceleratedDrawing
-            || useMockScrollbars != options.useMockScrollbars
-            || needsSiteSpecificQuirks != options.needsSiteSpecificQuirks
-            || useCharacterSelectionGranularity != options.useCharacterSelectionGranularity
-            || enableAttachmentElement != options.enableAttachmentElement
-            || enableIntersectionObserver != options.enableIntersectionObserver
-            || useEphemeralSession != options.useEphemeralSession
-            || enableMenuItemElement != options.enableMenuItemElement
-            || enableKeygenElement != options.enableKeygenElement
-            || enableModernMediaControls != options.enableModernMediaControls
-            || enablePointerLock != options.enablePointerLock
-            || enableWebAuthentication != options.enableWebAuthentication
-            || enableWebAuthenticationLocalAuthenticator != options.enableWebAuthenticationLocalAuthenticator
-            || enableInspectorAdditions != options.enableInspectorAdditions
-            || dumpJSConsoleLogInStdErr != options.dumpJSConsoleLogInStdErr
-            || applicationManifest != options.applicationManifest
-            || allowCrossOriginSubresourcesToAskForCredentials != options.allowCrossOriginSubresourcesToAskForCredentials
-            || domPasteAllowed != options.domPasteAllowed
-            || enableColorFilter != options.enableColorFilter
-            || punchOutWhiteBackgroundsInDarkMode != options.punchOutWhiteBackgroundsInDarkMode
-            || jscOptions != options.jscOptions
-            || additionalSupportedImageTypes != options.additionalSupportedImageTypes
-            || runSingly != options.runSingly
-            || checkForWorldLeaks != options.checkForWorldLeaks
-            || shouldShowSpellCheckingDots != options.shouldShowSpellCheckingDots
-            || enableServiceControls != options.enableServiceControls
-            || shouldIgnoreMetaViewport != options.shouldIgnoreMetaViewport
-            || editable != options.editable
-            || shouldHandleRunOpenPanel != options.shouldHandleRunOpenPanel
-            || shouldPresentPopovers != options.shouldPresentPopovers
-            || contentInsetTop != options.contentInsetTop
-            || contentMode != options.contentMode
-            || applicationBundleIdentifier != options.applicationBundleIdentifier
-            || enableAppNap != options.enableAppNap
-            || enableBackForwardCache != options.enableBackForwardCache
-            || allowsLinkPreview != options.allowsLinkPreview
-            || enableCaptureVideoInUIProcess != options.enableCaptureVideoInUIProcess
-            || enableCaptureVideoInGPUProcess != options.enableCaptureVideoInGPUProcess
-            || enableCaptureAudioInUIProcess != options.enableCaptureAudioInUIProcess
-            || enableCaptureAudioInGPUProcess != options.enableCaptureAudioInGPUProcess
-            || allowTopNavigationToDataURLs != options.allowTopNavigationToDataURLs
-            || enableInAppBrowserPrivacy != options.enableInAppBrowserPrivacy
-            || standaloneWebApplicationURL != options.standaloneWebApplicationURL
-            || isAppBoundWebView != options.isAppBoundWebView)
-            return false;
+        return {
+            overrideLanguages(),
+            ignoreSynchronousMessagingTimeouts(),
+            enableProcessSwapOnNavigation(),
+            enableProcessSwapOnWindowOpen(),
+            useServiceWorkerShortTimeout()
+        };
+    }
 
-        if (!contextOptions.hasSameInitializationOptions(options.contextOptions))
-            return false;
+    bool useThreadedScrolling() const { return boolFeatureValue("useThreadedScrolling"); }
+    bool useAcceleratedDrawing() const { return boolFeatureValue("useAcceleratedDrawing"); }
+    bool useRemoteLayerTree() const { return boolFeatureValue("useRemoteLayerTree"); }
+    bool shouldShowWebView() const { return boolFeatureValue("shouldShowWebView"); }
+    bool useFlexibleViewport() const { return boolFeatureValue("useFlexibleViewport"); }
+    bool useDataDetection() const { return boolFeatureValue("useDataDetection"); }
+    bool useMockScrollbars() const { return boolFeatureValue("useMockScrollbars"); }
+    bool needsSiteSpecificQuirks() const { return boolFeatureValue("needsSiteSpecificQuirks"); }
+    bool ignoresViewportScaleLimits() const { return boolFeatureValue("ignoresViewportScaleLimits"); }
+    bool useCharacterSelectionGranularity() const { return boolFeatureValue("useCharacterSelectionGranularity"); }
+    bool enableAttachmentElement() const { return boolFeatureValue("enableAttachmentElement"); }
+    bool enableIntersectionObserver() const { return boolFeatureValue("enableIntersectionObserver"); }
+    bool useEphemeralSession() const { return boolFeatureValue("useEphemeralSession"); }
+    bool enableMenuItemElement() const { return boolFeatureValue("enableMenuItemElement"); }
+    bool enableKeygenElement() const { return boolFeatureValue("enableKeygenElement"); }
+    bool enableModernMediaControls() const { return boolFeatureValue("enableModernMediaControls"); }
+    bool enablePointerLock() const { return boolFeatureValue("enablePointerLock"); }
+    bool enableWebAuthentication() const { return boolFeatureValue("enableWebAuthentication"); }
+    bool enableWebAuthenticationLocalAuthenticator() const { return boolFeatureValue("enableWebAuthenticationLocalAuthenticator"); }
+    bool enableInspectorAdditions() const { return boolFeatureValue("enableInspectorAdditions"); }
+    bool shouldShowTouches() const { return boolFeatureValue("shouldShowTouches"); }
+    bool dumpJSConsoleLogInStdErr() const { return boolFeatureValue("dumpJSConsoleLogInStdErr"); }
+    bool allowCrossOriginSubresourcesToAskForCredentials() const { return boolFeatureValue("allowCrossOriginSubresourcesToAskForCredentials"); }
+    bool domPasteAllowed() const { return boolFeatureValue("domPasteAllowed"); }
+    bool enableColorFilter() const { return boolFeatureValue("enableColorFilter"); }
+    bool punchOutWhiteBackgroundsInDarkMode() const { return boolFeatureValue("punchOutWhiteBackgroundsInDarkMode"); }
+    bool runSingly() const { return boolFeatureValue("runSingly"); }
+    bool checkForWorldLeaks() const { return boolFeatureValue("checkForWorldLeaks"); }
+    bool shouldIgnoreMetaViewport() const { return boolFeatureValue("shouldIgnoreMetaViewport"); }
+    bool shouldShowSpellCheckingDots() const { return boolFeatureValue("spellCheckingDots"); }
+    bool enableServiceControls() const { return boolFeatureValue("enableServiceControls"); }
+    bool editable() const { return boolFeatureValue("editable"); }
+    bool shouldHandleRunOpenPanel() const { return boolFeatureValue("shouldHandleRunOpenPanel"); }
+    bool shouldPresentPopovers() const { return boolFeatureValue("shouldPresentPopovers"); }
+    bool enableAppNap() const { return boolFeatureValue("enableAppNap"); }
+    bool enableBackForwardCache() const { return boolFeatureValue("enableBackForwardCache"); }
+    bool allowsLinkPreview() const { return boolFeatureValue("allowsLinkPreview"); }
+    bool enableCaptureVideoInUIProcess() const { return boolFeatureValue("enableCaptureVideoInUIProcess"); }
+    bool enableCaptureVideoInGPUProcess() const { return boolFeatureValue("enableCaptureVideoInGPUProcess"); }
+    bool enableCaptureAudioInUIProcess() const { return boolFeatureValue("enableCaptureAudioInUIProcess"); }
+    bool enableCaptureAudioInGPUProcess() const { return boolFeatureValue("enableCaptureAudioInGPUProcess"); }
+    bool allowTopNavigationToDataURLs() const { return boolFeatureValue("allowTopNavigationToDataURLs"); }
+    bool enableInAppBrowserPrivacy() const { return boolFeatureValue("enableInAppBrowserPrivacy"); }
+    bool isAppBoundWebView() const { return boolFeatureValue("isAppBoundWebView"); }
+    bool ignoreSynchronousMessagingTimeouts() const { return boolFeatureValue("ignoreSynchronousMessagingTimeouts"); }
+    bool enableProcessSwapOnNavigation() const { return boolFeatureValue("enableProcessSwapOnNavigation"); }
+    bool enableProcessSwapOnWindowOpen() const { return boolFeatureValue("enableProcessSwapOnWindowOpen"); }
+    bool useServiceWorkerShortTimeout() const { return boolFeatureValue("useServiceWorkerShortTimeout"); }
+    double contentInsetTop() const { return doubleFeatureValue("contentInset.top"); }
+    double deviceScaleFactor() const { return doubleFeatureValue("deviceScaleFactor"); }
+    double viewWidth() const { return doubleFeatureValue("viewWidth"); }
+    double viewHeight() const { return doubleFeatureValue("viewHeight"); }
+    std::string applicationManifest() const { return stringFeatureValue("applicationManifest"); }
+    std::string jscOptions() const { return stringFeatureValue("jscOptions"); }
+    std::string additionalSupportedImageTypes() const { return stringFeatureValue("additionalSupportedImageTypes"); }
+    std::string standaloneWebApplicationURL() const { return stringFeatureValue("standaloneWebApplicationURL"); }
+    std::string contentMode() const { return stringFeatureValue("contentMode"); }
+    std::string applicationBundleIdentifier() const { return stringFeatureValue("applicationBundleIdentifier"); }
+    std::vector<std::string> overrideLanguages() const { return stringVectorFeatureValue("language"); }
 
-        if (experimentalFeatures != options.experimentalFeatures)
-            return false;
+    const std::unordered_map<std::string, bool>& experimentalFeatures() const { return m_features.experimentalFeatures; }
+    const std::unordered_map<std::string, bool>& internalDebugFeatures() const { return m_features.internalDebugFeatures; }
 
-        if (internalDebugFeatures != options.internalDebugFeatures)
-            return false;
+    bool hasSameInitializationOptions(const TestOptions&) const;
 
-        return true;
-    }
+private:
+    bool boolFeatureValue(std::string key) const;
+    double doubleFeatureValue(std::string key) const;
+    std::string stringFeatureValue(std::string key) const;
+    std::vector<std::string> stringVectorFeatureValue(std::string key) const;
+
+    TestFeatures m_features;
 };
 
 }

Modified: trunk/Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm	2020-10-09 00:45:29 UTC (rev 268238)
@@ -114,24 +114,30 @@
     return (__bridge WKPreferencesRef)globalWebViewConfiguration.preferences;
 }
 
-void TestController::platformAddTestOptions(TestOptions& options) const
+TestFeatures TestController::platformSpecificFeatureOverridesDefaultsForTest(const TestCommand&) const
 {
+    TestFeatures features;
+
     if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EnableProcessSwapOnNavigation"])
-        options.contextOptions.enableProcessSwapOnNavigation = true;
+        features.boolFeatures.insert({ "enableProcessSwapOnNavigation", true });
     if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EnableProcessSwapOnWindowOpen"])
-        options.contextOptions.enableProcessSwapOnWindowOpen = true;
+        features.boolFeatures.insert({ "enableProcessSwapOnWindowOpen", true });
+
+    return features;
 }
 
 void TestController::platformInitializeDataStore(WKPageConfigurationRef, const TestOptions& options)
 {
-    if (options.useEphemeralSession || options.standaloneWebApplicationURL.length() || options.enableInAppBrowserPrivacy) {
-        auto websiteDataStoreConfig = options.useEphemeralSession ? [[[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration] autorelease] : [[[_WKWebsiteDataStoreConfiguration alloc] init] autorelease];
-        if (!options.useEphemeralSession)
+    bool useEphemeralSession = options.useEphemeralSession();
+    auto standaloneWebApplicationURL = options.standaloneWebApplicationURL();
+    if (useEphemeralSession || standaloneWebApplicationURL.length() || options.enableInAppBrowserPrivacy()) {
+        auto websiteDataStoreConfig = useEphemeralSession ? [[[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration] autorelease] : [[[_WKWebsiteDataStoreConfiguration alloc] init] autorelease];
+        if (!useEphemeralSession)
             configureWebsiteDataStoreTemporaryDirectories((WKWebsiteDataStoreConfigurationRef)websiteDataStoreConfig);
-        if (options.standaloneWebApplicationURL.length())
-            [websiteDataStoreConfig setStandaloneApplicationURL:[NSURL URLWithString:[NSString stringWithUTF8String:options.standaloneWebApplicationURL.c_str()]]];
+        if (standaloneWebApplicationURL.length())
+            [websiteDataStoreConfig setStandaloneApplicationURL:[NSURL URLWithString:[NSString stringWithUTF8String:standaloneWebApplicationURL.c_str()]]];
 #if PLATFORM(IOS_FAMILY)
-        if (options.enableInAppBrowserPrivacy)
+        if (options.enableInAppBrowserPrivacy())
             [websiteDataStoreConfig setEnableInAppBrowserPrivacyForTesting:YES];
 #endif
         m_websiteDataStore = (__bridge WKWebsiteDataStoreRef)[[[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfig] autorelease];
@@ -144,32 +150,33 @@
     auto copiedConfiguration = adoptNS([globalWebViewConfiguration copy]);
 
 #if PLATFORM(IOS_FAMILY)
-    if (options.useDataDetection)
+    if (options.useDataDetection())
         [copiedConfiguration setDataDetectorTypes:WKDataDetectorTypeAll];
-    if (options.ignoresViewportScaleLimits)
+    if (options.ignoresViewportScaleLimits())
         [copiedConfiguration setIgnoresViewportScaleLimits:YES];
-    if (options.useCharacterSelectionGranularity)
+    if (options.useCharacterSelectionGranularity())
         [copiedConfiguration setSelectionGranularity:WKSelectionGranularityCharacter];
-    if (options.isAppBoundWebView)
+    if (options.isAppBoundWebView())
         [copiedConfiguration setLimitsNavigationsToAppBoundDomains:YES];
 #else
-    [copiedConfiguration _setServiceControlsEnabled:options.enableServiceControls];
+    [copiedConfiguration _setServiceControlsEnabled:options.enableServiceControls()];
 #endif
 
-    if (options.enableAttachmentElement)
+    if (options.enableAttachmentElement())
         [copiedConfiguration _setAttachmentElementEnabled:YES];
 
-    if (options.enableColorFilter)
+    if (options.enableColorFilter())
         [copiedConfiguration _setColorFilterEnabled:YES];
 
     [copiedConfiguration setWebsiteDataStore:(WKWebsiteDataStore *)websiteDataStore()];
 
-    [copiedConfiguration _setAllowTopNavigationToDataURLs:options.allowTopNavigationToDataURLs];
+    [copiedConfiguration _setAllowTopNavigationToDataURLs:options.allowTopNavigationToDataURLs()];
 
     configureContentMode(copiedConfiguration.get(), options);
 
-    if (options.applicationManifest.length()) {
-        auto manifestPath = [NSString stringWithUTF8String:options.applicationManifest.c_str()];
+    auto applicationManifest = options.applicationManifest();
+    if (applicationManifest.length()) {
+        auto manifestPath = [NSString stringWithUTF8String:applicationManifest.c_str()];
         NSString *text = [NSString stringWithContentsOfFile:manifestPath usedEncoding:nullptr error:nullptr];
         [copiedConfiguration _setApplicationManifest:[_WKApplicationManifest applicationManifestFromJSON:text manifestURL:nil documentURL:nil]];
     }
@@ -177,13 +184,13 @@
     m_mainWebView = makeUnique<PlatformWebView>(copiedConfiguration.get(), options);
     finishCreatingPlatformWebView(m_mainWebView.get(), options);
 
-    if (options.punchOutWhiteBackgroundsInDarkMode)
+    if (options.punchOutWhiteBackgroundsInDarkMode())
         m_mainWebView->setDrawsBackground(false);
 
-    if (options.editable)
+    if (options.editable())
         m_mainWebView->setEditable(true);
 
-    m_mainWebView->platformView().allowsLinkPreview = options.allowsLinkPreview;
+    m_mainWebView->platformView().allowsLinkPreview = options.allowsLinkPreview();
     [m_mainWebView->platformView() _setShareSheetCompletesImmediatelyWithResolutionForTesting:YES];
 }
 
@@ -202,7 +209,7 @@
 void TestController::finishCreatingPlatformWebView(PlatformWebView* view, const TestOptions& options)
 {
 #if PLATFORM(MAC)
-    if (options.shouldShowWebView)
+    if (options.shouldShowWebView())
         [view->platformWindow() orderFront:nil];
     else
         [view->platformWindow() orderBack:nil];
@@ -262,12 +269,12 @@
     }
 }
 
-void TestController::setApplicationBundleIdentifier(const String& bundleIdentifier)
+void TestController::setApplicationBundleIdentifier(const std::string& bundleIdentifier)
 {
-    if (bundleIdentifier.isEmpty())
+    if (bundleIdentifier.empty())
         return;
     
-    [TestRunnerWKWebView _setApplicationBundleIdentifier:(NSString *)bundleIdentifier.createCFString().get()];
+    [TestRunnerWKWebView _setApplicationBundleIdentifier:(NSString *)toWTFString(bundleIdentifier).createCFString().get()];
 }
 
 void TestController::clearApplicationBundleIdentifierTestingOverride()
@@ -285,7 +292,7 @@
         TestRunnerWKWebView *platformView = webView->platformView();
         platformView._viewScale = 1;
         platformView._minimumEffectiveDeviceWidth = 0;
-        [platformView _setContinuousSpellCheckingEnabledForTesting:options.shouldShowSpellCheckingDots];
+        [platformView _setContinuousSpellCheckingEnabledForTesting:options.shouldShowSpellCheckingDots()];
         [platformView resetInteractionCallbacks];
         [platformView _resetNavigationGestureStateForTesting];
     }
@@ -486,12 +493,11 @@
 
 static WKContentMode contentMode(const TestOptions& options)
 {
-    if (options.contentMode == "desktop"_s)
+    auto mode = options.contentMode();
+    if (mode == "desktop")
         return WKContentModeDesktop;
-
-    if (options.contentMode == "mobile"_s)
+    if (mode == "mobile")
         return WKContentModeMobile;
-
     return WKContentModeRecommended;
 }
 

Modified: trunk/Tools/WebKitTestRunner/gtk/TestControllerGtk.cpp (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/gtk/TestControllerGtk.cpp	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/gtk/TestControllerGtk.cpp	2020-10-09 00:45:29 UTC (rev 268238)
@@ -150,9 +150,11 @@
     m_mainWebView->dismissAllPopupMenus();
 }
 
-void TestController::updatePlatformSpecificTestOptionsForTest(TestOptions& options, const std::string&) const
+TestFeatures TestController::platformSpecificFeatureDefaultsForTest(const TestCommand&) const
 {
-    options.enableModernMediaControls = false;
+    TestFeatures features;
+    features.boolFeatures.insert({ "enableModernMediaControls", false });
+    return features;
 }
 
 } // namespace WTR

Modified: trunk/Tools/WebKitTestRunner/ios/PlatformWebViewIOS.mm (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/ios/PlatformWebViewIOS.mm	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/ios/PlatformWebViewIOS.mm	2020-10-09 00:45:29 UTC (rev 268238)
@@ -189,7 +189,7 @@
     : m_windowIsKey(true)
     , m_options(options)
 {
-    CGRect rect = CGRectMake(0, 0, TestController::viewWidth, TestController::viewHeight);
+    CGRect rect = CGRectMake(0, 0, options.viewWidth(), options.viewHeight());
 
     m_window = [[WebKitTestRunnerWindow alloc] initWithFrame:rect];
     m_window.backgroundColor = [UIColor lightGrayColor];

Modified: trunk/Tools/WebKitTestRunner/ios/TestControllerIOS.mm (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/ios/TestControllerIOS.mm	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/ios/TestControllerIOS.mm	2020-10-09 00:45:29 UTC (rev 268238)
@@ -190,7 +190,7 @@
     }
 
     m_presentPopoverSwizzlers.clear();
-    if (!options.shouldPresentPopovers) {
+    if (!options.shouldPresentPopovers()) {
 #if USE(UICONTEXTMENU)
         m_presentPopoverSwizzlers.append(makeUnique<InstanceMethodSwizzler>([UIContextMenuInteraction class], @selector(_presentMenuAtLocation:), reinterpret_cast<IMP>(overridePresentMenuOrPopoverOrViewController)));
 #endif
@@ -213,8 +213,10 @@
         UIScrollView *scrollView = webView.scrollView;
         [scrollView _removeAllAnimations:YES];
         [scrollView setZoomScale:1 animated:NO];
-        scrollView.contentInset = UIEdgeInsetsMake(options.contentInsetTop, 0, 0, 0);
-        scrollView.contentOffset = CGPointMake(0, -options.contentInsetTop);
+        
+        auto contentInsetTop = options.contentInsetTop();
+        scrollView.contentInset = UIEdgeInsetsMake(contentInsetTop, 0, 0, 0);
+        scrollView.contentOffset = CGPointMake(0, -contentInsetTop);
 
         if (webView.interactingWithFormControl)
             shouldRestoreFirstResponder = [webView resignFirstResponder];
@@ -263,12 +265,14 @@
 
 void TestController::platformConfigureViewForTest(const TestInvocation& test)
 {
+    [[GeneratedTouchesDebugWindow sharedGeneratedTouchesDebugWindow] setShouldShowTouches:test.options().shouldShowTouches()];
+
     TestRunnerWKWebView *webView = mainWebView()->platformView();
 
-    if (test.options().shouldIgnoreMetaViewport)
+    if (test.options().shouldIgnoreMetaViewport())
         webView.configuration.preferences._shouldIgnoreMetaViewport = YES;
 
-    if (!test.options().useFlexibleViewport)
+    if (!test.options().useFlexibleViewport())
         return;
 
     CGRect screenBounds = [UIScreen mainScreen].bounds;
@@ -289,10 +293,9 @@
     // WKBundlePageSetUseTestingViewportConfiguration(false).
 }
 
-void TestController::updatePlatformSpecificTestOptionsForTest(TestOptions& options, const std::string&) const
+TestFeatures TestController::platformSpecificFeatureDefaultsForTest(const TestCommand&) const
 {
-    options.shouldShowTouches = shouldShowTouches();
-    [[GeneratedTouchesDebugWindow sharedGeneratedTouchesDebugWindow] setShouldShowTouches:options.shouldShowTouches];
+    return { };
 }
 
 void TestController::platformInitializeContext()

Modified: trunk/Tools/WebKitTestRunner/mac/PlatformWebViewMac.mm (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/mac/PlatformWebViewMac.mm	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/mac/PlatformWebViewMac.mm	2020-10-09 00:45:29 UTC (rev 268238)
@@ -61,18 +61,18 @@
     , m_options(options)
 {
     // FIXME: Not sure this is the best place for this; maybe we should have API to set this so we can do it from TestController?
-    if (m_options.useRemoteLayerTree)
+    if (m_options.useRemoteLayerTree())
         [[NSUserDefaults standardUserDefaults] setValue:@YES forKey:@"WebKit2UseRemoteLayerTreeDrawingArea"];
 
     auto copiedConfiguration = adoptNS([configuration copy]);
-    WKPreferencesSetThreadedScrollingEnabled((__bridge WKPreferencesRef)[copiedConfiguration preferences], m_options.useThreadedScrolling);
+    WKPreferencesSetThreadedScrollingEnabled((__bridge WKPreferencesRef)[copiedConfiguration preferences], m_options.useThreadedScrolling());
 
-    NSRect rect = NSMakeRect(0, 0, TestController::viewWidth, TestController::viewHeight);
+    NSRect rect = NSMakeRect(0, 0, options.viewWidth(), options.viewHeight());
     m_view = [[TestRunnerWKWebView alloc] initWithFrame:rect configuration:copiedConfiguration.get()];
     [m_view _setWindowOcclusionDetectionEnabled:NO];
 
     NSScreen *firstScreen = [[NSScreen screens] objectAtIndex:0];
-    NSRect windowRect = m_options.shouldShowWebView ? NSOffsetRect(rect, 100, 100) : NSOffsetRect(rect, -10000, [firstScreen frame].size.height - rect.size.height + 10000);
+    NSRect windowRect = m_options.shouldShowWebView() ? NSOffsetRect(rect, 100, 100) : NSOffsetRect(rect, -10000, [firstScreen frame].size.height - rect.size.height + 10000);
     m_window = [[WebKitTestRunnerWindow alloc] initWithContentRect:windowRect styleMask:NSWindowStyleMaskBorderless backing:(NSBackingStoreType)_NSBackingStoreUnbuffered defer:YES];
     m_window.platformWebView = this;
     [m_window setColorSpace:[firstScreen colorSpace]];

Modified: trunk/Tools/WebKitTestRunner/mac/TestControllerMac.mm (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/mac/TestControllerMac.mm	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/mac/TestControllerMac.mm	2020-10-09 00:45:29 UTC (rev 268238)
@@ -172,11 +172,11 @@
     return true;
 }
 
-void TestController::updatePlatformSpecificTestOptionsForTest(TestOptions& options, const std::string&) const
+TestFeatures TestController::platformSpecificFeatureDefaultsForTest(const TestCommand&) const
 {
-    options.useThreadedScrolling = true;
-    options.useRemoteLayerTree = shouldUseRemoteLayerTree();
-    options.shouldShowWebView = shouldShowWebView();
+    TestFeatures features;
+    features.boolFeatures.insert({ "useThreadedScrolling", true });
+    return features;
 }
 
 void TestController::configureContentExtensionForTest(const TestInvocation& test)

Modified: trunk/Tools/WebKitTestRunner/win/PlatformWebViewWin.cpp (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/win/PlatformWebViewWin.cpp	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/win/PlatformWebViewWin.cpp	2020-10-09 00:45:29 UTC (rev 268238)
@@ -144,7 +144,7 @@
         SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOCOPYBITS);
 
     UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOCOPYBITS;
-    if (m_options.shouldShowWebView)
+    if (m_options.shouldShowWebView())
         flags |= SWP_NOMOVE;
     ::SetWindowPos(
         m_window,

Modified: trunk/Tools/WebKitTestRunner/win/TestControllerWin.cpp (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/win/TestControllerWin.cpp	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/win/TestControllerWin.cpp	2020-10-09 00:45:29 UTC (rev 268238)
@@ -227,9 +227,9 @@
     notImplemented();
 }
 
-void TestController::updatePlatformSpecificTestOptionsForTest(TestOptions&, const std::string&) const
+TestFeatures TestController::platformSpecificFeatureDefaultsForTest(const TestCommand&) const
 {
-    notImplemented();
+    return { };
 }
 
 } // namespace WTR

Modified: trunk/Tools/WebKitTestRunner/wpe/TestControllerWPE.cpp (268237 => 268238)


--- trunk/Tools/WebKitTestRunner/wpe/TestControllerWPE.cpp	2020-10-09 00:34:13 UTC (rev 268237)
+++ trunk/Tools/WebKitTestRunner/wpe/TestControllerWPE.cpp	2020-10-09 00:45:29 UTC (rev 268238)
@@ -136,9 +136,11 @@
 {
 }
 
-void TestController::updatePlatformSpecificTestOptionsForTest(TestOptions& options, const std::string&) const
+TestFeatures TestController::platformSpecificFeatureDefaultsForTest(const TestCommand&) const
 {
-    options.enableModernMediaControls = false;
+    TestFeatures features;
+    features.boolFeatures.insert({ "enableModernMediaControls", false });
+    return features;
 }
 
 } // namespace WTR
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to