[Web App Manifest] Support display-mode media feature
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Dec 2017 20:04:37 +0000 (20:04 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Dec 2017 20:04:37 +0000 (20:04 +0000)
https://bugs.webkit.org/show_bug.cgi?id=180376
rdar://problem/35837993

Patch by David Quesada <david_quesada@apple.com> on 2017-12-07
Reviewed by Geoffrey Garen.

Source/WebCore:

Tests: applicationmanifest/display-mode-subframe.html
       applicationmanifest/display-mode.html

* Modules/applicationmanifest/ApplicationManifest.h:
(WebCore::ApplicationManifest::encode const):
(WebCore::ApplicationManifest::decode):
* Modules/applicationmanifest/ApplicationManifestParser.cpp:
(WebCore::ApplicationManifestParser::parseManifest):
(WebCore::ApplicationManifestParser::parseDisplay):
* Modules/applicationmanifest/ApplicationManifestParser.h:
    Add a 'display' property to ApplicationManifest and have ApplicationManifestParser
    populate it by parsing the 'display' property from the raw manifest.

* css/CSSValueKeywords.in:
* css/MediaFeatureNames.h:
* css/MediaQueryEvaluator.cpp:
(WebCore::displayModeEvaluate):
    To evaluate whether a display-mode query matches, compare the parameter to the
    display mode of the manifest applied to the main frame. If there is no manifest,
    use the fallback display mode 'browser' - the spec mandates that display-mode
    must be exposed even if there is no applied manifest.
* css/MediaQueryExpression.cpp:
(WebCore::featureWithValidIdent):
(WebCore::isFeatureValidWithoutValue):

Source/WebKit:

* UIProcess/API/Cocoa/_WKApplicationManifest.h:
* UIProcess/API/Cocoa/_WKApplicationManifest.mm:
(-[_WKApplicationManifest initWithCoder:]):
(-[_WKApplicationManifest encodeWithCoder:]):
(-[_WKApplicationManifest displayMode]):

Tools:

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebCore/ApplicationManifestParser.cpp:
(WebCore::operator<<):
(ApplicationManifestParserTest::testDisplay):
(TEST_F):
    Add unit tests for the parsing of the 'display' manifest property.

* TestWebKitAPI/Tests/WebKitCocoa/ApplicationManifest.mm:
(TestWebKitAPI::TEST):
* TestWebKitAPI/Tests/WebKitCocoa/display-mode.html: Added.
    Update API tests to include _WKApplicationManifest.displayMode.

* WebKitTestRunner/TestController.cpp:
(WTR::parseStringTestHeaderValueAsRelativePath):
(WTR::updateTestOptionsFromTestHeader):
* WebKitTestRunner/TestOptions.h:
(WTR::TestOptions::hasSameInitializationOptions const):
* WebKitTestRunner/cocoa/TestControllerCocoa.mm:
(WTR::TestController::platformCreateWebView):
    Teach WKTR to look for and apply an app manifest. A new <!--webkit-test-runner-->
    directive 'applicationManifest' can specify a path (relative to the test file itself)
    to a JSON app manifest to be applied to the web view before running the test.

LayoutTests:

* applicationmanifest/display-mode-expected.txt: Added.
* applicationmanifest/display-mode-subframe-expected.txt: Added.
* applicationmanifest/display-mode-subframe.html: Added.
* applicationmanifest/display-mode.html: Added.
* applicationmanifest/resources/display-mode-subframe-1.html: Added.
* applicationmanifest/resources/standalone.manifest: Added.

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

28 files changed:
LayoutTests/ChangeLog
LayoutTests/applicationmanifest/display-mode-expected.txt [new file with mode: 0644]
LayoutTests/applicationmanifest/display-mode-no-manifest-expected.txt [new file with mode: 0644]
LayoutTests/applicationmanifest/display-mode-no-manifest.html [new file with mode: 0644]
LayoutTests/applicationmanifest/display-mode-subframe-expected.txt [new file with mode: 0644]
LayoutTests/applicationmanifest/display-mode-subframe.html [new file with mode: 0644]
LayoutTests/applicationmanifest/display-mode.html [new file with mode: 0644]
LayoutTests/applicationmanifest/resources/display-mode-subframe-1.html [new file with mode: 0644]
LayoutTests/applicationmanifest/resources/standalone.manifest [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Modules/applicationmanifest/ApplicationManifest.h
Source/WebCore/Modules/applicationmanifest/ApplicationManifestParser.cpp
Source/WebCore/Modules/applicationmanifest/ApplicationManifestParser.h
Source/WebCore/css/CSSValueKeywords.in
Source/WebCore/css/MediaFeatureNames.h
Source/WebCore/css/MediaQueryEvaluator.cpp
Source/WebCore/css/MediaQueryExpression.cpp
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/API/Cocoa/_WKApplicationManifest.h
Source/WebKit/UIProcess/API/Cocoa/_WKApplicationManifest.mm
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebCore/ApplicationManifestParser.cpp
Tools/TestWebKitAPI/Tests/WebKitCocoa/ApplicationManifest.mm
Tools/TestWebKitAPI/Tests/WebKitCocoa/display-mode.html [new file with mode: 0644]
Tools/WebKitTestRunner/TestController.cpp
Tools/WebKitTestRunner/TestOptions.h
Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm

index 7dc6d81..da533a7 100644 (file)
@@ -1,3 +1,18 @@
+2017-12-07  David Quesada  <david_quesada@apple.com>
+
+        [Web App Manifest] Support display-mode media feature
+        https://bugs.webkit.org/show_bug.cgi?id=180376
+        rdar://problem/35837993
+
+        Reviewed by Geoffrey Garen.
+
+        * applicationmanifest/display-mode-expected.txt: Added.
+        * applicationmanifest/display-mode-subframe-expected.txt: Added.
+        * applicationmanifest/display-mode-subframe.html: Added.
+        * applicationmanifest/display-mode.html: Added.
+        * applicationmanifest/resources/display-mode-subframe-1.html: Added.
+        * applicationmanifest/resources/standalone.manifest: Added.
+
 2017-12-07  Jer Noble  <jer.noble@apple.com>
 
         Creating a second AVPlayerItemVideoOutput causes flakey failures
diff --git a/LayoutTests/applicationmanifest/display-mode-expected.txt b/LayoutTests/applicationmanifest/display-mode-expected.txt
new file mode 100644 (file)
index 0000000..3d6cb1b
--- /dev/null
@@ -0,0 +1 @@
+(display-mode) (display-mode: standalone)
diff --git a/LayoutTests/applicationmanifest/display-mode-no-manifest-expected.txt b/LayoutTests/applicationmanifest/display-mode-no-manifest-expected.txt
new file mode 100644 (file)
index 0000000..cc8d0d9
--- /dev/null
@@ -0,0 +1 @@
+(display-mode) (display-mode: browser)
diff --git a/LayoutTests/applicationmanifest/display-mode-no-manifest.html b/LayoutTests/applicationmanifest/display-mode-no-manifest.html
new file mode 100644 (file)
index 0000000..fdf353d
--- /dev/null
@@ -0,0 +1,31 @@
+<script>
+if (window.testRunner)
+    testRunner.dumpAsText();
+</script>
+<style>
+div { display: none; }
+@media (display-mode) {
+    .display-mode-null { display: inline; }
+}
+@media (display-mode: browser) {
+    .display-mode-browser { display: inline; }
+}
+@media (display-mode: minimal-ui) {
+    .display-mode-minimal-ui { display: inline; }
+}
+@media (display-mode: standalone) {
+    .display-mode-standalone { display: inline; }
+}
+@media (display-mode: fullscreen) {
+    .display-mode-fullscreen { display: inline; }
+}
+@media (display-mode: invalid-value) {
+    .display-mode-invalid-value { display: inline; }
+}
+</style>
+<div class="display-mode-null">(display-mode)</div>
+<div class="display-mode-browser">(display-mode: browser)</div>
+<div class="display-mode-minimal-ui">(display-mode: minimal-ui)</div>
+<div class="display-mode-standalone">(display-mode: standalone)</div>
+<div class="display-mode-fullscreen">(display-mode: fullscreen)</div>
+<div class="display-mode-invalid-value">(display-mode: invalid-value)</div>
\ No newline at end of file
diff --git a/LayoutTests/applicationmanifest/display-mode-subframe-expected.txt b/LayoutTests/applicationmanifest/display-mode-subframe-expected.txt
new file mode 100644 (file)
index 0000000..28e43ad
--- /dev/null
@@ -0,0 +1,6 @@
+
+
+--------
+Frame: '<!--framePath //<!--frame0-->-->'
+--------
+(display-mode) (display-mode: standalone)
diff --git a/LayoutTests/applicationmanifest/display-mode-subframe.html b/LayoutTests/applicationmanifest/display-mode-subframe.html
new file mode 100644 (file)
index 0000000..75e1ba2
--- /dev/null
@@ -0,0 +1,6 @@
+<!-- webkit-test-runner [ applicationManifest=resources/standalone.manifest ] -->
+<script>
+if (window.testRunner)
+       testRunner.dumpChildFramesAsText();
+</script>
+<iframe src="resources/display-mode-subframe-1.html"></iframe>
\ No newline at end of file
diff --git a/LayoutTests/applicationmanifest/display-mode.html b/LayoutTests/applicationmanifest/display-mode.html
new file mode 100644 (file)
index 0000000..4d6af53
--- /dev/null
@@ -0,0 +1,32 @@
+<!-- webkit-test-runner [ applicationManifest=resources/standalone.manifest ] -->
+<script>
+if (window.testRunner)
+    testRunner.dumpAsText();
+</script>
+<style>
+div { display: none; }
+@media (display-mode) {
+    .display-mode-null { display: inline; }
+}
+@media (display-mode: browser) {
+    .display-mode-browser { display: inline; }
+}
+@media (display-mode: minimal-ui) {
+    .display-mode-minimal-ui { display: inline; }
+}
+@media (display-mode: standalone) {
+    .display-mode-standalone { display: inline; }
+}
+@media (display-mode: fullscreen) {
+    .display-mode-fullscreen { display: inline; }
+}
+@media (display-mode: invalid-value) {
+    .display-mode-invalid-value { display: inline; }
+}
+</style>
+<div class="display-mode-null">(display-mode)</div>
+<div class="display-mode-browser">(display-mode: browser)</div>
+<div class="display-mode-minimal-ui">(display-mode: minimal-ui)</div>
+<div class="display-mode-standalone">(display-mode: standalone)</div>
+<div class="display-mode-fullscreen">(display-mode: fullscreen)</div>
+<div class="display-mode-invalid-value">(display-mode: invalid-value)</div>
\ No newline at end of file
diff --git a/LayoutTests/applicationmanifest/resources/display-mode-subframe-1.html b/LayoutTests/applicationmanifest/resources/display-mode-subframe-1.html
new file mode 100644 (file)
index 0000000..bbe35b4
--- /dev/null
@@ -0,0 +1,27 @@
+<style>
+div { display: none; }
+@media (display-mode) {
+    .display-mode-null { display: inline; }
+}
+@media (display-mode: browser) {
+    .display-mode-browser { display: inline; }
+}
+@media (display-mode: minimal-ui) {
+    .display-mode-minimal-ui { display: inline; }
+}
+@media (display-mode: standalone) {
+    .display-mode-standalone { display: inline; }
+}
+@media (display-mode: fullscreen) {
+    .display-mode-fullscreen { display: inline; }
+}
+@media (display-mode: invalid-value) {
+    .display-mode-invalid-value { display: inline; }
+}
+</style>
+<div class="display-mode-null">(display-mode)</div>
+<div class="display-mode-browser">(display-mode: browser)</div>
+<div class="display-mode-minimal-ui">(display-mode: minimal-ui)</div>
+<div class="display-mode-standalone">(display-mode: standalone)</div>
+<div class="display-mode-fullscreen">(display-mode: fullscreen)</div>
+<div class="display-mode-invalid-value">(display-mode: invalid-value)</div>
\ No newline at end of file
diff --git a/LayoutTests/applicationmanifest/resources/standalone.manifest b/LayoutTests/applicationmanifest/resources/standalone.manifest
new file mode 100644 (file)
index 0000000..de902f3
--- /dev/null
@@ -0,0 +1,3 @@
+{
+       "display": "standalone"
+}
\ No newline at end of file
index 9eb7162..70d5cf8 100644 (file)
@@ -1,3 +1,36 @@
+2017-12-07  David Quesada  <david_quesada@apple.com>
+
+        [Web App Manifest] Support display-mode media feature
+        https://bugs.webkit.org/show_bug.cgi?id=180376
+        rdar://problem/35837993
+
+        Reviewed by Geoffrey Garen.
+
+        Tests: applicationmanifest/display-mode-subframe.html
+               applicationmanifest/display-mode.html
+
+        * Modules/applicationmanifest/ApplicationManifest.h:
+        (WebCore::ApplicationManifest::encode const):
+        (WebCore::ApplicationManifest::decode):
+        * Modules/applicationmanifest/ApplicationManifestParser.cpp:
+        (WebCore::ApplicationManifestParser::parseManifest):
+        (WebCore::ApplicationManifestParser::parseDisplay):
+        * Modules/applicationmanifest/ApplicationManifestParser.h:
+            Add a 'display' property to ApplicationManifest and have ApplicationManifestParser
+            populate it by parsing the 'display' property from the raw manifest.
+
+        * css/CSSValueKeywords.in:
+        * css/MediaFeatureNames.h:
+        * css/MediaQueryEvaluator.cpp:
+        (WebCore::displayModeEvaluate):
+            To evaluate whether a display-mode query matches, compare the parameter to the
+            display mode of the manifest applied to the main frame. If there is no manifest,
+            use the fallback display mode 'browser' - the spec mandates that display-mode
+            must be exposed even if there is no applied manifest.
+        * css/MediaQueryExpression.cpp:
+        (WebCore::featureWithValidIdent):
+        (WebCore::isFeatureValidWithoutValue):
+
 2017-12-07  Jer Noble  <jer.noble@apple.com>
 
         Creating a second AVPlayerItemVideoOutput causes flakey failures
index c70e7e6..998b784 100644 (file)
 #if ENABLE(APPLICATION_MANIFEST)
 
 #include "URL.h"
+#include <wtf/EnumTraits.h>
 #include <wtf/Optional.h>
 
 namespace WebCore {
 
 struct ApplicationManifest {
+    enum class Display {
+        Browser,
+        MinimalUI,
+        Standalone,
+        Fullscreen,
+    };
+
     String name;
     String shortName;
     String description;
     URL scope;
+    Display display;
     URL startURL;
 
     template<class Encoder> void encode(Encoder&) const;
@@ -46,7 +55,7 @@ struct ApplicationManifest {
 template<class Encoder>
 void ApplicationManifest::encode(Encoder& encoder) const
 {
-    encoder << name << shortName << description << scope << startURL;
+    encoder << name << shortName << description << scope << display << startURL;
 }
 
 template<class Decoder>
@@ -62,6 +71,8 @@ std::optional<ApplicationManifest> ApplicationManifest::decode(Decoder& decoder)
         return std::nullopt;
     if (!decoder.decode(result.scope))
         return std::nullopt;
+    if (!decoder.decodeEnum(result.display))
+        return std::nullopt;
     if (!decoder.decode(result.startURL))
         return std::nullopt;
 
@@ -70,5 +81,19 @@ std::optional<ApplicationManifest> ApplicationManifest::decode(Decoder& decoder)
 
 } // namespace WebCore
 
+namespace WTF {
+
+template<> struct EnumTraits<WebCore::ApplicationManifest::Display> {
+    using values = EnumValues<
+        WebCore::ApplicationManifest::Display,
+        WebCore::ApplicationManifest::Display::Browser,
+        WebCore::ApplicationManifest::Display::MinimalUI,
+        WebCore::ApplicationManifest::Display::Standalone,
+        WebCore::ApplicationManifest::Display::Fullscreen
+    >;
+};
+
+} // namespace WTF;
+
 #endif // ENABLE(APPLICATION_MANIFEST)
 
index 0970a52..f22842b 100644 (file)
@@ -69,6 +69,7 @@ ApplicationManifest ApplicationManifestParser::parseManifest(const String& text,
     ApplicationManifest parsedManifest;
 
     parsedManifest.startURL = parseStartURL(*manifest, documentURL);
+    parsedManifest.display = parseDisplay(*manifest);
     parsedManifest.name = parseName(*manifest);
     parsedManifest.description = parseDescription(*manifest);
     parsedManifest.shortName = parseShortName(*manifest);
@@ -124,6 +125,33 @@ URL ApplicationManifestParser::parseStartURL(const JSON::Object& manifest, const
     return startURL;
 }
 
+ApplicationManifest::Display ApplicationManifestParser::parseDisplay(const JSON::Object& manifest)
+{
+    RefPtr<JSON::Value> value;
+    if (!manifest.getValue(ASCIILiteral("display"), value))
+        return ApplicationManifest::Display::Browser;
+
+    String stringValue;
+    if (!value->asString(stringValue)) {
+        logManifestPropertyNotAString(ASCIILiteral("display"));
+        return ApplicationManifest::Display::Browser;
+    }
+
+    stringValue = stringValue.stripWhiteSpace().convertToASCIILowercase();
+
+    if (stringValue == "fullscreen")
+        return ApplicationManifest::Display::Fullscreen;
+    if (stringValue == "standalone")
+        return ApplicationManifest::Display::Standalone;
+    if (stringValue == "minimal-ui")
+        return ApplicationManifest::Display::MinimalUI;
+    if (stringValue == "browser")
+        return ApplicationManifest::Display::Browser;
+
+    logDeveloperWarning(ASCIILiteral("\"") + stringValue + ASCIILiteral("\" is not a valid display mode."));
+    return ApplicationManifest::Display::Browser;
+}
+
 String ApplicationManifestParser::parseName(const JSON::Object& manifest)
 {
     return parseGenericString(manifest, ASCIILiteral("name"));
index 69a11dc..22540f7 100644 (file)
@@ -43,6 +43,7 @@ private:
     ApplicationManifest parseManifest(const String&, const URL&, const URL&);
 
     URL parseStartURL(const JSON::Object&, const URL&);
+    ApplicationManifest::Display parseDisplay(const JSON::Object&);
     String parseName(const JSON::Object&);
     String parseDescription(const JSON::Object&);
     String parseShortName(const JSON::Object&);
index 5a49fa6..7b2852a 100644 (file)
@@ -1346,3 +1346,10 @@ auto-fit
 swap
 fallback
 optional
+
+#if defined(ENABLE_APPLICATION_MANIFEST) && ENABLE_APPLICATION_MANIFEST
+fullscreen
+standalone
+minimal-ui
+browser
+#endif
index ff2bab8..21d8750 100644 (file)
 #include <wtf/NeverDestroyed.h>
 #include <wtf/text/AtomicString.h>
 
-#define CSS_MEDIAQUERY_VIEW_MODE(macro)
+#if ENABLE(APPLICATION_MANIFEST)
+#define CSS_MEDIAQUERY_DISPLAY_MODE(macro) macro(displayMode, "display-mode")
+#else
+#define CSS_MEDIAQUERY_DISPLAY_MODE(macro)
+#endif
 
 #define CSS_MEDIAQUERY_NAMES_FOR_EACH_MEDIAFEATURE(macro) \
     macro(animation, "-webkit-animation") \
@@ -73,7 +77,7 @@
     macro(transition, "-webkit-transition") \
     macro(videoPlayableInline, "-webkit-video-playable-inline") \
     macro(width, "width") \
-    CSS_MEDIAQUERY_VIEW_MODE(macro)
+    CSS_MEDIAQUERY_DISPLAY_MODE(macro) \
 
 // end of macro
 
index e6fda18..6fd024a 100644 (file)
@@ -734,6 +734,33 @@ static bool prefersReducedMotionEvaluate(CSSValue* value, const CSSToLengthConve
     return downcast<CSSPrimitiveValue>(*value).valueID() == (userPrefersReducedMotion ? CSSValueReduce : CSSValueNoPreference);
 }
 
+#if ENABLE(APPLICATION_MANIFEST)
+static bool displayModeEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame& frame, MediaFeaturePrefix)
+{
+    if (!value)
+        return true;
+
+    auto keyword = downcast<CSSPrimitiveValue>(*value).valueID();
+
+    auto manifest = frame.mainFrame().applicationManifest();
+    if (!manifest)
+        return keyword == CSSValueBrowser;
+
+    switch (manifest->display) {
+    case ApplicationManifest::Display::Fullscreen:
+        return keyword == CSSValueFullscreen;
+    case ApplicationManifest::Display::Standalone:
+        return keyword == CSSValueStandalone;
+    case ApplicationManifest::Display::MinimalUI:
+        return keyword == CSSValueMinimalUi;
+    case ApplicationManifest::Display::Browser:
+        return keyword == CSSValueBrowser;
+    }
+
+    return false;
+}
+#endif // ENABLE(APPLICATION_MANIFEST)
+
 // Use this function instead of calling add directly to avoid inlining.
 static void add(MediaQueryFunctionMap& map, AtomicStringImpl* key, MediaQueryFunction value)
 {
index ba7bafa..407852c 100644 (file)
@@ -46,6 +46,9 @@ static inline bool featureWithValidIdent(const AtomicString& mediaFeature)
     || mediaFeature == MediaFeatureNames::hover
     || mediaFeature == MediaFeatureNames::invertedColors
     || mediaFeature == MediaFeatureNames::pointer
+#if ENABLE(APPLICATION_MANIFEST)
+    || mediaFeature == MediaFeatureNames::displayMode
+#endif
     || mediaFeature == MediaFeatureNames::prefersReducedMotion;
 }
 
@@ -153,6 +156,9 @@ static inline bool isFeatureValidWithoutValue(const AtomicString& mediaFeature)
         || mediaFeature == MediaFeatureNames::prefersReducedMotion
         || mediaFeature == MediaFeatureNames::devicePixelRatio
         || mediaFeature == MediaFeatureNames::resolution
+#if ENABLE(APPLICATION_MANIFEST)
+        || mediaFeature == MediaFeatureNames::displayMode
+#endif
         || mediaFeature == MediaFeatureNames::videoPlayableInline;
 }
 
index 1c02abc..5cee2ed 100644 (file)
@@ -1,3 +1,17 @@
+2017-12-07  David Quesada  <david_quesada@apple.com>
+
+        [Web App Manifest] Support display-mode media feature
+        https://bugs.webkit.org/show_bug.cgi?id=180376
+        rdar://problem/35837993
+
+        Reviewed by Geoffrey Garen.
+
+        * UIProcess/API/Cocoa/_WKApplicationManifest.h:
+        * UIProcess/API/Cocoa/_WKApplicationManifest.mm:
+        (-[_WKApplicationManifest initWithCoder:]):
+        (-[_WKApplicationManifest encodeWithCoder:]):
+        (-[_WKApplicationManifest displayMode]):
+
 2017-12-07  Chris Dumez  <cdumez@apple.com>
 
         Unreviewed build fix after r225622.
index 61c50f2..587d0c5 100644 (file)
 
 NS_ASSUME_NONNULL_BEGIN
 
+typedef NS_ENUM(NSInteger, _WKApplicationManifestDisplayMode) {
+    _WKApplicationManifestDisplayModeBrowser,
+    _WKApplicationManifestDisplayModeMinimalUI,
+    _WKApplicationManifestDisplayModeStandalone,
+    _WKApplicationManifestDisplayModeFullScreen,
+} WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+
 WK_CLASS_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA))
 @interface _WKApplicationManifest : NSObject <NSCoding>
 
@@ -41,8 +48,9 @@ WK_CLASS_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA))
 @property (nonatomic, readonly, nullable, copy) NSString *applicationDescription;
 @property (nonatomic, readonly, nullable, copy) NSURL *scope;
 @property (nonatomic, readonly, copy) NSURL *startURL;
+@property (nonatomic, readonly) _WKApplicationManifestDisplayMode displayMode;
 
-+ (_WKApplicationManifest *)applicationManifestFromJSON:(NSString *)json manifestURL:(NSURL *)manifestURL documentURL:(nullable NSURL *)documentURL;
++ (_WKApplicationManifest *)applicationManifestFromJSON:(NSString *)json manifestURL:(nullable NSURL *)manifestURL documentURL:(nullable NSURL *)documentURL;
 
 @end
 
index 4f5e9a1..4a917c4 100644 (file)
@@ -41,6 +41,7 @@
     NSString *shortName = [aDecoder decodeObjectForKey:@"short_name"];
     NSString *description = [aDecoder decodeObjectForKey:@"description"];
     NSURL *scopeURL = [aDecoder decodeObjectForKey:@"scope"];
+    NSInteger display = [aDecoder decodeIntegerForKey:@"display"];
     NSURL *startURL = [aDecoder decodeObjectForKey:@"start_url"];
 
     WebCore::ApplicationManifest coreApplicationManifest {
@@ -48,6 +49,7 @@
         WTF::String(shortName),
         WTF::String(description),
         WebCore::URL(scopeURL),
+        static_cast<WebCore::ApplicationManifest::Display>(display),
         WebCore::URL(startURL)
     };
 
@@ -69,6 +71,7 @@
     [aCoder encodeObject:self.shortName forKey:@"short_name"];
     [aCoder encodeObject:self.applicationDescription forKey:@"description"];
     [aCoder encodeObject:self.scope forKey:@"scope"];
+    [aCoder encodeInteger:static_cast<NSInteger>(_applicationManifest->applicationManifest().display) forKey:@"display"];
     [aCoder encodeObject:self.startURL forKey:@"start_url"];
 }
 
@@ -113,6 +116,22 @@ static NSString *nullableNSString(const WTF::String& string)
     return _applicationManifest->applicationManifest().startURL;
 }
 
+- (_WKApplicationManifestDisplayMode)displayMode
+{
+    switch (_applicationManifest->applicationManifest().display) {
+    case WebCore::ApplicationManifest::Display::Browser:
+        return _WKApplicationManifestDisplayModeBrowser;
+    case WebCore::ApplicationManifest::Display::MinimalUI:
+        return _WKApplicationManifestDisplayModeMinimalUI;
+    case WebCore::ApplicationManifest::Display::Standalone:
+        return _WKApplicationManifestDisplayModeStandalone;
+    case WebCore::ApplicationManifest::Display::Fullscreen:
+        return _WKApplicationManifestDisplayModeFullScreen;
+    }
+
+    ASSERT_NOT_REACHED();
+}
+
 #else // ENABLE(APPLICATION_MANIFEST)
 
 + (_WKApplicationManifest *)applicationManifestFromJSON:(NSString *)json manifestURL:(NSURL *)manifestURL documentURL:(NSURL *)documentURL
@@ -160,6 +179,11 @@ static NSString *nullableNSString(const WTF::String& string)
     return nil;
 }
 
+- (_WKApplicationManifestDisplayMode)displayMode
+{
+    return _WKApplicationManifestDisplayModeBrowser;
+}
+
 #endif // ENABLE(APPLICATION_MANIFEST)
 
 @end
index c335c65..866074e 100644 (file)
@@ -1,3 +1,34 @@
+2017-12-07  David Quesada  <david_quesada@apple.com>
+
+        [Web App Manifest] Support display-mode media feature
+        https://bugs.webkit.org/show_bug.cgi?id=180376
+        rdar://problem/35837993
+
+        Reviewed by Geoffrey Garen.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebCore/ApplicationManifestParser.cpp:
+        (WebCore::operator<<):
+        (ApplicationManifestParserTest::testDisplay):
+        (TEST_F):
+            Add unit tests for the parsing of the 'display' manifest property.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/ApplicationManifest.mm:
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/Tests/WebKitCocoa/display-mode.html: Added.
+            Update API tests to include _WKApplicationManifest.displayMode.
+
+        * WebKitTestRunner/TestController.cpp:
+        (WTR::parseStringTestHeaderValueAsRelativePath):
+        (WTR::updateTestOptionsFromTestHeader):
+        * WebKitTestRunner/TestOptions.h:
+        (WTR::TestOptions::hasSameInitializationOptions const):
+        * WebKitTestRunner/cocoa/TestControllerCocoa.mm:
+        (WTR::TestController::platformCreateWebView):
+            Teach WKTR to look for and apply an app manifest. A new <!--webkit-test-runner-->
+            directive 'applicationManifest' can specify a path (relative to the test file itself)
+            to a JSON app manifest to be applied to the web view before running the test.
+
 2017-12-07  Eric Carlson  <eric.carlson@apple.com>
 
         Add WebRTC watchlist rule, update MediaStream rule
index e918758..60f1c23 100644 (file)
                6354F4D11F7C3AB500D89DF3 /* ApplicationManifestParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6354F4D01F7C3AB500D89DF3 /* ApplicationManifestParser.cpp */; };
                6356FB221EC4E0BA0044BF18 /* VisibleContentRect.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6356FB211EC4E0BA0044BF18 /* VisibleContentRect.mm */; };
                636353A71E98665D0009F8AF /* GeolocationGetCurrentPositionResult.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 636353A61E9861940009F8AF /* GeolocationGetCurrentPositionResult.html */; };
+               63A61B8B1FAD251100F06885 /* display-mode.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 63A61B8A1FAD204D00F06885 /* display-mode.html */; };
                63F668221F97F7F90032EE51 /* ApplicationManifest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 63F668201F97C3AA0032EE51 /* ApplicationManifest.mm */; };
                6BFD294C1D5E6C1D008EC968 /* HashCountedSet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A38D7E51C752D5F004F157D /* HashCountedSet.cpp */; };
                751B05D61F8EAC410028A09E /* DatabaseTrackerTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 751B05D51F8EAC1A0028A09E /* DatabaseTrackerTest.mm */; };
                                F4512E131F60C44600BB369E /* DataTransferItem-getAsEntry.html in Copy Resources */,
                                C07E6CB213FD73930038B22B /* devicePixelRatio.html in Copy Resources */,
                                0799C34B1EBA3301003B7532 /* disableGetUserMedia.html in Copy Resources */,
+                               63A61B8B1FAD251100F06885 /* display-mode.html in Copy Resources */,
                                F41AB9A21EF4696B0083FA08 /* div-and-large-image.html in Copy Resources */,
                                37E1064C1697681800B78BD0 /* DOMHTMLTableCellElementCellAbove.html in Copy Resources */,
                                37DC6791140D7D7600ABCCDB /* DOMRangeOfString.html in Copy Resources */,
                6354F4D01F7C3AB500D89DF3 /* ApplicationManifestParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ApplicationManifestParser.cpp; sourceTree = "<group>"; };
                6356FB211EC4E0BA0044BF18 /* VisibleContentRect.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = VisibleContentRect.mm; sourceTree = "<group>"; };
                636353A61E9861940009F8AF /* GeolocationGetCurrentPositionResult.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = GeolocationGetCurrentPositionResult.html; sourceTree = "<group>"; };
+               63A61B8A1FAD204D00F06885 /* display-mode.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "display-mode.html"; sourceTree = "<group>"; };
                63F668201F97C3AA0032EE51 /* ApplicationManifest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ApplicationManifest.mm; sourceTree = "<group>"; };
                751B05D51F8EAC1A0028A09E /* DatabaseTrackerTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = DatabaseTrackerTest.mm; sourceTree = "<group>"; };
                754CEC801F6722DC00D0039A /* AutoFillAvailable.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AutoFillAvailable.mm; sourceTree = "<group>"; };
                                F486B1CF1F6794FF00F34BDD /* DataTransfer-setDragImage.html */,
                                F4512E121F60C43400BB369E /* DataTransferItem-getAsEntry.html */,
                                0799C34A1EBA32F4003B7532 /* disableGetUserMedia.html */,
+                               63A61B8A1FAD204D00F06885 /* display-mode.html */,
                                F41AB99E1EF4692C0083FA08 /* div-and-large-image.html */,
                                837A35F01D9A1E6400663C57 /* DownloadRequestBlobURL.html */,
                                5714ECB81CA8B58800051AC8 /* DownloadRequestOriginalURL.html */,
index 749b33e..39de858 100644 (file)
 using namespace WTF;
 using namespace WebCore;
 
+namespace WebCore {
+static inline std::ostream& operator<<(std::ostream& os, const ApplicationManifest::Display& display)
+{
+    switch (display) {
+    case ApplicationManifest::Display::Browser:
+        return os << "ApplicationManifest::Display::Browser";
+    case ApplicationManifest::Display::MinimalUI:
+        return os << "ApplicationManifest::Display::MinimalUI";
+    case ApplicationManifest::Display::Standalone:
+        return os << "ApplicationManifest::Display::Standalone";
+    case ApplicationManifest::Display::Fullscreen:
+        return os << "ApplicationManifest::Display::Fullscreen";
+    }
+}
+} // namespace WebCore
+
 class ApplicationManifestParserTest : public testing::Test {
 public:
     URL m_manifestURL;
@@ -71,6 +87,13 @@ public:
         EXPECT_STREQ(expectedValue.string().utf8().data(), value.string().utf8().data());
     }
 
+    void testDisplay(const String& rawJSON, ApplicationManifest::Display expectedValue)
+    {
+        auto manifest = parseTopLevelProperty("display", rawJSON);
+        auto value = manifest.display;
+        EXPECT_EQ(expectedValue, value);
+    }
+
     void testName(const String& rawJSON, const String& expectedValue)
     {
         auto manifest = parseTopLevelProperty("name", rawJSON);
@@ -169,6 +192,24 @@ TEST_F(ApplicationManifestParserTest, StartURL)
     testStartURL("\"../page2\"", "https://example.com/page2");
 }
 
+TEST_F(ApplicationManifestParserTest, Display)
+{
+    testDisplay("123", ApplicationManifest::Display::Browser);
+    testDisplay("null", ApplicationManifest::Display::Browser);
+    testDisplay("true", ApplicationManifest::Display::Browser);
+    testDisplay("{ }", ApplicationManifest::Display::Browser);
+    testDisplay("[ ]", ApplicationManifest::Display::Browser);
+    testDisplay("\"\"", ApplicationManifest::Display::Browser);
+    testDisplay("\"garbage string\"", ApplicationManifest::Display::Browser);
+
+    testDisplay("\"browser\"", ApplicationManifest::Display::Browser);
+    testDisplay("\"standalone\"", ApplicationManifest::Display::Standalone);
+    testDisplay("\"minimal-ui\"", ApplicationManifest::Display::MinimalUI);
+    testDisplay("\"fullscreen\"", ApplicationManifest::Display::Fullscreen);
+
+    testDisplay("[\"\\tMINIMAL-ui\\t\"]", ApplicationManifest::Display::MinimalUI);
+}
+
 TEST_F(ApplicationManifestParserTest, Name)
 {
     testName("123", String());
index 710a77a..9bc154e 100644 (file)
@@ -31,6 +31,7 @@
 #import "Test.h"
 #import "TestNavigationDelegate.h"
 #import "TestWKWebView.h"
+#import <WebKit/WKWebViewConfigurationPrivate.h>
 #import <WebKit/WKWebViewPrivate.h>
 #import <WebKit/_WKApplicationManifest.h>
 
@@ -38,7 +39,7 @@ namespace TestWebKitAPI {
 
 TEST(WebKit, ApplicationManifestCoding)
 {
-    auto jsonString = @"{ \"name\": \"TestName\", \"short_name\": \"TestShortName\", \"description\": \"TestDescription\", \"scope\": \"https://test.com/app\", \"start_url\": \"https://test.com/app/index.html\" }";
+    auto jsonString = @"{ \"name\": \"TestName\", \"short_name\": \"TestShortName\", \"description\": \"TestDescription\", \"scope\": \"https://test.com/app\", \"start_url\": \"https://test.com/app/index.html\", \"display\": \"minimal-ui\" }";
     RetainPtr<_WKApplicationManifest> manifest { [_WKApplicationManifest applicationManifestFromJSON:jsonString manifestURL:[NSURL URLWithString:@"https://test.com/manifest.json"] documentURL:[NSURL URLWithString:@"https://test.com/"]] };
     
     manifest = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:manifest.get()]];
@@ -49,6 +50,7 @@ TEST(WebKit, ApplicationManifestCoding)
     EXPECT_STREQ("TestDescription", manifest.get().applicationDescription.UTF8String);
     EXPECT_STREQ("https://test.com/app", manifest.get().scope.absoluteString.UTF8String);
     EXPECT_STREQ("https://test.com/app/index.html", manifest.get().startURL.absoluteString.UTF8String);
+    EXPECT_EQ(_WKApplicationManifestDisplayModeMinimalUI,  manifest.get().displayMode);
 }
 
 TEST(WebKit, ApplicationManifestBasic)
@@ -102,6 +104,38 @@ TEST(WebKit, ApplicationManifestBasic)
     Util::run(&done);
 }
 
+TEST(WebKit, ApplicationManifestDisplayMode)
+{
+    static bool done;
+    NSDictionary *displayModesAndExpectedContent = @{
+        @"": @"(display-mode) (display-mode: browser)",
+        @"browser": @"(display-mode) (display-mode: browser)",
+        @"minimal-ui": @"(display-mode) (display-mode: minimal-ui)",
+        @"standalone": @"(display-mode) (display-mode: standalone)",
+        @"fullscreen": @"(display-mode) (display-mode: fullscreen)",
+    };
+
+    NSURL *baseURL = [[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:@"TestWebKitAPI.resources"];
+    [displayModesAndExpectedContent enumerateKeysAndObjectsUsingBlock:^(NSString *displayMode, NSString *expectedPageContent, BOOL* stop) {
+        @autoreleasepool {
+            NSString *m2 = displayMode.length ? [NSString stringWithFormat:@"{\"display\": \"%@\"}", displayMode] : @"{}";
+            RetainPtr<_WKApplicationManifest> manifest = [_WKApplicationManifest applicationManifestFromJSON:m2 manifestURL:[baseURL URLByAppendingPathComponent:@"manifest.json"] documentURL:baseURL];
+            auto config = adoptNS([[WKWebViewConfiguration alloc] init]);
+            config.get()._applicationManifest = manifest.get();
+            auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:config.get()]);
+            [webView synchronouslyLoadTestPageNamed:@"display-mode"];
+            
+            done = false;
+            [webView _getContentsAsStringWithCompletionHandler:^(NSString *contents, NSError *error) {
+                done = true;
+                EXPECT_STREQ(expectedPageContent.UTF8String, contents.UTF8String);
+            }];
+            Util::run(&done);
+            [webView removeFromSuperview];
+        }
+    }];
+}
+
 } // namespace TestWebKitAPI
 
 #endif // WK_API_ENABLED && ENABLE(APPLICATION_MANIFEST)
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/display-mode.html b/Tools/TestWebKitAPI/Tests/WebKitCocoa/display-mode.html
new file mode 100644 (file)
index 0000000..8d651ec
--- /dev/null
@@ -0,0 +1,27 @@
+<style>
+div { display: none; }
+@media (display-mode) {
+    .display-mode-null { display: inline; }
+}
+@media (display-mode: browser) {
+    .display-mode-browser { display: inline; }
+}
+@media (display-mode: minimal-ui) {
+    .display-mode-minimal-ui { display: inline; }
+}
+@media (display-mode: standalone) {
+    .display-mode-standalone { display: inline; }
+}
+@media (display-mode: fullscreen) {
+    .display-mode-fullscreen { display: inline; }
+}
+@media (display-mode: invalid-value) {
+    .display-mode-invalid-value { display: inline; }
+}
+</style>
+<div class="display-mode-null">(display-mode)</div>
+<div class="display-mode-browser">(display-mode: browser)</div>
+<div class="display-mode-minimal-ui">(display-mode: minimal-ui)</div>
+<div class="display-mode-standalone">(display-mode: standalone)</div>
+<div class="display-mode-fullscreen">(display-mode: fullscreen)</div>
+<div class="display-mode-invalid-value">(display-mode: invalid-value)</div>
index 34bae26..38fab6f 100644 (file)
@@ -982,6 +982,13 @@ static bool parseBooleanTestHeaderValue(const std::string& value)
     return false;
 }
 
+static std::string parseStringTestHeaderValueAsRelativePath(const std::string& value, const std::string& pathOrURL)
+{
+    WKRetainPtr<WKURLRef> baseURL(AdoptWK, createTestURL(pathOrURL.c_str()));
+    WKRetainPtr<WKURLRef> relativeURL(AdoptWK, WKURLCreateWithBaseURL(baseURL.get(), value.c_str()));
+    return toSTD(adoptWK(WKURLCopyPath(relativeURL.get())));
+}
+
 static void updateTestOptionsFromTestHeader(TestOptions& testOptions, const std::string& pathOrURL, const std::string& absolutePath)
 {
     std::string filename = absolutePath;
@@ -1058,6 +1065,8 @@ static void updateTestOptionsFromTestHeader(TestOptions& testOptions, const std:
             testOptions.enableInspectorAdditions = parseBooleanTestHeaderValue(value);
         if (key == "dumpJSConsoleLogInStdErr")
             testOptions.dumpJSConsoleLogInStdErr = parseBooleanTestHeaderValue(value);
+        if (key == "applicationManifest")
+            testOptions.applicationManifest = parseStringTestHeaderValueAsRelativePath(value, pathOrURL);
         pairStart = pairEnd + 1;
     }
 }
index 96bd7cc..011f49d 100644 (file)
@@ -57,6 +57,7 @@ struct TestOptions {
 
     float deviceScaleFactor { 1 };
     Vector<String> overrideLanguages;
+    std::string applicationManifest;
     
     TestOptions(const std::string& pathOrURL);
 
@@ -80,7 +81,8 @@ struct TestOptions {
             || enableCredentialManagement != options.enableCredentialManagement
             || enableIsSecureContextAttribute != options.enableIsSecureContextAttribute
             || enableInspectorAdditions != options.enableInspectorAdditions
-            || dumpJSConsoleLogInStdErr != options.dumpJSConsoleLogInStdErr)
+            || dumpJSConsoleLogInStdErr != options.dumpJSConsoleLogInStdErr
+            || applicationManifest != options.applicationManifest)
             return false;
 
         return true;
index d8c6c09..88182b0 100644 (file)
@@ -45,6 +45,7 @@
 #import <WebKit/WKWebsiteDataRecordPrivate.h>
 #import <WebKit/WKWebsiteDataStorePrivate.h>
 #import <WebKit/WKWebsiteDataStoreRef.h>
+#import <WebKit/_WKApplicationManifest.h>
 #import <WebKit/_WKProcessPoolConfiguration.h>
 #import <WebKit/_WKUserContentExtensionStore.h>
 #import <WebKit/_WKUserContentExtensionStorePrivate.h>
@@ -153,6 +154,12 @@ void TestController::platformCreateWebView(WKPageConfigurationRef, const TestOpt
     if (options.enableAttachmentElement)
         [copiedConfiguration _setAttachmentElementEnabled: YES];
 
+    if (options.applicationManifest.length()) {
+        auto manifestPath = [NSString stringWithUTF8String:options.applicationManifest.c_str()];
+        NSString *text = [NSString stringWithContentsOfFile:manifestPath usedEncoding:nullptr error:nullptr];
+        [copiedConfiguration _setApplicationManifest:[_WKApplicationManifest applicationManifestFromJSON:text manifestURL:nil documentURL:nil]];
+    }
+
     m_mainWebView = std::make_unique<PlatformWebView>(copiedConfiguration.get(), options);
 #else
     m_mainWebView = std::make_unique<PlatformWebView>(globalWebViewConfiguration, options);