Web Inspector: Elements: Computed: show shorthand properties in addition to longhand ones
https://bugs.webkit.org/show_bug.cgi?id=200554

Reviewed by Matt Baker.

Source/WebCore:

Test: inspector/css/getComputedStyleForNode.html

Include additional `CSS.CSSComputedStyleProperty` items for shorthand CSS properties when
calling `CSS.getComputedStyleForNode`.

* inspector/InspectorStyleSheet.h:
* inspector/InspectorStyleSheet.cpp:
(WebCore::InspectorStyle::buildArrayForComputedStyle const):
(WebCore::InspectorStyle::collectProperties const): Added.
(WebCore::InspectorStyle::styleWithProperties const):
(WebCore::InspectorStyle::populateAllProperties const): Deleted.

Source/WebInspectorUI:

Replace the "Show All [ ]" checkbox header option in the "Properties" section with a filter
icon that controls both "Show All" and "Prefer Shorthands", the latter of which will cause
any longhand properties to be hidden and their respective shorthands to be shown.

* UserInterface/Models/CSSProperty.js:
(WI.CSSProperty):
(WI.CSSProperty.prototype.update):
(WI.CSSProperty.prototype.get isVariable): Added.
(WI.CSSProperty.prototype.get isShorthand): Added.
(WI.CSSProperty.prototype.get shorthandPropertyNames): Added.
(WI.CSSProperty.prototype.get variable): Deleted.

* UserInterface/Views/ComputedStyleDetailsPanel.js:
(WI.ComputedStyleDetailsPanel):
(WI.ComputedStyleDetailsPanel.prototype.applyFilter):
(WI.ComputedStyleDetailsPanel.prototype.initialLayout):
(WI.ComputedStyleDetailsPanel.prototype._computedStyleShowAllCheckboxValueChanged): Deleted.
* UserInterface/Views/ComputedStyleDetailsPanel.css:
(.sidebar > .panel.details.css-style > .content > .computed > .details-section): Added.
(.sidebar > .panel.details.css-style > .content > .computed > .details-section > .content): Added.
(.sidebar > .panel.details.css-style > .content > .computed .spreadsheet-style-declaration-editor): Added.
(.sidebar > .panel.details.css-style > .content > .computed .property): Added.
(.sidebar > .panel.details.css-style > .content > .computed .property .go-to-arrow): Added.
(.sidebar > .panel.details.css-style > .content > .computed .property:not(:hover) .go-to-arrow): Added.
(.sidebar > .panel.details.css-style > .content > .computed .details-section:matches(.computed-style-properties, .computed-style-variables)): Deleted.
(.sidebar > .panel.details.css-style > .content > .computed .details-section:matches(.computed-style-properties, .computed-style-variables) .property): Deleted.
(.sidebar > .panel.details.css-style > .content > .computed .details-section:matches(.computed-style-properties, .computed-style-variables) .property .go-to-arrow): Deleted.
(.sidebar > .panel.details.css-style > .content > .computed .details-section:matches(.computed-style-properties, .computed-style-variables) .property:not(:hover) .go-to-arrow): Deleted.
(.sidebar > .panel.details.css-style > .content > .computed .details-section:matches(.computed-style-properties, .computed-style-variables) > .content): Deleted.

* UserInterface/Views/ComputedStyleSection.js:
(WI.ComputedStyleSection):
(WI.ComputedStyleSection.prototype.set showsShorthandsInsteadOfLonghands): Added.
(WI.ComputedStyleSection.prototype.get propertiesToRender):
(WI.ComputedStyleSection.prototype.set propertyVisibilityMode): Deleted.

* UserInterface/Views/SpreadsheetStyleProperty.js:
(WI.SpreadsheetStyleProperty.prototype.update):
(WI.SpreadsheetStyleProperty.prototype._replaceSpecialTokens):

* Localizations/en.lproj/localizedStrings.js:

LayoutTests:

* inspector/css/getComputedStyleForNode.html: Added.
* inspector/css/getComputedStyleForNode-expected.txt: Added.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251038 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 4cc5231..217691a 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,13 @@
+2019-10-11  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Elements: Computed: show shorthand properties in addition to longhand ones
+        https://bugs.webkit.org/show_bug.cgi?id=200554
+
+        Reviewed by Matt Baker.
+
+        * inspector/css/getComputedStyleForNode.html: Added.
+        * inspector/css/getComputedStyleForNode-expected.txt: Added.
+
 2019-10-11  Andy Estes  <aestes@apple.com>
 
         [iOS] Unreviewed test gardening
diff --git a/LayoutTests/inspector/css/getComputedStyleForNode-expected.txt b/LayoutTests/inspector/css/getComputedStyleForNode-expected.txt
new file mode 100644
index 0000000..01da0da
--- /dev/null
+++ b/LayoutTests/inspector/css/getComputedStyleForNode-expected.txt
@@ -0,0 +1,16 @@
+Testing CSS.getComputedStyleForNode.
+
+Enabling Page domain...
+Enabling CSS domain...
+Requesting document...
+Querying for "#test"...
+Getting matched styles for "#test"...
+
+PASS: Property "display" should have value "block".
+
+PASS: Property "margin" should have value "1px 2px 3px 4px".
+PASS: Property "margin-top" should have value "1px".
+PASS: Property "margin-right" should have value "2px".
+PASS: Property "margin-bottom" should have value "3px".
+PASS: Property "margin-left" should have value "4px".
+
diff --git a/LayoutTests/inspector/css/getComputedStyleForNode.html b/LayoutTests/inspector/css/getComputedStyleForNode.html
new file mode 100644
index 0000000..af6bca8
--- /dev/null
+++ b/LayoutTests/inspector/css/getComputedStyleForNode.html
@@ -0,0 +1,89 @@
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/protocol-test.js"></script>
+<style>
+#test {
+    margin: 1px 2px 3px 4px;
+}
+</style>
+<script>
+function test()
+{
+    Promise.resolve()
+    .then(() => {
+        ProtocolTest.log("Enabling Page domain...");
+        return InspectorProtocol.awaitCommand({
+            method: "Page.enable",
+            params: {},
+        });
+    })
+    .then(() => {
+        ProtocolTest.log("Enabling CSS domain...");
+        return InspectorProtocol.awaitCommand({
+            method: "CSS.enable",
+            params: {},
+        });
+    })
+    .then(() => {
+        ProtocolTest.log("Requesting document...");
+        return InspectorProtocol.awaitCommand({
+            method: "DOM.getDocument",
+            params: {},
+        });
+    })
+    .then(({root}) => {
+        ProtocolTest.log("Querying for \"#test\"...");
+        return InspectorProtocol.awaitCommand({
+            method: "DOM.querySelector",
+            params: {
+                nodeId: root.nodeId,
+                selector: "#test",
+            },
+        });
+    })
+    .then(({nodeId}) => {
+        ProtocolTest.log("Getting matched styles for \"#test\"...");
+        return InspectorProtocol.awaitCommand({
+            method: "CSS.getComputedStyleForNode",
+            params: {
+                nodeId,
+            },
+        });
+    })
+    .then(({computedStyle}) => {
+        let map = new Map;
+        for (let {name, value} of computedStyle) {
+            ProtocolTest.assert(!map.has(name), `Should have only one value for "${name}": ${value} vs ${map.get(name)}.`);
+            map.set(name, value);
+        }
+
+        ProtocolTest.newline();
+
+        function checkProperty(property, expected) {
+            ProtocolTest.expectEqual(map.get(property), expected, `Property "${property}" should have value "${expected}".`);
+        }
+
+        checkProperty("display", "block");
+
+        ProtocolTest.newline();
+
+        checkProperty("margin", "1px 2px 3px 4px");
+        checkProperty("margin-top", "1px");
+        checkProperty("margin-right", "2px");
+        checkProperty("margin-bottom", "3px");
+        checkProperty("margin-left", "4px");
+    })
+    .catch((error) => {
+        ProtocolTest.log(error);
+    })
+    .finally(() => {
+        ProtocolTest.completeTest();
+    });
+}
+</script>
+</head>
+<body onLoad="runTest()">
+<p>Testing CSS.getComputedStyleForNode.</p>
+<div id="test"></div>
+</body>
+</html>
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index c99645e..069782a 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,22 @@
+2019-10-11  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Elements: Computed: show shorthand properties in addition to longhand ones
+        https://bugs.webkit.org/show_bug.cgi?id=200554
+
+        Reviewed by Matt Baker.
+
+        Test: inspector/css/getComputedStyleForNode.html
+
+        Include additional `CSS.CSSComputedStyleProperty` items for shorthand CSS properties when
+        calling `CSS.getComputedStyleForNode`.
+
+        * inspector/InspectorStyleSheet.h:
+        * inspector/InspectorStyleSheet.cpp:
+        (WebCore::InspectorStyle::buildArrayForComputedStyle const):
+        (WebCore::InspectorStyle::collectProperties const): Added.
+        (WebCore::InspectorStyle::styleWithProperties const):
+        (WebCore::InspectorStyle::populateAllProperties const): Deleted.
+
 2019-10-08  Ryosuke Niwa  <rniwa@webkit.org>
 
         Make WebInspector's remote debug EventLoop code into RunLoop
diff --git a/Source/WebCore/inspector/InspectorStyleSheet.cpp b/Source/WebCore/inspector/InspectorStyleSheet.cpp
index dbe5553..e5a8bf5 100644
--- a/Source/WebCore/inspector/InspectorStyleSheet.cpp
+++ b/Source/WebCore/inspector/InspectorStyleSheet.cpp
@@ -541,10 +541,7 @@
 Ref<JSON::ArrayOf<Inspector::Protocol::CSS::CSSComputedStyleProperty>> InspectorStyle::buildArrayForComputedStyle() const
 {
     auto result = JSON::ArrayOf<Inspector::Protocol::CSS::CSSComputedStyleProperty>::create();
-    Vector<InspectorStyleProperty> properties;
-    populateAllProperties(&properties);
-
-    for (auto& property : properties) {
+    for (auto& property : collectProperties(true)) {
         const CSSPropertySourceData& propertyEntry = property.sourceData;
         auto entry = Inspector::Protocol::CSS::CSSComputedStyleProperty::create()
             .setName(propertyEntry.name)
@@ -552,7 +549,6 @@
             .release();
         result->addItem(WTFMove(entry));
     }
-
     return result;
 }
 
@@ -579,8 +575,9 @@
     return name.convertToASCIILowercase();
 }
 
-void InspectorStyle::populateAllProperties(Vector<InspectorStyleProperty>* result) const
+Vector<InspectorStyleProperty> InspectorStyle::collectProperties(bool includeAll) const
 {
+    Vector<InspectorStyleProperty> result;
     HashSet<String> sourcePropertyNames;
 
     auto sourceData = extractSourceData();
@@ -592,7 +589,7 @@
         for (auto& sourceData : *sourcePropertyData) {
             InspectorStyleProperty p(sourceData, true, sourceData.disabled);
             p.setRawTextFromStyleDeclaration(styleDeclaration);
-            result->append(p);
+            result.append(p);
             sourcePropertyNames.add(lowercasePropertyName(sourceData.name));
         }
     }
@@ -600,14 +597,33 @@
     for (int i = 0, size = m_style->length(); i < size; ++i) {
         String name = m_style->item(i);
         if (sourcePropertyNames.add(lowercasePropertyName(name)))
-            result->append(InspectorStyleProperty(CSSPropertySourceData(name, m_style->getPropertyValue(name), !m_style->getPropertyPriority(name).isEmpty(), false, true, SourceRange()), false, false));
+            result.append(InspectorStyleProperty(CSSPropertySourceData(name, m_style->getPropertyValue(name), !m_style->getPropertyPriority(name).isEmpty(), false, true, SourceRange()), false, false));
     }
+
+    if (includeAll) {
+        for (auto i = firstCSSProperty; i < lastCSSProperty; ++i) {
+            auto id = convertToCSSPropertyID(i);
+            if (isInternalCSSProperty(id) || !isEnabledCSSProperty(id))
+                continue;
+
+            auto name = getPropertyNameString(id);
+            if (!sourcePropertyNames.add(lowercasePropertyName(name)))
+                continue;
+
+            auto value = m_style->getPropertyValue(name);
+            if (value.isEmpty())
+                continue;
+
+            result.append(InspectorStyleProperty(CSSPropertySourceData(name, value, !m_style->getPropertyPriority(name).isEmpty(), false, true, SourceRange()), false, false));
+        }
+    }
+
+    return result;
 }
 
 Ref<Inspector::Protocol::CSS::CSSStyle> InspectorStyle::styleWithProperties() const
 {
-    Vector<InspectorStyleProperty> properties;
-    populateAllProperties(&properties);
+    auto properties = collectProperties(false);
 
     auto propertiesObject = JSON::ArrayOf<Inspector::Protocol::CSS::CSSProperty>::create();
     auto shorthandEntries = ArrayOf<Inspector::Protocol::CSS::ShorthandEntry>::create();
diff --git a/Source/WebCore/inspector/InspectorStyleSheet.h b/Source/WebCore/inspector/InspectorStyleSheet.h
index eb48f67..1ecee23 100644
--- a/Source/WebCore/inspector/InspectorStyleSheet.h
+++ b/Source/WebCore/inspector/InspectorStyleSheet.h
@@ -135,7 +135,7 @@
 private:
     InspectorStyle(const InspectorCSSId& styleId, Ref<CSSStyleDeclaration>&&, InspectorStyleSheet* parentStyleSheet);
 
-    void populateAllProperties(Vector<InspectorStyleProperty>* result) const;
+    Vector<InspectorStyleProperty> collectProperties(bool includeAll) const;
     Ref<Inspector::Protocol::CSS::CSSStyle> styleWithProperties() const;
     RefPtr<CSSRuleSourceData> extractSourceData() const;
     String shorthandValue(const String& shorthandProperty) const;
diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog
index 441c23d..0e783c1 100644
--- a/Source/WebInspectorUI/ChangeLog
+++ b/Source/WebInspectorUI/ChangeLog
@@ -1,3 +1,52 @@
+2019-10-11  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Elements: Computed: show shorthand properties in addition to longhand ones
+        https://bugs.webkit.org/show_bug.cgi?id=200554
+
+        Reviewed by Matt Baker.
+
+        Replace the "Show All [ ]" checkbox header option in the "Properties" section with a filter
+        icon that controls both "Show All" and "Prefer Shorthands", the latter of which will cause
+        any longhand properties to be hidden and their respective shorthands to be shown.
+
+        * UserInterface/Models/CSSProperty.js:
+        (WI.CSSProperty):
+        (WI.CSSProperty.prototype.update):
+        (WI.CSSProperty.prototype.get isVariable): Added.
+        (WI.CSSProperty.prototype.get isShorthand): Added.
+        (WI.CSSProperty.prototype.get shorthandPropertyNames): Added.
+        (WI.CSSProperty.prototype.get variable): Deleted.
+
+        * UserInterface/Views/ComputedStyleDetailsPanel.js:
+        (WI.ComputedStyleDetailsPanel):
+        (WI.ComputedStyleDetailsPanel.prototype.applyFilter):
+        (WI.ComputedStyleDetailsPanel.prototype.initialLayout):
+        (WI.ComputedStyleDetailsPanel.prototype._computedStyleShowAllCheckboxValueChanged): Deleted.
+        * UserInterface/Views/ComputedStyleDetailsPanel.css:
+        (.sidebar > .panel.details.css-style > .content > .computed > .details-section): Added.
+        (.sidebar > .panel.details.css-style > .content > .computed > .details-section > .content): Added.
+        (.sidebar > .panel.details.css-style > .content > .computed .spreadsheet-style-declaration-editor): Added.
+        (.sidebar > .panel.details.css-style > .content > .computed .property): Added.
+        (.sidebar > .panel.details.css-style > .content > .computed .property .go-to-arrow): Added.
+        (.sidebar > .panel.details.css-style > .content > .computed .property:not(:hover) .go-to-arrow): Added.
+        (.sidebar > .panel.details.css-style > .content > .computed .details-section:matches(.computed-style-properties, .computed-style-variables)): Deleted.
+        (.sidebar > .panel.details.css-style > .content > .computed .details-section:matches(.computed-style-properties, .computed-style-variables) .property): Deleted.
+        (.sidebar > .panel.details.css-style > .content > .computed .details-section:matches(.computed-style-properties, .computed-style-variables) .property .go-to-arrow): Deleted.
+        (.sidebar > .panel.details.css-style > .content > .computed .details-section:matches(.computed-style-properties, .computed-style-variables) .property:not(:hover) .go-to-arrow): Deleted.
+        (.sidebar > .panel.details.css-style > .content > .computed .details-section:matches(.computed-style-properties, .computed-style-variables) > .content): Deleted.
+
+        * UserInterface/Views/ComputedStyleSection.js:
+        (WI.ComputedStyleSection):
+        (WI.ComputedStyleSection.prototype.set showsShorthandsInsteadOfLonghands): Added.
+        (WI.ComputedStyleSection.prototype.get propertiesToRender):
+        (WI.ComputedStyleSection.prototype.set propertyVisibilityMode): Deleted.
+
+        * UserInterface/Views/SpreadsheetStyleProperty.js:
+        (WI.SpreadsheetStyleProperty.prototype.update):
+        (WI.SpreadsheetStyleProperty.prototype._replaceSpecialTokens):
+
+        * Localizations/en.lproj/localizedStrings.js:
+
 2019-10-10  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: Local Resource Overrides: UI for overriding image and font resource content
diff --git a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
index b6ae45f..33ccb91 100644
--- a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
+++ b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
@@ -822,6 +822,7 @@
 localizedStrings["Power Efficient Playback"] = "Power Efficient Playback";
 localizedStrings["Power Efficient Playback Started"] = "Power Efficient Playback Started";
 localizedStrings["Power Efficient Playback Stopped"] = "Power Efficient Playback Stopped";
+localizedStrings["Prefer Shorthands"] = "Prefer Shorthands";
 localizedStrings["Prefer indent using:"] = "Prefer indent using:";
 localizedStrings["Preserve Log"] = "Preserve Log";
 localizedStrings["Press %s to import a test or result file"] = "Press %s to import a test or result file";
diff --git a/Source/WebInspectorUI/UserInterface/Models/CSSProperty.js b/Source/WebInspectorUI/UserInterface/Models/CSSProperty.js
index d812d81..52d6d8e 100644
--- a/Source/WebInspectorUI/UserInterface/Models/CSSProperty.js
+++ b/Source/WebInspectorUI/UserInterface/Models/CSSProperty.js
@@ -115,9 +115,12 @@
         this._anonymous = anonymous;
         this._inherited = WI.CSSProperty.isInheritedPropertyName(name);
         this._valid = valid;
-        this._variable = name.startsWith("--");
+        this._isVariable = name.startsWith("--");
         this._styleSheetTextRange = styleSheetTextRange || null;
 
+        this._isShorthand = undefined;
+        this._shorthandPropertyNames = undefined;
+
         this._relatedShorthandProperty = null;
         this._relatedLonghandProperties = [];
 
@@ -324,7 +327,7 @@
     get anonymous() { return this._anonymous; }
     get inherited() { return this._inherited; }
     get valid() { return this._valid; }
-    get variable() { return this._variable; }
+    get isVariable() { return this._isVariable; }
     get styleSheetTextRange() { return this._styleSheetTextRange; }
 
     get initialState()
@@ -383,6 +386,28 @@
         this._relatedLonghandProperties = [];
     }
 
+    get isShorthand()
+    {
+        if (this._isShorthand === undefined) {
+            this._isShorthand = WI.CSSCompletions.cssNameCompletions.isShorthandPropertyName(this._name);
+            if (this._isShorthand) {
+                let longhands = WI.CSSKeywordCompletions.LonghandNamesForShorthandProperty.get(this._name);
+                if (longhands && longhands.length === 1)
+                    this._isShorthand = false;
+            }
+        }
+        return this._isShorthand;
+    }
+
+    get shorthandPropertyNames()
+    {
+        if (!this._shorthandPropertyNames) {
+            this._shorthandPropertyNames = WI.CSSCompletions.cssNameCompletions.shorthandsForLonghand(this._name);
+            this._shorthandPropertyNames.remove("all");
+        }
+        return this._shorthandPropertyNames;
+    }
+
     hasOtherVendorNameOrKeyword()
     {
         if ("_hasOtherVendorNameOrKeyword" in this)
diff --git a/Source/WebInspectorUI/UserInterface/Views/ComputedStyleDetailsPanel.css b/Source/WebInspectorUI/UserInterface/Views/ComputedStyleDetailsPanel.css
index 630db70c6..021f04d 100644
--- a/Source/WebInspectorUI/UserInterface/Views/ComputedStyleDetailsPanel.css
+++ b/Source/WebInspectorUI/UserInterface/Views/ComputedStyleDetailsPanel.css
@@ -24,29 +24,38 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+.sidebar > .panel.details.css-style > .content > .computed > .details-section {
+    --disclosure-button-size: 15px;
+}
+
 .sidebar > .panel.details.css-style > .content > .computed > .details-section:not(.collapsed) > :matches(.header, .content) {
     background-color: var(--background-color);
 }
 
-.sidebar > .panel.details.css-style > .content > .computed .details-section:matches(.computed-style-properties, .computed-style-variables) {
-    --disclosure-button-size: 15px;
+.sidebar > .panel.details.css-style > .content > .computed > .details-section > .content {
+    font: 12px -webkit-system-font, sans-serif;
 }
 
-.sidebar > .panel.details.css-style > .content > .computed .details-section:matches(.computed-style-properties, .computed-style-variables) .property {
+.sidebar > .panel.details.css-style > .content > .computed > .details-section.computed-style-properties > .header > .options.filter {
+    width: 13px;
+    height: 13px;
+}
+
+.sidebar > .panel.details.css-style > .content > .computed .spreadsheet-style-declaration-editor {
+    padding-bottom: 3px;
+}
+
+.sidebar > .panel.details.css-style > .content > .computed .property {
     position: relative;
 }
 
-.sidebar > .panel.details.css-style > .content > .computed .details-section:matches(.computed-style-properties, .computed-style-variables) .property .go-to-arrow {
+.sidebar > .panel.details.css-style > .content > .computed .property .go-to-arrow {
     position: absolute;
     bottom: 0;
     width: var(--disclosure-button-size);
     height: var(--disclosure-button-size);
 }
 
-.sidebar > .panel.details.css-style > .content > .computed .details-section:matches(.computed-style-properties, .computed-style-variables) .property:not(:hover) .go-to-arrow {
+.sidebar > .panel.details.css-style > .content > .computed .property:not(:hover) .go-to-arrow {
     display: none;
 }
-
-.sidebar > .panel.details.css-style > .content > .computed .details-section:matches(.computed-style-properties, .computed-style-variables) > .content {
-    font: 12px -webkit-system-font, sans-serif;
-}
diff --git a/Source/WebInspectorUI/UserInterface/Views/ComputedStyleDetailsPanel.js b/Source/WebInspectorUI/UserInterface/Views/ComputedStyleDetailsPanel.js
index bde139d..f54b5db 100644
--- a/Source/WebInspectorUI/UserInterface/Views/ComputedStyleDetailsPanel.js
+++ b/Source/WebInspectorUI/UserInterface/Views/ComputedStyleDetailsPanel.js
@@ -30,6 +30,7 @@
         super(delegate, WI.ComputedStyleDetailsPanel.StyleClassName, "computed", WI.UIString("Styles \u2014 Computed"));
 
         this._computedStyleShowAllSetting = new WI.Setting("computed-style-show-all", false);
+        this._computedStylePreferShorthandsSetting = new WI.Setting("computed-style-use-shorthands", false);
 
         this._filterText = null;
     }
@@ -89,25 +90,27 @@
 
         this.element.appendChild(boxModelSection.element);
 
-        let computedStyleShowAllLabel = document.createElement("label");
-        computedStyleShowAllLabel.textContent = WI.UIString("Show All");
+        let propertyFiltersElement = WI.ImageUtilities.useSVGSymbol("Images/FilterFieldGlyph.svg", "filter");
+        WI.addMouseDownContextMenuHandlers(propertyFiltersElement, (contextMenu) => {
+            contextMenu.appendCheckboxItem(WI.UIString("Show All"), () => {
+                this._computedStyleShowAllSetting.value = !this._computedStyleShowAllSetting.value;
+            }, this._computedStyleShowAllSetting.value);
 
-        this._computedStyleShowAllCheckbox = document.createElement("input");
-        this._computedStyleShowAllCheckbox.type = "checkbox";
-        this._computedStyleShowAllCheckbox.checked = this._computedStyleShowAllSetting.value;
-        this._computedStyleShowAllCheckbox.addEventListener("change", this._computedStyleShowAllCheckboxValueChanged.bind(this));
-        computedStyleShowAllLabel.appendChild(this._computedStyleShowAllCheckbox);
+            contextMenu.appendCheckboxItem(WI.UIString("Prefer Shorthands"), () => {
+                this._computedStylePreferShorthandsSetting.value = !this._computedStylePreferShorthandsSetting.value;
+            }, this._computedStylePreferShorthandsSetting.value);
+        });
 
         this._computedStyleSection = new WI.ComputedStyleSection(this);
-        this._computedStyleSection.propertyVisibilityMode = WI.ComputedStyleSection.PropertyVisibilityMode.HideVariables;
         this._computedStyleSection.addEventListener(WI.ComputedStyleSection.Event.FilterApplied, this._handleEditorFilterApplied, this);
         this._computedStyleSection.showsImplicitProperties = this._computedStyleShowAllSetting.value;
+        this._computedStyleSection.showsShorthandsInsteadOfLonghands = this._computedStylePreferShorthandsSetting.value;
         this._computedStyleSection.alwaysShowPropertyNames = ["display", "width", "height"];
         this._computedStyleSection.hideFilterNonMatchingProperties = true;
 
         let propertiesRow = new WI.DetailsSectionRow;
         let propertiesGroup = new WI.DetailsSectionGroup([propertiesRow]);
-        this._propertiesSection = new WI.DetailsSection("computed-style-properties", WI.UIString("Properties"), [propertiesGroup], computedStyleShowAllLabel);
+        this._propertiesSection = new WI.DetailsSection("computed-style-properties", WI.UIString("Properties"), [propertiesGroup], propertyFiltersElement);
         this._propertiesSection.addEventListener(WI.DetailsSection.Event.CollapsedStateChanged, this._handlePropertiesSectionCollapsedStateChanged, this);
 
         this.addSubview(this._computedStyleSection);
@@ -132,6 +135,9 @@
 
         this.element.appendChild(this._propertiesSection.element);
         this.element.appendChild(this._variablesSection.element);
+
+        this._computedStyleShowAllSetting.addEventListener(WI.Setting.Event.Changed, this._handleShowAllSettingChanged, this);
+        this._computedStylePreferShorthandsSetting.addEventListener(WI.Setting.Event.Changed, this._handleUseShorthandsSettingChanged, this);
     }
 
     filterDidChange(filterBar)
@@ -160,13 +166,6 @@
         return result;
     }
 
-    _computedStyleShowAllCheckboxValueChanged(event)
-    {
-        let checked = this._computedStyleShowAllCheckbox.checked;
-        this._computedStyleShowAllSetting.value = checked;
-        this._computedStyleSection.showsImplicitProperties = checked;
-    }
-
     _handlePropertiesSectionCollapsedStateChanged(event)
     {
         if (event && event.data && !event.data.collapsed)
@@ -190,6 +189,16 @@
         if (section)
             section.element.classList.toggle("hidden", !event.data.matches);
     }
+
+    _handleShowAllSettingChanged(event)
+    {
+        this._computedStyleSection.showsImplicitProperties = this._computedStyleShowAllSetting.value;
+    }
+
+    _handleUseShorthandsSettingChanged(event)
+    {
+        this._computedStyleSection.showsShorthandsInsteadOfLonghands = this._computedStylePreferShorthandsSetting.value;
+    }
 };
 
 WI.ComputedStyleDetailsPanel.StyleClassName = "computed";
diff --git a/Source/WebInspectorUI/UserInterface/Views/ComputedStyleSection.js b/Source/WebInspectorUI/UserInterface/Views/ComputedStyleSection.js
index 486fa31..ece8d62 100644
--- a/Source/WebInspectorUI/UserInterface/Views/ComputedStyleSection.js
+++ b/Source/WebInspectorUI/UserInterface/Views/ComputedStyleSection.js
@@ -38,8 +38,8 @@
         this._propertyViews = [];
 
         this._showsImplicitProperties = false;
+        this._showsShorthandsInsteadOfLonghands = false;
         this._alwaysShowPropertyNames = new Set;
-        this._propertyVisibilityMode = WI.ComputedStyleSection.PropertyVisibilityMode.ShowAll;
         this._hideFilterNonMatchingProperties = false;
         this._filterText = null;
     }
@@ -91,19 +91,19 @@
         this.needsLayout();
     }
 
-    set alwaysShowPropertyNames(propertyNames)
+    set showsShorthandsInsteadOfLonghands(value)
     {
-        this._alwaysShowPropertyNames = new Set(propertyNames);
+        if (value === this._showsShorthandsInsteadOfLonghands)
+            return;
+
+        this._showsShorthandsInsteadOfLonghands = value;
 
         this.needsLayout();
     }
 
-    set propertyVisibilityMode(propertyVisibilityMode)
+    set alwaysShowPropertyNames(propertyNames)
     {
-        if (this._propertyVisibilityMode === propertyVisibilityMode)
-            return;
-
-        this._propertyVisibilityMode = propertyVisibilityMode;
+        this._alwaysShowPropertyNames = new Set(propertyNames);
 
         this.needsLayout();
     }
@@ -132,13 +132,19 @@
         properties.sort((a, b) => a.name.extendedLocaleCompare(b.name));
 
         return properties.filter((property) => {
-            if (!property.variable && this._propertyVisibilityMode === WI.ComputedStyleSection.PropertyVisibilityMode.HideNonVariables)
+            if (this._alwaysShowPropertyNames.has(property.canonicalName))
+                return true;
+
+            if (property.implicit && !this._showsImplicitProperties)
                 return false;
 
-            if (property.variable && this._propertyVisibilityMode === WI.ComputedStyleSection.PropertyVisibilityMode.HideVariables)
+            if (this._showsShorthandsInsteadOfLonghands) {
+                if (property.shorthandPropertyNames.length)
+                    return false;
+            } else if (property.isShorthand)
                 return false;
 
-            return !property.implicit || this._showsImplicitProperties || this._alwaysShowPropertyNames.has(property.canonicalName);
+            return true;
         });
     }
 
@@ -265,9 +271,3 @@
 };
 
 WI.ComputedStyleSection.StyleClassName = "computed-style-section";
-
-WI.ComputedStyleSection.PropertyVisibilityMode = {
-    ShowAll: Symbol("variable-visibility-show-all"),
-    HideVariables: Symbol("variable-visibility-hide-variables"),
-    HideNonVariables: Symbol("variable-visibility-hide-non-variables"),
-};
diff --git a/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js b/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js
index 144c112..d0b3ee4 100644
--- a/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js
+++ b/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js
@@ -212,7 +212,7 @@
         } else
             this._contentElement.append(" */");
 
-        if (!this._property.implicit && this._property.ownerStyle.type === WI.CSSStyleDeclaration.Type.Computed) {
+        if (!this._property.implicit && this._property.ownerStyle.type === WI.CSSStyleDeclaration.Type.Computed && !this._property.isShorthand) {
             let effectiveProperty = this._property.ownerStyle.nodeStyles.effectivePropertyForName(this._property.name);
             if (effectiveProperty && !effectiveProperty.styleSheetTextRange)
                 effectiveProperty = effectiveProperty.relatedShorthandProperty;
@@ -540,12 +540,12 @@
 
         tokens = this._addVariableTokens(tokens);
 
-        if (this._property.variable || WI.CSSKeywordCompletions.isColorAwareProperty(this._property.name)) {
+        if (this._property.isVariable || WI.CSSKeywordCompletions.isColorAwareProperty(this._property.name)) {
             tokens = this._addGradientTokens(tokens);
             tokens = this._addColorTokens(tokens);
         }
 
-        if (this._property.variable || WI.CSSKeywordCompletions.isTimingFunctionAwareProperty(this._property.name)) {
+        if (this._property.isVariable || WI.CSSKeywordCompletions.isTimingFunctionAwareProperty(this._property.name)) {
             tokens = this._addTimingFunctionTokens(tokens, "cubic-bezier");
             tokens = this._addTimingFunctionTokens(tokens, "spring");
         }