Bug 850336: Devtools box model should be editable. r=pbrosset

This commit is contained in:
Dave Townsend 2014-04-03 17:37:26 -07:00
Родитель 40a13dcd87
Коммит 54bf4a2bec
12 изменённых файлов: 842 добавлений и 147 удалений

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

@ -1,4 +1,10 @@
[DEFAULT]
support-files =
head.js
[browser_layoutview.js]
skip-if = true
[browser_editablemodel.js]
[browser_editablemodel_allproperties.js]
[browser_editablemodel_border.js]
[browser_editablemodel_stylerules.js]

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

@ -0,0 +1,145 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function getStyle(node, property) {
return node.style.getPropertyValue(property);
}
let doc;
let inspector;
let test = asyncTest(function*() {
let style = "div { margin: 10px; padding: 3px } #div1 { margin-top: 5px } #div2 { border-bottom: 1em solid black; } #div3 { padding: 2em; }";
let html = "<style>" + style + "</style><div id='div1'></div><div id='div2'></div><div id='div3'></div>"
let content = yield loadTab("data:text/html," + encodeURIComponent(html));
doc = content.document;
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = yield gDevTools.showToolbox(target, "inspector");
inspector = toolbox.getCurrentPanel();
inspector.sidebar.select("layoutview");
yield inspector.sidebar.once("layoutview-ready");
yield runTests();
yield gDevTools.closeToolbox(toolbox);
});
addTest("Test that editing margin dynamically updates the document, pressing escape cancels the changes",
function*() {
let node = doc.getElementById("div1");
is(getStyle(node, "margin-top"), "", "Should be no margin-top on the element.")
let view = yield selectNode(node);
let span = view.document.querySelector(".margin.top > span");
is(span.textContent, 5, "Should have the right value in the box model.");
EventUtils.synthesizeMouseAtCenter(span, {}, view);
let editor = view.document.querySelector(".styleinspector-propertyeditor");
ok(editor, "Should have opened the editor.");
is(editor.value, "5px", "Should have the right value in the editor.");
EventUtils.synthesizeKey("3", {}, view);
yield waitForUpdate();
is(getStyle(node, "margin-top"), "3px", "Should have updated the margin.");
EventUtils.synthesizeKey("VK_ESCAPE", {}, view);
yield waitForUpdate();
is(getStyle(node, "margin-top"), "", "Should be no margin-top on the element.")
is(span.textContent, 5, "Should have the right value in the box model.");
});
addTest("Test that arrow keys work correctly and pressing enter commits the changes",
function*() {
let node = doc.getElementById("div1");
is(getStyle(node, "margin-left"), "", "Should be no margin-top on the element.")
let view = yield selectNode(node);
let span = view.document.querySelector(".margin.left > span");
is(span.textContent, 10, "Should have the right value in the box model.");
EventUtils.synthesizeMouseAtCenter(span, {}, view);
let editor = view.document.querySelector(".styleinspector-propertyeditor");
ok(editor, "Should have opened the editor.");
is(editor.value, "10px", "Should have the right value in the editor.");
EventUtils.synthesizeKey("VK_UP", {}, view);
yield waitForUpdate();
is(editor.value, "11px", "Should have the right value in the editor.");
is(getStyle(node, "margin-left"), "11px", "Should have updated the margin.");
EventUtils.synthesizeKey("VK_DOWN", {}, view);
yield waitForUpdate();
is(editor.value, "10px", "Should have the right value in the editor.");
is(getStyle(node, "margin-left"), "10px", "Should have updated the margin.");
EventUtils.synthesizeKey("VK_UP", { shiftKey: true }, view);
yield waitForUpdate();
is(editor.value, "20px", "Should have the right value in the editor.");
is(getStyle(node, "margin-left"), "20px", "Should have updated the margin.");
EventUtils.synthesizeKey("VK_RETURN", {}, view);
yield waitForUpdate();
is(getStyle(node, "margin-left"), "20px", "Should be the right margin-top on the element.")
is(span.textContent, 20, "Should have the right value in the box model.");
});
addTest("Test that deleting the value removes the property but escape undoes that",
function*() {
let node = doc.getElementById("div1");
is(getStyle(node, "margin-left"), "20px", "Should be the right margin-top on the element.")
let view = yield selectNode(node);
let span = view.document.querySelector(".margin.left > span");
is(span.textContent, 20, "Should have the right value in the box model.");
EventUtils.synthesizeMouseAtCenter(span, {}, view);
let editor = view.document.querySelector(".styleinspector-propertyeditor");
ok(editor, "Should have opened the editor.");
is(editor.value, "20px", "Should have the right value in the editor.");
EventUtils.synthesizeKey("VK_DELETE", {}, view);
yield waitForUpdate();
is(editor.value, "", "Should have the right value in the editor.");
is(getStyle(node, "margin-left"), "", "Should have updated the margin.");
EventUtils.synthesizeKey("VK_ESCAPE", {}, view);
yield waitForUpdate();
is(getStyle(node, "margin-left"), "20px", "Should be the right margin-top on the element.")
is(span.textContent, 20, "Should have the right value in the box model.");
});
addTest("Test that deleting the value removes the property",
function*() {
let node = doc.getElementById("div1");
node.style.marginRight = "15px";
let view = yield selectNode(node);
let span = view.document.querySelector(".margin.right > span");
is(span.textContent, 15, "Should have the right value in the box model.");
EventUtils.synthesizeMouseAtCenter(span, {}, view);
let editor = view.document.querySelector(".styleinspector-propertyeditor");
ok(editor, "Should have opened the editor.");
is(editor.value, "15px", "Should have the right value in the editor.");
EventUtils.synthesizeKey("VK_DELETE", {}, view);
yield waitForUpdate();
is(editor.value, "", "Should have the right value in the editor.");
is(getStyle(node, "margin-right"), "", "Should have updated the margin.");
EventUtils.synthesizeKey("VK_RETURN", {}, view);
yield waitForUpdate();
is(getStyle(node, "margin-right"), "", "Should be the right margin-top on the element.")
is(span.textContent, 10, "Should have the right value in the box model.");
});

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

@ -0,0 +1,134 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function getStyle(node, property) {
return node.style.getPropertyValue(property);
}
let doc;
let inspector;
let test = asyncTest(function*() {
let style = "div { margin: 10px; padding: 3px } #div1 { margin-top: 5px } #div2 { border-bottom: 1em solid black; } #div3 { padding: 2em; }";
let html = "<style>" + style + "</style><div id='div1'></div><div id='div2'></div><div id='div3'></div>"
let content = yield loadTab("data:text/html," + encodeURIComponent(html));
doc = content.document;
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = yield gDevTools.showToolbox(target, "inspector");
inspector = toolbox.getCurrentPanel();
inspector.sidebar.select("layoutview");
yield inspector.sidebar.once("layoutview-ready");
yield runTests();
yield gDevTools.closeToolbox(toolbox);
});
addTest("When all properties are set on the node editing one should work",
function*() {
let node = doc.getElementById("div1");
node.style.padding = "5px";
let view = yield selectNode(node);
let span = view.document.querySelector(".padding.bottom > span");
is(span.textContent, 5, "Should have the right value in the box model.");
EventUtils.synthesizeMouseAtCenter(span, {}, view);
let editor = view.document.querySelector(".styleinspector-propertyeditor");
ok(editor, "Should have opened the editor.");
is(editor.value, "5px", "Should have the right value in the editor.");
EventUtils.synthesizeKey("7", {}, view);
yield waitForUpdate();
is(editor.value, "7", "Should have the right value in the editor.");
is(getStyle(node, "padding-bottom"), "7px", "Should have updated the padding");
EventUtils.synthesizeKey("VK_RETURN", {}, view);
yield waitForUpdate();
is(getStyle(node, "padding-bottom"), "7px", "Should be the right padding.")
is(span.textContent, 7, "Should have the right value in the box model.");
});
addTest("When all properties are set on the node editing one should work",
function*() {
let node = doc.getElementById("div1");
node.style.padding = "5px";
let view = yield selectNode(node);
let span = view.document.querySelector(".padding.left > span");
is(span.textContent, 5, "Should have the right value in the box model.");
EventUtils.synthesizeMouseAtCenter(span, {}, view);
let editor = view.document.querySelector(".styleinspector-propertyeditor");
ok(editor, "Should have opened the editor.");
is(editor.value, "5px", "Should have the right value in the editor.");
EventUtils.synthesizeKey("8", {}, view);
yield waitForUpdate();
is(editor.value, "8", "Should have the right value in the editor.");
is(getStyle(node, "padding-left"), "8px", "Should have updated the padding");
EventUtils.synthesizeKey("VK_ESCAPE", {}, view);
yield waitForUpdate();
is(getStyle(node, "padding-left"), "5px", "Should be the right padding.")
is(span.textContent, 5, "Should have the right value in the box model.");
});
addTest("When all properties are set on the node deleting one should work",
function*() {
let node = doc.getElementById("div1");
node.style.padding = "5px";
let view = yield selectNode(node);
let span = view.document.querySelector(".padding.left > span");
is(span.textContent, 5, "Should have the right value in the box model.");
EventUtils.synthesizeMouseAtCenter(span, {}, view);
let editor = view.document.querySelector(".styleinspector-propertyeditor");
ok(editor, "Should have opened the editor.");
is(editor.value, "5px", "Should have the right value in the editor.");
EventUtils.synthesizeKey("VK_DELETE", {}, view);
yield waitForUpdate();
is(editor.value, "", "Should have the right value in the editor.");
is(getStyle(node, "padding-left"), "", "Should have updated the padding");
EventUtils.synthesizeKey("VK_RETURN", {}, view);
yield waitForUpdate();
is(getStyle(node, "padding-left"), "", "Should be the right padding.")
is(span.textContent, 3, "Should have the right value in the box model.");
});
addTest("When all properties are set on the node deleting one then cancelling should work",
function*() {
let node = doc.getElementById("div1");
node.style.padding = "5px";
let view = yield selectNode(node);
let span = view.document.querySelector(".padding.left > span");
is(span.textContent, 5, "Should have the right value in the box model.");
EventUtils.synthesizeMouseAtCenter(span, {}, view);
let editor = view.document.querySelector(".styleinspector-propertyeditor");
ok(editor, "Should have opened the editor.");
is(editor.value, "5px", "Should have the right value in the editor.");
EventUtils.synthesizeKey("VK_DELETE", {}, view);
yield waitForUpdate();
is(editor.value, "", "Should have the right value in the editor.");
is(getStyle(node, "padding-left"), "", "Should have updated the padding");
EventUtils.synthesizeKey("VK_ESCAPE", {}, view);
yield waitForUpdate();
is(getStyle(node, "padding-left"), "5px", "Should be the right padding.")
is(span.textContent, 5, "Should have the right value in the box model.");
});

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

@ -0,0 +1,56 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function getStyle(node, property) {
return node.style.getPropertyValue(property);
}
let doc;
let inspector;
let test = asyncTest(function*() {
let style = "div { margin: 10px; padding: 3px } #div1 { margin-top: 5px } #div2 { border-bottom: 1em solid black; } #div3 { padding: 2em; }";
let html = "<style>" + style + "</style><div id='div1'></div><div id='div2'></div><div id='div3'></div>"
let content = yield loadTab("data:text/html," + encodeURIComponent(html));
doc = content.document;
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = yield gDevTools.showToolbox(target, "inspector");
inspector = toolbox.getCurrentPanel();
inspector.sidebar.select("layoutview");
yield inspector.sidebar.once("layoutview-ready");
yield runTests();
yield gDevTools.closeToolbox(toolbox);
});
addTest("Test that adding a border applies a border style when necessary",
function*() {
let node = doc.getElementById("div1");
is(getStyle(node, "border-top-width"), "", "Should have the right border");
is(getStyle(node, "border-top-style"), "", "Should have the right border");
let view = yield selectNode(node);
let span = view.document.querySelector(".border.top > span");
is(span.textContent, 0, "Should have the right value in the box model.");
EventUtils.synthesizeMouseAtCenter(span, {}, view);
let editor = view.document.querySelector(".styleinspector-propertyeditor");
ok(editor, "Should have opened the editor.");
is(editor.value, "0", "Should have the right value in the editor.");
EventUtils.synthesizeKey("1", {}, view);
yield waitForUpdate();
is(editor.value, "1", "Should have the right value in the editor.");
is(getStyle(node, "border-top-width"), "1px", "Should have the right border");
is(getStyle(node, "border-top-style"), "solid", "Should have the right border");
EventUtils.synthesizeKey("VK_ESCAPE", {}, view);
yield waitForUpdate();
is(getStyle(node, "border-top-width"), "", "Should be the right padding.")
is(getStyle(node, "border-top-style"), "", "Should have the right border");
is(span.textContent, 0, "Should have the right value in the box model.");
});

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

@ -0,0 +1,108 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function getStyle(node, property) {
return node.style.getPropertyValue(property);
}
let doc;
let inspector;
let test = asyncTest(function*() {
let style = "div { margin: 10px; padding: 3px } #div1 { margin-top: 5px } #div2 { border-bottom: 1em solid black; } #div3 { padding: 2em; }";
let html = "<style>" + style + "</style><div id='div1'></div><div id='div2'></div><div id='div3'></div>"
let content = yield loadTab("data:text/html," + encodeURIComponent(html));
doc = content.document;
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = yield gDevTools.showToolbox(target, "inspector");
inspector = toolbox.getCurrentPanel();
inspector.sidebar.select("layoutview");
yield inspector.sidebar.once("layoutview-ready");
yield runTests();
yield gDevTools.closeToolbox(toolbox);
});
addTest("Test that entering units works",
function*() {
let node = doc.getElementById("div1");
is(getStyle(node, "padding-top"), "", "Should have the right padding");
let view = yield selectNode(node);
let span = view.document.querySelector(".padding.top > span");
is(span.textContent, 3, "Should have the right value in the box model.");
EventUtils.synthesizeMouseAtCenter(span, {}, view);
let editor = view.document.querySelector(".styleinspector-propertyeditor");
ok(editor, "Should have opened the editor.");
is(editor.value, "3px", "Should have the right value in the editor.");
EventUtils.synthesizeKey("1", {}, view);
yield waitForUpdate();
EventUtils.synthesizeKey("e", {}, view);
yield waitForUpdate();
is(getStyle(node, "padding-top"), "", "An invalid value is handled cleanly");
EventUtils.synthesizeKey("m", {}, view);
yield waitForUpdate();
is(editor.value, "1em", "Should have the right value in the editor.");
is(getStyle(node, "padding-top"), "1em", "Should have updated the padding.");
EventUtils.synthesizeKey("VK_RETURN", {}, view);
yield waitForUpdate();
is(getStyle(node, "padding-top"), "1em", "Should be the right padding.")
is(span.textContent, 16, "Should have the right value in the box model.");
});
addTest("Test that we pick up the value from a higher style rule",
function*() {
let node = doc.getElementById("div2");
is(getStyle(node, "border-bottom-width"), "", "Should have the right border-bottom-width");
let view = yield selectNode(node);
let span = view.document.querySelector(".border.bottom > span");
is(span.textContent, 16, "Should have the right value in the box model.");
EventUtils.synthesizeMouseAtCenter(span, {}, view);
let editor = view.document.querySelector(".styleinspector-propertyeditor");
ok(editor, "Should have opened the editor.");
is(editor.value, "1em", "Should have the right value in the editor.");
EventUtils.synthesizeKey("0", {}, view);
yield waitForUpdate();
is(editor.value, "0", "Should have the right value in the editor.");
is(getStyle(node, "border-bottom-width"), "0px", "Should have updated the border.");
EventUtils.synthesizeKey("VK_RETURN", {}, view);
yield waitForUpdate();
is(getStyle(node, "border-bottom-width"), "0px", "Should be the right border-bottom-width.")
is(span.textContent, 0, "Should have the right value in the box model.");
});
addTest("Test that shorthand properties are parsed correctly",
function*() {
let node = doc.getElementById("div3");
is(getStyle(node, "padding-right"), "", "Should have the right padding");
let view = yield selectNode(node);
let span = view.document.querySelector(".padding.right > span");
is(span.textContent, 32, "Should have the right value in the box model.");
EventUtils.synthesizeMouseAtCenter(span, {}, view);
let editor = view.document.querySelector(".styleinspector-propertyeditor");
ok(editor, "Should have opened the editor.");
is(editor.value, "2em", "Should have the right value in the editor.");
EventUtils.synthesizeKey("VK_RETURN", {}, view);
yield waitForUpdate();
is(getStyle(node, "padding-right"), "", "Should be the right padding.")
is(span.textContent, 32, "Should have the right value in the box model.");
});

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

@ -1,134 +1,99 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let TargetFactory = devtools.TargetFactory;
// Expected values:
let res1 = [
{selector: "#element-size", value: "160x160"},
{selector: ".size > span", value: "100x100"},
{selector: ".margin.top > span", value: 30},
{selector: ".margin.left > span", value: "auto"},
{selector: ".margin.bottom > span", value: 30},
{selector: ".margin.right > span", value: "auto"},
{selector: ".padding.top > span", value: 20},
{selector: ".padding.left > span", value: 20},
{selector: ".padding.bottom > span", value: 20},
{selector: ".padding.right > span", value: 20},
{selector: ".border.top > span", value: 10},
{selector: ".border.left > span", value: 10},
{selector: ".border.bottom > span", value: 10},
{selector: ".border.right > span", value: 10},
];
function test() {
waitForExplicitFinish();
let res2 = [
{selector: "#element-size", value: "190x210"},
{selector: ".size > span", value: "100x150"},
{selector: ".margin.top > span", value: 30},
{selector: ".margin.left > span", value: "auto"},
{selector: ".margin.bottom > span", value: 30},
{selector: ".margin.right > span", value: "auto"},
{selector: ".padding.top > span", value: 20},
{selector: ".padding.left > span", value: 20},
{selector: ".padding.bottom > span", value: 20},
{selector: ".padding.right > span", value: 50},
{selector: ".border.top > span", value: 10},
{selector: ".border.left > span", value: 10},
{selector: ".border.bottom > span", value: 10},
{selector: ".border.right > span", value: 10},
];
gDevTools.testing = true;
SimpleTest.registerCleanupFunction(() => {
gDevTools.testing = false;
});
Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", true);
let doc;
let node;
let view;
let inspector;
// Expected values:
let res1 = [
{selector: "#element-size", value: "160x160"},
{selector: ".size > span", value: "100x100"},
{selector: ".margin.top > span", value: 30},
{selector: ".margin.left > span", value: "auto"},
{selector: ".margin.bottom > span", value: 30},
{selector: ".margin.right > span", value: "auto"},
{selector: ".padding.top > span", value: 20},
{selector: ".padding.left > span", value: 20},
{selector: ".padding.bottom > span", value: 20},
{selector: ".padding.right > span", value: 20},
{selector: ".border.top > span", value: 10},
{selector: ".border.left > span", value: 10},
{selector: ".border.bottom > span", value: 10},
{selector: ".border.right > span", value: 10},
];
let res2 = [
{selector: "#element-size", value: "190x210"},
{selector: ".size > span", value: "100x150"},
{selector: ".margin.top > span", value: 30},
{selector: ".margin.left > span", value: "auto"},
{selector: ".margin.bottom > span", value: 30},
{selector: ".margin.right > span", value: "auto"},
{selector: ".padding.top > span", value: 20},
{selector: ".padding.left > span", value: 20},
{selector: ".padding.bottom > span", value: 20},
{selector: ".padding.right > span", value: 50},
{selector: ".border.top > span", value: 10},
{selector: ".border.left > span", value: 10},
{selector: ".border.bottom > span", value: 10},
{selector: ".border.right > span", value: 10},
];
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload() {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
doc = content.document;
waitForFocus(setupTest, content);
}, true);
let inspector;
let view;
let test = asyncTest(function*() {
let style = "div { position: absolute; top: 42px; left: 42px; height: 100px; width: 100px; border: 10px solid black; padding: 20px; margin: 30px auto;}";
let html = "<style>" + style + "</style><div></div>"
content.location = "data:text/html," + encodeURIComponent(html);
function setupTest() {
node = doc.querySelector("div");
ok(node, "node found");
let content = yield loadTab("data:text/html," + encodeURIComponent(html));
let node = content.document.querySelector("div");
ok(node, "node found");
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
openLayoutView(toolbox.getCurrentPanel());
});
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = yield gDevTools.showToolbox(target, "inspector");
inspector = toolbox.getCurrentPanel();
info("Inspector open");
inspector.sidebar.select("layoutview");
yield inspector.sidebar.once("layoutview-ready");
inspector.selection.setNode(node);
yield inspector.once("inspector-updated");
info("Layout view ready");
view = inspector.sidebar.getWindowForTab("layoutview");
ok(!!view.layoutview, "LayoutView document is alive.");
yield runTests();
executeSoon(function() {
inspector._toolbox.destroy();
});
yield gDevTools.once("toolbox-destroyed");
});
addTest("Test that the initial values of the box model are correct",
function*() {
let viewdoc = view.document;
for (let i = 0; i < res1.length; i++) {
let elt = viewdoc.querySelector(res1[i].selector);
is(elt.textContent, res1[i].value, res1[i].selector + " has the right value.");
}
});
function openLayoutView(aInspector) {
inspector = aInspector;
addTest("Test that changing the document updates the box model",
function*() {
let viewdoc = view.document;
info("Inspector open");
inspector.selection.node.style.height = "150px";
inspector.selection.node.style.paddingRight = "50px";
inspector.sidebar.select("layoutview");
inspector.sidebar.once("layoutview-ready", () => {
inspector.selection.setNode(node);
inspector.once("inspector-updated", viewReady);
});
yield waitForUpdate();
for (let i = 0; i < res2.length; i++) {
let elt = viewdoc.querySelector(res2[i].selector);
is(elt.textContent, res2[i].value, res2[i].selector + " has the right value after style update.");
}
function viewReady() {
info("Layout view ready");
view = inspector.sidebar.getWindowForTab("layoutview");
ok(!!view.layoutview, "LayoutView document is alive.");
test1();
}
function test1() {
let viewdoc = view.document;
for (let i = 0; i < res1.length; i++) {
let elt = viewdoc.querySelector(res1[i].selector);
is(elt.textContent, res1[i].value, res1[i].selector + " has the right value.");
}
inspector.once("layoutview-updated", test2);
inspector.selection.node.style.height = "150px";
inspector.selection.node.style.paddingRight = "50px";
}
function test2() {
let viewdoc = view.document;
for (let i = 0; i < res2.length; i++) {
let elt = viewdoc.querySelector(res2[i].selector);
is(elt.textContent, res2[i].value, res2[i].selector + " has the right value after style update.");
}
executeSoon(function() {
gDevTools.once("toolbox-destroyed", finishUp);
inspector._toolbox.destroy();
});
}
function finishUp() {
Services.prefs.clearUserPref("devtools.inspector.sidebarOpen");
gBrowser.removeCurrentTab();
finish();
}
}
});

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

@ -0,0 +1,87 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://gre/modules/Task.jsm");
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const promise = devtools.require("sdk/core/promise");
let TargetFactory = devtools.TargetFactory;
Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", true);
Services.prefs.setIntPref("devtools.toolbox.footer.height", 350);
gDevTools.testing = true;
SimpleTest.registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.inspector.sidebarOpen");
Services.prefs.clearUserPref("devtools.toolbox.footer.height");
gDevTools.testing = false;
});
// All tests are async in general
waitForExplicitFinish();
function loadTab(url) {
let deferred = promise.defer();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload() {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
waitForFocus(function() {
deferred.resolve(content);
}, content);
}, true);
content.location = url;
return deferred.promise;
}
function selectNode(aNode) {
info("selecting node");
let onSelect = inspector.once("layoutview-updated");
inspector.selection.setNode(aNode, "test");
return onSelect.then(() => {
let view = inspector.sidebar.getWindowForTab("layoutview");
ok(!!view.layoutview, "LayoutView document is alive.");
return view;
});
}
function waitForUpdate() {
return inspector.once("layoutview-updated");
}
function asyncTest(testfunc) {
return Task.async(function*() {
let initialTab = gBrowser.selectedTab;
yield testfunc();
// Remove all tabs except for the initial tab. This is basically
// gBrowser.removeAllTabsBut without the animation
let tabs = gBrowser.visibleTabs;
gBrowser.selectedTab = initialTab;
for (let i = tabs.length - 1; i >= 0; i--) {
if (tabs[i] != initialTab)
gBrowser.removeTab(tabs[i]);
}
// Reset the sidebar back to the default
Services.prefs.setCharPref("devtools.inspector.activeSidebar", "ruleview");
finish();
});
}
var TESTS = [];
function addTest(message, func) {
TESTS.push([message, Task.async(func)])
}
var runTests = Task.async(function*() {
for (let [message, test] of TESTS) {
info(message);
yield test();
}
});

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

@ -86,9 +86,16 @@ body {
#main > p > span {
vertical-align: middle;
pointer-events: auto;
}
.size > span {
cursor: default;
}
.editable {
-moz-user-select: text;
}
.top,
.bottom {
width: calc(100% - 2px);
@ -179,4 +186,3 @@ body.dim > #main > p,
body.dim > #main > .tooltip {
visibility: hidden;
}

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

@ -11,10 +11,113 @@ const Ci = Components.interfaces;
const Cc = Components.classes;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/devtools/Loader.jsm");
Cu.import("resource://gre/modules/devtools/Console.jsm");
const promise = devtools.require("sdk/core/promise");
const {InplaceEditor, editableItem} = devtools.require("devtools/shared/inplace-editor");
const {parseDeclarations} = devtools.require("devtools/styleinspector/css-parsing-utils");
const NUMERIC = /^-?[\d\.]+$/;
/**
* An instance of EditingSession tracks changes that have been made during the
* modification of box model values. All of these changes can be reverted by
* calling revert.
*
* @param doc A DOM document that can be used to test style rules.
* @param rules An array of the style rules defined for the node being edited.
* These should be in order of priority, least important first.
*/
function EditingSession(doc, rules) {
this._doc = doc;
this._rules = rules;
this._modifications = new Map();
}
EditingSession.prototype = {
/**
* Gets the value of a single property from the CSS rule.
*
* @param rule The CSS rule
* @param property The name of the property
*/
getPropertyFromRule: function(rule, property) {
let dummyStyle = this._element.style;
dummyStyle.cssText = rule.cssText;
return dummyStyle.getPropertyValue(property);
},
/**
* Returns the current value for a property as a string or the empty string if
* no style rules affect the property.
*
* @param property The name of the property as a string
*/
getProperty: function(property) {
// Create a hidden element for getPropertyFromRule to use
let div = this._doc.createElement("div");
div.setAttribute("style", "display: none");
this._doc.body.appendChild(div);
this._element = this._doc.createElement("p");
div.appendChild(this._element);
// As the rules are in order of priority we can just iterate until we find
// the first that defines a value for the property and return that.
for (let rule of this._rules) {
let value = this.getPropertyFromRule(rule, property);
if (value !== "") {
div.remove();
return value;
}
}
div.remove();
return "";
},
/**
* Sets a number of properties on the node. Returns a promise that will be
* resolved when the modifications are complete.
*
* @param properties An array of properties, each is an object with name and
* value properties. If the value is "" then the property
* is removed.
*/
setProperties: function(properties) {
let modifications = this._rules[0].startModifyingProperties();
for (let property of properties) {
if (!this._modifications.has(property.name))
this._modifications.set(property.name, this.getPropertyFromRule(this._rules[0], property.name));
if (property.value == "")
modifications.removeProperty(property.name);
else
modifications.setProperty(property.name, property.value, "");
}
return modifications.apply().then(null, console.error);
},
/**
* Reverts all of the property changes made by this instance. Returns a
* promise that will be resolved when complete.
*/
revert: function() {
let modifications = this._rules[0].startModifyingProperties();
for (let [property, value] of this._modifications) {
if (value != "")
modifications.setProperty(property, value, "");
else
modifications.removeProperty(property);
}
return modifications.apply().then(null, console.error);
}
};
function LayoutView(aInspector, aWindow)
{
@ -54,11 +157,17 @@ LayoutView.prototype = {
marginBottom: {selector: ".margin.bottom > span",
property: "margin-bottom",
value: undefined},
// margin-left is a shorthand for some internal properties,
// margin-left-ltr-source and margin-left-rtl-source for example. The
// real margin value we want is in margin-left-value
marginLeft: {selector: ".margin.left > span",
property: "margin-left",
realProperty: "margin-left-value",
value: undefined},
// margin-right behaves the same as margin-left
marginRight: {selector: ".margin.right > span",
property: "margin-right",
realProperty: "margin-right-value",
value: undefined},
paddingTop: {selector: ".padding.top > span",
property: "padding-top",
@ -66,11 +175,15 @@ LayoutView.prototype = {
paddingBottom: {selector: ".padding.bottom > span",
property: "padding-bottom",
value: undefined},
// padding-left behaves the same as margin-left
paddingLeft: {selector: ".padding.left > span",
property: "padding-left",
realProperty: "padding-left-value",
value: undefined},
// padding-right behaves the same as margin-left
paddingRight: {selector: ".padding.right > span",
property: "padding-right",
realProperty: "padding-right-value",
value: undefined},
borderTop: {selector: ".border.top > span",
property: "border-top-width",
@ -86,9 +199,58 @@ LayoutView.prototype = {
value: undefined},
};
// Make each element the dimensions editable
for (let i in this.map) {
if (i == "position")
continue;
let dimension = this.map[i];
editableItem({ element: this.doc.querySelector(dimension.selector) }, (element, event) => {
this.initEditor(element, event, dimension);
});
}
this.onNewNode();
},
/**
* Called when the user clicks on one of the editable values in the layoutview
*/
initEditor: function LV_initEditor(element, event, dimension) {
let { property, realProperty } = dimension;
if (!realProperty)
realProperty = property;
let session = new EditingSession(document, this.elementRules);
let initialValue = session.getProperty(realProperty);
new InplaceEditor({
element: element,
initial: initialValue,
change: (value) => {
if (NUMERIC.test(value))
value += "px";
let properties = [
{ name: property, value: value }
]
if (property.substring(0, 7) == "border-") {
let bprop = property.substring(0, property.length - 5) + "style";
let style = session.getProperty(bprop);
if (!style || style == "none" || style == "hidden")
properties.push({ name: bprop, value: "solid" });
}
session.setProperties(properties);
},
done: (value, commit) => {
if (!commit)
session.revert();
}
}, event);
},
/**
* Is the layoutview visible in the sidebar?
*/
@ -158,23 +320,28 @@ LayoutView.prototype = {
/**
* Compute the dimensions of the node and update the values in
* the layoutview/view.xhtml document.
* the layoutview/view.xhtml document. Returns a promise that will be resolved
* when complete.
*/
update: function LV_update() {
if (!this.isActive() ||
!this.inspector.selection.isConnected() ||
!this.inspector.selection.isElementNode()) {
return promise.resolve(undefined);
}
let lastRequest = Task.spawn((function*() {
if (!this.isActive() ||
!this.inspector.selection.isConnected() ||
!this.inspector.selection.isElementNode()) {
return;
}
let node = this.inspector.selection.nodeFront;
let layout = yield this.inspector.pageStyle.getLayout(node, {
autoMargins: !this.dimmed
});
let styleEntries = yield this.inspector.pageStyle.getApplied(node, {});
let node = this.inspector.selection.nodeFront;
let lastRequest = this.inspector.pageStyle.getLayout(node, {
autoMargins: !this.dimmed
}).then(layout => {
// If a subsequent request has been made, wait for that one instead.
if (this._lastRequest != lastRequest) {
return this._lastRequest;
}
this._lastRequest = null;
let width = layout.width;
let height = layout.height;
@ -233,12 +400,12 @@ LayoutView.prototype = {
this.sizeLabel.textContent = newValue;
}
this.inspector.emit("layoutview-updated");
return null;
});
this.elementRules = [e.rule for (e of styleEntries)];
this._lastRequest = lastRequest;
return this._lastRequest;
this.inspector.emit("layoutview-updated");
}).bind(this)).then(null, console.error);
return this._lastRequest = lastRequest;
},
showBoxModel: function(options={}) {

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

@ -39,20 +39,20 @@
</div>
</div>
<p class="border top"><span data-box="border" tooltip="border-top"></span></p>
<p class="border right"><span data-box="border" tooltip="border-right"></span></p>
<p class="border bottom"><span data-box="border" tooltip="border-bottom"></span></p>
<p class="border left"><span data-box="border" tooltip="border-left"></span></p>
<p class="border top"><span data-box="border" class="editable" tooltip="border-top"></span></p>
<p class="border right"><span data-box="border" class="editable" tooltip="border-right"></span></p>
<p class="border bottom"><span data-box="border" class="editable" tooltip="border-bottom"></span></p>
<p class="border left"><span data-box="border" class="editable" tooltip="border-left"></span></p>
<p class="margin top"><span data-box="margin" tooltip="margin-top"></span></p>
<p class="margin right"><span data-box="margin" tooltip="margin-right"></span></p>
<p class="margin bottom"><span data-box="margin" tooltip="margin-bottom"></span></p>
<p class="margin left"><span data-box="margin" tooltip="margin-left"></span></p>
<p class="margin top"><span data-box="margin" class="editable" tooltip="margin-top"></span></p>
<p class="margin right"><span data-box="margin" class="editable" tooltip="margin-right"></span></p>
<p class="margin bottom"><span data-box="margin" class="editable" tooltip="margin-bottom"></span></p>
<p class="margin left"><span data-box="margin" class="editable" tooltip="margin-left"></span></p>
<p class="padding top"><span data-box="padding" tooltip="padding-top"></span></p>
<p class="padding right"><span data-box="padding" tooltip="padding-right"></span></p>
<p class="padding bottom"><span data-box="padding" tooltip="padding-bottom"></span></p>
<p class="padding left"><span data-box="padding" tooltip="padding-left"></span></p>
<p class="padding top"><span data-box="padding" class="editable" tooltip="padding-top"></span></p>
<p class="padding right"><span data-box="padding" class="editable" tooltip="padding-right"></span></p>
<p class="padding bottom"><span data-box="padding" class="editable" tooltip="padding-bottom"></span></p>
<p class="padding left"><span data-box="padding" class="editable" tooltip="padding-left"></span></p>
<p class="size"><span data-box="content" tooltip="&content.tooltip;"></span></p>
@ -60,5 +60,8 @@
</div>
<div style="display: none">
<p id="dummy"></p>
</div>
</body>
</html>

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

@ -376,6 +376,11 @@ InplaceEditor.prototype = {
this.input.setSelectionRange(newValue.start, newValue.end);
this._doValidation();
// Call the user's change handler if available.
if (this.change) {
this.change(this.input.value.trim());
}
return true;
},

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

@ -39,3 +39,16 @@
background-color: #d89b28;
opacity: 0.6;
}
.editable {
border-bottom: 1px dashed transparent;
}
.editable:hover {
border-bottom-color: hsl(0,0%,50%);
}
.styleinspector-propertyeditor {
border: 1px solid #CCC;
padding: 0;
}