diff --git a/browser/devtools/commandline/commands-index.js b/browser/devtools/commandline/commands-index.js
index 82a045ffc1b6..6cf7f57e6135 100644
--- a/browser/devtools/commandline/commands-index.js
+++ b/browser/devtools/commandline/commands-index.js
@@ -15,6 +15,7 @@ const commandModules = [
"gcli/commands/cookie",
"gcli/commands/csscoverage",
"gcli/commands/folder",
+ "gcli/commands/highlight",
"gcli/commands/inject",
"gcli/commands/jsb",
"gcli/commands/listen",
diff --git a/browser/devtools/commandline/test/browser.ini b/browser/devtools/commandline/test/browser.ini
index 7831692cae74..c306724bb934 100644
--- a/browser/devtools/commandline/test/browser.ini
+++ b/browser/devtools/commandline/test/browser.ini
@@ -51,6 +51,8 @@ support-files =
browser_cmd_csscoverage_sheetC.css
browser_cmd_csscoverage_sheetD.css
[browser_cmd_folder.js]
+[browser_cmd_highlight_01.js]
+[browser_cmd_highlight_02.js]
[browser_cmd_inject.js]
support-files =
browser_cmd_inject.html
diff --git a/browser/devtools/commandline/test/browser_cmd_highlight_01.js b/browser/devtools/commandline/test/browser_cmd_highlight_01.js
new file mode 100644
index 000000000000..6c8e85afe74f
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_highlight_01.js
@@ -0,0 +1,257 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests the various highlight command parameters and options
+
+// Creating a test page with many elements to test the --showall option
+let TEST_PAGE = "data:text/html;charset=utf-8,
";
+for (let i = 0; i < 200; i ++) {
+ TEST_PAGE += "- " + i + "
";
+}
+TEST_PAGE += "
";
+
+function test() {
+ return Task.spawn(spawnTest).then(finish, helpers.handleError);
+}
+
+function* spawnTest() {
+ let options = yield helpers.openTab(TEST_PAGE);
+ yield helpers.openToolbar(options);
+
+ yield helpers.audit(options, [
+ {
+ setup: 'highlight',
+ check: {
+ input: 'highlight',
+ hints: ' [selector] [options]',
+ markup: 'VVVVVVVVV',
+ status: 'VALID'
+ },
+ exec: {
+ output: '0 nodes highlighted'
+ }
+ },
+ {
+ setup: 'highlight bo',
+ check: {
+ input: 'highlight bo',
+ hints: ' [options]',
+ markup: 'VVVVVVVVVVII',
+ status: 'ERROR'
+ },
+ exec: {
+ output: 'Error: No matches'
+ }
+ },
+ {
+ setup: 'highlight body',
+ check: {
+ input: 'highlight body',
+ hints: ' [options]',
+ markup: 'VVVVVVVVVVVVVV',
+ status: 'VALID'
+ },
+ exec: {
+ output: '1 nodes highlighted'
+ }
+ },
+ {
+ setup: 'highlight body --hide',
+ check: {
+ input: 'highlight body --hide',
+ hints: 'guides [options]',
+ markup: 'VVVVVVVVVVVVVVVIIIIII',
+ status: 'ERROR'
+ },
+ exec: {
+ output: 'Error: Too many arguments'
+ }
+ },
+ {
+ setup: 'highlight body --hideguides',
+ check: {
+ input: 'highlight body --hideguides',
+ hints: ' [options]',
+ markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVV',
+ status: 'VALID'
+ },
+ exec: {
+ output: '1 nodes highlighted'
+ }
+ },
+ {
+ setup: 'highlight body --show',
+ check: {
+ input: 'highlight body --show',
+ hints: 'infobar [options]',
+ markup: 'VVVVVVVVVVVVVVVIIIIII',
+ status: 'ERROR'
+ },
+ exec: {
+ output: 'Error: Too many arguments'
+ }
+ },
+ {
+ setup: 'highlight body --showinfobar',
+ check: {
+ input: 'highlight body --showinfobar',
+ hints: ' [options]',
+ markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+ status: 'VALID'
+ },
+ exec: {
+ output: '1 nodes highlighted'
+ }
+ },
+ {
+ setup: 'highlight body --showa',
+ check: {
+ input: 'highlight body --showa',
+ hints: 'll [options]',
+ markup: 'VVVVVVVVVVVVVVVIIIIIII',
+ status: 'ERROR'
+ },
+ exec: {
+ output: 'Error: Too many arguments'
+ }
+ },
+ {
+ setup: 'highlight body --showall',
+ check: {
+ input: 'highlight body --showall',
+ hints: ' [options]',
+ markup: 'VVVVVVVVVVVVVVVVVVVVVVVV',
+ status: 'VALID'
+ },
+ exec: {
+ output: '1 nodes highlighted'
+ }
+ },
+ {
+ setup: 'highlight body --r',
+ check: {
+ input: 'highlight body --r',
+ hints: 'egion [options]',
+ markup: 'VVVVVVVVVVVVVVVIII',
+ status: 'ERROR'
+ },
+ exec: {
+ output: 'Error: Too many arguments'
+ }
+ },
+ {
+ setup: 'highlight body --region',
+ check: {
+ input: 'highlight body --region',
+ hints: ' [options]',
+ markup: 'VVVVVVVVVVVVVVVIIIIIIII',
+ status: 'ERROR'
+ },
+ exec: {
+ output: 'Error: Value required for \'region\'.'
+ }
+ },
+ {
+ setup: 'highlight body --fi',
+ check: {
+ input: 'highlight body --fi',
+ hints: 'll [options]',
+ markup: 'VVVVVVVVVVVVVVVIIII',
+ status: 'ERROR'
+ },
+ exec: {
+ output: 'Error: Too many arguments'
+ }
+ },
+ {
+ setup: 'highlight body --fill',
+ check: {
+ input: 'highlight body --fill',
+ hints: ' [options]',
+ markup: 'VVVVVVVVVVVVVVVIIIIII',
+ status: 'ERROR'
+ },
+ exec: {
+ output: 'Error: Value required for \'fill\'.'
+ }
+ },
+ {
+ setup: 'highlight body --ke',
+ check: {
+ input: 'highlight body --ke',
+ hints: 'ep [options]',
+ markup: 'VVVVVVVVVVVVVVVIIII',
+ status: 'ERROR'
+ },
+ exec: {
+ output: 'Error: Too many arguments'
+ }
+ },
+ {
+ setup: 'highlight body --keep',
+ check: {
+ input: 'highlight body --keep',
+ hints: ' [options]',
+ markup: 'VVVVVVVVVVVVVVVVVVVVV',
+ status: 'VALID'
+ },
+ exec: {
+ output: '1 nodes highlighted'
+ }
+ },
+ {
+ setup: 'highlight body --hideguides --showinfobar --showall --region ' +
+ 'content --fill red --keep',
+ check: {
+ input: 'highlight body --hideguides --showinfobar --showall --region ' +
+ 'content --fill red --keep',
+ hints: '',
+ markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV' +
+ 'VVVVVVVVVVVVVVVVVVVVVVVVV',
+ status: 'VALID'
+ },
+ exec: {
+ output: '1 nodes highlighted'
+ }
+ },
+ {
+ setup: 'highlight .item',
+ check: {
+ input: 'highlight .item',
+ hints: ' [options]',
+ markup: 'VVVVVVVVVVVVVVV',
+ status: 'VALID'
+ },
+ exec: {
+ output: '200 nodes matched, but only 100 nodes highlighted. Use ' +
+ '\'--showall\' to show all'
+ }
+ },
+ {
+ setup: 'highlight .item --showall',
+ check: {
+ input: 'highlight .item --showall',
+ hints: ' [options]',
+ markup: 'VVVVVVVVVVVVVVVVVVVVVVVVV',
+ status: 'VALID'
+ },
+ exec: {
+ output: '200 nodes highlighted'
+ }
+ },
+ {
+ setup: 'unhighlight',
+ check: {
+ input: 'unhighlight',
+ hints: '',
+ markup: 'VVVVVVVVVVV',
+ status: 'VALID'
+ }
+ }
+ ]);
+
+ yield helpers.closeToolbar(options);
+ yield helpers.closeTab(options);
+}
diff --git a/browser/devtools/commandline/test/browser_cmd_highlight_02.js b/browser/devtools/commandline/test/browser_cmd_highlight_02.js
new file mode 100644
index 000000000000..1bed63ae8402
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_highlight_02.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the highlight command actually creates a highlighter
+
+const TEST_PAGE = "data:text/html;charset=utf-8,";
+
+function test() {
+ return Task.spawn(function*() {
+ let options = yield helpers.openTab(TEST_PAGE);
+ yield helpers.openToolbar(options);
+
+ info("highlighting the body node");
+ yield runCommand("highlight body", options);
+ is(getHighlighters().length, 1, "The highlighter element exists for body");
+
+ info("highlighting the div node");
+ yield runCommand("highlight div", options);
+ is(getHighlighters().length, 1, "The highlighter element exists for div");
+
+ info("highlighting the body node again, asking to keep the div");
+ yield runCommand("highlight body --keep", options);
+ is(getHighlighters().length, 2, "2 highlighter elements have been created");
+
+ info("unhighlighting all nodes");
+ yield runCommand("unhighlight", options);
+ is(getHighlighters().length, 0, "All highlighters have been removed");
+
+ yield helpers.closeToolbar(options);
+ yield helpers.closeTab(options);
+ }).then(finish, helpers.handleError);
+}
+
+function getHighlighters() {
+ return gBrowser.selectedBrowser.parentNode
+ .querySelectorAll(".highlighter-container");
+}
+
+function* runCommand(cmd, options) {
+ yield helpers.audit(options, [{ setup: cmd, exec: {} }]);
+}
diff --git a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
index 4a790df3f5bf..3fa843630622 100644
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
@@ -128,6 +128,113 @@ screenshotCopied=Copied to clipboard.
# LOCALIZATION NOTE (screenshotTooltip) Text displayed as tooltip for screenshot button in devtools ToolBox.
screenshotTooltip=Take a fullpage screenshot
+# LOCALIZATION NOTE (highlightDesc) A very short description of the
+# 'highlight' command. See highlightManual for a fuller description of what
+# it does. This string is designed to be shown in a menu alongside the
+# command name, which is why it should be as short as possible.
+highlightDesc=Highlight nodes
+
+# LOCALIZATION NOTE (highlightManual) A fuller description of the 'highlight'
+# command, displayed when the user asks for help on what it does.
+highlightManual=Highlight nodes that match a selector on the page
+
+# LOCALIZATION NOTE (highlightSelectorDesc) A very short string to describe
+# the 'selector' parameter to the 'highlight' command, which is displayed in
+# a dialog when the user is using this command.
+highlightSelectorDesc=CSS selector
+
+# LOCALIZATION NOTE (highlightSelectorManual) A fuller description of the
+# 'selector' parameter to the 'highlight' command, displayed when the user
+# asks for help on what it does.
+highlightSelectorManual=The CSS selector used to match nodes in the page
+
+# LOCALIZATION NOTE (highlightOptionsDesc) The title of a set of options to
+# the 'highlight' command, displayed as a heading to the list of option.
+highlightOptionsDesc=Options
+
+# LOCALIZATION NOTE (highlightHideGuidesDesc) A very short string to describe
+# the 'hideguides' option parameter to the 'highlight' command, which is
+# displayed in a dialog when the user is using this command.
+highlightHideGuidesDesc=Hide guides
+
+# LOCALIZATION NOTE (highlightHideGuidesManual) A fuller description of the
+# 'hideguides' option parameter to the 'highlight' command, displayed when the
+# user asks for help on what it does.
+highlightHideGuidesManual=Hide the guides around the highlighted node
+
+# LOCALIZATION NOTE (highlightShowInfoBarDesc) A very short string to describe
+# the 'showinfobar' option parameter to the 'highlight' command, which is
+# displayed in a dialog when the user is using this command.
+highlightShowInfoBarDesc=Show the node infobar
+
+# LOCALIZATION NOTE (highlightShowInfoBarManual) A fuller description of the
+# 'showinfobar' option parameter to the 'highlight' command, displayed when the
+# user asks for help on what it does.
+highlightShowInfoBarManual=Show the infobar above the highlighted node (the infobar displays the tagname, attributes and dimension)
+
+# LOCALIZATION NOTE (highlightShowAllDesc) A very short string to describe
+# the 'showall' option parameter to the 'highlight' command, which is
+# displayed in a dialog when the user is using this command.
+highlightShowAllDesc=Show all matches
+
+# LOCALIZATION NOTE (highlightShowAllManual) A fuller description of the
+# 'showall' option parameter to the 'highlight' command, displayed when the
+# user asks for help on what it does.
+highlightShowAllManual=If too many nodes match the selector, only the first 100 will be shown to avoid slowing down the page too much. Use this option to show all matches instead
+
+# LOCALIZATION NOTE (highlightRegionDesc) A very short string to describe the
+# 'region' option parameter to the 'highlight' command, which is displayed in a
+# dialog when the user is using this command.
+highlightRegionDesc=Box model region
+
+# LOCALIZATION NOTE (highlightRegionManual) A fuller description of the 'region'
+# option parameter to the 'highlight' command, displayed when the user asks for
+# help on what it does.
+highlightRegionManual=Which box model region should be highlighted: 'content', 'padding', 'border' or 'margin'
+
+# LOCALIZATION NOTE (highlightFillDesc) A very short string to describe the
+# 'fill' option parameter to the 'highlight' command, which is displayed in a
+# dialog when the user is using this command.
+highlightFillDesc=Fill style
+
+# LOCALIZATION NOTE (highlightFillManual) A fuller description of the 'fill'
+# option parameter to the 'highlight' command, displayed when the user asks for
+# help on what it does.
+highlightFillManual=Override the default region fill style with a custom color
+
+# LOCALIZATION NOTE (highlightKeepDesc) A very short string to describe the
+# 'keep' option parameter to the 'highlight' command, which is displayed in a
+# dialog when the user is using this command.
+highlightKeepDesc=Keep existing highlighters
+
+# LOCALIZATION NOTE (highlightKeepManual) A fuller description of the 'keep'
+# option parameter to the 'highlight' command, displayed when the user asks for
+# help on what it does.
+highlightKeepManual=By default, existing highlighters are hidden when running the command, unless this option is set
+
+# LOCALIZATION NOTE (highlightOutputConfirm) A confirmation message for the
+# 'highlight' command, displayed to the user once the command has been entered,
+# informing the user how many nodes have been highlighted successfully and how
+# to turn highlighting off
+highlightOutputConfirm=%1$S nodes highlighted
+
+# LOCALIZATION NOTE (highlightOutputMaxReached) A confirmation message for the
+# 'highlight' command, displayed to the user once the command has been entered,
+# informing the user how many nodes have been highlighted successfully and that
+# some nodes could not be highlighted due to the maximum number of nodes being
+# reached, and how to turn highlighting off
+highlightOutputMaxReached=%1$S nodes matched, but only %2$S nodes highlighted. Use '--showall' to show all
+
+# LOCALIZATION NOTE (unhighlightDesc) A very short description of the
+# 'unhighlight' command. See unhighlightManual for a fuller description of what
+# it does. This string is designed to be shown in a menu alongside the
+# command name, which is why it should be as short as possible.
+unhighlightDesc=Unhighlight all nodes
+
+# LOCALIZATION NOTE (unhighlightManual) A fuller description of the 'unhighlight'
+# command, displayed when the user asks for help on what it does.
+unhighlightManual=Unhighlight all nodes previously highlighted with the 'highlight' command
+
# LOCALIZATION NOTE (restartBrowserDesc) A very short description of the
# 'restart' command. This string is designed to be shown in a menu alongside the
# command name, which is why it should be as short as possible.
diff --git a/toolkit/devtools/gcli/commands/highlight.js b/toolkit/devtools/gcli/commands/highlight.js
new file mode 100644
index 000000000000..14ea5aa9a529
--- /dev/null
+++ b/toolkit/devtools/gcli/commands/highlight.js
@@ -0,0 +1,142 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {Cc, Ci, Cu} = require("chrome");
+const gcli = require("gcli/index");
+require("devtools/server/actors/inspector");
+const {HIGHLIGHTER_CLASSES} = require("devtools/server/actors/highlighter");
+const {BoxModelHighlighter} = HIGHLIGHTER_CLASSES;
+
+// How many maximum nodes can be highlighted in parallel
+const MAX_HIGHLIGHTED_ELEMENTS = 100;
+
+// Stores the highlighters instances so they can be destroyed
+let highlighters = [];
+
+/**
+ * Destroy all existing highlighters
+ */
+function unhighlightAll() {
+ for (let highlighter of highlighters) {
+ highlighter.destroy();
+ }
+ highlighters = [];
+}
+
+exports.items = [
+ {
+ name: "highlight",
+ description: gcli.lookup("highlightDesc"),
+ manual: gcli.lookup("highlightManual"),
+ params: [
+ {
+ name: "selector",
+ type: "nodelist",
+ description: gcli.lookup("highlightSelectorDesc"),
+ manual: gcli.lookup("highlightSelectorManual")
+ },
+ {
+ group: gcli.lookup("highlightOptionsDesc"),
+ params: [
+ {
+ name: "hideguides",
+ type: "boolean",
+ description: gcli.lookup("highlightHideGuidesDesc"),
+ manual: gcli.lookup("highlightHideGuidesManual")
+ },
+ {
+ name: "showinfobar",
+ type: "boolean",
+ description: gcli.lookup("highlightShowInfoBarDesc"),
+ manual: gcli.lookup("highlightShowInfoBarManual")
+ },
+ {
+ name: "showall",
+ type: "boolean",
+ description: gcli.lookup("highlightShowAllDesc"),
+ manual: gcli.lookup("highlightShowAllManual")
+ },
+ {
+ name: "region",
+ type: {
+ name: "selection",
+ data: ["content", "padding", "border", "margin"]
+ },
+ description: gcli.lookup("highlightRegionDesc"),
+ manual: gcli.lookup("highlightRegionManual"),
+ defaultValue: "border"
+ },
+ {
+ name: "fill",
+ type: "string",
+ description: gcli.lookup("highlightFillDesc"),
+ manual: gcli.lookup("highlightFillManual"),
+ defaultValue: null
+ },
+ {
+ name: "keep",
+ type: "boolean",
+ description: gcli.lookup("highlightKeepDesc"),
+ manual: gcli.lookup("highlightKeepManual"),
+ }
+ ]
+ }
+ ],
+ exec: function(args, context) {
+ // Remove all existing highlighters unless told otherwise
+ if (!args.keep) {
+ unhighlightAll();
+ }
+
+ let env = context.environment;
+
+ // Unhighlight on navigate
+ env.target.once("navigate", unhighlightAll);
+
+ // Build a tab context for the highlighter (which normally takes a
+ // TabActor as parameter to its constructor)
+ let tabContext = {
+ browser: env.chromeWindow.gBrowser.getBrowserForDocument(env.document),
+ window: env.window
+ };
+
+ let i = 0;
+ for (let node of args.selector) {
+ if (!args.showall && i >= MAX_HIGHLIGHTED_ELEMENTS) {
+ break;
+ }
+
+ let highlighter = new BoxModelHighlighter(tabContext);
+ if (args.fill) {
+ highlighter.regionFill[args.region] = args.fill;
+ }
+ highlighter.show(node, {
+ region: args.region,
+ hideInfoBar: !args.showinfobar,
+ hideGuides: args.hideguides,
+ showOnly: args.region
+ });
+ highlighters.push(highlighter);
+ i ++;
+ }
+
+ let output = gcli.lookupFormat("highlightOutputConfirm",
+ ["" + args.selector.length]);
+ if (args.selector.length > i) {
+ output = gcli.lookupFormat("highlightOutputMaxReached",
+ ["" + args.selector.length, "" + i]);
+ }
+
+ return output;
+ }
+ },
+ {
+ name: "unhighlight",
+ description: gcli.lookup("unhighlightDesc"),
+ manual: gcli.lookup("unhighlightManual"),
+ exec: unhighlightAll
+ }
+];
diff --git a/toolkit/devtools/server/actors/highlighter.js b/toolkit/devtools/server/actors/highlighter.js
index d9be26805e9c..1f0276e483f1 100644
--- a/toolkit/devtools/server/actors/highlighter.js
+++ b/toolkit/devtools/server/actors/highlighter.js
@@ -502,6 +502,12 @@ function BoxModelHighlighter(tabActor) {
this._initMarkup();
EventEmitter.decorate(this);
+ /**
+ * Optionally customize each region's fill color by adding an entry to the
+ * regionFill property: `highlighter.regionFill.margin = "red";
+ */
+ this.regionFill = {};
+
this._currentNode = null;
}
@@ -774,6 +780,13 @@ BoxModelHighlighter.prototype = Heritage.extend(XULBasedHighlighter.prototype, {
this.layoutHelpers.getAdjustedQuads(this.currentNode, boxType);
let boxNode = this._boxModelNodes[boxType];
+
+ if (this.regionFill[boxType]) {
+ boxNode.setAttribute("style", "fill:" + this.regionFill[boxType]);
+ } else {
+ boxNode.removeAttribute("style");
+ }
+
if (!this.options.showOnly || this.options.showOnly === boxType) {
boxNode.setAttribute("points",
p1.x + "," + p1.y + " " +