mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge fx-team to m-c.
This commit is contained in:
commit
7755496a0c
@ -200,7 +200,7 @@ const PanelUI = {
|
||||
}
|
||||
},
|
||||
|
||||
isReady: function() {
|
||||
get isReady() {
|
||||
return !!this._isReady;
|
||||
},
|
||||
|
||||
|
@ -191,7 +191,7 @@ CustomizeMode.prototype = {
|
||||
// is really not going to work. We pass "true" to ensureReady to
|
||||
// indicate that we're handling calling startBatchUpdate and
|
||||
// endBatchUpdate.
|
||||
if (!window.PanelUI.isReady()) {
|
||||
if (!window.PanelUI.isReady) {
|
||||
yield window.PanelUI.ensureReady(true);
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
html {
|
||||
background-color: #111;
|
||||
background-image: url("chrome://browser/skin/devtools/noise.png");
|
||||
}
|
||||
|
||||
body {
|
||||
|
@ -5,7 +5,7 @@ support-files =
|
||||
head.js
|
||||
|
||||
[browser_layoutview.js]
|
||||
skip-if = true
|
||||
[browser_layoutview_rotate-labels-on-sides.js]
|
||||
[browser_editablemodel.js]
|
||||
[browser_editablemodel_allproperties.js]
|
||||
[browser_editablemodel_border.js]
|
||||
|
@ -1,145 +1,147 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that editing the box-model values works as expected and test various
|
||||
// key bindings
|
||||
|
||||
const TEST_URI = "<style>" +
|
||||
"div { margin: 10px; padding: 3px }" +
|
||||
"#div1 { margin-top: 5px }" +
|
||||
"#div2 { border-bottom: 1em solid black; }" +
|
||||
"#div3 { padding: 2em; }" +
|
||||
"</style>" +
|
||||
"<div id='div1'></div><div id='div2'></div><div id='div3'></div>";
|
||||
|
||||
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>"
|
||||
yield addTab("data:text/html," + encodeURIComponent(TEST_URI));
|
||||
let {toolbox, inspector, view} = yield openLayoutView();
|
||||
|
||||
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 runTests(inspector, view);
|
||||
// TODO: Closing the toolbox in this test leaks - bug 994314
|
||||
// yield gDevTools.closeToolbox(target);
|
||||
// yield destroyToolbox(inspector);
|
||||
});
|
||||
|
||||
addTest("Test that editing margin dynamically updates the document, pressing escape cancels the changes",
|
||||
function*() {
|
||||
let node = doc.getElementById("div1");
|
||||
function*(inspector, view) {
|
||||
let node = content.document.getElementById("div1");
|
||||
is(getStyle(node, "margin-top"), "", "Should be no margin-top on the element.")
|
||||
let view = yield selectNode(node);
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
let span = view.document.querySelector(".margin.top > span");
|
||||
let span = view.doc.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");
|
||||
EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
|
||||
let editor = view.doc.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();
|
||||
EventUtils.synthesizeKey("3", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
is(getStyle(node, "margin-top"), "3px", "Should have updated the margin.");
|
||||
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {}, view);
|
||||
yield waitForUpdate();
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
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");
|
||||
function*(inspector, view) {
|
||||
let node = content.document.getElementById("div1");
|
||||
is(getStyle(node, "margin-left"), "", "Should be no margin-top on the element.")
|
||||
let view = yield selectNode(node);
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
let span = view.document.querySelector(".margin.left > span");
|
||||
let span = view.doc.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");
|
||||
EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
|
||||
let editor = view.doc.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();
|
||||
EventUtils.synthesizeKey("VK_UP", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
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();
|
||||
EventUtils.synthesizeKey("VK_DOWN", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
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();
|
||||
EventUtils.synthesizeKey("VK_UP", { shiftKey: true }, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
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();
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, view.doc.defaultView);
|
||||
|
||||
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");
|
||||
function*(inspector, view) {
|
||||
let node = content.document.getElementById("div1");
|
||||
is(getStyle(node, "margin-left"), "20px", "Should be the right margin-top on the element.")
|
||||
let view = yield selectNode(node);
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
let span = view.document.querySelector(".margin.left > span");
|
||||
let span = view.doc.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");
|
||||
EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
|
||||
let editor = view.doc.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();
|
||||
EventUtils.synthesizeKey("VK_DELETE", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
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();
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
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);
|
||||
function*(inspector, view) {
|
||||
let node = content.document.getElementById("div1");
|
||||
|
||||
let span = view.document.querySelector(".margin.right > span");
|
||||
node.style.marginRight = "15px";
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
let span = view.doc.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");
|
||||
EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
|
||||
let editor = view.doc.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();
|
||||
EventUtils.synthesizeKey("VK_DELETE", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
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();
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, view.doc.defaultView);
|
||||
|
||||
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.");
|
||||
|
@ -1,135 +1,144 @@
|
||||
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test editing box model values when all values are set
|
||||
|
||||
const TEST_URI = "<style>" +
|
||||
"div { margin: 10px; padding: 3px }" +
|
||||
"#div1 { margin-top: 5px }" +
|
||||
"#div2 { border-bottom: 1em solid black; }" +
|
||||
"#div3 { padding: 2em; }" +
|
||||
"</style>" +
|
||||
"<div id='div1'></div><div id='div2'></div><div id='div3'></div>";
|
||||
|
||||
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>"
|
||||
yield addTab("data:text/html," + encodeURIComponent(TEST_URI));
|
||||
let {toolbox, inspector, view} = yield openLayoutView();
|
||||
|
||||
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 runTests(inspector, view);
|
||||
// TODO: Closing the toolbox in this test leaks - bug 994314
|
||||
// yield gDevTools.closeToolbox(target);
|
||||
// yield destroyToolbox(inspector);
|
||||
});
|
||||
|
||||
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);
|
||||
function*(inspector, view) {
|
||||
let node = content.document.getElementById("div1");
|
||||
|
||||
let span = view.document.querySelector(".padding.bottom > span");
|
||||
node.style.padding = "5px";
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
let span = view.doc.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");
|
||||
EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
|
||||
let editor = view.doc.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();
|
||||
EventUtils.synthesizeKey("7", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
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();
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, view.doc.defaultView);
|
||||
|
||||
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);
|
||||
function*(inspector, view) {
|
||||
let node = content.document.getElementById("div1");
|
||||
|
||||
let span = view.document.querySelector(".padding.left > span");
|
||||
node.style.padding = "5px";
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
let span = view.doc.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");
|
||||
EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
|
||||
let editor = view.doc.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();
|
||||
EventUtils.synthesizeKey("8", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
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();
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
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);
|
||||
function*(inspector, view) {
|
||||
let node = content.document.getElementById("div1");
|
||||
|
||||
let span = view.document.querySelector(".padding.left > span");
|
||||
node.style.padding = "5px";
|
||||
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
let span = view.doc.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");
|
||||
EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
|
||||
let editor = view.doc.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();
|
||||
EventUtils.synthesizeKey("VK_DELETE", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
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();
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, view.doc.defaultView);
|
||||
|
||||
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);
|
||||
function*(inspector, view) {
|
||||
let node = content.document.getElementById("div1");
|
||||
|
||||
let span = view.document.querySelector(".padding.left > span");
|
||||
node.style.padding = "5px";
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
let span = view.doc.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");
|
||||
EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
|
||||
let editor = view.doc.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();
|
||||
EventUtils.synthesizeKey("VK_DELETE", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
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();
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
is(getStyle(node, "padding-left"), "5px", "Should be the right padding.")
|
||||
is(span.textContent, 5, "Should have the right value in the box model.");
|
||||
|
@ -1,55 +1,56 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that editing the border value in the box model applies the border style
|
||||
|
||||
const TEST_URI = "<style>" +
|
||||
"div { margin: 10px; padding: 3px }" +
|
||||
"#div1 { margin-top: 5px }" +
|
||||
"#div2 { border-bottom: 1em solid black; }" +
|
||||
"#div3 { padding: 2em; }" +
|
||||
"</style>" +
|
||||
"<div id='div1'></div><div id='div2'></div><div id='div3'></div>";
|
||||
|
||||
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>"
|
||||
yield addTab("data:text/html," + encodeURIComponent(TEST_URI));
|
||||
let {toolbox, inspector, view} = yield openLayoutView();
|
||||
|
||||
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 runTests(inspector, view);
|
||||
// TODO: Closing the toolbox in this test leaks - bug 994314
|
||||
// yield gDevTools.closeToolbox(target);
|
||||
// yield destroyToolbox(inspector);
|
||||
});
|
||||
|
||||
addTest("Test that adding a border applies a border style when necessary",
|
||||
function*() {
|
||||
let node = doc.getElementById("div1");
|
||||
function*(inspector, view) {
|
||||
let node = content.document.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);
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
let span = view.document.querySelector(".border.top > span");
|
||||
let span = view.doc.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");
|
||||
EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
|
||||
let editor = view.doc.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();
|
||||
EventUtils.synthesizeKey("1", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
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();
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
is(getStyle(node, "border-top-width"), "", "Should be the right padding.")
|
||||
is(getStyle(node, "border-top-style"), "", "Should have the right border");
|
||||
|
@ -1,108 +1,107 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that units are displayed correctly when editing values in the box model
|
||||
// and that values are retrieved and parsed correctly from the back-end
|
||||
|
||||
const TEST_URI = "<style>" +
|
||||
"div { margin: 10px; padding: 3px }" +
|
||||
"#div1 { margin-top: 5px }" +
|
||||
"#div2 { border-bottom: 1em solid black; }" +
|
||||
"#div3 { padding: 2em; }" +
|
||||
"</style>" +
|
||||
"<div id='div1'></div><div id='div2'></div><div id='div3'></div>";
|
||||
|
||||
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>"
|
||||
yield addTab("data:text/html," + encodeURIComponent(TEST_URI));
|
||||
let {toolbox, inspector, view} = yield openLayoutView();
|
||||
|
||||
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 runTests(inspector, view);
|
||||
// TODO: Closing the toolbox in this test leaks - bug 994314
|
||||
// yield gDevTools.closeToolbox(target);
|
||||
// yield destroyToolbox(inspector);
|
||||
});
|
||||
|
||||
addTest("Test that entering units works",
|
||||
function*() {
|
||||
let node = doc.getElementById("div1");
|
||||
function*(inspector, view) {
|
||||
let node = content.document.getElementById("div1");
|
||||
is(getStyle(node, "padding-top"), "", "Should have the right padding");
|
||||
let view = yield selectNode(node);
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
let span = view.document.querySelector(".padding.top > span");
|
||||
let span = view.doc.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");
|
||||
EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
|
||||
let editor = view.doc.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();
|
||||
EventUtils.synthesizeKey("1", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
EventUtils.synthesizeKey("e", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
is(getStyle(node, "padding-top"), "", "An invalid value is handled cleanly");
|
||||
|
||||
EventUtils.synthesizeKey("m", {}, view);
|
||||
yield waitForUpdate();
|
||||
EventUtils.synthesizeKey("m", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
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();
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, view.doc.defaultView);
|
||||
|
||||
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");
|
||||
function*(inspector, view) {
|
||||
let node = content.document.getElementById("div2");
|
||||
is(getStyle(node, "border-bottom-width"), "", "Should have the right border-bottom-width");
|
||||
let view = yield selectNode(node);
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
let span = view.document.querySelector(".border.bottom > span");
|
||||
let span = view.doc.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");
|
||||
EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
|
||||
let editor = view.doc.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();
|
||||
EventUtils.synthesizeKey("0", {}, view.doc.defaultView);
|
||||
yield waitForUpdate(inspector);
|
||||
|
||||
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();
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, view.doc.defaultView);
|
||||
|
||||
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");
|
||||
function*(inspector, view) {
|
||||
let node = content.document.getElementById("div3");
|
||||
is(getStyle(node, "padding-right"), "", "Should have the right padding");
|
||||
let view = yield selectNode(node);
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
let span = view.document.querySelector(".padding.right > span");
|
||||
let span = view.doc.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");
|
||||
EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
|
||||
let editor = view.doc.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();
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, view.doc.defaultView);
|
||||
|
||||
is(getStyle(node, "padding-right"), "", "Should be the right padding.")
|
||||
is(span.textContent, 32, "Should have the right value in the box model.");
|
||||
|
@ -1,5 +1,11 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the layout-view displays the right values and that it updates when
|
||||
// the node's style is changed
|
||||
|
||||
// Expected values:
|
||||
let res1 = [
|
||||
@ -36,46 +42,22 @@ let res2 = [
|
||||
{selector: ".border.right > span", value: 10},
|
||||
];
|
||||
|
||||
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>"
|
||||
|
||||
let content = yield loadTab("data:text/html," + encodeURIComponent(html));
|
||||
let node = content.document.querySelector("div");
|
||||
ok(node, "node found");
|
||||
yield addTab("data:text/html," + encodeURIComponent(html));
|
||||
let {toolbox, inspector, view} = yield openLayoutView();
|
||||
yield selectNode("div", inspector);
|
||||
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
let toolbox = yield gDevTools.showToolbox(target, "inspector");
|
||||
inspector = toolbox.getCurrentPanel();
|
||||
yield runTests(inspector, view);
|
||||
|
||||
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");
|
||||
yield destroyToolbox(inspector);
|
||||
});
|
||||
|
||||
addTest("Test that the initial values of the box model are correct",
|
||||
function*() {
|
||||
let viewdoc = view.document;
|
||||
function*(inspector, view) {
|
||||
let viewdoc = view.doc;
|
||||
|
||||
for (let i = 0; i < res1.length; i++) {
|
||||
let elt = viewdoc.querySelector(res1[i].selector);
|
||||
@ -84,32 +66,16 @@ function*() {
|
||||
});
|
||||
|
||||
addTest("Test that changing the document updates the box model",
|
||||
function*() {
|
||||
let viewdoc = view.document;
|
||||
function*(inspector, view) {
|
||||
let viewdoc = view.doc;
|
||||
|
||||
let onUpdated = waitForUpdate(inspector);
|
||||
inspector.selection.node.style.height = "150px";
|
||||
inspector.selection.node.style.paddingRight = "50px";
|
||||
|
||||
yield waitForUpdate();
|
||||
yield onUpdated;
|
||||
|
||||
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.");
|
||||
}
|
||||
});
|
||||
|
||||
addTest("Test that long labels on left/right are rotated 90 degrees",
|
||||
function*() {
|
||||
let viewdoc = view.document;
|
||||
const LONG_TEXT_ROTATE_LIMIT = 3;
|
||||
|
||||
for (let i = 0; i < res1.length; i++) {
|
||||
let elt = viewdoc.querySelector(res1[i].selector);
|
||||
let isLong = elt.textContent.length > LONG_TEXT_ROTATE_LIMIT;
|
||||
let classList = elt.parentNode.classList
|
||||
let canBeRotated = classList.contains("left") || classList.contains("right");
|
||||
let isRotated = classList.contains("rotate");
|
||||
|
||||
is(canBeRotated && isLong, isRotated, res1[i].selector + " correctly rotated.");
|
||||
}
|
||||
});
|
||||
|
@ -0,0 +1,48 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that longer values are rotated on the side
|
||||
|
||||
const res1 = [
|
||||
{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: 2000000},
|
||||
{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},
|
||||
];
|
||||
|
||||
const TEST_URI = encodeURIComponent([
|
||||
"<style>",
|
||||
"div{border:10px solid black; padding: 20px 20px 20px 2000000px; margin: 30px auto;}",
|
||||
"</style>",
|
||||
"<div></div>"
|
||||
].join(""));
|
||||
const LONG_TEXT_ROTATE_LIMIT = 3;
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
yield addTab("data:text/html," + TEST_URI);
|
||||
let {toolbox, inspector, view} = yield openLayoutView();
|
||||
yield selectNode("div", inspector);
|
||||
|
||||
for (let i = 0; i < res1.length; i++) {
|
||||
let elt = view.doc.querySelector(res1[i].selector);
|
||||
let isLong = elt.textContent.length > LONG_TEXT_ROTATE_LIMIT;
|
||||
let classList = elt.parentNode.classList
|
||||
let canBeRotated = classList.contains("left") || classList.contains("right");
|
||||
let isRotated = classList.contains("rotate");
|
||||
|
||||
is(canBeRotated && isLong, isRotated, res1[i].selector + " correctly rotated.");
|
||||
}
|
||||
|
||||
yield destroyToolbox(inspector);
|
||||
});
|
@ -1,87 +1,242 @@
|
||||
/* 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/ */
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
let TargetFactory = devtools.TargetFactory;
|
||||
let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
let {console} = Components.utils.import("resource://gre/modules/devtools/Console.jsm", {});
|
||||
|
||||
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
|
||||
// All test are asynchronous
|
||||
waitForExplicitFinish();
|
||||
|
||||
function loadTab(url) {
|
||||
let deferred = promise.defer();
|
||||
const TEST_URL_ROOT = "http://example.com/browser/browser/devtools/layoutview/test/";
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
// Uncomment to log events
|
||||
// Services.prefs.setBoolPref("devtools.dump.emit", true);
|
||||
// Services.prefs.setBoolPref("devtools.debugger.log", true);
|
||||
|
||||
// Set the testing flag on gDevTools and reset it when the test ends
|
||||
gDevTools.testing = true;
|
||||
registerCleanupFunction(() => gDevTools.testing = false);
|
||||
|
||||
// Clean-up all prefs that might have been changed during a test run
|
||||
// (safer here because if the test fails, then the pref is never reverted)
|
||||
Services.prefs.setIntPref("devtools.toolbox.footer.height", 350);
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("devtools.dump.emit");
|
||||
Services.prefs.clearUserPref("devtools.debugger.log");
|
||||
Services.prefs.clearUserPref("devtools.toolbox.footer.height");
|
||||
Services.prefs.setCharPref("devtools.inspector.activeSidebar", "ruleview");
|
||||
});
|
||||
|
||||
// Auto close the toolbox and close the test tabs when the test ends
|
||||
registerCleanupFunction(() => {
|
||||
// For now, tests must call `yield destroyToolbox(inspector);` at the end.
|
||||
// This should normally be handled automatically here but some tests
|
||||
// are leaking when we do so (browser_editablemodel_border.js)
|
||||
// try {
|
||||
// let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
// gDevTools.closeToolbox(target);
|
||||
// } catch (ex) {
|
||||
// dump(ex);
|
||||
// }
|
||||
while (gBrowser.tabs.length > 1) {
|
||||
gBrowser.removeCurrentTab();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Define an async test based on a generator function
|
||||
*/
|
||||
function asyncTest(generator) {
|
||||
return () => Task.spawn(generator).then(null, ok.bind(null, false)).then(finish);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new test tab in the browser and load the given url.
|
||||
* @param {String} url The url to be loaded in the new tab
|
||||
* @return a promise that resolves to the tab object when the url is loaded
|
||||
*/
|
||||
function addTab(url) {
|
||||
let def = promise.defer();
|
||||
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
waitForFocus(function() {
|
||||
deferred.resolve(content);
|
||||
info("URL " + url + " loading complete into new test tab");
|
||||
waitForFocus(() => {
|
||||
def.resolve(tab);
|
||||
}, content);
|
||||
}, true);
|
||||
|
||||
content.location = url;
|
||||
|
||||
return deferred.promise;
|
||||
return def.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.");
|
||||
/**
|
||||
* Destroy the toolbox
|
||||
* @param {InspectorPanel}
|
||||
* @return a promise that resolves when destroyed
|
||||
*/
|
||||
let destroyToolbox = Task.async(function*(inspector) {
|
||||
let onDestroyed = gDevTools.once("toolbox-destroyed");
|
||||
inspector._toolbox.destroy();
|
||||
yield onDestroyed;
|
||||
});
|
||||
|
||||
return view;
|
||||
});
|
||||
/**
|
||||
* Simple DOM node accesor function that takes either a node or a string css
|
||||
* selector as argument and returns the corresponding node
|
||||
* @param {String|DOMNode} nodeOrSelector
|
||||
* @return {DOMNode}
|
||||
*/
|
||||
function getNode(nodeOrSelector) {
|
||||
return typeof nodeOrSelector === "string" ?
|
||||
content.document.querySelector(nodeOrSelector) :
|
||||
nodeOrSelector;
|
||||
}
|
||||
|
||||
function waitForUpdate() {
|
||||
/**
|
||||
* Set the inspector's current selection to a node or to the first match of the
|
||||
* given css selector
|
||||
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
|
||||
* loaded in the toolbox
|
||||
* @param {String} reason Defaults to "test" which instructs the inspector not
|
||||
* to highlight the node upon selection
|
||||
* @param {String} reason Defaults to "test" which instructs the inspector not
|
||||
* to highlight the node upon selection
|
||||
* @return a promise that resolves when the inspector is updated with the new
|
||||
* node
|
||||
*/
|
||||
function selectNode(nodeOrSelector, inspector, reason="test") {
|
||||
info("Selecting the node " + nodeOrSelector);
|
||||
let node = getNode(nodeOrSelector);
|
||||
let updated = inspector.once("inspector-updated");
|
||||
inspector.selection.setNode(node, reason);
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the toolbox, with the inspector tool visible.
|
||||
* @return a promise that resolves when the inspector is ready
|
||||
*/
|
||||
let openInspector = Task.async(function*() {
|
||||
info("Opening the inspector");
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
|
||||
let inspector, toolbox;
|
||||
|
||||
// Checking if the toolbox and the inspector are already loaded
|
||||
// The inspector-updated event should only be waited for if the inspector
|
||||
// isn't loaded yet
|
||||
toolbox = gDevTools.getToolbox(target);
|
||||
if (toolbox) {
|
||||
inspector = toolbox.getPanel("inspector");
|
||||
if (inspector) {
|
||||
info("Toolbox and inspector already open");
|
||||
return {
|
||||
toolbox: toolbox,
|
||||
inspector: inspector
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
info("Opening the toolbox");
|
||||
toolbox = yield gDevTools.showToolbox(target, "inspector");
|
||||
yield waitForToolboxFrameFocus(toolbox);
|
||||
inspector = toolbox.getPanel("inspector");
|
||||
|
||||
info("Waiting for the inspector to update");
|
||||
yield inspector.once("inspector-updated");
|
||||
|
||||
return {
|
||||
toolbox: toolbox,
|
||||
inspector: inspector
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Wait for the toolbox frame to receive focus after it loads
|
||||
* @param {Toolbox} toolbox
|
||||
* @return a promise that resolves when focus has been received
|
||||
*/
|
||||
function waitForToolboxFrameFocus(toolbox) {
|
||||
info("Making sure that the toolbox's frame is focused");
|
||||
let def = promise.defer();
|
||||
let win = toolbox.frame.contentWindow;
|
||||
waitForFocus(def.resolve, win);
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the inspector's sidebar corresponding to the given id already
|
||||
* exists
|
||||
* @param {InspectorPanel}
|
||||
* @param {String}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function hasSideBarTab(inspector, id) {
|
||||
return !!inspector.sidebar.getWindowForTab(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the toolbox, with the inspector tool visible, and the layout-view
|
||||
* sidebar tab selected.
|
||||
* @return a promise that resolves when the inspector is ready and the layout
|
||||
* view is visible and ready
|
||||
*/
|
||||
let openLayoutView = Task.async(function*() {
|
||||
let {toolbox, inspector} = yield openInspector();
|
||||
|
||||
if (!hasSideBarTab(inspector, "layoutview")) {
|
||||
info("Waiting for the layoutview sidebar to be ready");
|
||||
yield inspector.sidebar.once("layoutview-ready");
|
||||
}
|
||||
|
||||
info("Selecting the layoutview sidebar");
|
||||
inspector.sidebar.select("layoutview");
|
||||
|
||||
return {
|
||||
toolbox: toolbox,
|
||||
inspector: inspector,
|
||||
view: inspector.sidebar.getWindowForTab("layoutview")["layoutview"]
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Wait for the layoutview-updated event
|
||||
* @return a promise
|
||||
*/
|
||||
function waitForUpdate(inspector) {
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The addTest/runTests function couple provides a simple way to define
|
||||
* subsequent test cases in a test file. Example:
|
||||
*
|
||||
* addTest("what this test does", function*() {
|
||||
* ... actual code for the test ...
|
||||
* });
|
||||
* addTest("what this second test does", function*() {
|
||||
* ... actual code for the second test ...
|
||||
* });
|
||||
* runTests().then(...);
|
||||
*/
|
||||
var TESTS = [];
|
||||
|
||||
function addTest(message, func) {
|
||||
TESTS.push([message, Task.async(func)])
|
||||
}
|
||||
|
||||
var runTests = Task.async(function*() {
|
||||
let runTests = Task.async(function*(...args) {
|
||||
for (let [message, test] of TESTS) {
|
||||
info(message);
|
||||
yield test();
|
||||
info("Running new test case: " + message);
|
||||
yield test.apply(null, args);
|
||||
}
|
||||
});
|
||||
|
@ -18,6 +18,7 @@ Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
const {InplaceEditor, editableItem} = devtools.require("devtools/shared/inplace-editor");
|
||||
const {parseDeclarations} = devtools.require("devtools/styleinspector/css-parsing-utils");
|
||||
const {ReflowFront} = devtools.require("devtools/server/actors/layout");
|
||||
|
||||
const NUMERIC = /^-?[\d\.]+$/;
|
||||
const LONG_TEXT_ROTATE_LIMIT = 3;
|
||||
@ -90,13 +91,16 @@ EditingSession.prototype = {
|
||||
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 (!this._modifications.has(property.name)) {
|
||||
this._modifications.set(property.name,
|
||||
this.getPropertyFromRule(this._rules[0], property.name));
|
||||
}
|
||||
|
||||
if (property.value == "")
|
||||
if (property.value == "") {
|
||||
modifications.removeProperty(property.name);
|
||||
else
|
||||
} else {
|
||||
modifications.setProperty(property.name, property.value, "");
|
||||
}
|
||||
}
|
||||
|
||||
return modifications.apply().then(null, console.error);
|
||||
@ -110,26 +114,33 @@ EditingSession.prototype = {
|
||||
let modifications = this._rules[0].startModifyingProperties();
|
||||
|
||||
for (let [property, value] of this._modifications) {
|
||||
if (value != "")
|
||||
if (value != "") {
|
||||
modifications.setProperty(property, value, "");
|
||||
else
|
||||
} else {
|
||||
modifications.removeProperty(property);
|
||||
}
|
||||
}
|
||||
|
||||
return modifications.apply().then(null, console.error);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this._doc = null;
|
||||
this._rules = null;
|
||||
this._modifications.clear();
|
||||
}
|
||||
};
|
||||
|
||||
function LayoutView(aInspector, aWindow)
|
||||
{
|
||||
this.inspector = aInspector;
|
||||
/**
|
||||
* The layout-view panel
|
||||
* @param {InspectorPanel} inspector An instance of the inspector-panel
|
||||
* currently loaded in the toolbox
|
||||
* @param {Window} win The window containing the panel
|
||||
*/
|
||||
function LayoutView(inspector, win) {
|
||||
this.inspector = inspector;
|
||||
|
||||
// <browser> is not always available (for Chrome targets for example)
|
||||
if (this.inspector.target.tab) {
|
||||
this.browser = aInspector.target.tab.linkedBrowser;
|
||||
}
|
||||
|
||||
this.doc = aWindow.document;
|
||||
this.doc = win.document;
|
||||
this.sizeLabel = this.doc.querySelector(".size > span");
|
||||
this.sizeHeadingLabel = this.doc.getElementById("element-size");
|
||||
|
||||
@ -137,13 +148,18 @@ function LayoutView(aInspector, aWindow)
|
||||
}
|
||||
|
||||
LayoutView.prototype = {
|
||||
init: function LV_init() {
|
||||
init: function() {
|
||||
this.update = this.update.bind(this);
|
||||
this.onNewNode = this.onNewNode.bind(this);
|
||||
|
||||
this.onNewSelection = this.onNewSelection.bind(this);
|
||||
this.inspector.selection.on("new-node-front", this.onNewSelection);
|
||||
|
||||
this.onNewNode = this.onNewNode.bind(this);
|
||||
this.inspector.sidebar.on("layoutview-selected", this.onNewNode);
|
||||
|
||||
this.onSidebarSelect = this.onSidebarSelect.bind(this);
|
||||
this.inspector.sidebar.on("select", this.onSidebarSelect);
|
||||
|
||||
// Store for the different dimensions of the node.
|
||||
// 'selector' refers to the element that holds the value in view.xhtml;
|
||||
// 'property' is what we are measuring;
|
||||
@ -206,7 +222,9 @@ LayoutView.prototype = {
|
||||
continue;
|
||||
|
||||
let dimension = this.map[i];
|
||||
editableItem({ element: this.doc.querySelector(dimension.selector) }, (element, event) => {
|
||||
editableItem({
|
||||
element: this.doc.querySelector(dimension.selector)
|
||||
}, (element, event) => {
|
||||
this.initEditor(element, event, dimension);
|
||||
});
|
||||
}
|
||||
@ -214,10 +232,39 @@ LayoutView.prototype = {
|
||||
this.onNewNode();
|
||||
},
|
||||
|
||||
/**
|
||||
* Start listening to reflows in the current tab.
|
||||
*/
|
||||
trackReflows: function() {
|
||||
if (!this.reflowFront) {
|
||||
let toolbox = this.inspector.toolbox;
|
||||
if (toolbox.target.form.reflowActor) {
|
||||
this.reflowFront = ReflowFront(toolbox.target.client, toolbox.target.form);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.reflowFront.on("reflows", this.update);
|
||||
this.reflowFront.start();
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop listening to reflows in the current tab.
|
||||
*/
|
||||
untrackReflows: function() {
|
||||
if (!this.reflowFront) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.reflowFront.off("reflows", this.update);
|
||||
this.reflowFront.stop();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the user clicks on one of the editable values in the layoutview
|
||||
*/
|
||||
initEditor: function LV_initEditor(element, event, dimension) {
|
||||
initEditor: function(element, event, dimension) {
|
||||
let { property, realProperty } = dimension;
|
||||
if (!realProperty)
|
||||
realProperty = property;
|
||||
@ -233,17 +280,20 @@ LayoutView.prototype = {
|
||||
},
|
||||
|
||||
change: (value) => {
|
||||
if (NUMERIC.test(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")
|
||||
if (!style || style == "none" || style == "hidden") {
|
||||
properties.push({ name: bprop, value: "solid" });
|
||||
}
|
||||
}
|
||||
|
||||
session.setProperties(properties);
|
||||
@ -251,8 +301,10 @@ LayoutView.prototype = {
|
||||
|
||||
done: (value, commit) => {
|
||||
editor.elt.parentNode.classList.remove("editing");
|
||||
if (!commit)
|
||||
if (!commit) {
|
||||
session.revert();
|
||||
session.destroy();
|
||||
}
|
||||
}
|
||||
}, event);
|
||||
},
|
||||
@ -260,23 +312,35 @@ LayoutView.prototype = {
|
||||
/**
|
||||
* Is the layoutview visible in the sidebar?
|
||||
*/
|
||||
isActive: function LV_isActive() {
|
||||
return this.inspector.sidebar.getCurrentTabID() == "layoutview";
|
||||
isActive: function() {
|
||||
return this.inspector &&
|
||||
this.inspector.sidebar.getCurrentTabID() == "layoutview";
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the nodes. Remove listeners.
|
||||
*/
|
||||
destroy: function LV_destroy() {
|
||||
destroy: function() {
|
||||
this.inspector.sidebar.off("layoutview-selected", this.onNewNode);
|
||||
this.inspector.selection.off("new-node-front", this.onNewSelection);
|
||||
if (this.browser) {
|
||||
this.browser.removeEventListener("MozAfterPaint", this.update, true);
|
||||
}
|
||||
this.inspector.sidebar.off("select", this.onSidebarSelect);
|
||||
|
||||
this.sizeHeadingLabel = null;
|
||||
this.sizeLabel = null;
|
||||
this.inspector = null;
|
||||
this.doc = null;
|
||||
|
||||
if (this.reflowFront) {
|
||||
this.untrackReflows();
|
||||
this.reflowFront.destroy();
|
||||
this.reflowFront = null;
|
||||
}
|
||||
},
|
||||
|
||||
onSidebarSelect: function(e, sidebar) {
|
||||
if (sidebar !== "layoutview") {
|
||||
this.dim();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -287,7 +351,10 @@ LayoutView.prototype = {
|
||||
this.onNewNode().then(done, (err) => { console.error(err); done() });
|
||||
},
|
||||
|
||||
onNewNode: function LV_onNewNode() {
|
||||
/**
|
||||
* @return a promise that resolves when the view has been updated
|
||||
*/
|
||||
onNewNode: function() {
|
||||
if (this.isActive() &&
|
||||
this.inspector.selection.isConnected() &&
|
||||
this.inspector.selection.isElementNode()) {
|
||||
@ -295,41 +362,36 @@ LayoutView.prototype = {
|
||||
} else {
|
||||
this.dim();
|
||||
}
|
||||
|
||||
return this.update();
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the layout boxes. No node are selected.
|
||||
* Hide the layout boxes and stop refreshing on reflows. No node is selected
|
||||
* or the layout-view sidebar is inactive.
|
||||
*/
|
||||
dim: function LV_dim() {
|
||||
if (this.browser) {
|
||||
this.browser.removeEventListener("MozAfterPaint", this.update, true);
|
||||
}
|
||||
this.trackingPaint = false;
|
||||
dim: function() {
|
||||
this.untrackReflows();
|
||||
this.doc.body.classList.add("dim");
|
||||
this.dimmed = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the layout boxes. A node is selected.
|
||||
* Show the layout boxes and start refreshing on reflows. A node is selected
|
||||
* and the layout-view side is active.
|
||||
*/
|
||||
undim: function LV_undim() {
|
||||
if (!this.trackingPaint) {
|
||||
if (this.browser) {
|
||||
this.browser.addEventListener("MozAfterPaint", this.update, true);
|
||||
}
|
||||
this.trackingPaint = true;
|
||||
}
|
||||
undim: function() {
|
||||
this.trackReflows();
|
||||
this.doc.body.classList.remove("dim");
|
||||
this.dimmed = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Compute the dimensions of the node and update the values in
|
||||
* the layoutview/view.xhtml document. Returns a promise that will be resolved
|
||||
* when complete.
|
||||
* the layoutview/view.xhtml document.
|
||||
* @return a promise that will be resolved when complete.
|
||||
*/
|
||||
update: function LV_update() {
|
||||
update: function() {
|
||||
let lastRequest = Task.spawn((function*() {
|
||||
if (!this.isActive() ||
|
||||
!this.inspector.selection.isConnected() ||
|
||||
@ -415,6 +477,10 @@ LayoutView.prototype = {
|
||||
return this._lastRequest = lastRequest;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the box-model highlighter on the currently selected element
|
||||
* @param {Object} options Options passed to the highlighter actor
|
||||
*/
|
||||
showBoxModel: function(options={}) {
|
||||
let toolbox = this.inspector.toolbox;
|
||||
let nodeFront = this.inspector.selection.nodeFront;
|
||||
@ -422,6 +488,9 @@ LayoutView.prototype = {
|
||||
toolbox.highlighterUtils.highlightNodeFront(nodeFront, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the box-model highlighter on the currently selected element
|
||||
*/
|
||||
hideBoxModel: function() {
|
||||
let toolbox = this.inspector.toolbox;
|
||||
|
||||
|
@ -191,7 +191,7 @@ ElementStyle.prototype = {
|
||||
return this.dummyElementPromise.then(() => {
|
||||
if (this.populated != populated) {
|
||||
// Don't care anymore.
|
||||
return promise.reject("unused");
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the current list of rules (if any) during the population
|
||||
|
@ -37,6 +37,10 @@ registerCleanupFunction(() => {
|
||||
// Uncomment to log events
|
||||
// Services.prefs.setBoolPref("devtools.dump.emit", true);
|
||||
|
||||
// Set the testing flag on gDevTools and reset it when the test ends
|
||||
gDevTools.testing = true;
|
||||
registerCleanupFunction(() => gDevTools.testing = false);
|
||||
|
||||
// Clean-up all prefs that might have been changed during a test run
|
||||
// (safer here because if the test fails, then the pref is never reverted)
|
||||
registerCleanupFunction(() => {
|
||||
|
@ -231,7 +231,7 @@ panelview:not([mainview]) .toolbarbutton-text,
|
||||
|
||||
#PanelUI-contents {
|
||||
display: block;
|
||||
flex: auto;
|
||||
flex: 1 0 auto;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: .5em 0;
|
||||
@ -787,7 +787,7 @@ menuitem.panel-subview-footer@menuStateActive@,
|
||||
}
|
||||
|
||||
/* Popups with only one item don't have a footer */
|
||||
menupopup[placespopup=true][singleitempopup=true] > hbox > .popup-internal-box > .arrowscrollbox-scrollbox > .scrollbox-innerbox,
|
||||
#BMB_bookmarksPopup menupopup[placespopup=true][singleitempopup=true] > hbox > .popup-internal-box > .arrowscrollbox-scrollbox > .scrollbox-innerbox,
|
||||
/* These popups never have a footer */
|
||||
#BMB_bookmarksToolbarPopup > hbox > .popup-internal-box > .arrowscrollbox-scrollbox > .scrollbox-innerbox,
|
||||
#BMB_unsortedBookmarksPopup > hbox > .popup-internal-box > .arrowscrollbox-scrollbox > .scrollbox-innerbox {
|
||||
|
@ -9,11 +9,15 @@ import java.io.BufferedReader;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
@ -105,7 +109,9 @@ import android.view.TextureView;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.webkit.URLUtil;
|
||||
import android.widget.AbsoluteLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class GeckoAppShell
|
||||
{
|
||||
@ -2639,4 +2645,77 @@ public class GeckoAppShell
|
||||
return "DIRECT";
|
||||
}
|
||||
|
||||
/* Downloads the uri pointed to by a share intent, and alters the intent to point to the locally stored file.
|
||||
*/
|
||||
public static void downloadImageForIntent(final Intent intent) {
|
||||
final String src = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
final File dir = GeckoApp.getTempDirectory();
|
||||
|
||||
if (dir == null) {
|
||||
showImageShareFailureToast();
|
||||
return;
|
||||
}
|
||||
|
||||
GeckoApp.deleteTempFiles();
|
||||
|
||||
String type = intent.getType();
|
||||
OutputStream os = null;
|
||||
try {
|
||||
// Create a temporary file for the image
|
||||
if (src.startsWith("data:")) {
|
||||
final int dataStart = src.indexOf(",");
|
||||
|
||||
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(type);
|
||||
|
||||
// If we weren't given an explicit mimetype, try to dig one out of the data uri.
|
||||
if (TextUtils.isEmpty(extension) && dataStart > 5) {
|
||||
type = src.substring(5, dataStart).replace(";base64", "");
|
||||
extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(type);
|
||||
}
|
||||
|
||||
final File imageFile = File.createTempFile("image", "." + extension, dir);
|
||||
os = new FileOutputStream(imageFile);
|
||||
|
||||
byte[] buf = Base64.decode(src.substring(dataStart + 1), Base64.DEFAULT);
|
||||
os.write(buf);
|
||||
|
||||
// Only alter the intent when we're sure everything has worked
|
||||
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(imageFile));
|
||||
} else {
|
||||
InputStream is = null;
|
||||
try {
|
||||
final byte[] buf = new byte[2048];
|
||||
final URL url = new URL(src);
|
||||
final String filename = URLUtil.guessFileName(src, null, type);
|
||||
is = url.openStream();
|
||||
|
||||
final File imageFile = new File(dir, filename);
|
||||
os = new FileOutputStream(imageFile);
|
||||
|
||||
int length;
|
||||
while ((length = is.read(buf)) != -1) {
|
||||
os.write(buf, 0, length);
|
||||
}
|
||||
|
||||
// Only alter the intent when we're sure everything has worked
|
||||
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(imageFile));
|
||||
} finally {
|
||||
safeStreamClose(is);
|
||||
}
|
||||
}
|
||||
} catch(IOException ex) {
|
||||
// If something went wrong, we'll just leave the intent un-changed
|
||||
} finally {
|
||||
safeStreamClose(os);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't fail silently, tell the user that we weren't able to share the image
|
||||
private static final void showImageShareFailureToast() {
|
||||
Toast toast = Toast.makeText(getContext(),
|
||||
getContext().getResources().getString(R.string.share_image_failed),
|
||||
Toast.LENGTH_SHORT);
|
||||
toast.show();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,12 +7,13 @@ import android.database.Cursor;
|
||||
import android.database.DataSetObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.util.ArrayMap;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
|
||||
import org.mozilla.gecko.db.BrowserContract.TopSites;
|
||||
@ -45,12 +46,14 @@ public class TopSitesCursorWrapper implements Cursor {
|
||||
TopSites.TYPE
|
||||
};
|
||||
|
||||
private static final ArrayMap<String, Integer> columnIndexes =
|
||||
new ArrayMap<String, Integer>(columnNames.length) {{
|
||||
private static final Map<String, Integer> columnIndexes =
|
||||
new HashMap<String, Integer>(columnNames.length);
|
||||
|
||||
static {
|
||||
for (int i = 0; i < columnNames.length; i++) {
|
||||
put(columnNames[i], i);
|
||||
columnIndexes.put(columnNames[i], i);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
// Maps column indexes from the wrapper to the cursor's.
|
||||
private SparseIntArray topIndexes;
|
||||
|
@ -8,6 +8,7 @@ import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
@ -31,6 +32,7 @@ public class PromptListItem {
|
||||
public Drawable mIcon;
|
||||
|
||||
PromptListItem(JSONObject aObject) {
|
||||
Context context = GeckoAppShell.getContext();
|
||||
label = aObject.isNull("label") ? "" : aObject.optString("label");
|
||||
isGroup = aObject.optBoolean("isGroup");
|
||||
inGroup = aObject.optBoolean("inGroup");
|
||||
@ -44,7 +46,8 @@ public class PromptListItem {
|
||||
String uri = obj.isNull("uri") ? "" : obj.optString("uri");
|
||||
String type = obj.isNull("type") ? GeckoActionProvider.DEFAULT_MIME_TYPE :
|
||||
obj.optString("type", GeckoActionProvider.DEFAULT_MIME_TYPE);
|
||||
mIntent = GeckoAppShell.getShareIntent(GeckoAppShell.getContext(), uri, type, "");
|
||||
|
||||
mIntent = GeckoAppShell.getShareIntent(context, uri, type, "");
|
||||
isParent = true;
|
||||
} else {
|
||||
mIntent = null;
|
||||
@ -55,7 +58,7 @@ public class PromptListItem {
|
||||
|
||||
final String iconStr = aObject.optString("icon");
|
||||
if (iconStr != null) {
|
||||
BitmapUtils.getDrawable(GeckoAppShell.getContext(), iconStr, new BitmapUtils.BitmapLoader() {
|
||||
BitmapUtils.getDrawable(context, iconStr, new BitmapUtils.BitmapLoader() {
|
||||
@Override
|
||||
public void onBitmapFound(Drawable d) {
|
||||
mIcon = d;
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.menu.MenuItemActionView;
|
||||
@ -184,6 +185,12 @@ public class GeckoActionProvider {
|
||||
ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
|
||||
Intent launchIntent = dataModel.chooseActivity(index);
|
||||
if (launchIntent != null) {
|
||||
// Share image syncrhonously downloads the image before sharing it.
|
||||
String type = launchIntent.getType();
|
||||
if (Intent.ACTION_SEND.equals(launchIntent.getAction()) && type != null && type.startsWith("image/")) {
|
||||
GeckoAppShell.downloadImageForIntent(launchIntent);
|
||||
}
|
||||
|
||||
launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
|
||||
mContext.startActivity(launchIntent);
|
||||
}
|
||||
|
@ -29,6 +29,7 @@
|
||||
[include:gfx/tests/unit/xpcshell.ini]
|
||||
[include:services/common/tests/unit/xpcshell.ini]
|
||||
[include:toolkit/components/telemetry/tests/unit/xpcshell.ini]
|
||||
[include:toolkit/components/osfile/tests/xpcshell/xpcshell.ini]
|
||||
[include:netwerk/test/unit/xpcshell.ini]
|
||||
[include:intl/strres/tests/unit/xpcshell.ini]
|
||||
[include:intl/unicharutil/tests/unit/xpcshell.ini]
|
||||
|
394
toolkit/devtools/server/actors/layout.js
Normal file
394
toolkit/devtools/server/actors/layout.js
Normal file
@ -0,0 +1,394 @@
|
||||
/* 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";
|
||||
|
||||
/**
|
||||
* About the types of objects in this file:
|
||||
*
|
||||
* - ReflowActor: the actor class used for protocol purposes.
|
||||
* Mostly empty, just gets an instance of LayoutChangesObserver and forwards
|
||||
* its "reflows" events to clients.
|
||||
*
|
||||
* - Observable: A utility parent class, meant at being extended by classes that
|
||||
* need a start/stop behavior.
|
||||
*
|
||||
* - LayoutChangesObserver: extends Observable and uses the ReflowObserver, to
|
||||
* track reflows on the page.
|
||||
* Used by the LayoutActor, but is also exported on the module, so can be used
|
||||
* by any other actor that needs it.
|
||||
*
|
||||
* - Dedicated observers: There's only one of them for now: ReflowObserver which
|
||||
* listens to reflow events via the docshell,
|
||||
* These dedicated classes are used by the LayoutChangesObserver.
|
||||
*/
|
||||
|
||||
const {Ci, Cu} = require("chrome");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const protocol = require("devtools/server/protocol");
|
||||
const {method, Arg, RetVal, types} = protocol;
|
||||
const events = require("sdk/event/core");
|
||||
const Heritage = require("sdk/core/heritage");
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
|
||||
exports.register = function(handle) {
|
||||
handle.addGlobalActor(ReflowActor, "reflowActor");
|
||||
handle.addTabActor(ReflowActor, "reflowActor");
|
||||
};
|
||||
|
||||
exports.unregister = function(handle) {
|
||||
handle.removeGlobalActor(ReflowActor);
|
||||
handle.removeTabActor(ReflowActor);
|
||||
};
|
||||
|
||||
/**
|
||||
* The reflow actor tracks reflows and emits events about them.
|
||||
*/
|
||||
let ReflowActor = protocol.ActorClass({
|
||||
typeName: "reflow",
|
||||
|
||||
events: {
|
||||
/**
|
||||
* The reflows event is emitted when reflows have been detected. The event
|
||||
* is sent with an array of reflows that occured. Each item has the
|
||||
* following properties:
|
||||
* - start {Number}
|
||||
* - end {Number}
|
||||
* - isInterruptible {Boolean}
|
||||
*/
|
||||
"reflows" : {
|
||||
type: "reflows",
|
||||
reflows: Arg(0, "array:json")
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function(conn, tabActor) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
|
||||
this.tabActor = tabActor;
|
||||
this._onReflow = this._onReflow.bind(this);
|
||||
this.observer = getLayoutChangesObserver(tabActor);
|
||||
this._isStarted = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* The reflow actor is the first (and last) in its hierarchy to use protocol.js
|
||||
* so it doesn't have a parent protocol actor that takes care of its lifetime.
|
||||
* So it needs a disconnect method to cleanup.
|
||||
*/
|
||||
disconnect: function() {
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.stop();
|
||||
releaseLayoutChangesObserver(this.tabActor);
|
||||
this.observer = null;
|
||||
this.tabActor = null;
|
||||
|
||||
protocol.Actor.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Start tracking reflows and sending events to clients about them.
|
||||
* This is a oneway method, do not expect a response and it won't return a
|
||||
* promise.
|
||||
*/
|
||||
start: method(function() {
|
||||
if (!this._isStarted) {
|
||||
this.observer.on("reflows", this._onReflow);
|
||||
this._isStarted = true;
|
||||
}
|
||||
}, {oneway: true}),
|
||||
|
||||
/**
|
||||
* Stop tracking reflows and sending events to clients about them.
|
||||
* This is a oneway method, do not expect a response and it won't return a
|
||||
* promise.
|
||||
*/
|
||||
stop: method(function() {
|
||||
if (this._isStarted) {
|
||||
this.observer.off("reflows", this._onReflow);
|
||||
this._isStarted = false;
|
||||
}
|
||||
}, {oneway: true}),
|
||||
|
||||
_onReflow: function(event, reflows) {
|
||||
if (this._isStarted) {
|
||||
events.emit(this, "reflows", reflows);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Usage example of the reflow front:
|
||||
*
|
||||
* let front = ReflowFront(toolbox.target.client, toolbox.target.form);
|
||||
* front.on("reflows", this._onReflows);
|
||||
* front.start();
|
||||
* // now wait for events to come
|
||||
*/
|
||||
exports.ReflowFront = protocol.FrontClass(ReflowActor, {
|
||||
initialize: function(client, {reflowActor}) {
|
||||
protocol.Front.prototype.initialize.call(this, client, {actor: reflowActor});
|
||||
client.addActorPool(this);
|
||||
this.manage(this);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
protocol.Front.prototype.destroy.call(this);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Base class for all sorts of observers we need to create for a given window.
|
||||
* @param {TabActor} tabActor
|
||||
* @param {Function} callback Executed everytime the observer observes something
|
||||
*/
|
||||
function Observable(tabActor, callback) {
|
||||
this.tabActor = tabActor;
|
||||
this.win = tabActor.window;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
Observable.prototype = {
|
||||
/**
|
||||
* Is the observer currently observing
|
||||
*/
|
||||
observing: false,
|
||||
|
||||
/**
|
||||
* Start observing whatever it is this observer is supposed to observe
|
||||
*/
|
||||
start: function() {
|
||||
if (!this.observing) {
|
||||
this._start();
|
||||
this.observing = true;
|
||||
}
|
||||
},
|
||||
|
||||
_start: function() {
|
||||
/* To be implemented by sub-classes */
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop observing
|
||||
*/
|
||||
stop: function() {
|
||||
if (this.observing) {
|
||||
this._stop();
|
||||
this.observing = false;
|
||||
}
|
||||
},
|
||||
|
||||
_stop: function() {
|
||||
/* To be implemented by sub-classes */
|
||||
},
|
||||
|
||||
/**
|
||||
* To be called by sub-classes when something has been observed
|
||||
*/
|
||||
notifyCallback: function(...args) {
|
||||
this.observing && this.callback && this.callback.apply(null, args);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop observing and detroy this observer instance
|
||||
*/
|
||||
destroy: function() {
|
||||
this.stop();
|
||||
this.callback = null;
|
||||
this.win = null;
|
||||
this.tabActor = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The LayoutChangesObserver class is instantiated only once per given tab
|
||||
* and is used to track reflows and dom and style changes in that tab.
|
||||
* The LayoutActor uses this class to send reflow events to its clients.
|
||||
*
|
||||
* This class isn't exported on the module because it shouldn't be instantiated
|
||||
* to avoid creating several instances per tabs.
|
||||
* Use `getLayoutChangesObserver(tabActor)`
|
||||
* and `releaseLayoutChangesObserver(tabActor)`
|
||||
* which are exported to get and release instances.
|
||||
*
|
||||
* The observer loops every EVENT_BATCHING_DELAY ms and checks if layout changes
|
||||
* have happened since the last loop iteration. If there are, it sends the
|
||||
* corresponding events:
|
||||
*
|
||||
* - "reflows", with an array of all the reflows that occured,
|
||||
*
|
||||
* @param {TabActor} tabActor
|
||||
*/
|
||||
function LayoutChangesObserver(tabActor) {
|
||||
Observable.call(this, tabActor);
|
||||
|
||||
this._startEventLoop = this._startEventLoop.bind(this);
|
||||
|
||||
// Creating the various observers we're going to need
|
||||
// For now, just the reflow observer, but later we can add markupMutation,
|
||||
// styleSheetChanges and styleRuleChanges
|
||||
this._onReflow = this._onReflow.bind(this);
|
||||
this.reflowObserver = new ReflowObserver(this.tabActor, this._onReflow);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
LayoutChangesObserver.prototype = Heritage.extend(Observable.prototype, {
|
||||
/**
|
||||
* How long does this observer waits before emitting a batched reflows event.
|
||||
* The lower the value, the more event packets will be sent to clients,
|
||||
* potentially impacting performance.
|
||||
* The higher the value, the more time we'll wait, this is better for
|
||||
* performance but has an effect on how soon changes are shown in the toolbox.
|
||||
*/
|
||||
EVENT_BATCHING_DELAY: 300,
|
||||
|
||||
/**
|
||||
* Destroying this instance of LayoutChangesObserver will stop the batched
|
||||
* events from being sent.
|
||||
*/
|
||||
destroy: function() {
|
||||
this.reflowObserver.destroy();
|
||||
this.reflows = null;
|
||||
|
||||
Observable.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
_start: function() {
|
||||
this.reflows = [];
|
||||
this._startEventLoop();
|
||||
this.reflowObserver.start();
|
||||
},
|
||||
|
||||
_stop: function() {
|
||||
this._stopEventLoop();
|
||||
this.reflows = [];
|
||||
this.reflowObserver.stop();
|
||||
},
|
||||
|
||||
/**
|
||||
* Start the event loop, which regularly checks if there are any observer
|
||||
* events to be sent as batched events
|
||||
* Calls itself in a loop.
|
||||
*/
|
||||
_startEventLoop: function() {
|
||||
// Send any reflows we have
|
||||
if (this.reflows && this.reflows.length) {
|
||||
this.emit("reflows", this.reflows);
|
||||
this.reflows = [];
|
||||
}
|
||||
this.eventLoopTimer = this.win.setTimeout(this._startEventLoop,
|
||||
this.EVENT_BATCHING_DELAY);
|
||||
},
|
||||
|
||||
_stopEventLoop: function() {
|
||||
this.win.clearTimeout(this.eventLoopTimer);
|
||||
},
|
||||
|
||||
/**
|
||||
* Executed whenever a reflow is observed. Only stacks the reflow in the
|
||||
* reflows array.
|
||||
* The EVENT_BATCHING_DELAY loop will take care of it later.
|
||||
* @param {Number} start When the reflow started
|
||||
* @param {Number} end When the reflow ended
|
||||
* @param {Boolean} isInterruptible
|
||||
*/
|
||||
_onReflow: function(start, end, isInterruptible) {
|
||||
// XXX: when/if bug 997092 gets fixed, we will be able to know which
|
||||
// elements have been reflowed, which would be a nice thing to add here.
|
||||
this.reflows.push({
|
||||
start: start,
|
||||
end: end,
|
||||
isInterruptible: isInterruptible
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a LayoutChangesObserver instance for a given window. This function makes
|
||||
* sure there is only one instance per window.
|
||||
* @param {TabActor} tabActor
|
||||
* @return {LayoutChangesObserver}
|
||||
*/
|
||||
let observedWindows = new Map();
|
||||
function getLayoutChangesObserver(tabActor) {
|
||||
let observerData = observedWindows.get(tabActor);
|
||||
if (observerData) {
|
||||
observerData.refCounting ++;
|
||||
return observerData.observer;
|
||||
}
|
||||
|
||||
let obs = new LayoutChangesObserver(tabActor);
|
||||
observedWindows.set(tabActor, {
|
||||
observer: obs,
|
||||
refCounting: 1 // counting references allows to stop the observer when no
|
||||
// tabActor owns an instance
|
||||
});
|
||||
obs.start();
|
||||
return obs;
|
||||
};
|
||||
exports.getLayoutChangesObserver = getLayoutChangesObserver;
|
||||
|
||||
/**
|
||||
* Release a LayoutChangesObserver instance that was retrieved by
|
||||
* getLayoutChangesObserver. This is required to ensure the tabActor reference
|
||||
* is removed and the observer is eventually stopped and destroyed.
|
||||
* @param {TabActor} tabActor
|
||||
*/
|
||||
function releaseLayoutChangesObserver(tabActor) {
|
||||
let observerData = observedWindows.get(tabActor);
|
||||
if (!observerData) {
|
||||
return;
|
||||
}
|
||||
|
||||
observerData.refCounting --;
|
||||
if (!observerData.refCounting) {
|
||||
observerData.observer.destroy();
|
||||
observedWindows.delete(tabActor);
|
||||
}
|
||||
};
|
||||
exports.releaseLayoutChangesObserver = releaseLayoutChangesObserver;
|
||||
|
||||
/**
|
||||
* Instantiate and start a reflow observer on a given window's document element.
|
||||
* Will report any reflow that occurs in this window's docshell.
|
||||
* @extends Observable
|
||||
* @param {TabActor} tabActor
|
||||
* @param {Function} callback Executed everytime a reflow occurs
|
||||
*/
|
||||
function ReflowObserver(tabActor, callback) {
|
||||
Observable.call(this, tabActor, callback);
|
||||
this.docshell = this.win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
}
|
||||
|
||||
ReflowObserver.prototype = Heritage.extend(Observable.prototype, {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
_start: function() {
|
||||
this.docshell.addWeakReflowObserver(this);
|
||||
},
|
||||
|
||||
_stop: function() {
|
||||
this.docshell.removeWeakReflowObserver(this);
|
||||
},
|
||||
|
||||
reflow: function(start, end) {
|
||||
this.notifyCallback(start, end, false);
|
||||
},
|
||||
|
||||
reflowInterruptible: function(start, end) {
|
||||
this.notifyCallback(start, end, true);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
Observable.prototype.destroy.call(this);
|
||||
this.docshell = null;
|
||||
}
|
||||
});
|
@ -391,6 +391,7 @@ var DebuggerServer = {
|
||||
this.registerModule("devtools/server/actors/tracer");
|
||||
this.registerModule("devtools/server/actors/memory");
|
||||
this.registerModule("devtools/server/actors/eventlooplag");
|
||||
this.registerModule("devtools/server/actors/layout");
|
||||
if ("nsIProfiler" in Ci) {
|
||||
this.addActors("resource://gre/modules/devtools/server/actors/profiler.js");
|
||||
}
|
||||
|
@ -0,0 +1,243 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test the LayoutChangesObserver
|
||||
|
||||
let {
|
||||
getLayoutChangesObserver,
|
||||
releaseLayoutChangesObserver
|
||||
} = devtools.require("devtools/server/actors/layout");
|
||||
|
||||
// Mock the tabActor since we only really want to test the LayoutChangesObserver
|
||||
// and don't want to depend on a window object, nor want to test protocol.js
|
||||
function MockTabActor() {
|
||||
this.window = new MockWindow();
|
||||
}
|
||||
|
||||
function MockWindow() {}
|
||||
MockWindow.prototype = {
|
||||
QueryInterface: function() {
|
||||
let self = this;
|
||||
return {
|
||||
getInterface: function() {
|
||||
return {
|
||||
QueryInterface: function() {
|
||||
self.docShell = new MockDocShell();
|
||||
return self.docShell;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
setTimeout: function(cb) {
|
||||
// Simply return the cb itself so that we can execute it in the test instead
|
||||
// of depending on a real timeout
|
||||
return cb;
|
||||
},
|
||||
clearTimeout: function() {}
|
||||
};
|
||||
|
||||
function MockDocShell() {
|
||||
this.observer = null;
|
||||
}
|
||||
MockDocShell.prototype = {
|
||||
addWeakReflowObserver: function(observer) {
|
||||
this.observer = observer;
|
||||
},
|
||||
removeWeakReflowObserver: function(observer) {}
|
||||
};
|
||||
|
||||
function run_test() {
|
||||
instancesOfObserversAreSharedBetweenWindows();
|
||||
eventsAreBatched();
|
||||
noEventsAreSentWhenThereAreNoReflowsAndLoopTimeouts();
|
||||
observerIsAlreadyStarted();
|
||||
destroyStopsObserving();
|
||||
stoppingAndStartingSeveralTimesWorksCorrectly();
|
||||
reflowsArentStackedWhenStopped();
|
||||
stackedReflowsAreResetOnStop();
|
||||
}
|
||||
|
||||
function instancesOfObserversAreSharedBetweenWindows() {
|
||||
do_print("Checking that when requesting twice an instances of the observer " +
|
||||
"for the same TabActor, the instance is shared");
|
||||
|
||||
do_print("Checking 2 instances of the observer for the tabActor 1");
|
||||
let tabActor1 = new MockTabActor();
|
||||
let obs11 = getLayoutChangesObserver(tabActor1);
|
||||
let obs12 = getLayoutChangesObserver(tabActor1);
|
||||
do_check_eq(obs11, obs12);
|
||||
|
||||
do_print("Checking 2 instances of the observer for the tabActor 2");
|
||||
let tabActor2 = new MockTabActor();
|
||||
let obs21 = getLayoutChangesObserver(tabActor2);
|
||||
let obs22 = getLayoutChangesObserver(tabActor2);
|
||||
do_check_eq(obs21, obs22);
|
||||
|
||||
do_print("Checking that observers instances for 2 different tabActors are " +
|
||||
"different");
|
||||
do_check_neq(obs11, obs21);
|
||||
|
||||
releaseLayoutChangesObserver(tabActor1);
|
||||
releaseLayoutChangesObserver(tabActor1);
|
||||
releaseLayoutChangesObserver(tabActor2);
|
||||
releaseLayoutChangesObserver(tabActor2);
|
||||
}
|
||||
|
||||
function eventsAreBatched() {
|
||||
do_print("Checking that reflow events are batched and only sent when the " +
|
||||
"timeout expires");
|
||||
|
||||
// Note that in this test, we mock the TabActor and its window property, so we
|
||||
// also mock the setTimeout/clearTimeout mechanism and just call the callback
|
||||
// manually
|
||||
|
||||
let tabActor = new MockTabActor();
|
||||
let observer = getLayoutChangesObserver(tabActor);
|
||||
|
||||
let reflowsEvents = [];
|
||||
let onReflows = (event, reflows) => reflowsEvents.push(reflows);
|
||||
observer.on("reflows", onReflows);
|
||||
|
||||
do_print("Fake one reflow event");
|
||||
tabActor.window.docShell.observer.reflow();
|
||||
do_print("Checking that no batched reflow event has been emitted");
|
||||
do_check_eq(reflowsEvents.length, 0);
|
||||
|
||||
do_print("Fake another reflow event");
|
||||
tabActor.window.docShell.observer.reflow();
|
||||
do_print("Checking that still no batched reflow event has been emitted");
|
||||
do_check_eq(reflowsEvents.length, 0);
|
||||
|
||||
do_print("Faking timeout expiration and checking that reflow events are sent");
|
||||
observer.eventLoopTimer();
|
||||
do_check_eq(reflowsEvents.length, 1);
|
||||
do_check_eq(reflowsEvents[0].length, 2);
|
||||
|
||||
observer.off("reflows", onReflows);
|
||||
releaseLayoutChangesObserver(tabActor);
|
||||
}
|
||||
|
||||
function noEventsAreSentWhenThereAreNoReflowsAndLoopTimeouts() {
|
||||
do_print("Checking that if no reflows were detected and the event batching " +
|
||||
"loop expires, then no reflows event is sent");
|
||||
|
||||
let tabActor = new MockTabActor();
|
||||
let observer = getLayoutChangesObserver(tabActor);
|
||||
|
||||
let reflowsEvents = [];
|
||||
let onReflows = (event, reflows) => reflowsEvents.push(reflows);
|
||||
observer.on("reflows", onReflows);
|
||||
|
||||
do_print("Faking timeout expiration and checking for reflows");
|
||||
observer.eventLoopTimer();
|
||||
do_check_eq(reflowsEvents.length, 0);
|
||||
|
||||
observer.off("reflows", onReflows);
|
||||
releaseLayoutChangesObserver(tabActor);
|
||||
}
|
||||
|
||||
function observerIsAlreadyStarted() {
|
||||
do_print("Checking that the observer is already started when getting it");
|
||||
|
||||
let tabActor = new MockTabActor();
|
||||
let observer = getLayoutChangesObserver(tabActor);
|
||||
do_check_true(observer.observing);
|
||||
|
||||
observer.stop();
|
||||
do_check_false(observer.observing);
|
||||
|
||||
observer.start();
|
||||
do_check_true(observer.observing);
|
||||
|
||||
releaseLayoutChangesObserver(tabActor);
|
||||
}
|
||||
|
||||
function destroyStopsObserving() {
|
||||
do_print("Checking that the destroying the observer stops it");
|
||||
|
||||
let tabActor = new MockTabActor();
|
||||
let observer = getLayoutChangesObserver(tabActor);
|
||||
do_check_true(observer.observing);
|
||||
|
||||
observer.destroy();
|
||||
do_check_false(observer.observing);
|
||||
|
||||
releaseLayoutChangesObserver(tabActor);
|
||||
}
|
||||
|
||||
function stoppingAndStartingSeveralTimesWorksCorrectly() {
|
||||
do_print("Checking that the stopping and starting several times the observer" +
|
||||
" works correctly");
|
||||
|
||||
let tabActor = new MockTabActor();
|
||||
let observer = getLayoutChangesObserver(tabActor);
|
||||
|
||||
do_check_true(observer.observing);
|
||||
observer.start();
|
||||
observer.start();
|
||||
observer.start();
|
||||
do_check_true(observer.observing);
|
||||
|
||||
observer.stop();
|
||||
do_check_false(observer.observing);
|
||||
|
||||
observer.stop();
|
||||
observer.stop();
|
||||
do_check_false(observer.observing);
|
||||
|
||||
releaseLayoutChangesObserver(tabActor);
|
||||
}
|
||||
|
||||
function reflowsArentStackedWhenStopped() {
|
||||
do_print("Checking that when stopped, reflows aren't stacked in the observer");
|
||||
|
||||
let tabActor = new MockTabActor();
|
||||
let observer = getLayoutChangesObserver(tabActor);
|
||||
|
||||
do_print("Stoping the observer");
|
||||
observer.stop();
|
||||
|
||||
do_print("Faking reflows");
|
||||
tabActor.window.docShell.observer.reflow();
|
||||
tabActor.window.docShell.observer.reflow();
|
||||
tabActor.window.docShell.observer.reflow();
|
||||
|
||||
do_print("Checking that reflows aren't recorded");
|
||||
do_check_eq(observer.reflows.length, 0);
|
||||
|
||||
do_print("Starting the observer and faking more reflows");
|
||||
observer.start();
|
||||
tabActor.window.docShell.observer.reflow();
|
||||
tabActor.window.docShell.observer.reflow();
|
||||
tabActor.window.docShell.observer.reflow();
|
||||
|
||||
do_print("Checking that reflows are recorded");
|
||||
do_check_eq(observer.reflows.length, 3);
|
||||
|
||||
releaseLayoutChangesObserver(tabActor);
|
||||
}
|
||||
|
||||
function stackedReflowsAreResetOnStop() {
|
||||
do_print("Checking that stacked reflows are reset on stop");
|
||||
|
||||
let tabActor = new MockTabActor();
|
||||
let observer = getLayoutChangesObserver(tabActor);
|
||||
|
||||
tabActor.window.docShell.observer.reflow();
|
||||
do_check_eq(observer.reflows.length, 1);
|
||||
|
||||
observer.stop();
|
||||
do_check_eq(observer.reflows.length, 0);
|
||||
|
||||
tabActor.window.docShell.observer.reflow();
|
||||
do_check_eq(observer.reflows.length, 0);
|
||||
|
||||
observer.start();
|
||||
do_check_eq(observer.reflows.length, 0);
|
||||
|
||||
tabActor.window.docShell.observer.reflow();
|
||||
do_check_eq(observer.reflows.length, 1);
|
||||
|
||||
releaseLayoutChangesObserver(tabActor);
|
||||
}
|
@ -198,5 +198,6 @@ reason = bug 820380
|
||||
[test_ignore_caught_exceptions.js]
|
||||
[test_requestTypes.js]
|
||||
reason = bug 937197
|
||||
[test_layout-reflows-observer.js]
|
||||
[test_protocolSpec.js]
|
||||
|
||||
|
@ -1592,6 +1592,8 @@ this.XPIProvider = {
|
||||
_telemetryDetails: {},
|
||||
// Experiments are disabled by default. Track ones that are locally enabled.
|
||||
_enabledExperiments: null,
|
||||
// A Map from an add-on install to its ID
|
||||
_addonFileMap: new Map(),
|
||||
|
||||
/*
|
||||
* Set a value in the telemetry hash for a given ID
|
||||
@ -1636,55 +1638,8 @@ this.XPIProvider = {
|
||||
* consumers may still have URIs of (leaked) resources they want to map.
|
||||
*/
|
||||
_addURIMapping: function XPI__addURIMapping(aID, aFile) {
|
||||
try {
|
||||
// Always use our own mechanics instead of nsIIOService.newFileURI, so
|
||||
// that we can be sure to map things as we want them mapped.
|
||||
let uri = this._resolveURIToFile(getURIForResourceInFile(aFile, "."));
|
||||
if (!uri) {
|
||||
throw new Error("Cannot resolve");
|
||||
}
|
||||
this._ensureURIMappings();
|
||||
this._uriMappings[aID] = uri.spec;
|
||||
}
|
||||
catch (ex) {
|
||||
logger.warn("Failed to add URI mapping", ex);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensures that the URI to Addon mappings are available.
|
||||
*
|
||||
* The function will add mappings for all non-bootstrapped but enabled
|
||||
* add-ons.
|
||||
* Bootstrapped add-on mappings will be added directly when the bootstrap
|
||||
* scope get loaded. (See XPIProvider._addURIMapping() and callers)
|
||||
*/
|
||||
_ensureURIMappings: function XPI__ensureURIMappings() {
|
||||
if (this._uriMappings) {
|
||||
return;
|
||||
}
|
||||
// XXX Convert to Map(), once it gets stable with stable iterators
|
||||
this._uriMappings = Object.create(null);
|
||||
|
||||
// XXX Convert to Set(), once it gets stable with stable iterators
|
||||
let enabled = Object.create(null);
|
||||
let enabledAddons = this.enabledAddons || "";
|
||||
for (let a of enabledAddons.split(",")) {
|
||||
a = decodeURIComponent(a.split(":")[0]);
|
||||
enabled[a] = null;
|
||||
}
|
||||
|
||||
let cache = JSON.parse(Prefs.getCharPref(PREF_INSTALL_CACHE, "[]"));
|
||||
for (let loc of cache) {
|
||||
for (let [id, val] in Iterator(loc.addons)) {
|
||||
if (!(id in enabled)) {
|
||||
continue;
|
||||
}
|
||||
let file = new nsIFile(val.descriptor);
|
||||
let spec = Services.io.newFileURI(file).spec;
|
||||
this._uriMappings[id] = spec;
|
||||
}
|
||||
}
|
||||
logger.info("Mapping " + aID + " to " + aFile.path);
|
||||
this._addonFileMap.set(aID, aFile.path);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1914,10 +1869,6 @@ this.XPIProvider = {
|
||||
|
||||
this.enabledAddons = Prefs.getCharPref(PREF_EM_ENABLED_ADDONS, "");
|
||||
|
||||
// Invalidate the URI mappings now that |enabledAddons| was updated.
|
||||
// |_ensureMappings()| will re-create the mappings when needed.
|
||||
delete this._uriMappings;
|
||||
|
||||
if ("nsICrashReporter" in Ci &&
|
||||
Services.appinfo instanceof Ci.nsICrashReporter) {
|
||||
// Annotate the crash report with relevant add-on information.
|
||||
@ -2027,9 +1978,7 @@ this.XPIProvider = {
|
||||
|
||||
// This is needed to allow xpcshell tests to simulate a restart
|
||||
this.extensionsActive = false;
|
||||
|
||||
// Remove URI mappings again
|
||||
delete this._uriMappings;
|
||||
this._addonFileMap.clear();
|
||||
|
||||
if (gLazyObjectsLoaded) {
|
||||
let done = XPIDatabase.shutdown();
|
||||
@ -3715,17 +3664,15 @@ this.XPIProvider = {
|
||||
* @see amIAddonManager.mapURIToAddonID
|
||||
*/
|
||||
mapURIToAddonID: function XPI_mapURIToAddonID(aURI) {
|
||||
this._ensureURIMappings();
|
||||
let resolved = this._resolveURIToFile(aURI);
|
||||
if (!resolved) {
|
||||
if (!resolved || !(resolved instanceof Ci.nsIFileURL))
|
||||
return null;
|
||||
}
|
||||
resolved = resolved.spec;
|
||||
for (let [id, spec] in Iterator(this._uriMappings)) {
|
||||
if (resolved.startsWith(spec)) {
|
||||
|
||||
for (let [id, path] of this._addonFileMap) {
|
||||
if (resolved.file.path.startsWith(path))
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
@ -4120,9 +4067,6 @@ this.XPIProvider = {
|
||||
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
|
||||
createInstance(Ci.mozIJSSubScriptLoader);
|
||||
|
||||
// Add a mapping for XPIProvider.mapURIToAddonID
|
||||
this._addURIMapping(aId, aFile);
|
||||
|
||||
try {
|
||||
// Copy the reason values from the global object into the bootstrap scope.
|
||||
for (let name in BOOTSTRAP_REASONS)
|
||||
@ -6945,6 +6889,7 @@ DirectoryInstallLocation.prototype = {
|
||||
|
||||
this._IDToFileMap[id] = entry;
|
||||
this._FileToIDMap[entry.path] = id;
|
||||
XPIProvider._addURIMapping(id, entry);
|
||||
}
|
||||
},
|
||||
|
||||
@ -7154,6 +7099,7 @@ DirectoryInstallLocation.prototype = {
|
||||
}
|
||||
this._FileToIDMap[newFile.path] = aId;
|
||||
this._IDToFileMap[aId] = newFile;
|
||||
XPIProvider._addURIMapping(aId, newFile);
|
||||
|
||||
if (aExistingAddonID && aExistingAddonID != aId &&
|
||||
aExistingAddonID in this._IDToFileMap) {
|
||||
@ -7348,6 +7294,7 @@ WinRegInstallLocation.prototype = {
|
||||
|
||||
this._IDToFileMap[id] = file;
|
||||
this._FileToIDMap[file.path] = id;
|
||||
XPIProvider._addURIMapping(id, file);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -179,12 +179,12 @@ function run_test_2(uri) {
|
||||
});
|
||||
}
|
||||
|
||||
// Tests that add-on URIs aren't mappable if the add-on was never started in a
|
||||
// Tests that add-on URIs are mappable if the add-on was never started in a
|
||||
// session
|
||||
function run_test_3(uri) {
|
||||
restartManager();
|
||||
|
||||
do_check_eq(AddonManager.mapURIToAddonID(uri), null);
|
||||
check_mapping(uri, "bootstrap1@tests.mozilla.org");
|
||||
|
||||
run_test_4();
|
||||
}
|
||||
@ -217,6 +217,43 @@ function run_test_4() {
|
||||
function run_test_5() {
|
||||
restartManager();
|
||||
|
||||
AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
|
||||
let uri = b1.getResourceURI(".");
|
||||
check_mapping(uri, b1.id);
|
||||
|
||||
do_execute_soon(run_test_6);
|
||||
});
|
||||
}
|
||||
|
||||
// Tests that add-on URIs are mappable after being uninstalled
|
||||
function run_test_6() {
|
||||
AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
|
||||
prepare_test({
|
||||
"bootstrap1@tests.mozilla.org": [
|
||||
["onUninstalling", false],
|
||||
"onUninstalled"
|
||||
]
|
||||
});
|
||||
|
||||
let uri = b1.getResourceURI(".");
|
||||
b1.uninstall();
|
||||
ensure_test_completed();
|
||||
|
||||
check_mapping(uri, b1.id);
|
||||
|
||||
restartManager();
|
||||
do_execute_soon(run_test_7);
|
||||
});
|
||||
}
|
||||
|
||||
// Tests that add-on URIs are mappable for add-ons detected at startup
|
||||
function run_test_7() {
|
||||
shutdownManager();
|
||||
|
||||
manuallyInstall(do_get_addon("test_bootstrap1_1"), profileDir, "bootstrap1@tests.mozilla.org");
|
||||
|
||||
startupManager();
|
||||
|
||||
AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
|
||||
let uri = b1.getResourceURI(".");
|
||||
check_mapping(uri, b1.id);
|
||||
|
Loading…
Reference in New Issue
Block a user