Web Inspector: Add support for forcing color scheme appearance in DOM tree.
authortimothy@apple.com <timothy@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 27 Nov 2018 21:16:07 +0000 (21:16 +0000)
committertimothy@apple.com <timothy@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 27 Nov 2018 21:16:07 +0000 (21:16 +0000)
https://bugs.webkit.org/show_bug.cgi?id=191820
rdar://problem/46153172

Reviewed by Devin Rousso.

Source/JavaScriptCore:

* inspector/protocol/Page.json: Added setForcedAppearance.
Also added the defaultAppearanceDidChange event and Appearance enum.

Source/WebCore:

Test: inspector/css/force-page-appearance.html

* inspector/InspectorInstrumentation.cpp:
(WebCore::InspectorInstrumentation::defaultAppearanceDidChangeImpl):
* inspector/InspectorInstrumentation.h:
(WebCore::InspectorInstrumentation::defaultAppearanceDidChange):
* inspector/agents/InspectorPageAgent.cpp:
(WebCore::InspectorPageAgent::enable): Fire defaultAppearanceDidChange() on macOS Majave.
(WebCore::InspectorPageAgent::disable): Call setForcedAppearance() with empty string.
(WebCore::InspectorPageAgent::defaultAppearanceDidChange): Added.
(WebCore::InspectorPageAgent::setForcedAppearance): Added.
* inspector/agents/InspectorPageAgent.h:
* page/Page.cpp:
(WebCore::Page::setUseDarkAppearance): Call InspectorInstrumentation::defaultAppearanceDidChange().
(WebCore::Page::useDarkAppearance const): Return override value if not nullopt.
(WebCore::Page::setUseDarkAppearanceOverride): Added.
* page/Page.h:
(WebCore::Page::defaultUseDarkAppearance const): Added.

Source/WebInspectorUI:

* Localizations/en.lproj/localizedStrings.js: Updated.
* UserInterface/Controllers/CSSManager.js:
(WI.CSSManager):
(WI.CSSManager.prototype.get defaultAppearance): Added.
(WI.CSSManager.prototype.get forcedAppearance): Added.
(WI.CSSManager.prototype.set forcedAppearance): Added.
(WI.CSSManager.prototype.canForceAppearance): Added.
(WI.CSSManager.prototype.defaultAppearanceDidChange): Added.
* UserInterface/Images/Appearance.svg: Added.
* UserInterface/Protocol/PageObserver.js:
(WI.PageObserver.prototype.defaultAppearanceChanged): Added.
* UserInterface/Views/DOMTreeContentView.js:
(WI.DOMTreeContentView):
(WI.DOMTreeContentView.prototype.get navigationItems):
(WI.DOMTreeContentView.prototype._defaultAppearanceDidChange): Added.
(WI.DOMTreeContentView.prototype._toggleAppearance): Added.

LayoutTests:

* TestExpectations: Skip dark mode tests on other platforms.
* inspector/css/force-page-appearance-expected.txt: Added.
* inspector/css/force-page-appearance.html: Added.
* platform/mac/TestExpectations: Expect dark mode tests to pass on Mojave and later.

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

20 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/inspector/css/force-page-appearance-expected.txt [new file with mode: 0644]
LayoutTests/inspector/css/force-page-appearance.html [new file with mode: 0644]
LayoutTests/platform/mac/TestExpectations
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/protocol/Page.json
Source/WebCore/ChangeLog
Source/WebCore/inspector/InspectorInstrumentation.cpp
Source/WebCore/inspector/InspectorInstrumentation.h
Source/WebCore/inspector/agents/InspectorPageAgent.cpp
Source/WebCore/inspector/agents/InspectorPageAgent.h
Source/WebCore/page/Page.cpp
Source/WebCore/page/Page.h
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Controllers/CSSManager.js
Source/WebInspectorUI/UserInterface/Images/Appearance.svg [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Protocol/PageObserver.js
Source/WebInspectorUI/UserInterface/Views/DOMTreeContentView.js

index 48e1deb..49ab806 100644 (file)
@@ -1,3 +1,16 @@
+2018-11-27  Timothy Hatcher  <timothy@apple.com>
+
+        Web Inspector: Add support for forcing color scheme appearance in DOM tree.
+        https://bugs.webkit.org/show_bug.cgi?id=191820
+        rdar://problem/46153172
+
+        Reviewed by Devin Rousso.
+
+        * TestExpectations: Skip dark mode tests on other platforms.
+        * inspector/css/force-page-appearance-expected.txt: Added.
+        * inspector/css/force-page-appearance.html: Added.
+        * platform/mac/TestExpectations: Expect dark mode tests to pass on Mojave and later.
+
 2018-11-27  Tim Horton  <timothy_horton@apple.com>
 
         Serialize and deserialize editable image strokes
index 53b7883..826cfe4 100644 (file)
@@ -74,7 +74,9 @@ fast/images/eps-as-image.html [ Skip ]
 fast/xmlhttprequest/set-dangerous-headers-in-dashboard.html [ WontFix ]
 
 # Only applicable on macOS
+css-dark-mode [ Skip ]
 fast/css/apple-system-control-colors.html [ Skip ]
+inspector/css/force-page-appearance.html [ Skip ]
 
 # Only Mac supports force tests.
 fast/events/cancelled-force-click-link-navigation.html [ Skip ]
diff --git a/LayoutTests/inspector/css/force-page-appearance-expected.txt b/LayoutTests/inspector/css/force-page-appearance-expected.txt
new file mode 100644 (file)
index 0000000..ee74295
--- /dev/null
@@ -0,0 +1,38 @@
+Testing the default appearance and forced appearance features.
+
+
+== Running test suite: ForcePageAppearance
+-- Running test case: Default appearance should be light
+PASS: WI.cssManager.defaultAppearance should be Light.
+PASS: WI.cssManager.forcedAppearance should be null.
+PASS: expectEqual("150px", "150px")
+PASS: expectEqual("rgb(0, 0, 0)", "rgb(0, 0, 0)")
+
+-- Running test case: Force appearance to Dark
+PASS: WI.cssManager.defaultAppearance should be Light.
+PASS: WI.cssManager.forcedAppearance should be Dark.
+PASS: DOMNodeStyles should need refresh.
+PASS: expectEqual("200px", "200px")
+PASS: expectEqual("rgb(255, 255, 255)", "rgb(255, 255, 255)")
+
+-- Running test case: Switch to Dark appearance by default
+PASS: WI.cssManager.defaultAppearance should be Dark.
+PASS: WI.cssManager.forcedAppearance should be Dark.
+PASS: DOMNodeStyles should need refresh.
+PASS: expectEqual("200px", "200px")
+PASS: expectEqual("rgb(255, 255, 255)", "rgb(255, 255, 255)")
+
+-- Running test case: Force appearance to Light
+PASS: WI.cssManager.defaultAppearance should be Dark.
+PASS: WI.cssManager.forcedAppearance should be Light.
+PASS: DOMNodeStyles should need refresh.
+PASS: expectEqual("150px", "150px")
+PASS: expectEqual("rgb(0, 0, 0)", "rgb(0, 0, 0)")
+
+-- Running test case: Disable forced appearance
+PASS: WI.cssManager.defaultAppearance should be Dark.
+PASS: WI.cssManager.forcedAppearance should be null.
+PASS: DOMNodeStyles should need refresh.
+PASS: expectEqual("200px", "200px")
+PASS: expectEqual("rgb(255, 255, 255)", "rgb(255, 255, 255)")
+
diff --git a/LayoutTests/inspector/css/force-page-appearance.html b/LayoutTests/inspector/css/force-page-appearance.html
new file mode 100644 (file)
index 0000000..178b4c0
--- /dev/null
@@ -0,0 +1,166 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function switchToDarkAppearance() {
+    if (window.internals)
+        internals.settings.setUseDarkAppearance(true);
+}
+
+function test() {
+    let nodeStyles = null;
+    let suite = InspectorTest.createAsyncSuite("ForcePageAppearance");
+
+    let getProperty = (propertyName) => {
+        let styleDeclaration = nodeStyles.computedStyle;
+        for (let property of styleDeclaration.properties) {
+            if (property.name === propertyName)
+                return property;
+        }
+    };
+
+    suite.addTestCase({
+        name: "Default appearance should be light",
+        test(resolve, reject) {
+            InspectorTest.expectEqual(WI.cssManager.defaultAppearance, WI.CSSManager.Appearance.Light, `WI.cssManager.defaultAppearance should be Light.`);
+            InspectorTest.expectNull(WI.cssManager.forcedAppearance, `WI.cssManager.forcedAppearance should be null.`);
+
+            InspectorTest.expectEqual(getProperty("width").rawValue, "150px");
+            InspectorTest.expectEqual(getProperty("color").rawValue, "rgb(0, 0, 0)");
+
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "Force appearance to Dark",
+        test(resolve, reject) {
+            // Styles should referesh when dark apeparance is forced.
+            WI.cssManager.awaitEvent(WI.CSSManager.Event.ForcedAppearanceDidChange).then((event) => {
+                InspectorTest.expectEqual(nodeStyles.needsRefresh, true, `DOMNodeStyles should need refresh.`);
+                nodeStyles.refresh();
+
+                return nodeStyles.computedStyle.awaitEvent(WI.CSSStyleDeclaration.Event.PropertiesChanged).then((event) => {
+                    InspectorTest.expectEqual(getProperty("width").rawValue, "200px");
+                    InspectorTest.expectEqual(getProperty("color").rawValue, "rgb(255, 255, 255)");
+                });
+            }).then(resolve, reject);
+
+            WI.cssManager.forcedAppearance = WI.CSSManager.Appearance.Dark;
+
+            InspectorTest.expectEqual(WI.cssManager.defaultAppearance, WI.CSSManager.Appearance.Light, `WI.cssManager.defaultAppearance should be Light.`);
+            InspectorTest.expectEqual(WI.cssManager.forcedAppearance, WI.CSSManager.Appearance.Dark, `WI.cssManager.forcedAppearance should be Dark.`);
+        }
+    });
+
+    suite.addTestCase({
+        name: "Switch to Dark appearance by default",
+        test(resolve, reject) {
+            InspectorTest.evaluateInPage(`switchToDarkAppearance()`);
+
+            WI.cssManager.awaitEvent(WI.CSSManager.Event.DefaultAppearanceDidChange).then((event) => {
+                InspectorTest.expectEqual(WI.cssManager.defaultAppearance, WI.CSSManager.Appearance.Dark, `WI.cssManager.defaultAppearance should be Dark.`);
+                InspectorTest.expectEqual(WI.cssManager.forcedAppearance, WI.CSSManager.Appearance.Dark, `WI.cssManager.forcedAppearance should be Dark.`);
+                InspectorTest.expectEqual(nodeStyles.needsRefresh, true, `DOMNodeStyles should need refresh.`);
+                nodeStyles.refresh();
+
+                return nodeStyles.computedStyle.awaitEvent(WI.CSSStyleDeclaration.Event.PropertiesChanged).then((event) => {
+                    InspectorTest.expectEqual(getProperty("width").rawValue, "200px");
+                    InspectorTest.expectEqual(getProperty("color").rawValue, "rgb(255, 255, 255)");
+                });
+            }).then(resolve, reject);
+        }
+    });
+
+    suite.addTestCase({
+        name: "Force appearance to Light",
+        test(resolve, reject) {
+            // Styles should referesh when light apeparance is forced.
+            WI.cssManager.awaitEvent(WI.CSSManager.Event.ForcedAppearanceDidChange).then((event) => {
+                InspectorTest.expectEqual(nodeStyles.needsRefresh, true, `DOMNodeStyles should need refresh.`);
+                nodeStyles.refresh();
+
+                return nodeStyles.computedStyle.awaitEvent(WI.CSSStyleDeclaration.Event.PropertiesChanged).then((event) => {
+                    InspectorTest.expectEqual(getProperty("width").rawValue, "150px");
+                    InspectorTest.expectEqual(getProperty("color").rawValue, "rgb(0, 0, 0)");
+                });
+            }).then(resolve, reject);
+
+            WI.cssManager.forcedAppearance = WI.CSSManager.Appearance.Light;
+
+            InspectorTest.expectEqual(WI.cssManager.defaultAppearance, WI.CSSManager.Appearance.Dark, `WI.cssManager.defaultAppearance should be Dark.`);
+            InspectorTest.expectEqual(WI.cssManager.forcedAppearance, WI.CSSManager.Appearance.Light, `WI.cssManager.forcedAppearance should be Light.`);
+        }
+    });
+
+    suite.addTestCase({
+        name: "Disable forced appearance",
+        test(resolve, reject) {
+            // Styles should referesh when forced apeparance is disabled.
+            WI.cssManager.awaitEvent(WI.CSSManager.Event.ForcedAppearanceDidChange).then((event) => {
+                InspectorTest.expectEqual(nodeStyles.needsRefresh, true, `DOMNodeStyles should need refresh.`);
+                nodeStyles.refresh();
+
+                return nodeStyles.computedStyle.awaitEvent(WI.CSSStyleDeclaration.Event.PropertiesChanged).then((event) => {
+                    InspectorTest.expectEqual(getProperty("width").rawValue, "200px");
+                    InspectorTest.expectEqual(getProperty("color").rawValue, "rgb(255, 255, 255)");
+                });
+            }).then(resolve, reject);
+
+            WI.cssManager.forcedAppearance = null;
+
+            InspectorTest.expectEqual(WI.cssManager.defaultAppearance, WI.CSSManager.Appearance.Dark, `WI.cssManager.defaultAppearance should be Dark.`);
+            InspectorTest.expectNull(WI.cssManager.forcedAppearance, `WI.cssManager.forcedAppearance should be null.`);
+        }
+    });
+
+    WI.domManager.requestDocument((documentNode) => {
+        WI.domManager.querySelector(documentNode.id, "#x", (contentNodeId) => {
+            if (contentNodeId) {
+                let domNode = WI.domManager.nodeForId(contentNodeId);
+                nodeStyles = WI.cssManager.stylesForNode(domNode);
+
+                if (nodeStyles.needsRefresh) {
+                    nodeStyles.singleFireEventListener(WI.DOMNodeStyles.Event.Refreshed, (event) => {
+                        suite.runTestCasesAndFinish()
+                    });
+                } else
+                    suite.runTestCasesAndFinish();
+            } else {
+                InspectorTest.fail("DOM node not found.");
+                InspectorTest.completeTest();
+            }
+        });
+    });
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Testing the default appearance and forced appearance features.</p>
+
+    <style>
+    :root {
+        supported-color-schemes: light dark;
+    }
+
+    .test-node {
+        width: 100px;
+    }
+
+    @media (prefers-color-scheme: light) {
+        .test-node {
+            width: 150px;
+        }
+    }
+
+    @media (prefers-color-scheme: dark) {
+        .test-node {
+            width: 200px;
+        }
+    }
+    </style>
+
+    <div id="x" class="test-node"></div>
+</body>
+</html>
index 7453086..2bd2806 100644 (file)
@@ -1768,7 +1768,8 @@ webkit.org/b/187393 imported/w3c/web-platform-tests/2dcontext/imagebitmap/create
 [ Mojave+ ] fast/gradients/conic-two-hints.html [ Pass ]
 
 # Dark Mode is Mojave and later.
-[ Sierra HighSierra ] css-dark-mode [ Skip ]
+[ Mojave+ ] css-dark-mode [ Pass ]
+[ Mojave+ ] inspector/css/force-page-appearance.html [ Pass ]
 
 webkit.org/b/185651 legacy-animation-engine/animations/play-state-in-shorthand.html [ Pass Failure ]
 
index e080314..2d49578 100644 (file)
@@ -1,3 +1,14 @@
+2018-11-27  Timothy Hatcher  <timothy@apple.com>
+
+        Web Inspector: Add support for forcing color scheme appearance in DOM tree.
+        https://bugs.webkit.org/show_bug.cgi?id=191820
+        rdar://problem/46153172
+
+        Reviewed by Devin Rousso.
+
+        * inspector/protocol/Page.json: Added setForcedAppearance.
+        Also added the defaultAppearanceDidChange event and Appearance enum.
+
 2018-11-27  Ryan Haddad  <ryanhaddad@apple.com>
 
         Unreviewed, rolling out r238509.
index 2aa488e..b94a5f9 100644 (file)
             "description": "Same-Site policy of a cookie."
         },
         {
+            "id": "Appearance",
+            "type": "string",
+            "enum": ["Light", "Dark"],
+            "description": "Page appearance name."
+        },
+        {
             "id": "Frame",
             "type": "object",
             "description": "Information about the Frame on the page.",
             ]
         },
         {
+            "name": "setForcedAppearance",
+            "description": "Forces the given appearance for the page.",
+            "parameters": [
+                { "name": "appearance", "$ref": "Appearance", "description": "Appearance name to force. Empty string disables the override." }
+            ]
+        },
+        {
             "name": "getCompositingBordersVisible",
             "description": "Indicates the visibility of compositing borders.",
             "returns": [
             "parameters": [
                 { "name": "frameId", "$ref": "Network.FrameId", "description": "Id of the frame that has cleared its scheduled navigation." }
             ]
+        },
+        {
+            "name": "defaultAppearanceDidChange",
+            "description": "Fired when page's default appearance changes, even if there is a forced appearance.",
+            "parameters": [
+                { "name": "appearance", "$ref": "Appearance", "description": "Name of the appearance that is active (not considering any forced appearance.)" }
+            ]
         }
     ]
 }
index cef83dd..a343069 100644 (file)
@@ -1,3 +1,30 @@
+2018-11-27  Timothy Hatcher  <timothy@apple.com>
+
+        Web Inspector: Add support for forcing color scheme appearance in DOM tree.
+        https://bugs.webkit.org/show_bug.cgi?id=191820
+        rdar://problem/46153172
+
+        Reviewed by Devin Rousso.
+
+        Test: inspector/css/force-page-appearance.html
+
+        * inspector/InspectorInstrumentation.cpp:
+        (WebCore::InspectorInstrumentation::defaultAppearanceDidChangeImpl):
+        * inspector/InspectorInstrumentation.h:
+        (WebCore::InspectorInstrumentation::defaultAppearanceDidChange):
+        * inspector/agents/InspectorPageAgent.cpp:
+        (WebCore::InspectorPageAgent::enable): Fire defaultAppearanceDidChange() on macOS Majave.
+        (WebCore::InspectorPageAgent::disable): Call setForcedAppearance() with empty string.
+        (WebCore::InspectorPageAgent::defaultAppearanceDidChange): Added.
+        (WebCore::InspectorPageAgent::setForcedAppearance): Added.
+        * inspector/agents/InspectorPageAgent.h:
+        * page/Page.cpp:
+        (WebCore::Page::setUseDarkAppearance): Call InspectorInstrumentation::defaultAppearanceDidChange().
+        (WebCore::Page::useDarkAppearance const): Return override value if not nullopt.
+        (WebCore::Page::setUseDarkAppearanceOverride): Added.
+        * page/Page.h:
+        (WebCore::Page::defaultUseDarkAppearance const): Added.
+
 2018-11-27  Tim Horton  <timothy_horton@apple.com>
 
         Serialize and deserialize editable image strokes
index 1eb72cf..59a8b08 100644 (file)
@@ -782,6 +782,12 @@ void InspectorInstrumentation::frameClearedScheduledNavigationImpl(Instrumenting
         inspectorPageAgent->frameClearedScheduledNavigation(frame);
 }
 
+void InspectorInstrumentation::defaultAppearanceDidChangeImpl(InstrumentingAgents& instrumentingAgents, bool useDarkAppearance)
+{
+    if (InspectorPageAgent* inspectorPageAgent = instrumentingAgents.inspectorPageAgent())
+        inspectorPageAgent->defaultAppearanceDidChange(useDarkAppearance);
+}
+
 void InspectorInstrumentation::willDestroyCachedResourceImpl(CachedResource& cachedResource)
 {
     if (!s_instrumentingAgentsSet)
index d91d0c2..5d2fb3d 100644 (file)
@@ -213,6 +213,7 @@ public:
     static void frameStoppedLoading(Frame&);
     static void frameScheduledNavigation(Frame&, Seconds delay);
     static void frameClearedScheduledNavigation(Frame&);
+    static void defaultAppearanceDidChange(Page&, bool useDarkAppearance);
     static void willDestroyCachedResource(CachedResource&);
 
     static void addMessageToConsole(Page&, std::unique_ptr<Inspector::ConsoleMessage>);
@@ -386,6 +387,7 @@ private:
     static void frameStoppedLoadingImpl(InstrumentingAgents&, Frame&);
     static void frameScheduledNavigationImpl(InstrumentingAgents&, Frame&, Seconds delay);
     static void frameClearedScheduledNavigationImpl(InstrumentingAgents&, Frame&);
+    static void defaultAppearanceDidChangeImpl(InstrumentingAgents&, bool useDarkAppearance);
     static void willDestroyCachedResourceImpl(CachedResource&);
 
     static void addMessageToConsoleImpl(InstrumentingAgents&, std::unique_ptr<Inspector::ConsoleMessage>);
@@ -1135,6 +1137,12 @@ inline void InspectorInstrumentation::frameClearedScheduledNavigation(Frame& fra
         frameClearedScheduledNavigationImpl(*instrumentingAgents, frame);
 }
 
+inline void InspectorInstrumentation::defaultAppearanceDidChange(Page& page, bool useDarkAppearance)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(void());
+    defaultAppearanceDidChangeImpl(instrumentingAgentsForPage(page), useDarkAppearance);
+}
+
 inline void InspectorInstrumentation::willDestroyCachedResource(CachedResource& cachedResource)
 {
     FAST_RETURN_IF_NO_FRONTENDS(void());
index d0e0213..ee44eac 100644 (file)
@@ -300,6 +300,10 @@ void InspectorPageAgent::enable(ErrorString&)
     auto stopwatch = m_environment.executionStopwatch();
     stopwatch->reset();
     stopwatch->start();
+
+#if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
+    defaultAppearanceDidChange(m_page.defaultUseDarkAppearance());
+#endif
 }
 
 void InspectorPageAgent::disable(ErrorString&)
@@ -310,6 +314,7 @@ void InspectorPageAgent::disable(ErrorString&)
     ErrorString unused;
     setShowPaintRects(unused, false);
     setEmulatedMedia(unused, emptyString());
+    setForcedAppearance(unused, emptyString());
 }
 
 void InspectorPageAgent::reload(ErrorString&, const bool* optionalReloadFromOrigin, const bool* optionalRevalidateAllResources)
@@ -685,6 +690,11 @@ void InspectorPageAgent::frameClearedScheduledNavigation(Frame& frame)
     m_frontendDispatcher->frameClearedScheduledNavigation(frameId(&frame));
 }
 
+void InspectorPageAgent::defaultAppearanceDidChange(bool useDarkAppearance)
+{
+    m_frontendDispatcher->defaultAppearanceDidChange(useDarkAppearance ? Inspector::Protocol::Page::Appearance::Dark : Inspector::Protocol::Page::Appearance::Light);
+}
+
 void InspectorPageAgent::didPaint(RenderObject& renderer, const LayoutRect& rect)
 {
     if (!m_enabled || !m_showPaintRects)
@@ -808,6 +818,21 @@ void InspectorPageAgent::setEmulatedMedia(ErrorString&, const String& media)
         document->updateLayout();
 }
 
+void InspectorPageAgent::setForcedAppearance(ErrorString&, const String& appearance)
+{
+    if (appearance == m_forcedAppearance)
+        return;
+
+    m_forcedAppearance = appearance;
+
+    if (appearance == "Light"_s)
+        m_page.setUseDarkAppearanceOverride(false);
+    else if (appearance == "Dark"_s)
+        m_page.setUseDarkAppearanceOverride(true);
+    else
+        m_page.setUseDarkAppearanceOverride(std::nullopt);
+}
+
 void InspectorPageAgent::applyEmulatedMedia(String& media)
 {
     if (!m_emulatedMedia.isEmpty())
index 6709f52..b407635 100644 (file)
@@ -100,6 +100,7 @@ public:
     void setShowRulers(ErrorString&, bool) final;
     void setShowPaintRects(ErrorString&, bool show) final;
     void setEmulatedMedia(ErrorString&, const String&) final;
+    void setForcedAppearance(ErrorString&, const String&) final;
     void getCompositingBordersVisible(ErrorString&, bool* out_param) final;
     void setCompositingBordersVisible(ErrorString&, bool) final;
     void snapshotNode(ErrorString&, int nodeId, String* outDataURL) final;
@@ -116,6 +117,7 @@ public:
     void frameStoppedLoading(Frame&);
     void frameScheduledNavigation(Frame&, Seconds delay);
     void frameClearedScheduledNavigation(Frame&);
+    void defaultAppearanceDidChange(bool useDarkAppearance);
     void applyEmulatedMedia(String&);
     void didPaint(RenderObject&, const LayoutRect&);
     void didLayout();
@@ -160,6 +162,7 @@ private:
     bool m_isFirstLayoutAfterOnLoad { false };
     bool m_showPaintRects { false };
     String m_emulatedMedia;
+    String m_forcedAppearance;
 };
 
 } // namespace WebCore
index 2e469eb..bd0347f 100644 (file)
@@ -2632,6 +2632,8 @@ void Page::setUseDarkAppearance(bool value)
 
     m_useDarkAppearance = value;
 
+    InspectorInstrumentation::defaultAppearanceDidChange(*this, value);
+
     appearanceDidChange();
 }
 
@@ -2640,9 +2642,21 @@ bool Page::useDarkAppearance() const
     FrameView* view = mainFrame().view();
     if (!view || !equalLettersIgnoringASCIICase(view->mediaType(), "screen"))
         return false;
+    if (m_useDarkAppearanceOverride)
+        return m_useDarkAppearanceOverride.value();
     return m_useDarkAppearance;
 }
 
+void Page::setUseDarkAppearanceOverride(std::optional<bool> valueOverride)
+{
+    if (valueOverride == m_useDarkAppearanceOverride)
+        return;
+
+    m_useDarkAppearanceOverride = valueOverride;
+
+    appearanceDidChange();
+}
+
 void Page::setFullscreenInsets(const FloatBoxExtent& insets)
 {
     if (insets == m_fullscreenInsets)
index f8465aa..3dbe572 100644 (file)
@@ -360,6 +360,8 @@ public:
     
     WEBCORE_EXPORT bool useDarkAppearance() const;
     WEBCORE_EXPORT void setUseDarkAppearance(bool);
+    bool defaultUseDarkAppearance() const { return m_useDarkAppearance; }
+    void setUseDarkAppearanceOverride(std::optional<bool>);
 
 #if ENABLE(TEXT_AUTOSIZING)
     float textAutosizingWidth() const { return m_textAutosizingWidth; }
@@ -793,6 +795,7 @@ private:
     
     bool m_useSystemAppearance { false };
     bool m_useDarkAppearance { false };
+    std::optional<bool> m_useDarkAppearanceOverride;
 
 #if ENABLE(TEXT_AUTOSIZING)
     float m_textAutosizingWidth { 0 };
index 7fcc49b..ef923bd 100644 (file)
@@ -1,3 +1,28 @@
+2018-11-27  Timothy Hatcher  <timothy@apple.com>
+
+        Web Inspector: Add support for forcing color scheme appearance in DOM tree.
+        https://bugs.webkit.org/show_bug.cgi?id=191820
+        rdar://problem/46153172
+
+        Reviewed by Devin Rousso.
+
+        * Localizations/en.lproj/localizedStrings.js: Updated.
+        * UserInterface/Controllers/CSSManager.js:
+        (WI.CSSManager):
+        (WI.CSSManager.prototype.get defaultAppearance): Added.
+        (WI.CSSManager.prototype.get forcedAppearance): Added.
+        (WI.CSSManager.prototype.set forcedAppearance): Added.
+        (WI.CSSManager.prototype.canForceAppearance): Added.
+        (WI.CSSManager.prototype.defaultAppearanceDidChange): Added.
+        * UserInterface/Images/Appearance.svg: Added.
+        * UserInterface/Protocol/PageObserver.js:
+        (WI.PageObserver.prototype.defaultAppearanceChanged): Added.
+        * UserInterface/Views/DOMTreeContentView.js:
+        (WI.DOMTreeContentView):
+        (WI.DOMTreeContentView.prototype.get navigationItems):
+        (WI.DOMTreeContentView.prototype._defaultAppearanceDidChange): Added.
+        (WI.DOMTreeContentView.prototype._toggleAppearance): Added.
+
 2018-11-27  Matt Baker  <mattbaker@apple.com>
 
         Web Inspector: Cookies table needs copy keyboard shortcut and context menu support
index 0b08172..6d917a3 100644 (file)
@@ -418,6 +418,8 @@ localizedStrings["Focus on Subtree"] = "Focus on Subtree";
 localizedStrings["Focused"] = "Focused";
 localizedStrings["Font"] = "Font";
 localizedStrings["Fonts"] = "Fonts";
+localizedStrings["Force Dark Appearance"] = "Force Dark Appearance";
+localizedStrings["Force Light Appearance"] = "Force Light Appearance";
 localizedStrings["Force Print Media Styles"] = "Force Print Media Styles";
 localizedStrings["Forced Layout"] = "Forced Layout";
 localizedStrings["Forced Pseudo-Classes"] = "Forced Pseudo-Classes";
@@ -981,6 +983,7 @@ localizedStrings["Unknown node"] = "Unknown node";
 localizedStrings["Unsupported property name"] = "Unsupported property name";
 localizedStrings["Unsupported property value"] = "Unsupported property value";
 localizedStrings["Untitled"] = "Untitled";
+localizedStrings["Use Default Appearance"] = "Use Default Appearance";
 localizedStrings["Use Default Media Styles"] = "Use Default Media Styles";
 localizedStrings["Use the resource cache when loading resources"] = "Use the resource cache when loading resources";
 localizedStrings["User Agent"] = "User Agent";
index 489067b..743220c 100644 (file)
@@ -45,6 +45,8 @@ WI.CSSManager = class CSSManager extends WI.Object
         this._styleSheetIdentifierMap = new Map;
         this._styleSheetFrameURLMap = new Map;
         this._nodeStylesMap = {};
+        this._defaultAppearance = null;
+        this._forcedAppearance = null;
 
         // COMPATIBILITY (iOS 9): Legacy backends did not send stylesheet
         // added/removed events and must be fetched manually.
@@ -107,6 +109,56 @@ WI.CSSManager = class CSSManager extends WI.Object
         return [...this._styleSheetIdentifierMap.values()];
     }
 
+    get defaultAppearance()
+    {
+        return this._defaultAppearance;
+    }
+
+    get forcedAppearance()
+    {
+        return this._forcedAppearance;
+    }
+
+    set forcedAppearance(name)
+    {
+        if (!this.canForceAppearance())
+            return;
+
+        let protocolName = "";
+
+        switch (name) {
+        case WI.CSSManager.Appearance.Light:
+            protocolName = PageAgent.Appearance.Light;
+            break;
+
+        case WI.CSSManager.Appearance.Dark:
+            protocolName = PageAgent.Appearance.Dark;
+            break;
+
+        case null:
+        case undefined:
+        case "":
+            protocolName = "";
+            break;
+
+        default:
+            // Abort for unknown values.
+            return;
+        }
+
+        this._forcedAppearance = name || null;
+
+        PageAgent.setForcedAppearance(protocolName).then(() => {
+            this.mediaQueryResultChanged();
+            this.dispatchEventToListeners(WI.CSSManager.Event.ForcedAppearanceDidChange, {appearance: this._forcedAppearance});
+        });
+    }
+
+    canForceAppearance()
+    {
+        return window.PageAgent && !!PageAgent.setForcedAppearance && this._defaultAppearance;
+    }
+
     canForcePseudoClasses()
     {
         return window.CSSAgent && !!CSSAgent.forcePseudoState;
@@ -269,6 +321,33 @@ WI.CSSManager = class CSSManager extends WI.Object
         this.mediaQueryResultChanged();
     }
 
+    defaultAppearanceDidChange(protocolName)
+    {
+        // Called from WI.PageObserver.
+
+        let appearance = null;
+
+        switch (protocolName) {
+        case PageAgent.Appearance.Light:
+            appearance = WI.CSSManager.Appearance.Light;
+            break;
+
+        case PageAgent.Appearance.Dark:
+            appearance = WI.CSSManager.Appearance.Dark;
+            break;
+
+        default:
+            console.error("Unknown default appearance name:", protocolName);
+            break;
+        }
+
+        this._defaultAppearance = appearance;
+
+        this.mediaQueryResultChanged();
+
+        this.dispatchEventToListeners(WI.CSSManager.Event.DefaultAppearanceDidChange, {appearance});
+    }
+
     // Protected
 
     mediaQueryResultChanged()
@@ -561,6 +640,13 @@ WI.CSSManager = class CSSManager extends WI.Object
 WI.CSSManager.Event = {
     StyleSheetAdded: "css-manager-style-sheet-added",
     StyleSheetRemoved: "css-manager-style-sheet-removed",
+    DefaultAppearanceDidChange: "css-manager-default-appearance-did-change",
+    ForcedAppearanceDidChange: "css-manager-forced-appearance-did-change",
+};
+
+WI.CSSManager.Appearance = {
+    Light: Symbol("light"),
+    Dark: Symbol("dark"),
 };
 
 WI.CSSManager.PseudoElementNames = ["before", "after"];
diff --git a/Source/WebInspectorUI/UserInterface/Images/Appearance.svg b/Source/WebInspectorUI/UserInterface/Images/Appearance.svg
new file mode 100644 (file)
index 0000000..3b394fb
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2018 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <path fill="currentColor" fill-rule="evenodd" d="M 3.99119893 1 L 12.0087973 1 C 13.048904 1 13.4260733 1.10829467 13.8063227 1.31165867 C 14.1865627 1.51501333 14.4849867 1.81343733 14.6883413 2.19367733 C 14.8917053 2.57392667 15 2.951096 15 3.99120267 L 15 12.0088011 C 15 13.0489059 14.8917053 13.4260733 14.6883413 13.8063199 C 14.4849867 14.1865669 14.1865627 14.4849869 13.8063227 14.6883452 C 13.4260733 14.8917035 13.048904 15 12.0087973 15 L 3.99119893 15 C 2.95109413 15 2.57392667 14.8917035 2.19368013 14.6883452 C 1.81343313 14.4849869 1.51501305 14.1865669 1.31165484 13.8063199 C 1.10829653 13.4260733 1 13.0489059 1 12.0088011 L 1 3.99120267 C 1 2.951096 1.10829653 2.57392667 1.31165484 2.19367733 C 1.51501305 1.81343733 1.81343313 1.51501333 2.19368013 1.31165867 C 2.57392667 1.10829467 2.95109413 1 3.99119893 1 Z M 3.5 4 L 12.5 4 C 13.3284271 4 14 4.67157288 14 5.5 L 14 12.5 C 14 13.3284271 13.3284271 14 12.5 14 L 3.5 14 C 2.67157288 14 2 13.3284271 2 12.5 L 2 5.5 C 2 4.67157288 2.67157288 4 3.5 4 Z M 4.1 5 L 11.9 5 C 12.5075132 5 13 5.49248678 13 6.1 L 13 11.9 C 13 12.5075132 12.5075132 13 11.9 13 L 4.1 13 C 3.49248678 13 3 12.5075132 3 11.9 L 3 6.1 C 3 5.49248678 3.49248678 5 4.1 5 Z M 3 2 L 4 2 L 4 3 L 3 3 L 3 2 Z M 5 2 L 6 2 L 6 3 L 5 3 L 5 2 Z M 7 2 L 8 2 L 8 3 L 7 3 L 7 2 Z"/>
+</svg>
index 31468a7..6b7af6f 100644 (file)
@@ -47,6 +47,11 @@ WI.PageObserver = class PageObserver
         WI.networkManager.frameDidDetach(frameId);
     }
 
+    defaultAppearanceDidChange(appearance)
+    {
+        WI.cssManager.defaultAppearanceDidChange(appearance);
+    }
+
     frameStartedLoading(frameId)
     {
         // Not handled yet.
index 2dd9194..c7a6fe0 100644 (file)
@@ -74,6 +74,8 @@ WI.DOMTreeContentView = class DOMTreeContentView extends WI.ContentView
         WI.domManager.addEventListener(WI.DOMManager.Event.AttributeRemoved, this._domNodeChanged, this);
         WI.domManager.addEventListener(WI.DOMManager.Event.CharacterDataModified, this._domNodeChanged, this);
 
+        WI.cssManager.addEventListener(WI.CSSManager.Event.DefaultAppearanceDidChange, this._defaultAppearanceDidChange, this);
+
         this._lastSelectedNodePathSetting = new WI.Setting("last-selected-node-path", null);
 
         this._numberOfSearchResults = null;
@@ -81,6 +83,9 @@ WI.DOMTreeContentView = class DOMTreeContentView extends WI.ContentView
         this._breakpointGutterEnabled = false;
         this._pendingBreakpointNodeIdentifiers = new Set;
 
+        if (WI.cssManager.canForceAppearance())
+            this._defaultAppearanceDidChange();
+
         if (WI.domDebuggerManager.supported) {
             WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.BreakpointsEnabledDidChange, this._breakpointsEnabledDidChange, this);
 
@@ -100,6 +105,9 @@ WI.DOMTreeContentView = class DOMTreeContentView extends WI.ContentView
     {
         let items = [this._showPrintStylesButtonNavigationItem, this._showsShadowDOMButtonNavigationItem];
 
+        if (this._forceAppearanceButtonNavigationItem)
+            items.unshift(this._forceAppearanceButtonNavigationItem);
+
         // COMPATIBILITY (iOS 11.3)
         if (window.PageAgent && PageAgent.setShowRulers)
             items.unshift(this._showRulersButtonNavigationItem);
@@ -582,6 +590,71 @@ WI.DOMTreeContentView = class DOMTreeContentView extends WI.ContentView
         this._showPrintStylesChanged();
     }
 
+    _defaultAppearanceDidChange()
+    {
+        let defaultAppearance = WI.cssManager.defaultAppearance;
+        if (!defaultAppearance) {
+            this._lastKnownDefaultAppearance = null;
+            this._forceAppearanceButtonNavigationItem = null;
+            this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange);
+            return;
+        }
+
+        // Don't update the navigation item if there is currently a forced appearance.
+        // The user will need to toggle it off to update it based on the new default appearance.
+        if (WI.cssManager.forcedAppearance && this._forceAppearanceButtonNavigationItem)
+            return;
+
+        this._forceAppearanceButtonNavigationItem = null;
+
+        switch (defaultAppearance) {
+        case WI.CSSManager.Appearance.Light:
+            this._forceAppearanceButtonNavigationItem = new WI.ActivateButtonNavigationItem("appearance", WI.UIString("Force Dark Appearance"), WI.UIString("Use Default Appearance"), "Images/Appearance.svg", 16, 16);
+            break;
+        case WI.CSSManager.Appearance.Dark:
+            this._forceAppearanceButtonNavigationItem = new WI.ActivateButtonNavigationItem("appearance", WI.UIString("Force Light Appearance"), WI.UIString("Use Default Appearance"), "Images/Appearance.svg", 16, 16);
+            break;
+        }
+
+        if (!this._forceAppearanceButtonNavigationItem) {
+            console.error("Unknown default appearance name:", defaultAppearance);
+            this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange);
+            return;
+        }
+
+        this._lastKnownDefaultAppearance = defaultAppearance;
+
+        this._forceAppearanceButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._toggleAppearance, this);
+        this._forceAppearanceButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
+        this._forceAppearanceButtonNavigationItem.activated = !!WI.cssManager.forcedAppearance;
+
+        this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange);
+    }
+
+    _toggleAppearance(event)
+    {
+        // Use the last known default appearance, since that is the appearance this navigation item was generated for.
+        let appearanceToForce = null;
+        switch (this._lastKnownDefaultAppearance) {
+        case WI.CSSManager.Appearance.Light:
+            appearanceToForce = WI.CSSManager.Appearance.Dark;
+            break;
+        case WI.CSSManager.Appearance.Dark:
+            appearanceToForce = WI.CSSManager.Appearance.Light;
+            break;
+        }
+
+        console.assert(appearanceToForce);
+        WI.cssManager.forcedAppearance = WI.cssManager.forcedAppearance == appearanceToForce ? null : appearanceToForce;
+
+        // When no longer forcing an appearance, if the last known default appearance is different than the current
+        // default appearance, then update the navigation button now. Otherwise just toggle the activated state.
+        if (!WI.cssManager.forcedAppearance && this._lastKnownDefaultAppearance !== WI.cssManager.defaultAppearance)
+            this._defaultAppearanceDidChange();
+        else
+            this._forceAppearanceButtonNavigationItem.activated = !!WI.cssManager.forcedAppearance;
+    }
+
     _showRulersChanged()
     {
         this._showRulersButtonNavigationItem.activated = WI.settings.showRulers.value;