This commit is contained in:
Tim Taubert 2013-04-27 17:34:36 +02:00
Родитель 9ba41b8203 71085f2b8c
Коммит d938a14112
11 изменённых файлов: 400 добавлений и 11 удалений

Просмотреть файл

@ -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);
}
}
});

Просмотреть файл

@ -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)

Просмотреть файл

@ -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(
[ "<!DOCTYPE html>",
"<html>",
" <head>",
" <title>Bug 855108</title>",
" </head>",
" <body>",
" <p>content</p>",
" </body>",
"</html>"
].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);
});
}

Просмотреть файл

@ -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;
},

Просмотреть файл

@ -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;

Просмотреть файл

@ -28,6 +28,10 @@
padding-right: 15px;
}
.ruleview-propertycontainer a {
cursor: pointer;
}
.ruleview-computedlist:not(.styleinspector-open),
.ruleview-warning[hidden] {
display: none;

Просмотреть файл

@ -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

Просмотреть файл

@ -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>

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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==');
}

Двоичные данные
browser/devtools/styleinspector/test/test-image.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 580 B