author | Brian Grinstead <briangrinstead@gmail.com> |
Fri, 19 Apr 2013 16:30:33 -0500 | |
changeset 141060 | 8911b764dc1e9b65d94facac590a737f10e6f958 |
parent 141059 | 229cd2ebe225bc6d6aa4038d491f04fcd958ae6d |
child 141061 | 40dafc3767942c72413c842a84b10a756b071ae8 |
push id | 2579 |
push user | akeybl@mozilla.com |
push date | Mon, 24 Jun 2013 18:52:47 +0000 |
treeherder | mozilla-beta@b69b7de8a05a [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
bugs | 677930 |
milestone | 23.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/devtools/styleinspector/CssLogic.jsm +++ b/browser/devtools/styleinspector/CssLogic.jsm @@ -740,16 +740,34 @@ CssLogic.isContentStylesheet = function if (aSheet.ownerRule instanceof Ci.nsIDOMCSSImportRule) { return CssLogic.isContentStylesheet(aSheet.parentStyleSheet); } 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. * * @param {CSSStyleSheet} aSheet the DOM object for the style sheet. */ CssLogic.shortSource = function CssLogic_shortSource(aSheet) { // Use a string like "inline" if there is no source href if (!aSheet || !aSheet.href) { @@ -922,31 +940,27 @@ CssSheet.prototype = { { if (this._mediaMatches === null) { this._mediaMatches = this._cssLogic.mediaMatches(this.domSheet); } return this._mediaMatches; }, /** - * 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; }, /** * Create a shorthand version of the href of a stylesheet. * * @return {string} the shorthand source of the stylesheet. */
--- a/browser/devtools/styleinspector/CssRuleView.jsm +++ b/browser/devtools/styleinspector/CssRuleView.jsm @@ -19,16 +19,22 @@ const XUL_NS = "http://www.mozilla.org/k */ // Used to split on css line separators 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"); Cu.import("resource:///modules/devtools/InplaceEditor.jsm"); this.EXPORTED_SYMBOLS = ["CssRuleView", "_ElementStyle"]; @@ -1316,16 +1322,22 @@ RuleEditor.prototype = { * @constructor */ function TextPropertyEditor(aRuleEditor, aProperty) { this.doc = aRuleEditor.doc; 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); this._onNameDone = this._onNameDone.bind(this); this._onValueDone = this._onValueDone.bind(this); this._create(); this.update(); @@ -1432,16 +1444,46 @@ TextPropertyEditor.prototype = { done: this._onValueDone, validate: this._validate.bind(this), warning: this.warning, advanceChars: ';' }); }, /** + * 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. */ update: function TextPropertyEditor_update() { if (this.prop.enabled) { this.enable.style.removeProperty("visibility"); this.enable.setAttribute("checked", ""); } else { @@ -1459,17 +1501,42 @@ TextPropertyEditor.prototype = { this.nameSpan.textContent = name; // Combine the property's value and priority into one string for // the value. let val = this.prop.value; 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; let propDirty = store.userProperties.contains(this.prop.rule.style, name); if (propDirty) { this.element.setAttribute("dirty", ""); } else { this.element.removeAttribute("dirty");
--- a/browser/devtools/styleinspector/ruleview.css +++ b/browser/devtools/styleinspector/ruleview.css @@ -23,12 +23,16 @@ cursor: text; } .ruleview-propertycontainer { cursor: text; padding-right: 15px; } +.ruleview-propertycontainer a { + cursor: pointer; +} + .ruleview-computedlist:not(.styleinspector-open), .ruleview-warning[hidden] { display: none; }
--- a/browser/devtools/styleinspector/test/Makefile.in +++ b/browser/devtools/styleinspector/test/Makefile.in @@ -32,24 +32,29 @@ MOCHITEST_BROWSER_FILES = \ browser_ruleview_update.js \ browser_bug705707_is_content_stylesheet.js \ browser_bug722196_property_view_media_queries.js \ browser_bug722196_rule_view_media_queries.js \ browser_bug_592743_specificity.js \ 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) MOCHITEST_BROWSER_FILES += \ browser_bug683672.html \ browser_bug705707_is_content_stylesheet.html \ browser_bug705707_is_content_stylesheet_imported.css \ browser_bug705707_is_content_stylesheet_imported2.css \ browser_bug705707_is_content_stylesheet_linked.css \ browser_bug705707_is_content_stylesheet_script.css \ 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
new file mode 100644 --- /dev/null +++ b/browser/devtools/styleinspector/test/browser_styleinspector_bug_677930_urls_clickable.html @@ -0,0 +1,21 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> + <head> + + <link href="./browser_styleinspector_bug_677930_urls_clickable/browser_styleinspector_bug_677930_urls_clickable.css" rel="stylesheet" type="text/css"> + + </head> + <body> + + <div class="relative">Background image with relative path (loaded from external css)</div> + + <div class="absolute">Background image with absolute path (loaded from external css)</div> + + <div class="base64">Background image with base64 url (loaded from external css)</div> + + <div class="inline" style="background: url(test-image.png);">Background image with relative path (loaded from style attribute)</div>'; + + <div class="noimage">No background image :(</div> + </body> +</html>
new file mode 100644 --- /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; +}
new file mode 100644 --- /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
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..769c636340e11f9d2a0b7eb6a84d574dd9563f0c GIT binary patch literal 580 zc$@)50=xZ*P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz)=5M`RCwBA z{Qv(y10?_;fS8au@87>?@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)?2<zN>X{|^lf1sei(+1<Me zFarPr2&}oIqvIC?)OL^o?-&p^!wh-{#k-;2f_VoZfS{%bLi}zFF<>UtAY{W<LBj?n zz6$CsfB=HI;P*^44eyZn#$YcBg1rF~2L~`P&;bI75#$0;v@zVf$It;Z4d`qJS0Dzu zh@l)BQx!mb7Roj*FK2kaXAgtR*|Q9LfP8=e0=od{pY0$UI-sVXf!f>w#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<MNUMnLSTYrIq9PS