зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
62ec63bb40
Коммит
59a118c808
|
@ -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.
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 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"
|
||||
|
|
Загрузка…
Ссылка в новой задаче