From c094610e662aa6ef01c1276be7e589afe73fc8b2 Mon Sep 17 00:00:00 2001 From: David Creswick Date: Sun, 21 Apr 2013 18:35:53 -0500 Subject: [PATCH 1/2] Bug 855108 - Disable tools toggled by the developer toolbar when it closes. r=jwalker --- browser/devtools/shared/DeveloperToolbar.jsm | 24 ++- browser/devtools/shared/test/Makefile.in | 1 + .../test/browser_toolbar_buttons_nopersist.js | 151 ++++++++++++++++++ 3 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 browser/devtools/shared/test/browser_toolbar_buttons_nopersist.js diff --git a/browser/devtools/shared/DeveloperToolbar.jsm b/browser/devtools/shared/DeveloperToolbar.jsm index 4f88380519ac..16d1061d9d6a 100644 --- a/browser/devtools/shared/DeveloperToolbar.jsm +++ b/browser/devtools/shared/DeveloperToolbar.jsm @@ -107,7 +107,9 @@ let CommandUtils = { button.setAttribute("tooltiptext", command.description); } + let buttonWasClickedAtLeastOnce = false; button.addEventListener("click", function() { + buttonWasClickedAtLeastOnce = true; requisition.update(buttonSpec.typed); //if (requisition.getStatus() == Status.VALID) { requisition.exec(); @@ -134,9 +136,27 @@ let CommandUtils = { }; command.state.onChange(target, onChange); onChange(null, target.tab); - document.defaultView.addEventListener("unload", function() { + let cleanUp = function () { + document.defaultView.removeEventListener("unload", cleanUp, false); + target.off("close", cleanUp); + command.state.offChange(target, onChange); - }, false); + + // If the command toggles state and if that state has been + // modified by the button, then make sure the state is + // cleared when the button's document unloads. This is so + // that the effects of buttons on the developer toolbar do + // not persist after the toolbar is closed. + if (buttonWasClickedAtLeastOnce && + command.state.isChecked(target)) { + // toggle state to the off position + requisition.update(buttonSpec.typed); + requisition.exec(); + buttonWasClickedAtLeastOnce = false; + } + }; + document.defaultView.addEventListener("unload", cleanUp, false); + target.on("close", cleanUp); } } }); diff --git a/browser/devtools/shared/test/Makefile.in b/browser/devtools/shared/test/Makefile.in index d453941933c6..cf59b8146dd9 100644 --- a/browser/devtools/shared/test/Makefile.in +++ b/browser/devtools/shared/test/Makefile.in @@ -22,6 +22,7 @@ MOCHITEST_BROWSER_FILES = \ browser_toolbar_webconsole_errors_count.js \ browser_layoutHelpers.js \ browser_eventemitter_basic.js \ + browser_toolbar_buttons_nopersist.js \ head.js \ leakhunt.js \ $(NULL) diff --git a/browser/devtools/shared/test/browser_toolbar_buttons_nopersist.js b/browser/devtools/shared/test/browser_toolbar_buttons_nopersist.js new file mode 100644 index 000000000000..8eaf42b2bc47 --- /dev/null +++ b/browser/devtools/shared/test/browser_toolbar_buttons_nopersist.js @@ -0,0 +1,151 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Bug 855108 - Commands toggled by the developer toolbar should not persist after the toolbar closes + +function test() { + waitForExplicitFinish(); + + let Toolbox = Cu.import("resource:///modules/devtools/Toolbox.jsm", {}).Toolbox; + let TargetFactory = Cu.import("resource:///modules/devtools/Target.jsm", {}).TargetFactory; + let gDevTools = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools; + let CommandUtils = Cu.import("resource:///modules/devtools/DeveloperToolbar.jsm", {}).CommandUtils; + let Requisition = (function () { + let require = Cu.import("resource://gre/modules/devtools/Require.jsm", {}).require; + Cu.import("resource:///modules/devtools/gcli.jsm", {}); + return require('gcli/cli').Requisition; + })(); + let Services = Cu.import("resource://gre/modules/Services.jsm", {}).Services; + let TiltGL = Cu.import("resource:///modules/devtools/TiltGL.jsm", {}).TiltGL; + let Promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise; + + // only test togglable items on the toolbar + const commandsToTest = + CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec") + .filter(function (command) { + if ( !(typeof command == "string" && + command.indexOf("toggle") >= 0)) + return false; + if (command == "tilt toggle") { + // Is it possible to run the tilt tool? + let XHTML_NS = "http://www.w3.org/1999/xhtml"; + let canvas = document.createElementNS(XHTML_NS, "canvas"); + return (TiltGL.isWebGLSupported() && + TiltGL.create3DContext(canvas)); + } + return true; + }); + + const URL = "data:text/html;charset=UTF-8," + encodeURIComponent( + [ "", + "", + " ", + " Bug 855108", + " ", + " ", + "

content

", + " ", + "" + ].join("\n")); + + function clearHostPref() { + let pref = Toolbox.prototype._prefs.LAST_HOST; + Services.prefs.getBranch("").clearUserPref(pref); + } + + registerCleanupFunction(clearHostPref); + + let requisition; + + function testNextCommand(tab) { + if (commandsToTest.length == 0) { + finish(); + return null; + } + let commandSpec = commandsToTest.pop(); + requisition.update(commandSpec); + let command = requisition.commandAssignment.value; + + let target = TargetFactory.forTab(tab); + function checkToggle(expected, msg) { + is(!!command.state.isChecked(target), expected, commandSpec+" "+msg); + } + let toolbox; + + clearHostPref(); + + // Actions: Toggle the command on using the toolbox button, then + // close the toolbox. + // Expected result: The command should no longer be toggled on. + return gDevTools.showToolbox(target) + .then(catchFail(function (aToolbox) { + toolbox = aToolbox; + let button = toolbox.doc.getElementById(command.buttonId); + return clickElement(button); + })).then(catchFail(function () { + checkToggle(true, "was toggled on by toolbox"); + return toolbox.destroy(); + })) + .then(catchFail(function () { + target = TargetFactory.forTab(tab); + checkToggle(false, "is untoggled after toobox closed"); + })) + + // Actions: Open the toolbox, toggle the command on use means + // OTHER than the toolbox, then close the toolbox. + // Expected result: The command should still be toggled. + // Cleanup: Toggle the command off again. + .then(function () gDevTools.showToolbox(target)) + .then(catchFail(function (toolbox) { + requisition.update(commandSpec); + requisition.exec(); + checkToggle(true, "was toggled on by command"); + return toolbox.destroy(); + })) + .then(catchFail(function () { + target = TargetFactory.forTab(tab); + checkToggle(true, "is still toggled after toolbox closed"); + requisition.update(commandSpec); + requisition.exec(); + })) + + // Actions: Toggle the command on using the button on a docked + // toolbox, detach the toolbox into a window, then close + // the toolbox. + // Expected result: The command should no longer be toggled on. + .then(function () gDevTools.showToolbox(target, null, + Toolbox.HostType.BOTTOM)) + .then(catchFail(function (aToolbox) { + toolbox = aToolbox; + let button = toolbox.doc.getElementById(command.buttonId); + return clickElement(button); + })).then(catchFail(function () { + checkToggle(true, "was toggled by docked toolbox"); + return toolbox.switchHost(Toolbox.HostType.WINDOW); + })) + .then(function () toolbox.destroy()) + .then(catchFail(function () { + target = TargetFactory.forTab(tab); + checkToggle(false, "is untoggled after detached toobox closed"); + })) + + .then(function () target.destroy()) + .then(function () testNextCommand(tab)); + } + + function clickElement(el) { + let deferred = Promise.defer(); + let window = el.ownerDocument.defaultView; + waitForFocus(function () { + EventUtils.synthesizeMouseAtCenter(el, {}, window); + deferred.resolve(); + }, window); + return deferred.promise; + } + + addTab(URL, function (browser, tab) { + let environment = { chromeDocument: tab.ownerDocument }; + requisition = new Requisition(environment); + testNextCommand(tab); + }); +} From 71085f2b8c1d439b2b70ddcd22869aab5e20df6f Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Fri, 19 Apr 2013 16:30:33 -0500 Subject: [PATCH 2/2] Bug 677930 - Style Inspector: make URLs clickable. --- browser/devtools/styleinspector/CssLogic.jsm | 30 ++++-- .../devtools/styleinspector/CssRuleView.jsm | 69 ++++++++++++- browser/devtools/styleinspector/ruleview.css | 4 + .../devtools/styleinspector/test/Makefile.in | 5 + ...leinspector_bug_677930_urls_clickable.html | 21 ++++ ...tyleinspector_bug_677930_urls_clickable.js | 97 ++++++++++++++++++ ...yleinspector_bug_677930_urls_clickable.css | 9 ++ .../styleinspector/test/test-image.png | Bin 0 -> 580 bytes 8 files changed, 226 insertions(+), 9 deletions(-) create mode 100644 browser/devtools/styleinspector/test/browser_styleinspector_bug_677930_urls_clickable.html create mode 100644 browser/devtools/styleinspector/test/browser_styleinspector_bug_677930_urls_clickable.js create mode 100644 browser/devtools/styleinspector/test/browser_styleinspector_bug_677930_urls_clickable/browser_styleinspector_bug_677930_urls_clickable.css create mode 100644 browser/devtools/styleinspector/test/test-image.png diff --git a/browser/devtools/styleinspector/CssLogic.jsm b/browser/devtools/styleinspector/CssLogic.jsm index 29a4b8f09b13..7e56e85616b9 100644 --- a/browser/devtools/styleinspector/CssLogic.jsm +++ b/browser/devtools/styleinspector/CssLogic.jsm @@ -744,6 +744,24 @@ CssLogic.isContentStylesheet = function CssLogic_isContentStylesheet(aSheet) return false; }; +/** + * Get a source for a stylesheet, taking into account embedded stylesheets + * for which we need to use document.defaultView.location.href rather than + * sheet.href + * + * @param {CSSStyleSheet} aSheet the DOM object for the style sheet. + * @return {string} the address of the stylesheet. + */ +CssLogic.href = function CssLogic_href(aSheet) +{ + let href = aSheet.href; + if (!href) { + href = aSheet.ownerNode.ownerDocument.location; + } + + return href; +}; + /** * Return a shortened version of a style sheet's source. * @@ -927,21 +945,17 @@ CssSheet.prototype = { }, /** - * Get a source for a stylesheet, taking into account embedded stylesheets - * for which we need to use document.defaultView.location.href rather than - * sheet.href + * Get a source for a stylesheet, using CssLogic.href * * @return {string} the address of the stylesheet. */ get href() { - if (!this._href) { - this._href = this.domSheet.href; - if (!this._href) { - this._href = this.domSheet.ownerNode.ownerDocument.location; - } + if (this._href) { + return this._href; } + this._href = CssLogic.href(this.domSheet); return this._href; }, diff --git a/browser/devtools/styleinspector/CssRuleView.jsm b/browser/devtools/styleinspector/CssRuleView.jsm index 8b3871d1d52e..ba9fd9177c4f 100644 --- a/browser/devtools/styleinspector/CssRuleView.jsm +++ b/browser/devtools/styleinspector/CssRuleView.jsm @@ -24,6 +24,12 @@ const CSS_LINE_RE = /(?:[^;\(]*(?:\([^\)]*?\))?[^;\(]*)*;?/g; // Used to parse a single property line. const CSS_PROP_RE = /\s*([^:\s]*)\s*:\s*(.*?)\s*(?:! (important))?;?$/; +// Used to parse an external resource from a property value +const CSS_RESOURCE_RE = /url\([\'\"]?(.*?)[\'\"]?\)/; + +const IOService = Components.classes["@mozilla.org/network/io-service;1"] + .getService(Components.interfaces.nsIIOService); + Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource:///modules/devtools/CssLogic.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -1321,6 +1327,12 @@ function TextPropertyEditor(aRuleEditor, aProperty) this.prop = aProperty; this.prop.editor = this; + let sheet = this.prop.rule.sheet; + let href = sheet ? CssLogic.href(sheet) : null; + if (href) { + this.sheetURI = IOService.newURI(href, null, null); + } + this._onEnableClicked = this._onEnableClicked.bind(this); this._onExpandClicked = this._onExpandClicked.bind(this); this._onStartEditing = this._onStartEditing.bind(this); @@ -1436,6 +1448,36 @@ TextPropertyEditor.prototype = { }); }, + /** + * Resolve a URI based on the rule stylesheet + * @param {string} relativePath the path to resolve + * @return {string} the resolved path. + */ + resolveURI: function(relativePath) + { + if (this.sheetURI) { + relativePath = this.sheetURI.resolve(relativePath); + } + return relativePath; + }, + + /** + * Check the property value to find an external resource (if any). + * @return {string} the URI in the property value, or null if there is no match. + */ + getResourceURI: function() + { + let val = this.prop.value; + let uriMatch = CSS_RESOURCE_RE.exec(val); + let uri = null; + + if (uriMatch && uriMatch[1]) { + uri = uriMatch[1]; + } + + return uri; + }, + /** * Populate the span based on changes to the TextProperty. */ @@ -1464,7 +1506,32 @@ TextPropertyEditor.prototype = { if (this.prop.priority) { val += " !" + this.prop.priority; } - this.valueSpan.textContent = val; + + // Treat URLs differently than other properties. + // Allow the user to click a link to the resource and open it. + let resourceURI = this.getResourceURI(); + if (resourceURI) { + this.valueSpan.textContent = ""; + + appendText(this.valueSpan, val.split(resourceURI)[0]); + + let a = createChild(this.valueSpan, "a", { + target: "_blank", + class: "theme-link", + textContent: resourceURI, + href: this.resolveURI(resourceURI) + }); + + a.addEventListener("click", function(aEvent) { + // Clicks within the link shouldn't trigger editing. + aEvent.stopPropagation(); + }, false); + + appendText(this.valueSpan, val.split(resourceURI)[1]); + } else { + this.valueSpan.textContent = val; + } + this.warning.hidden = this._validate(); let store = this.prop.rule.elementStyle.store; diff --git a/browser/devtools/styleinspector/ruleview.css b/browser/devtools/styleinspector/ruleview.css index 96af9dec68ec..086e506fc49c 100644 --- a/browser/devtools/styleinspector/ruleview.css +++ b/browser/devtools/styleinspector/ruleview.css @@ -28,6 +28,10 @@ padding-right: 15px; } +.ruleview-propertycontainer a { + cursor: pointer; +} + .ruleview-computedlist:not(.styleinspector-open), .ruleview-warning[hidden] { display: none; diff --git a/browser/devtools/styleinspector/test/Makefile.in b/browser/devtools/styleinspector/test/Makefile.in index 4de226d7067c..7d68cf61345c 100644 --- a/browser/devtools/styleinspector/test/Makefile.in +++ b/browser/devtools/styleinspector/test/Makefile.in @@ -37,6 +37,7 @@ MOCHITEST_BROWSER_FILES = \ browser_bug722691_rule_view_increment.js \ browser_computedview_734259_style_editor_link.js \ browser_computedview_copy.js\ + browser_styleinspector_bug_677930_urls_clickable.js \ head.js \ $(NULL) @@ -50,6 +51,10 @@ MOCHITEST_BROWSER_FILES += \ browser_bug705707_is_content_stylesheet.xul \ browser_bug705707_is_content_stylesheet_xul.css \ browser_bug722196_identify_media_queries.html \ + browser_styleinspector_bug_677930_urls_clickable.html \ + browser_styleinspector_bug_677930_urls_clickable \ + browser_styleinspector_bug_677930_urls_clickable/browser_styleinspector_bug_677930_urls_clickable.css \ + test-image.png \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/browser/devtools/styleinspector/test/browser_styleinspector_bug_677930_urls_clickable.html b/browser/devtools/styleinspector/test/browser_styleinspector_bug_677930_urls_clickable.html new file mode 100644 index 000000000000..44b5f0e3d1bd --- /dev/null +++ b/browser/devtools/styleinspector/test/browser_styleinspector_bug_677930_urls_clickable.html @@ -0,0 +1,21 @@ + + + + + + + + + +
Background image with relative path (loaded from external css)
+ +
Background image with absolute path (loaded from external css)
+ +
Background image with base64 url (loaded from external css)
+ +
Background image with relative path (loaded from style attribute)
'; + +
No background image :(
+ + diff --git a/browser/devtools/styleinspector/test/browser_styleinspector_bug_677930_urls_clickable.js b/browser/devtools/styleinspector/test/browser_styleinspector_bug_677930_urls_clickable.js new file mode 100644 index 000000000000..215313081a35 --- /dev/null +++ b/browser/devtools/styleinspector/test/browser_styleinspector_bug_677930_urls_clickable.js @@ -0,0 +1,97 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests to make sure that URLs are clickable in the rule view + +let doc; +let computedView; +let inspector; + +const BASE_URL = "http://example.com/browser/browser/" + + "devtools/styleinspector/test/"; +const TEST_URI = BASE_URL + + "browser_styleinspector_bug_677930_urls_clickable.html"; +const TEST_IMAGE = BASE_URL + "test-image.png"; +const BASE_64_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; + +function createDocument() +{ + doc.title = "Style Inspector URL Clickable test"; + + openInspector(function(aInspector) { + inspector = aInspector; + executeSoon(selectNode); + }); +} + + +function selectNode(aInspector) +{ + let sidebar = inspector.sidebar; + let iframe = sidebar._tabbox.querySelector(".iframe-ruleview"); + let contentDoc = iframe.contentWindow.document; + + let relative = doc.querySelector(".relative"); + let absolute = doc.querySelector(".absolute"); + let inline = doc.querySelector(".inline"); + let base64 = doc.querySelector(".base64"); + let noimage = doc.querySelector(".noimage"); + + ok(relative, "captain, we have the relative div"); + ok(absolute, "captain, we have the absolute div"); + ok(inline, "captain, we have the inline div"); + ok(base64, "captain, we have the base64 div"); + ok(noimage, "captain, we have the noimage div"); + + inspector.selection.setNode(relative); + is(inspector.selection.node, relative, "selection matches the relative element"); + let relativeLink = contentDoc.querySelector(".ruleview-propertycontainer a"); + ok (relativeLink, "Link exists for relative node"); + ok (relativeLink.getAttribute("href"), TEST_IMAGE); + + inspector.selection.setNode(absolute); + is(inspector.selection.node, absolute, "selection matches the absolute element"); + let absoluteLink = contentDoc.querySelector(".ruleview-propertycontainer a"); + ok (absoluteLink, "Link exists for absolute node"); + ok (absoluteLink.getAttribute("href"), TEST_IMAGE); + + inspector.selection.setNode(inline); + is(inspector.selection.node, inline, "selection matches the inline element"); + let inlineLink = contentDoc.querySelector(".ruleview-propertycontainer a"); + ok (inlineLink, "Link exists for inline node"); + ok (inlineLink.getAttribute("href"), TEST_IMAGE); + + inspector.selection.setNode(base64); + is(inspector.selection.node, base64, "selection matches the base64 element"); + let base64Link = contentDoc.querySelector(".ruleview-propertycontainer a"); + ok (base64Link, "Link exists for base64 node"); + ok (base64Link.getAttribute("href"), BASE_64_URL); + + inspector.selection.setNode(noimage); + is(inspector.selection.node, noimage, "selection matches the inline element"); + let noimageLink = contentDoc.querySelector(".ruleview-propertycontainer a"); + ok (!noimageLink, "There is no link for the node with no background image"); + + finishUp(); +} + +function finishUp() +{ + doc = computedView = inspector = null; + gBrowser.removeCurrentTab(); + finish(); +} + +function test() +{ + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) { + gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true); + doc = content.document; + waitForFocus(createDocument, content); + }, true); + + content.location = TEST_URI; +} diff --git a/browser/devtools/styleinspector/test/browser_styleinspector_bug_677930_urls_clickable/browser_styleinspector_bug_677930_urls_clickable.css b/browser/devtools/styleinspector/test/browser_styleinspector_bug_677930_urls_clickable/browser_styleinspector_bug_677930_urls_clickable.css new file mode 100644 index 000000000000..f55c54a89dff --- /dev/null +++ b/browser/devtools/styleinspector/test/browser_styleinspector_bug_677930_urls_clickable/browser_styleinspector_bug_677930_urls_clickable.css @@ -0,0 +1,9 @@ +.relative { + background-image: url(../test-image.png); +} +.absolute { + background: url("http://example.com/browser/browser/devtools/styleinspector/test/test-image.png"); +} +.base64 { + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='); +} \ No newline at end of file diff --git a/browser/devtools/styleinspector/test/test-image.png b/browser/devtools/styleinspector/test/test-image.png new file mode 100644 index 0000000000000000000000000000000000000000..769c636340e11f9d2a0b7eb6a84d574dd9563f0c GIT binary patch literal 580 zcmV-K0=xZ*P)?@9gaSt*os4-<6T^zln*-e<1(Y>eZ{a-@A8D7MlS80mJ}u z0SKQtbH>fs*!aH-1H=C_K)ecw?*efe5W7I}s#U9Y!PLVrKmdV>nKNfT1{(H16si$K zmjm&CV+al6D=8`c7X*o+82}JK4A3z64>JJB_`e(E3S)?2X{|^lf1sei(+1UtAY{Ww#jt?wfcn3@ zy!=0d5|Evi_8%aC7~Z{m#}1AKK|~<_M{=g1px}QcP=ErR57Iaj7$e5OumVLr$n^jL z1djz!sJcLHd57c@W2lWFi_p^m2m=HVoB>kgf@C|$pqa*ykjAAMgaHBwo)>`0c=vmt z+g3yQpuk*x7A(#H^u|wInF%0(FiZq_r2`t3pnwGh6fWCA7$ATcDb3CR0R{jJCzQv) SYsoAC0000