Bug 877686 - Add UI to toggle the blackboxing of specific sources; r=vporof

This commit is contained in:
Nick Fitzgerald 2013-07-17 23:04:13 -07:00
parent 17c6d3866d
commit b68e635891
19 changed files with 620 additions and 47 deletions

View File

@ -423,6 +423,7 @@ function StackFrames() {
this._onResumed = this._onResumed.bind(this);
this._onFrames = this._onFrames.bind(this);
this._onFramesCleared = this._onFramesCleared.bind(this);
this._onBlackBoxChange = this._onBlackBoxChange.bind(this);
this._afterFramesCleared = this._afterFramesCleared.bind(this);
this.evaluate = this.evaluate.bind(this);
}
@ -447,6 +448,7 @@ StackFrames.prototype = {
this.activeThread.addListener("resumed", this._onResumed);
this.activeThread.addListener("framesadded", this._onFrames);
this.activeThread.addListener("framescleared", this._onFramesCleared);
window.addEventListener("Debugger:BlackBoxChange", this._onBlackBoxChange, false);
this._handleTabNavigation();
},
@ -462,6 +464,7 @@ StackFrames.prototype = {
this.activeThread.removeListener("resumed", this._onResumed);
this.activeThread.removeListener("framesadded", this._onFrames);
this.activeThread.removeListener("framescleared", this._onFramesCleared);
window.removeEventListener("Debugger:BlackBoxChange", this._onBlackBoxChange, false);
},
/**
@ -594,12 +597,22 @@ StackFrames.prototype = {
// Make sure all the previous stackframes are removed before re-adding them.
DebuggerView.StackFrames.empty();
let previousBlackBoxed = null;
for (let frame of this.activeThread.cachedFrames) {
let { depth, where: { url, line } } = frame;
let { depth, where: { url, line }, isBlackBoxed } = frame;
let frameLocation = NetworkHelper.convertToUnicode(unescape(url));
let frameTitle = StackFrameUtils.getFrameTitle(frame);
DebuggerView.StackFrames.addFrame(frameTitle, frameLocation, line, depth);
if (isBlackBoxed) {
if (previousBlackBoxed == url) {
continue;
}
previousBlackBoxed = url;
} else {
previousBlackBoxed = null;
}
DebuggerView.StackFrames.addFrame(frameTitle, frameLocation, line, depth, isBlackBoxed);
}
if (this.currentFrame == null) {
DebuggerView.StackFrames.selectedDepth = 0;
@ -626,6 +639,18 @@ StackFrames.prototype = {
window.setTimeout(this._afterFramesCleared, FRAME_STEP_CLEAR_DELAY);
},
/**
* Handler for the debugger's BlackBoxChange notification.
*/
_onBlackBoxChange: function() {
if (this.activeThread.state == "paused") {
// We have to clear out the existing frames and refetch them to get their
// updated black boxed status.
this.activeThread._clearFrames();
this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
}
},
/**
* Called soon after the thread client's framescleared notification.
*/
@ -1000,6 +1025,27 @@ SourceScripts.prototype = {
window.dispatchEvent(document, "Debugger:AfterSourcesAdded");
},
/**
* Set the black boxed status of the given source.
*
* @param Object aSource
* The source form.
* @param bool aBlackBoxFlag
* True to black box the source, false to un-black box it.
*/
blackBox: function(aSource, aBlackBoxFlag) {
const sourceClient = this.activeThread.source(aSource);
sourceClient[aBlackBoxFlag ? "blackBox" : "unblackBox"](function({ error, message }) {
if (error) {
let msg = "Could not toggle black boxing for "
+ aSource.url + ": " + message;
dumpn(msg);
return void Cu.reportError(msg);
}
window.dispatchEvent(document, "Debugger:BlackBoxChange", sourceClient);
});
},
/**
* Gets a specified source's text.
*

View File

@ -18,6 +18,7 @@ function SourcesView() {
this._onSourceSelect = this._onSourceSelect.bind(this);
this._onSourceClick = this._onSourceClick.bind(this);
this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this);
this._onSourceCheck = this._onSourceCheck.bind(this);
this._onBreakpointClick = this._onBreakpointClick.bind(this);
this._onBreakpointCheckboxClick = this._onBreakpointCheckboxClick.bind(this);
this._onConditionalPopupShowing = this._onConditionalPopupShowing.bind(this);
@ -34,9 +35,12 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
initialize: function() {
dumpn("Initializing the SourcesView");
this.widget = new SideMenuWidget(document.getElementById("sources"));
this.widget = new SideMenuWidget(document.getElementById("sources"), {
showCheckboxes: true
});
this.emptyText = L10N.getStr("noSourcesText");
this.unavailableText = L10N.getStr("noMatchingSourcesText");
this._blackBoxCheckboxTooltip = L10N.getStr("blackBoxCheckboxTooltip");
this._commandset = document.getElementById("debuggerCommands");
this._popupset = document.getElementById("debuggerPopupset");
@ -48,6 +52,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
window.addEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
this.widget.addEventListener("select", this._onSourceSelect, false);
this.widget.addEventListener("click", this._onSourceClick, false);
this.widget.addEventListener("check", this._onSourceCheck, false);
this._cbPanel.addEventListener("popupshowing", this._onConditionalPopupShowing, false);
this._cbPanel.addEventListener("popupshown", this._onConditionalPopupShown, false);
this._cbPanel.addEventListener("popuphiding", this._onConditionalPopupHiding, false);
@ -70,6 +75,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
window.removeEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
this.widget.removeEventListener("select", this._onSourceSelect, false);
this.widget.removeEventListener("click", this._onSourceClick, false);
this.widget.removeEventListener("check", this._onSourceCheck, false);
this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false);
this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShown, false);
this._cbPanel.removeEventListener("popuphiding", this._onConditionalPopupHiding, false);
@ -109,6 +115,8 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
this.push([label, url, group], {
staged: aOptions.staged, /* stage the item to be appended later? */
attachment: {
checkboxState: !aSource.isBlackBoxed,
checkboxTooltip: this._blackBoxCheckboxTooltip,
source: aSource
}
});
@ -639,6 +647,14 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
DebuggerView.Filtering.target = this;
},
/**
* The check listener for the sources container.
*/
_onSourceCheck: function({ detail: { checked }, target }) {
let item = this.getItemForElement(target);
DebuggerController.SourceScripts.blackBox(item.attachment.source, !checked);
},
/**
* The click listener for a breakpoint container.
*/

View File

@ -424,8 +424,10 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
* The line number to be displayed in the list.
* @param number aDepth
* The frame depth specified by the debugger.
* @param boolean aIsBlackBoxed
* Whether or not the frame is black boxed.
*/
addFrame: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth) {
addFrame: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth, aIsBlackBoxed) {
// Create the element node and menu entry for the stack frame item.
let frameView = this._createFrameView.apply(this, arguments);
let menuEntry = this._createMenuEntry.apply(this, arguments);
@ -471,29 +473,35 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
* The line number to be displayed in the list.
* @param number aDepth
* The frame depth specified by the debugger.
* @param boolean aIsBlackBoxed
* Whether or not the frame is black boxed.
* @return nsIDOMNode
* The stack frame view.
*/
_createFrameView: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth) {
let frameDetails =
SourceUtils.trimUrlLength(
SourceUtils.getSourceLabel(aSourceLocation),
STACK_FRAMES_SOURCE_URL_MAX_LENGTH,
STACK_FRAMES_SOURCE_URL_TRIM_SECTION) + SEARCH_LINE_FLAG + aLineNumber;
let frameTitleNode = document.createElement("label");
frameTitleNode.className = "plain dbg-stackframe-title breadcrumbs-widget-item-tag";
frameTitleNode.setAttribute("value", aFrameTitle);
let frameDetailsNode = document.createElement("label");
frameDetailsNode.className = "plain dbg-stackframe-details breadcrumbs-widget-item-id";
frameDetailsNode.setAttribute("value", frameDetails);
_createFrameView: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth, aIsBlackBoxed) {
let container = document.createElement("hbox");
container.id = "stackframe-" + aDepth;
container.className = "dbg-stackframe";
container.appendChild(frameTitleNode);
let frameDetails = SourceUtils.trimUrlLength(
SourceUtils.getSourceLabel(aSourceLocation),
STACK_FRAMES_SOURCE_URL_MAX_LENGTH,
STACK_FRAMES_SOURCE_URL_TRIM_SECTION);
if (aIsBlackBoxed) {
container.classList.add("dbg-stackframe-black-boxed");
} else {
let frameTitleNode = document.createElement("label");
frameTitleNode.className = "plain dbg-stackframe-title breadcrumbs-widget-item-tag";
frameTitleNode.setAttribute("value", aFrameTitle);
container.appendChild(frameTitleNode);
frameDetails += SEARCH_LINE_FLAG + aLineNumber;
}
let frameDetailsNode = document.createElement("label");
frameDetailsNode.className = "plain dbg-stackframe-details breadcrumbs-widget-item-id";
frameDetailsNode.setAttribute("value", frameDetails);
container.appendChild(frameDetailsNode);
return container;
@ -510,10 +518,12 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
* The line number to be displayed in the list.
* @param number aDepth
* The frame depth specified by the debugger.
* @param boolean aIsBlackBoxed
* Whether or not the frame is black boxed.
* @return object
* An object containing the stack frame command and menu item.
*/
_createMenuEntry: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth) {
_createMenuEntry: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth, aIsBlackBoxed) {
let frameDescription =
SourceUtils.trimUrlLength(
SourceUtils.getSourceLabel(aSourceLocation),

View File

@ -12,6 +12,10 @@ include $(DEPTH)/config/autoconf.mk
MOCHITEST_BROWSER_TESTS = \
browser_dbg_aaa_run_first_leaktest.js \
browser_dbg_blackboxing-01.js \
browser_dbg_blackboxing-02.js \
browser_dbg_blackboxing-03.js \
browser_dbg_blackboxing-04.js \
browser_dbg_clean-exit.js \
browser_dbg_cmd.js \
browser_dbg_cmd_break.js \
@ -110,6 +114,11 @@ MOCHITEST_BROWSER_TESTS = \
$(NULL)
MOCHITEST_BROWSER_PAGES = \
browser_dbg_blackboxing.html \
browser_dbg_blackboxing_blackboxme.js \
browser_dbg_blackboxing_one.js \
browser_dbg_blackboxing_two.js \
browser_dbg_blackboxing_three.js \
browser_dbg_cmd_break.html \
browser_dbg_cmd.html \
testactors.js \

View File

@ -0,0 +1,76 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that if we black box a source and then refresh, it is still black boxed.
*/
const TAB_URL = EXAMPLE_URL + "binary_search.html";
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
function test()
{
let scriptShown = false;
let framesAdded = false;
let resumed = false;
let testStarted = false;
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
resumed = true;
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.panelWin;
testBlackBoxSource();
});
}
function testBlackBoxSource() {
once(gDebugger, "Debugger:SourceShown", function () {
const checkbox = gDebugger.document.querySelector(".side-menu-widget-item-checkbox");
ok(checkbox, "Should get the checkbox for black boxing the source");
ok(checkbox.checked, "Should not be black boxed by default");
once(gDebugger, "Debugger:BlackBoxChange", function (event) {
const sourceClient = event.detail;
ok(sourceClient.isBlackBoxed, "The source should be black boxed now");
ok(!checkbox.checked, "The checkbox should no longer be checked.");
testBlackBoxReload();
});
checkbox.click();
});
}
function testBlackBoxReload() {
once(gDebugger, "Debugger:SourceShown", function () {
const checkbox = gDebugger.document.querySelector(".side-menu-widget-item-checkbox");
ok(checkbox, "Should get the checkbox for black boxing the source");
ok(!checkbox.checked, "Should still be black boxed");
closeDebuggerAndFinish();
});
gDebuggee.location.reload();
}
function once(target, event, callback) {
target.addEventListener(event, function _listener(...args) {
target.removeEventListener(event, _listener, false);
callback.apply(null, args);
}, false);
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
});

View File

@ -0,0 +1,87 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that black boxed frames are compressed into a single frame on the stack
* view.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html";
const BLACKBOXME_URL = EXAMPLE_URL + "browser_dbg_blackboxing_blackboxme.js"
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
function test()
{
let scriptShown = false;
let framesAdded = false;
let resumed = false;
let testStarted = false;
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
resumed = true;
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.panelWin;
testBlackBoxSource();
});
}
function testBlackBoxSource() {
once(gDebugger, "Debugger:SourceShown", function () {
const checkbox = getBlackBoxCheckbox(BLACKBOXME_URL);
ok(checkbox, "Should get the checkbox for blackBoxing the source");
once(gDebugger, "Debugger:BlackBoxChange", function (event) {
const sourceClient = event.detail;
ok(sourceClient.isBlackBoxed, "The source should be black boxed now");
testBlackBoxStack();
});
checkbox.click();
});
}
function testBlackBoxStack() {
const { activeThread } = gDebugger.DebuggerController;
activeThread.addOneTimeListener("framesadded", function () {
const frames = gDebugger.DebuggerView.StackFrames.widget._list;
is(frames.querySelectorAll(".dbg-stackframe").length, 3,
"Should only get 3 frames");
is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
"And one of them should be the combined black boxed frames");
closeDebuggerAndFinish();
});
gDebuggee.runTest();
}
function getBlackBoxCheckbox(url) {
return gDebugger.document.querySelector(
".side-menu-widget-item[tooltiptext=\""
+ url + "\"] .side-menu-widget-item-checkbox");
}
function once(target, event, callback) {
target.addEventListener(event, function _listener(...args) {
target.removeEventListener(event, _listener, false);
callback.apply(null, args);
}, false);
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
});

View File

@ -0,0 +1,96 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that black boxed frames are compressed into a single frame on the stack
* view when we are already paused.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html";
const BLACKBOXME_URL = EXAMPLE_URL + "browser_dbg_blackboxing_blackboxme.js"
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
function test()
{
let scriptShown = false;
let framesAdded = false;
let resumed = false;
let testStarted = false;
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
resumed = true;
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.panelWin;
once(gDebugger, "Debugger:SourceShown", function () {
testBlackBoxStack();
});
});
}
function testBlackBoxStack() {
const { activeThread } = gDebugger.DebuggerController;
activeThread.addOneTimeListener("framesadded", function () {
const frames = gDebugger.DebuggerView.StackFrames.widget._list;
is(frames.querySelectorAll(".dbg-stackframe").length, 6,
"Should get 6 frames");
is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 0,
"And none of them are black boxed");
testBlackBoxSource();
});
gDebuggee.runTest();
}
function testBlackBoxSource() {
const checkbox = getBlackBoxCheckbox(BLACKBOXME_URL);
ok(checkbox, "Should get the checkbox for black boxing the source");
once(gDebugger, "Debugger:BlackBoxChange", function (event) {
const { activeThread } = gDebugger.DebuggerController;
activeThread.addOneTimeListener("framesadded", function () {
const sourceClient = event.detail;
ok(sourceClient.isBlackBoxed, "The source should be black boxed now");
const frames = gDebugger.DebuggerView.StackFrames.widget._list;
is(frames.querySelectorAll(".dbg-stackframe").length, 3,
"Should only get 3 frames");
is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
"And one of them is the combined black boxed frames");
closeDebuggerAndFinish();
});
});
checkbox.click();
}
function getBlackBoxCheckbox(url) {
return gDebugger.document.querySelector(
".side-menu-widget-item[tooltiptext=\""
+ url + "\"] .side-menu-widget-item-checkbox");
}
function once(target, event, callback) {
target.addEventListener(event, function _listener(...args) {
target.removeEventListener(event, _listener, false);
callback.apply(null, args);
}, false);
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
});

View File

@ -0,0 +1,84 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that we get a stack frame for each black boxed source, not a single one
* for all of them.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html";
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
function test()
{
let scriptShown = false;
let framesAdded = false;
let resumed = false;
let testStarted = false;
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
resumed = true;
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.panelWin;
once(gDebugger, "Debugger:SourceShown", function () {
blackBoxSources();
});
});
}
function blackBoxSources() {
let timesFired = 0;
gDebugger.addEventListener("Debugger:BlackBoxChange", function _onBlackboxChange() {
if (++timesFired !== 3) {
return;
}
gDebugger.removeEventListener("Debugger:BlackBoxChange", _onBlackboxChange, false);
const { activeThread } = gDebugger.DebuggerController;
activeThread.addOneTimeListener("framesadded", testStackFrames);
gDebuggee.one();
}, false);
getBlackBoxCheckbox(EXAMPLE_URL + "browser_dbg_blackboxing_one.js").click();
getBlackBoxCheckbox(EXAMPLE_URL + "browser_dbg_blackboxing_two.js").click();
getBlackBoxCheckbox(EXAMPLE_URL + "browser_dbg_blackboxing_three.js").click();
}
function testStackFrames() {
const frames = gDebugger.DebuggerView.StackFrames.widget._list;
is(frames.querySelectorAll(".dbg-stackframe").length, 4,
"Should get 4 frames (one -> two -> three -> doDebuggerStatement)");
is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 3,
"And one, two, and three should each have their own black boxed frame.");
closeDebuggerAndFinish();
}
function getBlackBoxCheckbox(url) {
return gDebugger.document.querySelector(
".side-menu-widget-item[tooltiptext=\""
+ url + "\"] .side-menu-widget-item-checkbox");
}
function once(target, event, callback) {
target.addEventListener(event, function _listener(...args) {
target.removeEventListener(event, _listener, false);
callback.apply(null, args);
}, false);
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
});

View File

@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset='utf-8'/>
<title>Browser Debugger Blackbox Test</title>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<script type="text/javascript" src="browser_dbg_blackboxing_blackboxme.js"></script>
<script type="text/javascript" src="browser_dbg_blackboxing_one.js"></script>
<script type="text/javascript" src="browser_dbg_blackboxing_two.js"></script>
<script type="text/javascript" src="browser_dbg_blackboxing_three.js"></script>
<script>
function runTest() {
blackboxme(doDebuggerStatement);
}
function doDebuggerStatement() {
debugger;
}
</script>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,9 @@
function blackboxme(fn) {
(function one() {
(function two() {
(function three() {
fn();
}());
}());
}());
}

View File

@ -0,0 +1 @@
function one() { two(); }

View File

@ -0,0 +1 @@
function three() { doDebuggerStatement(); }

View File

@ -0,0 +1 @@
function two() { three(); }

View File

@ -25,21 +25,28 @@ this.EXPORTED_SYMBOLS = ["SideMenuWidget"];
*
* @param nsIDOMNode aNode
* The element associated with the widget.
* @param boolean aShowArrows
* Specifies if items in this container should display horizontal arrows.
* @param Object aOptions
* - showArrows: Specifies if items in this container should display
* horizontal arrows.
* - showCheckboxes: Specifies if items in this container should display
* checkboxes.
*/
this.SideMenuWidget = function SideMenuWidget(aNode, aShowArrows = true) {
this.SideMenuWidget = function SideMenuWidget(aNode, aOptions={}) {
this.document = aNode.ownerDocument;
this.window = this.document.defaultView;
this._parent = aNode;
this._showArrows = aShowArrows;
let { showArrows, showCheckboxes } = aOptions;
this._showArrows = showArrows || false;
this._showCheckboxes = showCheckboxes || false;
// Create an internal scrollbox container.
this._list = this.document.createElement("scrollbox");
this._list.className = "side-menu-widget-container";
this._list.setAttribute("flex", "1");
this._list.setAttribute("orient", "vertical");
this._list.setAttribute("with-arrow", aShowArrows);
this._list.setAttribute("with-arrow", showArrows);
this._list.setAttribute("with-checkboxes", showCheckboxes);
this._list.setAttribute("tabindex", "0");
this._list.addEventListener("keypress", e => this.emit("keyPress", e), false);
this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false);
@ -91,10 +98,12 @@ SideMenuWidget.prototype = {
* A tooltip attribute for the displayed item.
* @param string aGroup [optional]
* The group to place the displayed item into.
* @param Object aAttachment [optional]
* Extra data for the user.
* @return nsIDOMNode
* The element associated with the displayed item.
*/
insertItemAt: function(aIndex, aContents, aTooltip = "", aGroup = "") {
insertItemAt: function(aIndex, aContents, aTooltip = "", aGroup = "", aAttachment={}) {
aTooltip = NetworkHelper.convertToUnicode(unescape(aTooltip));
aGroup = NetworkHelper.convertToUnicode(unescape(aGroup));
@ -115,7 +124,7 @@ SideMenuWidget.prototype = {
(this._list.scrollTop + this._list.clientHeight >= this._list.scrollHeight);
let group = this._getMenuGroupForName(aGroup);
let item = this._getMenuItemForGroup(group, aContents, aTooltip);
let item = this._getMenuItemForGroup(group, aContents, aTooltip, aAttachment);
let element = item.insertSelfAt(aIndex);
if (this.maintainSelectionVisible) {
@ -397,14 +406,17 @@ SideMenuWidget.prototype = {
* The string or node displayed in the container.
* @param string aTooltip [optional]
* A tooltip attribute for the displayed item.
* @param object aAttachment [optional]
* The attachement object.
*/
_getMenuItemForGroup: function(aGroup, aContents, aTooltip) {
return new SideMenuItem(aGroup, aContents, aTooltip, this._showArrows);
_getMenuItemForGroup: function(aGroup, aContents, aTooltip, aAttachment) {
return new SideMenuItem(aGroup, aContents, aTooltip, this._showArrows, this._showCheckboxes, aAttachment);
},
window: null,
document: null,
_showArrows: false,
_showCheckboxes: false,
_parent: null,
_list: null,
_boxObject: null,
@ -527,14 +539,30 @@ SideMenuGroup.prototype = {
* The string or node displayed in the container.
* @param boolean aArrowFlag
* True if a horizontal arrow should be shown.
* @param boolean aCheckboxFlag
* True if a checkbox should be shown.
* @param object aAttachment [optional]
* The attachment object.
*/
function SideMenuItem(aGroup, aContents, aTooltip, aArrowFlag) {
function SideMenuItem(aGroup, aContents, aTooltip, aArrowFlag, aCheckboxFlag, aAttachment={}) {
this.document = aGroup.document;
this.window = aGroup.window;
this.ownerView = aGroup;
// Show a horizontal arrow towards the content.
if (aArrowFlag) {
let makeCheckbox = () => {
let checkbox = this.document.createElement("checkbox");
checkbox.className = "side-menu-widget-item-checkbox";
checkbox.setAttribute("checked", aAttachment.checkboxState);
checkbox.setAttribute("tooltiptext", aAttachment.checkboxTooltip);
checkbox.addEventListener("command", function () {
ViewHelpers.dispatchEvent(checkbox, "check", {
checked: checkbox.checked,
});
}, false);
return checkbox;
};
if (aArrowFlag || aCheckboxFlag) {
let container = this._container = this.document.createElement("hbox");
container.className = "side-menu-widget-item";
container.setAttribute("tooltiptext", aTooltip);
@ -542,13 +570,22 @@ function SideMenuItem(aGroup, aContents, aTooltip, aArrowFlag) {
let target = this._target = this.document.createElement("vbox");
target.className = "side-menu-widget-item-contents";
let arrow = this._arrow = this.document.createElement("hbox");
arrow.className = "side-menu-widget-item-arrow";
// Show a checkbox before the content.
if (aCheckboxFlag) {
let checkbox = this._checkbox = makeCheckbox();
container.appendChild(checkbox);
}
container.appendChild(target);
container.appendChild(arrow);
// Show a horizontal arrow towards the content.
if (aArrowFlag) {
let arrow = this._arrow = this.document.createElement("hbox");
arrow.className = "side-menu-widget-item-arrow";
container.appendChild(arrow);
}
}
// Skip a few redundant nodes when no horizontal arrow is shown.
// Skip a few redundant nodes when no horizontal arrow or checkbox is shown.
else {
let target = this._target = this._container = this.document.createElement("hbox");
target.className = "side-menu-widget-item side-menu-widget-item-contents";

View File

@ -11,6 +11,7 @@ const Cu = Components.utils;
const PANE_APPEARANCE_DELAY = 50;
const PAGE_SIZE_ITEM_COUNT_RATIO = 5;
const WIDGET_FOCUSABLE_NODES = new Set(["vbox", "hbox"]);
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -609,7 +610,7 @@ this.WidgetMethods = {
* - relaxed: true if this container should allow dupes & degenerates
* - attachment: some attached primitive/object for the item
* - attributes: a batch of attributes set to the displayed element
* - finalize: function invoked when the item is removed
* - finalize: function invokde when the item is removed
* @return Item
* The item associated with the displayed element if an unstaged push,
* undefined if the item was staged for a later commit.
@ -1117,16 +1118,21 @@ this.WidgetMethods = {
_focusChange: function(aDirection) {
let commandDispatcher = this._commandDispatcher;
let prevFocusedElement = commandDispatcher.focusedElement;
let currFocusedElement;
commandDispatcher.suppressFocusScroll = true;
commandDispatcher[aDirection]();
do {
commandDispatcher.suppressFocusScroll = true;
commandDispatcher[aDirection]();
currFocusedElement = commandDispatcher.focusedElement;
// Make sure the newly focused item is a part of this container. If the
// focus goes out of bounds, revert the previously focused item.
if (!this.getItemForElement(currFocusedElement)) {
prevFocusedElement.focus();
return false;
}
} while (!WIDGET_FOCUSABLE_NODES.has(currFocusedElement.tagName));
// Make sure the newly focused item is a part of this container.
// If the focus goes out of bounds, revert the previously focused item.
if (!this.getItemForElement(commandDispatcher.focusedElement)) {
prevFocusedElement.focus();
return false;
}
// Focus remained within bounds.
return true;
},
@ -1208,7 +1214,10 @@ this.WidgetMethods = {
*/
getItemForElement: function(aElement) {
while (aElement) {
let item = this._itemsByElement.get(aElement);
let item =
this._itemsByElement.get(aElement) ||
this._itemsByElement.get(aElement.nextElementSibling) ||
this._itemsByElement.get(aElement.previousElementSibling);
if (item) {
return item;
}

View File

@ -73,6 +73,11 @@ noMatchingSourcesText=No matching sources.
# global search results when there are no matching strings after filtering.
noMatchingStringsText=No matches found
# LOCALIZATION NOTE (blackBoxCheckboxTooltip) = The tooltip text to display when
# the user hovers over the checkbox used to toggle black boxing its associated
# source.
blackBoxCheckboxTooltip=Toggle black boxing
# LOCALIZATION NOTE (emptyFilterText): This is the text that appears in the
# filter text box when it is empty and the scripts container is selected.
emptyFilterText=Filter scripts (%S)

View File

@ -17,6 +17,27 @@
-moz-border-start-color: transparent;
}
.side-menu-widget-item-checkbox {
-moz-appearance: none;
padding: 0;
margin: 0 -4px 0 4px;
}
.side-menu-widget-item-checkbox > .checkbox-check {
-moz-appearance: none;
background: none;
background-image: url(itemToggle.png);
background-repeat: no-repeat;
background-clip: content-box;
background-position: -24px 0;
width: 24px;
height: 24px;
}
.side-menu-widget-item-checkbox[checked] > .checkbox-check {
background-position: 0 0;
}
/* ListWidget items */
.list-widget-item {

View File

@ -19,6 +19,27 @@
-moz-border-start-color: transparent;
}
.side-menu-widget-item-checkbox {
-moz-appearance: none;
padding: 0;
margin: 0 -4px 0 4px;
}
.side-menu-widget-item-checkbox > .checkbox-check {
-moz-appearance: none;
background: none;
background-image: url(itemToggle.png);
background-repeat: no-repeat;
background-clip: content-box;
background-position: -24px 0;
width: 24px;
height: 24px;
}
.side-menu-widget-item-checkbox[checked] > .checkbox-check {
background-position: 0 0;
}
/* ListWidget items */
.list-widget-item {

View File

@ -17,6 +17,27 @@
-moz-border-start-color: transparent;
}
.side-menu-widget-item-checkbox {
-moz-appearance: none;
padding: 0;
margin: 0 -4px 0 4px;
}
.side-menu-widget-item-checkbox > .checkbox-check {
-moz-appearance: none;
background: none;
background-image: url(itemToggle.png);
background-repeat: no-repeat;
background-clip: content-box;
background-position: -24px 0;
width: 24px;
height: 24px;
}
.side-menu-widget-item-checkbox[checked] > .checkbox-check {
background-position: 0 0;
}
/* ListWidget items */
.list-widget-item {