Bug 719845 - [markup panel] The HTML Tree should have its own keybindings. r=dcamp

This commit is contained in:
Paul Rouget 2012-06-01 21:39:00 +02:00
Родитель 5b78a62d3d
Коммит 53febe327a
6 изменённых файлов: 328 добавлений и 11 удалений

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

@ -214,12 +214,13 @@ InsideOutBox.prototype =
this.selectObjectBox(objectBox, forceOpen);
if (makeBoxVisible) {
this.openObjectBox(objectBox);
if (scrollIntoView) {
// We want to center the label of the element, not the whole tag
// (which includes all of its children, and is vertically huge).
LayoutHelpers.scrollIntoViewIfNeeded(objectBox.firstElementChild);
}
}
if (scrollIntoView) {
// We want to center the label of the element, not the whole tag
// (which includes all of its children, and is vertically huge).
LayoutHelpers.scrollIntoViewIfNeeded(objectBox.firstElementChild);
}
return objectBox;
},
@ -340,6 +341,141 @@ InsideOutBox.prototype =
}
},
/**
* Returns the next object box in the tree for navigation purposes.
*/
nextObjectBox: function IOBox_nextObjectBox(aBoxObject)
{
let candidate;
let boxObject = aBoxObject || this.selectedObjectBox;
if (!boxObject)
return this.rootObjectBox;
// If expanded, return the first child.
let isOpen = this.view.hasClass(boxObject, "open");
let childObjectBox = this.getChildObjectBox(boxObject);
if (isOpen && childObjectBox && childObjectBox.firstChild) {
candidate = childObjectBox.firstChild;
} else {
// Otherwise we get the next available sibling.
while (boxObject) {
if (boxObject.nextSibling) {
boxObject = boxObject.nextSibling;
break;
}
boxObject = this.getParentObjectBox(boxObject);
}
candidate = boxObject;
}
// If the node is not an element (comments or text nodes), we
// jump to the next line.
if (candidate &&
candidate.repObject.nodeType != candidate.repObject.ELEMENT_NODE) {
return this.nextObjectBox(candidate);
}
return candidate;
},
/**
* Returns the next object in the tree for navigation purposes.
*/
nextObject: function IOBox_nextObject()
{
let next = this.nextObjectBox();
return next ? next.repObject : null;
},
/**
* Returns the object that is below the selection.
*
* @param aDistance Number of lines to jump.
*/
farNextObject: function IOBox_farPreviousProject(aDistance)
{
let boxObject = this.selectedObjectBox;
while (aDistance-- > 0) {
let newBoxObject = this.nextObjectBox(boxObject);
if (!newBoxObject) {
break;
}
boxObject = newBoxObject;
}
return boxObject ? boxObject.repObject : null;
},
/**
* Returns the last visible child box of an object box.
*/
lastVisible: function IOBox_lastVisibleChild(aNode)
{
if (!this.view.hasClass(aNode, "open"))
return aNode;
let childBox = this.getChildObjectBox(aNode);
if (!childBox || !childBox.lastChild)
return aNode;
return this.lastVisible(childBox.lastChild);
},
/**
* Returns the previous object box in the tree for navigation purposes.
*/
previousObjectBox: function IOBox_previousObjectBox(aBoxObject)
{
let boxObject = aBoxObject || this.selectedObjectBox;
if (!boxObject)
return this.rootObjectBox;
let candidate;
let sibling = boxObject.previousSibling;
if (sibling) {
candidate = this.lastVisible(sibling);
} else {
candidate = this.getParentObjectBox(boxObject);
}
// If the node is not an element (comments or text nodes), we
// jump to the previous line.
if (candidate &&
candidate.repObject.nodeType != candidate.repObject.ELEMENT_NODE) {
return this.previousObjectBox(candidate);
}
return candidate;
},
/**
* Returns the previous object in the tree for navigation purposes.
*/
previousObject: function IOBox_previousObject()
{
let boxObject = this.previousObjectBox();
return boxObject ? boxObject.repObject : null;
},
/**
* Returns the object that is above the selection.
*
* @param aDistance Number of lines to jump.
*/
farPreviousObject: function IOBox_farPreviousProject(aDistance)
{
let boxObject = this.selectedObjectBox;
while (aDistance-- > 0) {
let newBoxObject = this.previousObjectBox(boxObject);
if (!newBoxObject) {
break;
}
boxObject = newBoxObject;
if (boxObject === this.rootObjectBox)
break;
}
return boxObject ? boxObject.repObject : null;
},
/**
* Open the ancestors of the given object box.
* @param aObjectBox

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

@ -5,11 +5,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Cu = Components.utils;
const Ci = Components.interfaces;
Cu.import("resource:///modules/domplate.jsm");
Cu.import("resource:///modules/InsideOutBox.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource:///modules/inspector.jsm");
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
var EXPORTED_SYMBOLS = ["TreePanel", "DOMHelpers"];
@ -80,6 +82,8 @@ TreePanel.prototype = {
this.ioBox = new InsideOutBox(this, this.treePanelDiv);
this.ioBox.createObjectBox(this.IUI.win.document.documentElement);
this.treeLoaded = true;
this._boundTreeKeyPress = this.onTreeKeyPress.bind(this);
this.treeIFrame.addEventListener("keypress", this._boundTreeKeyPress.bind(this), true);
this.treeIFrame.addEventListener("click", this.onTreeClick.bind(this), false);
this.treeIFrame.addEventListener("dblclick", this.onTreeDblClick.bind(this), false);
this.treeIFrame.focus();
@ -182,6 +186,7 @@ TreePanel.prototype = {
this.treePanelDiv.ownerPanel = null;
let parent = this.treePanelDiv.parentNode;
parent.removeChild(this.treePanelDiv);
this.treeIFrame.removeEventListener("keypress", this._boundTreeKeyPress, true);
delete this.treePanelDiv;
delete this.treeBrowserDocument;
}
@ -272,8 +277,7 @@ TreePanel.prototype = {
if (this.IUI.inspecting) {
this.IUI.stopInspecting(true);
} else {
this.IUI.select(node, true, false);
this.IUI.highlighter.highlight(node);
this.navigate(node);
}
}
}
@ -316,6 +320,52 @@ TreePanel.prototype = {
}
},
navigate: function TP_navigate(node)
{
if (!node)
return;
this.ioBox.select(node, false, false, true);
if (this.IUI.highlighter.isNodeHighlightable(node)) {
this.IUI.select(node, true, false, "treepanel");
this.IUI.highlighter.highlight(node);
}
},
onTreeKeyPress: function TP_onTreeKeyPress(aEvent)
{
let handled = true;
switch(aEvent.keyCode) {
case Ci.nsIDOMKeyEvent.DOM_VK_LEFT:
this.ioBox.contractObjectBox(this.ioBox.selectedObjectBox);
break;
case Ci.nsIDOMKeyEvent.DOM_VK_RIGHT:
this.ioBox.expandObjectBox(this.ioBox.selectedObjectBox);
break;
case Ci.nsIDOMKeyEvent.DOM_VK_UP:
this.navigate(this.ioBox.previousObject());
break;
case Ci.nsIDOMKeyEvent.DOM_VK_DOWN:
this.navigate(this.ioBox.nextObject());
break;
case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP:
this.navigate(this.ioBox.farPreviousObject(10));
break;
case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN:
this.navigate(this.ioBox.farNextObject(10));
break;
case Ci.nsIDOMKeyEvent.DOM_VK_HOME:
this.navigate(this.ioBox.rootObject);
break;
default:
handled = false;
}
if (handled) {
aEvent.stopPropagation();
aEvent.preventDefault();
}
},
/**
* Starts the editor for an attribute name or value.
* @param aAttrObj
@ -542,10 +592,10 @@ TreePanel.prototype = {
* @param aNode the DOM node in the content document to select.
* @param aScroll boolean scroll to the visible node?
*/
select: function TP_select(aNode, aScroll)
select: function TP_select(aNode, aScroll, aFrom)
{
if (this.ioBox) {
this.ioBox.select(aNode, true, true, aScroll);
this.ioBox.select(aNode, true, aFrom != "treepanel", aScroll);
} else {
this.pendingSelection = { node: aNode, scroll: aScroll };
}

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

@ -440,7 +440,7 @@ InspectorUI.prototype = {
/**
* Toggle the TreePanel.
*/
toggleHTMLPanel: function TP_toggleHTMLPanel()
toggleHTMLPanel: function IUI_toggleHTMLPanel()
{
if (this.treePanel.isOpen()) {
this.treePanel.close();
@ -849,7 +849,7 @@ InspectorUI.prototype = {
this.breadcrumbs.update();
this.chromeWin.Tilt.update(aNode);
this.treePanel.select(aNode, aScroll);
this.treePanel.select(aNode, aScroll, aFrom);
this._notifySelected(aFrom);
},

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

@ -42,6 +42,8 @@ _BROWSER_FILES = \
browser_inspector_pseudoClass_menu.js \
browser_inspector_destroyselection.html \
browser_inspector_destroyselection.js \
browser_inspector_treePanel_navigation.html \
browser_inspector_treePanel_navigation.js \
head.js \
$(NULL)

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

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html class="html">
<meta charset=utf-8 />
<body class="body">
<div class="node0">
<p class="node1">line1</p>
<p class="node2">line2</p>
<p class="node3">line3</p>
<!-- A comment -->
<p class="node4">line4
<span class="node5">line5</span>
<span class="node6">line6</span>
<!-- A comment -->
<a class="node7">line7<span class="node8">line8</span></a>
<span class="node9">line9</span>
<span class="node10">line10</span>
<span class="node11">line11</span>
<a class="node12">line12<span class="node13">line13</span></a>
</p>
<p class="node14">line14</p>
<p class="node15">line15</p>
</div>
</body>
</html>

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

@ -0,0 +1,103 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
waitForExplicitFinish();
let doc;
let keySequence = "right down right ";
keySequence += "down down down down right ";
keySequence += "down down down right ";
keySequence += "down down down down down right ";
keySequence += "down down down down down ";
keySequence += "up up up left down home ";
keySequence += "pagedown left down down pageup pageup left down";
keySequence = keySequence.split(" ");
let keySequenceRes = "body node0 node0 ";
keySequenceRes += "node1 node2 node3 node4 node4 ";
keySequenceRes += "node5 node6 node7 node7 ";
keySequenceRes += "node8 node9 node10 node11 node12 node12 ";
keySequenceRes += "node13 node14 node15 node15 node15 ";
keySequenceRes += "node14 node13 node12 node12 node14 html ";
keySequenceRes += "node7 node7 node9 node10 body html html html";
keySequenceRes = keySequenceRes.split(" ");
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload() {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
doc = content.document;
waitForFocus(setupTest, content);
}, true);
content.location = "http://mochi.test:8888/browser/browser/devtools/highlighter/test/browser_inspector_treePanel_navigation.html";
function setupTest() {
Services.obs.addObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
InspectorUI.toggleInspectorUI();
}
function runTests() {
Services.obs.removeObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
Services.obs.addObserver(startNavigation, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
InspectorUI.select(doc.body, true, true, true);
InspectorUI.toggleHTMLPanel();
}
function startNavigation() {
Services.obs.removeObserver(startNavigation, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
nextStep(0);
}
function nextStep(cursor) {
let key = keySequence[cursor];
let className = keySequenceRes[cursor];
switch(key) {
case "right":
EventUtils.synthesizeKey("VK_RIGHT", {});
break;
case "down":
EventUtils.synthesizeKey("VK_DOWN", {});
break;
case "left":
EventUtils.synthesizeKey("VK_LEFT", {});
break;
case "up":
EventUtils.synthesizeKey("VK_UP", {});
break;
case "pageup":
EventUtils.synthesizeKey("VK_PAGE_UP", {});
break;
case "pagedown":
EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
break;
case "home":
EventUtils.synthesizeKey("VK_HOME", {});
break;
}
executeSoon(function() {
if (cursor >= keySequence.length) {
Services.obs.addObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
InspectorUI.closeInspectorUI();
} else {
let node = InspectorUI.treePanel.ioBox.selectedObjectBox.repObject;
is(node.className, className, "[" + cursor + "] right node selected: " + className);
nextStep(cursor + 1);
}
});
}
function finishUp() {
Services.obs.removeObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
doc = null;
gBrowser.removeCurrentTab();
finish();
}
}