Bug 1264907 - Don't show the firstChild of the current selection in breadcrumbs; r=jdescottes

The breadcrumbs widget used to have a feature where it would show the first
child of the current selection even the DOM tree hadn't been expanded that
far yet.
This was to allow keyboard navigating the DOM through the breadcrumbs.
The breadcrumbs is a very rarely used widget and this code was unnecessarily
making things complex.
It was decided that this feature would be removed.
Instead, the breadcrumbs now act as simple linear elements in a toolbar and
you can keyboard navigate them with LEFT/RIGHT only. TAB/shift-TAB simply go
in/out of the breadcrumbs widget.

MozReview-Commit-ID: BmcaLnVBOBn

--HG--
extra : rebase_source : 1673a6c9da02cf8b3b542e4ce905ccb239250aa7
This commit is contained in:
Patrick Brosset 2016-04-15 12:03:33 +02:00
parent 61c1f80284
commit 45c01647ea
10 changed files with 168 additions and 306 deletions

View File

@ -88,6 +88,7 @@ devtools/client/inspector/fonts/**
devtools/client/inspector/shared/test/**
devtools/client/inspector/test/**
devtools/client/inspector/*.js
!devtools/client/inspector/breadcrumbs.js
devtools/client/jsonview/lib/**
devtools/client/memory/**
devtools/client/netmonitor/test/**

View File

@ -6,8 +6,7 @@
"use strict";
const {Cu, Ci} = require("chrome");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const {Ci} = require("chrome");
const Services = require("Services");
const promise = require("promise");
const FocusManager = Services.focus;
@ -18,17 +17,6 @@ const ELLIPSIS = Services.prefs.getComplexValue(
"intl.ellipsis",
Ci.nsIPrefLocalizedString).data;
const MAX_LABEL_LENGTH = 40;
const LOW_PRIORITY_ELEMENTS = {
"HEAD": true,
"BASE": true,
"BASEFONT": true,
"ISINDEX": true,
"LINK": true,
"META": true,
"SCRIPT": true,
"STYLE": true,
"TITLE": true
};
/**
* Display the ancestors of the current node and its children.
@ -111,34 +99,7 @@ HTMLBreadcrumbs.prototype = {
},
/**
* Include in a promise's then() chain to reject the chain
* when the breadcrumbs' selection has changed while the promise
* was outstanding.
*/
selectionGuard: function () {
let selection = this.selection.nodeFront;
return result => {
if (selection != this.selection.nodeFront) {
return promise.reject("selection-changed");
}
return result;
};
},
/**
* Warn if rejection was caused by selection change, print an error otherwise.
* @param {Error} err
*/
selectionGuardEnd: function (err) {
// If the error is selection-changed, this is expected, the selection
// changed while we were waiting for a promise to resolve, so there's no
// need to proceed with the current update, and we should be silent.
if (err !== "selection-changed") {
console.error(err);
}
},
/**
* Build a string that represents the node: tagName#id.class1.class2.
* @param {NodeFront} node The node to pretty-print
* @return {String}
@ -299,63 +260,60 @@ HTMLBreadcrumbs.prototype = {
},
/**
* On key press, navigate the node hierarchy.
* On keypress, navigate through the list of breadcrumbs with the left/right
* arrow keys.
* @param {DOMEvent} event.
*/
handleKeyPress: function (event) {
let navigate = promise.resolve(null);
let win = this.chromeWin;
let {keyCode, shiftKey, metaKey, ctrlKey, altKey} = event;
this._keyPromise = (this._keyPromise || promise.resolve(null)).then(() => {
switch (event.keyCode) {
case this.chromeWin.KeyEvent.DOM_VK_LEFT:
if (this.currentIndex != 0) {
navigate = promise.resolve(
this.nodeHierarchy[this.currentIndex - 1].node);
}
break;
case this.chromeWin.KeyEvent.DOM_VK_RIGHT:
if (this.currentIndex < this.nodeHierarchy.length - 1) {
navigate = promise.resolve(
this.nodeHierarchy[this.currentIndex + 1].node);
}
break;
case this.chromeWin.KeyEvent.DOM_VK_UP:
navigate = this.walker.previousSibling(this.selection.nodeFront, {
whatToShow: Ci.nsIDOMNodeFilter.SHOW_ELEMENT
});
break;
case this.chromeWin.KeyEvent.DOM_VK_DOWN:
navigate = this.walker.nextSibling(this.selection.nodeFront, {
whatToShow: Ci.nsIDOMNodeFilter.SHOW_ELEMENT
});
break;
case this.chromeWin.KeyEvent.DOM_VK_TAB:
// Tabbing when breadcrumbs or its contents are focused should move
// focus to next/previous focusable element relative to breadcrumbs
// themselves.
let elm, type;
if (event.shiftKey) {
elm = this.container;
type = FocusManager.MOVEFOCUS_BACKWARD;
} else {
// To move focus to next element following the breadcrumbs, relative
// element needs to be the last element in breadcrumbs' subtree.
let last = this.container.lastChild;
while (last && last.lastChild) {
last = last.lastChild;
}
elm = last;
type = FocusManager.MOVEFOCUS_FORWARD;
}
FocusManager.moveFocus(this.chromeWin, elm, type, 0);
break;
}
// Only handle left, right, tab and shift tab, let anything else bubble up
// so native shortcuts work.
let hasModifier = metaKey || ctrlKey || altKey || shiftKey;
let isLeft = keyCode === win.KeyEvent.DOM_VK_LEFT && !hasModifier;
let isRight = keyCode === win.KeyEvent.DOM_VK_RIGHT && !hasModifier;
let isTab = keyCode === win.KeyEvent.DOM_VK_TAB && !hasModifier;
let isShiftTab = keyCode === win.KeyEvent.DOM_VK_TAB && shiftKey &&
!metaKey && !ctrlKey && !altKey;
return navigate.then(node => this.navigateTo(node));
});
if (!isLeft && !isRight && !isTab && !isShiftTab) {
return;
}
event.preventDefault();
event.stopPropagation();
this.keyPromise = (this.keyPromise || promise.resolve(null)).then(() => {
if (isLeft && this.currentIndex != 0) {
let node = this.nodeHierarchy[this.currentIndex - 1].node;
return this.selection.setNodeFront(node, "breadcrumbs");
} else if (isRight && this.currentIndex < this.nodeHierarchy.length - 1) {
let node = this.nodeHierarchy[this.currentIndex + 1].node;
return this.selection.setNodeFront(node, "breadcrumbs");
} else if (isTab || isShiftTab) {
// Tabbing when breadcrumbs or its contents are focused should move
// focus to next/previous focusable element relative to breadcrumbs
// themselves.
let elm, type;
if (shiftKey) {
elm = this.container;
type = FocusManager.MOVEFOCUS_BACKWARD;
} else {
// To move focus to next element following the breadcrumbs, relative
// element needs to be the last element in breadcrumbs' subtree.
let last = this.container.lastChild;
while (last && last.lastChild) {
last = last.lastChild;
}
elm = last;
type = FocusManager.MOVEFOCUS_FORWARD;
}
FocusManager.moveFocus(win, elm, type, 0);
}
return null;
});
},
/**
@ -368,9 +326,9 @@ HTMLBreadcrumbs.prototype = {
this.inspector.off("markupmutation", this.update);
this.container.removeEventListener("underflow",
this.onscrollboxreflow, false);
this.onscrollboxreflow, false);
this.container.removeEventListener("overflow",
this.onscrollboxreflow, false);
this.onscrollboxreflow, false);
this.container.removeEventListener("click", this, true);
this.container.removeEventListener("keypress", this, true);
this.container.removeEventListener("mouseover", this, true);
@ -442,14 +400,6 @@ HTMLBreadcrumbs.prototype = {
}
},
navigateTo: function (node) {
if (node) {
this.selection.setNodeFront(node, "breadcrumbs");
} else {
this.inspector.emit("breadcrumbs-navigation-cancelled");
}
},
/**
* Build a button representing the node.
* @param {NodeFront} node The node from the page.
@ -474,7 +424,7 @@ HTMLBreadcrumbs.prototype = {
};
button.onBreadcrumbsClick = () => {
this.navigateTo(node);
this.selection.setNodeFront(node, "breadcrumbs");
};
button.onBreadcrumbsHover = () => {
@ -512,46 +462,6 @@ HTMLBreadcrumbs.prototype = {
this.container.appendChild(fragment, this.container.firstChild);
},
/**
* Get a child of a node that can be displayed in the breadcrumbs and that is
* probably visible. See LOW_PRIORITY_ELEMENTS.
* @param {NodeFront} node The parent node.
* @return {Promise} Resolves to the NodeFront.
*/
getInterestingFirstNode: function (node) {
let deferred = promise.defer();
let fallback = null;
let lastNode = null;
let moreChildren = () => {
this.walker.children(node, {
start: lastNode,
maxNodes: 10,
whatToShow: Ci.nsIDOMNodeFilter.SHOW_ELEMENT
}).then(this.selectionGuard()).then(response => {
for (let childNode of response.nodes) {
if (!(childNode.tagName in LOW_PRIORITY_ELEMENTS)) {
deferred.resolve(childNode);
return;
}
if (!fallback) {
fallback = childNode;
}
lastNode = childNode;
}
if (response.hasLast) {
deferred.resolve(fallback);
return;
}
moreChildren();
}).catch(this.selectionGuardEnd);
};
moreChildren();
return deferred.promise;
},
/**
* Find the "youngest" ancestor of a node which is already in the breadcrumbs.
* @param {NodeFront} node.
@ -568,27 +478,6 @@ HTMLBreadcrumbs.prototype = {
return -1;
},
/**
* Make sure that the latest node in the breadcrumbs is not the selected node
* if the selected node still has children.
* @return {Promise}
*/
ensureFirstChild: function () {
// If the last displayed node is the selected node
if (this.currentIndex == this.nodeHierarchy.length - 1) {
let node = this.nodeHierarchy[this.currentIndex].node;
return this.getInterestingFirstNode(node).then(child => {
// If the node has a child and we've not been destroyed in the meantime
if (child && !this.isDestroyed) {
// Show this child
this.expand(child);
}
});
}
return waitForTick().then(() => true);
},
/**
* Ensure the selected node is visible.
*/
@ -650,11 +539,9 @@ HTMLBreadcrumbs.prototype = {
for (let {type, added, removed, target, attributeName} of mutations) {
if (type === "childList") {
// Only interested in childList mutations if the added or removed
// nodes are currently displayed, or if it impacts the last element in
// the breadcrumbs.
// nodes are currently displayed.
return added.some(node => this.indexOf(node) > -1) ||
removed.some(node => this.indexOf(node) > -1) ||
this.indexOf(target) === this.nodeHierarchy.length - 1;
removed.some(node => this.indexOf(node) > -1);
} else if (type === "attributes" && this.indexOf(target) > -1) {
// Only interested in attributes mutations if the target is
// currently displayed, and the attribute is either id or class.
@ -729,23 +616,14 @@ HTMLBreadcrumbs.prototype = {
}
let doneUpdating = this.inspector.updating("breadcrumbs");
// Add the first child of the very last node of the breadcrumbs if possible.
this.ensureFirstChild().then(this.selectionGuard()).then(() => {
if (this.isDestroyed) {
return null;
}
this.updateSelectors();
this.updateSelectors();
// Make sure the selected node and its neighbours are visible.
this.scroll();
return waitForTick().then(() => {
this.inspector.emit("breadcrumbs-updated", this.selection.nodeFront);
doneUpdating();
});
}).catch(err => {
doneUpdating(this.selection.nodeFront);
this.selectionGuardEnd(err);
// Make sure the selected node and its neighbours are visible.
this.scroll();
waitForTick().then(() => {
this.inspector.emit("breadcrumbs-updated", this.selection.nodeFront);
doneUpdating();
});
}
};

View File

@ -10,10 +10,24 @@ Services.scriptloader.loadSubScript(
this);
Services.prefs.setBoolPref("devtools.fontinspector.enabled", true);
Services.prefs.setCharPref("devtools.inspector.activeSidebar", "fontinspector");
registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.fontinspector.enabled");
});
/**
* The font-inspector doesn't participate in the inspector's update mechanism
* (i.e. it doesn't call inspector.updating() when updating), so simply calling
* the default selectNode isn't enough to guaranty that the panel has finished
* updating. We also need to wait for the fontinspector-updated event.
*/
var _selectNode = selectNode;
selectNode = function* (node, inspector, reason) {
let onUpdated = inspector.once("fontinspector-updated");
yield _selectNode(node, inspector, reason);
yield onUpdated;
};
/**
* Adds a new tab with the given URL, opens the inspector and selects the
* font-inspector tab.
@ -21,22 +35,12 @@ registerCleanupFunction(() => {
*/
var openFontInspectorForURL = Task.async(function*(url) {
yield addTab(url);
let {toolbox, inspector} = yield openInspectorSidebarTab("fontinspector");
let {toolbox, inspector} = yield openInspector();
/**
* Call selectNode to trigger font-inspector update so that we don't timeout
* if following conditions hold
* a) the initial 'fontinspector-updated' was emitted while we were waiting
* for openInspector to resolve
* b) the font-inspector tab was selected by default which means the call to
* select will not trigger another update.
*
* selectNode calls setNodeFront which always emits 'new-node' which calls
* FontInspector.update that emits the 'fontinspector-updated' event.
*/
let onUpdated = inspector.once("fontinspector-updated");
// Call selectNode again here to force a fontinspector update since we don't
// know if the fontinspector-updated event has been sent while the inspector
// was being opened or not.
yield selectNode("body", inspector);
yield onUpdated;
return {
toolbox,

View File

@ -18,12 +18,10 @@ add_task(function* () {
info("Removing panel's id attribute");
let onMutation = inspector.once("markupmutation");
let onInspectorUpdated = inspector.once("inspector-updated");
yield testActor.removeAttribute("#test", "id");
info("Waiting for markupmutation and inspector-updated");
info("Waiting for markupmutation");
yield onMutation;
yield onInspectorUpdated;
is(panelFront.hasAttribute("id"), false,
"panelFront doesn't have id attribute anymore");

View File

@ -8,15 +8,15 @@
const TEST_URI = URL_ROOT + "doc_inspector_breadcrumbs.html";
const NODES = [
{selector: "#i1111", result: "i1 i11 i111 i1111"},
{selector: "#i22", result: "i2 i22 i221"},
{selector: "#i22", result: "i2 i22"},
{selector: "#i2111", result: "i2 i21 i211 i2111"},
{selector: "#i21", result: "i2 i21 i211 i2111"},
{selector: "#i22211", result: "i2 i22 i222 i2221 i22211"},
{selector: "#i22", result: "i2 i22 i222 i2221 i22211"},
{selector: "#i3", result: "i3 i31"},
{selector: "#i3", result: "i3"},
];
add_task(function*() {
add_task(function* () {
let { inspector } = yield openInspectorForURL(TEST_URI);
let container = inspector.panelDoc.getElementById("inspector-breadcrumbs");
@ -54,21 +54,23 @@ add_task(function*() {
});
function* testPseudoElements(inspector, container) {
info ("Checking for pseudo elements");
info("Checking for pseudo elements");
let pseudoParent = yield getNodeFront("#pseudo-container", inspector);
let children = yield inspector.walker.children(pseudoParent);
is (children.nodes.length, 2, "Pseudo children returned from walker");
is(children.nodes.length, 2, "Pseudo children returned from walker");
let beforeElement = children.nodes[0];
let breadcrumbsUpdated = inspector.once("breadcrumbs-updated");
yield selectNode(beforeElement, inspector);
yield breadcrumbsUpdated;
is(container.childNodes[3].textContent, "::before", "::before shows up in breadcrumb");
is(container.childNodes[3].textContent, "::before",
"::before shows up in breadcrumb");
let afterElement = children.nodes[1];
breadcrumbsUpdated = inspector.once("breadcrumbs-updated");
yield selectNode(afterElement, inspector);
yield breadcrumbsUpdated;
is(container.childNodes[3].textContent, "::after", "::before shows up in breadcrumb");
is(container.childNodes[3].textContent, "::after",
"::before shows up in breadcrumb");
}

View File

@ -15,64 +15,24 @@ const TEST_DATA = [{
key: "VK_LEFT",
newSelection: "html"
}, {
desc: "Pressing left again should stay on root <html>",
desc: "Pressing left again should stay on <html>, it's the first element",
key: "VK_LEFT",
newSelection: "html"
}, {
desc: "Pressing right should go down to <body>",
desc: "Pressing right should go to <body>",
key: "VK_RIGHT",
newSelection: "body"
}, {
desc: "Pressing right again should go down to #i2",
desc: "Pressing right again should go to #i2",
key: "VK_RIGHT",
newSelection: "#i2"
}, {
desc: "Continue down to #i21",
desc: "Pressing right again should stay on #i2, it's the last element",
key: "VK_RIGHT",
newSelection: "#i21"
}, {
desc: "Continue down to #i211",
key: "VK_RIGHT",
newSelection: "#i211"
}, {
desc: "Continue down to #i2111",
key: "VK_RIGHT",
newSelection: "#i2111"
}, {
desc: "Pressing right once more should stay at leaf node #i2111",
key: "VK_RIGHT",
newSelection: "#i2111"
}, {
desc: "Go back to #i211",
key: "VK_LEFT",
newSelection: "#i211"
}, {
desc: "Go back to #i21",
key: "VK_LEFT",
newSelection: "#i21"
}, {
desc: "Pressing down should move to next sibling #i22",
key: "VK_DOWN",
newSelection: "#i22"
}, {
desc: "Pressing up should move to previous sibling #i21",
key: "VK_UP",
newSelection: "#i21"
}, {
desc: "Pressing up again should stay on #i21 as there's no previous sibling",
key: "VK_UP",
newSelection: "#i21"
}, {
desc: "Going back down to #i22",
key: "VK_DOWN",
newSelection: "#i22"
}, {
desc: "Pressing down again should stay on #i22 as there's no next sibling",
key: "VK_DOWN",
newSelection: "#i22"
newSelection: "#i2"
}];
add_task(function*() {
add_task(function* () {
let {inspector} = yield openInspectorForURL(TEST_URI);
info("Selecting the test node");
@ -88,13 +48,12 @@ add_task(function*() {
for (let {desc, key, newSelection} of TEST_DATA) {
info(desc);
let onUpdated;
// If the selection will change, wait for the breadcrumb to update,
// otherwise continue.
let onUpdated = null;
if (newSelection !== currentSelection) {
info("Expecting a new node to be selected");
onUpdated = inspector.once("breadcrumbs-updated");
} else {
info("Expecting the same node to remain selected");
onUpdated = inspector.once("breadcrumbs-navigation-cancelled");
}
EventUtils.synthesizeKey(key, {});

View File

@ -31,7 +31,8 @@ const TEST_DATA = [
options: { shiftKey: true }
},
{
desc: "Move the focus back away from breadcrumbs to a previous focusable element",
desc: "Move the focus back away from breadcrumbs to a previous focusable " +
"element",
focused: false,
key: "VK_TAB",
options: { shiftKey: true }
@ -44,9 +45,10 @@ const TEST_DATA = [
}
];
add_task(function*() {
add_task(function* () {
let { toolbox, inspector } = yield openInspectorForURL(TEST_URL);
let doc = inspector.panelDoc;
let {breadcrumbs} = inspector;
yield selectNode("#i2", inspector);
@ -64,15 +66,13 @@ add_task(function*() {
for (let { desc, focused, key, options } of TEST_DATA) {
info(desc);
let onUpdated;
if (!focused) {
onUpdated = inspector.once("breadcrumbs-navigation-cancelled");
}
EventUtils.synthesizeKey(key, options);
// Wait until the keyPromise promise resolves.
yield breadcrumbs.keyPromise;
if (focused) {
is(doc.activeElement, button, "Focus is on selected breadcrumb");
} else {
yield onUpdated;
ok(!containsFocus(doc, container), "Focus is outside of breadcrumbs");
}
}

View File

@ -23,19 +23,19 @@ const TEST_URI = URL_ROOT + "doc_inspector_breadcrumbs.html";
// - output {Array} A list of strings for the text that should be found in each
// button after the test has run.
const TEST_DATA = [{
desc: "Adding a child at the end of the chain should refresh and show it",
setup: function*(inspector) {
desc: "Adding a child at the end of the chain shouldn't change anything",
setup: function* (inspector) {
yield selectNode("#i1111", inspector);
},
run: function*({walker, selection}) {
run: function* ({walker, selection}) {
yield walker.setInnerHTML(selection.nodeFront, "<b>test</b>");
},
shouldRefresh: true,
output: ["html", "body", "article#i1", "div#i11", "div#i111", "div#i1111", "b"]
shouldRefresh: false,
output: ["html", "body", "article#i1", "div#i11", "div#i111", "div#i1111"]
}, {
desc: "Updating an ID to an displayed element should refresh",
setup: function*() {},
run: function*({walker}) {
setup: function* () {},
run: function* ({walker}) {
let node = yield walker.querySelector(walker.rootNode, "#i1");
yield node.modifyAttributes([{
attributeName: "id",
@ -43,11 +43,12 @@ const TEST_DATA = [{
}]);
},
shouldRefresh: true,
output: ["html", "body", "article#i1-changed", "div#i11", "div#i111", "div#i1111", "b"]
output: ["html", "body", "article#i1-changed", "div#i11", "div#i111",
"div#i1111"]
}, {
desc: "Updating an class to a displayed element should refresh",
setup: function*() {},
run: function*({walker}) {
setup: function* () {},
run: function* ({walker}) {
let node = yield walker.querySelector(walker.rootNode, "body");
yield node.modifyAttributes([{
attributeName: "class",
@ -55,11 +56,13 @@ const TEST_DATA = [{
}]);
},
shouldRefresh: true,
output: ["html", "body.test-class", "article#i1-changed", "div#i11", "div#i111", "div#i1111", "b"]
output: ["html", "body.test-class", "article#i1-changed", "div#i11",
"div#i111", "div#i1111"]
}, {
desc: "Updating a non id/class attribute to a displayed element should not refresh",
setup: function*() {},
run: function*({walker}) {
desc: "Updating a non id/class attribute to a displayed element should not " +
"refresh",
setup: function* () {},
run: function* ({walker}) {
let node = yield walker.querySelector(walker.rootNode, "#i11");
yield node.modifyAttributes([{
attributeName: "name",
@ -67,33 +70,37 @@ const TEST_DATA = [{
}]);
},
shouldRefresh: false,
output: ["html", "body.test-class", "article#i1-changed", "div#i11", "div#i111", "div#i1111", "b"]
output: ["html", "body.test-class", "article#i1-changed", "div#i11",
"div#i111", "div#i1111"]
}, {
desc: "Moving a child in an element that's not displayed should not refresh",
setup: function*() {},
run: function*({walker}) {
setup: function* () {},
run: function* ({walker}) {
// Re-append #i1211 as a last child of #i2.
let parent = yield walker.querySelector(walker.rootNode, "#i2");
let child = yield walker.querySelector(walker.rootNode, "#i211");
yield walker.insertBefore(child, parent);
},
shouldRefresh: false,
output: ["html", "body.test-class", "article#i1-changed", "div#i11", "div#i111", "div#i1111", "b"]
output: ["html", "body.test-class", "article#i1-changed", "div#i11",
"div#i111", "div#i1111"]
}, {
desc: "Moving an undisplayed child in a displayed element should not refresh",
setup: function*() {},
run: function*({walker}) {
setup: function* () {},
run: function* ({walker}) {
// Re-append #i2 in body (move it to the end).
let parent = yield walker.querySelector(walker.rootNode, "body");
let child = yield walker.querySelector(walker.rootNode, "#i2");
yield walker.insertBefore(child, parent);
},
shouldRefresh: false,
output: ["html", "body.test-class", "article#i1-changed", "div#i11", "div#i111", "div#i1111", "b"]
output: ["html", "body.test-class", "article#i1-changed", "div#i11",
"div#i111", "div#i1111"]
}, {
desc: "Updating attributes on an element that's not displayed should not refresh",
setup: function*() {},
run: function*({walker}) {
desc: "Updating attributes on an element that's not displayed should not " +
"refresh",
setup: function* () {},
run: function* ({walker}) {
let node = yield walker.querySelector(walker.rootNode, "#i2");
yield node.modifyAttributes([{
attributeName: "id",
@ -104,42 +111,43 @@ const TEST_DATA = [{
}]);
},
shouldRefresh: false,
output: ["html", "body.test-class", "article#i1-changed", "div#i11", "div#i111", "div#i1111", "b"]
output: ["html", "body.test-class", "article#i1-changed", "div#i11",
"div#i111", "div#i1111"]
}, {
desc: "Removing the currently selected node should refresh",
setup: function*(inspector) {
setup: function* (inspector) {
yield selectNode("#i2-changed", inspector);
},
run: function*({walker, selection}) {
run: function* ({walker, selection}) {
yield walker.removeNode(selection.nodeFront);
},
shouldRefresh: true,
output: ["html", "body.test-class", "article#i1-changed"]
output: ["html", "body.test-class"]
}, {
desc: "Changing the class of the currently selected node should refresh",
setup: function*() {},
run: function*({selection}) {
setup: function* () {},
run: function* ({selection}) {
yield selection.nodeFront.modifyAttributes([{
attributeName: "class",
newValue: "test-class-changed"
}]);
},
shouldRefresh: true,
output: ["html", "body.test-class-changed", "article#i1-changed"]
output: ["html", "body.test-class-changed"]
}, {
desc: "Changing the id of the currently selected node should refresh",
setup: function*() {},
run: function*({selection}) {
setup: function* () {},
run: function* ({selection}) {
yield selection.nodeFront.modifyAttributes([{
attributeName: "id",
newValue: "new-id"
}]);
},
shouldRefresh: true,
output: ["html", "body#new-id.test-class-changed", "article#i1-changed"]
output: ["html", "body#new-id.test-class-changed"]
}];
add_task(function*() {
add_task(function* () {
let {inspector} = yield openInspectorForURL(TEST_URI);
let container = inspector.panelDoc.getElementById("inspector-breadcrumbs");
let win = container.ownerDocument.defaultView;
@ -195,7 +203,7 @@ add_task(function*() {
info("Check the output of the breadcrumbs widget");
is(container.childNodes.length, output.length, "Correct number of buttons");
for (let i = 0; i < container.childNodes.length; i ++) {
for (let i = 0; i < container.childNodes.length; i++) {
is(output[i], container.childNodes[i].textContent,
"Text content for button " + i + " is correct");
}

View File

@ -8,21 +8,27 @@
const TEST_URI = "data:text/html;charset=utf-8," +
"<html><head><title>Test for the highlighter keybindings</title></head>" +
"<body><h1>Hello</h1><p><strong>Greetings, earthlings!</strong>" +
"<body><p><strong>Greetings, earthlings!</strong>" +
" I come in peace.</p></body></html>";
const TEST_DATA = [
{ key: "VK_RIGHT", selectedNode: "h1" },
{ key: "VK_DOWN", selectedNode: "p" },
{ key: "VK_UP", selectedNode: "h1" },
{ key: "VK_LEFT", selectedNode: "p" },
{ key: "VK_LEFT", selectedNode: "body" },
{ key: "VK_LEFT", selectedNode: "html" },
{ key: "VK_RIGHT", selectedNode: "body" },
{ key: "VK_RIGHT", selectedNode: "p" },
{ key: "VK_RIGHT", selectedNode: "strong" },
];
add_task(function* () {
let { inspector } = yield openInspectorForURL(TEST_URI);
let bodyFront = yield getNodeFront("body", inspector);
is(inspector.selection.nodeFront, bodyFront,
"Body should be selected initially.");
info("Selecting the deepest element to start with");
yield selectNode("strong", inspector);
let nodeFront = yield getNodeFront("strong", inspector);
is(inspector.selection.nodeFront, nodeFront,
"<strong> should be selected initially");
info("Focusing the currently active breadcrumb button");
let bc = inspector.breadcrumbs;

View File

@ -20,8 +20,12 @@ add_task(function* () {
assertHasResult(inspector, true);
info("Removing node #d1");
// Expect an inspector-updated event here, because removing #d1 causes the
// breadcrumbs to update (since #d1 is displayed in it).
let onUpdated = inspector.once("inspector-updated");
yield mutatePage(inspector, testActor,
"document.getElementById(\"d1\").remove()");
yield onUpdated;
info("Pressing return button to search again for node #d1.");
yield synthesizeKeys("VK_RETURN", inspector);
@ -38,6 +42,8 @@ add_task(function* () {
assertHasResult(inspector, false);
info("Create the #d3 node in the page");
// No need to expect an inspector-updated event here, Creating #d3 isn't going
// to update the breadcrumbs in any ways.
yield mutatePage(inspector, testActor,
`document.getElementById("d2").insertAdjacentHTML(
"afterend", "<div id=d3></div>")`);
@ -75,7 +81,7 @@ function assertHasResult(inspector, expectResult) {
}
function* mutatePage(inspector, testActor, expression) {
let onUpdated = inspector.once("inspector-updated");
let onMutation = inspector.once("markupmutation");
yield testActor.eval(expression);
yield onUpdated;
yield onMutation;
}