diff --git a/devtools/client/inspector/changes/ChangesView.js b/devtools/client/inspector/changes/ChangesView.js index d3d28edeb253..8cc0a4d87609 100644 --- a/devtools/client/inspector/changes/ChangesView.js +++ b/devtools/client/inspector/changes/ChangesView.js @@ -40,6 +40,7 @@ class ChangesView { this.onChangesFront = this.onChangesFront.bind(this); this.onContextMenu = this.onContextMenu.bind(this); this.onCopy = this.onCopy.bind(this); + this.onCopyAllChanges = this.copyAllChanges.bind(this); this.onCopyRule = this.copyRule.bind(this); this.destroy = this.destroy.bind(this); @@ -58,6 +59,7 @@ class ChangesView { const changesApp = ChangesApp({ onContextMenu: this.onContextMenu, onCopy: this.onCopy, + onCopyAllChanges: this.onCopyAllChanges, onCopyRule: this.onCopyRule, }); @@ -106,6 +108,14 @@ class ChangesView { } } + /** + * Handler for the "Copy All Changes" button. Simple wrapper that just calls + * |this.copyChanges()| with no filters in order to trigger default operation. + */ + copyAllChanges() { + this.copyChanges(); + } + /** * Handler for the "Copy Changes" option from the context menu. * Builds a CSS text with the aggregated changes and copies it to the clipboard. diff --git a/devtools/client/inspector/changes/components/ChangesApp.js b/devtools/client/inspector/changes/components/ChangesApp.js index 984f09522733..301e08c22bb0 100644 --- a/devtools/client/inspector/changes/components/ChangesApp.js +++ b/devtools/client/inspector/changes/components/ChangesApp.js @@ -23,6 +23,8 @@ class ChangesApp extends PureComponent { onContextMenu: PropTypes.func.isRequired, // Event handler for "copy" event onCopy: PropTypes.func.isRequired, + // Event handler for click on "Copy All Changes" button + onCopyAllChanges: PropTypes.func.isRequired, // Event handler for click on "Copy Rule" button onCopyRule: PropTypes.func.isRequired, }; @@ -32,6 +34,17 @@ class ChangesApp extends PureComponent { super(props); } + renderCopyAllChangesButton() { + return dom.button( + { + className: "changes__copy-all-changes-button", + onClick: this.props.onCopyAllChanges, + title: getStr("changes.contextmenu.copyAllChangesDescription"), + }, + getStr("changes.contextmenu.copyAllChanges") + ); + } + renderCopyButton(ruleId) { return dom.button( { @@ -190,6 +203,7 @@ class ChangesApp extends PureComponent { onCopy: this.props.onCopy, }, !hasChanges && this.renderEmptyState(), + hasChanges && this.renderCopyAllChangesButton(), hasChanges && this.renderDiff(this.props.changesTree) ); } diff --git a/devtools/client/inspector/changes/selectors/changes.js b/devtools/client/inspector/changes/selectors/changes.js index 744c68df5857..25c56034594b 100644 --- a/devtools/client/inspector/changes/selectors/changes.js +++ b/devtools/client/inspector/changes/selectors/changes.js @@ -225,10 +225,11 @@ function getChangesStylesheet(state, filter) { return Object.entries(changeTree).reduce((stylesheetText, [sourceId, source]) => { const { href, rules } = source; // Write code comment with source origin - stylesheetText += `/* ${getSourceForDisplay(source)} | ${href} */\n`; + stylesheetText += `\n/* ${getSourceForDisplay(source)} | ${href} */\n`; // Write CSS rules stylesheetText += Object.entries(rules).reduce((str, [ruleId, rule]) => { - str += writeRule(ruleId, rule, 0); + // Add a new like only after top-level rules (level == 0) + str += writeRule(ruleId, rule, 0) + "\n"; return str; }, ""); diff --git a/devtools/client/inspector/changes/test/unit/test_changes_stylesheet.js b/devtools/client/inspector/changes/test/unit/test_changes_stylesheet.js index 7e2fb5bfa19f..0227ab1adbee 100644 --- a/devtools/client/inspector/changes/test/unit/test_changes_stylesheet.js +++ b/devtools/client/inspector/changes/test/unit/test_changes_stylesheet.js @@ -10,7 +10,7 @@ const { getChangesStylesheet } = require("devtools/client/inspector/changes/sele const { CHANGES_STATE } = require("resource://test/mocks"); -// Wrap multi-line string in backticks and trim to ensure exact string check in test. +// Wrap multi-line string in backticks to ensure exact check in test, including new lines. const STYLESHEET_FOR_ANCESTOR = ` /* Inline #0 | http://localhost:5000/at-rules-nested.html */ @@ -22,9 +22,9 @@ const STYLESHEET_FOR_ANCESTOR = ` } } } -`.trim(); +`; -// Wrap multi-line string in backticks and trim to ensure exact string check in test. +// Wrap multi-line string in backticks to ensure exact check in test, including new lines. const STYLESHEET_FOR_DESCENDANT = ` /* Inline #0 | http://localhost:5000/at-rules-nested.html */ @@ -32,7 +32,7 @@ body { /* background-color: royalblue; */ background-color: red; } -`.trim(); +`; add_test(() => { info("Check stylesheet generated for the first ancestor in the CSS rule tree."); diff --git a/devtools/client/locales/en-US/changes.properties b/devtools/client/locales/en-US/changes.properties index 571e7468a605..352de201aa28 100644 --- a/devtools/client/locales/en-US/changes.properties +++ b/devtools/client/locales/en-US/changes.properties @@ -38,6 +38,11 @@ changes.contextmenu.copy.accessKey=C # stylesheet changes.contextmenu.copyAllChanges=Copy All Changes +# LOCALIZATION NOTE (changes.contextmenu.copyAllChangesDescription): Detailed explanation +# for "Copy All Changes" option in Changes panel. Used as title attribute on "Copy All +# Changes" button +changes.contextmenu.copyAllChangesDescription=Copy a list of all CSS changes to clipboard. + # LOCALIZATION NOTE (changes.contextmenu.copyRule): Label for "Copy Rule" option in # Changes panel context menu which copies the complete contents of a CSS rule. changes.contextmenu.copyRule=Copy Rule diff --git a/devtools/client/themes/changes.css b/devtools/client/themes/changes.css index 7ca7d2b4c7d1..1b45bae885a0 100644 --- a/devtools/client/themes/changes.css +++ b/devtools/client/themes/changes.css @@ -81,6 +81,18 @@ var(--diff-level-offset) * var(--diff-level)); } +.changes__copy-all-changes-button { + -moz-context-properties: fill; + background: url(chrome://devtools/skin/images/copy.svg) no-repeat; + background-size: 16px; + border: none; + color: var(--theme-body-color); + fill: currentColor; + height: 16px; + margin: 7px 10px; + padding-left: 21px; +} + /* Hide the Copy Rule button by default. */ .changes__copy-rule-button { background-color: var(--changes-button-background-color);