diff --git a/browser/devtools/commandline/commands-index.js b/browser/devtools/commandline/commands-index.js index 8f22f5d07b9f..dba9d84b47b6 100644 --- a/browser/devtools/commandline/commands-index.js +++ b/browser/devtools/commandline/commands-index.js @@ -13,6 +13,7 @@ const commandModules = [ "gcli/commands/calllog", "gcli/commands/cmd", "gcli/commands/cookie", + "gcli/commands/csscoverage", "gcli/commands/jsb", "gcli/commands/listen", "gcli/commands/media", diff --git a/browser/devtools/commandline/test/browser.ini b/browser/devtools/commandline/test/browser.ini index 7d67c4783944..313427ef7947 100644 --- a/browser/devtools/commandline/test/browser.ini +++ b/browser/devtools/commandline/test/browser.ini @@ -32,6 +32,25 @@ support-files = [browser_cmd_cookie.js] support-files = browser_cmd_cookie.html +[browser_cmd_csscoverage_oneshot.js] +support-files = + browser_cmd_csscoverage_page1.html + browser_cmd_csscoverage_page2.html + browser_cmd_csscoverage_page3.html + browser_cmd_csscoverage_sheetA.css + browser_cmd_csscoverage_sheetB.css + browser_cmd_csscoverage_sheetC.css + browser_cmd_csscoverage_sheetD.css +[browser_cmd_csscoverage_startstop.js] +support-files = + browser_cmd_csscoverage_page1.html + browser_cmd_csscoverage_page2.html + browser_cmd_csscoverage_page3.html + browser_cmd_csscoverage_sheetA.css + browser_cmd_csscoverage_sheetB.css + browser_cmd_csscoverage_sheetC.css + browser_cmd_csscoverage_sheetD.css +[browser_cmd_csscoverage_util.js] [browser_cmd_jsb.js] support-files = browser_cmd_jsb_script.jsi diff --git a/browser/devtools/commandline/test/browser_cmd_csscoverage_oneshot.js b/browser/devtools/commandline/test/browser_cmd_csscoverage_oneshot.js new file mode 100644 index 000000000000..ed667e881952 --- /dev/null +++ b/browser/devtools/commandline/test/browser_cmd_csscoverage_oneshot.js @@ -0,0 +1,140 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that the addon commands works as they should + +const csscoverage = require("devtools/server/actors/csscoverage"); + +const PAGE_1 = TEST_BASE_HTTPS + "browser_cmd_csscoverage_page1.html"; +const PAGE_2 = TEST_BASE_HTTPS + "browser_cmd_csscoverage_page2.html"; +const PAGE_3 = TEST_BASE_HTTPS + "browser_cmd_csscoverage_page3.html"; + +const SHEET_A = TEST_BASE_HTTPS + "browser_cmd_csscoverage_sheetA.css"; +const SHEET_B = TEST_BASE_HTTPS + "browser_cmd_csscoverage_sheetB.css"; +const SHEET_C = TEST_BASE_HTTPS + "browser_cmd_csscoverage_sheetC.css"; +const SHEET_D = TEST_BASE_HTTPS + "browser_cmd_csscoverage_sheetD.css"; + +let test = asyncTest(function*() { + let options = yield helpers.openTab(PAGE_3); + yield helpers.openToolbar(options); + + let usage = yield csscoverage.getUsage(options.target); + + yield usage.oneshot(); + + let running = yield usage._testOnly_isRunning(); + ok(!running, "csscoverage not is running"); + + // Page1 + let expectedPage1 = { reports: [] }; + let actualPage1 = yield usage.createEditorReport(PAGE_1); + isEqualJson(actualPage1, expectedPage1, 'Page1'); + + // Page2 + let expectedPage2 = { reports: [] }; + let actualPage2 = yield usage.createEditorReport(PAGE_2); + isEqualJson(actualPage2, expectedPage2, 'Page2'); + + // Page3 + let expectedPage3 = { + reports: [ + { + selectorText: ".page3-test2", + start: { line: 9, column: 5 }, + }, + { + selectorText: ".page3-test3", + start: { line: 3, column: 5 }, + } + ] + }; + let actualPage3 = yield usage.createEditorReport(PAGE_3); + isEqualJson(actualPage3, expectedPage3, 'Page3'); + + // SheetA + let expectedSheetA = { + reports: [ + { + selectorText: ".sheetA-test2", + start: { line: 8, column: 1 }, + }, + { + selectorText: ".sheetA-test3", + start: { line: 12, column: 1 }, + }, + { + selectorText: ".sheetA-test4", + start: { line: 16, column: 1 }, + } + ] + }; + let actualSheetA = yield usage.createEditorReport(SHEET_A); + isEqualJson(actualSheetA, expectedSheetA, 'SheetA'); + + // SheetB + let expectedSheetB = { + reports: [ + { + selectorText: ".sheetB-test2", + start: { line: 6, column: 1 }, + }, + { + selectorText: ".sheetB-test3", + start: { line: 10, column: 1 }, + }, + { + selectorText: ".sheetB-test4", + start: { line: 14, column: 1 }, + } + ] + }; + let actualSheetB = yield usage.createEditorReport(SHEET_B); + isEqualJson(actualSheetB, expectedSheetB, 'SheetB'); + + // SheetC + let expectedSheetC = { + reports: [ + { + selectorText: ".sheetC-test2", + start: { line: 6, column: 1 }, + }, + { + selectorText: ".sheetC-test3", + start: { line: 10, column: 1 }, + }, + { + selectorText: ".sheetC-test4", + start: { line: 14, column: 1 }, + } + ] + }; + let actualSheetC = yield usage.createEditorReport(SHEET_C); + isEqualJson(actualSheetC, expectedSheetC, 'SheetC'); + + // SheetD + let expectedSheetD = { + reports: [ + { + selectorText: ".sheetD-test2", + start: { line: 6, column: 1 }, + }, + { + selectorText: ".sheetD-test3", + start: { line: 10, column: 1 }, + }, + { + selectorText: ".sheetD-test4", + start: { line: 14, column: 1 }, + } + ] + }; + let actualSheetD = yield usage.createEditorReport(SHEET_D); + isEqualJson(actualSheetD, expectedSheetD, 'SheetD'); + + yield helpers.closeToolbar(options); + yield helpers.closeTab(options); +}); + +function isEqualJson(o1, o2, msg) { + is(JSON.stringify(o1), JSON.stringify(o2), msg); +} diff --git a/browser/devtools/commandline/test/browser_cmd_csscoverage_page1.html b/browser/devtools/commandline/test/browser_cmd_csscoverage_page1.html new file mode 100644 index 000000000000..c9dc2949325d --- /dev/null +++ b/browser/devtools/commandline/test/browser_cmd_csscoverage_page1.html @@ -0,0 +1,83 @@ + + +
+ + ++ Page 3 +
+ + + diff --git a/browser/devtools/commandline/test/browser_cmd_csscoverage_page2.html b/browser/devtools/commandline/test/browser_cmd_csscoverage_page2.html new file mode 100644 index 000000000000..d6a2c43cf987 --- /dev/null +++ b/browser/devtools/commandline/test/browser_cmd_csscoverage_page2.html @@ -0,0 +1,58 @@ + + + + ++ Page 1 +
+ + + diff --git a/browser/devtools/commandline/test/browser_cmd_csscoverage_sheetA.css b/browser/devtools/commandline/test/browser_cmd_csscoverage_sheetA.css new file mode 100644 index 000000000000..1a3bac926925 --- /dev/null +++ b/browser/devtools/commandline/test/browser_cmd_csscoverage_sheetA.css @@ -0,0 +1,22 @@ +@import url(browser_cmd_csscoverage_sheetC.css); + +/* This should match in page 1, 2 and 3 */ +.sheetA-test1 { + color: #0A1; +} +/* This should not match anywhere */ +.sheetA-test2 { + color: #0A2; +} +/* This should match in page 1 only */ +.sheetA-test3 { + color: #0A3; +} +/* This should match in page 2 only */ +.sheetA-test4 { + color: #0A4; +} +/* This should match in page 3 only */ +.sheetA-test5 { + color: #0A5; +} diff --git a/browser/devtools/commandline/test/browser_cmd_csscoverage_sheetB.css b/browser/devtools/commandline/test/browser_cmd_csscoverage_sheetB.css new file mode 100644 index 000000000000..9335bd60d7db --- /dev/null +++ b/browser/devtools/commandline/test/browser_cmd_csscoverage_sheetB.css @@ -0,0 +1,20 @@ +/* This should match in page 1, 2 and 3 */ +.sheetB-test1 { + color: #0B1; +} +/* This should not match anywhere */ +.sheetB-test2 { + color: #0B2; +} +/* This should match in page 1 only */ +.sheetB-test3 { + color: #0B3; +} +/* This should match in page 2 only */ +.sheetB-test4 { + color: #0B4; +} +/* This should match in page 3 only */ +.sheetB-test5 { + color: #0B5; +} diff --git a/browser/devtools/commandline/test/browser_cmd_csscoverage_sheetC.css b/browser/devtools/commandline/test/browser_cmd_csscoverage_sheetC.css new file mode 100644 index 000000000000..8c899ead98e0 --- /dev/null +++ b/browser/devtools/commandline/test/browser_cmd_csscoverage_sheetC.css @@ -0,0 +1,20 @@ +/* This should match in page 1, 2 and 3 */ +.sheetC-test1 { + color: #0C1; +} +/* This should not match anywhere */ +.sheetC-test2 { + color: #0C2; +} +/* This should match in page 1 only */ +.sheetC-test3 { + color: #0C3; +} +/* This should match in page 2 only */ +.sheetC-test4 { + color: #0C4; +} +/* This should match in page 3 only */ +.sheetC-test5 { + color: #0C5; +} diff --git a/browser/devtools/commandline/test/browser_cmd_csscoverage_sheetD.css b/browser/devtools/commandline/test/browser_cmd_csscoverage_sheetD.css new file mode 100644 index 000000000000..60ebb314a5df --- /dev/null +++ b/browser/devtools/commandline/test/browser_cmd_csscoverage_sheetD.css @@ -0,0 +1,20 @@ +/* This should match in page 1, 2 and 3 */ +.sheetD-test1 { + color: #0D1; +} +/* This should not match anywhere */ +.sheetD-test2 { + color: #0D2; +} +/* This should match in page 1 only */ +.sheetD-test3 { + color: #0D3; +} +/* This should match in page 2 only */ +.sheetD-test4 { + color: #0D4; +} +/* This should match in page 3 only */ +.sheetD-test5 { + color: #0D5; +} diff --git a/browser/devtools/commandline/test/browser_cmd_csscoverage_startstop.js b/browser/devtools/commandline/test/browser_cmd_csscoverage_startstop.js new file mode 100644 index 000000000000..af90d1718895 --- /dev/null +++ b/browser/devtools/commandline/test/browser_cmd_csscoverage_startstop.js @@ -0,0 +1,147 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that the addon commands works as they should + +const csscoverage = require("devtools/server/actors/csscoverage"); + +const PAGE_1 = TEST_BASE_HTTPS + "browser_cmd_csscoverage_page1.html"; +const PAGE_2 = TEST_BASE_HTTPS + "browser_cmd_csscoverage_page2.html"; +const PAGE_3 = TEST_BASE_HTTPS + "browser_cmd_csscoverage_page3.html"; + +const SHEET_A = TEST_BASE_HTTPS + "browser_cmd_csscoverage_sheetA.css"; +const SHEET_B = TEST_BASE_HTTPS + "browser_cmd_csscoverage_sheetB.css"; +const SHEET_C = TEST_BASE_HTTPS + "browser_cmd_csscoverage_sheetC.css"; +const SHEET_D = TEST_BASE_HTTPS + "browser_cmd_csscoverage_sheetD.css"; + +let test = asyncTest(function*() { + let options = yield helpers.openTab("about:blank"); + yield helpers.openToolbar(options); + + let usage = yield csscoverage.getUsage(options.target); + + yield usage.start(); + + let running = yield usage._testOnly_isRunning(); + ok(running, "csscoverage is running"); + + yield helpers.navigate(PAGE_3, options); + + yield usage.stop(); + + running = yield usage._testOnly_isRunning(); + ok(!running, "csscoverage not is running"); + + // Page1 + let expectedPage1 = { reports: [] }; + let actualPage1 = yield usage.createEditorReport(PAGE_1); + isEqualJson(actualPage1, expectedPage1, 'Page1'); + + // Page2 + let expectedPage2 = { reports: [] }; + let actualPage2 = yield usage.createEditorReport(PAGE_2); + isEqualJson(actualPage2, expectedPage2, 'Page2'); + + // Page3 + let expectedPage3 = { + reports: [ + { + selectorText: ".page3-test2", + start: { line: 9, column: 5 }, + }, + { + selectorText: ".page3-test3", + start: { line: 3, column: 5 }, + } + ] + }; + let actualPage3 = yield usage.createEditorReport(PAGE_3); + isEqualJson(actualPage3, expectedPage3, 'Page3'); + + // SheetA + let expectedSheetA = { + reports: [ + { + selectorText: ".sheetA-test2", + start: { line: 8, column: 1 }, + }, + { + selectorText: ".sheetA-test3", + start: { line: 12, column: 1 }, + }, + { + selectorText: ".sheetA-test4", + start: { line: 16, column: 1 }, + } + ] + }; + let actualSheetA = yield usage.createEditorReport(SHEET_A); + isEqualJson(actualSheetA, expectedSheetA, 'SheetA'); + + // SheetB + let expectedSheetB = { + reports: [ + { + selectorText: ".sheetB-test2", + start: { line: 6, column: 1 }, + }, + { + selectorText: ".sheetB-test3", + start: { line: 10, column: 1 }, + }, + { + selectorText: ".sheetB-test4", + start: { line: 14, column: 1 }, + } + ] + }; + let actualSheetB = yield usage.createEditorReport(SHEET_B); + isEqualJson(actualSheetB, expectedSheetB, 'SheetB'); + + // SheetC + let expectedSheetC = { + reports: [ + { + selectorText: ".sheetC-test2", + start: { line: 6, column: 1 }, + }, + { + selectorText: ".sheetC-test3", + start: { line: 10, column: 1 }, + }, + { + selectorText: ".sheetC-test4", + start: { line: 14, column: 1 }, + } + ] + }; + let actualSheetC = yield usage.createEditorReport(SHEET_C); + isEqualJson(actualSheetC, expectedSheetC, 'SheetC'); + + // SheetD + let expectedSheetD = { + reports: [ + { + selectorText: ".sheetD-test2", + start: { line: 6, column: 1 }, + }, + { + selectorText: ".sheetD-test3", + start: { line: 10, column: 1 }, + }, + { + selectorText: ".sheetD-test4", + start: { line: 14, column: 1 }, + } + ] + }; + let actualSheetD = yield usage.createEditorReport(SHEET_D); + isEqualJson(actualSheetD, expectedSheetD, 'SheetD'); + + yield helpers.closeToolbar(options); + yield helpers.closeTab(options); +}); + +function isEqualJson(o1, o2, msg) { + is(JSON.stringify(o1), JSON.stringify(o2), msg); +} diff --git a/browser/devtools/commandline/test/browser_cmd_csscoverage_util.js b/browser/devtools/commandline/test/browser_cmd_csscoverage_util.js new file mode 100644 index 000000000000..8accc4e61a32 --- /dev/null +++ b/browser/devtools/commandline/test/browser_cmd_csscoverage_util.js @@ -0,0 +1,24 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that the addon commands works as they should + +const csscoverage = require("devtools/server/actors/csscoverage"); + +let test = asyncTest(function*() { + testDeconstructRuleId(); +}); + +function testDeconstructRuleId() { + // This is the easy case + let rule = csscoverage.deconstructRuleId("http://thing/blah|10|20"); + is(rule.url, "http://thing/blah", "1 url"); + is(rule.line, 10, "1 line"); + is(rule.column, 20, "1 column"); + + // This is the harder case with a URL containing a '|' + let rule = csscoverage.deconstructRuleId("http://thing/blah?q=a|b|11|22"); + is(rule.url, "http://thing/blah?q=a|b", "2 url"); + is(rule.line, 11, "2 line"); + is(rule.column, 22, "2 column"); +} diff --git a/browser/devtools/commandline/test/head.js b/browser/devtools/commandline/test/head.js index 646752238932..602198769ff0 100644 --- a/browser/devtools/commandline/test/head.js +++ b/browser/devtools/commandline/test/head.js @@ -5,6 +5,9 @@ const TEST_BASE_HTTP = "http://example.com/browser/browser/devtools/commandline/test/"; const TEST_BASE_HTTPS = "https://example.com/browser/browser/devtools/commandline/test/"; +var require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; +var console = require("resource://gre/modules/devtools/Console.jsm").console; + // Import the GCLI test helper let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); Services.scriptloader.loadSubScript(testDir + "/helpers.js", this); @@ -34,3 +37,7 @@ registerCleanupFunction(function tearDown() { .getInterface(Ci.nsIDOMWindowUtils) .garbageCollect(); }); + +function asyncTest(generator) { + return () => Task.spawn(generator).then(null, ok.bind(null, false)).then(finish); +} diff --git a/browser/devtools/commandline/test/helpers.js b/browser/devtools/commandline/test/helpers.js index 2d4022d63c31..0f44f5bd68a6 100644 --- a/browser/devtools/commandline/test/helpers.js +++ b/browser/devtools/commandline/test/helpers.js @@ -185,13 +185,7 @@ helpers.openTab = function(url, options) { options.browser = tabbrowser.getBrowserForTab(options.tab); options.target = TargetFactory.forTab(options.tab); - options.browser.contentWindow.location = url; - - return helpers.listenOnce(options.browser, "load", true).then(function() { - options.document = options.browser.contentDocument; - options.window = options.document.defaultView; - return options; - }); + return helpers.navigate(url, options); }; /** @@ -225,13 +219,39 @@ helpers.closeTab = function(options) { * happens on the new tab */ helpers.openToolbar = function(options) { + options = options || {}; + options.chromeWindow = options.chromeWindow || window; + return options.chromeWindow.DeveloperToolbar.show(true).then(function() { var display = options.chromeWindow.DeveloperToolbar.display; options.automator = createFFDisplayAutomator(display); options.requisition = display.requisition; + return options; }); }; +/** + * Navigate the current tab to a URL + */ +helpers.navigate = function(url, options) { + options = options || {}; + options.chromeWindow = options.chromeWindow || window; + options.tab = options.tab || options.chromeWindow.gBrowser.selectedTab; + + var tabbrowser = options.chromeWindow.gBrowser; + options.browser = tabbrowser.getBrowserForTab(options.tab); + + var promise = helpers.listenOnce(options.browser, "load", true).then(function() { + options.document = options.browser.contentDocument; + options.window = options.document.defaultView; + return options; + }); + + options.browser.contentWindow.location = url; + + return promise; +}; + /** * Undo the effects of |helpers.openToolbar| * @param options The options object passed to |helpers.openToolbar| @@ -1128,7 +1148,13 @@ Object.defineProperty(helpers, 'timingSummary', { * If typeof output is a string then the output should be exactly equal * to the given string. If the type of output is a RegExp or array of * RegExps then the output should match all RegExps - * - post: Function to be called after the checks have been run + * - error: If true, then it is expected that this command will fail (that + * is, return a rejected promise or throw an exception) + * - type: A string documenting the expected type of the return value + * - post: Function to be called after the checks have been run, which will be + * passed 2 parameters: the first being output data (with type, data, and + * error properties), and the second being the converted text version of + * the output data */ helpers.audit = function(options, audits) { checkOptions(options); diff --git a/browser/devtools/shared/DeveloperToolbar.jsm b/browser/devtools/shared/DeveloperToolbar.jsm index 40a939a9c257..3c9cd83102fb 100644 --- a/browser/devtools/shared/DeveloperToolbar.jsm +++ b/browser/devtools/shared/DeveloperToolbar.jsm @@ -950,7 +950,11 @@ OutputPanel.prototype._update = function() { if (this.displayedOutput.data != null) { let context = this._devtoolbar.display.requisition.conversionContext; - this.displayedOutput.convert('dom', context).then((node) => { + this.displayedOutput.convert('dom', context).then(node => { + if (node == null) { + return; + } + while (this._div.hasChildNodes()) { this._div.removeChild(this._div.firstChild); } diff --git a/browser/devtools/styleeditor/StyleEditorUI.jsm b/browser/devtools/styleeditor/StyleEditorUI.jsm index 73d88fa18931..d9515c82d4c5 100644 --- a/browser/devtools/styleeditor/StyleEditorUI.jsm +++ b/browser/devtools/styleeditor/StyleEditorUI.jsm @@ -28,6 +28,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; const { PrefObserver, PREF_ORIG_SOURCES } = require("devtools/styleeditor/utils"); +const csscoverage = require("devtools/server/actors/csscoverage"); +const console = require("resource://gre/modules/devtools/Console.jsm").console; const LOAD_ERROR = "error-load"; const STYLE_EDITOR_TEMPLATE = "stylesheet"; @@ -468,6 +470,14 @@ StyleEditorUI.prototype = { editor.onShow(); + csscoverage.getUsage(this._target).then(usage => { + let href = editor.styleSheet.href || editor.styleSheet.nodeHref; + usage.createEditorReport(href).then(data => { + editor.removeAllUnusedRegions(); + editor.addUnusedRegions(data.reports); + }); + }, console.error); + this.emit("editor-selected", editor); }.bind(this)).then(null, Cu.reportError); }.bind(this) diff --git a/browser/devtools/styleeditor/StyleSheetEditor.jsm b/browser/devtools/styleeditor/StyleSheetEditor.jsm index 0721e494cb4c..e37c6caed8b7 100644 --- a/browser/devtools/styleeditor/StyleSheetEditor.jsm +++ b/browser/devtools/styleeditor/StyleSheetEditor.jsm @@ -5,7 +5,7 @@ "use strict"; -this.EXPORTED_SYMBOLS = ["StyleSheetEditor"]; +this.EXPORTED_SYMBOLS = ["StyleSheetEditor", "prettifyCSS"]; const Cc = Components.classes; const Ci = Components.interfaces; @@ -41,6 +41,9 @@ const CHECK_LINKED_SHEET_DELAY=500; // How many times to check for linked file changes const MAX_CHECK_COUNT=10; +// The classname used to show a line that is not used +const UNUSED_CLASS = "cm-unused-line"; + /** * StyleSheetEditor controls the editor linked to a particular StyleSheet * object. @@ -209,18 +212,57 @@ StyleSheetEditor.prototype = { * Start fetching the full text source for this editor's sheet. */ fetchSource: function(callback) { - this.styleSheet.getText().then((longStr) => { + return this.styleSheet.getText().then((longStr) => { longStr.string().then((source) => { this._state.text = prettifyCSS(source); this.sourceLoaded = true; - callback(source); + if (callback) { + callback(source); + } + return source; }); }, e => { this.emit("error", LOAD_ERROR, this.styleSheet.href); + throw e; }) }, + /** + * Add markup to a region. UNUSED_CLASS is added to specified lines + * @param region An object shaped like + * { + * start: { line: L1, column: C1 }, + * end: { line: L2, column: C2 } // optional + * } + */ + addUnusedRegion: function(region) { + this.sourceEditor.addLineClass(region.start.line - 1, UNUSED_CLASS); + if (region.end) { + for (let i = region.start.line; i <= region.end.line; i++) { + this.sourceEditor.addLineClass(i - 1, UNUSED_CLASS); + } + } + }, + + /** + * As addUnusedRegion except that it takes an array of regions + */ + addUnusedRegions: function(regions) { + for (let region of regions) { + this.addUnusedRegion(region); + } + }, + + /** + * Remove all the unused markup regions added by addUnusedRegion + */ + removeAllUnusedRegions: function() { + for (let i = 0; i < this.sourceEditor.lineCount(); i++) { + this.sourceEditor.removeLineClass(i, UNUSED_CLASS); + } + }, + /** * Forward property-change event from stylesheet. * diff --git a/browser/devtools/styleeditor/styleeditor.css b/browser/devtools/styleeditor/styleeditor.css index 061125b35752..4f3f9f0dd5b6 100644 --- a/browser/devtools/styleeditor/styleeditor.css +++ b/browser/devtools/styleeditor/styleeditor.css @@ -3,6 +3,23 @@ * 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/. */ +#style-editor-chrome { + -moz-box-flex: 1; +} + +.csscoverage-report-container { + -moz-box-flex: 1; +} + +.csscoverage-report { + -moz-box-orient: horizontal; +} + +.csscoverage-report-container { + overflow-x: hidden; + overflow-y: auto; +} + .stylesheet-error-message { display: none; } diff --git a/browser/devtools/styleeditor/styleeditor.xul b/browser/devtools/styleeditor/styleeditor.xul index e8f852011576..b63f5df461ea 100644 --- a/browser/devtools/styleeditor/styleeditor.xul +++ b/browser/devtools/styleeditor/styleeditor.xul @@ -9,6 +9,8 @@ %editMenuStrings; %sourceEditorStrings; + + %csscoverageDTD; ]> @@ -72,62 +74,116 @@&noStyleSheet.label;
-&noStyleSheet-tip-start.label; - &noStyleSheet-tip-action.label; - &noStyleSheet-tip-end.label;
-&noStyleSheet.label;
+&noStyleSheet-tip-start.label; + &noStyleSheet-tip-action.label; + &noStyleSheet-tip-end.label;
&csscoverage.noMatch;
+${rule.selectorText}
+ (${rule.shortHref} : ${rule.start.line})
+
+ &csscoverage.preload1;
+ <link ...>
+ &csscoverage.preload2;
+ <style>...
+ &csscoverage.preload3;
+
+ &csscoverage.footer1; + &csscoverage.footer3; + &csscoverage.footer4; +
++
l10n.lookup('BLAH') === l10n.propertyLookup.BLAH
+ * This is particularly nice for templates because you can pass
+ * l10n:l10n.propertyLookup
in the template data and use it
+ * like ${l10n.BLAH}
+ */
+exports.propertyLookup = Proxy.create({
+ get: function(rcvr, name) {
+ return exports.lookup(name);
+ }
+});
+
+/**
+ * Lookup a string in the GCLI string bundle
+ */
+exports.lookupFormat = function(name, swaps) {
+ try {
+ return stringBundle.formatStringFromName(name, swaps, swaps.length);
+ }
+ catch (ex) {
+ throw new Error('Failure in lookupFormat(\'' + name + '\')');
+ }
+};
+
+/**
+ * Allow GCLI users to be hidden by the 'devtools.chrome.enabled' pref.
+ * Use it in commands like this:
+ * + * name: "somecommand", + * hidden: l10n.hiddenByChromePref(), + * exec: function(args, context) { ... } + *+ */ +exports.hiddenByChromePref = function() { + return !prefBranch.prefHasUserValue('devtools.chrome.enabled'); +}; diff --git a/toolkit/devtools/server/actors/csscoverage.js b/toolkit/devtools/server/actors/csscoverage.js new file mode 100644 index 000000000000..c17073e1b000 --- /dev/null +++ b/toolkit/devtools/server/actors/csscoverage.js @@ -0,0 +1,718 @@ +/* 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 { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm"); +const Services = require("Services"); + +const promise = require("resource://gre/modules/Promise.jsm").Promise; +const { getRuleLocation } = require("devtools/server/actors/stylesheets"); + +const protocol = require("devtools/server/protocol"); +const { method, custom, RetVal, Arg } = protocol; + +loader.lazyGetter(this, "gDevTools", () => { + return require("resource:///modules/devtools/gDevTools.jsm").gDevTools; +}); +loader.lazyGetter(this, "DOMUtils", () => { + return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils) +}); +loader.lazyGetter(this, "prettifyCSS", () => { + return require("resource:///modules/devtools/StyleSheetEditor.jsm").prettifyCSS; +}); + +const CSSRule = Ci.nsIDOMCSSRule; + +const MAX_UNUSED_RULES = 10000; + +/** + * Allow: let foo = l10n.lookup("csscoverageFoo"); + */ +const l10n = exports.l10n = { + _URI: "chrome://global/locale/devtools/csscoverage.properties", + lookup: function(msg) { + if (this._stringBundle == null) { + this._stringBundle = Services.strings.createBundle(this._URI); + } + return this._stringBundle.GetStringFromName(msg); + } +}; + +/** + * UsageReport manages the collection of CSS usage data. + * The core of a UsageReport is a JSON-able data structure called _knownRules + * which looks like this: + * This records the CSSStyleRules and their usage. + * The format is: + * Map({ + *