author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Thu, 30 Jun 2016 11:54:32 +0200 | |
changeset 303149 | d700dc054751333e0735f975fce3d3adf153c62a |
parent 303124 | 0edb9df3c97ff7d205a99f8af607f99a858f140b (current diff) |
parent 303148 | 32bb090d7e625a763a80728da2c276724a2c6761 (diff) |
child 303150 | 4a860475d96a0f7705106498e7380debc7fb2ab9 |
push id | 30381 |
push user | cbook@mozilla.com |
push date | Thu, 30 Jun 2016 09:54:49 +0000 |
treeherder | mozilla-central@d700dc054751 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 50.0a1 |
first release with | nightly linux32
d700dc054751
/
50.0a1
/
20160630030207
/
files
nightly linux64
d700dc054751
/
50.0a1
/
20160630030207
/
files
nightly mac
d700dc054751
/
50.0a1
/
20160630030201
/
files
nightly win32
d700dc054751
/
50.0a1
/
20160630030207
/
files
nightly win64
d700dc054751
/
50.0a1
/
20160630030207
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
50.0a1
/
20160630030207
/
pushlog to previous
nightly linux64
50.0a1
/
20160630030207
/
pushlog to previous
nightly mac
50.0a1
/
20160630030201
/
pushlog to previous
nightly win32
50.0a1
/
20160630030207
/
pushlog to previous
nightly win64
50.0a1
/
20160630030207
/
pushlog to previous
|
devtools/client/responsive.html/audio/camera-click.mp3 | file | annotate | diff | comparison | revisions | |
devtools/client/responsive.html/audio/moz.build | file | annotate | diff | comparison | revisions | |
devtools/server/tests/mochitest/test_css-logic-inheritance.html | file | annotate | diff | comparison | revisions | |
toolkit/components/telemetry/Histograms.json | file | annotate | diff | comparison | revisions |
--- a/.eslintignore +++ b/.eslintignore @@ -104,33 +104,33 @@ devtools/client/sourceeditor/** devtools/client/webaudioeditor/** devtools/client/webconsole/** !devtools/client/webconsole/panel.js !devtools/client/webconsole/jsterm.js !devtools/client/webconsole/console-commands.js devtools/client/webide/** !devtools/client/webide/components/webideCli.js devtools/server/** +!devtools/server/css-logic.js !devtools/server/actors/webbrowser.js !devtools/server/actors/styles.js !devtools/server/actors/string.js devtools/shared/*.js !devtools/shared/css-lexer.js !devtools/shared/defer.js !devtools/shared/event-emitter.js !devtools/shared/task.js devtools/shared/*.jsm !devtools/shared/Loader.jsm devtools/shared/apps/** devtools/shared/client/** devtools/shared/discovery/** devtools/shared/gcli/** !devtools/shared/gcli/templater.js devtools/shared/heapsnapshot/** -devtools/shared/inspector/** devtools/shared/layout/** devtools/shared/locales/** devtools/shared/performance/** devtools/shared/qrcode/** devtools/shared/security/** devtools/shared/shims/** devtools/shared/tests/** !devtools/shared/tests/unit/test_csslexer.js
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -287,16 +287,20 @@ pref("browser.urlbar.match.url", "@"); // The default behavior for the urlbar can be configured to use any combination // of the match filters with each additional filter adding more results (union). pref("browser.urlbar.suggest.history", true); pref("browser.urlbar.suggest.bookmark", true); pref("browser.urlbar.suggest.openpage", true); pref("browser.urlbar.suggest.searches", false); pref("browser.urlbar.userMadeSearchSuggestionsChoice", false); +// 4 here means the suggestion notification will be automatically +// hidden the 4th day, so it will actually be shown on 3 different days. +pref("browser.urlbar.daysBeforeHidingSuggestionsPrompt", 4); +pref("browser.urlbar.lastSuggestionsPromptDate", 20160601); // Limit the number of characters sent to the current search engine to fetch // suggestions. pref("browser.urlbar.maxCharsForSearchSuggestions", 20); // Restrictions to current suggestions can also be applied (intersection). // Typed suggestion works only if history is set to true. pref("browser.urlbar.suggest.history.onlyTyped", false);
--- a/browser/base/content/urlbarBindings.xml +++ b/browser/base/content/urlbarBindings.xml @@ -956,17 +956,18 @@ file, You can obtain one at http://mozil <property name="shouldShowSearchSuggestionsNotification" readonly="true"> <getter><![CDATA[ return !this._userMadeSearchSuggestionsChoice && !this.inPrivateContext && // When _urlbarFocused is true, tabbrowser would close the // popup if it's opened here, so don't show the notification. !gBrowser.selectedBrowser._urlbarFocused && - Services.prefs.getBoolPref("browser.search.suggest.enabled"); + Services.prefs.getBoolPref("browser.search.suggest.enabled") && + this._prefs.getIntPref("daysBeforeHidingSuggestionsPrompt"); ]]></getter> </property> </implementation> <handlers> <handler event="keydown"><![CDATA[ if ((event.keyCode === KeyEvent.DOM_VK_ALT || @@ -1295,18 +1296,36 @@ file, You can obtain one at http://mozil <parameter name="aInput"/> <parameter name="aElement"/> <body> <![CDATA[ // initially the panel is hidden // to avoid impacting startup / new window performance aInput.popup.hidden = false; - if (aInput.shouldShowSearchSuggestionsNotification) { + let showNotification = aInput.shouldShowSearchSuggestionsNotification; + if (showNotification) { + let prefs = aInput._prefs; + let date = parseInt((new Date()).toLocaleFormat("%Y%m%d")); + let previousDate = prefs.getIntPref("lastSuggestionsPromptDate"); + if (previousDate < date) { + let remainingDays = + prefs.getIntPref("daysBeforeHidingSuggestionsPrompt") - 1; + prefs.setIntPref("daysBeforeHidingSuggestionsPrompt", + remainingDays); + prefs.setIntPref("lastSuggestionsPromptDate", date); + if (!remainingDays) + showNotification = false; + } + } + + if (showNotification) { this._showSearchSuggestionsNotification(); + } else if (this.classList.contains("showSearchSuggestionsNotification")) { + this._hideSearchSuggestionsNotification(); } this._openAutocompletePopup(aInput, aElement); ]]> </body> </method> <method name="_openAutocompletePopup"> @@ -1379,18 +1398,16 @@ file, You can obtain one at http://mozil <body> <![CDATA[ this.footer.collapsed = this._matchCount == 0; ]]> </body> </method> <method name="_showSearchSuggestionsNotification"> - <parameter name="aInput"/> - <parameter name="aElement"/> <body> <![CDATA[ // With the notification shown, the listbox's height can sometimes be // too small when it's flexed, as it normally is. Also, it can start // out slightly scrolled down. Both problems appear together, most // often when the popup is very narrow and the notification's text // must wrap. Work around them by removing the flex. //
--- a/browser/components/extensions/.eslintrc +++ b/browser/components/extensions/.eslintrc @@ -1,18 +1,18 @@ { "extends": "../../../toolkit/components/extensions/.eslintrc", "globals": { "AllWindowEvents": true, "currentWindow": true, "EventEmitter": true, - "IconDetails": true, "makeWidgetId": true, "pageActionFor": true, + "IconDetails": true, "PanelPopup": true, "TabContext": true, "ViewPopup": true, "WindowEventManager": true, "WindowListManager": true, "WindowManager": true, }, }
--- a/browser/components/extensions/ext-browserAction.js +++ b/browser/components/extensions/ext-browserAction.js @@ -5,16 +5,17 @@ XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm"); Cu.import("resource://devtools/shared/event-emitter.js"); Cu.import("resource://gre/modules/ExtensionUtils.jsm"); var { EventManager, + IconDetails, } = ExtensionUtils; const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; // WeakMap[Extension -> BrowserAction] var browserActionMap = new WeakMap(); // Responsible for the browser_action section of the manifest as well
--- a/browser/components/extensions/ext-pageAction.js +++ b/browser/components/extensions/ext-pageAction.js @@ -1,16 +1,17 @@ /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; Cu.import("resource://gre/modules/Task.jsm"); Cu.import("resource://gre/modules/ExtensionUtils.jsm"); var { EventManager, + IconDetails, } = ExtensionUtils; // WeakMap[Extension -> PageAction] var pageActionMap = new WeakMap(); // Handles URL bar icons, including the |page_action| manifest entry // and associated API. function PageAction(options, extension) { @@ -218,21 +219,23 @@ extensions.registerSchemaAPI("pageAction return () => { pageAction.off("click", listener); }; }).api(), show(tabId) { let tab = TabManager.getTab(tabId); PageAction.for(extension).setProperty(tab, "show", true); + return Promise.resolve(); }, hide(tabId) { let tab = TabManager.getTab(tabId); PageAction.for(extension).setProperty(tab, "show", false); + return Promise.resolve(); }, setTitle(details) { let tab = TabManager.getTab(details.tabId); // Clear the tab-specific title when given a null string. PageAction.for(extension).setProperty(tab, "title", details.title || null); },
--- a/browser/components/extensions/ext-utils.js +++ b/browser/components/extensions/ext-utils.js @@ -3,148 +3,31 @@ "use strict"; XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"); Cu.import("resource://gre/modules/ExtensionUtils.jsm"); -Cu.import("resource://gre/modules/AddonManager.jsm"); Cu.import("resource://gre/modules/AppConstants.jsm"); const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; -const INTEGER = /^[1-9]\d*$/; + // Minimum time between two resizes. const RESIZE_TIMEOUT = 100; var { EventManager, - instanceOf, } = ExtensionUtils; // This file provides some useful code for the |tabs| and |windows| // modules. All of the code is installed on |global|, which is a scope // shared among the different ext-*.js scripts. - -// Manages icon details for toolbar buttons in the |pageAction| and -// |browserAction| APIs. -global.IconDetails = { - // Normalizes the various acceptable input formats into an object - // with icon size as key and icon URL as value. - // - // If a context is specified (function is called from an extension): - // Throws an error if an invalid icon size was provided or the - // extension is not allowed to load the specified resources. - // - // If no context is specified, instead of throwing an error, this - // function simply logs a warning message. - normalize(details, extension, context = null) { - let result = {}; - - try { - if (details.imageData) { - let imageData = details.imageData; - - // The global might actually be from Schema.jsm, which - // normalizes most of our arguments. In that case it won't have - // an ImageData property. But Schema.jsm doesn't normalize - // actual ImageData objects, so they will come from a global - // with the right property. - if (instanceOf(imageData, "ImageData")) { - imageData = {"19": imageData}; - } - - for (let size of Object.keys(imageData)) { - if (!INTEGER.test(size)) { - throw new Error(`Invalid icon size ${size}, must be an integer`); - } - result[size] = this.convertImageDataToPNG(imageData[size], context); - } - } - - if (details.path) { - let path = details.path; - if (typeof path != "object") { - path = {"19": path}; - } - - let baseURI = context ? context.uri : extension.baseURI; - - for (let size of Object.keys(path)) { - if (!INTEGER.test(size)) { - throw new Error(`Invalid icon size ${size}, must be an integer`); - } - - let url = baseURI.resolve(path[size]); - - // The Chrome documentation specifies these parameters as - // relative paths. We currently accept absolute URLs as well, - // which means we need to check that the extension is allowed - // to load them. This will throw an error if it's not allowed. - Services.scriptSecurityManager.checkLoadURIStrWithPrincipal( - extension.principal, url, - Services.scriptSecurityManager.DISALLOW_SCRIPT); - - result[size] = url; - } - } - } catch (e) { - // Function is called from extension code, delegate error. - if (context) { - throw e; - } - // If there's no context, it's because we're handling this - // as a manifest directive. Log a warning rather than - // raising an error. - extension.manifestError(`Invalid icon data: ${e}`); - } - - return result; - }, - - // Returns the appropriate icon URL for the given icons object and the - // screen resolution of the given window. - getURL(icons, window, extension, size = 16) { - const DEFAULT = "chrome://browser/content/extension.svg"; - - size *= window.devicePixelRatio; - - let bestSize = null; - if (icons[size]) { - bestSize = size; - } else if (icons[2 * size]) { - bestSize = 2 * size; - } else { - let sizes = Object.keys(icons) - .map(key => parseInt(key, 10)) - .sort((a, b) => a - b); - - bestSize = sizes.find(candidate => candidate > size) || sizes.pop(); - } - - if (bestSize) { - return {size: bestSize, icon: icons[bestSize]}; - } - - return {size, icon: DEFAULT}; - }, - - convertImageDataToPNG(imageData, context) { - let document = context.contentWindow.document; - let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); - canvas.width = imageData.width; - canvas.height = imageData.height; - canvas.getContext("2d").putImageData(imageData, 0, 0); - - return canvas.toDataURL("image/png"); - }, -}; - global.makeWidgetId = id => { id = id.toLowerCase(); // FIXME: This allows for collisions. return id.replace(/[^a-z0-9_-]/g, "_"); }; function promisePopupShown(popup) { return new Promise(resolve => {
--- a/browser/components/extensions/schemas/page_action.json +++ b/browser/components/extensions/schemas/page_action.json @@ -50,24 +50,26 @@ "additionalProperties": { "type": "any" }, "description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)." } ], "functions": [ { "name": "show", "type": "function", + "async": true, "description": "Shows the page action. The page action is shown whenever the tab is selected.", "parameters": [ {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."} ] }, { "name": "hide", "type": "function", + "async": true, "description": "Hides the page action.", "parameters": [ {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."} ] }, { "name": "setTitle", "type": "function",
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js +++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js @@ -196,19 +196,19 @@ add_task(function* testDetailsObjects() } // Sort by resolution, so we don't needlessly switch back and forth // between each test. tests.sort(test => test.resolution); browser.tabs.query({active: true, currentWindow: true}, tabs => { tabId = tabs[0].id; - browser.pageAction.show(tabId); - - browser.test.sendMessage("ready", tests); + browser.pageAction.show(tabId).then(() => { + browser.test.sendMessage("ready", tests); + }); }); } let extension = ExtensionTestUtils.loadExtension({ manifest: { "browser_action": {}, "page_action": {}, "background": { @@ -332,18 +332,19 @@ add_task(function* testDefaultDetails() "browser_action": {"default_icon": icon}, "page_action": {"default_icon": icon}, }, background: function() { browser.tabs.query({active: true, currentWindow: true}, tabs => { let tabId = tabs[0].id; - browser.pageAction.show(tabId); - browser.test.sendMessage("ready"); + browser.pageAction.show(tabId).then(() => { + browser.test.sendMessage("ready"); + }); }); } }); yield Promise.all([extension.startup(), extension.awaitMessage("ready")]); let browserActionId = makeWidgetId(extension.id) + "-browser-action"; let pageActionId = makeWidgetId(extension.id) + "-page-action";
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_context.js +++ b/browser/components/extensions/test/browser/browser_ext_pageAction_context.js @@ -207,18 +207,19 @@ add_task(function* testTabSwitchContext( }; return [ expect => { browser.test.log("Initial state. No icon visible."); expect(null); }, expect => { browser.test.log("Show the icon on the first tab, expect default properties."); - browser.pageAction.show(tabs[0]); - expect(details[0]); + browser.pageAction.show(tabs[0]).then(() => { + expect(details[0]); + }); }, expect => { browser.test.log("Change the icon. Expect default properties excluding the icon."); browser.pageAction.setIcon({tabId: tabs[0], path: "1.png"}); expect(details[1]); }, expect => { browser.test.log("Create a new tab. No icon visible."); @@ -229,22 +230,23 @@ add_task(function* testTabSwitchContext( }, expect => { browser.test.log("Await tab load. No icon visible."); expect(null); }, expect => { browser.test.log("Change properties. Expect new properties."); let tabId = tabs[1]; - browser.pageAction.show(tabId); - browser.pageAction.setIcon({tabId, path: "2.png"}); - browser.pageAction.setPopup({tabId, popup: "2.html"}); - browser.pageAction.setTitle({tabId, title: "Title 2"}); + browser.pageAction.show(tabId).then(() => { + browser.pageAction.setIcon({tabId, path: "2.png"}); + browser.pageAction.setPopup({tabId, popup: "2.html"}); + browser.pageAction.setTitle({tabId, title: "Title 2"}); - expect(details[2]); + expect(details[2]); + }); }, expect => { browser.test.log("Clear the title. Expect default title."); browser.pageAction.setTitle({tabId: tabs[1], title: ""}); expect(details[3]); }, expect => { @@ -255,42 +257,45 @@ add_task(function* testTabSwitchContext( promiseTabLoad({id: tabs[1], url: "about:blank?1"}).then(() => { expect(null); }); browser.tabs.update(tabs[1], {url: "about:blank?1"}); }, expect => { browser.test.log("Show the icon. Expect default properties again."); - browser.pageAction.show(tabs[1]); - expect(details[0]); + browser.pageAction.show(tabs[1]).then(() => { + expect(details[0]); + }); }, expect => { browser.test.log("Switch back to the first tab. Expect previously set properties."); browser.tabs.update(tabs[0], {active: true}, () => { expect(details[1]); }); }, expect => { browser.test.log("Hide the icon on tab 2. Switch back, expect hidden."); - browser.pageAction.hide(tabs[1]); - browser.tabs.update(tabs[1], {active: true}, () => { - expect(null); + browser.pageAction.hide(tabs[1]).then(() => { + browser.tabs.update(tabs[1], {active: true}, () => { + expect(null); + }); }); }, expect => { browser.test.log("Switch back to tab 1. Expect previous results again."); browser.tabs.remove(tabs[1], () => { expect(details[1]); }); }, expect => { browser.test.log("Hide the icon. Expect hidden."); - browser.pageAction.hide(tabs[0]); - expect(null); + browser.pageAction.hide(tabs[0]).then(() => { + expect(null); + }); }, ]; }, }); }); add_task(function* testDefaultTitle() { yield runTests({ @@ -316,18 +321,19 @@ add_task(function* testDefaultTitle() { return [ expect => { browser.test.log("Initial state. No icon visible."); expect(null); }, expect => { browser.test.log("Show the icon on the first tab, expect extension title as default title."); - browser.pageAction.show(tabs[0]); - expect(details[0]); + browser.pageAction.show(tabs[0]).then(() => { + expect(details[0]); + }); }, expect => { browser.test.log("Change the title. Expect new title."); browser.pageAction.setTitle({tabId: tabs[0], title: "Foo Title"}); expect(details[1]); }, expect => { browser.test.log("Clear the title. Expect extension title.");
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js +++ b/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js @@ -123,18 +123,19 @@ add_task(function* testPageActionPopup() } else { browser.test.notifyPass("pageaction-tests-done"); } }); browser.tabs.query({active: true, currentWindow: true}, tabs => { tabId = tabs[0].id; - browser.pageAction.show(tabId); - browser.test.sendMessage("next-test"); + browser.pageAction.show(tabId).then(() => { + browser.test.sendMessage("next-test"); + }); }); }, }, }); let pageActionId = makeWidgetId(extension.id) + "-page-action"; let panelId = makeWidgetId(extension.id) + "-panel";
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js +++ b/browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js @@ -9,18 +9,19 @@ add_task(function* testPageActionPopupRe "default_popup": "popup.html", "browser_style": true, }, }, background: function() { browser.tabs.query({active: true, currentWindow: true}, tabs => { const tabId = tabs[0].id; - browser.pageAction.show(tabId); - browser.test.sendMessage("action-shown"); + browser.pageAction.show(tabId).then(() => { + browser.test.sendMessage("action-shown"); + }); }); }, files: { "popup.html": "<html><head><meta charset=\"utf-8\"></head></html>", }, });
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_simple.js +++ b/browser/components/extensions/test/browser/browser_ext_pageAction_simple.js @@ -27,18 +27,19 @@ add_task(function* () { background: function() { browser.runtime.onMessage.addListener(msg => { browser.test.assertEq(msg, "from-popup", "correct message received"); browser.test.sendMessage("popup"); }); browser.tabs.query({active: true, currentWindow: true}, tabs => { let tabId = tabs[0].id; - browser.pageAction.show(tabId); - browser.test.sendMessage("page-action-shown"); + browser.pageAction.show(tabId).then(() => { + browser.test.sendMessage("page-action-shown"); + }); }); }, }); SimpleTest.waitForExplicitFinish(); let waitForConsole = new Promise(resolve => { SimpleTest.monitorConsole(resolve, [{ message: /Reading manifest: Error processing page_action.unrecognized_property: An unexpected property was found/,
--- a/browser/components/extensions/test/browser/browser_ext_popup_api_injection.js +++ b/browser/components/extensions/test/browser/browser_ext_popup_api_injection.js @@ -24,18 +24,19 @@ add_task(function* testPageActionPopup() <script type="application/javascript" src="popup-b.js"></script></head></html>`, "popup-b.js": 'browser.test.sendMessage("from-popup-b");', }, background: function() { let tabId; browser.tabs.query({active: true, currentWindow: true}, tabs => { tabId = tabs[0].id; - browser.pageAction.show(tabId); - browser.test.sendMessage("ready"); + browser.pageAction.show(tabId).then(() => { + browser.test.sendMessage("ready"); + }); }); browser.test.onMessage.addListener(() => { browser.browserAction.setPopup({popup: "/popup-a.html"}); browser.pageAction.setPopup({tabId, popup: "popup-b.html"}); browser.test.sendMessage("ok"); });
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js +++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js @@ -90,18 +90,19 @@ add_task(function* testBadPermissions() yield testHasNoPermission({ manifest: { "permissions": ["http://example.com/", "activeTab"], "page_action": {}, }, contentSetup() { return new Promise(resolve => { browser.tabs.query({active: true, currentWindow: true}, tabs => { - browser.pageAction.show(tabs[0].id); - resolve(); + browser.pageAction.show(tabs[0].id).then(() => { + resolve(); + }); }); }); }, }); yield BrowserTestUtils.removeTab(tab2); yield BrowserTestUtils.removeTab(tab1); });
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js +++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js @@ -94,18 +94,19 @@ add_task(function* testGoodPermissions() yield testHasPermission({ manifest: { "permissions": ["activeTab"], "page_action": {}, }, contentSetup() { return new Promise(resolve => { browser.tabs.query({active: true, currentWindow: true}, tabs => { - browser.pageAction.show(tabs[0].id); - resolve(); + browser.pageAction.show(tabs[0].id).then(() => { + resolve(); + }); }); }); }, setup: clickPageAction, tearDown: closePageAction, }); info("Test activeTab permission with a browser action w/popup click"); @@ -122,18 +123,19 @@ add_task(function* testGoodPermissions() yield testHasPermission({ manifest: { "permissions": ["activeTab"], "page_action": {"default_popup": "_blank.html"}, }, contentSetup() { return new Promise(resolve => { browser.tabs.query({active: true, currentWindow: true}, tabs => { - browser.pageAction.show(tabs[0].id); - resolve(); + browser.pageAction.show(tabs[0].id).then(() => { + resolve(); + }); }); }); }, setup: clickPageAction, tearDown: closePageAction, }); info("Test activeTab permission with a context menu click");
--- a/browser/themes/shared/aboutNetError.css +++ b/browser/themes/shared/aboutNetError.css @@ -106,17 +106,17 @@ body.certerror #advancedButton { span#hostname { font-weight: bold; } #automaticallyReportInFuture { cursor: pointer; display: inline-block; - -moz-padding-start: 2.3em; + padding-inline-start: 2.3em; text-indent: -2.3em; line-height: 16px } #errorCode:not([href]) { color: var(--in-content-page-color); cursor: text; text-decoration: none;
--- a/devtools/client/debugger/debugger-controller.js +++ b/devtools/client/debugger/debugger-controller.js @@ -898,17 +898,18 @@ StackFrames.prototype = { // Start recording any added variables or properties in any scope and // clear existing scopes to create each one dynamically. DebuggerView.Variables.empty(); // If watch expressions evaluation results are available, create a scope // to contain all the values. if (this._syncedWatchExpressions && aDepth == 0) { let label = L10N.getStr("watchExpressionsScopeLabel"); - let scope = DebuggerView.Variables.addScope(label); + let scope = DebuggerView.Variables.addScope(label, + "variables-view-watch-expressions"); // Customize the scope for holding watch expressions evaluations. scope.descriptorTooltip = false; scope.contextMenuId = "debuggerWatchExpressionsContextMenu"; scope.separatorStr = L10N.getStr("watchExpressionsSeparatorLabel2"); scope.switch = DebuggerView.WatchExpressions.switchExpression; scope.delete = DebuggerView.WatchExpressions.deleteExpression;
--- a/devtools/client/inspector/computed/computed.js +++ b/devtools/client/inspector/computed/computed.js @@ -6,17 +6,17 @@ /* globals StopIteration */ "use strict"; const {Cc, Ci} = require("chrome"); const ToolDefinitions = require("devtools/client/definitions").Tools; -const {CssLogic} = require("devtools/shared/inspector/css-logic"); +const CssLogic = require("devtools/shared/inspector/css-logic"); const {ELEMENT_STYLE} = require("devtools/shared/specs/styles"); const promise = require("promise"); const defer = require("devtools/shared/defer"); const Services = require("Services"); const {OutputParser} = require("devtools/client/shared/output-parser"); const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/client/styleeditor/utils"); const {createChild} = require("devtools/client/inspector/shared/utils"); const {gDevTools} = require("devtools/client/framework/devtools");
--- a/devtools/client/inspector/rules/models/rule.js +++ b/devtools/client/inspector/rules/models/rule.js @@ -3,17 +3,17 @@ /* 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} = require("chrome"); const promise = require("promise"); -const {CssLogic} = require("devtools/shared/inspector/css-logic"); +const CssLogic = require("devtools/shared/inspector/css-logic"); const {ELEMENT_STYLE} = require("devtools/shared/specs/styles"); const {TextProperty} = require("devtools/client/inspector/rules/models/text-property"); const {promiseWarn} = require("devtools/client/inspector/shared/utils"); const {parseDeclarations} = require("devtools/shared/css-parsing-utils"); const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyGetter(this, "osString", function () {
--- a/devtools/client/inspector/rules/rules.js +++ b/devtools/client/inspector/rules/rules.js @@ -9,17 +9,17 @@ const {Cc, Ci} = require("chrome"); const promise = require("promise"); const defer = require("devtools/shared/defer"); const Services = require("Services"); const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm"); const {Task} = require("devtools/shared/task"); const {Tools} = require("devtools/client/definitions"); -const {CssLogic} = require("devtools/shared/inspector/css-logic"); +const {l10n} = require("devtools/shared/inspector/css-logic"); const {ELEMENT_STYLE} = require("devtools/shared/specs/styles"); const {OutputParser} = require("devtools/client/shared/output-parser"); const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/client/styleeditor/utils"); const {ElementStyle} = require("devtools/client/inspector/rules/models/element-style"); const {Rule} = require("devtools/client/inspector/rules/models/rule"); const {RuleEditor} = require("devtools/client/inspector/rules/views/rule-editor"); const {createChild, promiseWarn} = require("devtools/client/inspector/shared/utils"); const {gDevTools} = require("devtools/client/framework/devtools"); @@ -963,17 +963,17 @@ CssRuleView.prototype = { */ _showEmpty: function () { if (this.styleDocument.getElementById("noResults") > 0) { return; } createChild(this.element, "div", { id: "noResults", - textContent: CssLogic.l10n("rule.empty") + textContent: l10n("rule.empty") }); }, /** * Clear the rules. */ _clearRules: function () { this.element.innerHTML = ""; @@ -1006,28 +1006,28 @@ CssRuleView.prototype = { /** * Text for header that shows above rules for this element */ get selectedElementLabel() { if (this._selectedElementLabel) { return this._selectedElementLabel; } - this._selectedElementLabel = CssLogic.l10n("rule.selectedElement"); + this._selectedElementLabel = l10n("rule.selectedElement"); return this._selectedElementLabel; }, /** * Text for header that shows above rules for pseudo elements */ get pseudoElementLabel() { if (this._pseudoElementLabel) { return this._pseudoElementLabel; } - this._pseudoElementLabel = CssLogic.l10n("rule.pseudoElement"); + this._pseudoElementLabel = l10n("rule.pseudoElement"); return this._pseudoElementLabel; }, get showPseudoElements() { if (this._showPseudoElements === undefined) { this._showPseudoElements = Services.prefs.getBoolPref("devtools.inspector.show_pseudo_elements"); }
--- a/devtools/client/inspector/rules/test/doc_frame_script.js +++ b/devtools/client/inspector/rules/test/doc_frame_script.js @@ -14,17 +14,17 @@ // let response = yield executeInContent(browser, "Test:msgName", data, true); // The response message should have the same name "Test:msgName" // // Some listeners do not send a response message back. var {classes: Cc, interfaces: Ci, utils: Cu} = Components; var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {}); -var {CssLogic} = require("devtools/shared/inspector/css-logic"); +var {isContentStylesheet} = require("devtools/shared/inspector/css-logic"); var defer = require("devtools/shared/defer"); /** * Get a value for a given property name in a css rule in a stylesheet, given * their indexes * @param {Object} data Expects a data object with the following properties * - {Number} styleSheetIndex * - {Number} ruleIndex @@ -63,17 +63,17 @@ addMessageListener("Test:GetStyleSheetsI let domUtils = Cc["@mozilla.org/inspector/dom-utils;1"] .getService(Ci.inIDOMUtils); let domRules = domUtils.getCSSStyleRules(target); for (let i = 0, n = domRules.Count(); i < n; i++) { let sheet = domRules.GetElementAt(i).parentStyleSheet; sheets.push({ href: sheet.href, - isContentSheet: CssLogic.isContentStylesheet(sheet) + isContentSheet: isContentStylesheet(sheet) }); } sendAsyncMessage("Test:GetStyleSheetsInfoForNode", sheets); }); /** * Get the property value from the computed style for an element.
--- a/devtools/client/inspector/rules/views/rule-editor.js +++ b/devtools/client/inspector/rules/views/rule-editor.js @@ -1,17 +1,17 @@ /* 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 {Ci} = require("chrome"); const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm"); -const {CssLogic} = require("devtools/shared/inspector/css-logic"); +const {l10n} = require("devtools/shared/inspector/css-logic"); const {ELEMENT_STYLE} = require("devtools/shared/specs/styles"); const {PREF_ORIG_SOURCES} = require("devtools/client/styleeditor/utils"); const {Rule} = require("devtools/client/inspector/rules/models/rule"); const {InplaceEditor, editableField, editableItem} = require("devtools/client/shared/inplace-editor"); const {TextPropertyEditor} = require("devtools/client/inspector/rules/views/text-property-editor"); const { @@ -154,17 +154,17 @@ RuleEditor.prototype = { if (this.rule.domRule.type !== CSSRule.KEYFRAME_RULE && this.rule.domRule.selectors) { let selector = this.rule.domRule.selectors.join(", "); let selectorHighlighter = createChild(header, "span", { class: "ruleview-selectorhighlighter" + (this.ruleView.highlightedSelector === selector ? " highlighted" : ""), - title: CssLogic.l10n("rule.selectorHighlighter.tooltip") + title: l10n("rule.selectorHighlighter.tooltip") }); selectorHighlighter.addEventListener("click", () => { this.ruleView.toggleSelectorHighlighter(selectorHighlighter, selector); }); } this.openBrace = createChild(header, "span", { class: "ruleview-ruleopen",
--- a/devtools/client/inspector/rules/views/text-property-editor.js +++ b/devtools/client/inspector/rules/views/text-property-editor.js @@ -1,16 +1,16 @@ /* 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 {Ci} = require("chrome"); -const {CssLogic} = require("devtools/shared/inspector/css-logic"); +const {l10n} = require("devtools/shared/inspector/css-logic"); const {getCssProperties} = require("devtools/shared/fronts/css-properties"); const {InplaceEditor, editableField} = require("devtools/client/shared/inplace-editor"); const { createChild, appendText, advanceValidate, blurOnMultipleProperties, @@ -171,25 +171,25 @@ TextPropertyEditor.prototype = { value: parsedValue, priority: this.prop.priority }; appendText(this.valueContainer, ";"); this.warning = createChild(this.container, "div", { class: "ruleview-warning", hidden: "", - title: CssLogic.l10n("rule.warning.title"), + title: l10n("rule.warning.title"), }); // Filter button that filters for the current property name and is // displayed when the property is overridden by another rule. this.filterProperty = createChild(this.container, "div", { class: "ruleview-overridden-rule-filter", hidden: "", - title: CssLogic.l10n("rule.filterProperty.title"), + title: l10n("rule.filterProperty.title"), }); this.filterProperty.addEventListener("click", event => { this.ruleEditor.ruleView.setFilterStyles("`" + this.prop.name + "`"); event.stopPropagation(); }, false); // Holds the viewers for the computed properties. @@ -366,17 +366,17 @@ TextPropertyEditor.prototype = { // knows about this.ruleView.tooltips.colorPicker.addSwatch(span, { onShow: this._onStartEditing, onPreview: this._onSwatchPreview, onCommit: this._onSwatchCommit, onRevert: this._onSwatchRevert }); span.on("unit-change", this._onSwatchCommit); - let title = CssLogic.l10n("rule.colorSwatch.tooltip"); + let title = l10n("rule.colorSwatch.tooltip"); span.setAttribute("title", title); } } // Attach the cubic-bezier tooltip to the bezier swatches this._bezierSwatchSpans = this.valueSpan.querySelectorAll("." + BEZIER_SWATCH_CLASS); if (this.ruleEditor.isEditable) { @@ -384,44 +384,44 @@ TextPropertyEditor.prototype = { // Adding this swatch to the list of swatches our colorpicker // knows about this.ruleView.tooltips.cubicBezier.addSwatch(span, { onShow: this._onStartEditing, onPreview: this._onSwatchPreview, onCommit: this._onSwatchCommit, onRevert: this._onSwatchRevert }); - let title = CssLogic.l10n("rule.bezierSwatch.tooltip"); + let title = l10n("rule.bezierSwatch.tooltip"); span.setAttribute("title", title); } } // Attach the filter editor tooltip to the filter swatch let span = this.valueSpan.querySelector("." + FILTER_SWATCH_CLASS); if (this.ruleEditor.isEditable) { if (span) { parserOptions.filterSwatch = true; this.ruleView.tooltips.filterEditor.addSwatch(span, { onShow: this._onStartEditing, onPreview: this._onSwatchPreview, onCommit: this._onSwatchCommit, onRevert: this._onSwatchRevert }, outputParser, parserOptions); - let title = CssLogic.l10n("rule.filterSwatch.tooltip"); + let title = l10n("rule.filterSwatch.tooltip"); span.setAttribute("title", title); } } this.angleSwatchSpans = this.valueSpan.querySelectorAll("." + ANGLE_SWATCH_CLASS); if (this.ruleEditor.isEditable) { for (let angleSpan of this.angleSwatchSpans) { angleSpan.on("unit-change", this._onSwatchCommit); - let title = CssLogic.l10n("rule.angleSwatch.tooltip"); + let title = l10n("rule.angleSwatch.tooltip"); angleSpan.setAttribute("title", title); } } // Now that we have updated the property's value, we might have a pending // click on the value container. If we do, we have to trigger a click event // on the right element. if (this._hasPendingClick) {
--- a/devtools/client/inspector/shared/test/doc_frame_script.js +++ b/devtools/client/inspector/shared/test/doc_frame_script.js @@ -14,17 +14,17 @@ // let response = yield executeInContent(browser, "Test:MsgName", data, true); // The response message should have the same name "Test:MsgName" // // Some listeners do not send a response message back. var {classes: Cc, interfaces: Ci, utils: Cu} = Components; var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {}); -var {CssLogic} = require("devtools/shared/inspector/css-logic"); +var {isContentStylesheet} = require("devtools/shared/inspector/css-logic"); var defer = require("devtools/shared/defer"); /** * Get a value for a given property name in a css rule in a stylesheet, given * their indexes * @param {Object} data Expects a data object with the following properties * - {Number} styleSheetIndex * - {Number} ruleIndex @@ -63,17 +63,17 @@ addMessageListener("Test:GetStyleSheetsI let domUtils = Cc["@mozilla.org/inspector/dom-utils;1"] .getService(Ci.inIDOMUtils); let domRules = domUtils.getCSSStyleRules(target); for (let i = 0, n = domRules.Count(); i < n; i++) { let sheet = domRules.GetElementAt(i).parentStyleSheet; sheets.push({ href: sheet.href, - isContentSheet: CssLogic.isContentStylesheet(sheet) + isContentSheet: isContentStylesheet(sheet) }); } sendAsyncMessage("Test:GetStyleSheetsInfoForNode", sheets); }); /** * Get the property value from the computed style for an element.
--- a/devtools/client/inspector/shared/test/head.js +++ b/devtools/client/inspector/shared/test/head.js @@ -7,17 +7,16 @@ "use strict"; // Import the inspector's head.js first (which itself imports shared-head.js). Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js", this); var {CssRuleView} = require("devtools/client/inspector/rules/rules"); -var {CssLogic, CssSelector} = require("devtools/shared/inspector/css-logic"); var {getInplaceEditorForSpan: inplaceEditor} = require("devtools/client/shared/inplace-editor"); const TEST_URL_ROOT = "http://example.com/browser/devtools/client/inspector/shared/test/"; const TEST_URL_ROOT_SSL = "https://example.com/browser/devtools/client/inspector/shared/test/"; const ROOT_TEST_DIR = getRootDirectory(gTestPath);
--- a/devtools/client/netmonitor/netmonitor-view.js +++ b/devtools/client/netmonitor/netmonitor-view.js @@ -133,16 +133,21 @@ const LOAD_CAUSE_STRINGS = { [Ci.nsIContentPolicy.TYPE_WEBSOCKET]: "websocket", [Ci.nsIContentPolicy.TYPE_CSP_REPORT]: "csp", [Ci.nsIContentPolicy.TYPE_XSLT]: "xslt", [Ci.nsIContentPolicy.TYPE_BEACON]: "beacon", [Ci.nsIContentPolicy.TYPE_FETCH]: "fetch", [Ci.nsIContentPolicy.TYPE_IMAGESET]: "imageset", [Ci.nsIContentPolicy.TYPE_WEB_MANIFEST]: "webManifest" }; + +function loadCauseString(causeType) { + return LOAD_CAUSE_STRINGS[causeType] || "unknown"; +} + const DEFAULT_EDITOR_CONFIG = { mode: Editor.modes.text, readOnly: true, lineNumbers: true }; const GENERIC_VARIABLES_VIEW_SETTINGS = { lazyEmpty: true, // ms @@ -1223,16 +1228,23 @@ RequestsMenuView.prototype = Heritage.ex break; case "domain": if (direction == "ascending") { this.sortContents(this._byDomain); } else { this.sortContents((a, b) => !this._byDomain(a, b)); } break; + case "cause": + if (direction == "ascending") { + this.sortContents(this._byCause); + } else { + this.sortContents((a, b) => !this._byCause(a, b)); + } + break; case "type": if (direction == "ascending") { this.sortContents(this._byType); } else { this.sortContents((a, b) => !this._byType(a, b)); } break; case "transferred": @@ -1434,20 +1446,28 @@ RequestsMenuView.prototype = Heritage.ex _byDomain: function ({ attachment: first }, { attachment: second }) { let firstDomain = this._getUriHostPort(first.url).toLowerCase(); let secondDomain = this._getUriHostPort(second.url).toLowerCase(); return firstDomain == secondDomain ? first.startedMillis > second.startedMillis : firstDomain > secondDomain; }, + _byCause: function ({ attachment: first }, { attachment: second }) { + let firstCause = loadCauseString(first.cause.type); + let secondCause = loadCauseString(second.cause.type); + + return firstCause == secondCause + ? first.startedMillis > second.startedMillis + : firstCause > secondCause; + }, + _byType: function ({ attachment: first }, { attachment: second }) { let firstType = this._getAbbreviatedMimeType(first.mimeType).toLowerCase(); - let secondType = this._getAbbreviatedMimeType(second.mimeType) - .toLowerCase(); + let secondType = this._getAbbreviatedMimeType(second.mimeType).toLowerCase(); return firstType == secondType ? first.startedMillis > second.startedMillis : firstType > secondType; }, _byTransferred: function ({ attachment: first }, { attachment: second }) { return first.transferredSize > second.transferredSize; @@ -1935,18 +1955,17 @@ RequestsMenuView.prototype = Heritage.ex } case "statusText": { let node = $(".requests-menu-status", target); node.setAttribute("tooltiptext", value); break; } case "cause": { let labelNode = $(".requests-menu-cause-label", target); - let text = LOAD_CAUSE_STRINGS[value.type] || "unknown"; - labelNode.setAttribute("value", text); + labelNode.setAttribute("value", loadCauseString(value.type)); if (value.loadingDocumentUri) { labelNode.setAttribute("tooltiptext", value.loadingDocumentUri); } let stackNode = $(".requests-menu-cause-stack", target); if (value.stacktrace && value.stacktrace.length > 0) { stackNode.removeAttribute("hidden"); }
--- a/devtools/client/netmonitor/test/browser_net_cause.js +++ b/devtools/client/netmonitor/test/browser_net_cause.js @@ -80,17 +80,18 @@ const EXPECTED_REQUESTS = [ var test = Task.async(function* () { // the initNetMonitor function clears the network request list after the // page is loaded. That's why we first load a bogus page from SIMPLE_URL, // and only then load the real thing from CAUSE_URL - we want to catch // all the requests the page is making, not only the XHRs. // We can't use about:blank here, because initNetMonitor checks that the // page has actually made at least one request. let [, debuggee, monitor] = yield initNetMonitor(SIMPLE_URL); - let { RequestsMenu } = monitor.panelWin.NetMonitorView; + let { $, NetMonitorView } = monitor.panelWin; + let { RequestsMenu } = NetMonitorView; RequestsMenu.lazyUpdate = false; debuggee.location = CAUSE_URL; yield waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length); is(RequestsMenu.itemCount, EXPECTED_REQUESTS.length, "All the page events should be recorded."); @@ -124,11 +125,21 @@ var test = Task.async(function* () { `Request #${i} has the correct async cause on JS stack frame #${j}`); }); } } else { is(stackLen, 0, `Request #${i} (${causeType}) has an empty stacktrace`); } }); + // Sort the requests by cause and check the order + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-cause-button")); + let expectedOrder = EXPECTED_REQUESTS.map(r => r.causeType).sort(); + expectedOrder.forEach((expectedCause, i) => { + let { target } = RequestsMenu.getItemAtIndex(i); + let causeLabel = target.querySelector(".requests-menu-cause-label"); + let cause = causeLabel.getAttribute("value"); + is(cause, expectedCause, `The request #${i} has the expected cause after sorting`); + }); + yield teardown(monitor); finish(); });
--- a/devtools/client/responsive.html/actions/screenshot.js +++ b/devtools/client/responsive.html/actions/screenshot.js @@ -11,18 +11,17 @@ const { TAKE_SCREENSHOT_END, } = require("./index"); const { getFormatStr } = require("../utils/l10n"); const { getToplevelWindow } = require("sdk/window/utils"); const { Task: { spawn } } = require("devtools/shared/task"); const e10s = require("../utils/e10s"); -const BASE_URL = "resource://devtools/client/responsive.html"; -const audioCamera = new window.Audio(`${BASE_URL}/audio/camera-click.mp3`); +const audioCamera = new window.Audio("resource://devtools/client/themes/audio/shutter.wav"); const animationFrame = () => new Promise(resolve => { window.requestAnimationFrame(resolve); }); function getFileName() { let date = new Date(); let month = ("0" + (date.getMonth() + 1)).substr(-2);
deleted file mode 100644 index 6d9af013315d873e910ecf6e15bc298093dc1e99..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@<O00001
deleted file mode 100644 --- a/devtools/client/responsive.html/audio/moz.build +++ /dev/null @@ -1,9 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -DevToolsModules( - 'camera-click.mp3', -)
--- a/devtools/client/responsive.html/components/device-selector.js +++ b/devtools/client/responsive.html/components/device-selector.js @@ -74,45 +74,51 @@ module.exports = createClass({ } let state = devices.listState; let listContent; if (state == Types.deviceListState.LOADED) { listContent = [dom.option({ value: "", + title: "", disabled: true, hidden: true, }, getStr("responsive.noDeviceSelected")), options.map(device => { return dom.option({ key: device.name, value: device.name, + title: "", }, device.name); }), dom.option({ value: OPEN_DEVICE_MODAL_VALUE, + title: "", }, getStr("responsive.editDeviceList"))]; } else if (state == Types.deviceListState.LOADING || state == Types.deviceListState.INITIALIZED) { listContent = [dom.option({ value: "", + title: "", disabled: true, }, getStr("responsive.deviceListLoading"))]; } else if (state == Types.deviceListState.ERROR) { listContent = [dom.option({ value: "", + title: "", disabled: true, }, getStr("responsive.deviceListError"))]; } return dom.select( { className: selectClass, value: selectedDevice, + title: selectedDevice, onChange: this.onSelectChange, disabled: (state !== Types.deviceListState.LOADED), }, ...listContent ); }, });
--- a/devtools/client/responsive.html/index.css +++ b/devtools/client/responsive.html/index.css @@ -172,27 +172,28 @@ html, body { justify-content: center; height: 18px; } .viewport-device-selector { -moz-appearance: none; background-color: var(--theme-toolbar-background); background-image: var(--viewport-selection-arrow); - background-position: 136px; + background-position: 100% 52%; background-repeat: no-repeat; background-size: 7px; border: none; color: var(--viewport-color); height: 100%; - padding: 0 16px 0 0; + padding: 0 8px 0 8px; text-align: center; text-overflow: ellipsis; width: 150px; font-size: 11px; + width: -moz-fit-content; } .viewport-device-selector.selected { background-image: var(--viewport-selection-arrow-selected); color: var(--viewport-active-color); } .viewport-device-selector:hover {
--- a/devtools/client/responsive.html/moz.build +++ b/devtools/client/responsive.html/moz.build @@ -1,17 +1,16 @@ # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # 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/. DIRS += [ 'actions', - 'audio', 'browser', 'components', 'images', 'reducers', 'utils', ] DevToolsModules(
--- a/devtools/client/shared/components/test/mochitest/chrome.ini +++ b/devtools/client/shared/components/test/mochitest/chrome.ini @@ -3,16 +3,17 @@ support-files = head.js [test_HSplitBox_01.html] [test_notification_box_01.html] [test_notification_box_02.html] [test_notification_box_03.html] [test_reps_attribute.html] [test_reps_date-time.html] +[test_reps_function.html] [test_reps_grip.html] [test_reps_object-with-url.html] [test_reps_stylesheet.html] [test_reps_undefined.html] [test_reps_window.html] [test_frame_01.html] [test_tree_01.html] [test_tree_02.html]
--- a/devtools/client/shared/components/test/mochitest/head.js +++ b/devtools/client/shared/components/test/mochitest/head.js @@ -183,8 +183,23 @@ function renderComponent(component, prop } function shallowRenderComponent(component, props) { const el = React.createElement(component, props); const renderer = TestUtils.createRenderer(); renderer.render(el, {}); return renderer.getRenderOutput(); } + +/** + * Test that a rep renders correctly across different modes. + */ +function testRepRenderModes(modeTests, testName, componentUnderTest, gripStub) { + modeTests.forEach(({mode, expectedOutput, message}) => { + const modeString = typeof mode === "undefined" ? "no mode" : mode; + if (!message) { + message = `${testName}: ${modeString} renders correctly.`; + } + + const rendered = renderComponent(componentUnderTest.rep, { object: gripStub, mode }); + is(rendered.textContent, expectedOutput, message); + }); +}
new file mode 100644 --- /dev/null +++ b/devtools/client/shared/components/test/mochitest/test_reps_function.html @@ -0,0 +1,171 @@ + +<!DOCTYPE HTML> +<html> +<!-- +Test Func rep +--> +<head> + <meta charset="utf-8"> + <title>Rep test - Func</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="head.js" type="application/javascript;version=1.8"></script> +<script type="application/javascript;version=1.8"> +window.onload = Task.async(function* () { + let { Rep } = browserRequire("devtools/client/shared/components/reps/rep"); + let { Func } = browserRequire("devtools/client/shared/components/reps/function"); + + const componentUnderTest = Func; + + try { + // Test that correct rep is chosen + const gripStub = getGripStub("testNamed"); + const renderedRep = shallowRenderComponent(Rep, { object: gripStub }); + is(renderedRep.type, Func.rep, `Rep correctly selects ${Func.rep.displayName}`); + + yield testNamed(); + yield testVarNamed(); + yield testAnon(); + yield testLongName(); + } catch(e) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } finally { + SimpleTest.finish(); + } + + function testNamed() { + // Test declaration: `function testName{ let innerVar = "foo" }` + const testName = "testNamed"; + + const defaultOutput = `testName()`; + + const modeTests = [ + { + mode: undefined, + expectedOutput: defaultOutput, + } + ]; + + testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName)); + } + + function testVarNamed() { + // Test declaration: `let testVarName = function() { }` + const testName = "testVarNamed"; + + const defaultOutput = `testVarName()`; + + const modeTests = [ + { + mode: undefined, + expectedOutput: defaultOutput, + } + ]; + + testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName)); + } + + function testAnon() { + // Test declaration: `() => {}` + const testName = "testAnon"; + + const defaultOutput = `function()`; + + const modeTests = [ + { + mode: undefined, + expectedOutput: defaultOutput, + } + ]; + + testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName)); + } + + function testLongName() { + // Test declaration: `let f = function loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong() { }` + const testName = "testLongName"; + + const defaultOutput = `looooooooooooooooooooooooooooooooooooooooooooooooo\u2026ooooooooooooooooooooooooooooooooooooooooooooong()`; + + const modeTests = [ + { + mode: undefined, + expectedOutput: defaultOutput, + } + ]; + + testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName)); + } + + function getGripStub(functionName) { + switch (functionName) { + case "testNamed": + return { + "type": "object", + "class": "Function", + "actor": "server1.conn6.obj35", + "extensible": true, + "frozen": false, + "sealed": false, + "name": "testName", + "displayName": "testName", + "location": { + "url": "debugger eval code", + "line": 1 + } + }; + + case "testVarNamed": + return { + "type": "object", + "class": "Function", + "actor": "server1.conn7.obj41", + "extensible": true, + "frozen": false, + "sealed": false, + "displayName": "testVarName", + "location": { + "url": "debugger eval code", + "line": 1 + } + }; + + case "testAnon": + return { + "type": "object", + "class": "Function", + "actor": "server1.conn7.obj45", + "extensible": true, + "frozen": false, + "sealed": false, + "location": { + "url": "debugger eval code", + "line": 1 + } + }; + + case "testLongName": + return { + "type": "object", + "class": "Function", + "actor": "server1.conn7.obj67", + "extensible": true, + "frozen": false, + "sealed": false, + "name": "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", + "displayName": "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", + "location": { + "url": "debugger eval code", + "line": 1 + } + }; + } + } +}); +</script> +</pre> +</body> +</html>
--- a/devtools/client/shared/components/test/mochitest/test_reps_grip.html +++ b/devtools/client/shared/components/test/mochitest/test_reps_grip.html @@ -13,16 +13,18 @@ Test grip rep <body> <pre id="test"> <script src="head.js" type="application/javascript;version=1.8"></script> <script type="application/javascript;version=1.8"> window.onload = Task.async(function* () { let { Rep } = browserRequire("devtools/client/shared/components/reps/rep"); let { Grip } = browserRequire("devtools/client/shared/components/reps/grip"); + const componentUnderTest = Grip; + try { yield testBasic(); // Test property iterator yield testMaxProps(); yield testMoreThanMaxProps(); yield testUninterestingProps(); @@ -61,17 +63,17 @@ window.onload = Task.async(function* () expectedOutput: defaultOutput, }, { mode: "long", expectedOutput: defaultOutput, } ]; - testRenderingInMode(modeTests, testName); + testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName)); } function testMaxProps() { // Test object: `{a: "a", b: "b", c: "c"}`; const testName = "testMaxProps"; const defaultOutput = `Object {a: "a", b: "b", c: "c"}`; @@ -89,17 +91,17 @@ window.onload = Task.async(function* () expectedOutput: defaultOutput, }, { mode: "long", expectedOutput: defaultOutput, } ]; - testRenderingInMode(modeTests, testName); + testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName)); } function testMoreThanMaxProps() { // Test object = `{p0: "0", p1: "1", p2: "2", ..., p101: "101"}` const testName = "testMoreThanMaxProps"; const defaultOutput = `Object {p0: "0", p1: "1", p2: "2", more...}`; @@ -126,17 +128,17 @@ window.onload = Task.async(function* () expectedOutput: defaultOutput, }, { mode: "long", expectedOutput: longOutput, } ]; - testRenderingInMode(modeTests, testName); + testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName)); } function testUninterestingProps() { // Test object: `{a: undefined, b: undefined, c: "c", d: 1}` // @TODO This is not how we actually want the preview to be output. // See https://bugzilla.mozilla.org/show_bug.cgi?id=1276376 const expectedOutput = `Object {a: undefined, b: undefined, c: "c", more...}`; } @@ -161,17 +163,17 @@ window.onload = Task.async(function* () expectedOutput: defaultOutput, }, { mode: "long", expectedOutput: defaultOutput, } ]; - testRenderingInMode(modeTests, testName); + testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName)); } function testNestedArray() { // Test object: `{arrProp: ["foo", "bar", "baz"]}` const testName = "testNestedArray"; const defaultOutput = `Object {arrProp: [3]}`; @@ -189,30 +191,17 @@ window.onload = Task.async(function* () expectedOutput: defaultOutput, }, { mode: "long", expectedOutput: defaultOutput, } ]; - testRenderingInMode(modeTests, testName); - } - - function testRenderingInMode(modeTests, testName) { - modeTests.forEach(({mode, expectedOutput, message}) => { - const modeString = typeof mode === "undefined" ? "no mode" : mode; - if (!message) { - message = `${testName}: ${modeString} renders correctly.` - } - const gripStub = getGripStub(testName); - - const rendered = renderComponent(Grip.rep, { object: gripStub, mode }); - is(rendered.textContent, expectedOutput, message); - }); + testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName)); } function getGripStub(functionName) { switch (functionName) { case "testBasic": return { "type": "object", "class": "Object",
--- a/devtools/client/shared/widgets/VariablesView.jsm +++ b/devtools/client/shared/widgets/VariablesView.jsm @@ -123,24 +123,26 @@ VariablesView.prototype = { /** * Adds a scope to contain any inspected variables. * * This new scope will be considered the parent of any other scope * added afterwards. * * @param string aName * The scope's name (e.g. "Local", "Global" etc.). + * @param string aCustomClass + * An additional class name for the containing element. * @return Scope * The newly created Scope instance. */ - addScope: function (aName = "") { + addScope: function (aName = "", aCustomClass = "") { this._removeEmptyNotice(); this._toggleSearchVisibility(true); - let scope = new Scope(this, aName); + let scope = new Scope(this, aName, { customClass: aCustomClass }); this._store.push(scope); this._itemsByElement.set(scope._target, scope); this._currHierarchy.set(aName, scope); scope.header = !!aName; return scope; }, @@ -1787,17 +1789,18 @@ Scope.prototype = { * * @param string aName * The scope's name. * @param object aFlags [optional] * Additional options or flags for this scope. */ _init: function (aName, aFlags) { this._idString = generateId(this._nameString = aName); - this._displayScope(aName, this.targetClassName, "devtools-toolbar"); + this._displayScope(aName, `${this.targetClassName} ${aFlags.customClass}`, + "devtools-toolbar"); this._addEventListeners(); this.parentNode.appendChild(this._target); }, /** * Creates the necessary nodes for this scope. * * @param string aName @@ -1815,16 +1818,17 @@ Scope.prototype = { element.className = aTargetClassName; let arrow = this._arrow = document.createElement("hbox"); arrow.className = "arrow theme-twisty"; let name = this._name = document.createElement("label"); name.className = "plain name"; name.setAttribute("value", aName); + name.setAttribute("crop", "end"); let title = this._title = document.createElement("hbox"); title.className = "title " + aTitleClassName; title.setAttribute("align", "center"); let enumerable = this._enum = document.createElement("vbox"); let nonenum = this._nonenum = document.createElement("vbox"); enumerable.className = "variables-view-element-details enum";
--- a/devtools/client/sourceeditor/codemirror/README +++ b/devtools/client/sourceeditor/codemirror/README @@ -1,16 +1,16 @@ This is the CodeMirror editor packaged for the Mozilla Project. CodeMirror is a JavaScript component that provides a code editor in the browser. When a mode is available for the language you are coding in, it will color your code, and optionally help with indentation. # Upgrade -Currently used version is 5.15.2. To upgrade, download a new version of +Currently used version is 5.16.0. To upgrade, download a new version of CodeMirror from the project's page [1] and replace all JavaScript and CSS files inside the codemirror directory [2]. To confirm the functionality run mochitests for the following components: * sourceeditor * scratchpad * debugger
--- a/devtools/client/sourceeditor/codemirror/addon/fold/comment-fold.js +++ b/devtools/client/sourceeditor/codemirror/addon/fold/comment-fold.js @@ -24,17 +24,17 @@ CodeMirror.registerGlobalHelper("fold", if (found == -1) { if (pass == 1) return; pass = 1; at = lineText.length; continue; } if (pass == 1 && found < start.ch) return; if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) && - (lineText.slice(found - endToken.length, found) == endToken || + (found == 0 || lineText.slice(found - endToken.length, found) == endToken || !/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) { startCh = found + startToken.length; break; } at = found - 1; } var depth = 1, lastLine = cm.lastLine(), end, endCh;
--- a/devtools/client/sourceeditor/codemirror/addon/fold/foldcode.js +++ b/devtools/client/sourceeditor/codemirror/addon/fold/foldcode.js @@ -44,17 +44,17 @@ var myWidget = makeWidget(cm, options); CodeMirror.on(myWidget, "mousedown", function(e) { myRange.clear(); CodeMirror.e_preventDefault(e); }); var myRange = cm.markText(range.from, range.to, { replacedWith: myWidget, - clearOnEnter: true, + clearOnEnter: getOption(cm, options, "clearOnEnter"), __isFold: true }); myRange.on("clear", function(from, to) { CodeMirror.signal(cm, "unfold", cm, from, to); }); CodeMirror.signal(cm, "fold", cm, range.from, range.to); } @@ -124,17 +124,18 @@ if (cur) return cur; } }); var defaultOptions = { rangeFinder: CodeMirror.fold.auto, widget: "\u2194", minFoldSize: 0, - scanUp: false + scanUp: false, + clearOnEnter: true }; CodeMirror.defineOption("foldOptions", null); function getOption(cm, options, name) { if (options && options[name] !== undefined) return options[name]; var editorOptions = cm.options.foldOptions;
--- a/devtools/client/sourceeditor/codemirror/addon/fold/foldgutter.js +++ b/devtools/client/sourceeditor/codemirror/addon/fold/foldgutter.js @@ -45,17 +45,17 @@ if (opts === true) opts = {}; if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter"; if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open"; if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded"; return opts; } function isFolded(cm, line) { - var marks = cm.findMarksAt(Pos(line)); + var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0)); for (var i = 0; i < marks.length; ++i) if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i]; } function marker(spec) { if (typeof spec == "string") { var elt = document.createElement("div"); elt.className = spec + " CodeMirror-guttermarker-subtle";
--- a/devtools/client/sourceeditor/codemirror/addon/hint/show-hint.js +++ b/devtools/client/sourceeditor/codemirror/addon/hint/show-hint.js @@ -224,16 +224,17 @@ var left = pos.left, top = pos.bottom, below = true; hints.style.left = left + "px"; hints.style.top = top + "px"; // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth); var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight); (completion.options.container || document.body).appendChild(hints); var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH; + var scrolls = hints.scrollHeight > hints.clientHeight + 1 if (overlapY > 0) { var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top); if (curTop - height > 0) { // Fits above cursor hints.style.top = (top = pos.top - height) + "px"; below = false; } else if (height > winH) { hints.style.height = (winH - 5) + "px"; hints.style.top = (top = pos.bottom - box.top) + "px"; @@ -248,16 +249,18 @@ var overlapX = box.right - winW; if (overlapX > 0) { if (box.right - box.left > winW) { hints.style.width = (winW - 5) + "px"; overlapX -= (box.right - box.left) - winW; } hints.style.left = (left = pos.left - overlapX) + "px"; } + if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling) + node.style.paddingRight = cm.display.nativeBarWidth + "px" cm.addKeyMap(this.keyMap = buildKeyMap(completion, { moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); }, setFocus: function(n) { widget.changeActive(n); }, menuSize: function() { return widget.screenAmount(); }, length: completions.length, close: function() { completion.close(); }, pick: function() { widget.pick(); },
--- a/devtools/client/sourceeditor/codemirror/keymap/sublime.js +++ b/devtools/client/sourceeditor/codemirror/keymap/sublime.js @@ -415,16 +415,44 @@ at = word.from; cm.replaceRange(mod(word.word), word.from, word.to); } }); } map[cK + ctrl + "Backspace"] = "delLineLeft"; + cmds[map["Backspace"] = "smartBackspace"] = function(cm) { + if (cm.somethingSelected()) return CodeMirror.Pass; + + cm.operation(function() { + var cursors = cm.listSelections(); + var indentUnit = cm.getOption("indentUnit"); + + for (var i = cursors.length - 1; i >= 0; i--) { + var cursor = cursors[i].head; + var toStartOfLine = cm.getRange({line: cursor.line, ch: 0}, cursor); + var column = CodeMirror.countColumn(toStartOfLine, null, cm.getOption("tabSize")); + + // Delete by one character by default + var deletePos = cm.findPosH(cursor, -1, "char", false); + + if (toStartOfLine && !/\S/.test(toStartOfLine) && column % indentUnit == 0) { + var prevIndent = new Pos(cursor.line, + CodeMirror.findColumn(toStartOfLine, column - indentUnit, indentUnit)); + + // Smart delete only if we found a valid prevIndent location + if (prevIndent.ch != cursor.ch) deletePos = prevIndent; + } + + cm.replaceRange("", deletePos, cursor, "+delete"); + } + }); + }; + cmds[map[cK + ctrl + "K"] = "delLineRight"] = function(cm) { cm.operation(function() { var ranges = cm.listSelections(); for (var i = ranges.length - 1; i >= 0; i--) cm.replaceRange("", ranges[i].anchor, Pos(ranges[i].to().line), "+delete"); cm.scrollIntoView(); }); }; @@ -467,27 +495,28 @@ }; map[cK + ctrl + "G"] = "clearBookmarks"; cmds[map[cK + ctrl + "C"] = "showInCenter"] = function(cm) { var pos = cm.cursorCoords(null, "local"); cm.scrollTo(null, (pos.top + pos.bottom) / 2 - cm.getScrollInfo().clientHeight / 2); }; - cmds[map["Shift-Alt-Up"] = "selectLinesUpward"] = function(cm) { + var selectLinesCombo = mac ? "Ctrl-Shift-" : "Ctrl-Alt-"; + cmds[map[selectLinesCombo + "Up"] = "selectLinesUpward"] = function(cm) { cm.operation(function() { var ranges = cm.listSelections(); for (var i = 0; i < ranges.length; i++) { var range = ranges[i]; if (range.head.line > cm.firstLine()) cm.addSelection(Pos(range.head.line - 1, range.head.ch)); } }); }; - cmds[map["Shift-Alt-Down"] = "selectLinesDownward"] = function(cm) { + cmds[map[selectLinesCombo + "Down"] = "selectLinesDownward"] = function(cm) { cm.operation(function() { var ranges = cm.listSelections(); for (var i = 0; i < ranges.length; i++) { var range = ranges[i]; if (range.head.line < cm.lastLine()) cm.addSelection(Pos(range.head.line + 1, range.head.ch)); } });
--- a/devtools/client/sourceeditor/codemirror/lib/codemirror.css +++ b/devtools/client/sourceeditor/codemirror/lib/codemirror.css @@ -83,18 +83,24 @@ 100% {} } /* Can style cursor different in overwrite (non-insert) mode */ .CodeMirror-overwrite .CodeMirror-cursor {} .cm-tab { display: inline-block; text-decoration: inherit; } +.CodeMirror-rulers { + position: absolute; + left: 0; right: 0; top: -50px; bottom: -20px; + overflow: hidden; +} .CodeMirror-ruler { border-left: 1px solid #ccc; + top: 0; bottom: 0; position: absolute; } /* DEFAULT THEME */ .cm-s-default .cm-header {color: blue;} .cm-s-default .cm-quote {color: #090;} .cm-negative {color: #d44;} @@ -286,17 +292,20 @@ div.CodeMirror span.CodeMirror-nonmatchi .CodeMirror-measure { position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden; } -.CodeMirror-cursor { position: absolute; } +.CodeMirror-cursor { + position: absolute; + pointer-events: none; +} .CodeMirror-measure pre { position: static; } div.CodeMirror-cursors { visibility: hidden; position: relative; z-index: 3; } div.CodeMirror-dragcursors {
--- a/devtools/client/sourceeditor/codemirror/lib/codemirror.js +++ b/devtools/client/sourceeditor/codemirror/lib/codemirror.js @@ -2928,20 +2928,33 @@ var from = lineLeft(lineObj), to = lineRight(lineObj); var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine; if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); // Do a binary search between these bounds. for (;;) { if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { var ch = x < fromX || x - fromX <= toX - x ? from : to; + var outside = ch == from ? fromOutside : toOutside var xDiff = x - (ch == from ? fromX : toX); + // This is a kludge to handle the case where the coordinates + // are after a line-wrapped line. We should replace it with a + // more general handling of cursor positions around line + // breaks. (Issue #4078) + if (toOutside && !bidi && !/\s/.test(lineObj.text.charAt(ch)) && xDiff > 0 && + ch < lineObj.text.length && preparedMeasure.view.measure.heights.length > 1) { + var charSize = measureCharPrepared(cm, preparedMeasure, ch, "right"); + if (innerOff <= charSize.bottom && innerOff >= charSize.top && Math.abs(x - charSize.right) < xDiff) { + outside = false + ch++ + xDiff = x - charSize.right + } + } while (isExtendingChar(lineObj.text.charAt(ch))) ++ch; - var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside, - xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0); + var pos = PosWithInfo(lineNo, ch, outside, xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0); return pos; } var step = Math.ceil(dist / 2), middle = from + step; if (bidi) { middle = from; for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); } var middleX = getX(middle); @@ -3655,16 +3668,17 @@ setTimeout(function() {document.body.focus(); display.input.focus();}, 20); else display.input.focus(); } }); // Let the drag handler handle this. if (webkit) display.scroller.draggable = true; cm.state.draggingText = dragEnd; + dragEnd.copy = mac ? e.altKey : e.ctrlKey // IE's approach to draggable if (display.scroller.dragDrop) display.scroller.dragDrop(); on(document, "mouseup", dragEnd); on(display.scroller, "drop", dragEnd); } // Normal selection, as opposed to text dragging. function leftButtonSelect(cm, e, start, type, addNew) { @@ -3885,17 +3899,17 @@ cm.state.draggingText(e); // Ensure the editor is re-focused setTimeout(function() {cm.display.input.focus();}, 20); return; } try { var text = e.dataTransfer.getData("Text"); if (text) { - if (cm.state.draggingText && !(mac ? e.altKey : e.ctrlKey)) + if (cm.state.draggingText && !cm.state.draggingText.copy) var selected = cm.listSelections(); setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); if (selected) for (var i = 0; i < selected.length; ++i) replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag"); cm.replaceSelection(text, "around", "paste"); cm.display.input.focus(); } } @@ -8897,12 +8911,12 @@ order.push(new BidiSpan(order[0].level, len, len)); return order; }; })(); // THE END - CodeMirror.version = "5.15.2"; + CodeMirror.version = "5.16.0"; return CodeMirror; });
--- a/devtools/client/sourceeditor/test/codemirror/sublime_test.js +++ b/devtools/client/sourceeditor/test/codemirror/sublime_test.js @@ -269,16 +269,20 @@ "toggleBookmark", "prevBookmark", hasSel(2, 1, 2, 2), "prevBookmark", hasSel(0, 1, 0, 1), "selectBookmarks", hasSel(0, 1, 0, 1, 2, 1, 2, 2), "clearBookmarks", Pos(0, 0), "selectBookmarks", at(0, 0)); + stTest("smartBackspace", " foo\n bar", + setSel(0, 2, 0, 2, 1, 4, 1, 4, 1, 6, 1, 6), "smartBackspace", + val("foo\n br")) + stTest("upAndDowncaseAtCursor", "abc\ndef x\nghI", setSel(0, 1, 0, 3, 1, 1, 1, 1, 1, 4, 1, 4), "upcaseAtCursor", val("aBC\nDEF x\nghI"), hasSel(0, 1, 0, 3, 1, 3, 1, 3, 1, 4, 1, 4), "downcaseAtCursor",
--- a/devtools/client/styleeditor/StyleSheetEditor.jsm +++ b/devtools/client/styleeditor/StyleSheetEditor.jsm @@ -10,17 +10,17 @@ this.EXPORTED_SYMBOLS = ["StyleSheetEdit const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {}); const Editor = require("devtools/client/sourceeditor/editor"); const promise = require("promise"); const defer = require("devtools/shared/defer"); -const {CssLogic} = require("devtools/shared/inspector/css-logic"); +const {shortSource, prettifyCSS} = require("devtools/shared/inspector/css-logic"); const {console} = require("resource://gre/modules/Console.jsm"); const Services = require("Services"); const EventEmitter = require("devtools/shared/event-emitter"); const {Task} = require("devtools/shared/task"); const {FileUtils} = require("resource://gre/modules/FileUtils.jsm"); const {NetUtil} = require("resource://gre/modules/NetUtil.jsm"); const {TextDecoder, OS} = Cu.import("resource://gre/modules/osfile.jsm", {}); const { @@ -191,17 +191,17 @@ StyleSheetEditor.prototype = { if (!this.styleSheet.href) { let index = this.styleSheet.styleSheetIndex + 1; return getString("inlineStyleSheet", index); } if (!this._friendlyName) { let sheetURI = this.styleSheet.href; - this._friendlyName = CssLogic.shortSource({ href: sheetURI }); + this._friendlyName = shortSource({ href: sheetURI }); try { this._friendlyName = decodeURI(this._friendlyName); } catch (ex) { // Ignore. } } return this._friendlyName; }, @@ -257,29 +257,28 @@ StyleSheetEditor.prototype = { this._fileModDate = info.lastModificationDate.getTime(); }, this.markLinkedFileBroken); this.emit("linked-css-file"); }, /** * A helper function that fetches the source text from the style - * sheet. The text is possibly prettified using - * CssLogic.prettifyCSS. This also sets |this._state.text| to the - * new text. + * sheet. The text is possibly prettified using prettifyCSS. This + * also sets |this._state.text| to the new text. * * @return {Promise} a promise that resolves to the new text */ _getSourceTextAndPrettify: function () { return this.styleSheet.getText().then((longStr) => { return longStr.string(); }).then((source) => { let ruleCount = this.styleSheet.ruleCount; if (!this.styleSheet.isOriginalSource) { - source = CssLogic.prettifyCSS(source, ruleCount); + source = prettifyCSS(source, ruleCount); } this._state.text = source; return source; }); }, /** * Start fetching the full text source for this editor's sheet.
new file mode 100644 --- /dev/null +++ b/devtools/client/themes/audio/moz.build @@ -0,0 +1,9 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DevToolsModules( + 'shutter.wav', +)
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e9d742913eb4f38f2b66aacf115f13277130f18e GIT binary patch literal 25744 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
--- a/devtools/client/themes/moz.build +++ b/devtools/client/themes/moz.build @@ -1,10 +1,14 @@ # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # 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/. +DIRS += [ + 'audio', +] + DevToolsModules( 'common.css', 'variables.css', )
--- a/devtools/client/themes/widgets.css +++ b/devtools/client/themes/widgets.css @@ -622,16 +622,22 @@ widgets.css is overwritten. */ } .variables-view-scope > .title { border-top-width: 1px; border-top-style: solid; margin-top: -1px; } +/* Custom scope stylings */ + +.variables-view-watch-expressions .title > .name { + max-width: 14em; +} + .theme-firebug .variables-view-scope > .title { height: auto; border: none; } .theme-firebug .variables-view-scope > .title > .theme-twisty { display: none; }
--- a/devtools/server/actors/csscoverage.js +++ b/devtools/server/actors/csscoverage.js @@ -13,17 +13,17 @@ const events = require("sdk/event/core") const protocol = require("devtools/shared/protocol"); const { custom } = protocol; const { cssUsageSpec } = require("devtools/shared/specs/csscoverage"); loader.lazyGetter(this, "DOMUtils", () => { return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils); }); loader.lazyRequireGetter(this, "stylesheets", "devtools/server/actors/stylesheets"); -loader.lazyRequireGetter(this, "CssLogic", "devtools/shared/inspector/css-logic", true); +loader.lazyRequireGetter(this, "prettifyCSS", "devtools/shared/inspector/css-logic", true); const CSSRule = Ci.nsIDOMCSSRule; const MAX_UNUSED_RULES = 10000; /** * Allow: let foo = l10n.lookup("csscoverageFoo"); */ @@ -392,17 +392,17 @@ var CSSUsageActor = protocol.ActorClassW // Helper function to create a JSONable data structure representing a rule const ruleToRuleReport = function (rule, ruleData) { return { url: rule.url, shortUrl: rule.url.split("/").slice(-1)[0], start: { line: rule.line, column: rule.column }, selectorText: ruleData.selectorText, - formattedCssText: CssLogic.prettifyCSS(ruleData.cssText) + formattedCssText: prettifyCSS(ruleData.cssText) }; }; // A count of each type of rule for the bar chart let summary = { used: 0, unused: 0, preload: 0 }; // Create the set of the unused rules let unusedMap = new Map();
--- a/devtools/server/actors/highlighters/utils/markup.js +++ b/devtools/server/actors/highlighters/utils/markup.js @@ -7,17 +7,17 @@ const { Cc, Ci, Cu } = require("chrome"); const { getCurrentZoom, getRootBindingParent } = require("devtools/shared/layout/utils"); const { on, emit } = require("sdk/event/core"); const lazyContainer = {}; loader.lazyRequireGetter(lazyContainer, "CssLogic", - "devtools/shared/inspector/css-logic", true); + "devtools/server/css-logic", true); exports.getComputedStyle = (node) => lazyContainer.CssLogic.getComputedStyle(node); exports.getBindingElementAndPseudo = (node) => lazyContainer.CssLogic.getBindingElementAndPseudo(node); loader.lazyGetter(lazyContainer, "DOMUtils", () => Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils));
--- a/devtools/server/actors/inspector.js +++ b/devtools/server/actors/inspector.js @@ -145,17 +145,17 @@ loader.lazyGetter(this, "DOMParser", fun .createInstance(Ci.nsIDOMParser); }); loader.lazyGetter(this, "eventListenerService", function () { return Cc["@mozilla.org/eventlistenerservice;1"] .getService(Ci.nsIEventListenerService); }); -loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic").CssLogic); +loader.lazyGetter(this, "CssLogic", () => require("devtools/server/css-logic").CssLogic); /** * We only send nodeValue up to a certain size by default. This stuff * controls that size. */ exports.DEFAULT_VALUE_SUMMARY_LENGTH = 50; var gValueSummaryLength = exports.DEFAULT_VALUE_SUMMARY_LENGTH;
--- a/devtools/server/actors/script.js +++ b/devtools/server/actors/script.js @@ -28,17 +28,17 @@ const { threadSpec } = require("devtools const { defer, resolve, reject, all } = promise; loader.lazyGetter(this, "Debugger", () => { let Debugger = require("Debugger"); hackDebugger(Debugger); return Debugger; }); -loader.lazyRequireGetter(this, "CssLogic", "devtools/shared/inspector/css-logic", true); +loader.lazyRequireGetter(this, "CssLogic", "devtools/server/css-logic", true); loader.lazyRequireGetter(this, "events", "sdk/event/core"); loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id"); /** * A BreakpointActorMap is a map from locations to instances of BreakpointActor. */ function BreakpointActorMap() { this._size = 0;
--- a/devtools/server/actors/styleeditor.js +++ b/devtools/server/actors/styleeditor.js @@ -9,17 +9,17 @@ const Services = require("Services"); const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm"); const promise = require("promise"); const events = require("sdk/event/core"); const protocol = require("devtools/shared/protocol"); const {Arg, method, RetVal} = protocol; const {fetch} = require("devtools/shared/DevToolsUtils"); const {oldStyleSheetSpec, styleEditorSpec} = require("devtools/shared/specs/styleeditor"); -loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic").CssLogic); +loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic")); var TRANSITION_CLASS = "moz-styleeditor-transitioning"; var TRANSITION_DURATION_MS = 500; var TRANSITION_RULE = "\ :root.moz-styleeditor-transitioning, :root.moz-styleeditor-transitioning * {\ transition-duration: " + TRANSITION_DURATION_MS + "ms !important; \ transition-delay: 0ms !important;\ transition-timing-function: ease-out !important;\
--- a/devtools/server/actors/styles.js +++ b/devtools/server/actors/styles.js @@ -14,17 +14,18 @@ const {isCssPropertyKnown} = require("de const {Task} = require("devtools/shared/task"); const events = require("sdk/event/core"); // This will also add the "stylesheet" actor type for protocol.js to recognize const {UPDATE_PRESERVING_RULES, UPDATE_GENERAL} = require("devtools/server/actors/stylesheets"); const {pageStyleSpec, styleRuleSpec, ELEMENT_STYLE} = require("devtools/shared/specs/styles"); loader.lazyRequireGetter(this, "CSS", "CSS"); -loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic").CssLogic); +loader.lazyGetter(this, "CssLogic", () => require("devtools/server/css-logic").CssLogic); +loader.lazyGetter(this, "SharedCssLogic", () => require("devtools/shared/inspector/css-logic")); loader.lazyGetter(this, "DOMUtils", () => Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils)); // When gathering rules to read for pseudo elements, we will skip // :before and :after, which are handled as a special case. loader.lazyGetter(this, "PSEUDO_ELEMENTS_TO_READ", () => { return DOMUtils.getCSSPseudoElementNames().filter(pseudo => { return pseudo !== ":before" && pseudo !== ":after"; }); @@ -197,17 +198,17 @@ var PageStyleActor = protocol.ActorClass * matched: <true if there are matched selectors for this value> * }, * ... * } */ getComputed: function (node, options) { let ret = Object.create(null); - this.cssLogic.sourceFilter = options.filter || CssLogic.FILTER.UA; + this.cssLogic.sourceFilter = options.filter || SharedCssLogic.FILTER.UA; this.cssLogic.highlight(node.rawNode); let computed = this.cssLogic.computedStyle || []; Array.prototype.forEach.call(computed, name => { ret[name] = { value: computed.getPropertyValue(name), priority: computed.getPropertyPriority(name) || undefined }; @@ -375,17 +376,17 @@ var PageStyleActor = protocol.ActorClass * // The full form of any domrule referenced. * rules: [ <domrule>, ... ], // The full form of any domrule referenced * * // The full form of any sheets referenced. * sheets: [ <domsheet>, ... ] * } */ getMatchedSelectors: function (node, property, options) { - this.cssLogic.sourceFilter = options.filter || CssLogic.FILTER.UA; + this.cssLogic.sourceFilter = options.filter || SharedCssLogic.FILTER.UA; this.cssLogic.highlight(node.rawNode); let rules = new Set(); let sheets = new Set(); let matched = []; let propInfo = this.cssLogic.getPropertyInfo(property); for (let selectorInfo of propInfo.matchedSelectors) { @@ -572,19 +573,19 @@ var PageStyleActor = protocol.ActorClass let rules = []; // getCSSStyleRules returns ordered from least-specific to // most-specific. for (let i = domRules.Count() - 1; i >= 0; i--) { let domRule = domRules.GetElementAt(i); - let isSystem = !CssLogic.isContentStylesheet(domRule.parentStyleSheet); + let isSystem = !SharedCssLogic.isContentStylesheet(domRule.parentStyleSheet); - if (isSystem && options.filter != CssLogic.FILTER.UA) { + if (isSystem && options.filter != SharedCssLogic.FILTER.UA) { continue; } if (inherited) { // Don't include inherited rules if none of its properties // are inheritable. let hasInherited = [...domRule.style].some( prop => DOMUtils.isInheritedProperty(prop)
--- a/devtools/server/actors/stylesheets.js +++ b/devtools/server/actors/stylesheets.js @@ -13,17 +13,17 @@ const events = require("sdk/event/core") const protocol = require("devtools/shared/protocol"); const {LongStringActor} = require("devtools/server/actors/string"); const {fetch} = require("devtools/shared/DevToolsUtils"); const {listenOnce} = require("devtools/shared/async-utils"); const {originalSourceSpec, mediaRuleSpec, styleSheetSpec, styleSheetsSpec} = require("devtools/shared/specs/stylesheets"); const {SourceMapConsumer} = require("source-map"); -loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic").CssLogic); +loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic")); XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () { return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils); }); var TRANSITION_CLASS = "moz-styleeditor-transitioning"; var TRANSITION_DURATION_MS = 500; var TRANSITION_BUFFER_MS = 1000;
new file mode 100644 --- /dev/null +++ b/devtools/server/css-logic.js @@ -0,0 +1,1544 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. */ + +/* + * About the objects defined in this file: + * - CssLogic contains style information about a view context. It provides + * access to 2 sets of objects: Css[Sheet|Rule|Selector] provide access to + * information that does not change when the selected element changes while + * Css[Property|Selector]Info provide information that is dependent on the + * selected element. + * Its key methods are highlight(), getPropertyInfo() and forEachSheet(), etc + * + * - CssSheet provides a more useful API to a DOM CSSSheet for our purposes, + * including shortSource and href. + * - CssRule a more useful API to a nsIDOMCSSRule including access to the group + * of CssSelectors that the rule provides properties for + * - CssSelector A single selector - i.e. not a selector group. In other words + * a CssSelector does not contain ','. This terminology is different from the + * standard DOM API, but more inline with the definition in the spec. + * + * - CssPropertyInfo contains style information for a single property for the + * highlighted element. + * - CssSelectorInfo is a wrapper around CssSelector, which adds sorting with + * reference to the selected element. + */ + +"use strict"; + +const { Cc, Ci, Cu } = require("chrome"); +const DevToolsUtils = require("devtools/shared/DevToolsUtils"); +const { getRootBindingParent } = require("devtools/shared/layout/utils"); +const nodeConstants = require("devtools/shared/dom-node-constants"); +const {l10n, isContentStylesheet, shortSource, FILTER, STATUS} = require("devtools/shared/inspector/css-logic"); + +// This should be ok because none of the functions that use this should be used +// on the worker thread, where Cu is not available. +loader.lazyRequireGetter(this, "CSS", "CSS"); + +loader.lazyRequireGetter(this, "CSSLexer", "devtools/shared/css-lexer"); + +/** + * @param {function} isInherited A function that determines if the CSS property + * is inherited. + */ +function CssLogic(isInherited) { + // The cache of examined CSS properties. + this._isInherited = isInherited; + this._propertyInfos = {}; +} + +exports.CssLogic = CssLogic; + +CssLogic.prototype = { + // Both setup by highlight(). + viewedElement: null, + viewedDocument: null, + + // The cache of the known sheets. + _sheets: null, + + // Have the sheets been cached? + _sheetsCached: false, + + // The total number of rules, in all stylesheets, after filtering. + _ruleCount: 0, + + // The computed styles for the viewedElement. + _computedStyle: null, + + // Source filter. Only display properties coming from the given source + _sourceFilter: FILTER.USER, + + // Used for tracking unique CssSheet/CssRule/CssSelector objects, in a run of + // processMatchedSelectors(). + _passId: 0, + + // Used for tracking matched CssSelector objects. + _matchId: 0, + + _matchedRules: null, + _matchedSelectors: null, + + // Cached keyframes rules in all stylesheets + _keyframesRules: null, + + /** + * Reset various properties + */ + reset: function () { + this._propertyInfos = {}; + this._ruleCount = 0; + this._sheetIndex = 0; + this._sheets = {}; + this._sheetsCached = false; + this._matchedRules = null; + this._matchedSelectors = null; + this._keyframesRules = []; + }, + + /** + * Focus on a new element - remove the style caches. + * + * @param {nsIDOMElement} aViewedElement the element the user has highlighted + * in the Inspector. + */ + highlight: function (viewedElement) { + if (!viewedElement) { + this.viewedElement = null; + this.viewedDocument = null; + this._computedStyle = null; + this.reset(); + return; + } + + if (viewedElement === this.viewedElement) { + return; + } + + this.viewedElement = viewedElement; + + let doc = this.viewedElement.ownerDocument; + if (doc != this.viewedDocument) { + // New document: clear/rebuild the cache. + this.viewedDocument = doc; + + // Hunt down top level stylesheets, and cache them. + this._cacheSheets(); + } else { + // Clear cached data in the CssPropertyInfo objects. + this._propertyInfos = {}; + } + + this._matchedRules = null; + this._matchedSelectors = null; + this._computedStyle = CssLogic.getComputedStyle(this.viewedElement); + }, + + /** + * Get the values of all the computed CSS properties for the highlighted + * element. + * @returns {object} The computed CSS properties for a selected element + */ + get computedStyle() { + return this._computedStyle; + }, + + /** + * Get the source filter. + * @returns {string} The source filter being used. + */ + get sourceFilter() { + return this._sourceFilter; + }, + + /** + * Source filter. Only display properties coming from the given source (web + * address). Note that in order to avoid information overload we DO NOT show + * unmatched system rules. + * @see FILTER.* + */ + set sourceFilter(value) { + let oldValue = this._sourceFilter; + this._sourceFilter = value; + + let ruleCount = 0; + + // Update the CssSheet objects. + this.forEachSheet(function (sheet) { + sheet._sheetAllowed = -1; + if (sheet.contentSheet && sheet.sheetAllowed) { + ruleCount += sheet.ruleCount; + } + }, this); + + this._ruleCount = ruleCount; + + // Full update is needed because the this.processMatchedSelectors() method + // skips UA stylesheets if the filter does not allow such sheets. + let needFullUpdate = (oldValue == FILTER.UA || value == FILTER.UA); + + if (needFullUpdate) { + this._matchedRules = null; + this._matchedSelectors = null; + this._propertyInfos = {}; + } else { + // Update the CssPropertyInfo objects. + for (let property in this._propertyInfos) { + this._propertyInfos[property].needRefilter = true; + } + } + }, + + /** + * Return a CssPropertyInfo data structure for the currently viewed element + * and the specified CSS property. If there is no currently viewed element we + * return an empty object. + * + * @param {string} property The CSS property to look for. + * @return {CssPropertyInfo} a CssPropertyInfo structure for the given + * property. + */ + getPropertyInfo: function (property) { + if (!this.viewedElement) { + return {}; + } + + let info = this._propertyInfos[property]; + if (!info) { + info = new CssPropertyInfo(this, property, this._isInherited); + this._propertyInfos[property] = info; + } + + return info; + }, + + /** + * Cache all the stylesheets in the inspected document + * @private + */ + _cacheSheets: function () { + this._passId++; + this.reset(); + + // styleSheets isn't an array, but forEach can work on it anyway + Array.prototype.forEach.call(this.viewedDocument.styleSheets, + this._cacheSheet, this); + + this._sheetsCached = true; + }, + + /** + * Cache a stylesheet if it falls within the requirements: if it's enabled, + * and if the @media is allowed. This method also walks through the stylesheet + * cssRules to find @imported rules, to cache the stylesheets of those rules + * as well. In addition, the @keyframes rules in the stylesheet are cached. + * + * @private + * @param {CSSStyleSheet} domSheet the CSSStyleSheet object to cache. + */ + _cacheSheet: function (domSheet) { + if (domSheet.disabled) { + return; + } + + // Only work with stylesheets that have their media allowed. + if (!this.mediaMatches(domSheet)) { + return; + } + + // Cache the sheet. + let cssSheet = this.getSheet(domSheet, this._sheetIndex++); + if (cssSheet._passId != this._passId) { + cssSheet._passId = this._passId; + + // Find import and keyframes rules. + for (let aDomRule of domSheet.cssRules) { + if (aDomRule.type == CSSRule.IMPORT_RULE && + aDomRule.styleSheet && + this.mediaMatches(aDomRule)) { + this._cacheSheet(aDomRule.styleSheet); + } else if (aDomRule.type == CSSRule.KEYFRAMES_RULE) { + this._keyframesRules.push(aDomRule); + } + } + } + }, + + /** + * Retrieve the list of stylesheets in the document. + * + * @return {array} the list of stylesheets in the document. + */ + get sheets() { + if (!this._sheetsCached) { + this._cacheSheets(); + } + + let sheets = []; + this.forEachSheet(function (sheet) { + if (sheet.contentSheet) { + sheets.push(sheet); + } + }, this); + + return sheets; + }, + + /** + * Retrieve the list of keyframes rules in the document. + * + * @ return {array} the list of keyframes rules in the document. + */ + get keyframesRules() { + if (!this._sheetsCached) { + this._cacheSheets(); + } + return this._keyframesRules; + }, + + /** + * Retrieve a CssSheet object for a given a CSSStyleSheet object. If the + * stylesheet is already cached, you get the existing CssSheet object, + * otherwise the new CSSStyleSheet object is cached. + * + * @param {CSSStyleSheet} domSheet the CSSStyleSheet object you want. + * @param {number} index the index, within the document, of the stylesheet. + * + * @return {CssSheet} the CssSheet object for the given CSSStyleSheet object. + */ + getSheet: function (domSheet, index) { + let cacheId = ""; + + if (domSheet.href) { + cacheId = domSheet.href; + } else if (domSheet.ownerNode && domSheet.ownerNode.ownerDocument) { + cacheId = domSheet.ownerNode.ownerDocument.location; + } + + let sheet = null; + let sheetFound = false; + + if (cacheId in this._sheets) { + for (let i = 0, numSheets = this._sheets[cacheId].length; + i < numSheets; + i++) { + sheet = this._sheets[cacheId][i]; + if (sheet.domSheet === domSheet) { + if (index != -1) { + sheet.index = index; + } + sheetFound = true; + break; + } + } + } + + if (!sheetFound) { + if (!(cacheId in this._sheets)) { + this._sheets[cacheId] = []; + } + + sheet = new CssSheet(this, domSheet, index); + if (sheet.sheetAllowed && sheet.contentSheet) { + this._ruleCount += sheet.ruleCount; + } + + this._sheets[cacheId].push(sheet); + } + + return sheet; + }, + + /** + * Process each cached stylesheet in the document using your callback. + * + * @param {function} callback the function you want executed for each of the + * CssSheet objects cached. + * @param {object} scope the scope you want for the callback function. scope + * will be the this object when callback executes. + */ + forEachSheet: function (callback, scope) { + for (let cacheId in this._sheets) { + let sheets = this._sheets[cacheId]; + for (let i = 0; i < sheets.length; i++) { + // We take this as an opportunity to clean dead sheets + try { + let sheet = sheets[i]; + // If accessing domSheet raises an exception, then the style + // sheet is a dead object. + sheet.domSheet; + callback.call(scope, sheet, i, sheets); + } catch (e) { + sheets.splice(i, 1); + i--; + } + } + } + }, + + /** + + /** + * Get the number nsIDOMCSSRule objects in the document, counted from all of + * the stylesheets. System sheets are excluded. If a filter is active, this + * tells only the number of nsIDOMCSSRule objects inside the selected + * CSSStyleSheet. + * + * WARNING: This only provides an estimate of the rule count, and the results + * could change at a later date. Todo remove this + * + * @return {number} the number of nsIDOMCSSRule (all rules). + */ + get ruleCount() { + if (!this._sheetsCached) { + this._cacheSheets(); + } + + return this._ruleCount; + }, + + /** + * Process the CssSelector objects that match the highlighted element and its + * parent elements. scope.callback() is executed for each CssSelector + * object, being passed the CssSelector object and the match status. + * + * This method also includes all of the element.style properties, for each + * highlighted element parent and for the highlighted element itself. + * + * Note that the matched selectors are cached, such that next time your + * callback is invoked for the cached list of CssSelector objects. + * + * @param {function} callback the function you want to execute for each of + * the matched selectors. + * @param {object} scope the scope you want for the callback function. scope + * will be the this object when callback executes. + */ + processMatchedSelectors: function (callback, scope) { + if (this._matchedSelectors) { + if (callback) { + this._passId++; + this._matchedSelectors.forEach(function (value) { + callback.call(scope, value[0], value[1]); + value[0].cssRule._passId = this._passId; + }, this); + } + return; + } + + if (!this._matchedRules) { + this._buildMatchedRules(); + } + + this._matchedSelectors = []; + this._passId++; + + for (let i = 0; i < this._matchedRules.length; i++) { + let rule = this._matchedRules[i][0]; + let status = this._matchedRules[i][1]; + + rule.selectors.forEach(function (selector) { + if (selector._matchId !== this._matchId && + (selector.elementStyle || + this.selectorMatchesElement(rule.domRule, + selector.selectorIndex))) { + selector._matchId = this._matchId; + this._matchedSelectors.push([ selector, status ]); + if (callback) { + callback.call(scope, selector, status); + } + } + }, this); + + rule._passId = this._passId; + } + }, + + /** + * Check if the given selector matches the highlighted element or any of its + * parents. + * + * @private + * @param {DOMRule} domRule + * The DOM Rule containing the selector. + * @param {Number} idx + * The index of the selector within the DOMRule. + * @return {boolean} + * true if the given selector matches the highlighted element or any + * of its parents, otherwise false is returned. + */ + selectorMatchesElement: function (domRule, idx) { + let element = this.viewedElement; + do { + if (domUtils.selectorMatchesElement(element, domRule, idx)) { + return true; + } + } while ((element = element.parentNode) && + element.nodeType === nodeConstants.ELEMENT_NODE); + + return false; + }, + + /** + * Check if the highlighted element or it's parents have matched selectors. + * + * @param {array} aProperties The list of properties you want to check if they + * have matched selectors or not. + * @return {object} An object that tells for each property if it has matched + * selectors or not. Object keys are property names and values are booleans. + */ + hasMatchedSelectors: function (properties) { + if (!this._matchedRules) { + this._buildMatchedRules(); + } + + let result = {}; + + this._matchedRules.some(function (value) { + let rule = value[0]; + let status = value[1]; + properties = properties.filter((property) => { + // We just need to find if a rule has this property while it matches + // the viewedElement (or its parents). + if (rule.getPropertyValue(property) && + (status == STATUS.MATCHED || + (status == STATUS.PARENT_MATCH && + this._isInherited(property)))) { + result[property] = true; + return false; + } + // Keep the property for the next rule. + return true; + }); + return properties.length == 0; + }, this); + + return result; + }, + + /** + * Build the array of matched rules for the currently highlighted element. + * The array will hold rules that match the viewedElement and its parents. + * + * @private + */ + _buildMatchedRules: function () { + let domRules; + let element = this.viewedElement; + let filter = this.sourceFilter; + let sheetIndex = 0; + + this._matchId++; + this._passId++; + this._matchedRules = []; + + if (!element) { + return; + } + + do { + let status = this.viewedElement === element ? + STATUS.MATCHED : STATUS.PARENT_MATCH; + + try { + // Handle finding rules on pseudo by reading style rules + // on the parent node with proper pseudo arg to getCSSStyleRules. + let {bindingElement, pseudo} = + CssLogic.getBindingElementAndPseudo(element); + domRules = domUtils.getCSSStyleRules(bindingElement, pseudo); + } catch (ex) { + console.log("CL__buildMatchedRules error: " + ex); + continue; + } + + // getCSSStyleRules can return null with a shadow DOM element. + let numDomRules = domRules ? domRules.Count() : 0; + for (let i = 0; i < numDomRules; i++) { + let domRule = domRules.GetElementAt(i); + if (domRule.type !== CSSRule.STYLE_RULE) { + continue; + } + + let sheet = this.getSheet(domRule.parentStyleSheet, -1); + if (sheet._passId !== this._passId) { + sheet.index = sheetIndex++; + sheet._passId = this._passId; + } + + if (filter === FILTER.USER && !sheet.contentSheet) { + continue; + } + + let rule = sheet.getRule(domRule); + if (rule._passId === this._passId) { + continue; + } + + rule._matchId = this._matchId; + rule._passId = this._passId; + this._matchedRules.push([rule, status]); + } + + // Add element.style information. + if (element.style && element.style.length > 0) { + let rule = new CssRule(null, { style: element.style }, element); + rule._matchId = this._matchId; + rule._passId = this._passId; + this._matchedRules.push([rule, status]); + } + } while ((element = element.parentNode) && + element.nodeType === nodeConstants.ELEMENT_NODE); + }, + + /** + * Tells if the given DOM CSS object matches the current view media. + * + * @param {object} domObject The DOM CSS object to check. + * @return {boolean} True if the DOM CSS object matches the current view + * media, or false otherwise. + */ + mediaMatches: function (domObject) { + let mediaText = domObject.media.mediaText; + return !mediaText || + this.viewedDocument.defaultView.matchMedia(mediaText).matches; + }, +}; + +/** + * If the element has an id, return '#id'. Otherwise return 'tagname[n]' where + * n is the index of this element in its siblings. + * <p>A technically more 'correct' output from the no-id case might be: + * 'tagname:nth-of-type(n)' however this is unlikely to be more understood + * and it is longer. + * + * @param {nsIDOMElement} element the element for which you want the short name. + * @return {string} the string to be displayed for element. + */ +CssLogic.getShortName = function (element) { + if (!element) { + return "null"; + } + if (element.id) { + return "#" + element.id; + } + let priorSiblings = 0; + let temp = element; + while ((temp = temp.previousElementSibling)) { + priorSiblings++; + } + return element.tagName + "[" + priorSiblings + "]"; +}; + +/** + * Get a string list of selectors for a given DOMRule. + * + * @param {DOMRule} domRule + * The DOMRule to parse. + * @return {Array} + * An array of string selectors. + */ +CssLogic.getSelectors = function (domRule) { + let selectors = []; + + let len = domUtils.getSelectorCount(domRule); + for (let i = 0; i < len; i++) { + let text = domUtils.getSelectorText(domRule, i); + selectors.push(text); + } + return selectors; +}; + +/** + * Given a node, check to see if it is a ::before or ::after element. + * If so, return the node that is accessible from within the document + * (the parent of the anonymous node), along with which pseudo element + * it was. Otherwise, return the node itself. + * + * @returns {Object} + * - {DOMNode} node The non-anonymous node + * - {string} pseudo One of ':before', ':after', or null. + */ +CssLogic.getBindingElementAndPseudo = function (node) { + let bindingElement = node; + let pseudo = null; + if (node.nodeName == "_moz_generated_content_before") { + bindingElement = node.parentNode; + pseudo = ":before"; + } else if (node.nodeName == "_moz_generated_content_after") { + bindingElement = node.parentNode; + pseudo = ":after"; + } + return { + bindingElement: bindingElement, + pseudo: pseudo + }; +}; + +/** + * Get the computed style on a node. Automatically handles reading + * computed styles on a ::before/::after element by reading on the + * parent node with the proper pseudo argument. + * + * @param {Node} + * @returns {CSSStyleDeclaration} + */ +CssLogic.getComputedStyle = function (node) { + if (!node || + Cu.isDeadWrapper(node) || + node.nodeType !== nodeConstants.ELEMENT_NODE || + !node.ownerDocument || + !node.ownerDocument.defaultView) { + return null; + } + + let {bindingElement, pseudo} = CssLogic.getBindingElementAndPseudo(node); + return node.ownerDocument.defaultView.getComputedStyle(bindingElement, + pseudo); +}; + +/** + * Get a source for a stylesheet, taking into account embedded stylesheets + * for which we need to use document.defaultView.location.href rather than + * sheet.href + * + * @param {CSSStyleSheet} sheet the DOM object for the style sheet. + * @return {string} the address of the stylesheet. + */ +CssLogic.href = function (sheet) { + let href = sheet.href; + if (!href) { + href = sheet.ownerNode.ownerDocument.location; + } + + return href; +}; + +/** + * Find the position of [element] in [nodeList]. + * @returns an index of the match, or -1 if there is no match + */ +function positionInNodeList(element, nodeList) { + for (let i = 0; i < nodeList.length; i++) { + if (element === nodeList[i]) { + return i; + } + } + return -1; +} + +/** + * Find a unique CSS selector for a given element + * @returns a string such that ele.ownerDocument.querySelector(reply) === ele + * and ele.ownerDocument.querySelectorAll(reply).length === 1 + */ +CssLogic.findCssSelector = function (ele) { + ele = getRootBindingParent(ele); + let document = ele.ownerDocument; + if (!document || !document.contains(ele)) { + throw new Error("findCssSelector received element not inside document"); + } + + // document.querySelectorAll("#id") returns multiple if elements share an ID + if (ele.id && + document.querySelectorAll("#" + CSS.escape(ele.id)).length === 1) { + return "#" + CSS.escape(ele.id); + } + + // Inherently unique by tag name + let tagName = ele.localName; + if (tagName === "html") { + return "html"; + } + if (tagName === "head") { + return "head"; + } + if (tagName === "body") { + return "body"; + } + + // We might be able to find a unique class name + let selector, index, matches; + if (ele.classList.length > 0) { + for (let i = 0; i < ele.classList.length; i++) { + // Is this className unique by itself? + selector = "." + CSS.escape(ele.classList.item(i)); + matches = document.querySelectorAll(selector); + if (matches.length === 1) { + return selector; + } + // Maybe it's unique with a tag name? + selector = tagName + selector; + matches = document.querySelectorAll(selector); + if (matches.length === 1) { + return selector; + } + // Maybe it's unique using a tag name and nth-child + index = positionInNodeList(ele, ele.parentNode.children) + 1; + selector = selector + ":nth-child(" + index + ")"; + matches = document.querySelectorAll(selector); + if (matches.length === 1) { + return selector; + } + } + } + + // Not unique enough yet. As long as it's not a child of the document, + // continue recursing up until it is unique enough. + if (ele.parentNode !== document) { + index = positionInNodeList(ele, ele.parentNode.children) + 1; + selector = CssLogic.findCssSelector(ele.parentNode) + " > " + + tagName + ":nth-child(" + index + ")"; + } + + return selector; +}; + +/** + * A safe way to access cached bits of information about a stylesheet. + * + * @constructor + * @param {CssLogic} cssLogic pointer to the CssLogic instance working with + * this CssSheet object. + * @param {CSSStyleSheet} domSheet reference to a DOM CSSStyleSheet object. + * @param {number} index tells the index/position of the stylesheet within the + * main document. + */ +function CssSheet(cssLogic, domSheet, index) { + this._cssLogic = cssLogic; + this.domSheet = domSheet; + this.index = this.contentSheet ? index : -100 * index; + + // Cache of the sheets href. Cached by the getter. + this._href = null; + // Short version of href for use in select boxes etc. Cached by getter. + this._shortSource = null; + + // null for uncached. + this._sheetAllowed = null; + + // Cached CssRules from the given stylesheet. + this._rules = {}; + + this._ruleCount = -1; +} + +CssSheet.prototype = { + _passId: null, + _contentSheet: null, + + /** + * Tells if the stylesheet is provided by the browser or not. + * + * @return {boolean} false if this is a browser-provided stylesheet, or true + * otherwise. + */ + get contentSheet() { + if (this._contentSheet === null) { + this._contentSheet = isContentStylesheet(this.domSheet); + } + return this._contentSheet; + }, + + /** + * Tells if the stylesheet is disabled or not. + * @return {boolean} true if this stylesheet is disabled, or false otherwise. + */ + get disabled() { + return this.domSheet.disabled; + }, + + /** + * Get a source for a stylesheet, using CssLogic.href + * + * @return {string} the address of the stylesheet. + */ + get href() { + if (this._href) { + return this._href; + } + + this._href = CssLogic.href(this.domSheet); + return this._href; + }, + + /** + * Create a shorthand version of the href of a stylesheet. + * + * @return {string} the shorthand source of the stylesheet. + */ + get shortSource() { + if (this._shortSource) { + return this._shortSource; + } + + this._shortSource = shortSource(this.domSheet); + return this._shortSource; + }, + + /** + * Tells if the sheet is allowed or not by the current CssLogic.sourceFilter. + * + * @return {boolean} true if the stylesheet is allowed by the sourceFilter, or + * false otherwise. + */ + get sheetAllowed() { + if (this._sheetAllowed !== null) { + return this._sheetAllowed; + } + + this._sheetAllowed = true; + + let filter = this._cssLogic.sourceFilter; + if (filter === FILTER.USER && !this.contentSheet) { + this._sheetAllowed = false; + } + if (filter !== FILTER.USER && filter !== FILTER.UA) { + this._sheetAllowed = (filter === this.href); + } + + return this._sheetAllowed; + }, + + /** + * Retrieve the number of rules in this stylesheet. + * + * @return {number} the number of nsIDOMCSSRule objects in this stylesheet. + */ + get ruleCount() { + return this._ruleCount > -1 ? + this._ruleCount : + this.domSheet.cssRules.length; + }, + + /** + * Retrieve a CssRule object for the given CSSStyleRule. The CssRule object is + * cached, such that subsequent retrievals return the same CssRule object for + * the same CSSStyleRule object. + * + * @param {CSSStyleRule} aDomRule the CSSStyleRule object for which you want a + * CssRule object. + * @return {CssRule} the cached CssRule object for the given CSSStyleRule + * object. + */ + getRule: function (domRule) { + let cacheId = domRule.type + domRule.selectorText; + + let rule = null; + let ruleFound = false; + + if (cacheId in this._rules) { + for (let i = 0, rulesLen = this._rules[cacheId].length; + i < rulesLen; + i++) { + rule = this._rules[cacheId][i]; + if (rule.domRule === domRule) { + ruleFound = true; + break; + } + } + } + + if (!ruleFound) { + if (!(cacheId in this._rules)) { + this._rules[cacheId] = []; + } + + rule = new CssRule(this, domRule); + this._rules[cacheId].push(rule); + } + + return rule; + }, + + toString: function () { + return "CssSheet[" + this.shortSource + "]"; + } +}; + +/** + * Information about a single CSSStyleRule. + * + * @param {CSSSheet|null} cssSheet the CssSheet object of the stylesheet that + * holds the CSSStyleRule. If the rule comes from element.style, set this + * argument to null. + * @param {CSSStyleRule|object} domRule the DOM CSSStyleRule for which you want + * to cache data. If the rule comes from element.style, then provide + * an object of the form: {style: element.style}. + * @param {Element} [element] If the rule comes from element.style, then this + * argument must point to the element. + * @constructor + */ +function CssRule(cssSheet, domRule, element) { + this._cssSheet = cssSheet; + this.domRule = domRule; + + let parentRule = domRule.parentRule; + if (parentRule && parentRule.type == CSSRule.MEDIA_RULE) { + this.mediaText = parentRule.media.mediaText; + } + + if (this._cssSheet) { + // parse domRule.selectorText on call to this.selectors + this._selectors = null; + this.line = domUtils.getRuleLine(this.domRule); + this.source = this._cssSheet.shortSource + ":" + this.line; + if (this.mediaText) { + this.source += " @media " + this.mediaText; + } + this.href = this._cssSheet.href; + this.contentRule = this._cssSheet.contentSheet; + } else if (element) { + this._selectors = [ new CssSelector(this, "@element.style", 0) ]; + this.line = -1; + this.source = l10n("rule.sourceElement"); + this.href = "#"; + this.contentRule = true; + this.sourceElement = element; + } +} + +CssRule.prototype = { + _passId: null, + + mediaText: "", + + get isMediaRule() { + return !!this.mediaText; + }, + + /** + * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter. + * + * @return {boolean} true if the parent stylesheet is allowed by the current + * sourceFilter, or false otherwise. + */ + get sheetAllowed() { + return this._cssSheet ? this._cssSheet.sheetAllowed : true; + }, + + /** + * Retrieve the parent stylesheet index/position in the viewed document. + * + * @return {number} the parent stylesheet index/position in the viewed + * document. + */ + get sheetIndex() { + return this._cssSheet ? this._cssSheet.index : 0; + }, + + /** + * Retrieve the style property value from the current CSSStyleRule. + * + * @param {string} property the CSS property name for which you want the + * value. + * @return {string} the property value. + */ + getPropertyValue: function (property) { + return this.domRule.style.getPropertyValue(property); + }, + + /** + * Retrieve the style property priority from the current CSSStyleRule. + * + * @param {string} property the CSS property name for which you want the + * priority. + * @return {string} the property priority. + */ + getPropertyPriority: function (property) { + return this.domRule.style.getPropertyPriority(property); + }, + + /** + * Retrieve the list of CssSelector objects for each of the parsed selectors + * of the current CSSStyleRule. + * + * @return {array} the array hold the CssSelector objects. + */ + get selectors() { + if (this._selectors) { + return this._selectors; + } + + // Parse the CSSStyleRule.selectorText string. + this._selectors = []; + + if (!this.domRule.selectorText) { + return this._selectors; + } + + let selectors = CssLogic.getSelectors(this.domRule); + + for (let i = 0, len = selectors.length; i < len; i++) { + this._selectors.push(new CssSelector(this, selectors[i], i)); + } + + return this._selectors; + }, + + toString: function () { + return "[CssRule " + this.domRule.selectorText + "]"; + }, +}; + +/** + * The CSS selector class allows us to document the ranking of various CSS + * selectors. + * + * @constructor + * @param {CssRule} cssRule the CssRule instance from where the selector comes. + * @param {string} selector The selector that we wish to investigate. + * @param {Number} index The index of the selector within it's rule. + */ +function CssSelector(cssRule, selector, index) { + this.cssRule = cssRule; + this.text = selector; + this.elementStyle = this.text == "@element.style"; + this._specificity = null; + this.selectorIndex = index; +} + +exports.CssSelector = CssSelector; + +CssSelector.prototype = { + _matchId: null, + + /** + * Retrieve the CssSelector source, which is the source of the CssSheet owning + * the selector. + * + * @return {string} the selector source. + */ + get source() { + return this.cssRule.source; + }, + + /** + * Retrieve the CssSelector source element, which is the source of the CssRule + * owning the selector. This is only available when the CssSelector comes from + * an element.style. + * + * @return {string} the source element selector. + */ + get sourceElement() { + return this.cssRule.sourceElement; + }, + + /** + * Retrieve the address of the CssSelector. This points to the address of the + * CssSheet owning this selector. + * + * @return {string} the address of the CssSelector. + */ + get href() { + return this.cssRule.href; + }, + + /** + * Check if the selector comes from a browser-provided stylesheet. + * + * @return {boolean} true if the selector comes from a content-provided + * stylesheet, or false otherwise. + */ + get contentRule() { + return this.cssRule.contentRule; + }, + + /** + * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter. + * + * @return {boolean} true if the parent stylesheet is allowed by the current + * sourceFilter, or false otherwise. + */ + get sheetAllowed() { + return this.cssRule.sheetAllowed; + }, + + /** + * Retrieve the parent stylesheet index/position in the viewed document. + * + * @return {number} the parent stylesheet index/position in the viewed + * document. + */ + get sheetIndex() { + return this.cssRule.sheetIndex; + }, + + /** + * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet. + * + * @return {number} the line of the parent CSSStyleRule in the parent + * stylesheet. + */ + get ruleLine() { + return this.cssRule.line; + }, + + /** + * Retrieve specificity information for the current selector. + * + * @see http://www.w3.org/TR/css3-selectors/#specificity + * @see http://www.w3.org/TR/CSS2/selector.html + * + * @return {Number} The selector's specificity. + */ + get specificity() { + if (this.elementStyle) { + // We can't ask specificity from DOMUtils as element styles don't provide + // CSSStyleRule interface DOMUtils expect. However, specificity of element + // style is constant, 1,0,0,0 or 0x01000000, just return the constant + // directly. @see http://www.w3.org/TR/CSS2/cascade.html#specificity + return 0x01000000; + } + + if (this._specificity) { + return this._specificity; + } + + this._specificity = domUtils.getSpecificity(this.cssRule.domRule, + this.selectorIndex); + + return this._specificity; + }, + + toString: function () { + return this.text; + }, +}; + +/** + * A cache of information about the matched rules, selectors and values attached + * to a CSS property, for the highlighted element. + * + * The heart of the CssPropertyInfo object is the _findMatchedSelectors() + * method. This are invoked when the PropertyView tries to access the + * .matchedSelectors array. + * Results are cached, for later reuse. + * + * @param {CssLogic} cssLogic Reference to the parent CssLogic instance + * @param {string} property The CSS property we are gathering information for + * @param {function} isInherited A function that determines if the CSS property + * is inherited. + * @constructor + */ +function CssPropertyInfo(cssLogic, property, isInherited) { + this._cssLogic = cssLogic; + this.property = property; + this._value = ""; + this._isInherited = isInherited; + + // An array holding CssSelectorInfo objects for each of the matched selectors + // that are inside a CSS rule. Only rules that hold the this.property are + // counted. This includes rules that come from filtered stylesheets (those + // that have sheetAllowed = false). + this._matchedSelectors = null; +} + +CssPropertyInfo.prototype = { + /** + * Retrieve the computed style value for the current property, for the + * highlighted element. + * + * @return {string} the computed style value for the current property, for the + * highlighted element. + */ + get value() { + if (!this._value && this._cssLogic.computedStyle) { + try { + this._value = + this._cssLogic.computedStyle.getPropertyValue(this.property); + } catch (ex) { + console.log("Error reading computed style for " + this.property); + console.log(ex); + } + } + return this._value; + }, + + /** + * Retrieve the array holding CssSelectorInfo objects for each of the matched + * selectors, from each of the matched rules. Only selectors coming from + * allowed stylesheets are included in the array. + * + * @return {array} the list of CssSelectorInfo objects of selectors that match + * the highlighted element and its parents. + */ + get matchedSelectors() { + if (!this._matchedSelectors) { + this._findMatchedSelectors(); + } else if (this.needRefilter) { + this._refilterSelectors(); + } + + return this._matchedSelectors; + }, + + /** + * Find the selectors that match the highlighted element and its parents. + * Uses CssLogic.processMatchedSelectors() to find the matched selectors, + * passing in a reference to CssPropertyInfo._processMatchedSelector() to + * create CssSelectorInfo objects, which we then sort + * @private + */ + _findMatchedSelectors: function () { + this._matchedSelectors = []; + this.needRefilter = false; + + this._cssLogic.processMatchedSelectors(this._processMatchedSelector, this); + + // Sort the selectors by how well they match the given element. + this._matchedSelectors.sort(function (selectorInfo1, selectorInfo2) { + if (selectorInfo1.status > selectorInfo2.status) { + return -1; + } else if (selectorInfo2.status > selectorInfo1.status) { + return 1; + } + return selectorInfo1.compareTo(selectorInfo2); + }); + + // Now we know which of the matches is best, we can mark it BEST_MATCH. + if (this._matchedSelectors.length > 0 && + this._matchedSelectors[0].status > STATUS.UNMATCHED) { + this._matchedSelectors[0].status = STATUS.BEST; + } + }, + + /** + * Process a matched CssSelector object. + * + * @private + * @param {CssSelector} selector the matched CssSelector object. + * @param {STATUS} status the CssSelector match status. + */ + _processMatchedSelector: function (selector, status) { + let cssRule = selector.cssRule; + let value = cssRule.getPropertyValue(this.property); + if (value && + (status == STATUS.MATCHED || + (status == STATUS.PARENT_MATCH && + this._isInherited(this.property)))) { + let selectorInfo = new CssSelectorInfo(selector, this.property, value, + status); + this._matchedSelectors.push(selectorInfo); + } + }, + + /** + * Refilter the matched selectors array when the CssLogic.sourceFilter + * changes. This allows for quick filter changes. + * @private + */ + _refilterSelectors: function () { + let passId = ++this._cssLogic._passId; + let ruleCount = 0; + + let iterator = function (selectorInfo) { + let cssRule = selectorInfo.selector.cssRule; + if (cssRule._passId != passId) { + if (cssRule.sheetAllowed) { + ruleCount++; + } + cssRule._passId = passId; + } + }; + + if (this._matchedSelectors) { + this._matchedSelectors.forEach(iterator); + } + + this.needRefilter = false; + }, + + toString: function () { + return "CssPropertyInfo[" + this.property + "]"; + }, +}; + +/** + * A class that holds information about a given CssSelector object. + * + * Instances of this class are given to CssHtmlTree in the array of matched + * selectors. Each such object represents a displayable row in the PropertyView + * objects. The information given by this object blends data coming from the + * CssSheet, CssRule and from the CssSelector that own this object. + * + * @param {CssSelector} selector The CssSelector object for which to + * present information. + * @param {string} property The property for which information should + * be retrieved. + * @param {string} value The property value from the CssRule that owns + * the selector. + * @param {STATUS} status The selector match status. + * @constructor + */ +function CssSelectorInfo(selector, property, value, status) { + this.selector = selector; + this.property = property; + this.status = status; + this.value = value; + let priority = this.selector.cssRule.getPropertyPriority(this.property); + this.important = (priority === "important"); +} + +CssSelectorInfo.prototype = { + /** + * Retrieve the CssSelector source, which is the source of the CssSheet owning + * the selector. + * + * @return {string} the selector source. + */ + get source() { + return this.selector.source; + }, + + /** + * Retrieve the CssSelector source element, which is the source of the CssRule + * owning the selector. This is only available when the CssSelector comes from + * an element.style. + * + * @return {string} the source element selector. + */ + get sourceElement() { + return this.selector.sourceElement; + }, + + /** + * Retrieve the address of the CssSelector. This points to the address of the + * CssSheet owning this selector. + * + * @return {string} the address of the CssSelector. + */ + get href() { + return this.selector.href; + }, + + /** + * Check if the CssSelector comes from element.style or not. + * + * @return {boolean} true if the CssSelector comes from element.style, or + * false otherwise. + */ + get elementStyle() { + return this.selector.elementStyle; + }, + + /** + * Retrieve specificity information for the current selector. + * + * @return {object} an object holding specificity information for the current + * selector. + */ + get specificity() { + return this.selector.specificity; + }, + + /** + * Retrieve the parent stylesheet index/position in the viewed document. + * + * @return {number} the parent stylesheet index/position in the viewed + * document. + */ + get sheetIndex() { + return this.selector.sheetIndex; + }, + + /** + * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter. + * + * @return {boolean} true if the parent stylesheet is allowed by the current + * sourceFilter, or false otherwise. + */ + get sheetAllowed() { + return this.selector.sheetAllowed; + }, + + /** + * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet. + * + * @return {number} the line of the parent CSSStyleRule in the parent + * stylesheet. + */ + get ruleLine() { + return this.selector.ruleLine; + }, + + /** + * Check if the selector comes from a browser-provided stylesheet. + * + * @return {boolean} true if the selector comes from a browser-provided + * stylesheet, or false otherwise. + */ + get contentRule() { + return this.selector.contentRule; + }, + + /** + * Compare the current CssSelectorInfo instance to another instance, based on + * specificity information. + * + * @param {CssSelectorInfo} that The instance to compare ourselves against. + * @return number -1, 0, 1 depending on how that compares with this. + */ + compareTo: function (that) { + if (!this.contentRule && that.contentRule) { + return 1; + } + if (this.contentRule && !that.contentRule) { + return -1; + } + + if (this.elementStyle && !that.elementStyle) { + if (!this.important && that.important) { + return 1; + } + return -1; + } + + if (!this.elementStyle && that.elementStyle) { + if (this.important && !that.important) { + return -1; + } + return 1; + } + + if (this.important && !that.important) { + return -1; + } + if (that.important && !this.important) { + return 1; + } + + if (this.specificity > that.specificity) { + return -1; + } + if (that.specificity > this.specificity) { + return 1; + } + + if (this.sheetIndex > that.sheetIndex) { + return -1; + } + if (that.sheetIndex > this.sheetIndex) { + return 1; + } + + if (this.ruleLine > that.ruleLine) { + return -1; + } + if (that.ruleLine > this.ruleLine) { + return 1; + } + + return 0; + }, + + toString: function () { + return this.selector + " -> " + this.value; + }, +}; + +DevToolsUtils.defineLazyGetter(this, "domUtils", function () { + return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils); +});
--- a/devtools/server/docs/protocol.js.md +++ b/devtools/server/docs/protocol.js.md @@ -644,29 +644,8 @@ This will require some matching work on if (this._temporaryParent) { this._temporaryParent.destroy(); delete this._temporaryParent; } return this._clearTemporaryChildren(); }, { impl: "_clearTemporaryChildren" }) - -Telemetry ---------- - -You can specify a telemetry probe id in your method spec: - - // spec: - methods: { - echo: { - request: { str: Arg(0) }, - response: { str: RetVal() }, - telemetry: "ECHO" - } - } - - // implementation: - echo: function (str) { - return str; - } - -... and the time to execute that request will be included as a telemetry probe.
--- a/devtools/server/moz.build +++ b/devtools/server/moz.build @@ -27,13 +27,14 @@ SOURCES += [ ] FINAL_LIBRARY = 'xul' DevToolsModules( 'child.js', 'content-globals.js', 'content-server.jsm', + 'css-logic.js', 'main.js', 'primitive.js', 'service-worker-child.js', 'worker.js' )
--- a/devtools/server/tests/mochitest/chrome.ini +++ b/devtools/server/tests/mochitest/chrome.ini @@ -24,17 +24,16 @@ support-files = setup-in-parent.js [test_animation_actor-lifetime.html] [test_connection-manager.html] skip-if = buildapp == 'mulet' [test_connectToChild.html] skip-if = buildapp == 'mulet' [test_css-logic.html] -[test_css-logic-inheritance.html] [test_css-logic-media-queries.html] [test_css-logic-specificity.html] [test_css-properties.html] [test_Debugger.Source.prototype.introductionScript.html] [test_Debugger.Source.prototype.introductionType.html] [test_Debugger.Source.prototype.element.html] [test_Debugger.Script.prototype.global.html] [test_device.html]
deleted file mode 100644 --- a/devtools/server/tests/mochitest/test_css-logic-inheritance.html +++ /dev/null @@ -1,46 +0,0 @@ -<!DOCTYPE HTML> -<html> -<!-- -Test that css-logic handles inherited properties correctly ---> -<head> - <meta charset="utf-8"> - <title>Test css-logic inheritance</title> - <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> - <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> -</head> -<body> - <div style="margin-left:10px; font-size: 5px"> - <div id="innerdiv">Inner div</div> - </div> - <script type="application/javascript;version=1.8"> - - window.onload = function() { - var { classes: Cc, utils: Cu, interfaces: Ci } = Components; - const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"] - .getService(Ci.inIDOMUtils); - - const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {}); - var Services = require("Services"); - const {CssLogic} = require("devtools/shared/inspector/css-logic"); - - - SimpleTest.waitForExplicitFinish(); - - let cssLogic = new CssLogic(DOMUtils.isInheritedProperty); - cssLogic.highlight(document.getElementById("innerdiv")); - - let marginProp = cssLogic.getPropertyInfo("margin-left"); - is(marginProp.matchedRuleCount, 0, - "margin-left should not be included in matched selectors."); - - let fontSizeProp = cssLogic.getPropertyInfo("font-size"); - is(fontSizeProp.matchedRuleCount, 1, - "font-size should be included in matched selectors."); - - SimpleTest.finish(); - } - - </script> -</body> -</html>
--- a/devtools/server/tests/mochitest/test_css-logic-media-queries.html +++ b/devtools/server/tests/mochitest/test_css-logic-media-queries.html @@ -28,17 +28,17 @@ Test that css-logic handles media-querie window.onload = function() { var { classes: Cc, utils: Cu, interfaces: Ci } = Components; const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"] .getService(Ci.inIDOMUtils); var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {}); var Services = require("Services"); - const {CssLogic} = require("devtools/shared/inspector/css-logic"); + const {CssLogic} = require("devtools/server/css-logic"); SimpleTest.waitForExplicitFinish(); let div = document.querySelector("div"); let cssLogic = new CssLogic(DOMUtils.isInheritedProperty); cssLogic.highlight(div); cssLogic.processMatchedSelectors();
--- a/devtools/server/tests/mochitest/test_css-logic-specificity.html +++ b/devtools/server/tests/mochitest/test_css-logic-specificity.html @@ -10,17 +10,17 @@ Test that css-logic calculates CSS speci </head> <body style="background:blue;"> <script type="application/javascript;version=1.8"> window.onload = function() { var {utils: Cu, classes: Cc, interfaces: Ci} = Components; const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {}); - const {CssLogic, CssSelector} = require("devtools/shared/inspector/css-logic"); + const {CssLogic, CssSelector} = require("devtools/server/css-logic"); const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"] .getService(Ci.inIDOMUtils); const TEST_DATA = [ {text: "*", expected: 0}, {text: "LI", expected: 1}, {text: "UL LI", expected: 2}, {text: "UL OL + LI", expected: 3},
--- a/devtools/server/tests/mochitest/test_css-logic.html +++ b/devtools/server/tests/mochitest/test_css-logic.html @@ -6,17 +6,17 @@ https://bugzilla.mozilla.org/show_bug.cg <head> <meta charset="utf-8"> <title>Test for Bug </title> <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script> <script type="application/javascript;version=1.8"> -const {CssLogic} = require("devtools/shared/inspector/css-logic"); +const {CssLogic} = require("devtools/server/css-logic"); window.onload = function() { SimpleTest.waitForExplicitFinish(); runNextTest(); } addTest(function findAllCssSelectors() { var nodes = document.querySelectorAll('*');
--- a/devtools/server/tests/mochitest/test_styles-matched.html +++ b/devtools/server/tests/mochitest/test_styles-matched.html @@ -7,17 +7,17 @@ https://bugzilla.mozilla.org/show_bug.cg <meta charset="utf-8"> <title>Test for Bug </title> <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script> <script type="application/javascript;version=1.8"> const inspector = require("devtools/server/actors/inspector"); -const {CssLogic} = require("devtools/shared/inspector/css-logic"); +const CssLogic = require("devtools/shared/inspector/css-logic"); window.onload = function() { SimpleTest.waitForExplicitFinish(); runNextTest(); } var gWalker = null; var gStyles = null;
--- a/devtools/server/tests/unit/test_protocol_children.js +++ b/devtools/server/tests/unit/test_protocol_children.js @@ -52,17 +52,16 @@ const childSpec = protocol.generateActor detail: Arg(0, "array:childActor#detail2"), } }, methods: { echo: { request: { str: Arg(0) }, response: { str: RetVal("string") }, - telemetry: "ECHO" }, getDetail1: { // This also exercises return-value-as-packet. response: RetVal("childActor#detail1"), }, getDetail2: { // This also exercises return-value-as-packet. response: RetVal("childActor#detail2"),
--- a/devtools/shared/client/main.js +++ b/devtools/shared/client/main.js @@ -229,44 +229,32 @@ const DebuggerClient = exports.DebuggerC }; /** * A declarative helper for defining methods that send requests to the server. * * @param aPacketSkeleton * The form of the packet to send. Can specify fields to be filled from * the parameters by using the |args| function. - * @param telemetry - * The unique suffix of the telemetry histogram id. * @param before * The function to call before sending the packet. Is passed the packet, * and the return value is used as the new packet. The |this| context is * the instance of the client object we are defining a method for. * @param after * The function to call after the response is received. It is passed the * response, and the return value is considered the new response that * will be passed to the callback. The |this| context is the instance of * the client object we are defining a method for. * @return Request * The `Request` object that is a Promise object and resolves once * we receive the response. (See request method for more details) */ DebuggerClient.requester = function (aPacketSkeleton, config = {}) { - let { telemetry, before, after } = config; + let { before, after } = config; return DevToolsUtils.makeInfallible(function (...args) { - let histogram, startTime; - if (telemetry) { - let transportType = this._transport.onOutputStreamReady === undefined - ? "LOCAL_" - : "REMOTE_"; - let histogramId = "DEVTOOLS_DEBUGGER_RDP_" - + transportType + telemetry + "_MS"; - histogram = Services.telemetry.getHistogramById(histogramId); - startTime = +new Date; - } let outgoingPacket = { to: aPacketSkeleton.to || this.actor }; let maxPosition = -1; for (let k of Object.keys(aPacketSkeleton)) { if (aPacketSkeleton[k] instanceof DebuggerClient.Argument) { let { position } = aPacketSkeleton[k]; @@ -290,20 +278,16 @@ DebuggerClient.requester = function (aPa } } // The callback is always the last parameter. let thisCallback = args[maxPosition + 1]; if (thisCallback) { thisCallback(aResponse); } - - if (histogram) { - histogram.add(+new Date - startTime); - } }, "DebuggerClient.requester request callback")); }, "DebuggerClient.requester"); }; function args(aPos) { return new DebuggerClient.Argument(aPos); } @@ -644,18 +628,16 @@ DebuggerClient.prototype = { * The actor ID to send the request to. * @param aOnResponse function * If specified, will be called with the response packet when * debugging server responds. */ release: DebuggerClient.requester({ to: args(0), type: "release" - }, { - telemetry: "RELEASE" }), /** * Send a request to the debugging server. * * @param aRequest object * A JSON packet to send to the debugging server. * @param aOnResponse function @@ -1309,17 +1291,16 @@ TabClient.prototype = { this.thread.detach(); } return aPacket; }, after: function (aResponse) { this.client.unregisterClient(this); return aResponse; }, - telemetry: "TABDETACH" }), /** * Bring the window to the front. */ focus: DebuggerClient.requester({ type: "focus" }, {}), @@ -1332,52 +1313,44 @@ TabClient.prototype = { * this reload should skip the cache */ reload: function (options = { force: false }) { return this._reload(options); }, _reload: DebuggerClient.requester({ type: "reload", options: args(0) - }, { - telemetry: "RELOAD" }), /** * Navigate to another URL. * * @param string url * The URL to navigate to. */ navigateTo: DebuggerClient.requester({ type: "navigateTo", url: args(0) - }, { - telemetry: "NAVIGATETO" }), /** * Reconfigure the tab actor. * * @param object aOptions * A dictionary object of the new options to use in the tab actor. * @param function aOnResponse * Called with the response packet. */ reconfigure: DebuggerClient.requester({ type: "reconfigure", options: args(0) - }, { - telemetry: "RECONFIGURETAB" }), listWorkers: DebuggerClient.requester({ type: "listWorkers" - }, { - telemetry: "LISTWORKERS" }), attachWorker: function (aWorkerActor, aOnResponse) { this.client.attachWorker(aWorkerActor, aOnResponse); }, /** * Resolve a location ({ url, line, column }) to its current @@ -1432,18 +1405,16 @@ WorkerClient.prototype = { detach: DebuggerClient.requester({ type: "detach" }, { after: function (aResponse) { if (this.thread) { this.client.unregisterClient(this.thread); } this.client.unregisterClient(this); return aResponse; }, - - telemetry: "WORKERDETACH" }), attachThread: function (aOptions = {}, aOnResponse = noop) { if (this.thread) { let response = [{ type: "connected", threadActor: this.thread._actor, consoleActor: this.consoleActor, @@ -1525,17 +1496,16 @@ AddonClient.prototype = { }, { after: function (aResponse) { if (this._client.activeAddon === this) { this._client.activeAddon = null; } this._client.unregisterClient(this); return aResponse; }, - telemetry: "ADDONDETACH" }) }; /** * A RootClient object represents a root actor on the server. Each * DebuggerClient keeps a RootClient instance representing the root actor * for the initial connection; DebuggerClient's 'listTabs' and * 'listChildProcesses' methods forward to that root actor. @@ -1566,54 +1536,51 @@ RootClient.prototype = { constructor: RootClient, /** * List the open tabs. * * @param function aOnResponse * Called with the response packet. */ - listTabs: DebuggerClient.requester({ type: "listTabs" }, - { telemetry: "LISTTABS" }), + listTabs: DebuggerClient.requester({ type: "listTabs" }), /** * List the installed addons. * * @param function aOnResponse * Called with the response packet. */ - listAddons: DebuggerClient.requester({ type: "listAddons" }, - { telemetry: "LISTADDONS" }), + listAddons: DebuggerClient.requester({ type: "listAddons" }), /** * List the registered workers. * * @param function aOnResponse * Called with the response packet. */ - listWorkers: DebuggerClient.requester({ type: "listWorkers" }, - { telemetry: "LISTWORKERS" }), + listWorkers: DebuggerClient.requester({ type: "listWorkers" }), /** * List the registered service workers. * * @param function aOnResponse * Called with the response packet. */ - listServiceWorkerRegistrations: DebuggerClient.requester({ type: "listServiceWorkerRegistrations" }, - { telemetry: "LISTSERVICEWORKERREGISTRATIONS" }), + listServiceWorkerRegistrations: DebuggerClient.requester({ + type: "listServiceWorkerRegistrations" + }), /** * List the running processes. * * @param function aOnResponse * Called with the response packet. */ - listProcesses: DebuggerClient.requester({ type: "listProcesses" }, - { telemetry: "LISTPROCESSES" }), + listProcesses: DebuggerClient.requester({ type: "listProcesses" }), /** * Fetch the TabActor for the currently selected tab, or for a specific * tab given as first parameter. * * @param [optional] object aFilter * A dictionary object with following optional attributes: * - outerWindowID: used to match tabs in parent process @@ -1659,18 +1626,17 @@ RootClient.prototype = { }, /** * Description of protocol's actors and methods. * * @param function aOnResponse * Called with the response packet. */ - protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }, - { telemetry: "PROTOCOLDESCRIPTION" }), + protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }), /* * Methods constructed by DebuggerClient.requester require these forwards * on their 'this'. */ get _transport() { return this._client._transport; }, get request() { return this._client.request; } }; @@ -1752,32 +1718,29 @@ ThreadClient.prototype = { }, after: function (aResponse) { if (aResponse.error) { // There was an error resuming, back to paused state. this._state = "paused"; } return aResponse; }, - telemetry: "RESUME" }), /** * Reconfigure the thread actor. * * @param object aOptions * A dictionary object of the new options to use in the thread actor. * @param function aOnResponse * Called with the response packet. */ reconfigure: DebuggerClient.requester({ type: "reconfigure", options: args(0) - }, { - telemetry: "RECONFIGURETHREAD" }), /** * Resume a paused thread. */ resume: function (aOnResponse) { return this._doResume(null, aOnResponse); }, @@ -1846,18 +1809,16 @@ ThreadClient.prototype = { * Interrupt a running thread. * * @param function aOnResponse * Called with the response packet. */ _doInterrupt: DebuggerClient.requester({ type: "interrupt", when: args(0) - }, { - telemetry: "INTERRUPT" }), /** * Enable or disable pausing when an exception is thrown. * * @param boolean aFlag * Enables pausing if true, disables otherwise. * @param function aOnResponse @@ -1942,86 +1903,76 @@ ThreadClient.prototype = { }, after: function (aResponse) { if (aResponse.error) { // There was an error resuming, back to paused state. this._state = "paused"; } return aResponse; }, - telemetry: "CLIENTEVALUATE" }), /** * Detach from the thread actor. * * @param function aOnResponse * Called with the response packet. */ detach: DebuggerClient.requester({ type: "detach" }, { after: function (aResponse) { this.client.unregisterClient(this); this._parent.thread = null; return aResponse; }, - telemetry: "THREADDETACH" }), /** * Release multiple thread-lifetime object actors. If any pause-lifetime * actors are included in the request, a |notReleasable| error will return, * but all the thread-lifetime ones will have been released. * * @param array actors * An array with actor IDs to release. */ releaseMany: DebuggerClient.requester({ type: "releaseMany", actors: args(0), - }, { - telemetry: "RELEASEMANY" }), /** * Promote multiple pause-lifetime object actors to thread-lifetime ones. * * @param array actors * An array with actor IDs to promote. */ threadGrips: DebuggerClient.requester({ type: "threadGrips", actors: args(0) - }, { - telemetry: "THREADGRIPS" }), /** * Return the event listeners defined on the page. * * @param aOnResponse Function * Called with the thread's response. */ eventListeners: DebuggerClient.requester({ type: "eventListeners" - }, { - telemetry: "EVENTLISTENERS" }), /** * Request the loaded sources for the current thread. * * @param aOnResponse Function * Called with the thread's response. */ getSources: DebuggerClient.requester({ type: "sources" - }, { - telemetry: "SOURCES" }), /** * Clear the thread's source script cache. A scriptscleared event * will be sent. */ _clearScripts: function () { if (Object.keys(this._scriptCache).length > 0) { @@ -2041,18 +1992,16 @@ ThreadClient.prototype = { * frames. * @param aOnResponse function * Called with the thread's response. */ getFrames: DebuggerClient.requester({ type: "frames", start: args(0), count: args(1) - }, { - telemetry: "FRAMES" }), /** * An array of cached frames. Clients can observe the framesadded and * framescleared event to keep up to date on changes to this cache, * and can fill it using the fillFrames method. */ get cachedFrames() { return this._frameCache; }, @@ -2259,18 +2208,16 @@ ThreadClient.prototype = { * @param aOnResponse function * Called with the request's response. * @param actors [string] * List of actor ID of the queried objects. */ getPrototypesAndProperties: DebuggerClient.requester({ type: "prototypesAndProperties", actors: args(0) - }, { - telemetry: "PROTOTYPESANDPROPERTIES" }), events: ["newSource"] }; eventSource(ThreadClient.prototype); /** @@ -2305,17 +2252,16 @@ TraceClient.prototype = { */ detach: DebuggerClient.requester({ type: "detach" }, { after: function (aResponse) { this._client.unregisterClient(this); return aResponse; }, - telemetry: "TRACERDETACH" }), /** * Start a new trace. * * @param aTrace [string] * An array of trace types to be recorded by the new trace. * @@ -2338,17 +2284,16 @@ TraceClient.prototype = { if (!this.tracing) { this._waitingPackets.clear(); this._expectedPacket = 0; } this._activeTraces.add(aResponse.name); return aResponse; }, - telemetry: "STARTTRACE" }), /** * End a trace. If a name is provided, stop the named * trace. Otherwise, stop the most recently started trace. * * @param aName string * The name of the trace to stop. @@ -2364,17 +2309,16 @@ TraceClient.prototype = { if (aResponse.error) { return aResponse; } this._activeTraces.delete(aResponse.name); return aResponse; }, - telemetry: "STOPTRACE" }) }; /** * Grip clients are used to retrieve information about the relevant object. * * @param aClient DebuggerClient * The debugger client parent. @@ -2428,40 +2372,35 @@ ObjectClient.prototype = { type: "parameterNames" }, { before: function (aPacket) { if (this._grip["class"] !== "Function") { throw new Error("getParameterNames is only valid for function grips."); } return aPacket; }, - telemetry: "PARAMETERNAMES" }), /** * Request the names of the properties defined on the object and not its * prototype. * * @param aOnResponse function Called with the request's response. */ getOwnPropertyNames: DebuggerClient.requester({ type: "ownPropertyNames" - }, { - telemetry: "OWNPROPERTYNAMES" }), /** * Request the prototype and own properties of the object. * * @param aOnResponse function Called with the request's response. */ getPrototypeAndProperties: DebuggerClient.requester({ type: "prototypeAndProperties" - }, { - telemetry: "PROTOTYPEANDPROPERTIES" }), /** * Request a PropertyIteratorClient instance to ease listing * properties for this object. * * @param options Object * A dictionary object with various boolean attributes: @@ -2482,17 +2421,16 @@ ObjectClient.prototype = { options: args(0) }, { after: function (aResponse) { if (aResponse.iterator) { return { iterator: new PropertyIteratorClient(this._client, aResponse.iterator) }; } return aResponse; }, - telemetry: "ENUMPROPERTIES" }), /** * Request a PropertyIteratorClient instance to enumerate entries in a * Map/Set-like object. * * @param aOnResponse function Called with the request's response. */ @@ -2519,57 +2457,50 @@ ObjectClient.prototype = { * Request the property descriptor of the object's specified property. * * @param aName string The name of the requested property. * @param aOnResponse function Called with the request's response. */ getProperty: DebuggerClient.requester({ type: "property", name: args(0) - }, { - telemetry: "PROPERTY" }), /** * Request the prototype of the object. * * @param aOnResponse function Called with the request's response. */ getPrototype: DebuggerClient.requester({ type: "prototype" - }, { - telemetry: "PROTOTYPE" }), /** * Request the display string of the object. * * @param aOnResponse function Called with the request's response. */ getDisplayString: DebuggerClient.requester({ type: "displayString" - }, { - telemetry: "DISPLAYSTRING" }), /** * Request the scope of the object. * * @param aOnResponse function Called with the request's response. */ getScope: DebuggerClient.requester({ type: "scope" }, { before: function (aPacket) { if (this._grip.class !== "Function") { throw new Error("scope is only valid for function grips."); } return aPacket; }, - telemetry: "SCOPE" }), /** * Request the promises directly depending on the current promise. */ getDependentPromises: DebuggerClient.requester({ type: "dependentPromises" }, { @@ -2728,18 +2659,16 @@ LongStringClient.prototype = { * The ending index. * @param aCallback Function * The function called when we receive the substring. */ substring: DebuggerClient.requester({ type: "substring", start: args(0), end: args(1) - }, { - telemetry: "SUBSTRING" }), }; /** * A SourceClient provides a way to access the source text of a script. * * @param aClient ThreadClient * The thread client parent. @@ -2778,17 +2707,16 @@ SourceClient.prototype = { * Black box this SourceClient's source. * * @param aCallback Function * The callback function called when we receive the response from the server. */ blackBox: DebuggerClient.requester({ type: "blackbox" }, { - telemetry: "BLACKBOX", after: function (aResponse) { if (!aResponse.error) { this._isBlackBoxed = true; if (this._activeThread) { this._activeThread.emit("blackboxchange", this); } } return aResponse; @@ -2799,17 +2727,16 @@ SourceClient.prototype = { * Un-black box this SourceClient's source. * * @param aCallback Function * The callback function called when we receive the response from the server. */ unblackBox: DebuggerClient.requester({ type: "unblackbox" }, { - telemetry: "UNBLACKBOX", after: function (aResponse) { if (!aResponse.error) { this._isBlackBoxed = false; if (this._activeThread) { this._activeThread.emit("blackboxchange", this); } } return aResponse; @@ -3023,18 +2950,16 @@ BreakpointClient.prototype = { get actor() { return this._actor; }, get _transport() { return this._client._transport; }, /** * Remove the breakpoint from the server. */ remove: DebuggerClient.requester({ type: "delete" - }, { - telemetry: "DELETE" }), /** * Determines if this breakpoint has a condition */ hasCondition: function () { let root = this._client.mainRoot; // XXX bug 990137: We will remove support for client-side handling of @@ -3131,26 +3056,22 @@ EnvironmentClient.prototype = { }, get _transport() { return this._client._transport; }, /** * Fetches the bindings introduced by this lexical environment. */ getBindings: DebuggerClient.requester({ type: "bindings" - }, { - telemetry: "BINDINGS" }), /** * Changes the value of the identifier whose name is name (a string) to that * represented by value (a grip). */ assign: DebuggerClient.requester({ type: "assign", name: args(0), value: args(1) - }, { - telemetry: "ASSIGN" }) }; eventSource(EnvironmentClient.prototype);
--- a/devtools/shared/gcli/commands/screenshot.js +++ b/devtools/shared/gcli/commands/screenshot.js @@ -1,15 +1,15 @@ /* 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, Cr } = require("chrome"); +const { Cc, Ci, Cr, Cu } = require("chrome"); const l10n = require("gcli/l10n"); const Services = require("Services"); const { NetUtil } = require("resource://gre/modules/NetUtil.jsm"); const { getRect } = require("devtools/shared/layout/utils"); const promise = require("promise"); const defer = require("devtools/shared/defer"); const { Task } = require("devtools/shared/task"); @@ -186,17 +186,17 @@ exports.items = [ name: "chrome", type: "boolean", description: l10n.lookupFormat("screenshotChromeDesc2", [BRAND_SHORT_NAME]), manual: l10n.lookupFormat("screenshotChromeManual2", [BRAND_SHORT_NAME]) }, ] }, ], - exec: function(args, context) { + exec: function (args, context) { if (args.chrome && args.selector) { // Node screenshot with chrome option does not work as intended // Refer https://bugzilla.mozilla.org/show_bug.cgi?id=659268#c7 // throwing for now. throw new Error(l10n.lookup("screenshotSelectorChromeConflict")); } let capture; @@ -204,34 +204,49 @@ exports.items = [ // Re-execute the command on the server const command = context.typed.replace(/^screenshot/, "screenshot_server"); capture = context.updateExec(command).then(output => { return output.error ? Promise.reject(output.data) : output.data; }); } else { capture = captureScreenshot(args, context.environment.chromeDocument); } - + simulateCameraEffect(context.environment.chromeDocument, "shutter"); return capture.then(saveScreenshot.bind(null, args, context)); }, }, { item: "command", runAt: "server", name: "screenshot_server", hidden: true, returnType: "imageSummary", params: [ filenameParam, standardParams ], - exec: function(args, context) { + exec: function (args, context) { return captureScreenshot(args, context.environment.document); }, } ]; /** + * This function is called to simulate camera effects + */ +function simulateCameraEffect(document, effect) { + let window = document.defaultView; + if (effect === "shutter") { + const audioCamera = new window.Audio("resource://devtools/client/themes/audio/shutter.wav"); + audioCamera.play(); + } + if (effect == "flash") { + const frames = Cu.cloneInto({ opacity: [ 0, 1 ] }, window); + document.documentElement.animate(frames, 500); + } +} + +/** * This function simply handles the --delay argument before calling * createScreenshotData */ function captureScreenshot(args, document) { if (args.delay > 0) { return new Promise((resolve, reject) => { document.defaultView.setTimeout(() => { createScreenshotData(document, args).then(resolve, reject); @@ -316,16 +331,18 @@ function createScreenshotData(document, ctx.drawWindow(window, left, top, width, height, "#fff"); const data = canvas.toDataURL("image/png", ""); // See comment above on bug 961832 if (args.fullpage) { window.scrollTo(currentX, currentY); } + simulateCameraEffect(document, "flash"); + return Promise.resolve({ destinations: [], data: data, height: height, width: width, filename: filename, }); }
--- a/devtools/shared/inspector/css-logic.js +++ b/devtools/shared/inspector/css-logic.js @@ -35,828 +35,83 @@ * CssLogic uses the standard DOM API, and the Gecko inIDOMUtils API to access * styling information in the page, and present this to the user in a way that * helps them understand: * - why their expectations may not have been fulfilled * - how browsers process CSS * @constructor */ -const { Cc, Ci, Cu } = require("chrome"); +const { Cc, Ci } = require("chrome"); const Services = require("Services"); -const DevToolsUtils = require("devtools/shared/DevToolsUtils"); -const { getRootBindingParent } = require("devtools/shared/layout/utils"); -const nodeConstants = require("devtools/shared/dom-node-constants"); // This should be ok because none of the functions that use this should be used // on the worker thread, where Cu is not available. loader.lazyRequireGetter(this, "CSS", "CSS"); loader.lazyRequireGetter(this, "CSSLexer", "devtools/shared/css-lexer"); /** - * @param {function} isInherited A function that determines if the CSS property - * is inherited. - */ -function CssLogic(isInherited) { - // The cache of examined CSS properties. - this._isInherited = isInherited; - this._propertyInfos = {}; -} - -exports.CssLogic = CssLogic; - -/** * Special values for filter, in addition to an href these values can be used */ -CssLogic.FILTER = { +exports.FILTER = { // show properties for all user style sheets. USER: "user", // USER, plus user-agent (i.e. browser) style sheets UA: "ua", }; /** - * Known media values. To distinguish "all" stylesheets (above) from "all" media - * The full list includes braille, embossed, handheld, print, projection, - * speech, tty, and tv, but this is only a hack because these are not defined - * in the DOM at all. - * @see http://www.w3.org/TR/CSS21/media.html#media-types - */ -CssLogic.MEDIA = { - ALL: "all", - SCREEN: "screen", -}; - -/** * Each rule has a status, the bigger the number, the better placed it is to * provide styling information. * * These statuses are localized inside the styleinspector.properties * string bundle. * @see csshtmltree.js RuleView._cacheStatusNames() */ -CssLogic.STATUS = { +exports.STATUS = { BEST: 3, MATCHED: 2, PARENT_MATCH: 1, UNMATCHED: 0, UNKNOWN: -1, }; -CssLogic.prototype = { - // Both setup by highlight(). - viewedElement: null, - viewedDocument: null, - - // The cache of the known sheets. - _sheets: null, - - // Have the sheets been cached? - _sheetsCached: false, - - // The total number of rules, in all stylesheets, after filtering. - _ruleCount: 0, - - // The computed styles for the viewedElement. - _computedStyle: null, - - // Source filter. Only display properties coming from the given source - _sourceFilter: CssLogic.FILTER.USER, - - // Used for tracking unique CssSheet/CssRule/CssSelector objects, in a run of - // processMatchedSelectors(). - _passId: 0, - - // Used for tracking matched CssSelector objects. - _matchId: 0, - - _matchedRules: null, - _matchedSelectors: null, - - // Cached keyframes rules in all stylesheets - _keyframesRules: null, - - /** - * Reset various properties - */ - reset: function () { - this._propertyInfos = {}; - this._ruleCount = 0; - this._sheetIndex = 0; - this._sheets = {}; - this._sheetsCached = false; - this._matchedRules = null; - this._matchedSelectors = null; - this._keyframesRules = []; - }, - - /** - * Focus on a new element - remove the style caches. - * - * @param {nsIDOMElement} aViewedElement the element the user has highlighted - * in the Inspector. - */ - highlight: function (viewedElement) { - if (!viewedElement) { - this.viewedElement = null; - this.viewedDocument = null; - this._computedStyle = null; - this.reset(); - return; - } - - if (viewedElement === this.viewedElement) { - return; - } - - this.viewedElement = viewedElement; - - let doc = this.viewedElement.ownerDocument; - if (doc != this.viewedDocument) { - // New document: clear/rebuild the cache. - this.viewedDocument = doc; - - // Hunt down top level stylesheets, and cache them. - this._cacheSheets(); - } else { - // Clear cached data in the CssPropertyInfo objects. - this._propertyInfos = {}; - } - - this._matchedRules = null; - this._matchedSelectors = null; - this._computedStyle = CssLogic.getComputedStyle(this.viewedElement); - }, - - /** - * Get the values of all the computed CSS properties for the highlighted - * element. - * @returns {object} The computed CSS properties for a selected element - */ - get computedStyle() { - return this._computedStyle; - }, - - /** - * Get the source filter. - * @returns {string} The source filter being used. - */ - get sourceFilter() { - return this._sourceFilter; - }, - - /** - * Source filter. Only display properties coming from the given source (web - * address). Note that in order to avoid information overload we DO NOT show - * unmatched system rules. - * @see CssLogic.FILTER.* - */ - set sourceFilter(value) { - let oldValue = this._sourceFilter; - this._sourceFilter = value; - - let ruleCount = 0; - - // Update the CssSheet objects. - this.forEachSheet(function (sheet) { - sheet._sheetAllowed = -1; - if (sheet.contentSheet && sheet.sheetAllowed) { - ruleCount += sheet.ruleCount; - } - }, this); - - this._ruleCount = ruleCount; - - // Full update is needed because the this.processMatchedSelectors() method - // skips UA stylesheets if the filter does not allow such sheets. - let needFullUpdate = (oldValue == CssLogic.FILTER.UA || - value == CssLogic.FILTER.UA); - - if (needFullUpdate) { - this._matchedRules = null; - this._matchedSelectors = null; - this._propertyInfos = {}; - } else { - // Update the CssPropertyInfo objects. - for (let property in this._propertyInfos) { - this._propertyInfos[property].needRefilter = true; - } - } - }, - - /** - * Return a CssPropertyInfo data structure for the currently viewed element - * and the specified CSS property. If there is no currently viewed element we - * return an empty object. - * - * @param {string} property The CSS property to look for. - * @return {CssPropertyInfo} a CssPropertyInfo structure for the given - * property. - */ - getPropertyInfo: function (property) { - if (!this.viewedElement) { - return {}; - } - - let info = this._propertyInfos[property]; - if (!info) { - info = new CssPropertyInfo(this, property, this._isInherited); - this._propertyInfos[property] = info; - } - - return info; - }, - - /** - * Cache all the stylesheets in the inspected document - * @private - */ - _cacheSheets: function () { - this._passId++; - this.reset(); - - // styleSheets isn't an array, but forEach can work on it anyway - Array.prototype.forEach.call(this.viewedDocument.styleSheets, - this._cacheSheet, this); - - this._sheetsCached = true; - }, - - /** - * Cache a stylesheet if it falls within the requirements: if it's enabled, - * and if the @media is allowed. This method also walks through the stylesheet - * cssRules to find @imported rules, to cache the stylesheets of those rules - * as well. In addition, the @keyframes rules in the stylesheet are cached. - * - * @private - * @param {CSSStyleSheet} domSheet the CSSStyleSheet object to cache. - */ - _cacheSheet: function (domSheet) { - if (domSheet.disabled) { - return; - } - - // Only work with stylesheets that have their media allowed. - if (!this.mediaMatches(domSheet)) { - return; - } - - // Cache the sheet. - let cssSheet = this.getSheet(domSheet, this._sheetIndex++); - if (cssSheet._passId != this._passId) { - cssSheet._passId = this._passId; - - // Find import and keyframes rules. - for (let aDomRule of domSheet.cssRules) { - if (aDomRule.type == CSSRule.IMPORT_RULE && - aDomRule.styleSheet && - this.mediaMatches(aDomRule)) { - this._cacheSheet(aDomRule.styleSheet); - } else if (aDomRule.type == CSSRule.KEYFRAMES_RULE) { - this._keyframesRules.push(aDomRule); - } - } - } - }, - - /** - * Retrieve the list of stylesheets in the document. - * - * @return {array} the list of stylesheets in the document. - */ - get sheets() { - if (!this._sheetsCached) { - this._cacheSheets(); - } - - let sheets = []; - this.forEachSheet(function (sheet) { - if (sheet.contentSheet) { - sheets.push(sheet); - } - }, this); - - return sheets; - }, - - /** - * Retrieve the list of keyframes rules in the document. - * - * @ return {array} the list of keyframes rules in the document. - */ - get keyframesRules() { - if (!this._sheetsCached) { - this._cacheSheets(); - } - return this._keyframesRules; - }, - - /** - * Retrieve a CssSheet object for a given a CSSStyleSheet object. If the - * stylesheet is already cached, you get the existing CssSheet object, - * otherwise the new CSSStyleSheet object is cached. - * - * @param {CSSStyleSheet} domSheet the CSSStyleSheet object you want. - * @param {number} index the index, within the document, of the stylesheet. - * - * @return {CssSheet} the CssSheet object for the given CSSStyleSheet object. - */ - getSheet: function (domSheet, index) { - let cacheId = ""; - - if (domSheet.href) { - cacheId = domSheet.href; - } else if (domSheet.ownerNode && domSheet.ownerNode.ownerDocument) { - cacheId = domSheet.ownerNode.ownerDocument.location; - } - - let sheet = null; - let sheetFound = false; - - if (cacheId in this._sheets) { - for (let i = 0, numSheets = this._sheets[cacheId].length; - i < numSheets; - i++) { - sheet = this._sheets[cacheId][i]; - if (sheet.domSheet === domSheet) { - if (index != -1) { - sheet.index = index; - } - sheetFound = true; - break; - } - } - } - - if (!sheetFound) { - if (!(cacheId in this._sheets)) { - this._sheets[cacheId] = []; - } - - sheet = new CssSheet(this, domSheet, index); - if (sheet.sheetAllowed && sheet.contentSheet) { - this._ruleCount += sheet.ruleCount; - } - - this._sheets[cacheId].push(sheet); - } - - return sheet; - }, - - /** - * Process each cached stylesheet in the document using your callback. - * - * @param {function} callback the function you want executed for each of the - * CssSheet objects cached. - * @param {object} scope the scope you want for the callback function. scope - * will be the this object when callback executes. - */ - forEachSheet: function (callback, scope) { - for (let cacheId in this._sheets) { - let sheets = this._sheets[cacheId]; - for (let i = 0; i < sheets.length; i++) { - // We take this as an opportunity to clean dead sheets - try { - let sheet = sheets[i]; - // If accessing domSheet raises an exception, then the style - // sheet is a dead object. - sheet.domSheet; - callback.call(scope, sheet, i, sheets); - } catch (e) { - sheets.splice(i, 1); - i--; - } - } - } - }, - - /** - * Process *some* cached stylesheets in the document using your callback. The - * callback function should return true in order to halt processing. - * - * @param {function} callback the function you want executed for some of the - * CssSheet objects cached. - * @param {object} scope the scope you want for the callback function. scope - * will be the this object when callback executes. - * @return {Boolean} true if callback returns true during any iteration, - * otherwise false is returned. - */ - forSomeSheets: function (callback, scope) { - for (let cacheId in this._sheets) { - if (this._sheets[cacheId].some(callback, scope)) { - return true; - } - } - return false; - }, - - /** - * Get the number nsIDOMCSSRule objects in the document, counted from all of - * the stylesheets. System sheets are excluded. If a filter is active, this - * tells only the number of nsIDOMCSSRule objects inside the selected - * CSSStyleSheet. - * - * WARNING: This only provides an estimate of the rule count, and the results - * could change at a later date. Todo remove this - * - * @return {number} the number of nsIDOMCSSRule (all rules). - */ - get ruleCount() { - if (!this._sheetsCached) { - this._cacheSheets(); - } - - return this._ruleCount; - }, - - /** - * Process the CssSelector objects that match the highlighted element and its - * parent elements. scope.callback() is executed for each CssSelector - * object, being passed the CssSelector object and the match status. - * - * This method also includes all of the element.style properties, for each - * highlighted element parent and for the highlighted element itself. - * - * Note that the matched selectors are cached, such that next time your - * callback is invoked for the cached list of CssSelector objects. - * - * @param {function} callback the function you want to execute for each of - * the matched selectors. - * @param {object} scope the scope you want for the callback function. scope - * will be the this object when callback executes. - */ - processMatchedSelectors: function (callback, scope) { - if (this._matchedSelectors) { - if (callback) { - this._passId++; - this._matchedSelectors.forEach(function (value) { - callback.call(scope, value[0], value[1]); - value[0].cssRule._passId = this._passId; - }, this); - } - return; - } - - if (!this._matchedRules) { - this._buildMatchedRules(); - } - - this._matchedSelectors = []; - this._passId++; - - for (let i = 0; i < this._matchedRules.length; i++) { - let rule = this._matchedRules[i][0]; - let status = this._matchedRules[i][1]; - - rule.selectors.forEach(function (selector) { - if (selector._matchId !== this._matchId && - (selector.elementStyle || - this.selectorMatchesElement(rule.domRule, - selector.selectorIndex))) { - selector._matchId = this._matchId; - this._matchedSelectors.push([ selector, status ]); - if (callback) { - callback.call(scope, selector, status); - } - } - }, this); - - rule._passId = this._passId; - } - }, - - /** - * Check if the given selector matches the highlighted element or any of its - * parents. - * - * @private - * @param {DOMRule} domRule - * The DOM Rule containing the selector. - * @param {Number} idx - * The index of the selector within the DOMRule. - * @return {boolean} - * true if the given selector matches the highlighted element or any - * of its parents, otherwise false is returned. - */ - selectorMatchesElement: function (domRule, idx) { - let element = this.viewedElement; - do { - if (domUtils.selectorMatchesElement(element, domRule, idx)) { - return true; - } - } while ((element = element.parentNode) && - element.nodeType === nodeConstants.ELEMENT_NODE); - - return false; - }, - - /** - * Check if the highlighted element or it's parents have matched selectors. - * - * @param {array} aProperties The list of properties you want to check if they - * have matched selectors or not. - * @return {object} An object that tells for each property if it has matched - * selectors or not. Object keys are property names and values are booleans. - */ - hasMatchedSelectors: function (properties) { - if (!this._matchedRules) { - this._buildMatchedRules(); - } - - let result = {}; - - this._matchedRules.some(function (value) { - let rule = value[0]; - let status = value[1]; - properties = properties.filter((property) => { - // We just need to find if a rule has this property while it matches - // the viewedElement (or its parents). - if (rule.getPropertyValue(property) && - (status == CssLogic.STATUS.MATCHED || - (status == CssLogic.STATUS.PARENT_MATCH && - this._isInherited(property)))) { - result[property] = true; - return false; - } - // Keep the property for the next rule. - return true; - }); - return properties.length == 0; - }, this); - - return result; - }, - - /** - * Build the array of matched rules for the currently highlighted element. - * The array will hold rules that match the viewedElement and its parents. - * - * @private - */ - _buildMatchedRules: function () { - let domRules; - let element = this.viewedElement; - let filter = this.sourceFilter; - let sheetIndex = 0; - - this._matchId++; - this._passId++; - this._matchedRules = []; - - if (!element) { - return; - } - - do { - let status = this.viewedElement === element ? - CssLogic.STATUS.MATCHED : CssLogic.STATUS.PARENT_MATCH; - - try { - // Handle finding rules on pseudo by reading style rules - // on the parent node with proper pseudo arg to getCSSStyleRules. - let {bindingElement, pseudo} = - CssLogic.getBindingElementAndPseudo(element); - domRules = domUtils.getCSSStyleRules(bindingElement, pseudo); - } catch (ex) { - console.log("CL__buildMatchedRules error: " + ex); - continue; - } - - // getCSSStyleRules can return null with a shadow DOM element. - let numDomRules = domRules ? domRules.Count() : 0; - for (let i = 0; i < numDomRules; i++) { - let domRule = domRules.GetElementAt(i); - if (domRule.type !== CSSRule.STYLE_RULE) { - continue; - } - - let sheet = this.getSheet(domRule.parentStyleSheet, -1); - if (sheet._passId !== this._passId) { - sheet.index = sheetIndex++; - sheet._passId = this._passId; - } - - if (filter === CssLogic.FILTER.USER && !sheet.contentSheet) { - continue; - } - - let rule = sheet.getRule(domRule); - if (rule._passId === this._passId) { - continue; - } - - rule._matchId = this._matchId; - rule._passId = this._passId; - this._matchedRules.push([rule, status]); - } - - // Add element.style information. - if (element.style && element.style.length > 0) { - let rule = new CssRule(null, { style: element.style }, element); - rule._matchId = this._matchId; - rule._passId = this._passId; - this._matchedRules.push([rule, status]); - } - } while ((element = element.parentNode) && - element.nodeType === nodeConstants.ELEMENT_NODE); - }, - - /** - * Tells if the given DOM CSS object matches the current view media. - * - * @param {object} domObject The DOM CSS object to check. - * @return {boolean} True if the DOM CSS object matches the current view - * media, or false otherwise. - */ - mediaMatches: function (domObject) { - let mediaText = domObject.media.mediaText; - return !mediaText || - this.viewedDocument.defaultView.matchMedia(mediaText).matches; - }, -}; - /** - * If the element has an id, return '#id'. Otherwise return 'tagname[n]' where - * n is the index of this element in its siblings. - * <p>A technically more 'correct' output from the no-id case might be: - * 'tagname:nth-of-type(n)' however this is unlikely to be more understood - * and it is longer. - * - * @param {nsIDOMElement} element the element for which you want the short name. - * @return {string} the string to be displayed for element. - */ -CssLogic.getShortName = function (element) { - if (!element) { - return "null"; - } - if (element.id) { - return "#" + element.id; - } - let priorSiblings = 0; - let temp = element; - while ((temp = temp.previousElementSibling)) { - priorSiblings++; - } - return element.tagName + "[" + priorSiblings + "]"; -}; - -/** - * Get an array of short names from the given element to document.body. - * - * @param {nsIDOMElement} element the element for which you want the array of - * short names. - * @return {array} The array of elements. - * <p>Each element is an object of the form: - * <ul> - * <li>{ display: "what to display for the given (parent) element", - * <li> element: referenceToTheElement } - * </ul> - */ -CssLogic.getShortNamePath = function (element) { - let doc = element.ownerDocument; - let reply = []; - - if (!element) { - return reply; - } - - // We want to exclude nodes high up the tree (body/html) unless the user - // has selected that node, in which case we need to report something. - do { - reply.unshift({ - display: CssLogic.getShortName(element), - element: element - }); - element = element.parentNode; - } while (element && element != doc.body && element != doc.head && - element != doc); - - return reply; -}; - -/** - * Get a string list of selectors for a given DOMRule. - * - * @param {DOMRule} domRule - * The DOMRule to parse. - * @return {Array} - * An array of string selectors. - */ -CssLogic.getSelectors = function (domRule) { - let selectors = []; - - let len = domUtils.getSelectorCount(domRule); - for (let i = 0; i < len; i++) { - let text = domUtils.getSelectorText(domRule, i); - selectors.push(text); - } - return selectors; -}; - -/** - * Given a node, check to see if it is a ::before or ::after element. - * If so, return the node that is accessible from within the document - * (the parent of the anonymous node), along with which pseudo element - * it was. Otherwise, return the node itself. - * - * @returns {Object} - * - {DOMNode} node The non-anonymous node - * - {string} pseudo One of ':before', ':after', or null. - */ -CssLogic.getBindingElementAndPseudo = function (node) { - let bindingElement = node; - let pseudo = null; - if (node.nodeName == "_moz_generated_content_before") { - bindingElement = node.parentNode; - pseudo = ":before"; - } else if (node.nodeName == "_moz_generated_content_after") { - bindingElement = node.parentNode; - pseudo = ":after"; - } - return { - bindingElement: bindingElement, - pseudo: pseudo - }; -}; - -/** - * Get the computed style on a node. Automatically handles reading - * computed styles on a ::before/::after element by reading on the - * parent node with the proper pseudo argument. - * - * @param {Node} - * @returns {CSSStyleDeclaration} - */ -CssLogic.getComputedStyle = function (node) { - if (!node || - Cu.isDeadWrapper(node) || - node.nodeType !== nodeConstants.ELEMENT_NODE || - !node.ownerDocument || - !node.ownerDocument.defaultView) { - return null; - } - - let {bindingElement, pseudo} = CssLogic.getBindingElementAndPseudo(node); - return node.ownerDocument.defaultView.getComputedStyle(bindingElement, - pseudo); -}; - -/** - * Memonized lookup of a l10n string from a string bundle. + * Memoized lookup of a l10n string from a string bundle. * @param {string} name The key to lookup. * @returns A localized version of the given key. */ -CssLogic.l10n = function (name) { - return CssLogic._strings.GetStringFromName(name); +exports.l10n = function (name) { + return exports._strings.GetStringFromName(name); }; -DevToolsUtils.defineLazyGetter(CssLogic, "_strings", function () { - return Services.strings - .createBundle("chrome://devtools-shared/locale/styleinspector.properties"); -}); +exports._strings = Services.strings + .createBundle("chrome://devtools-shared/locale/styleinspector.properties"); /** * Is the given property sheet a content stylesheet? * * @param {CSSStyleSheet} sheet a stylesheet * @return {boolean} true if the given stylesheet is a content stylesheet, * false otherwise. */ -CssLogic.isContentStylesheet = function (sheet) { +exports.isContentStylesheet = function (sheet) { return sheet.parsingMode !== "agent"; }; /** - * Get a source for a stylesheet, taking into account embedded stylesheets - * for which we need to use document.defaultView.location.href rather than - * sheet.href - * - * @param {CSSStyleSheet} sheet the DOM object for the style sheet. - * @return {string} the address of the stylesheet. - */ -CssLogic.href = function (sheet) { - let href = sheet.href; - if (!href) { - href = sheet.ownerNode.ownerDocument.location; - } - - return href; -}; - -/** * Return a shortened version of a style sheet's source. * * @param {CSSStyleSheet} sheet the DOM object for the style sheet. */ -CssLogic.shortSource = function (sheet) { +exports.shortSource = function (sheet) { // Use a string like "inline" if there is no source href if (!sheet || !sheet.href) { - return CssLogic.l10n("rule.sourceInline"); + return exports.l10n("rule.sourceInline"); } // We try, in turn, the filename, filePath, query string, whole thing let url = {}; try { url = Services.io.newURI(sheet.href, null, null); url = url.QueryInterface(Ci.nsIURL); } catch (ex) { @@ -874,109 +129,29 @@ CssLogic.shortSource = function (sheet) if (url.query) { return url.query; } let dataUrl = sheet.href.match(/^(data:[^,]*),/); return dataUrl ? dataUrl[1] : sheet.href; }; -/** - * Find the position of [element] in [nodeList]. - * @returns an index of the match, or -1 if there is no match - */ -function positionInNodeList(element, nodeList) { - for (let i = 0; i < nodeList.length; i++) { - if (element === nodeList[i]) { - return i; - } - } - return -1; -} - -/** - * Find a unique CSS selector for a given element - * @returns a string such that ele.ownerDocument.querySelector(reply) === ele - * and ele.ownerDocument.querySelectorAll(reply).length === 1 - */ -CssLogic.findCssSelector = function (ele) { - ele = getRootBindingParent(ele); - let document = ele.ownerDocument; - if (!document || !document.contains(ele)) { - throw new Error("findCssSelector received element not inside document"); - } - - // document.querySelectorAll("#id") returns multiple if elements share an ID - if (ele.id && - document.querySelectorAll("#" + CSS.escape(ele.id)).length === 1) { - return "#" + CSS.escape(ele.id); - } - - // Inherently unique by tag name - let tagName = ele.localName; - if (tagName === "html") { - return "html"; - } - if (tagName === "head") { - return "head"; - } - if (tagName === "body") { - return "body"; - } - - // We might be able to find a unique class name - let selector, index, matches; - if (ele.classList.length > 0) { - for (let i = 0; i < ele.classList.length; i++) { - // Is this className unique by itself? - selector = "." + CSS.escape(ele.classList.item(i)); - matches = document.querySelectorAll(selector); - if (matches.length === 1) { - return selector; - } - // Maybe it's unique with a tag name? - selector = tagName + selector; - matches = document.querySelectorAll(selector); - if (matches.length === 1) { - return selector; - } - // Maybe it's unique using a tag name and nth-child - index = positionInNodeList(ele, ele.parentNode.children) + 1; - selector = selector + ":nth-child(" + index + ")"; - matches = document.querySelectorAll(selector); - if (matches.length === 1) { - return selector; - } - } - } - - // Not unique enough yet. As long as it's not a child of the document, - // continue recursing up until it is unique enough. - if (ele.parentNode !== document) { - index = positionInNodeList(ele, ele.parentNode.children) + 1; - selector = CssLogic.findCssSelector(ele.parentNode) + " > " + - tagName + ":nth-child(" + index + ")"; - } - - return selector; -}; - const TAB_CHARS = "\t"; /** * Prettify minified CSS text. * This prettifies CSS code where there is no indentation in usual places while * keeping original indentation as-is elsewhere. * @param string text The CSS source to prettify. * @return string Prettified CSS source */ -CssLogic.prettifyCSS = function (text, ruleCount) { - if (CssLogic.LINE_SEPARATOR == null) { +function prettifyCSS(text, ruleCount) { + if (prettifyCSS.LINE_SEPARATOR == null) { let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS; - CssLogic.LINE_SEPARATOR = (os === "WINNT" ? "\r\n" : "\n"); + prettifyCSS.LINE_SEPARATOR = (os === "WINNT" ? "\r\n" : "\n"); } // remove initial and terminating HTML comments and surrounding whitespace text = text.replace(/(?:^\s*<!--[\r\n]*)|(?:\s*-->\s*$)/g, ""); let originalText = text; text = text.trim(); // don't attempt to prettify if there's more than one line per rule. @@ -1103,17 +278,17 @@ CssLogic.prettifyCSS = function (text, r // Append any saved up text to the result, applying indentation. if (startIndex !== undefined) { if (isCloseBrace && !anyNonWS) { // If we saw only whitespace followed by a "}", then we don't // need anything here. } else { result = result + indent + text.substring(startIndex, endIndex); if (isCloseBrace) { - result += CssLogic.LINE_SEPARATOR; + result += prettifyCSS.LINE_SEPARATOR; } } } if (isCloseBrace) { indent = TAB_CHARS.repeat(--indentLevel); result = result + indent + "}"; } @@ -1138,865 +313,20 @@ CssLogic.prettifyCSS = function (text, r // Here we ignore the case where whitespace appears at the end of // the text. if (pushbackToken && token && token.tokenType === "whitespace" && /\n/g.test(text.substring(token.startOffset, token.endOffset))) { return originalText; } // Finally time for that newline. - result = result + CssLogic.LINE_SEPARATOR; + result = result + prettifyCSS.LINE_SEPARATOR; // Maybe we hit EOF. if (!pushbackToken) { break; } } return result; -}; - -/** - * A safe way to access cached bits of information about a stylesheet. - * - * @constructor - * @param {CssLogic} cssLogic pointer to the CssLogic instance working with - * this CssSheet object. - * @param {CSSStyleSheet} domSheet reference to a DOM CSSStyleSheet object. - * @param {number} index tells the index/position of the stylesheet within the - * main document. - */ -function CssSheet(cssLogic, domSheet, index) { - this._cssLogic = cssLogic; - this.domSheet = domSheet; - this.index = this.contentSheet ? index : -100 * index; - - // Cache of the sheets href. Cached by the getter. - this._href = null; - // Short version of href for use in select boxes etc. Cached by getter. - this._shortSource = null; - - // null for uncached. - this._sheetAllowed = null; - - // Cached CssRules from the given stylesheet. - this._rules = {}; - - this._ruleCount = -1; -} - -CssSheet.prototype = { - _passId: null, - _contentSheet: null, - _mediaMatches: null, - - /** - * Tells if the stylesheet is provided by the browser or not. - * - * @return {boolean} false if this is a browser-provided stylesheet, or true - * otherwise. - */ - get contentSheet() { - if (this._contentSheet === null) { - this._contentSheet = CssLogic.isContentStylesheet(this.domSheet); - } - return this._contentSheet; - }, - - /** - * Tells if the stylesheet is disabled or not. - * @return {boolean} true if this stylesheet is disabled, or false otherwise. - */ - get disabled() { - return this.domSheet.disabled; - }, - - /** - * Tells if the stylesheet matches the current browser view media. - * @return {boolean} true if this stylesheet matches the current browser view - * media, or false otherwise. - */ - get mediaMatches() { - if (this._mediaMatches === null) { - this._mediaMatches = this._cssLogic.mediaMatches(this.domSheet); - } - return this._mediaMatches; - }, - - /** - * Get a source for a stylesheet, using CssLogic.href - * - * @return {string} the address of the stylesheet. - */ - get href() { - if (this._href) { - return this._href; - } - - this._href = CssLogic.href(this.domSheet); - return this._href; - }, - - /** - * Create a shorthand version of the href of a stylesheet. - * - * @return {string} the shorthand source of the stylesheet. - */ - get shortSource() { - if (this._shortSource) { - return this._shortSource; - } - - this._shortSource = CssLogic.shortSource(this.domSheet); - return this._shortSource; - }, - - /** - * Tells if the sheet is allowed or not by the current CssLogic.sourceFilter. - * - * @return {boolean} true if the stylesheet is allowed by the sourceFilter, or - * false otherwise. - */ - get sheetAllowed() { - if (this._sheetAllowed !== null) { - return this._sheetAllowed; - } - - this._sheetAllowed = true; - - let filter = this._cssLogic.sourceFilter; - if (filter === CssLogic.FILTER.USER && !this.contentSheet) { - this._sheetAllowed = false; - } - if (filter !== CssLogic.FILTER.USER && filter !== CssLogic.FILTER.UA) { - this._sheetAllowed = (filter === this.href); - } - - return this._sheetAllowed; - }, - - /** - * Retrieve the number of rules in this stylesheet. - * - * @return {number} the number of nsIDOMCSSRule objects in this stylesheet. - */ - get ruleCount() { - return this._ruleCount > -1 ? - this._ruleCount : - this.domSheet.cssRules.length; - }, - - /** - * Retrieve a CssRule object for the given CSSStyleRule. The CssRule object is - * cached, such that subsequent retrievals return the same CssRule object for - * the same CSSStyleRule object. - * - * @param {CSSStyleRule} aDomRule the CSSStyleRule object for which you want a - * CssRule object. - * @return {CssRule} the cached CssRule object for the given CSSStyleRule - * object. - */ - getRule: function (domRule) { - let cacheId = domRule.type + domRule.selectorText; - - let rule = null; - let ruleFound = false; - - if (cacheId in this._rules) { - for (let i = 0, rulesLen = this._rules[cacheId].length; - i < rulesLen; - i++) { - rule = this._rules[cacheId][i]; - if (rule.domRule === domRule) { - ruleFound = true; - break; - } - } - } - - if (!ruleFound) { - if (!(cacheId in this._rules)) { - this._rules[cacheId] = []; - } - - rule = new CssRule(this, domRule); - this._rules[cacheId].push(rule); - } - - return rule; - }, - - /** - * Process each rule in this stylesheet using your callback function. Your - * function receives one argument: the CssRule object for each CSSStyleRule - * inside the stylesheet. - * - * Note that this method also iterates through @media rules inside the - * stylesheet. - * - * @param {function} callback the function you want to execute for each of - * the style rules. - * @param {object} scope the scope you want for the callback function. scope - * will be the this object when callback executes. - */ - forEachRule: function (callback, scope) { - let ruleCount = 0; - let domRules = this.domSheet.cssRules; - - function _iterator(domRule) { - if (domRule.type == CSSRule.STYLE_RULE) { - callback.call(scope, this.getRule(domRule)); - ruleCount++; - } else if (domRule.type == CSSRule.MEDIA_RULE && - domRule.cssRules && this._cssLogic.mediaMatches(domRule)) { - Array.prototype.forEach.call(domRule.cssRules, _iterator, this); - } - } - - Array.prototype.forEach.call(domRules, _iterator, this); - - this._ruleCount = ruleCount; - }, - - /** - * Process *some* rules in this stylesheet using your callback function. Your - * function receives one argument: the CssRule object for each CSSStyleRule - * inside the stylesheet. In order to stop processing the callback function - * needs to return a value. - * - * Note that this method also iterates through @media rules inside the - * stylesheet. - * - * @param {function} callback the function you want to execute for each of - * the style rules. - * @param {object} scope the scope you want for the callback function. scope - * will be the this object when callback executes. - * @return {Boolean} true if callback returns true during any iteration, - * otherwise false is returned. - */ - forSomeRules: function (callback, scope) { - let domRules = this.domSheet.cssRules; - function _iterator(domRule) { - if (domRule.type == CSSRule.STYLE_RULE) { - return callback.call(scope, this.getRule(domRule)); - } else if (domRule.type == CSSRule.MEDIA_RULE && - domRule.cssRules && this._cssLogic.mediaMatches(domRule)) { - return Array.prototype.some.call(domRule.cssRules, _iterator, this); - } - return false; - } - return Array.prototype.some.call(domRules, _iterator, this); - }, - - toString: function () { - return "CssSheet[" + this.shortSource + "]"; - } -}; - -/** - * Information about a single CSSStyleRule. - * - * @param {CSSSheet|null} cssSheet the CssSheet object of the stylesheet that - * holds the CSSStyleRule. If the rule comes from element.style, set this - * argument to null. - * @param {CSSStyleRule|object} domRule the DOM CSSStyleRule for which you want - * to cache data. If the rule comes from element.style, then provide - * an object of the form: {style: element.style}. - * @param {Element} [element] If the rule comes from element.style, then this - * argument must point to the element. - * @constructor - */ -function CssRule(cssSheet, domRule, element) { - this._cssSheet = cssSheet; - this.domRule = domRule; - - let parentRule = domRule.parentRule; - if (parentRule && parentRule.type == CSSRule.MEDIA_RULE) { - this.mediaText = parentRule.media.mediaText; - } - - if (this._cssSheet) { - // parse domRule.selectorText on call to this.selectors - this._selectors = null; - this.line = domUtils.getRuleLine(this.domRule); - this.source = this._cssSheet.shortSource + ":" + this.line; - if (this.mediaText) { - this.source += " @media " + this.mediaText; - } - this.href = this._cssSheet.href; - this.contentRule = this._cssSheet.contentSheet; - } else if (element) { - this._selectors = [ new CssSelector(this, "@element.style", 0) ]; - this.line = -1; - this.source = CssLogic.l10n("rule.sourceElement"); - this.href = "#"; - this.contentRule = true; - this.sourceElement = element; - } -} - -CssRule.prototype = { - _passId: null, - - mediaText: "", - - get isMediaRule() { - return !!this.mediaText; - }, - - /** - * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter. - * - * @return {boolean} true if the parent stylesheet is allowed by the current - * sourceFilter, or false otherwise. - */ - get sheetAllowed() { - return this._cssSheet ? this._cssSheet.sheetAllowed : true; - }, - - /** - * Retrieve the parent stylesheet index/position in the viewed document. - * - * @return {number} the parent stylesheet index/position in the viewed - * document. - */ - get sheetIndex() { - return this._cssSheet ? this._cssSheet.index : 0; - }, - - /** - * Retrieve the style property value from the current CSSStyleRule. - * - * @param {string} property the CSS property name for which you want the - * value. - * @return {string} the property value. - */ - getPropertyValue: function (property) { - return this.domRule.style.getPropertyValue(property); - }, - - /** - * Retrieve the style property priority from the current CSSStyleRule. - * - * @param {string} property the CSS property name for which you want the - * priority. - * @return {string} the property priority. - */ - getPropertyPriority: function (property) { - return this.domRule.style.getPropertyPriority(property); - }, - - /** - * Retrieve the list of CssSelector objects for each of the parsed selectors - * of the current CSSStyleRule. - * - * @return {array} the array hold the CssSelector objects. - */ - get selectors() { - if (this._selectors) { - return this._selectors; - } - - // Parse the CSSStyleRule.selectorText string. - this._selectors = []; - - if (!this.domRule.selectorText) { - return this._selectors; - } - - let selectors = CssLogic.getSelectors(this.domRule); - - for (let i = 0, len = selectors.length; i < len; i++) { - this._selectors.push(new CssSelector(this, selectors[i], i)); - } - - return this._selectors; - }, - - toString: function () { - return "[CssRule " + this.domRule.selectorText + "]"; - }, -}; - -/** - * The CSS selector class allows us to document the ranking of various CSS - * selectors. - * - * @constructor - * @param {CssRule} cssRule the CssRule instance from where the selector comes. - * @param {string} selector The selector that we wish to investigate. - * @param {Number} index The index of the selector within it's rule. - */ -function CssSelector(cssRule, selector, index) { - this.cssRule = cssRule; - this.text = selector; - this.elementStyle = this.text == "@element.style"; - this._specificity = null; - this.selectorIndex = index; } -exports.CssSelector = CssSelector; - -CssSelector.prototype = { - _matchId: null, - - /** - * Retrieve the CssSelector source, which is the source of the CssSheet owning - * the selector. - * - * @return {string} the selector source. - */ - get source() { - return this.cssRule.source; - }, - - /** - * Retrieve the CssSelector source element, which is the source of the CssRule - * owning the selector. This is only available when the CssSelector comes from - * an element.style. - * - * @return {string} the source element selector. - */ - get sourceElement() { - return this.cssRule.sourceElement; - }, - - /** - * Retrieve the address of the CssSelector. This points to the address of the - * CssSheet owning this selector. - * - * @return {string} the address of the CssSelector. - */ - get href() { - return this.cssRule.href; - }, - - /** - * Check if the selector comes from a browser-provided stylesheet. - * - * @return {boolean} true if the selector comes from a content-provided - * stylesheet, or false otherwise. - */ - get contentRule() { - return this.cssRule.contentRule; - }, - - /** - * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter. - * - * @return {boolean} true if the parent stylesheet is allowed by the current - * sourceFilter, or false otherwise. - */ - get sheetAllowed() { - return this.cssRule.sheetAllowed; - }, - - /** - * Retrieve the parent stylesheet index/position in the viewed document. - * - * @return {number} the parent stylesheet index/position in the viewed - * document. - */ - get sheetIndex() { - return this.cssRule.sheetIndex; - }, - - /** - * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet. - * - * @return {number} the line of the parent CSSStyleRule in the parent - * stylesheet. - */ - get ruleLine() { - return this.cssRule.line; - }, - - /** - * Retrieve specificity information for the current selector. - * - * @see http://www.w3.org/TR/css3-selectors/#specificity - * @see http://www.w3.org/TR/CSS2/selector.html - * - * @return {Number} The selector's specificity. - */ - get specificity() { - if (this.elementStyle) { - // We can't ask specificity from DOMUtils as element styles don't provide - // CSSStyleRule interface DOMUtils expect. However, specificity of element - // style is constant, 1,0,0,0 or 0x01000000, just return the constant - // directly. @see http://www.w3.org/TR/CSS2/cascade.html#specificity - return 0x01000000; - } - - if (this._specificity) { - return this._specificity; - } - - this._specificity = domUtils.getSpecificity(this.cssRule.domRule, - this.selectorIndex); - - return this._specificity; - }, - - toString: function () { - return this.text; - }, -}; - -/** - * A cache of information about the matched rules, selectors and values attached - * to a CSS property, for the highlighted element. - * - * The heart of the CssPropertyInfo object is the _findMatchedSelectors() - * method. This are invoked when the PropertyView tries to access the - * .matchedSelectors array. - * Results are cached, for later reuse. - * - * @param {CssLogic} cssLogic Reference to the parent CssLogic instance - * @param {string} property The CSS property we are gathering information for - * @param {function} isInherited A function that determines if the CSS property - * is inherited. - * @constructor - */ -function CssPropertyInfo(cssLogic, property, isInherited) { - this._cssLogic = cssLogic; - this.property = property; - this._value = ""; - this._isInherited = isInherited; - - // The number of matched rules holding the this.property style property. - // Additionally, only rules that come from allowed stylesheets are counted. - this._matchedRuleCount = 0; - - // An array holding CssSelectorInfo objects for each of the matched selectors - // that are inside a CSS rule. Only rules that hold the this.property are - // counted. This includes rules that come from filtered stylesheets (those - // that have sheetAllowed = false). - this._matchedSelectors = null; -} - -CssPropertyInfo.prototype = { - /** - * Retrieve the computed style value for the current property, for the - * highlighted element. - * - * @return {string} the computed style value for the current property, for the - * highlighted element. - */ - get value() { - if (!this._value && this._cssLogic.computedStyle) { - try { - this._value = - this._cssLogic.computedStyle.getPropertyValue(this.property); - } catch (ex) { - console.log("Error reading computed style for " + this.property); - console.log(ex); - } - } - return this._value; - }, - - /** - * Retrieve the number of matched rules holding the this.property style - * property. Only rules that come from allowed stylesheets are counted. - * - * @return {number} the number of matched rules. - */ - get matchedRuleCount() { - if (!this._matchedSelectors) { - this._findMatchedSelectors(); - } else if (this.needRefilter) { - this._refilterSelectors(); - } - - return this._matchedRuleCount; - }, - - /** - * Retrieve the array holding CssSelectorInfo objects for each of the matched - * selectors, from each of the matched rules. Only selectors coming from - * allowed stylesheets are included in the array. - * - * @return {array} the list of CssSelectorInfo objects of selectors that match - * the highlighted element and its parents. - */ - get matchedSelectors() { - if (!this._matchedSelectors) { - this._findMatchedSelectors(); - } else if (this.needRefilter) { - this._refilterSelectors(); - } - - return this._matchedSelectors; - }, - - /** - * Find the selectors that match the highlighted element and its parents. - * Uses CssLogic.processMatchedSelectors() to find the matched selectors, - * passing in a reference to CssPropertyInfo._processMatchedSelector() to - * create CssSelectorInfo objects, which we then sort - * @private - */ - _findMatchedSelectors: function () { - this._matchedSelectors = []; - this._matchedRuleCount = 0; - this.needRefilter = false; - - this._cssLogic.processMatchedSelectors(this._processMatchedSelector, this); - - // Sort the selectors by how well they match the given element. - this._matchedSelectors.sort(function (selectorInfo1, selectorInfo2) { - if (selectorInfo1.status > selectorInfo2.status) { - return -1; - } else if (selectorInfo2.status > selectorInfo1.status) { - return 1; - } - return selectorInfo1.compareTo(selectorInfo2); - }); - - // Now we know which of the matches is best, we can mark it BEST_MATCH. - if (this._matchedSelectors.length > 0 && - this._matchedSelectors[0].status > CssLogic.STATUS.UNMATCHED) { - this._matchedSelectors[0].status = CssLogic.STATUS.BEST; - } - }, - - /** - * Process a matched CssSelector object. - * - * @private - * @param {CssSelector} selector the matched CssSelector object. - * @param {CssLogic.STATUS} status the CssSelector match status. - */ - _processMatchedSelector: function (selector, status) { - let cssRule = selector.cssRule; - let value = cssRule.getPropertyValue(this.property); - if (value && - (status == CssLogic.STATUS.MATCHED || - (status == CssLogic.STATUS.PARENT_MATCH && - this._isInherited(this.property)))) { - let selectorInfo = new CssSelectorInfo(selector, this.property, value, - status); - this._matchedSelectors.push(selectorInfo); - if (this._cssLogic._passId !== cssRule._passId && cssRule.sheetAllowed) { - this._matchedRuleCount++; - } - } - }, - - /** - * Refilter the matched selectors array when the CssLogic.sourceFilter - * changes. This allows for quick filter changes. - * @private - */ - _refilterSelectors: function () { - let passId = ++this._cssLogic._passId; - let ruleCount = 0; - - let iterator = function (selectorInfo) { - let cssRule = selectorInfo.selector.cssRule; - if (cssRule._passId != passId) { - if (cssRule.sheetAllowed) { - ruleCount++; - } - cssRule._passId = passId; - } - }; - - if (this._matchedSelectors) { - this._matchedSelectors.forEach(iterator); - this._matchedRuleCount = ruleCount; - } - - this.needRefilter = false; - }, - - toString: function () { - return "CssPropertyInfo[" + this.property + "]"; - }, -}; - -/** - * A class that holds information about a given CssSelector object. - * - * Instances of this class are given to CssHtmlTree in the array of matched - * selectors. Each such object represents a displayable row in the PropertyView - * objects. The information given by this object blends data coming from the - * CssSheet, CssRule and from the CssSelector that own this object. - * - * @param {CssSelector} selector The CssSelector object for which to - * present information. - * @param {string} property The property for which information should - * be retrieved. - * @param {string} value The property value from the CssRule that owns - * the selector. - * @param {CssLogic.STATUS} status The selector match status. - * @constructor - */ -function CssSelectorInfo(selector, property, value, status) { - this.selector = selector; - this.property = property; - this.status = status; - this.value = value; - let priority = this.selector.cssRule.getPropertyPriority(this.property); - this.important = (priority === "important"); -} - -CssSelectorInfo.prototype = { - /** - * Retrieve the CssSelector source, which is the source of the CssSheet owning - * the selector. - * - * @return {string} the selector source. - */ - get source() { - return this.selector.source; - }, - - /** - * Retrieve the CssSelector source element, which is the source of the CssRule - * owning the selector. This is only available when the CssSelector comes from - * an element.style. - * - * @return {string} the source element selector. - */ - get sourceElement() { - return this.selector.sourceElement; - }, - - /** - * Retrieve the address of the CssSelector. This points to the address of the - * CssSheet owning this selector. - * - * @return {string} the address of the CssSelector. - */ - get href() { - return this.selector.href; - }, - - /** - * Check if the CssSelector comes from element.style or not. - * - * @return {boolean} true if the CssSelector comes from element.style, or - * false otherwise. - */ - get elementStyle() { - return this.selector.elementStyle; - }, - - /** - * Retrieve specificity information for the current selector. - * - * @return {object} an object holding specificity information for the current - * selector. - */ - get specificity() { - return this.selector.specificity; - }, - - /** - * Retrieve the parent stylesheet index/position in the viewed document. - * - * @return {number} the parent stylesheet index/position in the viewed - * document. - */ - get sheetIndex() { - return this.selector.sheetIndex; - }, - - /** - * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter. - * - * @return {boolean} true if the parent stylesheet is allowed by the current - * sourceFilter, or false otherwise. - */ - get sheetAllowed() { - return this.selector.sheetAllowed; - }, - - /** - * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet. - * - * @return {number} the line of the parent CSSStyleRule in the parent - * stylesheet. - */ - get ruleLine() { - return this.selector.ruleLine; - }, - - /** - * Check if the selector comes from a browser-provided stylesheet. - * - * @return {boolean} true if the selector comes from a browser-provided - * stylesheet, or false otherwise. - */ - get contentRule() { - return this.selector.contentRule; - }, - - /** - * Compare the current CssSelectorInfo instance to another instance, based on - * specificity information. - * - * @param {CssSelectorInfo} that The instance to compare ourselves against. - * @return number -1, 0, 1 depending on how that compares with this. - */ - compareTo: function (that) { - if (!this.contentRule && that.contentRule) { - return 1; - } - if (this.contentRule && !that.contentRule) { - return -1; - } - - if (this.elementStyle && !that.elementStyle) { - if (!this.important && that.important) { - return 1; - } - return -1; - } - - if (!this.elementStyle && that.elementStyle) { - if (this.important && !that.important) { - return -1; - } - return 1; - } - - if (this.important && !that.important) { - return -1; - } - if (that.important && !this.important) { - return 1; - } - - if (this.specificity > that.specificity) { - return -1; - } - if (that.specificity > this.specificity) { - return 1; - } - - if (this.sheetIndex > that.sheetIndex) { - return -1; - } - if (that.sheetIndex > this.sheetIndex) { - return 1; - } - - if (this.ruleLine > that.ruleLine) { - return -1; - } - if (that.ruleLine > this.ruleLine) { - return 1; - } - - return 0; - }, - - toString: function () { - return this.selector + " -> " + this.value; - }, -}; - -DevToolsUtils.defineLazyGetter(this, "domUtils", function () { - return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils); -}); +exports.prettifyCSS = prettifyCSS;
--- a/devtools/shared/protocol.js +++ b/devtools/shared/protocol.js @@ -929,17 +929,16 @@ exports.Actor = Actor; * * @param function fn * The implementation function, will be returned. * @param spec * The method specification, with the following (optional) properties: * request (object): a request template. * response (object): a response template. * oneway (bool): 'true' if no response should be sent. - * telemetry (string): Telemetry probe ID for measuring completion time. */ exports.method = function (fn, spec = {}) { fn._methodSpec = Object.freeze(spec); if (spec.request) Object.freeze(spec.request); if (spec.response) Object.freeze(spec.response); return fn; }; @@ -971,34 +970,32 @@ var generateActorSpec = function (actorD } if (desc.value._methodSpec) { let methodSpec = desc.value._methodSpec; let spec = {}; spec.name = methodSpec.name || name; spec.request = Request(object.merge({type: spec.name}, methodSpec.request || undefined)); spec.response = Response(methodSpec.response || undefined); - spec.telemetry = methodSpec.telemetry; spec.release = methodSpec.release; spec.oneway = methodSpec.oneway; actorSpec.methods.push(spec); } } // Find additional method specifications if (actorDesc.methods) { for (let name in actorDesc.methods) { let methodSpec = actorDesc.methods[name]; let spec = {}; spec.name = methodSpec.name || name; spec.request = Request(object.merge({type: spec.name}, methodSpec.request || undefined)); spec.response = Response(methodSpec.response || undefined); - spec.telemetry = methodSpec.telemetry; spec.release = methodSpec.release; spec.oneway = methodSpec.oneway; actorSpec.methods.push(spec); } } // Find event specifications @@ -1341,37 +1338,16 @@ var generateRequestMethods = function (a // If the user doesn't need the impl don't generate it. if (!custom.impl) { return; } name = custom.impl; } frontProto[name] = function (...args) { - let histogram, startTime; - if (spec.telemetry) { - if (spec.oneway) { - // That just doesn't make sense. - throw Error("Telemetry specified for a oneway request"); - } - let transportType = this.conn.localTransport - ? "LOCAL_" - : "REMOTE_"; - let histogramId = "DEVTOOLS_DEBUGGER_RDP_" - + transportType + spec.telemetry + "_MS"; - try { - histogram = Services.telemetry.getHistogramById(histogramId); - startTime = new Date(); - } catch (ex) { - // XXX: Is this expected in xpcshell tests? - console.error(ex); - spec.telemetry = false; - } - } - let packet; try { packet = spec.request.write(args, this); } catch (ex) { console.error("Error writing request: " + name); throw ex; } if (spec.oneway) { @@ -1383,21 +1359,16 @@ var generateRequestMethods = function (a return this.request(packet).then(response => { let ret; try { ret = spec.response.read(response, this); } catch (ex) { console.error("Error reading response to: " + name); throw ex; } - - if (histogram) { - histogram.add(+new Date - startTime); - } - return ret; }); }; // Release methods should call the destroy function on return. if (spec.release) { let fn = frontProto[name]; frontProto[name] = function (...args) {
--- a/devtools/shared/specs/addons.js +++ b/devtools/shared/specs/addons.js @@ -7,14 +7,13 @@ const {Arg, RetVal, generateActorSpec} = const addonsSpec = generateActorSpec({ typeName: "addons", methods: { installTemporaryAddon: { request: { addonPath: Arg(0, "string") }, response: { addon: RetVal("json") }, - telemetry: "INSTALL_TEMPORARY_ADDON" }, }, }); exports.addonsSpec = addonsSpec;
--- a/devtools/shared/tests/unit/test_prettifyCSS.js +++ b/devtools/shared/tests/unit/test_prettifyCSS.js @@ -1,16 +1,16 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ // Test prettifyCSS. "use strict"; -const {CssLogic} = require("devtools/shared/inspector/css-logic"); +const {prettifyCSS} = require("devtools/shared/inspector/css-logic"); const TESTS = [ { name: "simple test", input: "div { font-family:'Arial Black', Arial, sans-serif; }", expected: [ "div {", "\tfont-family:'Arial Black', Arial, sans-serif;", "}" @@ -47,22 +47,22 @@ const TESTS = [ "div {", "\tcolor: red;", "}" ] }, ]; function run_test() { - // Note that CssLogic.LINE_SEPARATOR is computed lazily, so we + // Note that prettifyCSS.LINE_SEPARATOR is computed lazily, so we // ensure it is set. - CssLogic.prettifyCSS(""); + prettifyCSS(""); for (let test of TESTS) { do_print(test.name); - let input = test.input.split("\n").join(CssLogic.LINE_SEPARATOR); - let output = CssLogic.prettifyCSS(input); - let expected = test.expected.join(CssLogic.LINE_SEPARATOR) + - CssLogic.LINE_SEPARATOR; + let input = test.input.split("\n").join(prettifyCSS.LINE_SEPARATOR); + let output = prettifyCSS(input); + let expected = test.expected.join(prettifyCSS.LINE_SEPARATOR) + + prettifyCSS.LINE_SEPARATOR; equal(output, expected, test.name); } }
--- a/intl/hyphenation/glue/nsHyphenationManager.cpp +++ b/intl/hyphenation/glue/nsHyphenationManager.cpp @@ -14,16 +14,18 @@ #include "nsDirectoryServiceDefs.h" #include "nsNetUtil.h" #include "nsUnicharUtils.h" #include "mozilla/Preferences.h" #include "nsZipArchive.h" #include "mozilla/Services.h" #include "nsIObserverService.h" #include "nsCRT.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" using namespace mozilla; static const char kIntlHyphenationAliasPrefix[] = "intl.hyphenation-alias."; static const char kMemoryPressureNotification[] = "memory-pressure"; nsHyphenationManager *nsHyphenationManager::sInstance = nullptr; @@ -163,16 +165,24 @@ nsHyphenationManager::LoadPatternList() NS_GET_IID(nsIFile), getter_AddRefs(appDir)); if (NS_SUCCEEDED(rv)) { appDir->AppendNative(NS_LITERAL_CSTRING("hyphenation")); bool equals; if (NS_SUCCEEDED(appDir->Equals(greDir, &equals)) && !equals) { LoadPatternListFromDir(appDir); } } + + nsCOMPtr<nsIFile> profileDir; + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, + getter_AddRefs(profileDir)); + if (NS_SUCCEEDED(rv)) { + profileDir->AppendNative(NS_LITERAL_CSTRING("hyphenation")); + LoadPatternListFromDir(profileDir); + } } void nsHyphenationManager::LoadPatternListFromOmnijar(Omnijar::Type aType) { nsCString base; nsresult rv = Omnijar::GetURIString(aType, base); if (NS_FAILED(rv)) {
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java +++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java @@ -756,16 +756,19 @@ public final class BrowserDatabaseHelper } } /** * We used to have a separate history extensions database which was used by Sync to store arrays * of visits for individual History GUIDs. It was only used by Sync. * This function migrates contents of that database over to the Visits table. * + * Warning to callers: this method might throw IllegalStateException if we fail to allocate a + * cursor to read HistoryExtensionsDB data for whatever reason. See Bug 1280409. + * * @param historyExtensionDb Source History Extensions database * @param db Destination database */ private void copyHistoryExtensionDataToVisitsTable(final SQLiteDatabase historyExtensionDb, final SQLiteDatabase db) { final String historyExtensionTable = "HistoryExtension"; final String columnGuid = "guid"; final String columnVisits = "visits"; @@ -1767,24 +1770,30 @@ public final class BrowserDatabaseHelper // Otherwise, we risk overwhelming their Top Sites with remote history, just as we did before this migration. try { // If FxAccount exists (Sync is enabled) then port data over to the Visits table. if (FirefoxAccounts.firefoxAccountsExist(mContext)) { try { historyExtensionDb = SQLiteDatabase.openDatabase(historyExtensionsDatabase.getPath(), null, SQLiteDatabase.OPEN_READONLY); + if (historyExtensionDb != null) { + copyHistoryExtensionDataToVisitsTable(historyExtensionDb, db); + } + // If we fail to open HistoryExtensionDatabase, then synthesize visits marking them as remote } catch (SQLiteException e) { Log.w(LOGTAG, "Couldn't open history extension database; synthesizing visits instead", e); synthesizeAndInsertVisits(db, false); - } - if (historyExtensionDb != null) { - copyHistoryExtensionDataToVisitsTable(historyExtensionDb, db); + // It's possible that we might fail to copy over visit data from the HistoryExtensionsDB, + // so let's synthesize visits marking them as remote. See Bug 1280409. + } catch (IllegalStateException e) { + Log.w(LOGTAG, "Couldn't copy over history extension data; synthesizing visits instead", e); + synthesizeAndInsertVisits(db, false); } // FxAccount doesn't exist, but there's evidence Sync was enabled at some point. // Synthesize visits from History table marking them all as remote. } else if (historyExtensionsDatabase.exists()) { synthesizeAndInsertVisits(db, false); // FxAccount doesn't exist and there's no evidence sync was ever enabled.
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java +++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java @@ -795,26 +795,28 @@ public class BrowserProvider extends Sha " LEFT OUTER JOIN " + " (SELECT position FROM numbers WHERE position NOT IN (SELECT " + Bookmarks.POSITION + " " + pinnedSitesFromClause + ")) AS free_ids" + " ON numbers.position > free_ids.position" + " GROUP BY numbers.position" + " ORDER BY numbers.position ASC" + " LIMIT " + suggestedGridLimit; // Filter out: unvisited pages (history_id == -1) pinned (and other special) sites, deleted sites, - // and about: pages. + // pages which weren't visited locally, and about: pages. final String ignoreForTopSitesWhereClause = "(" + Combined.HISTORY_ID + " IS NOT -1)" + " AND " + Combined.URL + " NOT IN (SELECT " + Bookmarks.URL + " FROM bookmarks WHERE " + DBUtils.qualifyColumn("bookmarks", Bookmarks.PARENT) + " < " + Bookmarks.FIXED_ROOT_ID + " AND " + DBUtils.qualifyColumn("bookmarks", Bookmarks.IS_DELETED) + " == 0)" + " AND " + - "(" + Combined.URL + " NOT LIKE ?)"; + "(" + Combined.URL + " NOT LIKE ?)" + + " AND " + + "(" + Combined.LOCAL_VISITS_COUNT + " > 0)"; final String[] ignoreForTopSitesArgs = new String[] { AboutPages.URL_FILTER }; // Stuff the suggested sites into SQL: this allows us to filter pinned and topsites out of the suggested // sites list as part of the final query (as opposed to walking cursors in java) final SuggestedSites suggestedSites = GeckoProfile.get(getContext(), uri.getQueryParameter(BrowserContract.PARAM_PROFILE)).getDB().getSuggestedSites();
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java +++ b/mobile/android/base/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java @@ -8,18 +8,18 @@ package org.mozilla.gecko.gfx; import org.mozilla.gecko.AppConstants; import org.mozilla.gecko.PrefsHelper; import org.mozilla.gecko.util.FloatUtils; import org.mozilla.gecko.util.ThreadUtils; import android.graphics.PointF; import android.support.v4.view.ViewCompat; import android.util.Log; -import android.view.animation.DecelerateInterpolator; import android.view.MotionEvent; +import android.view.animation.LinearInterpolator; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Set; public class DynamicToolbarAnimator { @@ -31,17 +31,17 @@ public class DynamicToolbarAnimator { ACTION_MODE, FULL_SCREEN, CARET_DRAG } private final Set<PinReason> pinFlags = Collections.synchronizedSet(EnumSet.noneOf(PinReason.class)); // The duration of the animation in ns - private static final long ANIMATION_DURATION = 250000000; + private static final long ANIMATION_DURATION = 150000000; private final GeckoLayerClient mTarget; private final List<LayerView.DynamicToolbarListener> mListeners; /* The translation to be applied to the toolbar UI view. This is the * distance from the default/initial location (at the top of the screen, * visible to the user) to where we want it to be. This variable should * always be between 0 (toolbar fully visible) and the height of the toolbar @@ -58,17 +58,17 @@ public class DynamicToolbarAnimator { private float mLayerViewTranslation; /* This stores the maximum translation that can be applied to the toolbar * and layerview when scrolling. This is populated with the height of the * toolbar. */ private float mMaxTranslation; /* This interpolator is used for the above mentioned animation */ - private DecelerateInterpolator mInterpolator; + private LinearInterpolator mInterpolator; /* This is the proportion of the viewport rect that needs to be travelled * while scrolling before the translation will start taking effect. */ private float SCROLL_TOOLBAR_THRESHOLD = 0.20f; /* The ID of the prefs listener for the scroll-toolbar threshold */ private final PrefsHelper.PrefHandler mPrefObserver; @@ -97,17 +97,17 @@ public class DynamicToolbarAnimator { /* Set to true when root content is being scrolled */ private boolean mScrollingRootContent; public DynamicToolbarAnimator(GeckoLayerClient aTarget) { mTarget = aTarget; mListeners = new ArrayList<LayerView.DynamicToolbarListener>(); - mInterpolator = new DecelerateInterpolator(); + mInterpolator = new LinearInterpolator(); // Listen to the dynamic toolbar pref mPrefObserver = new PrefsHelper.PrefHandlerBase() { @Override public void prefValue(String pref, int value) { SCROLL_TOOLBAR_THRESHOLD = value / 100.0f; } };
--- a/mobile/android/components/NSSDialogService.js +++ b/mobile/android/components/NSSDialogService.js @@ -11,38 +11,54 @@ Cu.import("resource://gre/modules/Servic XPCOMUtils.defineLazyModuleGetter(this, "Prompt", "resource://gre/modules/Prompt.jsm"); // ----------------------------------------------------------------------- // NSS Dialog Service // ----------------------------------------------------------------------- -function dump(a) { - Components.classes["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a); -} - function NSSDialogs() { } NSSDialogs.prototype = { classID: Components.ID("{cbc08081-49b6-4561-9c18-a7707a50bda1}"), QueryInterface: XPCOMUtils.generateQI([Ci.nsICertificateDialogs, Ci.nsIClientAuthDialogs]), + /** + * Escapes the given input via HTML entity encoding. Used to prevent HTML + * injection when the input is to be placed inside an HTML body, but not in + * any other context. + * + * @param {String} input The input to interpret as a plain string. + * @returns {String} The escaped input. + */ + escapeHTML: function(input) { + return input.replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, """) + .replace(/'/g, "'") + .replace(/\//g, "/"); + }, + getString: function(aName) { if (!this.bundle) { this.bundle = Services.strings.createBundle("chrome://browser/locale/pippki.properties"); } return this.bundle.GetStringFromName(aName); }, formatString: function(aName, argList) { if (!this.bundle) { - this.bundle = Services.strings.createBundle("chrome://browser/locale/pippki.properties"); + this.bundle = + Services.strings.createBundle("chrome://browser/locale/pippki.properties"); } - return this.bundle.formatStringFromName(aName, argList, argList.length); + let escapedArgList = Array.from(argList, x => this.escapeHTML(x)); + return this.bundle.formatStringFromName(aName, escapedArgList, + escapedArgList.length); }, getPrompt: function(aTitle, aText, aButtons) { return new Prompt({ title: aTitle, text: aText, buttons: aButtons, }); @@ -109,42 +125,53 @@ NSSDialogs.prototype = { return false; } aPassword.value = response.pw; return true; }, certInfoSection: function(aHeading, aDataPairs, aTrailingNewline = true) { - var str = "<big>" + this.getString(aHeading) + "</big><br/>"; - for (var i = 0; i < aDataPairs.length; i += 2) { - str += this.getString(aDataPairs[i]) + ": " + aDataPairs[i+1] + "<br/>"; + let certInfoStrings = [ + "<big>" + this.getString(aHeading) + "</big>", + ]; + + for (let i = 0; i < aDataPairs.length; i += 2) { + let key = aDataPairs[i]; + let value = aDataPairs[i + 1]; + certInfoStrings.push(this.formatString(key, [value])); } - return str + (aTrailingNewline ? "<br/>" : ""); + + if (aTrailingNewline) { + certInfoStrings.push("<br/>"); + } + + return certInfoStrings.join("<br/>"); }, viewCert: function(aCtx, aCert) { - let p = this.getPrompt(this.getString("certmgr.title"), - "", - [ this.getString("nssdialogs.ok.label") ]) + let p = this.getPrompt(this.getString("certmgr.title"), "", [ + this.getString("nssdialogs.ok.label"), + ]); p.addLabel({ label: this.certInfoSection("certmgr.subjectinfo.label", - ["certmgr.certdetail.cn", aCert.commonName, - "certmgr.certdetail.o", aCert.organization, - "certmgr.certdetail.ou", aCert.organizationalUnit, - "certmgr.certdetail.serialnumber", aCert.serialNumber])}) + ["certdetail.cn", aCert.commonName, + "certdetail.o", aCert.organization, + "certdetail.ou", aCert.organizationalUnit, + "certdetail.serialnumber", aCert.serialNumber])}) .addLabel({ label: this.certInfoSection("certmgr.issuerinfo.label", - ["certmgr.certdetail.cn", aCert.issuerCommonName, - "certmgr.certdetail.o", aCert.issuerOrganization, - "certmgr.certdetail.ou", aCert.issuerOrganizationUnit])}) + ["certdetail.cn", aCert.issuerCommonName, + "certdetail.o", aCert.issuerOrganization, + "certdetail.ou", aCert.issuerOrganizationUnit])}) .addLabel({ label: this.certInfoSection("certmgr.periodofvalidity.label", - ["certmgr.begins", aCert.validity.notBeforeLocalDay, - "certmgr.expires", aCert.validity.notAfterLocalDay])}) + ["certdetail.notBefore", aCert.validity.notBeforeLocalDay, + "certdetail.notAfter", aCert.validity.notAfterLocalDay])}) .addLabel({ label: this.certInfoSection("certmgr.fingerprints.label", - ["certmgr.certdetail.sha256fingerprint", aCert.sha256Fingerprint, - "certmgr.certdetail.sha1fingerprint", aCert.sha1Fingerprint], false) }); + ["certdetail.sha256fingerprint", aCert.sha256Fingerprint, + "certdetail.sha1fingerprint", aCert.sha1Fingerprint], + false) }); this.showPrompt(p); }, /** * Returns a list of details of the given cert relevant for TLS client * authentication. * * @param {nsIX509Cert} cert Cert to get the details of. @@ -186,17 +213,17 @@ NSSDialogs.prototype = { }, chooseCertificate: function(ctx, cnAndPort, organization, issuerOrg, certList, selectedIndex) { let rememberSetting = Services.prefs.getBoolPref("security.remember_cert_checkbox_default_setting"); let serverRequestedDetails = [ - cnAndPort, + this.escapeHTML(cnAndPort), this.formatString("clientAuthAsk.organization", [organization]), this.formatString("clientAuthAsk.issuer", [issuerOrg]), ].join("<br/>"); let certNickList = []; let certDetailsList = []; for (let i = 0; i < certList.length; i++) { let cert = certList.queryElementAt(i, Ci.nsIX509Cert);
--- a/mobile/android/components/extensions/ext-pageAction.js +++ b/mobile/android/components/extensions/ext-pageAction.js @@ -10,58 +10,82 @@ XPCOMUtils.defineLazyModuleGetter(this, // Import the android PageActions module. XPCOMUtils.defineLazyModuleGetter(this, "PageActions", "resource://gre/modules/PageActions.jsm"); Cu.import("resource://gre/modules/ExtensionUtils.jsm"); var { + IconDetails, SingletonEventManager, } = ExtensionUtils; // WeakMap[Extension -> PageAction] var pageActionMap = new WeakMap(); function PageAction(options, extension) { this.id = null; - let DEFAULT_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC"; + this.extension = extension; + this.icons = IconDetails.normalize({path: options.default_icon}, extension); this.popupUrl = options.default_popup; this.options = { title: options.default_title || extension.name, - icon: DEFAULT_ICON, id: extension.id, clickCallback: () => { if (this.popupUrl) { let win = Services.wm.getMostRecentWindow("navigator:browser"); win.BrowserApp.addTab(this.popupUrl, { selected: true, parentId: win.BrowserApp.selectedTab.id, }); } else { this.emit("click"); } }, }; + this.shouldShow = false; + EventEmitter.decorate(this); } PageAction.prototype = { - show(tabId) { - // TODO: Only show the PageAction for the tab with the provided tabId. - if (!this.id) { + show(tabId, context) { + if (this.id) { + return Promise.resolve(); + } + + if (this.options.icon) { this.id = PageActions.add(this.options); + return Promise.resolve(); } + + this.shouldShow = true; + + let {icon} = IconDetails.getURL(this.icons, context.contentWindow, this.extension, 18); + + let browserWindow = Services.wm.getMostRecentWindow("navigator:browser"); + return IconDetails.convertImageURLToDataURL(icon, context, browserWindow).then(dataURI => { + if (this.shouldShow) { + this.options.icon = dataURI; + this.id = PageActions.add(this.options); + } + }).catch(() => { + return Promise.reject({ + message: "Failed to load PageAction icon", + }); + }); }, hide(tabId) { + this.shouldShow = false; if (this.id) { PageActions.remove(this.id); this.id = null; } }, setPopup(tab, url) { // TODO: Only set the popup for the specified tab once we have Tabs API support. @@ -101,21 +125,24 @@ extensions.registerSchemaAPI("pageAction }; pageActionMap.get(extension).on("click", listener); return () => { pageActionMap.get(extension).off("click", listener); }; }).api(), show(tabId) { - pageActionMap.get(extension).show(tabId); + return pageActionMap.get(extension) + .show(tabId, context) + .then(() => {}); }, hide(tabId) { pageActionMap.get(extension).hide(tabId); + return Promise.resolve(); }, setPopup(details) { // TODO: Use the Tabs API to get the tab from details.tabId. let tab = null; let url = details.popup && context.uri.resolve(details.popup); pageActionMap.get(extension).setPopup(tab, url); },
--- a/mobile/android/components/extensions/schemas/page_action.json +++ b/mobile/android/components/extensions/schemas/page_action.json @@ -14,17 +14,16 @@ "additionalProperties": { "$ref": "UnrecognizedProperty" }, "properties": { "default_title": { "type": "string", "optional": true, "preprocess": "localize" }, "default_icon": { - "unsupported": true, "$ref": "IconPath", "optional": true }, "default_popup": { "type": "string", "format": "relativeUrl", "optional": true, "preprocess": "localize" @@ -52,24 +51,26 @@ "description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)." } ], "functions": [ { "name": "show", "type": "function", "description": "Shows the page action. The page action is shown whenever the tab is selected.", + "async": true, "parameters": [ {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."} ] }, { "name": "hide", "type": "function", "description": "Hides the page action.", + "async": true, "parameters": [ {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."} ] }, { "name": "setTitle", "unsupported": true, "type": "function", @@ -156,16 +157,17 @@ "optional": true, "parameters": [] } ] }, { "name": "setPopup", "type": "function", + "async": true, "description": "Sets the html document to be opened as a popup when the user clicks on the page action's icon.", "parameters": [ { "name": "details", "type": "object", "properties": { "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."}, "popup": { @@ -175,37 +177,27 @@ } } ] }, { "name": "getPopup", "type": "function", "description": "Gets the html document set as the popup for this page action.", - "async": "callback", + "async": true, "parameters": [ { "name": "details", "type": "object", "properties": { "tabId": { "type": "integer", "description": "Specify the tab to get the popup from." } } - }, - { - "type": "function", - "name": "callback", - "parameters": [ - { - "name": "result", - "type": "string" - } - ] } ] } ], "events": [ { "name": "onClicked", "type": "function",
--- a/mobile/android/components/extensions/test/mochitest/test_ext_pageAction.html +++ b/mobile/android/components/extensions/test/mochitest/test_ext_pageAction.html @@ -8,29 +8,36 @@ <script type="text/javascript" src="head.js"></script> <link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/> </head> <body> <script type="text/javascript"> "use strict"; +let dataURI = "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC"; + +let image = atob(dataURI); +const IMAGE_ARRAYBUFFER = Uint8Array.from(image, byte => byte.charCodeAt(0)).buffer; + function backgroundScript() { browser.test.assertTrue("pageAction" in browser, "Namespace 'pageAction' exists in browser"); browser.test.assertTrue("show" in browser.pageAction, "API method 'show' exists in browser.pageAction"); // TODO: Use the Tabs API to obtain the tab ids for showing pageActions. let tabId = 1; browser.test.onMessage.addListener(msg => { if (msg === "pageAction-show") { - browser.pageAction.show(tabId); - browser.test.sendMessage("page-action-shown"); + browser.pageAction.show(tabId).then(() => { + browser.test.sendMessage("page-action-shown"); + }); } else if (msg === "pageAction-hide") { - browser.pageAction.hide(tabId); - browser.test.sendMessage("page-action-hidden"); + browser.pageAction.hide(tabId).then(() => { + browser.test.sendMessage("page-action-hidden"); + }); } }); browser.pageAction.onClicked.addListener(tab => { // TODO: Make sure we get the correct tab once basic tabs support is added. browser.test.sendMessage("page-action-clicked"); }); @@ -39,18 +46,24 @@ function backgroundScript() { add_task(function* test_contentscript() { let extension = ExtensionTestUtils.loadExtension({ background: "(" + backgroundScript.toString() + ")()", manifest: { "name": "PageAction Extension", "page_action": { "default_title": "Page Action", + "default_icon": { + "18": "extension.png", + }, }, }, + files: { + "extension.png": IMAGE_ARRAYBUFFER, + }, }); yield extension.startup(); yield extension.awaitMessage("ready"); extension.sendMessage("pageAction-show"); yield extension.awaitMessage("page-action-shown"); ok(isPageActionShown(extension.id), "The PageAction should be shown");
--- a/mobile/android/components/extensions/test/mochitest/test_ext_pageAction_popup.html +++ b/mobile/android/components/extensions/test/mochitest/test_ext_pageAction_popup.html @@ -10,30 +10,37 @@ </head> <body> <script type="text/javascript"> "use strict"; Cu.import("resource://gre/modules/Services.jsm"); +let dataURI = "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC"; + +let image = atob(dataURI); +const IMAGE_ARRAYBUFFER = Uint8Array.from(image, byte => byte.charCodeAt(0)).buffer; + add_task(function* test_contentscript() { function backgroundScript() { // TODO: Use the Tabs API to obtain the tab ids for showing pageActions. let tabId = 1; let onClickedListenerEnabled = false; browser.test.onMessage.addListener((msg, details) => { if (msg === "page-action-show") { // TODO: switch to using .show(tabId).then(...) once bug 1270742 lands. - browser.pageAction.show(tabId); - browser.test.sendMessage("page-action-shown"); + browser.pageAction.show(tabId).then(() => { + browser.test.sendMessage("page-action-shown"); + }); } else if (msg == "page-action-set-popup") { - browser.pageAction.setPopup({popup: details.name, tabId: tabId}); - browser.test.sendMessage("page-action-popup-set"); + browser.pageAction.setPopup({popup: details.name, tabId: tabId}).then(() => { + browser.test.sendMessage("page-action-popup-set"); + }); } else if (msg == "page-action-get-popup") { browser.pageAction.getPopup({tabId: tabId}).then(url => { browser.test.sendMessage("page-action-got-popup", url); }); } else if (msg == "page-action-enable-onClicked-listener") { onClickedListenerEnabled = true; browser.test.sendMessage("page-action-onClicked-listener-enabled"); } else if (msg == "page-action-disable-onClicked-listener") { @@ -65,20 +72,24 @@ add_task(function* test_contentscript() let extension = ExtensionTestUtils.loadExtension({ background: `(${backgroundScript}())`, manifest: { "name": "PageAction Extension", "page_action": { "default_title": "Page Action", "default_popup": "default.html", + "default_icon": { + "18": "extension.png", + }, }, }, files: { "default.html": `<html><head><meta charset="utf-8"><script src="popup.js"></${"script"}></head></html>`, + "extension.png": IMAGE_ARRAYBUFFER, "a.html": `<html><head><meta charset="utf-8"><script src="popup.js"></${"script"}></head></html>`, "b.html": `<html><head><meta charset="utf-8"><script src="popup.js"></${"script"}></head></html>`, "popup.js": `(${popupScript})()`, }, }); let tabClosedPromise = () => { return new Promise(resolve => {
--- a/mobile/android/locales/en-US/chrome/pippki.properties +++ b/mobile/android/locales/en-US/chrome/pippki.properties @@ -56,16 +56,26 @@ clientAuthAsk.storedOn=Stored on: %1$S clientAuthAsk.viewCert.label=View certmgr.title=Certificate Details # These strings are stolen from security/manager/locales/en-US/chrome/pippki/certManager.dtd certmgr.subjectinfo.label=Issued To certmgr.issuerinfo.label=Issued By certmgr.periodofvalidity.label=Period of Validity certmgr.fingerprints.label=Fingerprints -certmgr.certdetail.cn=Common Name (CN) -certmgr.certdetail.o=Organization (O) -certmgr.certdetail.ou=Organizational Unit (OU) -certmgr.certdetail.serialnumber=Serial Number -certmgr.certdetail.sha256fingerprint=SHA-256 Fingerprint -certmgr.certdetail.sha1fingerprint=SHA1 Fingerprint -certmgr.begins=Begins On -certmgr.expires=Expires On +certdetail.cn=Common Name (CN): %1$S +certdetail.o=Organization (O): %1$S +certdetail.ou=Organizational Unit (OU): %1$S +# LOCALIZATION NOTE(certdetail.serialnumber): %1$S is the serial number of the +# cert being viewed in AA:BB:CC hex format. +certdetail.serialnumber=Serial Number: %1$S +# LOCALIZATION NOTE(certdetail.sha256fingerprint): %1$S is the SHA-256 +# Fingerprint of the cert being viewed in AA:BB:CC hex format. +certdetail.sha256fingerprint=SHA-256 Fingerprint: %1$S +# LOCALIZATION NOTE(certdetail.sha1fingerprint): %1$S is the SHA-1 Fingerprint +# of the cert being viewed in AA:BB:CC hex format. +certdetail.sha1fingerprint=SHA1 Fingerprint: %1$S +# LOCALIZATION NOTE(certdetail.notBefore): %1$S is the already localized +# notBefore date of the cert being viewed. +certdetail.notBefore=Begins On: %1$S +# LOCALIZATION NOTE(certdetail.notAfter): %1$S is the already localized notAfter +# date of the cert being viewed. +certdetail.notAfter=Expires On: %1$S
--- a/toolkit/components/extensions/ExtensionUtils.jsm +++ b/toolkit/components/extensions/ExtensionUtils.jsm @@ -6,19 +6,23 @@ this.EXPORTED_SYMBOLS = ["ExtensionUtils"]; const Ci = Components.interfaces; const Cc = Components.classes; const Cu = Components.utils; const Cr = Components.results; +const INTEGER = /^[1-9]\d*$/; + Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", + "resource://gre/modules/AddonManager.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector", "resource:///modules/translation/LanguageDetector.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Locale", "resource://gre/modules/Locale.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel", "resource://gre/modules/MessageChannel.jsm"); @@ -386,16 +390,162 @@ class BaseContext { }); for (let obj of this.onClose) { obj.close(); } } } +// Manages icon details for toolbar buttons in the |pageAction| and +// |browserAction| APIs. +let IconDetails = { + // Normalizes the various acceptable input formats into an object + // with icon size as key and icon URL as value. + // + // If a context is specified (function is called from an extension): + // Throws an error if an invalid icon size was provided or the + // extension is not allowed to load the specified resources. + // + // If no context is specified, instead of throwing an error, this + // function simply logs a warning message. + normalize(details, extension, context = null) { + let result = {}; + + try { + if (details.imageData) { + let imageData = details.imageData; + + // The global might actually be from Schema.jsm, which + // normalizes most of our arguments. In that case it won't have + // an ImageData property. But Schema.jsm doesn't normalize + // actual ImageData objects, so they will come from a global + // with the right property. + if (instanceOf(imageData, "ImageData")) { + imageData = {"19": imageData}; + } + + for (let size of Object.keys(imageData)) { + if (!INTEGER.test(size)) { + throw new Error(`Invalid icon size ${size}, must be an integer`); + } + result[size] = this.convertImageDataToDataURL(imageData[size], context); + } + } + + if (details.path) { + let path = details.path; + if (typeof path != "object") { + path = {"19": path}; + } + + let baseURI = context ? context.uri : extension.baseURI; + + for (let size of Object.keys(path)) { + if (!INTEGER.test(size)) { + throw new Error(`Invalid icon size ${size}, must be an integer`); + } + + let url = baseURI.resolve(path[size]); + + // The Chrome documentation specifies these parameters as + // relative paths. We currently accept absolute URLs as well, + // which means we need to check that the extension is allowed + // to load them. This will throw an error if it's not allowed. + Services.scriptSecurityManager.checkLoadURIStrWithPrincipal( + extension.principal, url, + Services.scriptSecurityManager.DISALLOW_SCRIPT); + + result[size] = url; + } + } + } catch (e) { + // Function is called from extension code, delegate error. + if (context) { + throw e; + } + // If there's no context, it's because we're handling this + // as a manifest directive. Log a warning rather than + // raising an error. + extension.manifestError(`Invalid icon data: ${e}`); + } + + return result; + }, + + // Returns the appropriate icon URL for the given icons object and the + // screen resolution of the given window. + getURL(icons, window, extension, size = 16) { + const DEFAULT = "chrome://browser/content/extension.svg"; + + size *= window.devicePixelRatio; + + let bestSize = null; + if (icons[size]) { + bestSize = size; + } else if (icons[2 * size]) { + bestSize = 2 * size; + } else { + let sizes = Object.keys(icons) + .map(key => parseInt(key, 10)) + .sort((a, b) => a - b); + + bestSize = sizes.find(candidate => candidate > size) || sizes.pop(); + } + + if (bestSize) { + return {size: bestSize, icon: icons[bestSize]}; + } + + return {size, icon: DEFAULT}; + }, + + convertImageURLToDataURL(imageURL, context, browserWindow, size = 18) { + return new Promise((resolve, reject) => { + let image = new context.contentWindow.Image(); + image.onload = function() { + let canvas = context.contentWindow.document.createElement("canvas"); + let ctx = canvas.getContext("2d"); + let dSize = size * browserWindow.devicePixelRatio; + + // Scales the image while maintaing width to height ratio. + // If the width and height differ, the image is centered using the + // smaller of the two dimensions. + let dWidth, dHeight, dx, dy; + if (this.width > this.height) { + dWidth = dSize; + dHeight = image.height * (dSize / image.width); + dx = 0; + dy = (dSize - dHeight) / 2; + } else { + dWidth = image.width * (dSize / image.height); + dHeight = dSize; + dx = (dSize - dWidth) / 2; + dy = 0; + } + + ctx.drawImage(this, 0, 0, this.width, this.height, dx, dy, dWidth, dHeight); + resolve(canvas.toDataURL("image/png")); + }; + image.onerror = reject; + image.src = imageURL; + }); + }, + + convertImageDataToDataURL(imageData, context) { + let document = context.contentWindow.document; + let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); + canvas.width = imageData.width; + canvas.height = imageData.height; + canvas.getContext("2d").putImageData(imageData, 0, 0); + + return canvas.toDataURL("image/png"); + }, +}; + function LocaleData(data) { this.defaultLocale = data.defaultLocale; this.selectedLocale = data.selectedLocale; this.locales = data.locales || new Map(); this.warnedMissingKeys = new Set(); // Map(locale-name -> Map(message-key -> localized-string)) // @@ -403,16 +553,17 @@ function LocaleData(data) { // Map of message keys to their localized strings. this.messages = data.messages || new Map(); if (data.builtinMessages) { this.messages.set(this.BUILTIN, data.builtinMessages); } } + LocaleData.prototype = { // Representation of the object to send to content processes. This // should include anything the content process might need. serialize() { return { defaultLocale: this.defaultLocale, selectedLocale: this.selectedLocale, messages: this.messages, @@ -1251,15 +1402,16 @@ this.ExtensionUtils = { promiseDocumentReady, runSafe, runSafeSync, runSafeSyncWithoutClone, runSafeWithoutClone, BaseContext, DefaultWeakMap, EventManager, + IconDetails, LocaleData, Messenger, PlatformInfo, SingletonEventManager, SpreadArgs, ChildAPIManager, };
--- a/toolkit/components/extensions/Schemas.jsm +++ b/toolkit/components/extensions/Schemas.jsm @@ -941,20 +941,21 @@ class ArrayType extends Type { } checkBaseType(baseType) { return baseType == "array"; } } class FunctionType extends Type { - constructor(schema, parameters, isAsync) { + constructor(schema, parameters, isAsync, hasAsyncCallback) { super(schema); this.parameters = parameters; this.isAsync = isAsync; + this.hasAsyncCallback = hasAsyncCallback; } normalize(value, context) { return this.normalizeBase("function", value, context); } checkBaseType(baseType) { return baseType == "function"; @@ -1151,33 +1152,37 @@ class CallEntry extends Entry { class FunctionEntry extends CallEntry { constructor(schema, path, name, type, unsupported, allowAmbiguousOptionalArguments, returns, permissions) { super(schema, path, name, type.parameters, allowAmbiguousOptionalArguments); this.unsupported = unsupported; this.returns = returns; this.permissions = permissions; this.isAsync = type.isAsync; + this.hasAsyncCallback = type.hasAsyncCallback; } inject(path, name, dest, context) { if (this.unsupported) { return; } if (this.permissions && !this.permissions.some(perm => context.hasPermission(perm))) { return; } let stub; if (this.isAsync) { stub = (...args) => { this.checkDeprecated(context); let actuals = this.checkParameters(args, context); - let callback = actuals.pop(); + let callback = null; + if (this.hasAsyncCallback) { + callback = actuals.pop(); + } return context.callAsyncFunction(path, name, actuals, callback); }; } else if (!this.returns) { stub = (...args) => { this.checkDeprecated(context); let actuals = this.checkParameters(args, context); return context.callFunctionNoReturn(path, name, actuals); }; @@ -1398,17 +1403,17 @@ this.Schemas = { return new NumberType(type); } else if (type.type == "integer") { checkTypeProperties("minimum", "maximum"); return new IntegerType(type, type.minimum || -Infinity, type.maximum || Infinity); } else if (type.type == "boolean") { checkTypeProperties(); return new BooleanType(type); } else if (type.type == "function") { - let isAsync = typeof(type.async) == "string"; + let isAsync = Boolean(type.async); let parameters = null; if ("parameters" in type) { parameters = []; for (let param of type.parameters) { // Callbacks default to optional for now, because of promise // handling. let isCallback = isAsync && param.name == type.async; @@ -1416,27 +1421,28 @@ this.Schemas = { parameters.push({ type: this.parseType(path, param, ["name", "optional"]), name: param.name, optional: param.optional == null ? isCallback : param.optional, }); } } + let hasAsyncCallback = false; if (isAsync) { - if (!parameters || !parameters.length || parameters[parameters.length - 1].name != type.async) { - throw new Error(`Internal error: "async" property must name the last parameter of the function.`); + if (parameters && parameters.length && parameters[parameters.length - 1].name == type.async) { + hasAsyncCallback = true; } if (type.returns || type.allowAmbiguousOptionalArguments) { throw new Error(`Internal error: Async functions must not have return values or ambiguous arguments.`); } } checkTypeProperties("parameters", "async", "returns"); - return new FunctionType(type, parameters, isAsync); + return new FunctionType(type, parameters, isAsync, hasAsyncCallback); } else if (type.type == "any") { // Need to see what minimum and maximum are supposed to do here. checkTypeProperties("minimum", "maximum"); return new AnyType(type); } else { throw new Error(`Unexpected type ${type.type}`); } },
--- a/toolkit/components/prompts/test/chromeScript.js +++ b/toolkit/components/prompts/test/chromeScript.js @@ -217,18 +217,25 @@ function getDialogDoc() { var childDocShell = containedDocShells.getNext(); // We don't want it if it's not done loading. if (childDocShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE) continue; var childDoc = childDocShell.QueryInterface(Ci.nsIDocShell) .contentViewer .DOMDocument; - //ok(true, "Got window: " + childDoc.location.href); - if (childDoc.location.href == "chrome://global/content/commonDialog.xul") - return childDoc; - if (childDoc.location.href == "chrome://global/content/selectDialog.xul") - return childDoc; + if (childDoc.location.href != "chrome://global/content/commonDialog.xul" && + childDoc.location.href != "chrome://global/content/selectDialog.xul") + continue; + + // We're expecting the dialog to be focused. If it's not yet, try later. + // (In particular, this is needed on Linux to reliably check focused elements.) + let fm = Cc["@mozilla.org/focus-manager;1"]. + getService(Ci.nsIFocusManager); + if (fm.focusedWindow != childDoc.defaultView) + continue; + + return childDoc; } } return null; }
--- a/toolkit/components/prompts/test/prompt_common.js +++ b/toolkit/components/prompts/test/prompt_common.js @@ -7,17 +7,16 @@ function hasTabModalPrompts() { var prefName = "prompts.tab_modal.enabled"; var Services = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm").Services; return Services.prefs.getPrefType(prefName) == Services.prefs.PREF_BOOL && Services.prefs.getBoolPref(prefName); } var isTabModal = hasTabModalPrompts(); var isSelectDialog = false; var isOSX = ("nsILocalFileMac" in SpecialPowers.Ci); -var isLinux = ("@mozilla.org/gnome-gconf-service;1" in SpecialPowers.Cc); var isE10S = SpecialPowers.Services.appinfo.processType == 2; var gChromeScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL("chromeScript.js")); SimpleTest.registerCleanupFunction(() => gChromeScript.destroy()); function onloadPromiseFor(id) { var iframe = document.getElementById(id); @@ -68,27 +67,21 @@ function checkPromptState(promptState, e } // For prompts with a time-delay button. if (expectedState.butt0Disabled) { is(promptState.butt0Disabled, true, "Checking accept-button is disabled"); is(promptState.butt1Disabled, false, "Checking cancel-button isn't disabled"); } - if (isLinux && !promptState.focused) { - todo(false, "Checking button default fails if focus is not correct."); // bug 1278418 - } else { - is(promptState.defButton0, expectedState.defButton == "button0", "checking button0 default"); - is(promptState.defButton1, expectedState.defButton == "button1", "checking button1 default"); - is(promptState.defButton2, expectedState.defButton == "button2", "checking button2 default"); - } + is(promptState.defButton0, expectedState.defButton == "button0", "checking button0 default"); + is(promptState.defButton1, expectedState.defButton == "button1", "checking button1 default"); + is(promptState.defButton2, expectedState.defButton == "button2", "checking button2 default"); - if (isLinux && (!promptState.focused || isE10S)) { - todo(false, "Focus seems missing or wrong on Linux"); // bug 1265077 - } else if (isOSX && expectedState.focused && expectedState.focused.startsWith("button")) { + if (isOSX && expectedState.focused && expectedState.focused.startsWith("button")) { is(promptState.focused, "infoBody", "buttons don't focus on OS X, but infoBody does instead"); } else { is(promptState.focused, expectedState.focused, "Checking focused element"); } } function checkEchoedAuthInfo(expectedState, doc) { // The server echos back the HTTP auth info it received.
--- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -5742,524 +5742,30 @@ "PRINT_PREVIEW_SIMPLIFY_PAGE_OPENED_COUNT": { "alert_emails": ["carnold@mozilla.org"], "bug_numbers": [1275570], "expires_in_version": "56", "kind": "count", "releaseChannelCollection": "opt-out", "description": "A counter incremented every time the browser enters simplified mode on print preview." }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_RELOAD_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'reload' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_RELOAD_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'reload' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_NAVIGATETO_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'navigateTo' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_NAVIGATETO_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'navigateTo' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_EVENTLISTENERS_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took an 'eventListeners' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_EVENTLISTENERS_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took an 'eventListeners' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_DETACH_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'detach' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_DETACH_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'detach' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_RESUME_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'resume' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_RESUME_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'resume' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_INTERRUPT_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took an 'interrupt' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_INTERRUPT_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took an 'interrupt' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_CLIENTEVALUATE_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'clientEvaluate' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_CLIENTEVALUATE_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'clientEvaluate' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_RELEASEMANY_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'releaseMany' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_RELEASEMANY_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'releaseMany' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_THREADGRIPS_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'threadGrips' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_THREADGRIPS_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'threadGrips' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_SOURCES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'sources' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_SOURCES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'sources' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_FRAMES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'frames' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_FRAMES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'frames' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_PARAMETERNAMES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'parameterNames' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_PARAMETERNAMES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'parameterNames' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_OWNPROPERTYNAMES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'ownPropertyNames' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_OWNPROPERTYNAMES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'ownPropertyNames' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_PROTOTYPEANDPROPERTIES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'prototypeAndProperties' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROTOTYPEANDPROPERTIES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'prototypeAndProperties' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_ENUMPROPERTIES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'enumProperties' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_ENUMPROPERTIES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'enumProperties' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_PROTOTYPESANDPROPERTIES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'prototypesAndProperties' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROTOTYPESANDPROPERTIES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'prototypesAndProperties' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_PROPERTY_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'property' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROPERTY_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'property' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_PROTOTYPE_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'prototype' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROTOTYPE_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'prototype' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_DISPLAYSTRING_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'displayString' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_DISPLAYSTRING_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'displayString' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_SUBSTRING_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'substring' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_SUBSTRING_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'substring' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_RELEASE_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'release' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_RELEASE_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'release' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTTABS_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'listTabs' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_LISTTABS_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'listTabs' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTSERVICEWORKERREGISTRATIONS_MS": { - "alert_emails": ["dev-developer-tools@lists.mozilla.org", "ejpbruel@mozilla.com"], - "expires_in_version": "50", - "kind": "exponential", - "high": 10000, - "n_buckets": 100, - "description": "The time (in milliseconds) that it took a 'listServiceWorkerRegistrations' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_LISTSERVICEWORKERREGISTRATIONS_MS": { - "alert_emails": ["dev-developer-tools@lists.mozilla.org", "ejpbruel@mozilla.com"], - "expires_in_version": "50", - "kind": "exponential", - "high": 10000, - "n_buckets": 100, - "description": "The time (in milliseconds) that it took a 'listServiceWorkerRegistrations' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_PROTOCOLDESCRIPTION_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'protocolDescription' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROTOCOLDESCRIPTION_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'protocolDescription' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTADDONS_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'listAddons' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_LISTADDONS_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'listAddons' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTWORKERS_MS": { - "alert_emails": ["dev-developer-tools@lists.mozilla.org", "jan@mozilla.com"], - "expires_in_version": "55", - "kind": "exponential", - "high": 10000, - "n_buckets": 50, - "description": "The time (in milliseconds) that it took a 'listWorkers' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_LISTWORKERS_MS": { - "alert_emails": ["dev-developer-tools@lists.mozilla.org", "jan@mozilla.com"], - "expires_in_version": "55", - "kind": "exponential", - "high": 10000, - "n_buckets": 50, - "description": "The time (in milliseconds) that it took a 'listWorkers' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTPROCESSES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'listProcesses' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_LISTPROCESSES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'listProcesses' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_DELETE_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'delete' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_DELETE_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'delete' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_THREADDETACH_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'detach' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_THREADDETACH_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'detach' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_ADDONDETACH_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'detach' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_ADDONDETACH_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'detach' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_TABDETACH_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'detach' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_TABDETACH_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'detach' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_WORKERDETACH_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'detach' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_WORKERDETACH_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'detach' request to go round trip." - }, "DEVTOOLS_DEBUGGER_DISPLAY_SOURCE_LOCAL_MS": { "expires_in_version": "never", "kind": "exponential", "high": 10000, "n_buckets": 1000, "description": "The time (in milliseconds) that it took to display a selected source to the user." }, "DEVTOOLS_DEBUGGER_DISPLAY_SOURCE_REMOTE_MS": { "expires_in_version": "never", "kind": "exponential", "high": 10000, "n_buckets": 1000, "description": "The time (in milliseconds) that it took to display a selected source to the user." }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_RECONFIGURETAB_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'reconfigure tab' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_RECONFIGURETAB_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'reconfigure tab' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTWORKERS_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'listWorkers' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_LISTWORKERS_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'listWorkers' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_RECONFIGURETHREAD_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'reconfigure thread' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_RECONFIGURETHREAD_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'reconfigure thread' request to go round trip." - }, "MEDIA_RUST_MP4PARSE_SUCCESS": { "alert_emails": ["giles@mozilla.com", "kinetik@flim.org"], "expires_in_version": "55", "kind": "boolean", "bug_numbers": [1220885], "description": "(Bug 1220885) Whether the rust mp4 demuxer successfully parsed a stream segment.", "cpp_guard": "MOZ_RUST_MP4PARSE" }, @@ -6845,142 +6351,16 @@ }, "WEBRTC_CALL_TYPE": { "alert_emails": ["webrtc-telemetry-alerts@mozilla.com"], "expires_in_version": "never", "kind": "enumerated", "n_values": 8, "description": "Type of call: (Bitmask) Audio = 1, Video = 2, DataChannels = 4" }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_TRACERDETACH_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'detach' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_TRACERDETACH_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'detach' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_STARTTRACE_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'startTrace' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_STARTTRACE_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'startTrace' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_STOPTRACE_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'stopTrace' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_STOPTRACE_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'stopTrace' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_GET_EXECUTABLE_LINES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'getExecutableLines' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_GET_EXECUTABLE_LINES_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'getExecutableLines' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_BLACKBOX_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'blackbox' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_BLACKBOX_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'blackbox' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_UNBLACKBOX_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took an 'ublackbox' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_UNBLACKBOX_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took an 'unblackbox' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_SCOPE_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'scope' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_SCOPE_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'scope' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_BINDINGS_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'bindings' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_BINDINGS_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took a 'bindings' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_LOCAL_ASSIGN_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took an 'assign' request to go round trip." - }, - "DEVTOOLS_DEBUGGER_RDP_REMOTE_ASSIGN_MS": { - "expires_in_version": "never", - "kind": "exponential", - "high": 10000, - "n_buckets": 1000, - "description": "The time (in milliseconds) that it took an 'assign' request to go round trip." - }, "DEVTOOLS_TOOLBOX_OPENED_COUNT": { "alert_emails": ["dev-developer-tools@lists.mozilla.org"], "expires_in_version": "never", "kind": "count", "bug_numbers": [1247985], "description": "Number of times the DevTools toolbox has been opened.", "releaseChannelCollection": "opt-out" },
--- a/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.cc @@ -759,28 +759,30 @@ bool ExceptionHandler::WriteMinidumpForE bool success = WriteMinidumpOnHandlerThread(exinfo, NULL); UpdateNextID(); return success; } // static bool ExceptionHandler::WriteMinidump(const wstring &dump_path, MinidumpCallback callback, - void* callback_context) { + void* callback_context, + MINIDUMP_TYPE dump_type) { ExceptionHandler handler(dump_path, NULL, callback, callback_context, - HANDLER_NONE); + HANDLER_NONE, dump_type, (HANDLE)NULL, NULL); return handler.WriteMinidump(); } // static bool ExceptionHandler::WriteMinidumpForChild(HANDLE child, DWORD child_blamed_thread, const wstring& dump_path, MinidumpCallback callback, - void* callback_context) { + void* callback_context, + MINIDUMP_TYPE dump_type) { EXCEPTION_RECORD ex; CONTEXT ctx; EXCEPTION_POINTERS exinfo = { NULL, NULL }; DWORD last_suspend_count = kFailedToSuspendThread; HANDLE child_thread_handle = OpenThread(THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION | THREAD_SUSPEND_RESUME, FALSE, @@ -801,17 +803,17 @@ bool ExceptionHandler::WriteMinidumpForC #endif exinfo.ExceptionRecord = &ex; exinfo.ContextRecord = &ctx; } } } ExceptionHandler handler(dump_path, NULL, callback, callback_context, - HANDLER_NONE); + HANDLER_NONE, dump_type, (HANDLE)NULL, NULL); bool success = handler.WriteMinidumpWithExceptionForProcess( child_blamed_thread, exinfo.ExceptionRecord ? &exinfo : NULL, NULL, child, false); if (last_suspend_count != kFailedToSuspendThread) { ResumeThread(child_thread_handle); }
--- a/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.h +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.h @@ -233,28 +233,30 @@ class ExceptionHandler { // Writes a minidump immediately, with the user-supplied exception // information. bool WriteMinidumpForException(EXCEPTION_POINTERS* exinfo); // Convenience form of WriteMinidump which does not require an // ExceptionHandler instance. static bool WriteMinidump(const wstring &dump_path, - MinidumpCallback callback, void* callback_context); + MinidumpCallback callback, void* callback_context, + MINIDUMP_TYPE dump_type = MiniDumpNormal); // Write a minidump of |child| immediately. This can be used to // capture the execution state of |child| independently of a crash. // Pass a meaningful |child_blamed_thread| to make that thread in // the child process the one from which a crash signature is // extracted. static bool WriteMinidumpForChild(HANDLE child, DWORD child_blamed_thread, const wstring& dump_path, MinidumpCallback callback, - void* callback_context); + void* callback_context, + MINIDUMP_TYPE dump_type = MiniDumpNormal); // Get the thread ID of the thread requesting the dump (either the exception // thread or any other thread that called WriteMinidump directly). This // may be useful if you want to include additional thread state in your // dumps. DWORD get_requesting_thread_id() const { return requesting_thread_id_; } // Controls behavior of EXCEPTION_BREAKPOINT and EXCEPTION_SINGLE_STEP.
--- a/toolkit/crashreporter/nsExceptionHandler.cpp +++ b/toolkit/crashreporter/nsExceptionHandler.cpp @@ -3860,17 +3860,21 @@ bool TakeMinidump(nsIFile** aResult, boo // capture the dump if (!google_breakpad::ExceptionHandler::WriteMinidump( dump_path, #ifdef XP_MACOSX true, #endif PairedDumpCallback, - static_cast<void*>(aResult))) { + static_cast<void*>(aResult) +#ifdef XP_WIN32 + , GetMinidumpType() +#endif + )) { return false; } if (aMoveToPending) { MoveToPending(*aResult, nullptr, nullptr); } return true; } @@ -3901,33 +3905,40 @@ CreateMinidumpsAndPair(ProcessHandle aTa // dump the target nsCOMPtr<nsIFile> targetMinidump; if (!google_breakpad::ExceptionHandler::WriteMinidumpForChild( aTargetPid, targetThread, dump_path, PairedDumpCallbackExtra, - static_cast<void*>(&targetMinidump))) + static_cast<void*>(&targetMinidump) +#ifdef XP_WIN32 + , GetMinidumpType() +#endif + )) return false; nsCOMPtr<nsIFile> targetExtra; GetExtraFileForMinidump(targetMinidump, getter_AddRefs(targetExtra)); // If aIncomingDumpToPair isn't valid, create a dump of this process. nsCOMPtr<nsIFile> incomingDump; if (aIncomingDumpToPair == nullptr) { if (!google_breakpad::ExceptionHandler::WriteMinidump( dump_path, #ifdef XP_MACOSX true, #endif PairedDumpCallback, - static_cast<void*>(&incomingDump))) { - + static_cast<void*>(&incomingDump) +#ifdef XP_WIN32 + , GetMinidumpType() +#endif + )) { targetMinidump->Remove(false); targetExtra->Remove(false); return false; } } else { incomingDump = aIncomingDumpToPair; } @@ -3967,17 +3978,21 @@ CreateAdditionalChildMinidump(ProcessHan // dump the child nsCOMPtr<nsIFile> childMinidump; if (!google_breakpad::ExceptionHandler::WriteMinidumpForChild( childPid, childThread, dump_path, PairedDumpCallback, - static_cast<void*>(&childMinidump))) { + static_cast<void*>(&childMinidump) +#ifdef XP_WIN32 + , GetMinidumpType() +#endif + )) { return false; } RenameAdditionalHangMinidump(childMinidump, parentMinidump, name); return true; }
--- a/toolkit/mozapps/extensions/internal/AddonRepository.jsm +++ b/toolkit/mozapps/extensions/internal/AddonRepository.jsm @@ -395,17 +395,17 @@ AddonSearchResult.prototype = { * particular application and platform version. * * @param appVersion * An application version to test against * @param platformVersion * A platform version to test against * @return Boolean representing if the add-on is compatible */ - isCompatibleWith: function(aAppVerison, aPlatformVersion) { + isCompatibleWith: function(aAppVersion, aPlatformVersion) { return true; }, /** * Starts an update check for this add-on. This will perform * asynchronously and deliver results to the given listener. * * @param aListener
--- a/toolkit/mozapps/extensions/internal/PluginProvider.jsm +++ b/toolkit/mozapps/extensions/internal/PluginProvider.jsm @@ -573,17 +573,17 @@ PluginWrapper.prototype = { get providesUpdatesSecurely() { return true; }, get foreignInstall() { return true; }, - isCompatibleWith: function(aAppVerison, aPlatformVersion) { + isCompatibleWith: function(aAppVersion, aPlatformVersion) { return true; }, findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) { if ("onNoCompatibilityUpdateAvailable" in aListener) aListener.onNoCompatibilityUpdateAvailable(this); if ("onNoUpdateAvailable" in aListener) aListener.onNoUpdateAvailable(this);