author | Ryan VanderMeulen <ryanvm@gmail.com> |
Wed, 18 Jun 2014 16:15:32 -0400 | |
changeset 189393 | f78e532e8a105c597392f43212b8bfe9792b3193 |
parent 189381 | 00d973a0bdbf63a1a5bd0fc7e1fc9927f7bae8fc (current diff) |
parent 189392 | 9f10f484bb2643122d377d8a4917258b50edbc3d (diff) |
child 189398 | 673a55b794f38dd0272e9cd4d071f5982d9db1ab |
child 189466 | ed9d62bef0df94f75f27a8d98fb4089b2f188cd4 |
child 189554 | 815c4fab188b032d2a4ddccff0b7f2a7d4a4d248 |
push id | 26986 |
push user | ryanvm@gmail.com |
push date | Wed, 18 Jun 2014 20:15:38 +0000 |
treeherder | mozilla-central@f78e532e8a10 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 33.0a1 |
first release with | nightly linux32
f78e532e8a10
/
33.0a1
/
20140619030203
/
files
nightly linux64
f78e532e8a10
/
33.0a1
/
20140619030203
/
files
nightly mac
f78e532e8a10
/
33.0a1
/
20140619030203
/
files
nightly win32
f78e532e8a10
/
33.0a1
/
20140619030203
/
files
nightly win64
f78e532e8a10
/
33.0a1
/
20140619030203
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
33.0a1
/
20140619030203
/
pushlog to previous
nightly linux64
33.0a1
/
20140619030203
/
pushlog to previous
nightly mac
33.0a1
/
20140619030203
/
pushlog to previous
nightly win32
33.0a1
/
20140619030203
/
pushlog to previous
nightly win64
33.0a1
/
20140619030203
/
pushlog to previous
|
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -6897,43 +6897,58 @@ let gRemoteTabsUI = { * URI to search for * @param aOpenNew * True to open a new tab and switch to it, if no existing tab is found. * If no suitable window is found, a new one will be opened. * @param aOpenParams * If switching to this URI results in us opening a tab, aOpenParams * will be the parameter object that gets passed to openUILinkIn. Please * see the documentation for openUILinkIn to see what parameters can be - * passed via this object. + * passed via this object. This object also allows the 'ignoreFragment' + * property to be set to true to exclude fragment-portion matching when + * comparing URIs. * @return True if an existing tab was found, false otherwise */ -function switchToTabHavingURI(aURI, aOpenNew, aOpenParams) { +function switchToTabHavingURI(aURI, aOpenNew, aOpenParams={}) { // Certain URLs can be switched to irrespective of the source or destination // window being in private browsing mode: const kPrivateBrowsingWhitelist = new Set([ "about:customizing", ]); + + let ignoreFragment = aOpenParams.ignoreFragment; + // This property is only used by switchToTabHavingURI and should + // not be used as a parameter for the new load. + delete aOpenParams.ignoreFragment; + // This will switch to the tab in aWindow having aURI, if present. function switchIfURIInWindow(aWindow) { // Only switch to the tab if neither the source nor the destination window // are private and they are not in permanent private browsing mode if (!kPrivateBrowsingWhitelist.has(aURI.spec) && (PrivateBrowsingUtils.isWindowPrivate(window) || PrivateBrowsingUtils.isWindowPrivate(aWindow)) && !PrivateBrowsingUtils.permanentPrivateBrowsing) { return false; } let browsers = aWindow.gBrowser.browsers; for (let i = 0; i < browsers.length; i++) { let browser = browsers[i]; - if (browser.currentURI.equals(aURI)) { + if (ignoreFragment ? browser.currentURI.equalsExceptRef(aURI) : + browser.currentURI.equals(aURI)) { // Focus the matching window & tab aWindow.focus(); aWindow.gBrowser.tabContainer.selectedIndex = i; + if (ignoreFragment) { + let spec = aURI.spec; + if (!aURI.ref) + spec += "#"; + browser.loadURI(spec); + } return true; } } return false; } // This can be passed either nsIURI or a string. if (!(aURI instanceof Ci.nsIURI))
--- a/browser/base/content/test/general/browser.ini +++ b/browser/base/content/test/general/browser.ini @@ -423,8 +423,9 @@ skip-if = (os == "win" && !debug) || e10 skip-if = e10s # Bug ?????? - test directly manipulates content (content.document.getElementById) [browser_zbug569342.js] skip-if = e10s # Bug 516755 - SessionStore disabled for e10s [browser_registerProtocolHandler_notification.js] skip-if = e10s # Bug 940206 - nsIWebContentHandlerRegistrar::registerProtocolHandler doesn't work in e10s [browser_no_mcb_on_http_site.js] skip-if = e10s # Bug 516755 - SessionStore disabled for e10s [browser_bug1003461-switchtab-override.js] +[browser_bug1025195_switchToTabHavingURI_ignoreFragment.js]
new file mode 100644 --- /dev/null +++ b/browser/base/content/test/general/browser_bug1025195_switchToTabHavingURI_ignoreFragment.js @@ -0,0 +1,35 @@ +/* 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/. */ + +add_task(function() { + registerCleanupFunction(function() { + while (gBrowser.tabs.length > 1) + gBrowser.removeCurrentTab(); + }); + let tabRefAboutHome = gBrowser.addTab("about:home#1"); + yield promiseTabLoaded(tabRefAboutHome); + let tabRefAboutMozilla = gBrowser.addTab("about:mozilla"); + yield promiseTabLoaded(tabRefAboutMozilla); + + gBrowser.selectedTab = tabRefAboutMozilla; + let numTabsAtStart = gBrowser.tabs.length; + + switchTab("about:home#1", false, true); + switchTab("about:mozilla", false, true); + switchTab("about:home#2", true, true); + is(tabRefAboutHome, gBrowser.selectedTab, "The same about:home tab should be switched to"); + is(gBrowser.currentURI.ref, "2", "The ref should be updated to the new ref"); + switchTab("about:mozilla", false, true); + switchTab("about:home#1", false, false); + isnot(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should not be initial about:blank tab"); + is(gBrowser.tabs.length, numTabsAtStart + 1, "Should have one new tab opened"); + switchTab("about:about", true, false); +}); + +function switchTab(aURI, aIgnoreFragment, aShouldFindExistingTab) { + let tabFound = switchToTabHavingURI(aURI, true, {ignoreFragment: aIgnoreFragment}); + is(tabFound, aShouldFindExistingTab, + "Should switch to existing " + aURI + " tab if one existed, " + + (aIgnoreFragment ? "ignoring" : "including") + " fragment portion"); +}
--- a/browser/base/content/test/general/head.js +++ b/browser/base/content/test/general/head.js @@ -466,8 +466,9 @@ let FullZoomHelper = { failAndContinue: function failAndContinue(func) { return function (err) { ok(false, err); func(); }; }, }; +
--- a/browser/devtools/scratchpad/scratchpad.js +++ b/browser/devtools/scratchpad/scratchpad.js @@ -1953,33 +1953,35 @@ let scratchpadTargets = new WeakMap(); * @return Promise * The promise for the connection information. */ ScratchpadTab.consoleFor = function consoleFor(aSubject) { if (!scratchpadTargets.has(aSubject)) { scratchpadTargets.set(aSubject, new this(aSubject)); } - return scratchpadTargets.get(aSubject).connect(); + return scratchpadTargets.get(aSubject).connect(aSubject); }; ScratchpadTab.prototype = { /** * The promise for the connection. */ _connector: null, /** * Initialize a debugger client and connect it to the debugger server. * + * @param object aSubject + * The tab or window to obtain the connection for. * @return Promise * The promise for the result of connecting to this tab or window. */ - connect: function ST_connect() + connect: function ST_connect(aSubject) { if (this._connector) { return this._connector; } let deferred = promise.defer(); this._connector = deferred.promise; @@ -1987,17 +1989,17 @@ ScratchpadTab.prototype = { deferred.reject({ error: "timeout", message: Scratchpad.strings.GetStringFromName("connectionTimeout"), }); }, REMOTE_TIMEOUT); deferred.promise.then(() => clearTimeout(connectTimer)); - this._attach().then(aTarget => { + this._attach(aSubject).then(aTarget => { let consoleActor = aTarget.form.consoleActor; let client = aTarget.client; client.attachConsole(consoleActor, [], (aResponse, aWebConsoleClient) => { if (aResponse.error) { reportError("attachConsole", aResponse); deferred.reject(aResponse); } else { @@ -2010,22 +2012,29 @@ ScratchpadTab.prototype = { }); return deferred.promise; }, /** * Attach to this tab. * + * @param object aSubject + * The tab or window to obtain the connection for. * @return Promise * The promise for the TabTarget for this tab. */ - _attach: function ST__attach() + _attach: function ST__attach(aSubject) { let target = TargetFactory.forTab(this._tab); + target.once("close", () => { + if (scratchpadTargets) { + scratchpadTargets.delete(aSubject); + } + }); return target.makeRemote().then(() => target); }, }; /** * Represents the DebuggerClient connection to a specific window as used by the * Scratchpad.
--- a/browser/devtools/scratchpad/test/browser.ini +++ b/browser/devtools/scratchpad/test/browser.ini @@ -34,8 +34,9 @@ support-files = head.js [browser_scratchpad_open_error_console.js] [browser_scratchpad_throw_output.js] [browser_scratchpad_pprint-02.js] [browser_scratchpad_pprint.js] [browser_scratchpad_pprint_error_goto_line.js] [browser_scratchpad_restore.js] [browser_scratchpad_tab_switch.js] [browser_scratchpad_ui.js] +[browser_scratchpad_close_toolbox.js]
new file mode 100644 --- /dev/null +++ b/browser/devtools/scratchpad/test/browser_scratchpad_close_toolbox.js @@ -0,0 +1,38 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that closing the toolbox after having opened a scratchpad leaves the +// latter in a functioning state. + +let {Task} = Cu.import("resource://gre/modules/Task.jsm", {}); +let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); + +function test() { + const options = { + tabContent: "test closing toolbox and then reusing scratchpad" + }; + openTabAndScratchpad(options) + .then(Task.async(runTests)) + .then(finish, console.error); +} + +function* runTests([win, sp]) { + // Use the scratchpad before opening the toolbox. + const source = "window.foobar = 7;"; + sp.setText(source); + let [,,result] = yield sp.display(); + is(result, 7, "Display produced the expected output."); + + // Now open the toolbox and close it again. + let target = devtools.TargetFactory.forTab(gBrowser.selectedTab); + let toolbox = yield gDevTools.showToolbox(target, "webconsole"); + ok(toolbox, "Toolbox was opened."); + let closed = yield gDevTools.closeToolbox(target); + is(closed, true, "Toolbox was closed."); + + // Now see if using the scratcphad works as expected. + sp.setText(source); + let [,,result2] = yield sp.display(); + is(result2, 7, + "Display produced the expected output after the toolbox was gone."); +}
--- a/browser/devtools/styleeditor/StyleEditorUI.jsm +++ b/browser/devtools/styleeditor/StyleEditorUI.jsm @@ -755,17 +755,19 @@ StyleEditorUI.prototype = { cond.className = "media-rule-condition" if (!rule.matches) { cond.classList.add("media-condition-unmatched"); } div.appendChild(cond); let link = this._panelDoc.createElement("div"); link.className = "media-rule-line theme-link"; - link.textContent = ":" + location.line; + if (location.line != -1) { + link.textContent = ":" + location.line; + } div.appendChild(link); list.appendChild(div); } sidebar.hidden = !showSidebar || !inSource; this.emit("media-list-changed", editor);
--- a/browser/devtools/styleeditor/test/browser_styleeditor_media_sidebar.js +++ b/browser/devtools/styleeditor/test/browser_styleeditor_media_sidebar.js @@ -2,18 +2,19 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ // https rather than chrome to improve coverage const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules.html"; const MEDIA_PREF = "devtools.styleeditor.showMediaSidebar"; const RESIZE = 300; -const LABELS = ["not all", "all", "(max-width: 400px)"]; -const LINE_NOS = [2, 8, 20]; +const LABELS = ["not all", "all", "(max-width: 400px)", "(max-width: 600px)"]; +const LINE_NOS = [2, 8, 20, 25]; +const NEW_RULE = "\n@media (max-width: 600px) { div { color: blue; } }"; waitForExplicitFinish(); let test = asyncTest(function*() { let {UI} = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI); is(UI.editors.length, 2, "correct number of editors"); @@ -23,46 +24,34 @@ let test = asyncTest(function*() { testPlainEditor(plainEditor); // Test editor with @media rules let mediaEditor = UI.editors[1]; yield openEditor(mediaEditor); testMediaEditor(mediaEditor); // Test that sidebar hides when flipping pref - testShowHide(mediaEditor); + yield testShowHide(UI, mediaEditor); + + // Test adding a rule updates the list + yield testMediaRuleAdded(UI, mediaEditor); // Test resizing and seeing @media matching state change let originalWidth = window.outerWidth; let originalHeight = window.outerHeight; let onMatchesChange = listenForMediaChange(UI); window.resizeTo(RESIZE, RESIZE); yield onMatchesChange; testMediaMatchChanged(mediaEditor); window.resizeTo(originalWidth, originalHeight); }); -function* testShowHide(editor) { - let sidebarChange = listenForMediaChange(UI); - Services.prefs.setBoolPref(MEDIA_PREF, false); - yield sidebarChange; - - let sidebar = editor.details.querySelector(".stylesheet-sidebar"); - is(sidebar.hidden, true, "sidebar is hidden after flipping pref"); - - sidebarChange = listenForMediaChange(UI); - Services.prefs.clearUserPref(MEDIA_PREF); - yield sidebarChange; - - is(sidebar.hidden, false, "sidebar is showing after flipping pref back"); -} - function testPlainEditor(editor) { let sidebar = editor.details.querySelector(".stylesheet-sidebar"); is(sidebar.hidden, true, "sidebar is hidden on editor without @media"); } function testMediaEditor(editor) { let sidebar = editor.details.querySelector(".stylesheet-sidebar"); is(sidebar.hidden, false, "sidebar is showing on editor with @media"); @@ -79,16 +68,47 @@ function testMediaMatchChanged(editor) { let sidebar = editor.details.querySelector(".stylesheet-sidebar"); let cond = sidebar.querySelectorAll(".media-rule-condition")[2]; is(cond.textContent, "(max-width: 400px)", "third rule condition text is correct"); ok(!cond.classList.contains("media-condition-unmatched"), "media rule is now matched after resizing"); } +function* testShowHide(UI, editor) { + let sidebarChange = listenForMediaChange(UI); + Services.prefs.setBoolPref(MEDIA_PREF, false); + yield sidebarChange; + + let sidebar = editor.details.querySelector(".stylesheet-sidebar"); + is(sidebar.hidden, true, "sidebar is hidden after flipping pref"); + + sidebarChange = listenForMediaChange(UI); + Services.prefs.clearUserPref(MEDIA_PREF); + yield sidebarChange; + + is(sidebar.hidden, false, "sidebar is showing after flipping pref back"); +} + +function* testMediaRuleAdded(UI, editor) { + yield editor.getSourceEditor(); + let text = editor.sourceEditor.getText(); + text += NEW_RULE; + + let listChange = listenForMediaChange(UI); + editor.sourceEditor.setText(text); + yield listChange; + + let sidebar = editor.details.querySelector(".stylesheet-sidebar"); + let entries = [...sidebar.querySelectorAll(".media-rule-label")]; + is(entries.length, 4, "four @media rules after changing text"); + + testRule(entries[3], LABELS[3], false, LINE_NOS[3]); +} + function testRule(rule, text, matches, lineno) { let cond = rule.querySelector(".media-rule-condition"); is(cond.textContent, text, "media label is correct for " + text); let matched = !cond.classList.contains("media-condition-unmatched"); ok(matches ? matched : !matched, "media rule is " + (matches ? "matched" : "unmatched"));
--- a/browser/devtools/styleinspector/computed-view.js +++ b/browser/devtools/styleinspector/computed-view.js @@ -7,31 +7,30 @@ const {Cc, Ci, Cu} = require("chrome"); const ToolDefinitions = require("main").Tools; const {CssLogic} = require("devtools/styleinspector/css-logic"); const {ELEMENT_STYLE} = require("devtools/server/actors/styles"); const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); const {EventEmitter} = require("devtools/toolkit/event-emitter"); const {OutputParser} = require("devtools/output-parser"); -const {Tooltip} = require("devtools/shared/widgets/Tooltip"); const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/styleeditor/utils"); const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}); +const overlays = require("devtools/styleinspector/style-inspector-overlays"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/devtools/Templater.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm"); const FILTER_CHANGED_TIMEOUT = 300; const HTML_NS = "http://www.w3.org/1999/xhtml"; const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; -const TRANSFORM_HIGHLIGHTER_TYPE = "CssTransformHighlighter"; /** * Helper for long-running processes that should yield occasionally to * the mainloop. * * @param {Window} aWin * Timeouts will be set on this window when appropriate. * @param {Generator} aGenerator @@ -129,16 +128,17 @@ UpdateProcess.prototype = { * * @constructor */ function CssHtmlTree(aStyleInspector, aPageStyle) { this.styleWindow = aStyleInspector.window; this.styleDocument = aStyleInspector.window.document; this.styleInspector = aStyleInspector; + this.inspector = this.styleInspector.inspector; this.pageStyle = aPageStyle; this.propertyViews = []; this._outputParser = new OutputParser(); let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"]. getService(Ci.nsIXULChromeRegistry); this.getRTLAttr = chromeReg.isLocaleRTL("global") ? "rtl" : "ltr"; @@ -154,20 +154,20 @@ function CssHtmlTree(aStyleInspector, aP this.styleDocument.addEventListener("copy", this._onCopy); this.styleDocument.addEventListener("mousedown", this.focusWindow); this.styleDocument.addEventListener("contextmenu", this._onContextMenu); // Nodes used in templating this.root = this.styleDocument.getElementById("root"); this.templateRoot = this.styleDocument.getElementById("templateRoot"); - this.propertyContainer = this.styleDocument.getElementById("propertyContainer"); + this.element = this.styleDocument.getElementById("propertyContainer"); // Listen for click events - this.propertyContainer.addEventListener("click", this._onClick, false); + this.element.addEventListener("click", this._onClick, false); // No results text. this.noResults = this.styleDocument.getElementById("noResults"); // Refresh panel when color unit changed. this._handlePrefChange = this._handlePrefChange.bind(this); gDevTools.on("pref-changed", this._handlePrefChange); @@ -176,29 +176,24 @@ function CssHtmlTree(aStyleInspector, aP this._prefObserver = new PrefObserver("devtools."); this._prefObserver.on(PREF_ORIG_SOURCES, this._updateSourceLinks); CssHtmlTree.processTemplate(this.templateRoot, this.root, this); // The element that we're inspecting, and the document that it comes from. this.viewedElement = null; - // Properties preview tooltip - this.tooltip = new Tooltip(this.styleInspector.inspector.panelDoc); - this.tooltip.startTogglingOnHover(this.propertyContainer, - this._onTooltipTargetHover.bind(this)); - this._buildContextMenu(); this.createStyleViews(); - // Initialize the css transform highlighter if the target supports it - let hUtils = this.styleInspector.inspector.toolbox.highlighterUtils; - if (hUtils.hasCustomHighlighter(TRANSFORM_HIGHLIGHTER_TYPE)) { - this._initTransformHighlighter(); - } + // Add the tooltips and highlightersoverlay + this.tooltips = new overlays.TooltipsOverlay(this); + this.tooltips.addToView(); + this.highlighters = new overlays.HighlightersOverlay(this); + this.highlighters.addToView(); } /** * Memoized lookup of a l10n string from a string bundle. * @param {string} aName The key to lookup. * @returns A localized version of the given key. */ CssHtmlTree.l10n = function CssHtmlTree_l10n(aName) @@ -305,28 +300,70 @@ CssHtmlTree.prototype = { } // Hiding all properties for (let propView of this.propertyViews) { propView.refresh(); } return promise.resolve(undefined); } - this.tooltip.hide(); - if (aElement === this.viewedElement) { return promise.resolve(undefined); } this.viewedElement = aElement; this.refreshSourceFilter(); return this.refreshPanel(); }, + /** + * Get the type of a given node in the computed-view + * @param {DOMNode} node The node which we want information about + * @return {Object} The type information object contains the following props: + * - type {String} One of the VIEW_NODE_XXX_TYPE const in + * style-inspector-overlays + * - value {Object} Depends on the type of the node + * returns null of the node isn't anything we care about + */ + getNodeInfo: function(node) { + let type, value; + let classes = node.classList; + + if (classes.contains("property-name") || + classes.contains("property-value") || + (classes.contains("theme-link") && !classes.contains("link"))) { + // Go up to the common parent to find the property and value + let parent = node.parentNode; + while (!parent.classList.contains("property-view")) { + parent = parent.parentNode; + } + value = { + property: parent.querySelector(".property-name").textContent, + value: parent.querySelector(".property-value").textContent + }; + } + + if (classes.contains("property-name")) { + type = overlays.VIEW_NODE_PROPERTY_TYPE; + } else if (classes.contains("property-value")) { + type = overlays.VIEW_NODE_VALUE_TYPE; + } else if (classes.contains("theme-link")) { + type = overlays.VIEW_NODE_IMAGE_URL_TYPE; + value.url = node.textContent; + } else { + return null; + } + + return { + type: type, + value: value + }; + }, + _createPropertyViews: function() { if (this._createViewsPromise) { return this._createViewsPromise; } let deferred = promise.defer(); this._createViewsPromise = deferred.promise; @@ -347,17 +384,17 @@ CssHtmlTree.prototype = { } this.propertyViews.push(propView); }, onCancel: () => { deferred.reject("_createPropertyViews cancelled"); }, onDone: () => { // Completed callback. - this.propertyContainer.appendChild(fragment); + this.element.appendChild(fragment); this.noResults.hidden = this.numVisibleProperties > 0; deferred.resolve(undefined); } }); this._createViewsProcess.schedule(); return deferred.promise; }, @@ -402,17 +439,17 @@ CssHtmlTree.prototype = { let deferred = promise.defer(); this._refreshProcess = new UpdateProcess(this.styleWindow, this.propertyViews, { onItem: (aPropView) => { aPropView.refresh(); }, onDone: () => { this._refreshProcess = null; this.noResults.hidden = this.numVisibleProperties > 0; - this.styleInspector.inspector.emit("computed-view-refreshed"); + this.inspector.emit("computed-view-refreshed"); deferred.resolve(undefined); } }); this._refreshProcess.schedule(); return deferred.promise; }).then(null, (err) => console.error(err)); }, @@ -516,116 +553,16 @@ CssHtmlTree.prototype = { */ focusWindow: function(aEvent) { let win = this.styleDocument.defaultView; win.focus(); }, /** - * Get the css transform highlighter front, initializing it if needed - * @param a promise that resolves to the highlighter - */ - getTransformHighlighter: function() { - if (this.transformHighlighterPromise) { - return this.transformHighlighterPromise; - } - - let utils = this.styleInspector.inspector.toolbox.highlighterUtils; - this.transformHighlighterPromise = - utils.getHighlighterByType(TRANSFORM_HIGHLIGHTER_TYPE).then(highlighter => { - this.transformHighlighter = highlighter; - return this.transformHighlighter; - }); - - return this.transformHighlighterPromise; - }, - - _initTransformHighlighter: function() { - this.isTransformHighlighterShown = false; - - this._onMouseMove = this._onMouseMove.bind(this); - this._onMouseLeave = this._onMouseLeave.bind(this); - - this.propertyContainer.addEventListener("mousemove", this._onMouseMove, false); - this.propertyContainer.addEventListener("mouseleave", this._onMouseLeave, false); - }, - - _onMouseMove: function(event) { - if (event.target === this._lastHovered) { - return; - } - - if (this.isTransformHighlighterShown) { - this.isTransformHighlighterShown = false; - this.getTransformHighlighter().then(highlighter => highlighter.hide()); - } - - this._lastHovered = event.target; - if (this._lastHovered.classList.contains("property-value")) { - let propName = this._lastHovered.parentNode.querySelector(".property-name"); - - if (propName.textContent === "transform") { - this.isTransformHighlighterShown = true; - let node = this.styleInspector.inspector.selection.nodeFront; - this.getTransformHighlighter().then(highlighter => highlighter.show(node)); - } - } - }, - - _onMouseLeave: function(event) { - this._lastHovered = null; - if (this.isTransformHighlighterShown) { - this.isTransformHighlighterShown = false; - this.getTransformHighlighter().then(highlighter => highlighter.hide()); - } - }, - - /** - * Executed by the tooltip when the pointer hovers over an element of the view. - * Used to decide whether the tooltip should be shown or not and to actually - * put content in it. - * Checks if the hovered target is a css value we support tooltips for. - */ - _onTooltipTargetHover: function(target) - { - let inspector = this.styleInspector.inspector; - - // Test for image url - if (target.classList.contains("theme-link") && inspector.hasUrlToImageDataResolver) { - let propValue = target.parentNode; - let propName = propValue.parentNode.querySelector(".property-name"); - if (propName.textContent === "background-image") { - let maxDim = Services.prefs.getIntPref("devtools.inspector.imagePreviewTooltipSize"); - let uri = CssLogic.getBackgroundImageUriFromProperty(propValue.textContent); - return this.tooltip.setRelativeImageContent(uri, inspector.inspector, maxDim); - } - } - - if (target.classList.contains("property-value")) { - let propValue = target; - let propName = target.parentNode.querySelector(".property-name"); - - // Test for font family - if (propName.textContent === "font-family") { - let prop = propValue.textContent.toLowerCase(); - - if (prop !== "inherit" && prop !== "unset" && prop !== "initial") { - return this.tooltip.setFontFamilyContent(propValue.textContent, - inspector.selection.nodeFront); - } - } - } - - // If the target isn't one that should receive a tooltip, signal it by rejecting - // a promise - return promise.reject(); - }, - - /** * Create a context menu. */ _buildContextMenu: function() { let doc = this.styleDocument.defaultView.parent.document; this._contextmenu = this.styleDocument.createElementNS(XUL_NS, "menupopup"); this._contextmenu.addEventListener("popupshowing", this._contextMenuUpdate); @@ -751,18 +688,17 @@ CssHtmlTree.prototype = { }, _onClick: function(event) { let target = event.target; if (target.nodeName === "a") { event.stopPropagation(); event.preventDefault(); - let browserWin = this.styleInspector.inspector.target - .tab.ownerDocument.defaultView; + let browserWin = this.inspector.target.tab.ownerDocument.defaultView; browserWin.openUILinkIn(target.href, "tab"); } }, _onCopyColor: function() { clipboardHelper.copyString(this._colorToCopy, this.styleDocument); }, @@ -820,18 +756,18 @@ CssHtmlTree.prototype = { Services.prefs.setBoolPref(PREF_ORIG_SOURCES, !isEnabled); }, /** * Destructor for CssHtmlTree. */ destroy: function CssHtmlTree_destroy() { - delete this.viewedElement; - delete this._outputParser; + this.viewedElement = null; + this._outputParser = null; // Remove event listeners this.includeBrowserStylesCheckbox.removeEventListener("command", this.includeBrowserStylesChanged); this.searchField.removeEventListener("command", this.filterChanged); gDevTools.off("pref-changed", this._handlePrefChange); this._prefObserver.off(PREF_ORIG_SOURCES, this._updateSourceLinks); @@ -840,17 +776,17 @@ CssHtmlTree.prototype = { // Cancel tree construction if (this._createViewsProcess) { this._createViewsProcess.cancel(); } if (this._refreshProcess) { this._refreshProcess.cancel(); } - this.propertyContainer.removeEventListener("click", this._onClick, false); + this.element.removeEventListener("click", this._onClick, false); // Remove context menu if (this._contextmenu) { // Destroy the Select All menuitem. this.menuitemCopy.removeEventListener("command", this._onCopy); this.menuitemCopy = null; // Destroy the Copy menuitem. @@ -864,51 +800,41 @@ CssHtmlTree.prototype = { // Destroy the context menu. this._contextmenu.removeEventListener("popupshowing", this._contextMenuUpdate); this._contextmenu.parentNode.removeChild(this._contextmenu); this._contextmenu = null; } this.popupNode = null; - this.tooltip.stopTogglingOnHover(this.propertyContainer); - this.tooltip.destroy(); - - if (this.transformHighlighter) { - this.transformHighlighter.finalize(); - this.transformHighlighter = null; - - this.propertyContainer.removeEventListener("mousemove", this._onMouseMove, false); - this.propertyContainer.removeEventListener("mouseleave", this._onMouseLeave, false); - - this._lastHovered = null; - } + this.tooltips.destroy(); + this.highlighters.destroy(); // Remove bound listeners this.styleDocument.removeEventListener("contextmenu", this._onContextMenu); this.styleDocument.removeEventListener("copy", this._onCopy); this.styleDocument.removeEventListener("mousedown", this.focusWindow); // Nodes used in templating - delete this.root; - delete this.propertyContainer; - delete this.panel; + this.root = null; + this.element = null; + this.panel = null; // The document in which we display the results (csshtmltree.xul). - delete this.styleDocument; + this.styleDocument = null; for (let propView of this.propertyViews) { propView.destroy(); } // The element that we're inspecting, and the document that it comes from. - delete this.propertyViews; - delete this.styleWindow; - delete this.styleDocument; - delete this.styleInspector; + this.propertyViews = null; + this.styleWindow = null; + this.styleDocument = null; + this.styleInspector = null; } }; function PropertyInfo(aTree, aName) { this.tree = aTree; this.name = aName; } PropertyInfo.prototype = { @@ -1187,22 +1113,22 @@ PropertyView.prototype = { if (!this.matchedExpanded) { return; } this._matchedSelectorResponse = matched; CssHtmlTree.processTemplate(this.templateMatchedSelectors, this.matchedSelectorsContainer, this); this.matchedExpander.setAttribute("open", ""); - this.tree.styleInspector.inspector.emit("computed-view-property-expanded"); + this.tree.inspector.emit("computed-view-property-expanded"); }).then(null, console.error); } else { this.matchedSelectorsContainer.innerHTML = ""; this.matchedExpander.removeAttribute("open"); - this.tree.styleInspector.inspector.emit("computed-view-property-collapsed"); + this.tree.inspector.emit("computed-view-property-collapsed"); return promise.resolve(undefined); } }, get matchedSelectors() { return this._matchedSelectorResponse; }, @@ -1251,17 +1177,17 @@ PropertyView.prototype = { aEvent.preventDefault(); }, /** * The action when a user clicks on the MDN help link for a property. */ mdnLinkClick: function PropertyView_mdnLinkClick(aEvent) { - let inspector = this.tree.styleInspector.inspector; + let inspector = this.tree.inspector; if (inspector.target.tab) { let browserWin = inspector.target.tab.ownerDocument.defaultView; browserWin.openUILinkIn(this.link, "tab"); } aEvent.preventDefault(); }, @@ -1393,19 +1319,19 @@ SelectorView.prototype = { /** * Update the text of the source link to reflect whether we're showing * original sources or not. */ updateSourceLink: function() { this.updateSource().then((oldSource) => { - if (oldSource != this.source && this.tree.propertyContainer) { + if (oldSource != this.source && this.tree.element) { let selector = '[sourcelocation="' + oldSource + '"]'; - let link = this.tree.propertyContainer.querySelector(selector); + let link = this.tree.element.querySelector(selector); if (link) { link.textContent = this.source; link.setAttribute("sourcelocation", this.source); } } }); }, @@ -1464,17 +1390,17 @@ SelectorView.prototype = { * * We can only view stylesheets contained in document.styleSheets inside the * style editor. * * @param aEvent The click event */ openStyleEditor: function(aEvent) { - let inspector = this.tree.styleInspector.inspector; + let inspector = this.tree.inspector; let rule = this.selectorInfo.rule; // The style editor can only display stylesheets coming from content because // chrome stylesheets are not listed in the editor's stylesheet selector. // // If the stylesheet is a content stylesheet we send it to the style // editor else we display it in the view source window. let sheet = rule.parentStyleSheet;
--- a/browser/devtools/styleinspector/rule-view.js +++ b/browser/devtools/styleinspector/rule-view.js @@ -7,29 +7,28 @@ "use strict"; const {Cc, Ci, Cu} = require("chrome"); const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); const {CssLogic} = require("devtools/styleinspector/css-logic"); const {InplaceEditor, editableField, editableItem} = require("devtools/shared/inplace-editor"); const {ELEMENT_STYLE, PSEUDO_ELEMENTS} = require("devtools/server/actors/styles"); const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}); -const {Tooltip, SwatchColorPickerTooltip} = require("devtools/shared/widgets/Tooltip"); const {OutputParser} = require("devtools/output-parser"); const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/styleeditor/utils"); const {parseSingleValue, parseDeclarations} = require("devtools/styleinspector/css-parsing-utils"); +const overlays = require("devtools/styleinspector/style-inspector-overlays"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); const HTML_NS = "http://www.w3.org/1999/xhtml"; const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; const PREF_UA_STYLES = "devtools.inspector.showUserAgentStyles"; const PREF_DEFAULT_COLOR_UNIT = "devtools.defaultColorUnit"; -const TRANSFORM_HIGHLIGHTER_TYPE = "CssTransformHighlighter"; /** * These regular expressions are adapted from firebug's css.js, and are * used to parse CSSStyleDeclaration's cssText attribute. */ // Used to split on css line separators const CSS_LINE_RE = /(?:[^;\(]*(?:\([^\)]*?\))?[^;\(]*)*;?/g; @@ -1095,33 +1094,24 @@ function CssRuleView(aInspector, aDoc, a this.showUserAgentStyles = Services.prefs.getBoolPref(PREF_UA_STYLES); let options = { autoSelect: true, theme: "auto" }; this.popup = new AutocompletePopup(aDoc.defaultView.parent.document, options); - // Create a tooltip for previewing things in the rule view (images for now) - this.previewTooltip = new Tooltip(this.inspector.panelDoc); - this.previewTooltip.startTogglingOnHover(this.element, - this._onTooltipTargetHover.bind(this)); - - // Also create a more complex tooltip for editing colors with the spectrum - // color picker - this.colorPicker = new SwatchColorPickerTooltip(this.inspector.panelDoc); - this._buildContextMenu(); this._showEmpty(); - // Initialize the css transform highlighter if the target supports it - let hUtils = this.inspector.toolbox.highlighterUtils; - if (hUtils.hasCustomHighlighter(TRANSFORM_HIGHLIGHTER_TYPE)) { - this._initTransformHighlighter(); - } + // Add the tooltips and highlighters to the view + this.tooltips = new overlays.TooltipsOverlay(this); + this.tooltips.addToView(); + this.highlighters = new overlays.HighlightersOverlay(this); + this.highlighters.addToView(); } exports.CssRuleView = CssRuleView; CssRuleView.prototype = { // The element that we're inspecting. _viewedElement: null, @@ -1161,147 +1151,16 @@ CssRuleView.prototype = { popupset = doc.createElementNS(XUL_NS, "popupset"); doc.documentElement.appendChild(popupset); } popupset.appendChild(this._contextmenu); }, /** - * Get the css transform highlighter front, initializing it if needed - * @param a promise that resolves to the highlighter - */ - getTransformHighlighter: function() { - if (this.transformHighlighterPromise) { - return this.transformHighlighterPromise; - } - - let utils = this.inspector.toolbox.highlighterUtils; - this.transformHighlighterPromise = - utils.getHighlighterByType(TRANSFORM_HIGHLIGHTER_TYPE).then(highlighter => { - this.transformHighlighter = highlighter; - return this.transformHighlighter; - }); - - return this.transformHighlighterPromise; - }, - - _initTransformHighlighter: function() { - this.isTransformHighlighterShown = false; - - this._onMouseMove = this._onMouseMove.bind(this); - this._onMouseLeave = this._onMouseLeave.bind(this); - - this.element.addEventListener("mousemove", this._onMouseMove, false); - this.element.addEventListener("mouseleave", this._onMouseLeave, false); - }, - - _onMouseMove: function(event) { - if (event.target === this._lastHovered) { - return; - } - - if (this.isTransformHighlighterShown) { - this.isTransformHighlighterShown = false; - this.getTransformHighlighter().then(highlighter => highlighter.hide()); - } - - this._lastHovered = event.target; - let prop = event.target.textProperty; - let isHighlightable = prop && prop.name === "transform" && - prop.enabled && !prop.overridden && - !prop.rule.pseudoElement; - - if (isHighlightable) { - this.isTransformHighlighterShown = true; - let node = this.inspector.selection.nodeFront; - this.getTransformHighlighter().then(highlighter => highlighter.show(node)); - } - }, - - _onMouseLeave: function(event) { - this._lastHovered = null; - if (this.isTransformHighlighterShown) { - this.isTransformHighlighterShown = false; - this.getTransformHighlighter().then(highlighter => highlighter.hide()); - } - }, - - /** - * Which type of hover-tooltip should be shown for the given element? - * This depends on the element: does it contain a URL, a font-family, ... - * @param {DOMNode} el The element to test - * @return {String} The type of hover-tooltip - */ - _getHoverTooltipTypeForTarget: function(el) { - let prop = el.textProperty; - - // Test for image - let isUrl = el.classList.contains("theme-link") && - el.parentNode.classList.contains("ruleview-propertyvalue"); - if (this.inspector.hasUrlToImageDataResolver && isUrl) { - return "image"; - } - - // Test for font-family - let propertyRoot = el.parentNode; - let propertyNameNode = propertyRoot.querySelector(".ruleview-propertyname"); - if (!propertyNameNode) { - propertyRoot = propertyRoot.parentNode; - propertyNameNode = propertyRoot.querySelector(".ruleview-propertyname"); - } - let propertyName; - if (propertyNameNode) { - propertyName = propertyNameNode.textContent; - } - if (propertyName === "font-family" && el.classList.contains("ruleview-propertyvalue")) { - return "font"; - } - }, - - /** - * Executed by the tooltip when the pointer hovers over an element of the view. - * Used to decide whether the tooltip should be shown or not and to actually - * put content in it. - * Checks if the hovered target is a css value we support tooltips for. - * @param {DOMNode} target - * @return {Boolean|Promise} Either a boolean or a promise, used by the - * Tooltip class to wait for the content to be put in the tooltip and finally - * decide whether or not the tooltip should be shown. - */ - _onTooltipTargetHover: function(target) { - let tooltipType = this._getHoverTooltipTypeForTarget(target); - if (!tooltipType) { - return false; - } - - if (this.colorPicker.tooltip.isShown()) { - this.colorPicker.revert(); - this.colorPicker.hide(); - } - - if (tooltipType === "image") { - let prop = target.parentNode.textProperty; - let dim = Services.prefs.getIntPref("devtools.inspector.imagePreviewTooltipSize"); - let uri = CssLogic.getBackgroundImageUriFromProperty(prop.value, prop.rule.domRule.href); - return this.previewTooltip.setRelativeImageContent(uri, this.inspector.inspector, dim); - } - if (tooltipType === "font") { - let prop = target.textContent.toLowerCase(); - - if (prop !== "inherit" && prop !== "unset" && prop !== "initial") { - return this.previewTooltip.setFontFamilyContent(target.textContent, - this.inspector.selection.nodeFront); - } - } - - return false; - }, - - /** * Update the context menu. This means enabling or disabling menuitems as * appropriate. */ _contextMenuUpdate: function() { let win = this.doc.defaultView; // Copy selection. let selection = win.getSelection(); @@ -1334,16 +1193,75 @@ CssRuleView.prototype = { _strings.GetStringFromName(label)); let accessKey = label + ".accessKey"; this.menuitemSources.setAttribute("accesskey", _strings.GetStringFromName(accessKey)); }, /** + * Get the type of a given node in the rule-view + * @param {DOMNode} node The node which we want information about + * @return {Object} The type information object contains the following props: + * - type {String} One of the VIEW_NODE_XXX_TYPE const in + * style-inspector-overlays + * - value {Object} Depends on the type of the node + * returns null of the node isn't anything we care about + */ + getNodeInfo: function(node) { + let type, value; + let classes = node.classList; + let prop = getParentTextProperty(node); + + if (classes.contains("ruleview-propertyname") && prop) { + type = overlays.VIEW_NODE_PROPERTY_TYPE; + value = { + property: node.textContent, + value: getPropertyNameAndValue(node).value, + enabled: prop.enabled, + overridden: prop.overridden, + pseudoElement: prop.rule.pseudoElement, + sheetHref: prop.rule.domRule.href + }; + } else if (classes.contains("ruleview-propertyvalue") && prop) { + type = overlays.VIEW_NODE_VALUE_TYPE; + value = { + property: getPropertyNameAndValue(node).name, + value: node.textContent, + enabled: prop.enabled, + overridden: prop.overridden, + pseudoElement: prop.rule.pseudoElement, + sheetHref: prop.rule.domRule.href + }; + } else if (classes.contains("theme-link") && prop) { + type = overlays.VIEW_NODE_IMAGE_URL_TYPE; + value = { + property: getPropertyNameAndValue(node).name, + value: node.parentNode.textContent, + url: node.textContent, + enabled: prop.enabled, + overridden: prop.overridden, + pseudoElement: prop.rule.pseudoElement, + sheetHref: prop.rule.domRule.href + }; + } else if (classes.contains("ruleview-selector-unmatched") || + classes.contains("ruleview-selector-matched")) { + type = overlays.VIEW_NODE_SELECTOR_TYPE; + value = node.textContent; + } else { + return null; + } + + return { + type: type, + value: value + }; + }, + + /** * A helper that determines if the popup was opened with a click to a color * value and saves the color to this._colorToCopy. * * @return {Boolean} * true if click on color opened the popup, false otherwise. */ _isColorPopup: function () { this._colorToCopy = ""; @@ -1440,17 +1358,17 @@ CssRuleView.prototype = { this.pageStyle = aPageStyle; }, /** * Return {bool} true if the rule view currently has an input editor visible. */ get isEditing() { return this.element.querySelectorAll(".styleinspector-propertyeditor").length > 0 - || this.colorPicker.tooltip.isShown(); + || this.tooltips.colorPicker.tooltip.isShown(); }, _handlePrefChange: function(pref) { if (pref === PREF_UA_STYLES) { this.showUserAgentStyles = Services.prefs.getBoolPref(pref); } // Reselect the currently selected element @@ -1512,29 +1430,18 @@ CssRuleView.prototype = { this._contextmenu.removeEventListener("popupshowing", this._contextMenuUpdate); this._contextmenu.parentNode.removeChild(this._contextmenu); this._contextmenu = null; } // We manage the popupNode ourselves so we also need to destroy it. this.doc.popupNode = null; - this.previewTooltip.stopTogglingOnHover(this.element); - this.previewTooltip.destroy(); - this.colorPicker.destroy(); - - if (this.transformHighlighter) { - this.transformHighlighter.finalize(); - this.transformHighlighter = null; - - this.element.removeEventListener("mousemove", this._onMouseMove, false); - this.element.removeEventListener("mouseleave", this._onMouseLeave, false); - - this._lastHovered = null; - } + this.tooltips.destroy(); + this.highlighters.destroy(); if (this.element.parentNode) { this.element.parentNode.removeChild(this.element); } if (this.elementStyle) { this.elementStyle.destroy(); } @@ -1635,19 +1542,16 @@ CssRuleView.prototype = { /** * Clear the rule view. */ clear: function() { this._clearRules(); this._viewedElement = null; this._elementStyle = null; - - this.previewTooltip.hide(); - this.colorPicker.hide(); }, /** * Called when the user has made changes to the ElementStyle. * Emits an event that clients can listen to. */ _changed: function() { var evt = this.doc.createEvent("Events"); @@ -2149,18 +2053,18 @@ function TextPropertyEditor(aRuleEditor, } TextPropertyEditor.prototype = { /** * Boolean indicating if the name or value is being currently edited. */ get editing() { return !!(this.nameSpan.inplaceEditor || this.valueSpan.inplaceEditor || - this.ruleEditor.ruleView.colorPicker.tooltip.isShown() || - this.ruleEditor.ruleView.colorPicker.eyedropperOpen) || + this.ruleEditor.ruleView.tooltips.colorPicker.tooltip.isShown() || + this.ruleEditor.ruleView.tooltips.colorPicker.eyedropperOpen) || this.popup.isOpen; }, /** * Create the property editor's DOM. */ _create: function() { this.element = this.doc.createElementNS(HTML_NS, "li"); @@ -2202,19 +2106,20 @@ TextPropertyEditor.prototype = { // Property value, editable when focused. Changes to the // property value are applied as they are typed, and reverted // if the user presses escape. this.valueSpan = createChild(propertyContainer, "span", { class: "ruleview-propertyvalue theme-fg-color1", tabindex: this.ruleEditor.isEditable ? "0" : "-1", }); - // Storing the TextProperty on the valuespan for easy access + // Storing the TextProperty on the elements for easy access // (for instance by the tooltip) this.valueSpan.textProperty = this.prop; + this.nameSpan.textProperty = this.prop; // Save the initial value as the last committed value, // for restoring after pressing escape. this.committed = { name: this.prop.name, value: this.prop.value, priority: this.prop.priority }; appendText(propertyContainer, ";"); @@ -2400,17 +2305,17 @@ TextPropertyEditor.prototype = { // Attach the color picker tooltip to the color swatches this._swatchSpans = this.valueSpan.querySelectorAll("." + swatchClass); if (this.ruleEditor.isEditable) { for (let span of this._swatchSpans) { // Capture the original declaration value to be able to revert later let originalValue = this.valueSpan.textContent; // Adding this swatch to the list of swatches our colorpicker knows about - this.ruleEditor.ruleView.colorPicker.addSwatch(span, { + this.ruleEditor.ruleView.tooltips.colorPicker.addSwatch(span, { onPreview: () => this._previewValue(this.valueSpan.textContent), onCommit: () => this._applyNewValue(this.valueSpan.textContent), onRevert: () => this._applyNewValue(originalValue) }); } } // Populate the computed styles. @@ -2541,22 +2446,23 @@ TextPropertyEditor.prototype = { /** * Remove property from style and the editors from DOM. * Begin editing next available property. */ remove: function() { if (this._swatchSpans && this._swatchSpans.length) { for (let span of this._swatchSpans) { - this.ruleEditor.ruleView.colorPicker.removeSwatch(span); + this.ruleEditor.ruleView.tooltips.colorPicker.removeSwatch(span); } } this.element.parentNode.removeChild(this.element); this.ruleEditor.rule.editClosestTextProperty(this.prop); + this.nameSpan.textProperty = null; this.valueSpan.textProperty = null; this.prop.remove(); }, /** * Called when a value editor closes. If the user pressed escape, * revert to the value this property had before editing. * @@ -2865,16 +2771,73 @@ function blurOnMultipleProperties(e) { /** * Append a text node to an element. */ function appendText(aParent, aText) { aParent.appendChild(aParent.ownerDocument.createTextNode(aText)); } +/** + * Walk up the DOM from a given node until a parent property holder is found. + * For elements inside the computed property list, the non-computed parent + * property holder will be returned + * @param {DOMNode} node The node to start from + * @return {DOMNode} The parent property holder node, or null if not found + */ +function getParentTextPropertyHolder(node) { + while (true) { + if (!node || !node.classList) { + return null; + } + if (node.classList.contains("ruleview-property")) { + return node; + } + node = node.parentNode; + } +} + +/** + * For any given node, find the TextProperty it is in if any + * @param {DOMNode} node The node to start from + * @return {TextProperty} + */ +function getParentTextProperty(node) { + let parent = getParentTextPropertyHolder(node); + if (!parent) { + return null; + } + return parent.querySelector(".ruleview-propertyvalue").textProperty; +} + +/** + * Walker up the DOM from a given node until a parent property holder is found, + * and return the textContent for the name and value nodes. + * Stops at the first property found, so if node is inside the computed property + * list, the computed property will be returned + * @param {DOMNode} node The node to start from + * @return {Object} {name, value} + */ +function getPropertyNameAndValue(node) { + while (true) { + if (!node || !node.classList) { + return null; + } + // Check first for ruleview-computed since it's the deepest + if (node.classList.contains("ruleview-computed") || + node.classList.contains("ruleview-property")) { + return { + name: node.querySelector(".ruleview-propertyname").textContent, + value: node.querySelector(".ruleview-propertyvalue").textContent + }; + } + node = node.parentNode; + } +} + XPCOMUtils.defineLazyGetter(this, "clipboardHelper", function() { return Cc["@mozilla.org/widget/clipboardhelper;1"]. getService(Ci.nsIClipboardHelper); }); XPCOMUtils.defineLazyGetter(this, "_strings", function() { return Services.strings.createBundle( "chrome://global/locale/devtools/styleinspector.properties");
new file mode 100644 --- /dev/null +++ b/browser/devtools/styleinspector/style-inspector-overlays.js @@ -0,0 +1,375 @@ +/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +"use strict"; + +// The style-inspector overlays are: +// - tooltips that appear when hovering over property values +// - editor tooltips that appear when clicking color swatches, etc. +// - in-content highlighters that appear when hovering over property values +// - etc. + +const {Cc, Ci, Cu} = require("chrome"); +const { + Tooltip, + SwatchColorPickerTooltip +} = require("devtools/shared/widgets/Tooltip"); +const {CssLogic} = require("devtools/styleinspector/css-logic"); +const {Promise:promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); +Cu.import("resource://gre/modules/Task.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +const PREF_IMAGE_TOOLTIP_SIZE = "devtools.inspector.imagePreviewTooltipSize"; + +// Types of existing tooltips +const TOOLTIP_IMAGE_TYPE = "image"; +const TOOLTIP_FONTFAMILY_TYPE = "font-family"; + +// Types of existing highlighters +const HIGHLIGHTER_TRANSFORM_TYPE = "CssTransformHighlighter"; +const HIGHLIGHTER_TYPES = [ + HIGHLIGHTER_TRANSFORM_TYPE +]; + +// Types of nodes in the rule/computed-view +const VIEW_NODE_SELECTOR_TYPE = exports.VIEW_NODE_SELECTOR_TYPE = 1; +const VIEW_NODE_PROPERTY_TYPE = exports.VIEW_NODE_PROPERTY_TYPE = 2; +const VIEW_NODE_VALUE_TYPE = exports.VIEW_NODE_VALUE_TYPE = 3; +const VIEW_NODE_IMAGE_URL_TYPE = exports.VIEW_NODE_IMAGE_URL_TYPE = 4; + +/** + * Manages all highlighters in the style-inspector. + * @param {CssRuleView|CssHtmlTree} view Either the rule-view or computed-view + * panel + */ +function HighlightersOverlay(view) { + this.view = view; + + let {CssRuleView} = require("devtools/styleinspector/rule-view"); + this.isRuleView = view instanceof CssRuleView; + + this.highlighterUtils = this.view.inspector.toolbox.highlighterUtils; + + this._onMouseMove = this._onMouseMove.bind(this); + this._onMouseLeave = this._onMouseLeave.bind(this); + + this.promises = {}; + this.highlighters = {}; + + // Only initialize the overlay if at least one of the highlighter types is + // supported + this.supportsHighlighters = HIGHLIGHTER_TYPES.some(type => { + return this.highlighterUtils.hasCustomHighlighter(type); + }); +} + +exports.HighlightersOverlay = HighlightersOverlay; + +HighlightersOverlay.prototype = { + /** + * Add the highlighters overlay to the view. This will start tracking mouse + * movements and display highlighters when needed + */ + addToView: function() { + if (!this.supportsHighlighters || this._isStarted || this._isDestroyed) { + return; + } + + let el = this.view.element; + el.addEventListener("mousemove", this._onMouseMove, false); + el.addEventListener("mouseleave", this._onMouseLeave, false); + + this._isStarted = true; + }, + + /** + * Remove the overlay from the current view. This will stop tracking mouse + * movement and showing highlighters + */ + removeFromView: function() { + if (!this.supportsHighlighters || !this._isStarted || this._isDestroyed) { + return; + } + + this._hideCurrent(); + + let el = this.view.element; + el.removeEventListener("mousemove", this._onMouseMove, false); + el.removeEventListener("mouseleave", this._onMouseLeave, false); + + this._isStarted = false; + }, + + _onMouseMove: function(event) { + // Bail out if the target is the same as for the last mousemove + if (event.target === this._lastHovered) { + return; + } + + // Only one highlighter can be displayed at a time, hide the currently shown + this._hideCurrent(); + + this._lastHovered = event.target; + + let nodeInfo = this.view.getNodeInfo(event.target); + if (!nodeInfo) { + return; + } + + // Choose the type of highlighter required for the hovered node + let type; + if (this._isRuleViewTransform(nodeInfo) || + this._isComputedViewTransform(nodeInfo)) { + type = HIGHLIGHTER_TRANSFORM_TYPE; + } + + if (type) { + this.highlighterShown = type; + let node = this.view.inspector.selection.nodeFront; + this._getHighlighter(type).then(highlighter => highlighter.show(node)); + } + }, + + _onMouseLeave: function(event) { + this._lastHovered = null; + this._hideCurrent(); + }, + + /** + * Is the current hovered node a css transform property value in the rule-view + * @param {Object} nodeInfo + * @return {Boolean} + */ + _isRuleViewTransform: function(nodeInfo) { + let isTransform = nodeInfo.type === VIEW_NODE_VALUE_TYPE && + nodeInfo.value.property === "transform"; + let isEnabled = nodeInfo.value.enabled && + !nodeInfo.value.overridden && + !nodeInfo.value.pseudoElement; + return this.isRuleView && isTransform && isEnabled; + }, + + /** + * Is the current hovered node a css transform property value in the + * computed-view + * @param {Object} nodeInfo + * @return {Boolean} + */ + _isComputedViewTransform: function(nodeInfo) { + let isTransform = nodeInfo.type === VIEW_NODE_VALUE_TYPE && + nodeInfo.value.property === "transform"; + return !this.isRuleView && isTransform; + }, + + /** + * Hide the currently shown highlighter + */ + _hideCurrent: function() { + if (this.highlighterShown) { + this._getHighlighter(this.highlighterShown).then(highlighter => { + highlighter.hide(); + this.highlighterShown = null; + }); + } + }, + + /** + * Get a highlighter front given a type. It will only be initialized once + * @param {String} type The highlighter type. One of this.highlighters + * @return a promise that resolves to the highlighter + */ + _getHighlighter: function(type) { + let utils = this.highlighterUtils; + if (!utils.hasCustomHighlighter(type)) { + return promise.reject(); + } + + if (this.promises[type]) { + return this.promises[type]; + } + + return this.promises[type] = utils.getHighlighterByType(type).then(highlighter => { + this.highlighters[type] = highlighter; + return highlighter; + }); + }, + + /** + * Destroy this overlay instance, removing it from the view and destroying + * all initialized highlighters + */ + destroy: function() { + this.removeFromView(); + + for (let type in this.highlighters) { + if (this.highlighters[type]) { + this.highlighters[type].finalize(); + this.highlighters[type] = null; + } + } + + this.promises = null; + this.view = null; + this.highlighterUtils = null; + + this._isDestroyed = true; + } +}; + +/** + * Manages all tooltips in the style-inspector. + * @param {CssRuleView|CssHtmlTree} view Either the rule-view or computed-view + * panel + */ +function TooltipsOverlay(view) { + this.view = view; + + let {CssRuleView} = require("devtools/styleinspector/rule-view"); + this.isRuleView = view instanceof CssRuleView; + + this._onNewSelection = this._onNewSelection.bind(this); + this.view.inspector.selection.on("new-node-front", this._onNewSelection); +} + +exports.TooltipsOverlay = TooltipsOverlay; + +TooltipsOverlay.prototype = { + /** + * Add the tooltips overlay to the view. This will start tracking mouse + * movements and display tooltips when needed + */ + addToView: function() { + if (this._isStarted || this._isDestroyed) { + return; + } + + // Image, fonts, ... preview tooltip + this.previewTooltip = new Tooltip(this.view.inspector.panelDoc); + this.previewTooltip.startTogglingOnHover(this.view.element, + this._onPreviewTooltipTargetHover.bind(this)); + + // Color picker tooltip + if (this.isRuleView) { + this.colorPicker = new SwatchColorPickerTooltip(this.view.inspector.panelDoc); + } + + this._isStarted = true; + }, + + /** + * Remove the tooltips overlay from the view. This will stop tracking mouse + * movements and displaying tooltips + */ + removeFromView: function() { + if (!this._isStarted || this._isDestroyed) { + return; + } + + this.previewTooltip.stopTogglingOnHover(this.view.element); + this.previewTooltip.destroy(); + + if (this.colorPicker) { + this.colorPicker.destroy(); + } + + this._isStarted = false; + }, + + /** + * Given a hovered node info, find out which type of tooltip should be shown, + * if any + * @param {Object} nodeInfo + * @return {String} The tooltip type to be shown, or null + */ + _getTooltipType: function({type, value:prop}) { + let tooltipType = null; + let inspector = this.view.inspector; + + // Image preview tooltip + if (type === VIEW_NODE_IMAGE_URL_TYPE && inspector.hasUrlToImageDataResolver) { + let dim = Services.prefs.getIntPref(PREF_IMAGE_TOOLTIP_SIZE); + let uri = CssLogic.getBackgroundImageUriFromProperty(prop.value, + prop.sheetHref); // sheetHref is undefined for computed-view properties, + // but we don't care as URIs are absolute + tooltipType = TOOLTIP_IMAGE_TYPE; + } + + // Font preview tooltip + if (type === VIEW_NODE_VALUE_TYPE && prop.property === "font-family") { + let value = prop.value.toLowerCase(); + if (value !== "inherit" && value !== "unset" && value !== "initial") { + tooltipType = TOOLTIP_FONTFAMILY_TYPE; + } + } + + return tooltipType; + }, + + /** + * Executed by the tooltip when the pointer hovers over an element of the view. + * Used to decide whether the tooltip should be shown or not and to actually + * put content in it. + * Checks if the hovered target is a css value we support tooltips for. + * @param {DOMNode} target The currently hovered node + */ + _onPreviewTooltipTargetHover: function(target) { + let nodeInfo = this.view.getNodeInfo(target); + if (!nodeInfo) { + // The hovered node isn't something we care about + return promise.reject(); + } + + let type = this._getTooltipType(nodeInfo); + if (!type) { + // There is no tooltip type defined for the hovered node + return promise.reject(); + } + + if (this.isRuleView && this.colorPicker.tooltip.isShown()) { + this.colorPicker.revert(); + this.colorPicker.hide(); + } + + let inspector = this.view.inspector; + + if (type === TOOLTIP_IMAGE_TYPE) { + let dim = Services.prefs.getIntPref(PREF_IMAGE_TOOLTIP_SIZE); + let uri = CssLogic.getBackgroundImageUriFromProperty(nodeInfo.value.value, + nodeInfo.value.sheetHref); // sheetHref is undefined for computed-view + // properties, but we don't care as uris are + // absolute + return this.previewTooltip.setRelativeImageContent(uri, + inspector.inspector, dim); + } + + if (type === TOOLTIP_FONTFAMILY_TYPE) { + return this.previewTooltip.setFontFamilyContent(nodeInfo.value.value, + inspector.selection.nodeFront); + } + }, + + _onNewSelection: function() { + if (this.previewTooltip) { + this.previewTooltip.hide(); + } + + if (this.colorPicker) { + this.colorPicker.hide(); + } + }, + + /** + * Destroy this overlay instance, removing it from the view + */ + destroy: function() { + this.removeFromView(); + + this.view.inspector.selection.off("new-node-front", this._onNewSelection); + this.view = null; + + this._isDestroyed = true; + } +};
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-and-image-tooltip_01.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-and-image-tooltip_01.js @@ -25,22 +25,22 @@ let test = asyncTest(function*() { let value = getRuleViewProperty(view, "body", "background").valueSpan; let swatch = value.querySelector(".ruleview-colorswatch"); let url = value.querySelector(".theme-link"); yield testImageTooltipAfterColorChange(swatch, url, view); }); function* testImageTooltipAfterColorChange(swatch, url, ruleView) { info("First, verify that the image preview tooltip works"); - let anchor = yield isHoverTooltipTarget(ruleView.previewTooltip, url); + let anchor = yield isHoverTooltipTarget(ruleView.tooltips.previewTooltip, url); ok(anchor, "The image preview tooltip is shown on the url span"); is(anchor, url, "The anchor returned by the showOnHover callback is correct"); info("Open the color picker tooltip and change the color"); - let picker = ruleView.colorPicker; + let picker = ruleView.tooltips.colorPicker; let onShown = picker.tooltip.once("shown"); swatch.click(); yield onShown; yield simulateColorPickerChange(picker, [0, 0, 0, 1], { element: content.document.body, name: "backgroundImage", value: 'url("chrome://global/skin/icons/warning-64.png"), linear-gradient(rgb(0, 0, 0), rgb(255, 0, 102) 400px)' }); @@ -49,12 +49,12 @@ function* testImageTooltipAfterColorChan let onHidden = picker.tooltip.once("hidden"); EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView); yield onHidden; info("Verify again that the image preview tooltip works"); // After a color change, the property is re-populated, we need to get the new // dom node url = getRuleViewProperty(ruleView, "body", "background").valueSpan.querySelector(".theme-link"); - let anchor = yield isHoverTooltipTarget(ruleView.previewTooltip, url); + let anchor = yield isHoverTooltipTarget(ruleView.tooltips.previewTooltip, url); ok(anchor, "The image preview tooltip is shown on the url span"); is(anchor, url, "The anchor returned by the showOnHover callback is correct"); }
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-and-image-tooltip_02.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-and-image-tooltip_02.js @@ -26,17 +26,17 @@ let test = asyncTest(function*() { yield testColorChangeIsntRevertedWhenOtherTooltipIsShown(view); }); function* testColorChangeIsntRevertedWhenOtherTooltipIsShown(ruleView) { let swatch = getRuleViewProperty(ruleView, "body", "background").valueSpan .querySelector(".ruleview-colorswatch"); info("Open the color picker tooltip and change the color"); - let picker = ruleView.colorPicker; + let picker = ruleView.tooltips.colorPicker; let onShown = picker.tooltip.once("shown"); swatch.click(); yield onShown; yield simulateColorPickerChange(picker, [0, 0, 0, 1], { element: content.document.body, name: "backgroundColor", value: "rgb(0, 0, 0)" @@ -44,18 +44,18 @@ function* testColorChangeIsntRevertedWhe let spectrum = yield picker.spectrum; let onHidden = picker.tooltip.once("hidden"); EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView); yield onHidden; info("Open the image preview tooltip"); let value = getRuleViewProperty(ruleView, "body", "background").valueSpan; let url = value.querySelector(".theme-link"); - let onShown = ruleView.previewTooltip.once("shown"); - let anchor = yield isHoverTooltipTarget(ruleView.previewTooltip, url); - ruleView.previewTooltip.show(anchor); + let onShown = ruleView.tooltips.previewTooltip.once("shown"); + let anchor = yield isHoverTooltipTarget(ruleView.tooltips.previewTooltip, url); + ruleView.tooltips.previewTooltip.show(anchor); yield onShown; info("Image tooltip is shown, verify that the swatch is still correct"); let swatch = value.querySelector(".ruleview-colorswatch"); is(swatch.style.backgroundColor, "rgb(0, 0, 0)", "The swatch's color is correct"); is(swatch.nextSibling.textContent, "#000", "The color name is correct"); }
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-appears-on-swatch-click.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-appears-on-swatch-click.js @@ -32,17 +32,17 @@ let test = asyncTest(function*() { for (let swatch of [cSwatch, bgSwatch, bSwatch]) { info("Testing that the colorpicker appears colorswatch click"); yield testColorPickerAppearsOnColorSwatchClick(view, swatch); } }); function* testColorPickerAppearsOnColorSwatchClick(view, swatch) { - let cPicker = view.colorPicker; + let cPicker = view.tooltips.colorPicker; ok(cPicker, "The rule-view has the expected colorPicker property"); let cPickerPanel = cPicker.tooltip.panel; ok(cPickerPanel, "The XUL panel for the color picker exists"); let onShown = cPicker.tooltip.once("shown"); swatch.click(); yield onShown;
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-commit-on-ENTER.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-commit-on-ENTER.js @@ -21,17 +21,17 @@ let test = asyncTest(function*() { let {toolbox, inspector, view} = yield openRuleView(); let swatch = getRuleViewProperty(view, "body" , "border").valueSpan .querySelector(".ruleview-colorswatch"); yield testPressingEnterCommitsChanges(swatch, view); }); function* testPressingEnterCommitsChanges(swatch, ruleView) { - let cPicker = ruleView.colorPicker; + let cPicker = ruleView.tooltips.colorPicker; let onShown = cPicker.tooltip.once("shown"); swatch.click(); yield onShown; yield simulateColorPickerChange(cPicker, [0, 255, 0, .5], { element: content.document.body, name: "borderLeftColor",
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-edit-gradient.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-edit-gradient.js @@ -52,17 +52,17 @@ function testColorParsing(view) { function* testPickingNewColor(view) { // Grab the first color swatch and color in the gradient let ruleEl = getRuleViewProperty(view, "body", "background-image"); let swatchEl = ruleEl.valueSpan.querySelector(".ruleview-colorswatch"); let colorEl = ruleEl.valueSpan.querySelector(".ruleview-color"); info("Getting the color picker tooltip and clicking on the swatch to show it"); - let cPicker = view.colorPicker; + let cPicker = view.tooltips.colorPicker; let onShown = cPicker.tooltip.once("shown"); swatchEl.click(); yield onShown; yield simulateColorPickerChange(cPicker, [1, 1, 1, 1]); is(swatchEl.style.backgroundColor, "rgb(1, 1, 1)", "The color swatch's background was updated");
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-hides-on-tooltip.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-hides-on-tooltip.js @@ -27,22 +27,22 @@ let test = asyncTest(function*() { .querySelector(".ruleview-colorswatch"); yield testColorPickerHidesWhenImageTooltipAppears(view, swatch); }); function* testColorPickerHidesWhenImageTooltipAppears(view, swatch) { let bgImageSpan = getRuleViewProperty(view, "body", "background-image").valueSpan; let uriSpan = bgImageSpan.querySelector(".theme-link"); - let tooltip = view.colorPicker.tooltip; + let tooltip = view.tooltips.colorPicker.tooltip; info("Showing the color picker tooltip by clicking on the color swatch"); let onShown = tooltip.once("shown"); swatch.click(); yield onShown; info("Now showing the image preview tooltip to hide the color picker"); let onHidden = tooltip.once("hidden"); - yield assertHoverTooltipOn(view.previewTooltip, uriSpan); + yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan); yield onHidden; ok(true, "The color picker closed when the image preview tooltip appeared"); }
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-multiple-changes.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-multiple-changes.js @@ -37,17 +37,17 @@ let test = asyncTest(function*() { function* testSimpleMultipleColorChanges(inspector, ruleView) { yield selectNode("p", inspector); info("Getting the <p> tag's color property"); let swatch = getRuleViewProperty(ruleView, "p", "color").valueSpan .querySelector(".ruleview-colorswatch"); info("Opening the color picker"); - let picker = ruleView.colorPicker; + let picker = ruleView.tooltips.colorPicker; let onShown = picker.tooltip.once("shown"); swatch.click(); yield onShown; info("Changing the color several times"); let colors = [ {rgba: [0, 0, 0, 1], computed: "rgb(0, 0, 0)"}, {rgba: [100, 100, 100, 1], computed: "rgb(100, 100, 100)"}, @@ -65,17 +65,17 @@ function* testSimpleMultipleColorChanges function* testComplexMultipleColorChanges(inspector, ruleView) { yield selectNode("body", inspector); info("Getting the <body> tag's color property"); let swatch = getRuleViewProperty(ruleView, "body", "background").valueSpan .querySelector(".ruleview-colorswatch"); info("Opening the color picker"); - let picker = ruleView.colorPicker; + let picker = ruleView.tooltips.colorPicker; let onShown = picker.tooltip.once("shown"); swatch.click(); yield onShown; info("Changing the color several times"); let colors = [ {rgba: [0, 0, 0, 1], computed: "rgb(0, 0, 0)"}, {rgba: [100, 100, 100, 1], computed: "rgb(100, 100, 100)"}, @@ -98,17 +98,17 @@ function* testComplexMultipleColorChange function* testOverriddenMultipleColorChanges(inspector, ruleView) { yield selectNode("p", inspector); info("Getting the <body> tag's color property"); let swatch = getRuleViewProperty(ruleView, "body", "color").valueSpan .querySelector(".ruleview-colorswatch"); info("Opening the color picker"); - let picker = ruleView.colorPicker; + let picker = ruleView.tooltips.colorPicker; let onShown = picker.tooltip.once("shown"); swatch.click(); yield onShown; info("Changing the color several times"); let colors = [ {rgba: [0, 0, 0, 1], computed: "rgb(0, 0, 0)"}, {rgba: [100, 100, 100, 1], computed: "rgb(100, 100, 100)"},
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-revert-on-ESC.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-revert-on-ESC.js @@ -21,17 +21,17 @@ let test = asyncTest(function*() { let {toolbox, inspector, view} = yield openRuleView(); let swatch = getRuleViewProperty(view, "body", "background-color").valueSpan .querySelector(".ruleview-colorswatch"); yield testPressingEscapeRevertsChanges(swatch, view); }); function* testPressingEscapeRevertsChanges(swatch, ruleView) { - let cPicker = ruleView.colorPicker; + let cPicker = ruleView.tooltips.colorPicker; let onShown = cPicker.tooltip.once("shown"); swatch.click(); yield onShown; yield simulateColorPickerChange(cPicker, [0, 0, 0, 1], { element: content.document.body, name: "backgroundColor",
--- a/browser/devtools/styleinspector/test/browser_ruleview_eyedropper.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_eyedropper.js @@ -35,17 +35,17 @@ let test = asyncTest(function*() { yield inspector.once("inspector-updated"); let property = getRuleViewProperty(view, "div", "background-color"); let swatch = property.valueSpan.querySelector(".ruleview-colorswatch"); ok(swatch, "Color swatch is displayed for the bg-color property"); let dropper = yield openEyedropper(view, swatch); - let tooltip = view.colorPicker.tooltip; + let tooltip = view.tooltips.colorPicker.tooltip; ok(tooltip.isHidden(), "color picker tooltip is closed after opening eyedropper"); yield testESC(swatch, dropper); dropper = yield openEyedropper(view, swatch); ok(dropper, "dropper opened"); @@ -93,17 +93,17 @@ function testSelect(swatch, dropper) { } /* Helpers */ function openEyedropper(view, swatch) { let deferred = promise.defer(); - let tooltip = view.colorPicker.tooltip; + let tooltip = view.tooltips.colorPicker.tooltip; tooltip.once("shown", () => { let tooltipDoc = tooltip.content.contentDocument; let dropperButton = tooltipDoc.querySelector("#eyedropper-button"); tooltip.once("eyedropper-opened", (event, dropper) => { deferred.resolve(dropper) });
--- a/browser/devtools/styleinspector/test/browser_ruleview_user-agent-styles-uneditable.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_user-agent-styles-uneditable.js @@ -39,12 +39,12 @@ function* userAgentStylesUneditable(insp ok (rule.editor.element.hasAttribute("uneditable"), "UA rules have uneditable attribute"); ok (!rule.textProps[0].editor.nameSpan._editable, "nameSpan is not editable"); ok (!rule.textProps[0].editor.valueSpan._editable, "valueSpan is not editable"); ok (!rule.editor.closeBrace._editable, "closeBrace is not editable"); let colorswatch = rule.editor.element.querySelector(".ruleview-colorswatch"); if (colorswatch) { - ok (!view.colorPicker.swatches.has(colorswatch), "The swatch is not editable"); + ok (!view.tooltips.colorPicker.swatches.has(colorswatch), "The swatch is not editable"); } } }
--- a/browser/devtools/styleinspector/test/browser_styleinspector_context-menu-copy-color_02.js +++ b/browser/devtools/styleinspector/test/browser_styleinspector_context-menu-copy-color_02.js @@ -77,17 +77,17 @@ function* testManualEdit(inspector, view function* testColorPickerEdit(inspector, view) { info("Testing colors edited via color picker"); yield selectNode("div", inspector); let swatch = getRuleViewProperty(view, "div", "color").valueSpan .querySelector(".ruleview-colorswatch"); info("Opening the color picker"); - let picker = view.colorPicker; + let picker = view.tooltips.colorPicker; let onShown = picker.tooltip.once("shown"); swatch.click(); yield onShown; let rgbaColor = [83, 183, 89, 1]; let rgbaColorText = "rgba(83, 183, 89, 1)"; yield simulateColorPickerChange(picker, rgbaColor);
--- a/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-background-image.js +++ b/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-background-image.js @@ -45,77 +45,77 @@ let test = asyncTest(function*() { let {view} = yield openComputedView(); info("Testing that the background-image computed style has a tooltip too"); yield testComputedView(view); }); function* testBodyRuleView(view) { info("Testing tooltips in the rule view"); - let panel = view.previewTooltip.panel; + let panel = view.tooltips.previewTooltip.panel; // Check that the rule view has a tooltip and that a XUL panel has been created - ok(view.previewTooltip, "Tooltip instance exists"); + ok(view.tooltips.previewTooltip, "Tooltip instance exists"); ok(panel, "XUL panel exists"); // Get the background-image property inside the rule view let {valueSpan} = getRuleViewProperty(view, "body", "background-image"); let uriSpan = valueSpan.querySelector(".theme-link"); - yield assertHoverTooltipOn(view.previewTooltip, uriSpan); + yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan); let images = panel.getElementsByTagName("image"); is(images.length, 1, "Tooltip contains an image"); ok(images[0].getAttribute("src").indexOf("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHe") !== -1, "The image URL seems fine"); } function* testDivRuleView(view) { - let panel = view.previewTooltip.panel; + let panel = view.tooltips.previewTooltip.panel; // Get the background property inside the rule view let {valueSpan} = getRuleViewProperty(view, ".test-element", "background"); let uriSpan = valueSpan.querySelector(".theme-link"); - yield assertHoverTooltipOn(view.previewTooltip, uriSpan); + yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan); let images = panel.getElementsByTagName("image"); is(images.length, 1, "Tooltip contains an image"); ok(images[0].getAttribute("src").startsWith("data:"), "Tooltip contains a data-uri image as expected"); } function* testTooltipAppearsEvenInEditMode(view) { - let panel = view.previewTooltip.panel; + let panel = view.tooltips.previewTooltip.panel; info("Switching to edit mode in the rule view"); let editor = yield turnToEditMode(view); info("Now trying to show the preview tooltip"); let {valueSpan} = getRuleViewProperty(view, ".test-element", "background"); let uriSpan = valueSpan.querySelector(".theme-link"); - yield assertHoverTooltipOn(view.previewTooltip, uriSpan); + yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan); is(view.doc.activeElement, editor.input, "Tooltip was shown in edit mode, and inplace-editor still focused"); } function turnToEditMode(ruleView) { let brace = ruleView.doc.querySelector(".ruleview-ruleclose"); return focusEditableField(brace); } function* testComputedView(view) { - let tooltip = view.tooltip; + let tooltip = view.tooltips.previewTooltip; ok(tooltip, "The computed-view has a tooltip defined"); let panel = tooltip.panel; ok(panel, "The computed-view tooltip has a XUL panel"); let {valueSpan} = getComputedViewProperty(view, "background-image"); let uriSpan = valueSpan.querySelector(".theme-link"); - yield assertHoverTooltipOn(view.tooltip, uriSpan); + yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan); let images = panel.getElementsByTagName("image"); is(images.length, 1, "Tooltip contains an image"); ok(images[0].getAttribute("src").startsWith("data:"), "Tooltip contains a data-uri in the computed-view too"); }
--- a/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-closes-on-new-selection.js +++ b/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-closes-on-new-selection.js @@ -17,31 +17,31 @@ let test = asyncTest(function*() { info("Testing computed view tooltip closes on new selection"); let {view} = yield openComputedView(); yield testComputedView(view, inspector); }); function* testRuleView(ruleView, inspector) { info("Showing the tooltip"); - let tooltip = ruleView.previewTooltip; + let tooltip = ruleView.tooltips.previewTooltip; let onShown = tooltip.once("shown"); tooltip.show(); yield onShown; info("Selecting a new node"); let onHidden = tooltip.once("hidden"); yield selectNode(".two", inspector); ok(true, "Rule view tooltip closed after a new node got selected"); } function* testComputedView(computedView, inspector) { info("Showing the tooltip"); - let tooltip = computedView.tooltip; + let tooltip = computedView.tooltips.previewTooltip; let onShown = tooltip.once("shown"); tooltip.show(); yield onShown; info("Selecting a new node"); let onHidden = tooltip.once("hidden"); yield selectNode(".one", inspector);
--- a/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-longhand-fontfamily.js +++ b/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-longhand-fontfamily.js @@ -35,43 +35,45 @@ let test = asyncTest(function*() { let {toolbox, inspector, view} = yield openComputedView(); yield testComputedView(view, inspector.selection.nodeFront); }); function* testRuleView(ruleView, nodeFront) { info("Testing font-family tooltips in the rule view"); - let panel = ruleView.previewTooltip.panel; + let tooltip = ruleView.tooltips.previewTooltip; + let panel = tooltip.panel; // Check that the rule view has a tooltip and that a XUL panel has been created - ok(ruleView.previewTooltip, "Tooltip instance exists"); + ok(tooltip, "Tooltip instance exists"); ok(panel, "XUL panel exists"); // Get the font family property inside the rule view let {valueSpan} = getRuleViewProperty(ruleView, "#testElement", "font-family"); // And verify that the tooltip gets shown on this property - yield assertHoverTooltipOn(ruleView.previewTooltip, valueSpan); + yield assertHoverTooltipOn(tooltip, valueSpan); let images = panel.getElementsByTagName("image"); is(images.length, 1, "Tooltip contains an image"); ok(images[0].getAttribute("src").startsWith("data:"), "Tooltip contains a data-uri image as expected"); let dataURL = yield getFontFamilyDataURL(valueSpan.textContent, nodeFront); is(images[0].getAttribute("src"), dataURL, "Tooltip contains the correct data-uri image"); } function* testComputedView(computedView, nodeFront) { info("Testing font-family tooltips in the computed view"); - let panel = computedView.tooltip.panel; + let tooltip = computedView.tooltips.previewTooltip; + let panel = tooltip.panel; let {valueSpan} = getComputedViewProperty(computedView, "font-family"); - yield assertHoverTooltipOn(computedView.tooltip, valueSpan); + yield assertHoverTooltipOn(tooltip, valueSpan); let images = panel.getElementsByTagName("image"); is(images.length, 1, "Tooltip contains an image"); ok(images[0].getAttribute("src").startsWith("data:"), "Tooltip contains a data-uri image as expected"); let dataURL = yield getFontFamilyDataURL(valueSpan.textContent, nodeFront); is(images[0].getAttribute("src"), dataURL, "Tooltip contains the correct data-uri image"); }
--- a/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-shorthand-fontfamily.js +++ b/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-shorthand-fontfamily.js @@ -33,48 +33,51 @@ let test = asyncTest(function*() { let {toolbox, inspector, view} = yield openComputedView(); yield testComputedView(view, inspector.selection.nodeFront); }); function* testRuleView(ruleView, nodeFront) { info("Testing font-family tooltips in the rule view"); - let panel = ruleView.previewTooltip.panel; + let tooltip = ruleView.tooltips.previewTooltip; + let panel = tooltip.panel; // Check that the rule view has a tooltip and that a XUL panel has been created - ok(ruleView.previewTooltip, "Tooltip instance exists"); + ok(tooltip, "Tooltip instance exists"); ok(panel, "XUL panel exists"); // Get the computed font family property inside the font rule view let propertyList = ruleView.element.querySelectorAll(".ruleview-propertylist"); let fontExpander = propertyList[1].querySelectorAll(".ruleview-expander")[0]; fontExpander.click(); let rule = getRuleViewRule(ruleView, "#testElement"); let valueSpan = rule.querySelector(".ruleview-computed .ruleview-propertyvalue"); // And verify that the tooltip gets shown on this property - yield assertHoverTooltipOn(ruleView.previewTooltip, valueSpan); + yield assertHoverTooltipOn(tooltip, valueSpan); let images = panel.getElementsByTagName("image"); is(images.length, 1, "Tooltip contains an image"); ok(images[0].getAttribute("src").startsWith("data:"), "Tooltip contains a data-uri image as expected"); let dataURL = yield getFontFamilyDataURL(valueSpan.textContent, nodeFront); is(images[0].getAttribute("src"), dataURL, "Tooltip contains the correct data-uri image"); } function* testComputedView(computedView, nodeFront) { info("Testing font-family tooltips in the computed view"); - let panel = computedView.tooltip.panel; + let tooltip = computedView.tooltips.previewTooltip; + let panel = tooltip.panel; + let {valueSpan} = getComputedViewProperty(computedView, "font-family"); - yield assertHoverTooltipOn(computedView.tooltip, valueSpan); + yield assertHoverTooltipOn(tooltip, valueSpan); let images = panel.getElementsByTagName("image"); is(images.length, 1, "Tooltip contains an image"); ok(images[0].getAttribute("src").startsWith("data:"), "Tooltip contains a data-uri image as expected"); let dataURL = yield getFontFamilyDataURL(valueSpan.textContent, nodeFront); is(images[0].getAttribute("src"), dataURL, "Tooltip contains the correct data-uri image"); }
--- a/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-size.js +++ b/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-size.js @@ -26,17 +26,17 @@ let test = asyncTest(function*() { yield testImageDimension(view); yield testPickerDimension(view); }); function* testImageDimension(ruleView) { info("Testing background-image tooltip dimensions"); - let tooltip = ruleView.previewTooltip; + let tooltip = ruleView.tooltips.previewTooltip; let panel = tooltip.panel; let {valueSpan} = getRuleViewProperty(ruleView, "div", "background"); let uriSpan = valueSpan.querySelector(".theme-link"); // Make sure there is a hover tooltip for this property, this also will fill // the tooltip with its content yield assertHoverTooltipOn(tooltip, uriSpan); @@ -60,17 +60,17 @@ function* testImageDimension(ruleView) { yield onHidden; } function* testPickerDimension(ruleView) { info("Testing color-picker tooltip dimensions"); let {valueSpan} = getRuleViewProperty(ruleView, "div", "background"); let swatch = valueSpan.querySelector(".ruleview-colorswatch"); - let cPicker = ruleView.colorPicker; + let cPicker = ruleView.tooltips.colorPicker; let onShown = cPicker.tooltip.once("shown"); swatch.click(); yield onShown; // The colorpicker spectrum's iframe has a fixed width height, so let's // make sure the tooltip is at least as big as that let w = cPicker.tooltip.panel.querySelector("iframe").width;
--- a/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-01.js +++ b/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-01.js @@ -10,29 +10,33 @@ const PAGE_CONTENT = [ '<style type="text/css">', ' body {', ' transform: skew(16deg);', ' }', '</style>', 'Test the css transform highlighter' ].join("\n"); +const TYPE = "CssTransformHighlighter"; + let test = asyncTest(function*() { yield addTab("data:text/html," + PAGE_CONTENT); let {view: rView} = yield openRuleView(); + let overlay = rView.highlighters; - ok(!rView.transformHighlighter, "No highlighter exists in the rule-view"); - let h = yield rView.getTransformHighlighter(); - ok(rView.transformHighlighter, "The highlighter has been created in the rule-view"); - is(h, rView.transformHighlighter, "The right highlighter has been created"); - let h2 = yield rView.getTransformHighlighter(); + ok(!overlay.highlighters[TYPE], "No highlighter exists in the rule-view"); + let h = yield overlay._getHighlighter(TYPE); + ok(overlay.highlighters[TYPE], "The highlighter has been created in the rule-view"); + is(h, overlay.highlighters[TYPE], "The right highlighter has been created"); + let h2 = yield overlay._getHighlighter(TYPE); is(h, h2, "The same instance of highlighter is returned everytime in the rule-view"); let {view: cView} = yield openComputedView(); + let overlay = cView.highlighters; - ok(!cView.transformHighlighter, "No highlighter exists in the computed-view"); - let h = yield cView.getTransformHighlighter(); - ok(cView.transformHighlighter, "The highlighter has been created in the computed-view"); - is(h, cView.transformHighlighter, "The right highlighter has been created"); - let h2 = yield cView.getTransformHighlighter(); + ok(!overlay.highlighters[TYPE], "No highlighter exists in the computed-view"); + let h = yield overlay._getHighlighter(TYPE); + ok(overlay.highlighters[TYPE], "The highlighter has been created in the computed-view"); + is(h, overlay.highlighters[TYPE], "The right highlighter has been created"); + let h2 = yield overlay._getHighlighter(TYPE); is(h, h2, "The same instance of highlighter is returned everytime in the computed-view"); });
--- a/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-02.js +++ b/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-02.js @@ -12,43 +12,52 @@ const PAGE_CONTENT = [ ' body {', ' transform: skew(16deg);', ' color: yellow;', ' }', '</style>', 'Test the css transform highlighter' ].join("\n"); +let TYPE = "CssTransformHighlighter"; + let test = asyncTest(function*() { yield addTab("data:text/html," + PAGE_CONTENT); + let {view: rView} = yield openRuleView(); - ok(!rView.transformHighlighter, "No highlighter exists in the rule-view (1)"); + let hs = rView.highlighters; + + ok(!hs.highlighters[TYPE], "No highlighter exists in the rule-view (1)"); + ok(!hs.promises[TYPE], "No highlighter is being created in the rule-view (1)"); info("Faking a mousemove on a non-transform property"); let {valueSpan} = getRuleViewProperty(rView, "body", "color"); - rView._onMouseMove({target: valueSpan}); - ok(!rView.transformHighlighter, "No highlighter exists in the rule-view (2)"); - ok(!rView.transformHighlighterPromise, "No highlighter is being initialized"); + hs._onMouseMove({target: valueSpan}); + ok(!hs.highlighters[TYPE], "No highlighter exists in the rule-view (2)"); + ok(!hs.promises[TYPE], "No highlighter is being created in the rule-view (2)"); info("Faking a mousemove on a transform property"); let {valueSpan} = getRuleViewProperty(rView, "body", "transform"); - rView._onMouseMove({target: valueSpan}); - ok(rView.transformHighlighterPromise, "The highlighter is being initialized"); - let h = yield rView.transformHighlighterPromise; - is(h, rView.transformHighlighter, "The initialized highlighter is the right one"); + hs._onMouseMove({target: valueSpan}); + ok(hs.promises[TYPE], "The highlighter is being initialized"); + let h = yield hs.promises[TYPE]; + is(h, hs.highlighters[TYPE], "The initialized highlighter is the right one"); let {view: cView} = yield openComputedView(); - ok(!cView.transformHighlighter, "No highlighter exists in the computed-view (1)"); + let hs = cView.highlighters; + + ok(!hs.highlighters[TYPE], "No highlighter exists in the computed-view (1)"); + ok(!hs.promises[TYPE], "No highlighter is being created in the computed-view (1)"); info("Faking a mousemove on a non-transform property"); let {valueSpan} = getComputedViewProperty(cView, "color"); - cView._onMouseMove({target: valueSpan}); - ok(!cView.transformHighlighter, "No highlighter exists in the computed-view (2)"); - ok(!cView.transformHighlighterPromise, "No highlighter is being initialized"); + hs._onMouseMove({target: valueSpan}); + ok(!hs.highlighters[TYPE], "No highlighter exists in the computed-view (2)"); + ok(!hs.promises[TYPE], "No highlighter is being created in the computed-view (2)"); info("Faking a mousemove on a transform property"); let {valueSpan} = getComputedViewProperty(cView, "transform"); - cView._onMouseMove({target: valueSpan}); - ok(cView.transformHighlighterPromise, "The highlighter is being initialized"); - let h = yield cView.transformHighlighterPromise; - is(h, cView.transformHighlighter, "The initialized highlighter is the right one"); + hs._onMouseMove({target: valueSpan}); + ok(hs.promises[TYPE], "The highlighter is being initialized"); + let h = yield hs.promises[TYPE]; + is(h, hs.highlighters[TYPE], "The initialized highlighter is the right one"); });
--- a/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-03.js +++ b/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-03.js @@ -18,16 +18,18 @@ const PAGE_CONTENT = [ ' body {', ' transform: skew(16deg);', ' color: purple;', ' }', '</style>', 'Test the css transform highlighter' ].join("\n"); +const TYPE = "CssTransformHighlighter"; + let test = asyncTest(function*() { yield addTab("data:text/html," + PAGE_CONTENT); let {inspector, view: rView} = yield openRuleView(); // Mock the highlighter front to get the reference of the NodeFront let HighlighterFront = { isShown: false, @@ -40,50 +42,50 @@ let test = asyncTest(function*() { }, hide: function() { this.nodeFront = null; this.isShown = false; } }; // Inject the mock highlighter in the rule-view - rView.transformHighlighterPromise = { + rView.highlighters.promises[TYPE] = { then: function(cb) { cb(HighlighterFront); } }; let {valueSpan} = getRuleViewProperty(rView, "body", "transform"); info("Checking that the HighlighterFront's show/hide methods are called"); - rView._onMouseMove({target: valueSpan}); + rView.highlighters._onMouseMove({target: valueSpan}); ok(HighlighterFront.isShown, "The highlighter is shown"); - rView._onMouseLeave(); + rView.highlighters._onMouseLeave(); ok(!HighlighterFront.isShown, "The highlighter is hidden"); info("Checking that hovering several times over the same property doesn't" + " show the highlighter several times"); let nb = HighlighterFront.nbOfTimesShown; - rView._onMouseMove({target: valueSpan}); + rView.highlighters._onMouseMove({target: valueSpan}); is(HighlighterFront.nbOfTimesShown, nb + 1, "The highlighter was shown once"); - rView._onMouseMove({target: valueSpan}); - rView._onMouseMove({target: valueSpan}); + rView.highlighters._onMouseMove({target: valueSpan}); + rView.highlighters._onMouseMove({target: valueSpan}); is(HighlighterFront.nbOfTimesShown, nb + 1, "The highlighter was shown once, after several mousemove"); info("Checking that the right NodeFront reference is passed"); yield selectNode(content.document.documentElement, inspector); let {valueSpan} = getRuleViewProperty(rView, "html", "transform"); - rView._onMouseMove({target: valueSpan}); + rView.highlighters._onMouseMove({target: valueSpan}); is(HighlighterFront.nodeFront.tagName, "HTML", "The right NodeFront is passed to the highlighter (1)"); yield selectNode("body", inspector); let {valueSpan} = getRuleViewProperty(rView, "body", "transform"); - rView._onMouseMove({target: valueSpan}); + rView.highlighters._onMouseMove({target: valueSpan}); is(HighlighterFront.nodeFront.tagName, "BODY", "The right NodeFront is passed to the highlighter (2)"); info("Checking that the highlighter gets hidden when hovering a non-transform property"); let {valueSpan} = getRuleViewProperty(rView, "body", "color"); - rView._onMouseMove({target: valueSpan}); + rView.highlighters._onMouseMove({target: valueSpan}); ok(!HighlighterFront.isShown, "The highlighter is hidden"); });
--- a/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-04.js +++ b/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-04.js @@ -20,39 +20,43 @@ const PAGE_CONTENT = [ ' }', ' .test {', ' transform: skew(25deg);', ' }', '</style>', '<div class="test"></div>' ].join("\n"); +const TYPE = "CssTransformHighlighter"; + let test = asyncTest(function*() { yield addTab("data:text/html," + PAGE_CONTENT); let {view: rView, inspector} = yield openRuleView(); yield selectNode(".test", inspector); + let hs = rView.highlighters; + info("Faking a mousemove on the overriden property"); let {valueSpan} = getRuleViewProperty(rView, "div", "transform"); - rView._onMouseMove({target: valueSpan}); - ok(!rView.transformHighlighter, "No highlighter was created for the overriden property"); - ok(!rView.transformHighlighterPromise, "And no highlighter is being initialized either"); + hs._onMouseMove({target: valueSpan}); + ok(!hs.highlighters[TYPE], "No highlighter was created for the overriden property"); + ok(!hs.promises[TYPE], "And no highlighter is being initialized either"); info("Disabling the applied property"); let classRuleEditor = rView.element.children[1]._ruleEditor; let propEditor = classRuleEditor.rule.textProps[0].editor; propEditor.enable.click(); yield classRuleEditor.rule._applyingModifications; info("Faking a mousemove on the disabled property"); let {valueSpan} = getRuleViewProperty(rView, ".test", "transform"); - rView._onMouseMove({target: valueSpan}); - ok(!rView.transformHighlighter, "No highlighter was created for the disabled property"); - ok(!rView.transformHighlighterPromise, "And no highlighter is being initialized either"); + hs._onMouseMove({target: valueSpan}); + ok(!hs.highlighters[TYPE], "No highlighter was created for the disabled property"); + ok(!hs.promises[TYPE], "And no highlighter is being initialized either"); info("Faking a mousemove on the now unoverriden property"); let {valueSpan} = getRuleViewProperty(rView, "div", "transform"); - rView._onMouseMove({target: valueSpan}); - ok(rView.transformHighlighterPromise, "The highlighter is being initialized now"); - let h = yield rView.transformHighlighterPromise; - is(h, rView.transformHighlighter, "The initialized highlighter is the right one"); + hs._onMouseMove({target: valueSpan}); + ok(hs.promises[TYPE], "The highlighter is being initialized now"); + let h = yield hs.promises[TYPE]; + is(h, hs.highlighters[TYPE], "The initialized highlighter is the right one"); });
--- a/browser/themes/shared/devtools/styleeditor.css +++ b/browser/themes/shared/devtools/styleeditor.css @@ -60,17 +60,17 @@ font-style: italic; } .splitview-nav.empty > p { padding: 0 10px; } .stylesheet-sidebar { - width: 230px; + width: 237px; -moz-border-start: 1px solid; } .theme-light .stylesheet-sidebar { border-color: #aaa; /* Splitters */ } .theme-dark .stylesheet-sidebar { @@ -86,16 +86,20 @@ } .media-rule-label { padding: 4px; cursor: pointer; border-bottom: 1px solid; } +.media-rule-line { + -moz-padding-start: 4px; +} + .theme-light .media-condition-unmatched { color: grey; } .theme-dark .media-condition-unmatched { color: #606C75; }
--- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -1986,39 +1986,39 @@ abstract public class BrowserApp extends } /** * Hides certain UI elements (e.g. button toast, tabs tray) when the * user touches the main layout. */ private class HideOnTouchListener implements TouchEventInterceptor { private boolean mIsHidingTabs = false; + private final Rect mTempRect = new Rect(); @Override public boolean onInterceptTouchEvent(View view, MotionEvent event) { // Only try to hide the button toast if it's already inflated. if (mToast != null) { mToast.hide(false, ButtonToast.ReasonHidden.TOUCH_OUTSIDE); } // We need to account for scroll state for the touched view otherwise // tapping on an "empty" part of the view will still be considered a // valid touch event. if (view.getScrollX() != 0 || view.getScrollY() != 0) { - Rect rect = new Rect(); - view.getHitRect(rect); - rect.offset(-view.getScrollX(), -view.getScrollY()); + view.getHitRect(mTempRect); + mTempRect.offset(-view.getScrollX(), -view.getScrollY()); int[] viewCoords = new int[2]; view.getLocationOnScreen(viewCoords); int x = (int) event.getRawX() - viewCoords[0]; int y = (int) event.getRawY() - viewCoords[1]; - if (!rect.contains(x, y)) + if (!mTempRect.contains(x, y)) return false; } // If the tab tray is showing, hide the tab tray and don't send the event to content. if (event.getActionMasked() == MotionEvent.ACTION_DOWN && autoHideTabs()) { mIsHidingTabs = true; return true; }
--- a/toolkit/components/crashmonitor/CrashMonitor.jsm +++ b/toolkit/components/crashmonitor/CrashMonitor.jsm @@ -160,19 +160,20 @@ this.CrashMonitor = { // called after receiving it CrashMonitorInternal.checkpoints["profile-after-change"] = true; NOTIFICATIONS.forEach(function (aTopic) { Services.obs.addObserver(this, aTopic, false); }, this); // Add shutdown blocker for profile-before-change - AsyncShutdown.profileBeforeChange.addBlocker( + OS.File.profileBeforeChange.addBlocker( "CrashMonitor: Writing notifications to file after receiving profile-before-change", - CrashMonitorInternal.profileBeforeChangeDeferred.promise + CrashMonitorInternal.profileBeforeChangeDeferred.promise, + () => this.checkpoints ); CrashMonitorInternal.initialized = true; if (Services.metro && Services.metro.immersive) { OS.File.makeDir(OS.Path.join(OS.Constants.Path.profileDir, "metro")); } return promise; },
--- a/toolkit/components/osfile/modules/osfile_async_front.jsm +++ b/toolkit/components/osfile/modules/osfile_async_front.jsm @@ -449,16 +449,17 @@ let Scheduler = { Scheduler.restartTimer(); let data; let reply; let isError = false; try { try { + Scheduler.Debugging.messagesSent++; data = yield this.worker.post(method, ...args); } finally { Scheduler.Debugging.messagesReceived++; } reply = data; } catch (error) { reply = error; isError = true; @@ -603,17 +604,28 @@ if (SharedAll.Config.DEBUG && Scheduler. const WEB_WORKERS_SHUTDOWN_TOPIC = "web-workers-shutdown"; // Preference used to configure test shutdown observer. const PREF_OSFILE_TEST_SHUTDOWN_OBSERVER = "toolkit.osfile.test.shutdown.observer"; AsyncShutdown.webWorkersShutdown.addBlocker( "OS.File: flush pending requests, warn about unclosed files, shut down service.", - () => Scheduler.kill({reset: false, shutdown: true}) + Task.async(function*() { + // Give clients a last chance to enqueue requests. + yield Barriers.shutdown.wait({crashAfterMS: null}); + + // Wait until all requests are complete and kill the worker. + yield Scheduler.kill({reset: false, shutdown: true}); + }), + () => { + let details = Barriers.getDetails(); + details.clients = Barriers.shutdown.state; + return details; + } ); // Attaching an observer for PREF_OSFILE_TEST_SHUTDOWN_OBSERVER to enable or // disable the test shutdown event observer. // Note: By default the PREF_OSFILE_TEST_SHUTDOWN_OBSERVER is unset. // Note: This is meant to be used for testing purposes only. Services.prefs.addObserver(PREF_OSFILE_TEST_SHUTDOWN_OBSERVER, @@ -1501,51 +1513,62 @@ this.OS.Path = Path; // Returns a resolved promise when all the queued operation have been completed. Object.defineProperty(OS.File, "queue", { get: function() { return Scheduler.queue; } }); -// Auto-flush OS.File during profile-before-change. This ensures that any I/O -// that has been queued *before* profile-before-change is properly completed. -// To ensure that I/O queued *during* profile-before-change is completed, -// clients should register using AsyncShutdown.addBlocker. -AsyncShutdown.profileBeforeChange.addBlocker( - "OS.File: flush I/O queued before profile-before-change", - // Wait until the latest currently enqueued promise is satisfied/rejected - function() { - let DEBUG = false; - try { - DEBUG = Services.prefs.getBoolPref("toolkit.osfile.debug.failshutdown"); - } catch (ex) { - // Ignore - } - if (DEBUG) { - // Return a promise that will never be satisfied - return Promise.defer().promise; - } else { - return Scheduler.queue; - } - }, - function getDetails() { +/** + * Shutdown barriers, to let clients register to be informed during shutdown. + */ +let Barriers = { + profileBeforeChange: new AsyncShutdown.Barrier("OS.File: Waiting for clients before profile-before-shutdown"), + shutdown: new AsyncShutdown.Barrier("OS.File: Waiting for clients before full shutdown"), + /** + * Return the shutdown state of OS.File + */ + getDetails: function() { let result = { launched: Scheduler.launched, shutdown: Scheduler.shutdown, worker: !!Scheduler._worker, pendingReset: !!Scheduler.resetTimer, latestSent: Scheduler.Debugging.latestSent, latestReceived: Scheduler.Debugging.latestReceived, messagesSent: Scheduler.Debugging.messagesSent, messagesReceived: Scheduler.Debugging.messagesReceived, messagesQueued: Scheduler.Debugging.messagesQueued, - DEBUG: SharedAll.Config.DEBUG + DEBUG: SharedAll.Config.DEBUG, }; // Convert dates to strings for better readability for (let key of ["latestSent", "latestReceived"]) { if (result[key] && typeof result[key][0] == "number") { result[key][0] = Date(result[key][0]); } } return result; } +}; + +File.profileBeforeChange = Barriers.profileBeforeChange.client; +File.shutdown = Barriers.shutdown.client; + +// Auto-flush OS.File during profile-before-change. This ensures that any I/O +// that has been queued *before* profile-before-change is properly completed. +// To ensure that I/O queued *during* profile-before-change is completed, +// clients should register using AsyncShutdown.addBlocker. +AsyncShutdown.profileBeforeChange.addBlocker( + "OS.File: flush I/O queued before profile-before-change", + Task.async(function*() { + // Give clients a last chance to enqueue requests. + yield Barriers.profileBeforeChange.wait({crashAfterMS: null}); + + // Wait until all currently enqueued requests are completed. + yield Scheduler.queue; + }), + () => { + let details = Barriers.getDetails(); + details.clients = Barriers.profileBeforeChange.state; + return details; + } );
--- a/toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js +++ b/toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js @@ -35,28 +35,30 @@ function after_crash(mdump, extra) { } // Test that AsyncShutdown + OS.File reports errors correctly, in a case in which // the latest operation succeeded function setup_osfile_crash_noerror() { Components.utils.import("resource://gre/modules/Services.jsm", this); Components.utils.import("resource://gre/modules/osfile.jsm", this); + Components.utils.import("resource://gre/modules/Promise.jsm", this); - Services.prefs.setBoolPref("toolkit.osfile.debug.failshutdown", true); Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", 1); Services.prefs.setBoolPref("toolkit.osfile.native", false); + OS.File.profileBeforeChange.addBlocker("Adding a blocker that will never be resolved", () => Promise.defer().promise); OS.File.getCurrentDirectory(); + Services.obs.notifyObservers(null, "profile-before-change", null); dump("Waiting for crash\n"); }; function after_osfile_crash_noerror(mdump, extra) { - do_print("after OS.File crash: " + JSON.stringify(extra.AsyncShutdownTimeout)); + do_print("after OS.File crash: " + extra.AsyncShutdownTimeout); let info = JSON.parse(extra.AsyncShutdownTimeout); let state = info.conditions[0].state; do_print("Keys: " + Object.keys(state).join(", ")); do_check_eq(info.phase, "profile-before-change"); do_check_true(state.launched); do_check_false(state.shutdown); do_check_true(state.worker); do_check_true(!!state.latestSent); @@ -64,28 +66,30 @@ function after_osfile_crash_noerror(mdum } // Test that AsyncShutdown + OS.File reports errors correctly, in a case in which // the latest operation failed function setup_osfile_crash_exn() { Components.utils.import("resource://gre/modules/Services.jsm", this); Components.utils.import("resource://gre/modules/osfile.jsm", this); + Components.utils.import("resource://gre/modules/Promise.jsm", this); - Services.prefs.setBoolPref("toolkit.osfile.debug.failshutdown", true); Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", 1); Services.prefs.setBoolPref("toolkit.osfile.native", false); + OS.File.profileBeforeChange.addBlocker("Adding a blocker that will never be resolved", () => Promise.defer().promise); OS.File.read("I do not exist"); + Services.obs.notifyObservers(null, "profile-before-change", null); dump("Waiting for crash\n"); }; function after_osfile_crash_exn(mdump, extra) { - do_print("after OS.File crash: " + JSON.stringify(extra.AsyncShutdownTimeout)); + do_print("after OS.File crash: " + extra.AsyncShutdownTimeout); let info = JSON.parse(extra.AsyncShutdownTimeout); let state = info.conditions[0].state; do_print("Keys: " + Object.keys(state).join(", ")); do_check_eq(info.phase, "profile-before-change"); do_check_false(state.shutdown); do_check_true(state.worker); do_check_true(!!state.latestSent); do_check_eq(state.latestSent[1], "read");
--- a/toolkit/devtools/server/actors/stylesheets.js +++ b/toolkit/devtools/server/actors/stylesheets.js @@ -69,19 +69,16 @@ let StyleSheetsActor = protocol.ActorCla return { actor: this.actorID }; }, initialize: function (conn, tabActor) { protocol.Actor.prototype.initialize.call(this, null); this.parentActor = tabActor; - // so we can get events when stylesheets and rules are added - this.document.styleSheetChangeEventsEnabled = true; - // keep a map of sheets-to-actors so we don't create two actors for one sheet this._sheets = new Map(); }, /** * Destroy the current StyleSheetsActor instance. */ destroy: function() @@ -474,34 +471,16 @@ let StyleSheetActor = protocol.ActorClas this._window = aWindow; // text and index are unknown until source load this.text = null; this._styleSheetIndex = -1; this._transitionRefCount = 0; - - this._onRuleAddedOrRemoved = this._onRuleAddedOrRemoved.bind(this); - - if (this.browser) { - this.browser.addEventListener("StyleRuleAdded", this._onRuleAddedOrRemoved, true); - this.browser.addEventListener("StyleRuleRemoved", this._onRuleAddedOrRemoved, true); - } - }, - - _onRuleAddedOrRemoved: function(event) { - if (event.target != this.document || event.stylesheet != this.rawSheet) { - return; - } - if (event.rule && event.rule.type == Ci.nsIDOMCSSRule.MEDIA_RULE) { - this._getMediaRules().then((rules) => { - events.emit(this, "media-rules-changed", rules); - }); - } }, /** * Get the raw stylesheet's cssRules once the sheet has been loaded. * * @return {Promise} * Promise that resolves with a CSSRuleList */ @@ -916,16 +895,20 @@ let StyleSheetActor = protocol.ActorClas this._notifyPropertyChanged("ruleCount"); if (transition) { this._insertTransistionRule(); } else { this._notifyStyleApplied(); } + + this._getMediaRules().then((rules) => { + events.emit(this, "media-rules-changed", rules); + }); }, { request: { text: Arg(0, "string"), transition: Arg(1, "boolean") } }), /**
--- a/toolkit/devtools/server/actors/webbrowser.js +++ b/toolkit/devtools/server/actors/webbrowser.js @@ -43,16 +43,18 @@ XPCOMUtils.defineLazyGetter(this, "event function allAppShellDOMWindows(aWindowType) { let e = Services.wm.getEnumerator(aWindowType); while (e.hasMoreElements()) { yield e.getNext(); } } +exports.allAppShellDOMWindows = allAppShellDOMWindows; + /** * Retrieve the window type of the top-level window |aWindow|. */ function appShellDOMWindowType(aWindow) { /* This is what nsIWindowMediator's enumerator checks. */ return aWindow.document.documentElement.getAttribute('windowtype'); }
--- a/webapprt/content/dbg-webapp-actors.js +++ b/webapprt/content/dbg-webapp-actors.js @@ -1,15 +1,19 @@ /* 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 { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); +const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); +const { BrowserTabActor, BrowserTabList, allAppShellDOMWindows, + sendShutdownEvent } = devtools.require("devtools/server/actors/webbrowser"); +const { RootActor } = devtools.require("devtools/server/actors/root"); /** * WebappRT-specific actors. */ /** * Construct a root actor appropriate for use in a server running in the webapp * runtime. The returned root actor: