Title: [259170] trunk
Revision
259170
Author
[email protected]
Date
2020-03-28 19:56:05 -0700 (Sat, 28 Mar 2020)

Log Message

Web Inspector: CSS: create visual editor for `box-shadow`
https://bugs.webkit.org/show_bug.cgi?id=208380

Reviewed by Timothy Hatcher.

Source/WebInspectorUI:

Recognize `box-shadow` CSS properties in the Styles sidebar, parse the comma-separated list
value for individual box shadows, and create a `WI.InlineSwatch` for each. When clicked,
show a `WI.Popover` with a `WI.BoxShadowEditor`, which contains a table of editors:

    Offset X |   <input type="text">   |   [ 2D (X & Y) ]
    Offset Y |   <input type="text">   |   [   Slider   ]
       Inset | <input type="checkbox"> |
        Blur |   <input type="text">   | <input type="range">
      Spread |   <input type="text">   | <input type="range">
    [                                                       ]
    [                                                       ]
    [                   full color picker                   ]
    [                                                       ]
    [                                                       ]

* UserInterface/Models/BoxShadow.js: Added.
(WI.BoxShadow):
(WI.BoxShadow.fromString):
(WI.BoxShadow.parseNumberComponent):
(WI.BoxShadow.prototype.get offsetX):
(WI.BoxShadow.prototype.get offsetY):
(WI.BoxShadow.prototype.get blurRadius):
(WI.BoxShadow.prototype.get spreadRadius):
(WI.BoxShadow.prototype.get inset):
(WI.BoxShadow.prototype.get color):
(WI.BoxShadow.prototype.copy):
(WI.BoxShadow.prototype.toString):
(WI.BoxShadow.prototype.toString.stringifyNumberComponent):

* UserInterface/Models/CSSCompletions.js:
Add a `Set` of allowed CSS length units.

* UserInterface/Views/BoxShadowEditor.js: Added.
(WI.BoxShadowEditor):
(WI.BoxShadowEditor.createInputRow):
(WI.BoxShadowEditor.createSlider):
(WI.BoxShadowEditor.prototype.get element):
(WI.BoxShadowEditor.prototype.get boxShadow):
(WI.BoxShadowEditor.prototype.set boxShadow):
(WI.BoxShadowEditor.prototype.handleEvent):
(WI.BoxShadowEditor.prototype._updateBoxShadow):
(WI.BoxShadowEditor.prototype._updateBoxShadowOffsetFromSliderMouseEvent):
(WI.BoxShadowEditor.prototype._determineShiftForEvent):
(WI.BoxShadowEditor.prototype._handleOffsetSliderSVGKeyDown):
(WI.BoxShadowEditor.prototype._handleOffsetSliderSVGMouseDown):
(WI.BoxShadowEditor.prototype._handleWindowMouseMove):
(WI.BoxShadowEditor.prototype._handleWindowMouseUp):
(WI.BoxShadowEditor.prototype._handleOffsetXInputInput):
(WI.BoxShadowEditor.prototype._handleOffsetXInputKeyDown):
(WI.BoxShadowEditor.prototype._handleOffsetYInputInput):
(WI.BoxShadowEditor.prototype._handleOffsetYInputKeyDown):
(WI.BoxShadowEditor.prototype._handleBlurRadiusInputInput):
(WI.BoxShadowEditor.prototype._handleBlurRadiusInputKeyDown):
(WI.BoxShadowEditor.prototype._handleBlurRadiusSliderInput):
(WI.BoxShadowEditor.prototype._handleSpreadRadiusInputInput):
(WI.BoxShadowEditor.prototype._handleSpreadRadiusInputKeyDown):
(WI.BoxShadowEditor.prototype._handleSpreadRadiusSliderInput):
(WI.BoxShadowEditor.prototype._handleInsetCheckboxChange):
(WI.BoxShadowEditor.prototype._handleColorChanged):
* UserInterface/Views/BoxShadowEditor.css: Added.
(.box-shadow-editor):
(.box-shadow-editor > table):
(.box-shadow-editor > table > tr > th):
(.box-shadow-editor > table > tr > td):
(.box-shadow-editor > table > tr > td > input[type="text"]):
(.box-shadow-editor > table > tr > td > input[type="range"]):
(.box-shadow-editor > table > tr > td > svg):
(.box-shadow-editor > table > tr > td > svg line.axis):
(.box-shadow-editor > table > tr > td > svg line:not(.axis)):
(.box-shadow-editor > table > tr > td > svg circle):
(@media (prefers-color-scheme: dark) .box-shadow-editor > table > tr > th):

* UserInterface/Views/InlineSwatch.js:
(WI.InlineSwatch):
(WI.InlineSwatch.prototype._fallbackValue):
(WI.InlineSwatch.prototype._valueEditorValueDidChange):
* UserInterface/Views/InlineSwatch.css:
(.inline-swatch):
(.inline-swatch:not(.box-shadow), .inline-swatch.box-shadow:matches(:hover, :active)): Added.
(.inline-swatch:matches(.bezier, .box-shadow, .spring, .variable)): Added.
(.inline-swatch:not(.read-only):matches(.bezier, .box-shadow, .spring, .variable):hover): Added.
(.inline-swatch:not(.read-only):matches(.bezier, .box-shadow, .spring, .variable):active): Added.
(.inline-swatch:matches(.bezier, .box-shadow, .spring, .variable) > span): Added.
(@media (prefers-color-scheme: dark) .inline-swatch.box-shadow > svg): Added.
(.inline-swatch:not(.read-only):matches(.bezier, .spring, .variable):hover): Deleted.
(.inline-swatch:not(.read-only):matches(.bezier, .spring, .variable):active): Deleted.
(.inline-swatch:matches(.bezier, .spring, .variable) > span): Deleted.

* UserInterface/Views/SpreadsheetStyleProperty.js:
(WI.SpreadsheetStyleProperty.prototype._replaceSpecialTokens):
(WI.SpreadsheetStyleProperty.prototype._addGradientTokens):
(WI.SpreadsheetStyleProperty.prototype._addColorTokens):
(WI.SpreadsheetStyleProperty.prototype._addTimingFunctionTokens):
(WI.SpreadsheetStyleProperty.prototype._addBoxShadowTokens):
(WI.SpreadsheetStyleProperty.prototype._resolveVariables):

* UserInterface/Views/Variables.css:
(:root):
* UserInterface/Views/ColorPicker.css:
(.color-picker):
Move `--color-picker-width` to `:root` so that `WI.BoxShadowEditor` can use it.

* UserInterface/Main.html:
* UserInterface/Test.html:
* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Images/BoxShadow.svg: Added.

LayoutTests:

* inspector/model/boxShadow.html: Added.
* inspector/model/boxShadow-expected.txt: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (259169 => 259170)


--- trunk/LayoutTests/ChangeLog	2020-03-29 02:54:09 UTC (rev 259169)
+++ trunk/LayoutTests/ChangeLog	2020-03-29 02:56:05 UTC (rev 259170)
@@ -1,3 +1,13 @@
+2020-03-28  Devin Rousso  <[email protected]>
+
+        Web Inspector: CSS: create visual editor for `box-shadow`
+        https://bugs.webkit.org/show_bug.cgi?id=208380
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/model/boxShadow.html: Added.
+        * inspector/model/boxShadow-expected.txt: Added.
+
 2020-03-27  Simon Fraser  <[email protected]>
 
         Sideways jiggles when scrolling the shelves on beta.music.apple.com

Added: trunk/LayoutTests/inspector/model/boxShadow-expected.txt (0 => 259170)


--- trunk/LayoutTests/inspector/model/boxShadow-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/model/boxShadow-expected.txt	2020-03-29 02:56:05 UTC (rev 259170)
@@ -0,0 +1,80 @@
+Tests for the WI.BoxShadow model object.
+
+
+== Running test suite: WI.BoxShadow
+-- Running test case: WI.BoxShadow.fromString
+PASS: default value should be "none"
+
+"1px 2rem" resolves to "1px 2rem"
+"red 1px 2rem" resolves to "1px 2rem red"
+"1px red 2rem" resolves to "1px 2rem red"
+"1px 2rem red" resolves to "1px 2rem red"
+"inset 1px 2rem" resolves to "1px 2rem inset"
+"1px inset 2rem" resolves to "1px 2rem inset"
+"1px 2rem inset" resolves to "1px 2rem inset"
+"inset 1px 2rem red" resolves to "1px 2rem red inset"
+"1px inset 2rem red" resolves to "1px 2rem red inset"
+"1px 2rem inset red" resolves to "1px 2rem red inset"
+"1px 2rem red inset" resolves to "1px 2rem red inset"
+
+"1px 2rem 3in" resolves to "1px 2rem 3in"
+"red 1px 2rem 3in" resolves to "1px 2rem 3in red"
+"1px red 2rem 3in" resolves to "1px 2rem 3in red"
+"1px 2rem red 3in" resolves to "1px 2rem 3in red"
+"1px 2rem 3in red" resolves to "1px 2rem 3in red"
+"inset 1px 2rem 3in" resolves to "1px 2rem 3in inset"
+"1px inset 2rem 3in" resolves to "1px 2rem 3in inset"
+"1px 2rem inset 3in" resolves to "1px 2rem 3in inset"
+"1px 2rem 3in inset" resolves to "1px 2rem 3in inset"
+"inset 1px 2rem 3in red" resolves to "1px 2rem 3in red inset"
+"1px inset 2rem 3in red" resolves to "1px 2rem 3in red inset"
+"1px 2rem inset 3in red" resolves to "1px 2rem 3in red inset"
+"1px 2rem 3in inset red" resolves to "1px 2rem 3in red inset"
+"1px 2rem 3in red inset" resolves to "1px 2rem 3in red inset"
+
+"1px 2rem 3in 4q" resolves to "1px 2rem 3in 4q"
+"red 1px 2rem 3in 4q" resolves to "1px 2rem 3in 4q red"
+"1px red 2rem 3in 4q" resolves to "1px 2rem 3in 4q red"
+"1px 2rem red 3in 4q" resolves to "1px 2rem 3in 4q red"
+"1px 2rem 3in red 4q" resolves to "1px 2rem 3in 4q red"
+"1px 2rem 3in 4q red" resolves to "1px 2rem 3in 4q red"
+"inset 1px 2rem 3in 4q" resolves to "1px 2rem 3in 4q inset"
+"1px inset 2rem 3in 4q" resolves to "1px 2rem 3in 4q inset"
+"1px 2rem inset 3in 4q" resolves to "1px 2rem 3in 4q inset"
+"1px 2rem 3in inset 4q" resolves to "1px 2rem 3in 4q inset"
+"1px 2rem 3in 4q inset" resolves to "1px 2rem 3in 4q inset"
+"inset 1px 2rem 3in 4q red" resolves to "1px 2rem 3in 4q red inset"
+"1px inset 2rem 3in 4q red" resolves to "1px 2rem 3in 4q red inset"
+"1px 2rem inset 3in 4q red" resolves to "1px 2rem 3in 4q red inset"
+"1px 2rem 3in inset 4q red" resolves to "1px 2rem 3in 4q red inset"
+"1px 2rem 3in 4q inset red" resolves to "1px 2rem 3in 4q red inset"
+"1px 2rem 3in 4q red inset" resolves to "1px 2rem 3in 4q red inset"
+
+"0 0 0 0 red inset" resolves to "0 0 0 0 red inset"
+"0px 0px 0px 0px red inset" resolves to "0 0 0 0 red inset"
+"0rem 0rem 0rem 0rem red inset" resolves to "0 0 0 0 red inset"
+"0in 0in 0in 0in red inset" resolves to "0 0 0 0 red inset"
+"0q 0q 0q 0q red inset" resolves to "0 0 0 0 red inset"
+
+"none" resolves to "none"
+
+"1px 2rem 3in 4q rgb(11, 12, 13) inset" resolves to "1px 2rem 3in 4q rgb(11, 12, 13) inset"
+
+PASS: '1' should not be detected
+
+PASS: '1%' should not be detected
+PASS: '1px 2%' should not be detected
+PASS: '1px 2px 3%' should not be detected
+PASS: '1px 2px 3px 4%' should not be detected
+
+PASS: '1px' should not be detected
+PASS: '1px 2rem 3in 4q 5pt' should not be detected
+PASS: '1px 2rem 3in 4q invalid' should not be detected
+PASS: '1px 2rem 3in 4q red inset extra' should not be detected
+PASS: 'red' should not be detected
+PASS: 'inset' should not be detected
+
+PASS: 'red red' should not be detected
+PASS: 'inset inset' should not be detected
+
+

Added: trunk/LayoutTests/inspector/model/boxShadow.html (0 => 259170)


--- trunk/LayoutTests/inspector/model/boxShadow.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/model/boxShadow.html	2020-03-29 02:56:05 UTC (rev 259170)
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script>
+function test()
+{
+    let suite = InspectorTest.createSyncSuite("WI.BoxShadow");
+
+    suite.addTestCase({
+        name: "WI.BoxShadow.fromString",
+        description: "Test we can detect box shadows from strings.",
+        test() {
+            InspectorTest.expectEqual((new WI.BoxShadow).toString(), "none", `default value should be "none"`);
+            InspectorTest.newline();
+
+            function testGood(string) {
+                let boxShadow = WI.BoxShadow.fromString(string);
+                InspectorTest.assert(boxShadow instanceof WI.BoxShadow, `'${string}' should be detected`);
+                if (boxShadow)
+                    InspectorTest.log(`"${string}" resolves to "${boxShadow.toString()}"`);
+            }
+
+            // offsetX and offsetY
+            testGood("1px 2rem");
+            testGood("red 1px 2rem");
+            testGood("1px red 2rem");
+            testGood("1px 2rem red");
+            testGood("inset 1px 2rem");
+            testGood("1px inset 2rem");
+            testGood("1px 2rem inset");
+            testGood("inset 1px 2rem red");
+            testGood("1px inset 2rem red");
+            testGood("1px 2rem inset red");
+            testGood("1px 2rem red inset");
+            InspectorTest.newline();
+
+            // blurRadius
+            testGood("1px 2rem 3in");
+            testGood("red 1px 2rem 3in");
+            testGood("1px red 2rem 3in");
+            testGood("1px 2rem red 3in");
+            testGood("1px 2rem 3in red");
+            testGood("inset 1px 2rem 3in");
+            testGood("1px inset 2rem 3in");
+            testGood("1px 2rem inset 3in");
+            testGood("1px 2rem 3in inset");
+            testGood("inset 1px 2rem 3in red");
+            testGood("1px inset 2rem 3in red");
+            testGood("1px 2rem inset 3in red");
+            testGood("1px 2rem 3in inset red");
+            testGood("1px 2rem 3in red inset");
+            InspectorTest.newline();
+
+            // spreadRadius
+            testGood("1px 2rem 3in 4q");
+            testGood("red 1px 2rem 3in 4q");
+            testGood("1px red 2rem 3in 4q");
+            testGood("1px 2rem red 3in 4q");
+            testGood("1px 2rem 3in red 4q");
+            testGood("1px 2rem 3in 4q red");
+            testGood("inset 1px 2rem 3in 4q");
+            testGood("1px inset 2rem 3in 4q");
+            testGood("1px 2rem inset 3in 4q");
+            testGood("1px 2rem 3in inset 4q");
+            testGood("1px 2rem 3in 4q inset");
+            testGood("inset 1px 2rem 3in 4q red");
+            testGood("1px inset 2rem 3in 4q red");
+            testGood("1px 2rem inset 3in 4q red");
+            testGood("1px 2rem 3in inset 4q red");
+            testGood("1px 2rem 3in 4q inset red");
+            testGood("1px 2rem 3in 4q red inset");
+            InspectorTest.newline();
+
+            // 0 ignores unit
+            testGood("0 0 0 0 red inset");
+            testGood("0px 0px 0px 0px red inset");
+            testGood("0rem 0rem 0rem 0rem red inset");
+            testGood("0in 0in 0in 0in red inset");
+            testGood("0q 0q 0q 0q red inset");
+            InspectorTest.newline();
+
+            // keywords
+            testGood("none");
+            InspectorTest.newline();
+
+            // color with parenthesis
+            testGood("1px 2rem 3in 4q rgb(11, 12, 13) inset");
+            InspectorTest.newline();
+
+            function testBad(string) {
+                let boxShadow = WI.BoxShadow.fromString(string);
+                InspectorTest.expectNull(boxShadow, `'${string}' should not be detected`);
+            }
+
+            // missing unit
+            testBad("1");
+            InspectorTest.newline();
+
+            // invalid unit
+            testBad("1%");
+            testBad("1px 2%");
+            testBad("1px 2px 3%");
+            testBad("1px 2px 3px 4%");
+            InspectorTest.newline();
+
+            // missing or extra components
+            testBad("1px");
+            testBad("1px 2rem 3in 4q 5pt");
+            testBad("1px 2rem 3in 4q invalid");
+            testBad("1px 2rem 3in 4q red inset extra");
+            testBad("red");
+            testBad("inset");
+            InspectorTest.newline();
+
+            // duplicate components
+            testBad("red red");
+            testBad("inset inset");
+            InspectorTest.newline();
+
+            return true;
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>Tests for the WI.BoxShadow model object.</p>
+</body>
+</html>

Modified: trunk/Source/WebInspectorUI/ChangeLog (259169 => 259170)


--- trunk/Source/WebInspectorUI/ChangeLog	2020-03-29 02:54:09 UTC (rev 259169)
+++ trunk/Source/WebInspectorUI/ChangeLog	2020-03-29 02:56:05 UTC (rev 259170)
@@ -1,5 +1,120 @@
 2020-03-28  Devin Rousso  <[email protected]>
 
+        Web Inspector: CSS: create visual editor for `box-shadow`
+        https://bugs.webkit.org/show_bug.cgi?id=208380
+
+        Reviewed by Timothy Hatcher.
+
+        Recognize `box-shadow` CSS properties in the Styles sidebar, parse the comma-separated list
+        value for individual box shadows, and create a `WI.InlineSwatch` for each. When clicked,
+        show a `WI.Popover` with a `WI.BoxShadowEditor`, which contains a table of editors:
+
+            Offset X |   <input type="text">   |   [ 2D (X & Y) ]
+            Offset Y |   <input type="text">   |   [   Slider   ]
+               Inset | <input type="checkbox"> |
+                Blur |   <input type="text">   | <input type="range">
+              Spread |   <input type="text">   | <input type="range">
+            [                                                       ]
+            [                                                       ]
+            [                   full color picker                   ]
+            [                                                       ]
+            [                                                       ]
+
+
+        * UserInterface/Models/BoxShadow.js: Added.
+        (WI.BoxShadow):
+        (WI.BoxShadow.fromString):
+        (WI.BoxShadow.parseNumberComponent):
+        (WI.BoxShadow.prototype.get offsetX):
+        (WI.BoxShadow.prototype.get offsetY):
+        (WI.BoxShadow.prototype.get blurRadius):
+        (WI.BoxShadow.prototype.get spreadRadius):
+        (WI.BoxShadow.prototype.get inset):
+        (WI.BoxShadow.prototype.get color):
+        (WI.BoxShadow.prototype.copy):
+        (WI.BoxShadow.prototype.toString):
+        (WI.BoxShadow.prototype.toString.stringifyNumberComponent):
+
+        * UserInterface/Models/CSSCompletions.js:
+        Add a `Set` of allowed CSS length units.
+
+        * UserInterface/Views/BoxShadowEditor.js: Added.
+        (WI.BoxShadowEditor):
+        (WI.BoxShadowEditor.createInputRow):
+        (WI.BoxShadowEditor.createSlider):
+        (WI.BoxShadowEditor.prototype.get element):
+        (WI.BoxShadowEditor.prototype.get boxShadow):
+        (WI.BoxShadowEditor.prototype.set boxShadow):
+        (WI.BoxShadowEditor.prototype.handleEvent):
+        (WI.BoxShadowEditor.prototype._updateBoxShadow):
+        (WI.BoxShadowEditor.prototype._updateBoxShadowOffsetFromSliderMouseEvent):
+        (WI.BoxShadowEditor.prototype._determineShiftForEvent):
+        (WI.BoxShadowEditor.prototype._handleOffsetSliderSVGKeyDown):
+        (WI.BoxShadowEditor.prototype._handleOffsetSliderSVGMouseDown):
+        (WI.BoxShadowEditor.prototype._handleWindowMouseMove):
+        (WI.BoxShadowEditor.prototype._handleWindowMouseUp):
+        (WI.BoxShadowEditor.prototype._handleOffsetXInputInput):
+        (WI.BoxShadowEditor.prototype._handleOffsetXInputKeyDown):
+        (WI.BoxShadowEditor.prototype._handleOffsetYInputInput):
+        (WI.BoxShadowEditor.prototype._handleOffsetYInputKeyDown):
+        (WI.BoxShadowEditor.prototype._handleBlurRadiusInputInput):
+        (WI.BoxShadowEditor.prototype._handleBlurRadiusInputKeyDown):
+        (WI.BoxShadowEditor.prototype._handleBlurRadiusSliderInput):
+        (WI.BoxShadowEditor.prototype._handleSpreadRadiusInputInput):
+        (WI.BoxShadowEditor.prototype._handleSpreadRadiusInputKeyDown):
+        (WI.BoxShadowEditor.prototype._handleSpreadRadiusSliderInput):
+        (WI.BoxShadowEditor.prototype._handleInsetCheckboxChange):
+        (WI.BoxShadowEditor.prototype._handleColorChanged):
+        * UserInterface/Views/BoxShadowEditor.css: Added.
+        (.box-shadow-editor):
+        (.box-shadow-editor > table):
+        (.box-shadow-editor > table > tr > th):
+        (.box-shadow-editor > table > tr > td):
+        (.box-shadow-editor > table > tr > td > input[type="text"]):
+        (.box-shadow-editor > table > tr > td > input[type="range"]):
+        (.box-shadow-editor > table > tr > td > svg):
+        (.box-shadow-editor > table > tr > td > svg line.axis):
+        (.box-shadow-editor > table > tr > td > svg line:not(.axis)):
+        (.box-shadow-editor > table > tr > td > svg circle):
+        (@media (prefers-color-scheme: dark) .box-shadow-editor > table > tr > th):
+
+        * UserInterface/Views/InlineSwatch.js:
+        (WI.InlineSwatch):
+        (WI.InlineSwatch.prototype._fallbackValue):
+        (WI.InlineSwatch.prototype._valueEditorValueDidChange):
+        * UserInterface/Views/InlineSwatch.css:
+        (.inline-swatch):
+        (.inline-swatch:not(.box-shadow), .inline-swatch.box-shadow:matches(:hover, :active)): Added.
+        (.inline-swatch:matches(.bezier, .box-shadow, .spring, .variable)): Added.
+        (.inline-swatch:not(.read-only):matches(.bezier, .box-shadow, .spring, .variable):hover): Added.
+        (.inline-swatch:not(.read-only):matches(.bezier, .box-shadow, .spring, .variable):active): Added.
+        (.inline-swatch:matches(.bezier, .box-shadow, .spring, .variable) > span): Added.
+        (@media (prefers-color-scheme: dark) .inline-swatch.box-shadow > svg): Added.
+        (.inline-swatch:not(.read-only):matches(.bezier, .spring, .variable):hover): Deleted.
+        (.inline-swatch:not(.read-only):matches(.bezier, .spring, .variable):active): Deleted.
+        (.inline-swatch:matches(.bezier, .spring, .variable) > span): Deleted.
+
+        * UserInterface/Views/SpreadsheetStyleProperty.js:
+        (WI.SpreadsheetStyleProperty.prototype._replaceSpecialTokens):
+        (WI.SpreadsheetStyleProperty.prototype._addGradientTokens):
+        (WI.SpreadsheetStyleProperty.prototype._addColorTokens):
+        (WI.SpreadsheetStyleProperty.prototype._addTimingFunctionTokens):
+        (WI.SpreadsheetStyleProperty.prototype._addBoxShadowTokens):
+        (WI.SpreadsheetStyleProperty.prototype._resolveVariables):
+
+        * UserInterface/Views/Variables.css:
+        (:root):
+        * UserInterface/Views/ColorPicker.css:
+        (.color-picker):
+        Move `--color-picker-width` to `:root` so that `WI.BoxShadowEditor` can use it.
+
+        * UserInterface/Main.html:
+        * UserInterface/Test.html:
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Images/BoxShadow.svg: Added.
+
+2020-03-28  Devin Rousso  <[email protected]>
+
         Web Inspector: REGRESSION(r257759): Network: graph in Timing pane of selected resource is missing bars
         https://bugs.webkit.org/show_bug.cgi?id=209525
 

Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (259169 => 259170)


--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2020-03-29 02:54:09 UTC (rev 259169)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2020-03-29 02:56:05 UTC (rev 259170)
@@ -192,6 +192,8 @@
 localizedStrings["Blackbox Script"] = "Blackbox Script";
 localizedStrings["Blackbox script to ignore it when debugging"] = "Blackbox script to ignore it when debugging";
 localizedStrings["Block Variables"] = "Block Variables";
+/* Input label for the blur radius of a CSS box shadow */
+localizedStrings["Blur @ Box Shadow Editor"] = "Blur";
 localizedStrings["Body:"] = "Body:";
 localizedStrings["Boundary"] = "Boundary";
 localizedStrings["Box Model"] = "Box Model";
@@ -448,6 +450,7 @@
 localizedStrings["Edit Breakpoint\u2026"] = "Edit Breakpoint\u2026";
 localizedStrings["Edit Local Override\u2026"] = "Edit Local Override\u2026";
 localizedStrings["Edit \u201C%s\u201D"] = "Edit \u201C%s\u201D";
+localizedStrings["Edit \u201Cbox-shadow\u201D"] = "Edit \u201Cbox-shadow\u201D";
 localizedStrings["Edit \u201Ccubic-bezier\u201D function"] = "Edit \u201Ccubic-bezier\u201D function";
 localizedStrings["Edit \u201Cspring\u201D function"] = "Edit \u201Cspring\u201D function";
 localizedStrings["Edit configuration"] = "Edit configuration";
@@ -679,6 +682,8 @@
 localizedStrings["Initiated"] = "Initiated";
 localizedStrings["Initiator"] = "Initiator";
 localizedStrings["Input: "] = "Input: ";
+/* Checkbox label for the inset of a CSS box shadow. */
+localizedStrings["Inset @ Box Shadow Editor"] = "Inset";
 localizedStrings["Inspector Bootstrap Script"] = "Inspector Bootstrap Script";
 localizedStrings["Inspector Override"] = "Inspector Override";
 localizedStrings["Inspector Style Sheet"] = "Inspector Style Sheet";
@@ -846,6 +851,10 @@
 localizedStrings["Observer Handlers:"] = "Observer Handlers:";
 localizedStrings["Observers:"] = "Observers:";
 localizedStrings["Off"] = "Off";
+/* Input label for the x-axis of the offset of a CSS box shadow */
+localizedStrings["Offset X @ Box Shadow Editor"] = "Offset X";
+/* Input label for the y-axis of the offset of a CSS box shadow */
+localizedStrings["Offset Y @ Box Shadow Editor"] = "Offset Y";
 localizedStrings["Once"] = "Once";
 localizedStrings["Online"] = "Online";
 localizedStrings["Only show resources with issues"] = "Only show resources with issues";
@@ -1148,6 +1157,8 @@
 localizedStrings["Specificity: (%d, %d, %d)"] = "Specificity: (%d, %d, %d)";
 localizedStrings["Specificity: No value for selected element"] = "Specificity: No value for selected element";
 localizedStrings["Spelling"] = "Spelling";
+/* Input label for the spread radius of a CSS box shadow */
+localizedStrings["Spread @ Box Shadow Editor"] = "Spread";
 localizedStrings["Staging:"] = "Staging:";
 localizedStrings["Stalled"] = "Stalled";
 localizedStrings["Start"] = "Start";

Added: trunk/Source/WebInspectorUI/UserInterface/Images/BoxShadow.svg (0 => 259170)


--- trunk/Source/WebInspectorUI/UserInterface/Images/BoxShadow.svg	                        (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Images/BoxShadow.svg	2020-03-29 02:56:05 UTC (rev 259170)
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2020 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <path d="M 4 2 V 4 H 2 V 14 H 12 V 12 H 14 V 2 Z M 5.5 10.5 V 3.5 H 12.5 V 10.5 Z"/>
+</svg>

Modified: trunk/Source/WebInspectorUI/UserInterface/Main.html (259169 => 259170)


--- trunk/Source/WebInspectorUI/UserInterface/Main.html	2020-03-29 02:54:09 UTC (rev 259169)
+++ trunk/Source/WebInspectorUI/UserInterface/Main.html	2020-03-29 02:56:05 UTC (rev 259170)
@@ -43,6 +43,7 @@
     <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
+    <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
     <link rel="stylesheet" href=""
@@ -366,6 +367,7 @@
     <script src=""
     <script src=""
     <script src=""
+    <script src=""
     <script src=""
     <script src=""
     <script src=""
@@ -605,6 +607,7 @@
     <script src=""
     <script src=""
     <script src=""
+    <script src=""
     <script src=""
     <script src=""
     <script src=""

Added: trunk/Source/WebInspectorUI/UserInterface/Models/BoxShadow.js (0 => 259170)


--- trunk/Source/WebInspectorUI/UserInterface/Models/BoxShadow.js	                        (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/BoxShadow.js	2020-03-29 02:56:05 UTC (rev 259170)
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.BoxShadow = class BoxShadow
+{
+    constructor(offsetX, offsetY, blurRadius, spreadRadius, inset, color)
+    {
+        console.assert(!offsetX || (typeof offsetX === "object" && !isNaN(offsetX.value) && offsetX.unit), offsetX);
+        console.assert(!offsetY || (typeof offsetY === "object" && !isNaN(offsetY.value) && offsetY.unit), offsetY);
+        console.assert(!blurRadius || (typeof blurRadius === "object" && blurRadius.value >= 0 && blurRadius.unit), blurRadius);
+        console.assert(!spreadRadius || (typeof spreadRadius === "object" && !isNaN(spreadRadius.value) && spreadRadius.unit), spreadRadius);
+        console.assert(!inset || typeof inset === "boolean", inset);
+        console.assert(!color || color instanceof WI.Color, color);
+
+        this._offsetX = offsetX || null;
+        this._offsetY = offsetY || null;
+        this._blurRadius = blurRadius || null;
+        this._spreadRadius = spreadRadius || null;
+        this._inset = !!inset;
+        this._color = color || null;
+    }
+
+    // Static
+
+    static fromString(cssString)
+    {
+        if (cssString === "none")
+            return new WI.BoxShadow;
+
+        let offsetX = null;
+        let offsetY = null;
+        let blurRadius = null;
+        let spreadRadius = null;
+        let inset = false;
+        let color = null;
+
+        let startIndex = 0;
+        let openParentheses = 0;
+        let numberComponentCount = 0;
+        for (let i = 0; i <= cssString.length; ++i) {
+            if (cssString[i] === "(") {
+                ++openParentheses;
+                continue;
+            }
+
+            if (cssString[i] === ")") {
+                --openParentheses;
+                continue;
+            }
+
+            if ((cssString[i] !== " " || openParentheses) && i !== cssString.length)
+                continue;
+
+            let component = cssString.substring(startIndex, i + 1).trim();
+
+            startIndex = i + 1;
+
+            if (!component.length)
+                continue;
+
+            if (component === "inset") {
+                if (inset)
+                    return null;
+                inset = true;
+                continue;
+            }
+
+            let possibleColor = WI.Color.fromString(component);
+            if (possibleColor) {
+                if (color)
+                    return null;
+                color = possibleColor;
+                continue;
+            }
+
+            let numberComponent = WI.BoxShadow.parseNumberComponent(component);
+            if (!numberComponent)
+                return null;
+
+            switch (++numberComponentCount) {
+            case 1:
+                offsetX = numberComponent;
+                break;
+
+            case 2:
+                offsetY = numberComponent;
+                break;
+
+            case 3:
+                blurRadius = numberComponent;
+                if (blurRadius.value < 0)
+                    return null;
+                break;
+
+            case 4:
+                spreadRadius = numberComponent;
+                break;
+
+            default:
+                return null;
+            }
+        }
+
+        if (!offsetX || !offsetY)
+            return null;
+
+        return new WI.BoxShadow(offsetX, offsetY, blurRadius, spreadRadius, inset, color);
+    }
+
+    static parseNumberComponent(string)
+    {
+        let value = parseFloat(string);
+        if (isNaN(value))
+            return null;
+
+        let unit = string.replace(value, "");
+        if (!unit) {
+            if (value)
+                return null;
+            unit = "px";
+        } else if (!WI.CSSCompletions.lengthUnits.has(unit))
+            return null;
+
+        return {unit, value};
+    }
+
+    // Public
+
+    get offsetX() { return this._offsetX; }
+    get offsetY() { return this._offsetY; }
+    get blurRadius() { return this._blurRadius; }
+    get spreadRadius() { return this._spreadRadius; }
+    get inset() { return this._inset; }
+    get color() { return this._color; }
+
+    copy()
+    {
+        return new WI.BoxShadow(this._offsetX, this._offsetY, this._blurRadius, this._spreadRadius, this._inset, this._color);
+    }
+
+    toString()
+    {
+        if (!this._offsetX || !this._offsetY)
+            return "none";
+
+        function stringifyNumberComponent({value, unit}) {
+            return value + (value ? unit : "");
+        }
+
+        let values = [
+            stringifyNumberComponent(this._offsetX),
+            stringifyNumberComponent(this._offsetY),
+        ];
+
+        if (this._blurRadius)
+            values.push(stringifyNumberComponent(this._blurRadius));
+
+        if (this._spreadRadius)
+            values.push(stringifyNumberComponent(this._spreadRadius));
+
+        if (this._color)
+            values.push(this._color.toString());
+
+        if (this._inset)
+            values.push("inset");
+
+        return values.join(" ");
+    }
+};

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/CSSCompletions.js (259169 => 259170)


--- trunk/Source/WebInspectorUI/UserInterface/Models/CSSCompletions.js	2020-03-29 02:54:09 UTC (rev 259169)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/CSSCompletions.js	2020-03-29 02:56:05 UTC (rev 259170)
@@ -357,3 +357,21 @@
 };
 
 WI.CSSCompletions.cssNameCompletions = null;
+
+WI.CSSCompletions.lengthUnits = new Set([
+    "ch",
+    "cm",
+    "em",
+    "ex",
+    "in",
+    "mm",
+    "pc",
+    "pt",
+    "px",
+    "q",
+    "rem",
+    "vh",
+    "vmax",
+    "vmin",
+    "vw",
+]);

Modified: trunk/Source/WebInspectorUI/UserInterface/Test.html (259169 => 259170)


--- trunk/Source/WebInspectorUI/UserInterface/Test.html	2020-03-29 02:54:09 UTC (rev 259169)
+++ trunk/Source/WebInspectorUI/UserInterface/Test.html	2020-03-29 02:56:05 UTC (rev 259170)
@@ -125,6 +125,7 @@
 
     <script src=""
     <script src=""
+    <script src=""
     <script src=""
     <script src=""
     <script src=""

Added: trunk/Source/WebInspectorUI/UserInterface/Views/BoxShadowEditor.css (0 => 259170)


--- trunk/Source/WebInspectorUI/UserInterface/Views/BoxShadowEditor.css	                        (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/BoxShadowEditor.css	2020-03-29 02:56:05 UTC (rev 259170)
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.box-shadow-editor {
+    max-width: var(--color-picker-width);
+}
+
+.box-shadow-editor > table {
+    width: 100%;
+    padding: 0 4px;
+    border-spacing: 0;
+}
+
+.box-shadow-editor > table > tr > th {
+    font-weight: bold;
+    text-align: end;
+    line-height: 23px;
+    vertical-align: top;
+    white-space: nowrap;
+    color: hsl(0, 0%, 34%);
+}
+
+.box-shadow-editor > table > tr > td {
+    -webkit-padding-start: 4px;
+}
+
+.box-shadow-editor > table > tr > td > input[type="text"] {
+    width: 100%;
+    padding: 3px 4px 2px;
+    font-family: Menlo, monospace;
+    text-align: end;
+    background-color: var(--background-color-code);
+    border: 1px solid var(--text-color-quaternary);
+    -webkit-appearance: none;
+}
+
+.box-shadow-editor > table > tr > td > input[type="range"] {
+    width: 120px;
+    height: 19px;
+    background-color: transparent;
+}
+
+.box-shadow-editor > table > tr.offset-x > td > svg {
+    -webkit-margin-start: 5px;
+}
+
+.box-shadow-editor > table > tr.offset-x > td > svg line.axis {
+    fill: none;
+    stroke: var(--text-color-quaternary);
+    stroke-width: 2;
+    stroke-linecap: round;
+}
+
+.box-shadow-editor > table > tr.offset-x > td > svg line:not(.axis) {
+    fill: none;
+    stroke: var(--glyph-color-active);
+    stroke-width: 2;
+    stroke-linecap: round;
+}
+
+.box-shadow-editor > table > tr.offset-x > td > svg circle {
+    r: 5px; /* keep in sync with `this._offsetSliderKnobRadius` */
+    fill: var(--glyph-color-active);
+}
+
+.box-shadow-editor > table > tr.inset > td {
+    vertical-align: top;
+}
+
+.box-shadow-editor > table > tr.inset > td > input[type="checkbox"] {
+    margin-top: 0.5em;
+}
+
+@media (prefers-color-scheme: dark) {
+    .box-shadow-editor > table > tr > th {
+        color: var(--text-color-secondary);
+    }
+}

Added: trunk/Source/WebInspectorUI/UserInterface/Views/BoxShadowEditor.js (0 => 259170)


--- trunk/Source/WebInspectorUI/UserInterface/Views/BoxShadowEditor.js	                        (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/BoxShadowEditor.js	2020-03-29 02:56:05 UTC (rev 259170)
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.BoxShadowEditor = class BoxShadowEditor extends WI.Object
+{
+    constructor()
+    {
+        super();
+
+        this._element = document.createElement("div");
+        this._element.classList.add("box-shadow-editor");
+
+        let tableElement = this._element.appendChild(document.createElement("table"));
+
+        function createInputRow(identifier, label) {
+            let id = `box-shadow-editor-${identifier}-input`;
+
+            let rowElement = tableElement.appendChild(document.createElement("tr"));
+            rowElement.className = identifier;
+
+            let headerElement = rowElement.appendChild(document.createElement("th"));
+
+            let labelElement = headerElement.appendChild(document.createElement("label"));
+            labelElement.setAttribute("for", id);
+            labelElement.textContent = label;
+
+            let dataElement = rowElement.appendChild(document.createElement("td"));
+
+            let inputElement = dataElement.appendChild(document.createElement("input"));
+            inputElement.id = id;
+            return {rowElement, inputElement};
+        }
+
+        function createSlider(rowElement, min, max) {
+            let dataElement = rowElement.appendChild(document.createElement("td"));
+
+            let rangeElement = dataElement.appendChild(document.createElement("input"));
+            rangeElement.type = "range";
+            rangeElement.min = min;
+            rangeElement.max = max;
+            return rangeElement;
+        }
+
+        let offsetXRow = createInputRow("offset-x", WI.UIString("Offset X", "Offset X @ Box Shadow Editor", "Input label for the x-axis of the offset of a CSS box shadow"));
+
+        this._offsetXInput = offsetXRow.inputElement;
+        this._offsetXInput.type = "text"
+        this._offsetXInput.addEventListener("input", this._handleOffsetXInputInput.bind(this));
+        this._offsetXInput.addEventListener("keydown", this._handleOffsetXInputKeyDown.bind(this));
+
+        let offsetSliderDataElement = offsetXRow.rowElement.appendChild(document.createElement("td"));
+        offsetSliderDataElement.setAttribute("rowspan", 3);
+
+        this._offsetSliderKnobRadius = 5; // keep in sync with `.box-shadow-editor > table > tr > td > svg circle`
+        this._offsetSliderAreaSize = 100;
+
+        const offsetSliderContainerSize = this._offsetSliderAreaSize + (this._offsetSliderKnobRadius * 2);
+
+        this._offsetSliderSVG = offsetSliderDataElement.appendChild(createSVGElement("svg"));
+        this._offsetSliderSVG.setAttribute("tabindex", 0);
+        this._offsetSliderSVG.setAttribute("width", offsetSliderContainerSize);
+        this._offsetSliderSVG.setAttribute("height", offsetSliderContainerSize);
+        this._offsetSliderSVG.addEventListener("mousedown", this);
+        this._offsetSliderSVG.addEventListener("keydown", this);
+
+        this._offsetSliderSVGMouseDownPoint = null;
+
+        let offsetSliderGroup = this._offsetSliderSVG.appendChild(createSVGElement("g"));
+        offsetSliderGroup.setAttribute("transform", `translate(${this._offsetSliderKnobRadius}, ${this._offsetSliderKnobRadius})`);
+
+        let offsetSliderXAxisLine = offsetSliderGroup.appendChild(createSVGElement("line"));
+        offsetSliderXAxisLine.classList.add("axis");
+        offsetSliderXAxisLine.setAttribute("x1", this._offsetSliderAreaSize / 2);
+        offsetSliderXAxisLine.setAttribute("y1", 0);
+        offsetSliderXAxisLine.setAttribute("x2", this._offsetSliderAreaSize / 2);
+        offsetSliderXAxisLine.setAttribute("y2", this._offsetSliderAreaSize);
+
+        let offsetSliderYAxisLine = offsetSliderGroup.appendChild(createSVGElement("line"));
+        offsetSliderYAxisLine.classList.add("axis");
+        offsetSliderYAxisLine.setAttribute("x1", 0);
+        offsetSliderYAxisLine.setAttribute("y1", this._offsetSliderAreaSize / 2);
+        offsetSliderYAxisLine.setAttribute("x2", this._offsetSliderAreaSize);
+        offsetSliderYAxisLine.setAttribute("y2", this._offsetSliderAreaSize / 2);
+
+        this._offsetSliderLine = offsetSliderGroup.appendChild(createSVGElement("line"));
+        this._offsetSliderLine.setAttribute("x1", this._offsetSliderAreaSize / 2);
+        this._offsetSliderLine.setAttribute("y1", this._offsetSliderAreaSize / 2);
+
+        this._offsetSliderKnob = offsetSliderGroup.appendChild(createSVGElement("circle"));
+
+        let offsetYRow = createInputRow("offset-y", WI.UIString("Offset Y", "Offset Y @ Box Shadow Editor", "Input label for the y-axis of the offset of a CSS box shadow"));
+
+        this._offsetYInput = offsetYRow.inputElement;
+        this._offsetYInput.type = "text"
+        this._offsetYInput.addEventListener("input", this._handleOffsetYInputInput.bind(this));
+        this._offsetYInput.addEventListener("keydown", this._handleOffsetYInputKeyDown.bind(this));
+
+        let insetRow = createInputRow("inset", WI.UIString("Inset", "Inset @ Box Shadow Editor", "Checkbox label for the inset of a CSS box shadow."));
+
+        this._insetCheckbox = insetRow.inputElement;
+        this._insetCheckbox.type = "checkbox";
+        this._insetCheckbox.addEventListener("change", this._handleInsetCheckboxChange.bind(this));
+
+        let blurRadiusRow = createInputRow("blur-radius", WI.UIString("Blur", "Blur @ Box Shadow Editor", "Input label for the blur radius of a CSS box shadow"));
+
+        this._blurRadiusInput = blurRadiusRow.inputElement;
+        this._blurRadiusInput.type = "text"
+        this._blurRadiusInput.addEventListener("input", this._handleBlurRadiusInputInput.bind(this));
+        this._blurRadiusInput.addEventListener("keydown", this._handleBlurRadiusInputKeyDown.bind(this));
+        this._blurRadiusInput.min = 0;
+
+        this._blurRadiusSlider = createSlider(blurRadiusRow.rowElement, 0, 100);
+        this._blurRadiusSlider.addEventListener("input", this._handleBlurRadiusSliderInput.bind(this));
+
+        let spreadRadiusRow = createInputRow("spread-radius", WI.UIString("Spread", "Spread @ Box Shadow Editor", "Input label for the spread radius of a CSS box shadow"));
+
+        this._spreadRadiusInput = spreadRadiusRow.inputElement;
+        this._spreadRadiusInput.type = "text"
+        this._spreadRadiusInput.addEventListener("input", this._handleSpreadRadiusInputInput.bind(this));
+        this._spreadRadiusInput.addEventListener("keydown", this._handleSpreadRadiusInputKeyDown.bind(this));
+
+        this._spreadRadiusSlider = createSlider(spreadRadiusRow.rowElement, -50, 50);
+        this._spreadRadiusSlider.addEventListener("input", this._handleSpreadRadiusSliderInput.bind(this));
+
+        this._colorPicker = new WI.ColorPicker;
+        this._colorPicker.addEventListener(WI.ColorPicker.Event.ColorChanged, this._handleColorChanged, this);
+        this._element.appendChild(this._colorPicker.element);
+
+        this.boxShadow = new WI.BoxShadow;
+
+        WI.addWindowKeydownListener(this);
+    }
+
+    // Public
+
+    get element() { return this._element; }
+
+    get boxShadow()
+    {
+        return this._boxShadow;
+    }
+
+    set boxShadow(boxShadow)
+    {
+        console.assert(boxShadow instanceof WI.BoxShadow);
+
+        this._boxShadow = boxShadow;
+
+        let offsetX = this._boxShadow?.offsetX || {value: 0, unit: ""};
+        let offsetY = this._boxShadow?.offsetY || {value: 0, unit: ""};
+        this._offsetXInput.value = offsetX.value + offsetX.unit;
+        this._offsetYInput.value = offsetY.value + offsetY.unit;
+
+        let offsetSliderCenter = this._offsetSliderAreaSize / 2;
+        let offsetSliderX = Number.constrain(offsetX.value + offsetSliderCenter, 0, this._offsetSliderAreaSize);
+        let offsetSliderY = Number.constrain(offsetY.value + offsetSliderCenter, 0, this._offsetSliderAreaSize);
+
+        this._offsetSliderLine.setAttribute("x2", offsetSliderX);
+        this._offsetSliderKnob.setAttribute("cx", offsetSliderX);
+
+        this._offsetSliderLine.setAttribute("y2", offsetSliderY);
+        this._offsetSliderKnob.setAttribute("cy", offsetSliderY);
+
+        let blurRadius = this._boxShadow?.blurRadius || {value: 0, unit: ""};
+        this._blurRadiusInput.value = blurRadius.value + blurRadius.unit;
+        this._blurRadiusSlider.value = blurRadius.value;
+
+        let spreadRadius = this._boxShadow?.spreadRadius || {value: 0, unit: ""};
+        this._spreadRadiusInput.value = spreadRadius.value + spreadRadius.unit;
+        this._spreadRadiusSlider.value = spreadRadius.value;
+
+        let inset = this._boxShadow?.inset || false;
+        this._insetCheckbox.checked = inset;
+
+        let color = this._boxShadow?.color || WI.Color.fromString("transparent");
+        this._colorPicker.color = color;
+    }
+
+    // Protected
+
+    handleEvent(event)
+    {
+        switch (event.type) {
+        case "keydown":
+            console.assert(event.target === this._offsetSliderSVG);
+            this._handleOffsetSliderSVGKeyDown(event);
+            return;
+
+        case "mousedown":
+            console.assert(event.target === this._offsetSliderSVG);
+            this._handleOffsetSliderSVGMouseDown(event);
+            return;
+
+        case "mousemove":
+            this._handleWindowMouseMove(event);
+            return;
+
+        case "mouseup":
+            this._handleWindowMouseUp(event);
+            return;
+        }
+
+        console.assert();
+    }
+
+    // Private
+
+    _updateBoxShadow({offsetX, offsetY, blurRadius, spreadRadius, inset, color})
+    {
+        let change = false;
+
+        if (!offsetX)
+            offsetX = this._boxShadow.offsetX;
+        else if (!Object.shallowEqual(offsetX, this._boxShadow.offsetX))
+            change = true;
+
+        if (!offsetY)
+            offsetY = this._boxShadow.offsetY;
+        else if (!Object.shallowEqual(offsetY, this._boxShadow.offsetY))
+            change = true;
+
+        if (!blurRadius)
+            blurRadius = this._boxShadow.blurRadius;
+        else if (!Object.shallowEqual(blurRadius, this._boxShadow.blurRadius))
+            change = true;
+
+        if (!spreadRadius)
+            spreadRadius = this._boxShadow.spreadRadius;
+        else if (!Object.shallowEqual(spreadRadius, this._boxShadow.spreadRadius))
+            change = true;
+
+        if (inset === undefined)
+            inset = this._boxShadow.inset;
+        else if (inset !== this._boxShadow.inset)
+            change = true;
+
+        if (!color)
+            color = this._boxShadow.color;
+        else if (color.toString() !== this._boxShadow.color?.toString())
+            change = true;
+
+        if (!change)
+            return;
+
+        this.boxShadow = new WI.BoxShadow(offsetX, offsetY, blurRadius, spreadRadius, inset, color);
+
+        this.dispatchEventToListeners(WI.BoxShadowEditor.Event.BoxShadowChanged, {boxShadow: this._boxShadow});
+    }
+
+    _updateBoxShadowOffsetFromSliderMouseEvent(event, saveMouseDownPoint)
+    {
+        let point = WI.Point.fromEventInElement(event, this._offsetSliderSVG);
+        point.x = Number.constrain(point.x - this._offsetSliderKnobRadius, 0, this._offsetSliderAreaSize);
+        point.y = Number.constrain(point.y - this._offsetSliderKnobRadius, 0, this._offsetSliderAreaSize);
+
+        if (saveMouseDownPoint)
+            this._offsetSliderSVGMouseDownPoint = point;
+
+        if (event.shiftKey && this._offsetSliderSVGMouseDownPoint) {
+            if (Math.abs(this._offsetSliderSVGMouseDownPoint.x - point.x) > Math.abs(this._offsetSliderSVGMouseDownPoint.y - point.y))
+                point.y = this._offsetSliderSVGMouseDownPoint.y;
+            else
+                point.x = this._offsetSliderSVGMouseDownPoint.x;
+        }
+
+        let offsetSliderCenter = this._offsetSliderAreaSize / 2;
+
+        this._updateBoxShadow({
+            offsetX: {
+                value: point.x - offsetSliderCenter,
+                unit: this._boxShadow.offsetX.unit,
+            },
+            offsetY: {
+                value: point.y - offsetSliderCenter,
+                unit: this._boxShadow.offsetY.unit,
+            },
+        });
+    }
+
+    _determineShiftForEvent(event)
+    {
+        let shift = 0;
+        if (event.key === "ArrowUp")
+            shift = 1;
+        else if (event.key === "ArrowDown")
+            shift = -1;
+
+        if (!shift)
+            return NaN;
+
+        if (event.metaKey)
+            shift *= 100;
+        else if (event.shiftKey)
+            shift *= 10;
+        else if (event.altKey)
+            shift /= 10;
+
+        event.preventDefault();
+
+        return shift;
+    }
+
+    _handleOffsetSliderSVGKeyDown(event) {
+        let shiftX = 0;
+        let shiftY = 0;
+
+        switch (event.keyCode) {
+        case WI.KeyboardShortcut.Key.Up.keyCode:
+            shiftY = -1;
+            break;
+
+        case WI.KeyboardShortcut.Key.Right.keyCode:
+            shiftX = 1;
+            break;
+
+        case WI.KeyboardShortcut.Key.Down.keyCode:
+            shiftY = 1;
+            break;
+
+        case WI.KeyboardShortcut.Key.Left.keyCode:
+            shiftX = -1;
+            break;
+        }
+
+        if (!shiftX && !shiftY)
+            return false;
+
+        let multiplier = 1;
+
+        if (event.shiftKey)
+            multiplier = 10;
+        else if (event.altKey)
+            multiplier = 0.1;
+
+        shiftX *= multiplier;
+        shiftY *= multiplier;
+
+        let offsetValueLimit = this._offsetSliderAreaSize / 2;
+
+        this._updateBoxShadow({
+            offsetX: {
+                value: Number.constrain(this._boxShadow.offsetX.value + shiftX, -1 * offsetValueLimit, offsetValueLimit).maxDecimals(1),
+                unit: this._boxShadow.offsetX.unit,
+            },
+            offsetY: {
+                value: Number.constrain(this._boxShadow.offsetY.value + shiftY, -1 * offsetValueLimit, offsetValueLimit).maxDecimals(1),
+                unit: this._boxShadow.offsetY.unit,
+            },
+        });
+
+    }
+
+    _handleOffsetSliderSVGMouseDown(event)
+    {
+        if (event.button !== 0)
+            return;
+
+        event.stop();
+
+        this._offsetSliderSVG.focus();
+
+        window.addEventListener("mousemove", this, true);
+        window.addEventListener("mouseup", this, true);
+
+        this._updateBoxShadowOffsetFromSliderMouseEvent(event, true);
+    }
+
+    _handleWindowMouseMove(event)
+    {
+        this._updateBoxShadowOffsetFromSliderMouseEvent(event);
+    }
+
+    _handleWindowMouseUp(event)
+    {
+        this._offsetSliderSVGMouseDownPoint = null;
+
+        window.removeEventListener("mousemove", this, true);
+        window.removeEventListener("mouseup", this, true);
+    }
+
+    _handleOffsetXInputInput(event)
+    {
+        this._updateBoxShadow({
+            offsetX: WI.BoxShadow.parseNumberComponent(this._offsetXInput.value),
+        });
+    }
+
+    _handleOffsetXInputKeyDown(event)
+    {
+        let shift = this._determineShiftForEvent(event);
+        if (isNaN(shift))
+            return;
+
+        this._updateBoxShadow({
+            offsetX: {
+                value: (this._boxShadow.offsetX.value + shift).maxDecimals(1),
+                unit: this._boxShadow.offsetX.unit,
+            },
+        });
+    }
+
+    _handleOffsetYInputInput(event)
+    {
+        this._updateBoxShadow({
+            offsetY: WI.BoxShadow.parseNumberComponent(this._offsetYInput.value),
+        });
+    }
+
+    _handleOffsetYInputKeyDown(event)
+    {
+        let shift = this._determineShiftForEvent(event);
+        if (isNaN(shift))
+            return;
+
+        this._updateBoxShadow({
+            offsetY: {
+                value: (this._boxShadow.offsetY.value + shift).maxDecimals(1),
+                unit: this._boxShadow.offsetY.unit,
+            },
+        });
+    }
+
+    _handleBlurRadiusInputInput(event)
+    {
+        this._updateBoxShadow({
+            blurRadius: WI.BoxShadow.parseNumberComponent(this._blurRadiusInput.value),
+        });
+    }
+
+    _handleBlurRadiusInputKeyDown(event)
+    {
+        let shift = this._determineShiftForEvent(event);
+        if (isNaN(shift))
+            return;
+
+        this._updateBoxShadow({
+            blurRadius: {
+                value: (this._boxShadow.blurRadius.value + shift).maxDecimals(1),
+                unit: this._boxShadow.blurRadius.unit,
+            }
+        });
+    }
+
+    _handleBlurRadiusSliderInput(event)
+    {
+        this._updateBoxShadow({
+            blurRadius: {
+                value: this._blurRadiusSlider.valueAsNumber,
+                unit: this._boxShadow.blurRadius.unit,
+            }
+        });
+    }
+
+    _handleSpreadRadiusInputInput(event)
+    {
+        this._updateBoxShadow({
+            spreadRadius: WI.BoxShadow.parseNumberComponent(this._spreadRadiusInput.value),
+        });
+    }
+
+    _handleSpreadRadiusInputKeyDown(event)
+    {
+        let shift = this._determineShiftForEvent(event);
+        if (isNaN(shift))
+            return;
+
+        this._updateBoxShadow({
+            spreadRadius: {
+                value: (this._boxShadow.spreadRadius.value + shift).maxDecimals(1),
+                unit: this._boxShadow.spreadRadius.unit,
+            }
+        });
+    }
+
+    _handleSpreadRadiusSliderInput(event)
+    {
+        this._updateBoxShadow({
+            spreadRadius: {
+                value: this._spreadRadiusSlider.valueAsNumber,
+                unit: this._boxShadow.spreadRadius.unit,
+            }
+        });
+    }
+
+    _handleInsetCheckboxChange(event)
+    {
+        this._updateBoxShadow({
+            inset: !!this._insetCheckbox.checked,
+        });
+    }
+
+    _handleColorChanged(event)
+    {
+        this._updateBoxShadow({
+            color: this._colorPicker.color,
+        });
+    }
+};
+
+WI.BoxShadowEditor.Event = {
+    BoxShadowChanged: "box-shadow-editor-box-shadow-changed"
+};

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ColorPicker.css (259169 => 259170)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ColorPicker.css	2020-03-29 02:54:09 UTC (rev 259169)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ColorPicker.css	2020-03-29 02:56:05 UTC (rev 259170)
@@ -29,7 +29,6 @@
     height: 236px;
     padding: 5px;
 
-    --color-picker-width: 256px;
     --color-picker-hue-offset-start: 41px;
     --color-picker-opacity-offset-start: 18px;
 }

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/InlineSwatch.css (259169 => 259170)


--- trunk/Source/WebInspectorUI/UserInterface/Views/InlineSwatch.css	2020-03-29 02:54:09 UTC (rev 259169)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/InlineSwatch.css	2020-03-29 02:56:05 UTC (rev 259170)
@@ -31,12 +31,16 @@
     height: 1em;
     margin-right: 3px;
     vertical-align: -2px;
-    background-color: var(--background-color);
     border-radius: 2px;
     overflow: hidden;
     cursor: default;
 }
 
+.inline-swatch:not(.box-shadow),
+.inline-swatch.box-shadow:matches(:hover, :active) {
+    background-color: var(--background-color-content);
+}
+
 .inline-swatch:matches(.color, .gradient) {
     /* Make a checkered background for transparent colors to show against. */
     background-image: linear-gradient(to bottom, hsl(0, 0%, 80%), hsl(0, 0%, 80%)),
@@ -50,15 +54,15 @@
     color: var(--glyph-color-active);
 }
 
-.inline-swatch:matches(.bezier, .spring, .variable) {
+.inline-swatch:matches(.bezier, .box-shadow, .spring, .variable) {
     margin-right: 2px;
 }
 
-.inline-swatch:not(.read-only):matches(.bezier, .spring, .variable):hover {
+.inline-swatch:not(.read-only):matches(.bezier, .box-shadow, .spring, .variable):hover {
     filter: brightness(0.9);
 }
 
-.inline-swatch:not(.read-only):matches(.bezier, .spring, .variable):active {
+.inline-swatch:not(.read-only):matches(.bezier, .box-shadow, .spring, .variable):active {
     filter: brightness(0.8);
 }
 
@@ -90,7 +94,7 @@
     border-color: hsl(0, 0%, 25%);
 }
 
-.inline-swatch:matches(.bezier, .spring, .variable) > span {
+.inline-swatch:matches(.bezier, .box-shadow, .spring, .variable) > span {
     display: none;
 }
 
@@ -114,3 +118,9 @@
 .inline-swatch-variable-popover .CodeMirror pre {
     padding: 0 3px;
 }
+
+@media (prefers-color-scheme: dark) {
+    .inline-swatch.box-shadow > svg {
+        filter: invert();
+    }
+}

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/InlineSwatch.js (259169 => 259170)


--- trunk/Source/WebInspectorUI/UserInterface/Views/InlineSwatch.js	2020-03-29 02:54:09 UTC (rev 259169)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/InlineSwatch.js	2020-03-29 02:56:05 UTC (rev 259170)
@@ -32,37 +32,59 @@
 
         this._type = type;
 
-        if (this._type === WI.InlineSwatch.Type.Bezier || this._type === WI.InlineSwatch.Type.Spring)
+        switch (this._type) {
+        case WI.InlineSwatch.Type.Bezier:
+        case WI.InlineSwatch.Type.Spring:
             this._swatchElement = WI.ImageUtilities.useSVGSymbol("Images/CubicBezier.svg");
-        else if (this._type === WI.InlineSwatch.Type.Variable)
+            break;
+
+        case WI.InlineSwatch.Type.BoxShadow:
+            this._swatchElement = WI.ImageUtilities.useSVGSymbol("Images/BoxShadow.svg");
+            break;
+
+        case WI.InlineSwatch.Type.Variable:
             this._swatchElement = WI.ImageUtilities.useSVGSymbol("Images/CSSVariable.svg");
-        else
+            break;
+
+        default:
             this._swatchElement = document.createElement("span");
+            break;
+        }
 
-        this._swatchElement.classList.add("inline-swatch", this._type.split("-").lastValue);
+        this._swatchElement.classList.add("inline-swatch", this._type.replace("inline-swatch-type-", ""));
 
         if (readOnly)
             this._swatchElement.classList.add("read-only");
         else {
             switch (this._type) {
+            case WI.InlineSwatch.Type.Bezier:
+                this._swatchElement.title = WI.UIString("Edit \u201Ccubic-bezier\u201D function");
+                break;
+
+            case WI.InlineSwatch.Type.BoxShadow:
+                this._swatchElement.title = WI.UIString("Edit \u201Cbox-shadow\u201D");
+                break;
+
             case WI.InlineSwatch.Type.Color:
                 // Handled later by _updateSwatch.
                 break;
+
             case WI.InlineSwatch.Type.Gradient:
                 this._swatchElement.title = WI.UIString("Edit custom gradient");
                 break;
-            case WI.InlineSwatch.Type.Bezier:
-                this._swatchElement.title = WI.UIString("Edit \u201Ccubic-bezier\u201D function");
+
+            case WI.InlineSwatch.Type.Image:
+                this._swatchElement.title = WI.UIString("View Image");
                 break;
+
             case WI.InlineSwatch.Type.Spring:
                 this._swatchElement.title = WI.UIString("Edit \u201Cspring\u201D function");
                 break;
+
             case WI.InlineSwatch.Type.Variable:
                 this._swatchElement.title = WI.UIString("Click to view variable value\nShift-click to replace variable with value");
                 break;
-            case WI.InlineSwatch.Type.Image:
-                this._swatchElement.title = WI.UIString("View Image");
-                break;
+
             default:
                 WI.reportInternalError(`Unknown InlineSwatch type "${type}"`);
                 break;
@@ -127,12 +149,14 @@
         switch (this._type) {
         case WI.InlineSwatch.Type.Bezier:
             return WI.CubicBezier.fromString("linear");
+        case WI.InlineSwatch.Type.BoxShadow:
+            return WI.BoxShadow.fromString("none");
+        case WI.InlineSwatch.Type.Color:
+            return WI.Color.fromString("white");
         case WI.InlineSwatch.Type.Gradient:
             return WI.Gradient.fromString("linear-gradient(transparent, transparent)");
         case WI.InlineSwatch.Type.Spring:
             return WI.Spring.fromString("1 100 10 0");
-        case WI.InlineSwatch.Type.Color:
-            return WI.Color.fromString("white");
         default:
             return null;
         }
@@ -209,6 +233,16 @@
 
         this._valueEditor = null;
         switch (this._type) {
+        case WI.InlineSwatch.Type.Bezier:
+            this._valueEditor = new WI.BezierEditor;
+            this._valueEditor.addEventListener(WI.BezierEditor.Event.BezierChanged, this._valueEditorValueDidChange, this);
+            break;
+
+        case WI.InlineSwatch.Type.BoxShadow:
+            this._valueEditor = new WI.BoxShadowEditor;
+            this._valueEditor.addEventListener(WI.BoxShadowEditor.Event.BoxShadowChanged, this._valueEditorValueDidChange, this);
+            break;
+
         case WI.InlineSwatch.Type.Color:
             this._valueEditor = new WI.ColorPicker;
             this._valueEditor.addEventListener(WI.ColorPicker.Event.ColorChanged, this._valueEditorValueDidChange, this);
@@ -220,9 +254,15 @@
             this._valueEditor.addEventListener(WI.GradientEditor.Event.ColorPickerToggled, (event) => popover.update());
             break;
 
-        case WI.InlineSwatch.Type.Bezier:
-            this._valueEditor = new WI.BezierEditor;
-            this._valueEditor.addEventListener(WI.BezierEditor.Event.BezierChanged, this._valueEditorValueDidChange, this);
+        case WI.InlineSwatch.Type.Image:
+            if (value.src) {
+                this._valueEditor = {};
+                this._valueEditor.element = document.createElement("img");
+                this._valueEditor.element.src = ""
+                this._valueEditor.element.classList.add("show-grid");
+                this._valueEditor.element.style.setProperty("max-width", "50vw");
+                this._valueEditor.element.style.setProperty("max-height", "50vh");
+            }
             break;
 
         case WI.InlineSwatch.Type.Spring:
@@ -244,17 +284,6 @@
                 popover.update();
             });
             break;
-
-        case WI.InlineSwatch.Type.Image:
-            if (value.src) {
-                this._valueEditor = {};
-                this._valueEditor.element = document.createElement("img");
-                this._valueEditor.element.src = ""
-                this._valueEditor.element.classList.add("show-grid");
-                this._valueEditor.element.style.setProperty("max-width", "50vw");
-                this._valueEditor.element.style.setProperty("max-height", "50vh");
-            }
-            break;
         }
 
         if (!this._valueEditor)
@@ -266,6 +295,14 @@
         this.dispatchEventToListeners(WI.InlineSwatch.Event.Activated);
 
         switch (this._type) {
+        case WI.InlineSwatch.Type.Bezier:
+            this._valueEditor.bezier = value;
+            break;
+
+        case WI.InlineSwatch.Type.BoxShadow:
+            this._valueEditor.boxShadow = value;
+            break;
+
         case WI.InlineSwatch.Type.Color:
             this._valueEditor.color = value;
             this._valueEditor.focus();
@@ -275,10 +312,6 @@
             this._valueEditor.gradient = value;
             break;
 
-        case WI.InlineSwatch.Type.Bezier:
-            this._valueEditor.bezier = value;
-            break;
-
         case WI.InlineSwatch.Type.Spring:
             this._valueEditor.spring = value;
             break;
@@ -308,14 +341,27 @@
 
     _valueEditorValueDidChange(event)
     {
-        if (this._type === WI.InlineSwatch.Type.Color)
+        switch (this._type) {
+        case WI.InlineSwatch.Type.BoxShadow:
+            this._value = event.data.boxShadow;
+            break;
+
+        case WI.InlineSwatch.Type.Bezier:
+            this._value = event.data.bezier;
+            break;
+
+        case WI.InlineSwatch.Type.Color:
             this._value = event.data.color;
-        else if (this._type === WI.InlineSwatch.Type.Gradient)
+            break;
+
+        case WI.InlineSwatch.Type.Gradient:
             this._value = event.data.gradient;
-        else if (this._type === WI.InlineSwatch.Type.Bezier)
-            this._value = event.data.bezier;
-        else if (this._type === WI.InlineSwatch.Type.Spring)
+            break;
+
+        case WI.InlineSwatch.Type.Spring:
             this._value = event.data.spring;
+            break;
+        }
 
         this._updateSwatch();
     }
@@ -454,6 +500,7 @@
     Color: "inline-swatch-type-color",
     Gradient: "inline-swatch-type-gradient",
     Bezier: "inline-swatch-type-bezier",
+    BoxShadow: "inline-swatch-type-box-shadow",
     Spring: "inline-swatch-type-spring",
     Variable: "inline-swatch-type-variable",
     Image: "inline-swatch-type-image",

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js (259169 => 259170)


--- trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js	2020-03-29 02:54:09 UTC (rev 259169)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js	2020-03-29 02:56:05 UTC (rev 259170)
@@ -552,6 +552,9 @@
     {
         // FIXME: <https://webkit.org/b/178636> Web Inspector: Styles: Make inline widgets work with CSS functions (var(), calc(), etc.)
 
+        if (this._property.name === "box-shadow")
+            return this._addBoxShadowTokens(tokens);
+
         tokens = this._addVariableTokens(tokens);
 
         if (this._property.isVariable || WI.CSSKeywordCompletions.isColorAwareProperty(this._property.name)) {
@@ -589,7 +592,7 @@
                 }
 
                 let rawTokens = tokens.slice(gradientStartIndex, i + 1);
-                let text = rawTokens.map((token) => token.value).join("");
+                let text = this._resolveVariables(rawTokens.map((token) => token.value).join(""));
                 let gradient = WI.Gradient.fromString(text);
                 if (gradient)
                     newTokens.push(this._createInlineSwatch(WI.InlineSwatch.Type.Gradient, rawTokens, gradient));
@@ -607,6 +610,7 @@
     _addColorTokens(tokens)
     {
         let newTokens = [];
+        let openParentheses = 0;
 
         let pushPossibleColorToken = (text, ...rawTokens) => {
             let color = WI.Color.fromString(text);
@@ -630,12 +634,21 @@
                 // Color keyword
                 pushPossibleColorToken(token.value, token);
             } else if (!isNaN(colorFunctionStartIndex)) {
-                // Color Function end
-                if (token.value !== ")")
+                if (token.value === "(") {
+                    ++openParentheses;
                     continue;
+                }
 
+                if (token.value === ")") {
+                    --openParentheses;
+                    if (openParentheses)
+                        continue;
+                }
+
+                // Color Function end
+
                 let rawTokens = tokens.slice(colorFunctionStartIndex, i + 1);
-                let text = rawTokens.map((token) => token.value).join("");
+                let text = this._resolveVariables(rawTokens.map((token) => token.value).join(""));
                 pushPossibleColorToken(text, ...rawTokens);
                 colorFunctionStartIndex = NaN;
             } else
@@ -665,7 +678,7 @@
                     continue;
 
                 let rawTokens = tokens.slice(startIndex, i + 1);
-                let text = rawTokens.map((token) => token.value).join("");
+                let text = this._resolveVariables(rawTokens.map((token) => token.value).join(""));
 
                 let valueObject;
                 let inlineSwatchType;
@@ -692,6 +705,61 @@
         return newTokens;
     }
 
+    _addBoxShadowTokens(tokens)
+    {
+        let newTokens = [];
+        let startIndex = 0;
+        let openParentheses = 0;
+
+        for (let i = 0; i <= tokens.length; i++) {
+            let token = tokens[i];
+            if (i === tokens.length || (token.value === "," && !openParentheses)) {
+                let rawTokens = tokens.slice(startIndex, i);
+
+                let firstNonWhitespaceIndex = Infinity;
+                let lastNonWhitespaceIndex = 0;
+                for (let j = 0; j < rawTokens.length; ++j) {
+                    if (/\s/.test(rawTokens[j].value))
+                        continue;
+
+                    firstNonWhitespaceIndex = Math.min(firstNonWhitespaceIndex, j);
+                    lastNonWhitespaceIndex = Math.max(lastNonWhitespaceIndex, j);
+                }
+
+                let nonWhitespaceTokens = rawTokens.slice(firstNonWhitespaceIndex, lastNonWhitespaceIndex + 1);
+
+                newTokens.pushAll(rawTokens.slice(0, firstNonWhitespaceIndex));
+
+                let text = this._resolveVariables(nonWhitespaceTokens.map((rawToken) => rawToken.value).join(""));
+                let boxShadow = WI.BoxShadow.fromString(text);
+                if (boxShadow)
+                    newTokens.push(this._createInlineSwatch(WI.InlineSwatch.Type.BoxShadow, nonWhitespaceTokens, boxShadow));
+                else
+                    newTokens.pushAll(nonWhitespaceTokens);
+
+                newTokens.pushAll(rawTokens.slice(lastNonWhitespaceIndex + 1));
+
+                if (token)
+                    newTokens.push(token);
+
+                startIndex = i + 1;
+                continue;
+            }
+
+            if (token.value === "(") {
+                ++openParentheses;
+                continue;
+            }
+
+            if (token.value === ")") {
+                --openParentheses;
+                continue;
+            }
+        }
+
+        return newTokens;
+    }
+
     _addVariableTokens(tokens)
     {
         let newTokens = [];
@@ -742,6 +810,11 @@
         return newTokens;
     }
 
+    _resolveVariables(cssText)
+    {
+        return cssText.replace(/var\(--[^\)]+\)/g, (match) => this._property.ownerStyle.nodeStyles.computedStyle.resolveVariableValue(match) || match);
+    }
+
     _handleNameChange()
     {
         this._property.name = this._nameElement.textContent.trim();

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css (259169 => 259170)


--- trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css	2020-03-29 02:54:09 UTC (rev 259169)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css	2020-03-29 02:56:05 UTC (rev 259170)
@@ -196,6 +196,8 @@
     --diff-deletion-text-color: hsl(0, 100%, 35%);
     --diff-deletion-background-color: hsl(5, 100%, 94%);
     --diff-addition-border-color: hsl(90, 100%, 40%);
+
+    --color-picker-width: 256px;
 }
 
 body.window-inactive {
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to