Bug 762761 - part 2: front end changes for debugger pretty printing; r=vporof

This commit is contained in:
Nick Fitzgerald 2013-09-18 17:56:20 -07:00
Родитель 72115182ca
Коммит 99320a9b6c
18 изменённых файлов: 445 добавлений и 30 удалений

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

@ -1145,6 +1145,48 @@ SourceScripts.prototype = {
});
},
/**
* Pretty print a source's text. All subsequent calls to |getText| will return
* the pretty text.
*
* @param Object aSource
* The source form from the RDP.
* @returns Promise
* A promise that resolves to [aSource, prettyText] or rejects to
* [aSource, error].
*/
prettyPrint: function(aSource) {
let textPromise = this._cache.get(aSource.url);
// Only use the existing promise if it is pretty printed.
if (textPromise && textPromise.pretty) {
return textPromise;
}
const deferred = promise.defer();
this._cache.set(aSource.url, deferred.promise);
this.activeThread.source(aSource)
.prettyPrint(Prefs.editorTabSize, ({ error, message, source }) => {
if (error) {
deferred.reject([aSource, message || error]);
return;
}
DebuggerController.Parser.clearSource(aSource.url);
if (this.activeThread.paused) {
// Update the stack frame list.
this.activeThread._clearFrames();
this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
}
deferred.resolve([aSource, source]);
});
deferred.promise.pretty = true;
return deferred.promise;
},
/**
* Gets a specified source's text.
*
@ -1697,18 +1739,19 @@ let L10N = new ViewHelpers.L10N(DBG_STRINGS_URI);
/**
* Shortcuts for accessing various debugger preferences.
*/
let Prefs = new ViewHelpers.Prefs("devtools.debugger", {
chromeDebuggingHost: ["Char", "chrome-debugging-host"],
chromeDebuggingPort: ["Int", "chrome-debugging-port"],
sourcesWidth: ["Int", "ui.panes-sources-width"],
instrumentsWidth: ["Int", "ui.panes-instruments-width"],
panesVisibleOnStartup: ["Bool", "ui.panes-visible-on-startup"],
variablesSortingEnabled: ["Bool", "ui.variables-sorting-enabled"],
variablesOnlyEnumVisible: ["Bool", "ui.variables-only-enum-visible"],
variablesSearchboxVisible: ["Bool", "ui.variables-searchbox-visible"],
pauseOnExceptions: ["Bool", "pause-on-exceptions"],
ignoreCaughtExceptions: ["Bool", "ignore-caught-exceptions"],
sourceMapsEnabled: ["Bool", "source-maps-enabled"]
let Prefs = new ViewHelpers.Prefs("devtools", {
chromeDebuggingHost: ["Char", "debugger.chrome-debugging-host"],
chromeDebuggingPort: ["Int", "debugger.chrome-debugging-port"],
sourcesWidth: ["Int", "debugger.ui.panes-sources-width"],
instrumentsWidth: ["Int", "debugger.ui.panes-instruments-width"],
panesVisibleOnStartup: ["Bool", "debugger.ui.panes-visible-on-startup"],
variablesSortingEnabled: ["Bool", "debugger.ui.variables-sorting-enabled"],
variablesOnlyEnumVisible: ["Bool", "debugger.ui.variables-only-enum-visible"],
variablesSearchboxVisible: ["Bool", "debugger.ui.variables-searchbox-visible"],
pauseOnExceptions: ["Bool", "debugger.pause-on-exceptions"],
ignoreCaughtExceptions: ["Bool", "debugger.ignore-caught-exceptions"],
sourceMapsEnabled: ["Bool", "debugger.source-maps-enabled"],
editorTabSize: ["Int", "editor.tabsize"]
});
/**

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

@ -11,6 +11,7 @@
function SourcesView() {
dumpn("SourcesView was instantiated");
this.prettyPrint = this.prettyPrint.bind(this);
this._onEditorLoad = this._onEditorLoad.bind(this);
this._onEditorUnload = this._onEditorUnload.bind(this);
this._onEditorSelection = this._onEditorSelection.bind(this);
@ -51,6 +52,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
this._cbTextbox = document.getElementById("conditional-breakpoint-panel-textbox");
this._editorDeck = document.getElementById("editor-deck");
this._stopBlackBoxButton = document.getElementById("black-boxed-message-button");
this._prettyPrintButton = document.getElementById("pretty-print");
window.on(EVENTS.EDITOR_LOADED, this._onEditorLoad, false);
window.on(EVENTS.EDITOR_UNLOADED, this._onEditorUnload, false);
@ -58,6 +60,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
this.widget.addEventListener("click", this._onSourceClick, false);
this.widget.addEventListener("check", this._onSourceCheck, false);
this._stopBlackBoxButton.addEventListener("click", this._onStopBlackBoxing, false);
this._prettyPrintButton.addEventListener("click", this.prettyPrint, false);
this._cbPanel.addEventListener("popupshowing", this._onConditionalPopupShowing, false);
this._cbPanel.addEventListener("popupshown", this._onConditionalPopupShown, false);
this._cbPanel.addEventListener("popuphiding", this._onConditionalPopupHiding, false);
@ -82,6 +85,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
this.widget.removeEventListener("click", this._onSourceClick, false);
this.widget.removeEventListener("check", this._onSourceCheck, false);
this._stopBlackBoxButton.removeEventListener("click", this._onStopBlackBoxing, false);
this._prettyPrintButton.removeEventListener("click", this.prettyPrint, false);
this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false);
this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShown, false);
this._cbPanel.removeEventListener("popuphiding", this._onConditionalPopupHiding, false);
@ -371,6 +375,25 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
this._hideConditionalPopup();
},
/**
* Pretty print the selected source.
*/
prettyPrint: function() {
const resetEditor = () => {
// Only set the text when the source is still selected.
if (this.selectedValue === source.url) {
DebuggerView.setEditorLocation(source.url, 0, { force: true });
}
};
let { source } = this.selectedItem.attachment;
// Reset the editor even when we fail, so that we can give the user a clue
// as to why the source isn't pretty printed and what happened.
DebuggerController.SourceScripts.prettyPrint(source)
.then(resetEditor,
resetEditor);
},
/**
* Marks a breakpoint as selected in this sources container.
*

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

@ -271,12 +271,16 @@ let DebuggerView = {
*
* @param object aSource
* The source object coming from the active thread.
* @param object aFlags
* Additional options for setting the source. Supported options:
* - force: boolean allowing whether we can get the selected url's
* text again.
* @return object
* A promise that is resolved after the source text has been set.
*/
_setEditorSource: function(aSource) {
_setEditorSource: function(aSource, aFlags={}) {
// Avoid setting the same source text in the editor again.
if (this._editorSource.url == aSource.url) {
if (this._editorSource.url == aSource.url && !aFlags.force) {
return this._editorSource.promise;
}
@ -332,6 +336,8 @@ let DebuggerView = {
* - columnOffset: column offset for the caret or debug location
* - noCaret: don't set the caret location at the specified line
* - noDebug: don't set the debug location at the specified line
* - force: boolean allowing whether we can get the selected url's
* text again.
* @return object
* A promise that is resolved after the source text has been set.
*/
@ -356,7 +362,7 @@ let DebuggerView = {
// Make sure the requested source client is shown in the editor, then
// update the source editor's caret position and debug location.
return this._setEditorSource(sourceForm).then(() => {
return this._setEditorSource(sourceForm, aFlags).then(() => {
// Line numbers in the source editor should start from 1. If invalid
// or not specified, then don't do anything.
if (aLine < 1) {

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

@ -32,6 +32,8 @@
<commandset id="sourceEditorCommands"/>
<commandset id="debuggerCommands">
<command id="prettyPrintCommand"
oncommand="DebuggerView.Sources.prettyPrint()"/>
<command id="unBlackBoxButton"
oncommand="DebuggerView.Sources._onStopBlackBoxing()"/>
<command id="nextSourceCommand"
@ -139,6 +141,9 @@
accesskey="&debuggerUI.focusVariables.key;"
key="variablesFocusKey"
command="variablesFocusCommand"/>
<menuitem id="se-dbg-cMenu-prettyPrint"
label="&debuggerUI.sources.prettyPrint;"
command="prettyPrintCommand"/>
</menupopup>
<menupopup id="debuggerWatchExpressionsContextMenu">
<menuitem id="add-watch-expression"
@ -314,8 +319,18 @@
<scrollbox id="globalsearch" orient="vertical" hidden="true"/>
<splitter class="devtools-horizontal-splitter" hidden="true"/>
<hbox flex="1">
<vbox id="sources-pane">
<vbox id="sources" flex="1"/>
<vbox id="sources-container">
<vbox id="sources-pane" flex="1">
<vbox id="sources" flex="1"/>
</vbox>
<toolbar id="sources-toolbar" class="devtools-toolbar">
<toolbarbutton id="pretty-print"
tooltiptext="&debuggerUI.sources.prettyPrint;"
class="devtools-toolbarbutton devtools-monospace"
command="prettyPrintCommand"
value="{}">
</toolbarbutton>
</toolbar>
</vbox>
<splitter class="devtools-side-splitter"/>
<deck id="editor-deck" flex="1" selectedIndex="0">

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

@ -50,6 +50,10 @@ MOCHITEST_BROWSER_TESTS = \
browser_dbg_pause-exceptions-02.js \
browser_dbg_pause-resume.js \
browser_dbg_pause-warning.js \
browser_dbg_pretty-print-01.js \
browser_dbg_pretty-print-02.js \
browser_dbg_pretty-print-03.js \
browser_dbg_pretty-print-04.js \
browser_dbg_progress-listener-bug.js \
browser_dbg_reload-preferred-script-01.js \
browser_dbg_reload-preferred-script-02.js \
@ -143,6 +147,7 @@ MOCHITEST_BROWSER_PAGES = \
doc_large-array-buffer.html \
doc_minified.html \
doc_pause-exceptions.html \
doc_pretty-print.html \
doc_recursion-stack.html \
doc_script-switching-01.html \
doc_script-switching-02.html \
@ -166,6 +171,7 @@ MOCHITEST_BROWSER_PAGES = \
code_script-switching-01.js \
code_script-switching-02.js \
code_test-editor-mode \
code_ugly.js \
testactors.js \
addon1.xpi \
addon2.xpi \

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

@ -0,0 +1,74 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that clicking the pretty print button prettifies the source.
*/
const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gEditor, gSources;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
gSources = gDebugger.DebuggerView.Sources;
waitForSourceShown(gPanel, "code_ugly.js")
.then(testSourceIsUgly)
.then(() => {
const finished = waitForSourceShown(gPanel, "code_ugly.js");
clickPrettyPrintButton();
return finished;
})
.then(testSourceIsPretty)
.then(testSourceIsStillPretty)
.then(() => closeDebuggerAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
});
});
}
function testSourceIsUgly() {
ok(!gEditor.getText().contains("\n "),
"The source shouldn't be pretty printed yet.");
}
function clickPrettyPrintButton() {
EventUtils.sendMouseEvent({ type: "click" },
gDebugger.document.getElementById("pretty-print"),
gDebugger);
}
function testSourceIsPretty() {
ok(gEditor.getText().contains("\n "),
"The source should be pretty printed.")
}
function testSourceIsStillPretty() {
const deferred = promise.defer();
const { source } = gSources.selectedItem.attachment;
gDebugger.DebuggerController.SourceScripts.getText(source).then(([, text]) => {
ok(text.contains("\n "),
"Subsequent calls to getText return the pretty printed source.");
deferred.resolve();
});
return deferred.promise;
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;
gSources = null;
});

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

@ -0,0 +1,57 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that right clicking and selecting the pretty print context menu
* item prettifies the source.
*/
const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gEditor, gContextMenu;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
gContextMenu = gDebugger.document.getElementById("sourceEditorContextMenu");
waitForSourceShown(gPanel, "code_ugly.js")
.then(() => {
const finished = waitForSourceShown(gPanel, "code_ugly.js");
selectContextMenuItem();
return finished;
})
.then(testSourceIsPretty)
.then(closeDebuggerAndFinish.bind(null, gPanel))
.then(null, aError => {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
});
});
}
function selectContextMenuItem() {
once(gContextMenu, "popupshown").then(() => {
const menuItem = gDebugger.document.getElementById("se-dbg-cMenu-prettyPrint");
menuItem.click();
});
gContextMenu.openPopup(gEditor.editorElement, "overlap", 0, 0, true, false);
}
function testSourceIsPretty() {
ok(gEditor.getText().contains("\n "),
"The source should be pretty printed.")
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;
gContextMenu = null;
});

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

@ -0,0 +1,54 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that we have the correct line selected after pretty printing.
*/
const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html";
let gTab, gDebuggee, gPanel, gDebugger;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
waitForSourceShown(gPanel, "code_ugly.js")
.then(runCodeAndPause)
.then(() => {
const sourceShown = waitForSourceShown(gPanel, "code_ugly.js");
const caretUpdated = waitForCaretUpdated(gPanel, 7);
const finished = promise.all([sourceShown, caretUpdated]);
clickPrettyPrintButton();
return finished;
})
.then(resumeDebuggerThenCloseAndFinish.bind(null, gPanel))
.then(null, aError => {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
});
});
}
function runCodeAndPause() {
const deferred = promise.defer();
once(gDebugger.gThreadClient, "paused").then(deferred.resolve);
// Have to executeSoon so that we don't pause before this function returns.
executeSoon(gDebuggee.foo);
return deferred.promise;
}
function clickPrettyPrintButton() {
EventUtils.sendMouseEvent({ type: "click" },
gDebugger.document.getElementById("pretty-print"),
gDebugger);
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
});

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

@ -0,0 +1,74 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the function searching works with pretty printed sources.
*/
const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gSearchBox;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
waitForSourceShown(gPanel, "code_ugly.js")
.then(testUglySearch)
.then(() => {
const finished = waitForSourceShown(gPanel, "code_ugly.js");
clickPrettyPrintButton();
return finished;
})
.then(testPrettyPrintedSearch)
.then(() => closeDebuggerAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
});
}
function testUglySearch() {
const deferred = promise.defer();
once(gDebugger, "popupshown").then(() => {
ok(isCaretPos(gPanel, 2, 10),
"The bar function's non-pretty-printed location should be shown.");
deferred.resolve();
});
setText(gSearchBox, "@bar");
return deferred.promise;
}
function clickPrettyPrintButton() {
EventUtils.sendMouseEvent({ type: "click" },
gDebugger.document.getElementById("pretty-print"),
gDebugger);
}
function testPrettyPrintedSearch() {
const deferred = promise.defer();
once(gDebugger, "popupshown").then(() => {
ok(isCaretPos(gPanel, 6, 10),
"The bar function's pretty printed location should be shown.");
deferred.resolve();
});
setText(gSearchBox, "@bar");
return deferred.promise;
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gSearchBox = null;
});

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

@ -0,0 +1,3 @@
function foo() { var a=1; var b=2; bar(a, b); }
function bar(c, d) { debugger; }
foo();

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

@ -0,0 +1,6 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8"/>
<title>Debugger Pretty Printing Test Page</title>
</head>
<script src="code_ugly.js"></script>

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

@ -16,6 +16,7 @@ let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
let { Promise: promise } = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
let { BrowserDebuggerProcess } = Cu.import("resource:///modules/devtools/DebuggerProcess.jsm", {});
let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});

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

@ -93,6 +93,16 @@ Parser.prototype = {
this._cache.clear();
},
/**
* Clears the AST for a particular source.
*
* @param String aUrl
* The URL of the source that is being cleared.
*/
clearSource: function P_clearSource(aUrl) {
this._cache.delete(aUrl);
},
_cache: null
};

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

@ -33,6 +33,10 @@
- the button that opens up an options context menu for the debugger UI. -->
<!ENTITY debuggerUI.optsButton.tooltip "Debugger Options">
<!-- LOCALIZATION NOTE (debuggerUI.sources.prettyPrint): This is the tooltip for the
button that pretty prints the selected source. -->
<!ENTITY debuggerUI.sources.prettyPrint "Prettify Source">
<!-- LOCALIZATION NOTE (debuggerUI.pauseExceptions): This is the label for the
- checkbox that toggles pausing on exceptions. -->
<!ENTITY debuggerUI.pauseExceptions "Pause on exceptions">

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

@ -13,10 +13,14 @@
min-width: 50px;
}
#sources-pane + .devtools-side-splitter {
#sources-container + .devtools-side-splitter {
-moz-border-start-color: transparent;
}
#pretty-print {
font-weight: bold;
}
.side-menu-widget-item-checkbox {
-moz-appearance: none;
-moz-margin-end: -6px;

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

@ -15,10 +15,14 @@
min-width: 50px;
}
#sources-pane + .devtools-side-splitter {
#sources-container + .devtools-side-splitter {
-moz-border-start-color: transparent;
}
#pretty-print {
font-weight: bold;
}
.side-menu-widget-item-checkbox {
-moz-appearance: none;
-moz-margin-end: -6px;

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

@ -13,10 +13,14 @@
min-width: 50px;
}
#sources-pane + .devtools-side-splitter {
#sources-container + .devtools-side-splitter {
-moz-border-start-color: transparent;
}
#pretty-print {
font-weight: bold;
}
.side-menu-widget-item-checkbox {
-moz-appearance: none;
-moz-margin-end: -6px;

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

@ -12,6 +12,7 @@ var gSource;
function run_test() {
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-pretty-print");
gDebuggee.noop = x => x;
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient.connect(function() {
attachTestTabAndResume(gClient, "test-pretty-print", function(aResponse, aTabClient, aThreadClient) {
@ -22,9 +23,15 @@ function run_test() {
do_test_pending();
}
const CODE = "" + function main() { debugger; return 10; };
const CODE = "" + function main() { var a = 1; debugger; noop(a); return 10; };
const CODE_URL = "data:text/javascript," + CODE;
const BP_LOCATION = {
url: CODE_URL,
line: 5,
column: 2
};
function evalCode() {
gClient.addOneTimeListener("newSource", prettyPrintSource);
Cu.evalInSandbox(
@ -47,21 +54,41 @@ function runCode({ error }) {
gDebuggee.main();
}
function testDbgStatement(event, { frame }) {
function testDbgStatement(event, { why, frame }) {
do_check_eq(why.type, "debuggerStatement");
const { url, line, column } = frame.where;
do_check_eq(url, CODE_URL);
do_check_eq(line, 2);
do_check_eq(column, 2);
testStepping();
do_check_eq(line, 3);
setBreakpoint();
}
function setBreakpoint() {
gThreadClient.setBreakpoint(BP_LOCATION, ({ error, actualLocation }) => {
do_check_true(!error);
do_check_true(!actualLocation);
testStepping();
});
}
function testStepping() {
gClient.addOneTimeListener("paused", (event, { frame }) => {
const { url, line, column } = frame.where;
gClient.addOneTimeListener("paused", (event, { why, frame }) => {
do_check_eq(why.type, "resumeLimit");
const { url, line } = frame.where;
do_check_eq(url, CODE_URL);
do_check_eq(line, 3);
do_check_eq(column, 2);
finishClient(gClient);
do_check_eq(line, 4);
testHitBreakpoint();
});
gThreadClient.stepIn();
}
function testHitBreakpoint() {
gClient.addOneTimeListener("paused", (event, { why, frame }) => {
do_check_eq(why.type, "breakpoint");
const { url, line, column } = frame.where;
do_check_eq(url, CODE_URL);
do_check_eq(line, BP_LOCATION.line);
do_check_eq(column, BP_LOCATION.column);
finishClient(gClient);
});
gThreadClient.resume();
}