DevTools: Add an option to copy element styles

Adds a `Copy styles` menuitem to DOM elements, which copies all active
user-authored styles of the element.

Change-Id: Id78e94eecb506393dfd20f2da4771ece3a72eddc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1665428
Commit-Queue: Joel Einbinder <[email protected]>
Reviewed-by: Erik Luo <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#672606}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 3acba91a70ebe1c21a0c29759309abf8aaa2ac99
diff --git a/front_end/elements/ElementsTreeElement.js b/front_end/elements/ElementsTreeElement.js
index 7221f93..7cb3077 100644
--- a/front_end/elements/ElementsTreeElement.js
+++ b/front_end/elements/ElementsTreeElement.js
@@ -526,6 +526,7 @@
       section.appendItem(Common.UIString('Copy selector'), this._copyCSSPath.bind(this));
       section.appendItem(
           Common.UIString('Copy JS path'), this._copyJSPath.bind(this), !Elements.DOMPath.canGetJSPath(this._node));
+      section.appendItem(ls`Copy styles`, this._copyStyles.bind(this));
     }
     if (!isShadowRoot) {
       section.appendItem(Common.UIString('Copy XPath'), this._copyXPath.bind(this));
@@ -1641,6 +1642,30 @@
     InspectorFrontendHost.copyText(Elements.DOMPath.xPath(this._node, false));
   }
 
+  async _copyStyles() {
+    const node = this._node;
+    const cssModel = node.domModel().cssModel();
+    const cascade = await cssModel.cachedMatchedCascadeForNode(node);
+    if (!cascade)
+      return;
+    /** @type {!Array<string>} */
+    const lines = [];
+    for (const style of cascade.nodeStyles().reverse()) {
+      for (const property of style.leadingProperties()) {
+        if (!property.parsedOk || property.disabled || !property.activeInStyle() || property.implicit)
+          continue;
+        if (cascade.isInherited(style) && !SDK.cssMetadata().isPropertyInherited(property.name))
+          continue;
+        if (style.parentRule && style.parentRule.isUserAgent())
+          continue;
+        if (cascade.propertyState(property) !== SDK.CSSMatchedStyles.PropertyState.Active)
+          continue;
+        lines.push(`${property.name}: ${property.value};`);
+      }
+    }
+    InspectorFrontendHost.copyText(lines.join('\n'));
+  }
+
   _highlightSearchResults() {
     if (!this._searchQuery || !this._searchHighlightsVisible)
       return;
diff --git a/front_end/elements/elements_strings.grdp b/front_end/elements/elements_strings.grdp
index a625ffc..f057d55 100644
--- a/front_end/elements/elements_strings.grdp
+++ b/front_end/elements/elements_strings.grdp
@@ -99,6 +99,9 @@
   <message name="IDS_DEVTOOLS_4757fe07fd492a8be0ea6a760d683d6e" desc="">
     position
   </message>
+  <message name="IDS_DEVTOOLS_4a27b8d01ae86c80074e4c57be48f8c6" desc="">
+    Copy styles
+  </message>
   <message name="IDS_DEVTOOLS_4c1c7d945fcd04d68bd3b3f1d99a55a1" desc="">
     Styles
   </message>