Bug 1060069: Break out Debugger API tutorials into their own pages, and add a Debugger.Memory allocation log tutorial. DONTBUILD r=jorendorff

This commit is contained in:
Jim Blandy 2014-08-28 16:46:27 -07:00
Родитель 62ec63bb40
Коммит 59a118c808
5 изменённых файлов: 316 добавлений и 76 удалений

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

@ -101,85 +101,14 @@ per-`Debugger`, tools can do so without worrying about interfering with
other tools that use their own `Debugger` instances.
## Example
## Examples
You can try out `Debugger` yourself in Firefox's Scratchpad.
Here are some things you can try out yourself that show off some of `Debugger`'s
features:
1) Visit the URL `about:config`, and set the `devtools.chrome.enabled`
preference to `true`:
- [Evaluating an expression in a web page's stack frame when it executes a `debugger;` statement.][tut debugger]
![Setting the 'devtools.chrome.enabled' preference][img-chrome-pref]
2) Save the following HTML text to a file, and visit the file in your
browser:
```language-html
<div onclick="var x = 'snoo'; debugger;">Click me!</div>
```
3) Open a developer Scratchpad (Menu button > Developer > Scratchpad), and
select "Browser" from the "Environment" menu. (This menu will not be
present unless you have changed the preference as explained above.)
![Selecting the 'browser' context in the Scratchpad][img-scratchpad-browser]
4) Enter the following code in the Scratchpad:
```language-js
// This simply defines 'Debugger' in this Scratchpad;
// it doesn't actually start debugging anything.
Cu.import("resource://gre/modules/jsdebugger.jsm");
addDebuggerToGlobal(window);
// Create a 'Debugger' instance.
var dbg = new Debugger;
// Get the current tab's content window, and make it a debuggee.
var w = gBrowser.selectedBrowser.contentWindow.wrappedJSObject;
dbg.addDebuggee(w);
// When the debuggee executes a 'debugger' statement, evaluate
// the expression 'x' in that stack frame, and show its value.
dbg.onDebuggerStatement = function (frame) {
alert('hit debugger statement; x = ' + frame.eval('x').return);
}
```
5) In the Scratchpad, ensure that no text is selected, and press the "Run"
button.
6) Now, click on the text that says "Click me!" in the web page. This runs
the `div` element's `onclick` handler. When control reaches the
`debugger;` statement, `Debugger` calls your callback function, passing
a `Debugger.Frame` instance. Your callback function evaluates the
expression `x` in the given stack frame, and displays the alert:
![The Debugger callback displaying an alert][img-example-alert]
7) Press "Run" in the Scratchpad again. Now, clicking on the "Click me!"
text causes *two* alerts to show---one for each `Debugger`
instance.
Multiple `Debugger` instances can observe the same debuggee. Re-running
the code in the Scratchpad created a fresh `Debugger` instance, added
the same web page as its debuggee, and then registered a fresh
`debugger;` statement handler with the new instance. When you clicked
on the `div` element, both of them ran. This shows how any number of
`Debugger`-based tools can observe a single web page
simultaneously---although, since the order in which their handlers
run is not specified, such tools should probably only observe, and not
influence, the debuggee's behavior.
8) Close the web page and the Scratchpad.
Since both the Scratchpad's global object and the debuggee window are
now gone, the `Debugger` instances will be garbage collected, since
they can no longer have any visible effect on Firefox's behavior. The
`Debugger` API tries to interact with garbage collection as
transparently as possible; for example, if both a `Debugger.Object`
instance and its referent are not reachable, they will both be
collected, even while the `Debugger` instance to which the shadow
belonged continues to exist.
- [Showing how many objects different call paths allocate.][tut alloc log]
## Gecko-specific features

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

@ -0,0 +1,222 @@
Tutorial: Show Allocations Per Call Path
========================================
{{ gecko_minversion_header(\'34\') }}
This page shows how to use the [`Debugger` API][debugger] to show how many
objects a web page allocates, sorted by the function call path that allocated
them.
1) Visit the URL `about:config`, and set the `devtools.chrome.enabled`
preference to `true`:
![Setting the 'devtools.chrome.enabled' preference][img-chrome-pref]
2) Open a developer Scratchpad (Menu button > Developer > Scratchpad), and
select "Browser" from the "Environment" menu. (This menu will not be
present unless you have changed the preference as explained above.)
![Selecting the 'browser' context in the Scratchpad][img-scratchpad-browser]
3) Enter the following code in the Scratchpad:
```language-js
// This simply defines the 'Debugger' constructor in this
// Scratchpad; it doesn't actually start debugging anything.
Components.utils.import('resource://gre/modules/jsdebugger.jsm');
addDebuggerToGlobal(window);
(function () {
// The debugger we'll use to observe a tab's allocation.
var dbg;
// Start measuring the selected tab's main window's memory
// consumption. This function is available in the browser
// console.
window.demoTrackAllocations = function() {
dbg = new Debugger;
// This makes hacking on the demo *much* more
// pleasant.
dbg.uncaughtExceptionHook = handleUncaughtException;
// Find the current tab's main content window.
var w = gBrowser.selectedBrowser.contentWindow;
console.log("Tracking allocations in page: " +
w.location.href);
// Make that window a debuggee of our Debugger.
dbg.addDebuggee(w.wrappedJSObject);
// Enable allocation tracking in dbg's debuggees.
dbg.memory.trackingAllocationSites = true;
}
window.demoPlotAllocations = function() {
// Grab the allocation log.
var log = dbg.memory.drainAllocationsLog();
// Neutralize the Debugger, and drop it on the floor
// for the GC to collect.
console.log("Stopping allocation tracking.");
dbg.removeAllDebuggees();
dbg = undefined;
// Analyze and display the allocation log.
plot(log);
}
function handleUncaughtException(ex) {
console.log('Debugger hook threw:');
console.log(ex.toString());
console.log('Stack:');
console.log(ex.stack);
};
function plot(log) {
// Given the log, compute a map from allocation sites to
// allocation counts. Note that stack entries are '===' if
// they represent the same site with the same callers.
var counts = new Map;
for (let site of log) {
// This is a kludge, necessary for now. The saved stacks
// are new, and Firefox doesn't yet understand that they
// are safe for chrome code to use, so we must tell it
// so explicitly.
site = Components.utils.waiveXrays(site);
if (!counts.has(site))
counts.set(site, 0);
counts.set(site, counts.get(site) + 1);
}
// Walk from each site that allocated something up to the
// root, computing allocation totals that include
// children. Remember that 'null' is a valid site,
// representing the root.
var totals = new Map;
for (let [site, count] of counts) {
for(;;) {
if (!totals.has(site))
totals.set(site, 0);
totals.set(site, totals.get(site) + count);
if (!site)
break;
site = site.parent;
}
}
// Compute parent-to-child links, since saved stack frames
// have only parent links.
var rootChildren = new Map;
function childMapFor(site) {
if (!site)
return rootChildren;
let parentMap = childMapFor(site.parent);
if (parentMap.has(site))
return parentMap.get(site);
var m = new Map;
parentMap.set(site, m);
return m;
}
for (let [site, total] of totals) {
childMapFor(site);
}
// Print the allocation count for |site|. Print
// |children|'s entries as |site|'s child nodes. Indent
// the whole thing by |indent|.
function walk(site, children, indent) {
var name, place;
if (site) {
name = site.functionDisplayName;
place = ' ' + site.source + ':' + site.line + ':' + site.column;
} else {
name = '(root)';
place = '';
}
console.log(indent + totals.get(site) + ': ' + name + place);
for (let [child, grandchildren] of children)
walk(child, grandchildren, indent + ' ');
}
walk(null, rootChildren, '');
}
})();
```
4) In the Scratchpad, ensure that no text is selected, and press the "Run"
button. (If you get an error complaining that `Components.utils` is not
defined, be sure you've selected `Browser` from the scratchpad's
`Environment` menu, as described in step 2.)
5) Save the following HTML text to a file, and visit the file in your browser.
Make sure the current browser tab is displaying this page.
```language-html
<div onclick="doDivsAndSpans()">
Click here to make the page do some allocations.
</div>
<script>
function makeFactory(type) {
return function factory(content) {
var elt = document.createElement(type);
elt.textContent = content;
return elt;
};
}
var divFactory = makeFactory('div');
var spanFactory = makeFactory('span');
function divsAndSpans() {
for (i = 0; i < 10; i++) {
var div = divFactory('div #' + i);
div.appendChild(spanFactory('span #' + i));
document.body.appendChild(div);
}
}
function doDivsAndSpans() { divsAndSpans(); }
</script>
```
6) Open the browser console (Menu Button > Developer > Browser Console), and
then evaluate the expression `demoTrackAllocations()` in the browser
console. This begins logging allocations in the current browser tab.
7) In the browser tab, click on the text that says "Click here...". The event
handler should add some text to the end of the page.
8) Back in the browser console, evaluate the expression
`demoPlotAllocations()`. This stops logging allocations, and displays a tree
of allocations:
![An allocation plot, displayed in the console][img-alloc-plot]
The numbers at the left edge of each line show the total number of objects
allocated at that site or at sites called from there. After the count, we
see the function name, and the source code location of the call site or
allocation.
The `(root)` node's count includes objects allocated in the content page by
the web browser, like DOM events. Indeed, this display shows that
`popup.xml` and `content.js`, which are internal components of Firefox,
allocated more objects in the page's compartment than the page itself. (We
will probably revise the allocation log to present such allocations in a way
that is more informative, and that exposes less of Firefox's internal
structure.)
As expected, the `onclick` handler is responsible for all allocation done by
the page's own code. (The line number for the onclick handler is `1`,
indicating that the allocating call is located on line one of the handler
text itself. We will probably change this to be the line number within
`page.html`, not the line number within the handler code.)
The `onclick` handler calls `doDivsAndSpans`, which calls `divsAndSpans`,
which invokes closures of `factory` to do all the actual allocation. (It is
unclear why `spanFactory` allocated thirteen objects, despite being called
only ten times.)

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

@ -0,0 +1,82 @@
Tutorial: Evaluate An Expression When a debugger; Statement is Executed
=======================================================================
This page shows how you can try out the [`Debugger` API][debugger] yourself
using Firefox's Scratchpad. We use the API to evaluate an expression in the web
page whenever it executes a JavaScript `debugger;` statement.
1) Visit the URL `about:config`, and set the `devtools.chrome.enabled`
preference to `true`:
![Setting the 'devtools.chrome.enabled' preference][img-chrome-pref]
2) Save the following HTML text to a file, and visit the file in your
browser:
```language-html
<div onclick="var x = 'snoo'; debugger;">Click me!</div>
```
3) Open a developer Scratchpad (Menu button > Developer > Scratchpad), and
select "Browser" from the "Environment" menu. (This menu will not be
present unless you have changed the preference as explained above.)
![Selecting the 'browser' context in the Scratchpad][img-scratchpad-browser]
4) Enter the following code in the Scratchpad:
```language-js
// This simply defines 'Debugger' in this Scratchpad;
// it doesn't actually start debugging anything.
Components.utils.import("resource://gre/modules/jsdebugger.jsm");
addDebuggerToGlobal(window);
// Create a 'Debugger' instance.
var dbg = new Debugger;
// Get the current tab's content window, and make it a debuggee.
var w = gBrowser.selectedBrowser.contentWindow.wrappedJSObject;
dbg.addDebuggee(w);
// When the debuggee executes a 'debugger' statement, evaluate
// the expression 'x' in that stack frame, and show its value.
dbg.onDebuggerStatement = function (frame) {
alert('hit debugger statement; x = ' + frame.eval('x').return);
}
```
5) In the Scratchpad, ensure that no text is selected, and press the "Run"
button.
6) Now, click on the text that says "Click me!" in the web page. This runs
the `div` element's `onclick` handler. When control reaches the
`debugger;` statement, `Debugger` calls your callback function, passing
a `Debugger.Frame` instance. Your callback function evaluates the
expression `x` in the given stack frame, and displays the alert:
![The Debugger callback displaying an alert][img-example-alert]
7) Press "Run" in the Scratchpad again. Now, clicking on the "Click me!"
text causes *two* alerts to show---one for each `Debugger`
instance.
Multiple `Debugger` instances can observe the same debuggee. Re-running
the code in the Scratchpad created a fresh `Debugger` instance, added
the same web page as its debuggee, and then registered a fresh
`debugger;` statement handler with the new instance. When you clicked
on the `div` element, both of them ran. This shows how any number of
`Debugger`-based tools can observe a single web page
simultaneously---although, since the order in which their handlers
run is not specified, such tools should probably only observe, and not
influence, the debuggee's behavior.
8) Close the web page and the Scratchpad.
Since both the Scratchpad's global object and the debuggee window are
now gone, the `Debugger` instances will be garbage collected, since
they can no longer have any visible effect on Firefox's behavior. The
`Debugger` API tries to interact with garbage collection as
transparently as possible; for example, if both a `Debugger.Object`
instance and its referent are not reachable, they will both be
collected, even while the `Debugger` instance to which the shadow
belonged continues to exist.

Двоичные данные
js/src/doc/Debugger/alloc-plot-console.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 80 KiB

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

@ -44,12 +44,19 @@ markdown Debugger.Memory.md Debugger-API/Debugger.Memory
label 'max-alloc-log' '#max-alloc-log' "Debugger.Memory: maxAllocationsLogLength"
label 'take-census' '#take-census' "Debugger.Memory: takeCensus"
markdown Tutorial-Debugger-Statement.md Debugger-API/Tutorial-Debugger-Statement
label 'tut debugger' "Tutorial: the debugger; statement"
markdown Tutorial-Alloc-Log-Tree.md Debugger-API/Tutorial-Allocation-Log-Tree
label 'tut alloc log' "Tutorial: the allocation log"
# Images:
RBASE=https://mdn.mozillademos.org/files
resource 'img-shadows' shadows.svg $RBASE/7225/shadows.svg
resource 'img-chrome-pref' enable-chrome-devtools.png $RBASE/7233/enable-chrome-devtools.png
resource 'img-scratchpad-browser' scratchpad-browser-environment.png $RBASE/7229/scratchpad-browser-environment.png
resource 'img-example-alert' debugger-alert.png $RBASE/7231/debugger-alert.png
resource 'img-alloc-plot' alloc-plot-console.png $RBASE/8461/alloc-plot-console.png
# External links:
absolute-label 'protocol' https://wiki.mozilla.org/Remote_Debugging_Protocol "Remote Debugging Protocol"