1 // Copyright (c) 2008-2014 Kris Maglione <maglione.k at Gmail>
3 // This work is licensed for reuse under an MIT license. Details are
4 // given in the LICENSE.txt file included with this file.
7 let { getOwnPropertyNames } = Object;
11 defineModule("javascript", {
12 exports: ["JavaScript", "javascript"],
16 lazyRequire("template", ["template"]);
18 let isPrototypeOf = Object.prototype.isPrototypeOf;
20 // TODO: Clean this up.
22 var JavaScript = Module("javascript", {
26 this._top = {}; // The element on the top of the stack.
27 this._last = ""; // The last opening char pushed onto the stack.
28 this._lastNonwhite = ""; // Last non-whitespace character we saw.
29 this._lastChar = ""; // Last character we saw, used for \ escaping quotes.
34 this._cacheKey = null;
36 this._nullSandbox = Cu.Sandbox("about:blank");
39 Local: function (dactyl, modules, window) ({
40 init: function init() {
41 this.modules = modules;
48 globals: Class.Memoize(function () [
49 [this.modules.userContext, /*L*/"Global Variables"],
50 [this.modules, "modules"],
51 [this.window, "window"]
54 toplevel: Class.Memoize(function () this.modules.jsmodules),
58 newContext: function () this.modules.newContext(this.modules.userContext, false,
59 "Dactyl JS Temp Context"),
61 completers: Class.Memoize(() => Object.create(JavaScript.completers)),
63 // Some object members are only accessible as function calls
64 getKey: function (obj, key) {
72 iter: function iter_(obj, toplevel) {
76 let seen = RealSet(isinstance(obj, ["Sandbox"]) ? JavaScript.magicalNames : []);
77 let globals = values(toplevel && this.window === obj ? this.globalNames : []);
79 if (toplevel && isObject(obj) && "wrappedJSObject" in obj)
80 if (!seen.add("wrappedJSObject"))
81 yield "wrappedJSObject";
83 for (let key in iter(globals, properties(obj, !toplevel)))
87 // Properties aren't visible in an XPCNativeWrapper until
89 for (let key in properties(this.getKey(obj, "wrappedJSObject"),
92 if (key in obj && !seen.has(key))
98 objectKeys: function objectKeys(obj, toplevel) {
99 // Things we can dereference
100 if (!obj || ["object", "string", "function"].indexOf(typeof obj) === -1)
102 if (isinstance(obj, ["Sandbox"]) && !toplevel) // Temporary hack.
104 if (isPrototypeOf.call(this.toplevel, obj) && !toplevel)
107 let completions = [k for (k in this.iter(obj, toplevel))];
108 if (obj === this.modules) // Hack.
109 completions = array.uniq(completions.concat([k for (k in this.iter(this.modules.jsmodules, toplevel))]));
113 evalled: function evalled(arg, key, tmp) {
114 let cache = this.context.cache.evalled;
115 let context = this.context.cache.evalContext;
122 context[JavaScript.EVAL_TMP] = tmp;
124 cache[key] = this.modules.dactyl.userEval(arg, context,
125 /*L*/"[Command Line Completion]", 1);
131 this.context.message = _("error.error", e);
135 delete context[JavaScript.EVAL_TMP];
139 // Get an element from the stack. If @frame is negative,
140 // count from the top of the stack, otherwise, the bottom.
141 // If @nth is provided, return the @mth value of element @type
142 // of the stack entry at @frame.
143 _get: function (frame, nth, type) {
144 let a = this._stack[frame >= 0 ? frame : this._stack.length + frame];
149 return a[a.length - nth - 1];
152 // Push and pop the stack, maintaining references to 'top' and 'last'.
153 _push: function push(arg) {
157 statements: [this._i],
163 this._last = this._top.char;
164 this._stack.push(this._top);
167 _pop: function pop(arg) {
168 if (this._i == this.context.caret - 1)
169 this.context.highlight(this._top.offset, 1, "FIND");
171 if (this._top.char != arg) {
172 this.context.highlight(this._top.offset, this._i - this._top.offset, "SPELLCHECK");
173 throw Error(/*L*/"Invalid JS");
176 // The closing character of this stack frame will have pushed a new
177 // statement, leaving us with an empty statement. This doesn't matter,
178 // now, as we simply throw away the frame when we pop it, but it may later.
179 if (this._top.statements[this._top.statements.length - 1] == this._i)
180 this._top.statements.pop();
181 this._top = this._get(-2);
182 this._last = this._top.char;
183 return this._stack.pop();
186 _buildStack: function (filter) {
187 // Todo: Fix these one-letter variable names.
189 this._c = ""; // Current index and character, respectively.
191 // Reuse the old stack.
192 if (this._str && filter.substr(0, this._str.length) == this._str) {
193 this.context.highlight(0, 0, "FIND");
194 this._i = this._str.length;
195 if (this.popStatement)
196 this._top.statements.pop();
199 this.context.highlight();
201 this._functions = [];
205 // Build a parse stack, discarding entries as opening characters
206 // match closing characters. The stack is walked from the top entry
207 // and down as many levels as it takes us to figure out what it is
208 // that we're completing.
210 let length = this._str.length;
211 for (; this._i < length; this._lastChar = this._c, this._i++) {
212 this._c = this._str[this._i];
213 if (/['"\/]/.test(this._last)) {
214 if (this._lastChar == "\\") { // Escape. Skip the next char, whatever it may be.
218 else if (this._c == this._last)
222 // A word character following a non-word character, or simply a non-word
223 // character. Start a new statement.
224 if (/[a-zA-Z_$]/.test(this._c) && !/[\w$]/.test(this._lastChar) || !/[\w\s$]/.test(this._c))
225 this._top.statements.push(this._i);
227 // A "." or a "[" dereferences the last "statement" and effectively
228 // joins it to this logical statement.
229 if ((this._c == "." || this._c == "[") && /[\w$\])"']/.test(this._lastNonwhite)
230 || this._lastNonwhite == "." && /[a-zA-Z_$]/.test(this._c))
231 this._top.statements.pop();
235 // Function call, or if/while/for/...
236 if (/[\w$]/.test(this._lastNonwhite)) {
237 this._functions.push(this._i);
238 this._top.functions.push(this._i);
239 this._top.statements.pop();
249 this._top.dots.push(this._i);
251 case ")": this._pop("("); break;
252 case "]": this._pop("["); break;
253 case "}": this._pop("{"); // Fallthrough
255 this._top.fullStatements.push(this._i);
258 this._top.comma.push(this._i);
262 if (/\S/.test(this._c))
263 this._lastNonwhite = this._c;
267 this.popStatement = false;
268 if (!/[\w$]/.test(this._lastChar) && this._lastNonwhite != ".") {
269 this.popStatement = true;
270 this._top.statements.push(this._i);
273 this._lastIdx = this._i;
276 // Don't eval any function calls unless the user presses tab.
277 _checkFunction: function (start, end, key) {
278 let res = this._functions.some(idx => (idx >= start && idx < end));
279 if (!res || this.context.tabPressed || key in this.cache.evalled)
281 this.context.waitingForTab = true;
285 // For each DOT in a statement, prefix it with TMP, eval it,
286 // and save the result back to TMP. The point of this is to
287 // cache the entire path through an object chain, mainly in
288 // the presence of function calls. There are drawbacks. For
289 // instance, if the value of a variable changes in the course
290 // of inputting a command (let foo=bar; frob(foo); foo=foo.bar; ...),
291 // we'll still use the old value. But, it's worth it.
292 _getObj: function (frame, stop) {
293 let statement = this._get(frame, 0, "statements") || 0; // Current statement.
294 let prev = statement;
295 let obj = this.window;
297 for (let [, dot] in Iterator(this._get(frame).dots.concat(stop))) {
300 if (dot > stop || dot <= prev)
303 let s = this._str.substring(prev, dot);
304 if (prev != statement)
305 s = JavaScript.EVAL_TMP + "." + s;
306 cacheKey = this._str.substring(statement, dot);
308 if (this._checkFunction(prev, dot, cacheKey))
310 if (prev != statement && obj == null) {
311 this.context.message = /*L*/"Error: " + cacheKey.quote() + " is " + String(obj);
316 obj = this.evalled(s, cacheKey, obj);
318 return [[obj, cacheKey]];
321 _getObjKey: function (frame) {
322 let dot = this._get(frame, 0, "dots") || -1; // Last dot in frame.
323 let statement = this._get(frame, 0, "statements") || 0; // Current statement.
324 let end = (frame == -1 ? this._lastIdx : this._get(frame + 1).offset);
326 this._cacheKey = null;
327 let obj = [[this.cache.evalContext, /*L*/"Local Variables"]].concat(this.globals);
328 // Is this an object dereference?
329 if (dot < statement) // No.
331 else // Yes. Set the object to the string before the dot.
332 obj = this._getObj(frame, dot);
334 let [, space, key] = this._str.substring(dot + 1, end).match(/^(\s*)(.*)/);
335 return [dot + 1 + space.length, obj, key];
338 _complete: function (objects, key, compl, string, last) {
341 let base = this.context.fork("js", this._top.offset);
342 base.forceAnchored = true;
343 base.filter = last == null ? key : string;
344 let prefix = last != null ? key : "";
346 if (last == null) // We're not looking for a quoted string, so filter out anything that's not a valid identifier
347 base.filters.push(item => /^[a-zA-Z_$][\w$]*$/.test(item.text));
349 base.quote = [last, text => util.escapeString(text, ""), last];
351 base.filters.push(item => item.item.startsWith(prefix));
355 base.process[1] = function highlight(item, v)
356 template.highlight(typeof v == "xml" ? new String(v.toXMLString()) : v, true, 200);
358 // Sort in a logical fashion for object keys:
359 // Numbers are sorted as numbers, rather than strings, and appear first.
360 // Constants are unsorted, and appear before other non-null strings.
361 // Other strings are sorted in the default manner.
363 let isnan = function isnan(item) item != '' && isNaN(item);
364 let compare = base.compare;
366 base.compare = function (a, b) {
367 if (!isnan(a.key) && !isnan(b.key))
368 return a.key - b.key;
369 return isnan(b.key) - isnan(a.key) || compare(a, b);
373 text: prefix ? text => text.substr(prefix.length)
375 description: function (item) self.getKey(this.obj, item),
376 key: function (item) {
378 return parseInt(key);
379 if (/^[A-Z_][A-Z0-9_]*$/.test(key))
386 // We've already listed anchored matches, so don't list them again here.
387 function unanchored(item) util.compareIgnoreCase(item.text.substr(0, this.filter.length), this.filter);
389 objects.forEach(function (obj) {
390 let context = base.fork(obj[1]);
391 context.title = [obj[1]];
392 context.keys.obj = () => obj[0];
393 context.key = obj[1] + last;
394 if (obj[0] == this.cache.evalContext)
395 context.regenerate = true;
397 obj.ctxt_t = context.fork("toplevel");
399 obj.ctxt_p = context.fork("prototypes");
400 obj.ctxt_t.generate = () => self.objectKeys(obj[0], true);
401 obj.ctxt_p.generate = () => self.objectKeys(obj[0], false);
405 // TODO: Make this a generic completion helper function.
406 objects.forEach(function (obj) {
407 obj.ctxt_t.split(obj[1] + "/anchored", this, function (context) {
408 context.anchored = true;
410 compl(context, obj[0]);
417 objects.forEach(function (obj) {
418 obj.ctxt_p.split(obj[1] + "/anchored", this, function (context) {
419 context.anchored = true;
420 context.title[0] += /*L*/" (prototypes)";
424 objects.forEach(function (obj) {
425 obj.ctxt_t.split(obj[1] + "/unanchored", this, function (context) {
426 context.anchored = false;
427 context.title[0] += /*L*/" (substrings)";
428 context.filters.push(unanchored);
432 objects.forEach(function (obj) {
433 obj.ctxt_p.split(obj[1] + "/unanchored", this, function (context) {
434 context.anchored = false;
435 context.title[0] += /*L*/" (prototype substrings)";
436 context.filters.push(unanchored);
441 _getKey: function () {
442 if (this._last == "")
444 // After the opening [ upto the opening ", plus '' to take care of any operators before it
445 let key = this._str.substring(this._get(-2, null, "offset") + 1, this._get(-1, null, "offset")) + "''";
446 // Now eval the key, to process any referenced variables.
447 return this.evalled(key);
450 get cache() this.context.cache,
452 complete: function _complete(context) {
454 this.context = context;
457 this._buildStack.call(this, context.filter);
461 util.assert(!e.message, e.message);
465 this.context.getCache("evalled", Object);
466 this.context.getCache("evalContext", this.bound.newContext);
468 // Okay, have parse stack. Figure out what we're completing.
470 // Find any complete statements that we can eval before we eval our object.
471 // This allows for things like:
472 // let doc = content.document; let elem = doc.createEle<Tab> ...
474 for (let [, v] in Iterator(this._get(0).fullStatements)) {
475 let key = this._str.substring(prev, v + 1);
476 if (this._checkFunction(prev, v, key))
482 // If this is a function argument, try to get the function's
483 // prototype and show it.
485 let i = (this._get(-2) && this._get(-2).char == "(") ? -2 : -1;
486 if (this._get(i).char == "(") {
487 let [offset, obj, funcName] = this._getObjKey(i - 1);
489 let func = obj[0][0][funcName];
490 if (callable(func)) {
491 let [, prefix, args] = /^(function .*?)\((.*?)\)/.exec(Function.prototype.toString.call(func));
492 let n = this._get(i).comma.length;
493 args = template.map(Iterator(args.split(", ")),
494 ([i, arg]) => ["span", { highlight: i == n ? "Filter" : "" }, arg],
496 this.context.message = ["", prefix + "(", args, ")"];
503 // In a string. Check if we're dereferencing an object or
504 // completing a function argument. Otherwise, do nothing.
505 if (this._last == "'" || this._last == '"') {
507 // str = "foo[bar + 'baz"
511 // The top of the stack is the sting we're completing.
512 // Wrap it in its delimiters and eval it to process escape sequences.
513 let string = this._str.substring(this._get(-1).offset + 1, this._lastIdx).replace(/((?:\\\\)*)\\/, "$1");
514 string = Cu.evalInSandbox(this._last + string + this._last, this._nullSandbox);
516 // Is this an object accessor?
517 if (this._get(-2).char == "[") { // Are we inside of []?
521 // [-3]: base statement
523 // Yes. If the [ starts at the beginning of a logical
524 // statement, we're in an array literal, and we're done.
525 if (this._get(-3, 0, "statements") == this._get(-2).offset)
528 // Beginning of the statement upto the opening [
529 let obj = this._getObj(-3, this._get(-2).offset);
531 return this._complete(obj, this._getKey(), null, string, this._last);
534 // Is this a function call?
535 if (this._get(-2).char == "(") {
539 // [-3]: base statement
541 // Does the opening "(" mark a function call?
542 if (this._get(-3, 0, "functions") != this._get(-2).offset)
543 return null; // No. We're done.
545 let [offset, obj, funcName] = this._getObjKey(-3);
548 obj = obj.slice(0, 1);
551 let func = obj[0][0][funcName];
552 var completer = func.dactylCompleter;
556 completer = this.completers[funcName];
560 // Split up the arguments
561 let prev = this._get(-2).offset;
563 for (let [i, idx] in Iterator(this._get(-2).comma)) {
564 let arg = this._str.substring(prev + 1, idx);
566 memoize(args, i, () => self.evalled(arg));
568 let key = this._getKey();
569 args.push(key + string);
571 let compl = function (context, obj) {
572 let res = completer.call(self, context, funcName, obj, args);
574 context.completions = res;
577 obj[0][1] += "." + funcName + "(... [" + args.length + "]";
578 return this._complete(obj, key, compl, string, this._last);
581 // In a string that's not an obj key or a function arg.
586 // str = "foo.bar.baz"
591 // obj = [modules, window]
594 let [offset, obj, key] = this._getObjKey(-1);
596 // Wait for a keypress before completing when there's no key
597 if (!this.context.tabPressed && key == "" && obj.length > 1) {
598 let message = this.context.message || "";
599 this.context.waitingForTab = true;
600 this.context.message = ["", message, "\n",
601 _("completion.waitingForKeyPress")];
605 if (!/^(?:[a-zA-Z_$][\w$]*)?$/.test(key))
606 return null; // Not a word. Forget it. Can this even happen?
609 var o = this._top.offset;
610 this._top.offset = offset;
611 return this._complete(obj, key);
614 this._top.offset = o;
619 magicalNames: Class.Memoize(function () Object.getOwnPropertyNames(Cu.Sandbox(this.window), true).sort()),
622 * A list of properties of the global object which are not
623 * enumerable by any standard method.
625 globalNames: Class.Memoize(function () let (self = this) array.uniq([
626 "Array", "ArrayBuffer", "AttributeName", "Audio", "Boolean", "Components",
627 "CSSFontFaceStyleDecl", "CSSGroupRuleRuleList", "CSSNameSpaceRule",
628 "CSSRGBColor", "CSSRect", "ComputedCSSStyleDeclaration", "Date", "Error",
629 "EvalError", "File", "Float32Array", "Float64Array", "Function",
630 "HTMLDelElement", "HTMLInsElement", "HTMLSpanElement", "Infinity",
631 "InnerModalContentWindow", "InnerWindow", "Int16Array", "Int32Array",
632 "Int8Array", "InternalError", "Iterator", "JSON", "KeyboardEvent",
633 "Math", "NaN", "Namespace", "Number", "Object", "Proxy", "QName",
634 "ROCSSPrimitiveValue", "RangeError", "ReferenceError", "RegExp",
635 "StopIteration", "String", "SyntaxError", "TypeError", "URIError",
636 "Uint16Array", "Uint32Array", "Uint8Array", "XML", "XMLHttpProgressEvent",
637 "XMLList", "XMLSerializer", "XPCNativeWrapper",
638 "XULControllers", "constructor", "decodeURI", "decodeURIComponent",
639 "encodeURI", "encodeURIComponent", "escape", "eval", "isFinite", "isNaN",
640 "isXMLName", "parseFloat", "parseInt", "undefined", "unescape", "uneval"
641 ].concat([k.substr(6) for (k in keys(Ci)) if (/^nsIDOM/.test(k))])
642 .concat([k.substr(3) for (k in keys(Ci)) if (/^nsI/.test(k))])
643 .concat(this.magicalNames)
644 .filter(k => k in self.window))),
647 EVAL_TMP: "__dactyl_eval_tmp",
650 * A map of argument completion functions for named methods. The
651 * signature and specification of the completion function
652 * are fairly complex and yet undocumented.
654 * @see JavaScript.setCompleter
659 * Installs argument string completers for a set of functions.
660 * The second argument is an array of functions (or null
661 * values), each corresponding the argument of the same index.
662 * Each provided completion function receives as arguments a
663 * CompletionContext, the 'this' object of the method, and an
664 * array of values for the preceding arguments.
666 * It is important to note that values in the arguments array
667 * provided to the completers are lazily evaluated the first
668 * time they are accessed, so they should be accessed
671 * @param {function|[function]} funcs The functions for which to
672 * install the completers.
673 * @param {[function]} completers An array of completer
676 setCompleter: function (funcs, completers) {
677 funcs = Array.concat(funcs);
678 for (let [, func] in Iterator(funcs)) {
679 func.dactylCompleter = function (context, func, obj, args) {
680 let completer = completers[args.length - 1];
683 return completer.call(obj, context, obj, args);
689 init: function init(dactyl, modules, window) {
690 init.superapply(this, arguments);
691 modules.JavaScript = Class("JavaScript", JavaScript, { modules: modules, window: window });
693 completion: function (dactyl, modules, window) {
694 const { completion } = modules;
695 update(modules.completion, {
696 get javascript() modules.javascript.bound.complete,
697 javascriptCompleter: JavaScript // Backwards compatibility
700 modes: function initModes(dactyl, modules, window) {
701 initModes.require("commandline");
702 const { modes } = modules;
704 modes.addMode("REPL", {
705 description: "JavaScript Read Eval Print Loop",
706 bases: [modes.COMMAND_LINE],
707 displayName: Class.Memoize(function () this.name)
710 commandline: function initCommandLine(dactyl, modules, window) {
711 const { Buffer, modes } = modules;
713 var REPL = Class("REPL", {
714 init: function init(context) {
715 this.context = context;
719 addOutput: function addOutput(js) {
723 var result = dactyl.userEval(js, this.context);
724 var xml = result === undefined ? "" : util.objectToString(result, true);
731 e = util.fixURI(e.fileName) + ":" + e.lineNumber + ": " + e;
732 xml = ["span", { highlight: "ErrorMsg" }, e];
735 let prompt = "js" + this.count;
736 Class.replaceProperty(this.context, prompt, result);
739 this.rootNode.appendChild(
741 [["div", { highlight: "REPL-E", key: "e" },
742 ["span", { highlight: "REPL-R" },
743 prompt, ">"], " ", js],
744 ["div", { highlight: "REPL-P", key: "p" },
746 this.document, nodes));
748 this.rootNode.scrollTop += nodes.e.getBoundingClientRect().top
749 - this.rootNode.getBoundingClientRect().top;
754 message: Class.Memoize(function () {
755 DOM.fromJSON(["div", { highlight: "REPL", key: "rootNode" }],
756 this.document, this);
758 return this.rootNode;
761 __noSuchMethod__: function (meth, args) Buffer[meth].apply(Buffer, [this.rootNode].concat(args))
764 modules.CommandREPLMode = Class("CommandREPLMode", modules.CommandMode, {
765 init: function init(context) {
766 init.supercall(this);
768 let sandbox = true || isinstance(context, ["Sandbox"]);
770 this.context = modules.newContext(context, !sandbox, "Dactyl REPL Context");
771 this.js = modules.JavaScript();
772 this.js.replContext = this.context;
773 this.js.newContext = () => modules.newContext(this.context, !sandbox, "Dactyl REPL Temp Context");
776 [this.context, /*L*/"REPL Variables"],
777 [context, /*L*/"REPL Global"]
778 ].concat(this.js.globals.filter(([global]) => isPrototypeOf.call(global, context)));
780 if (!isPrototypeOf.call(modules.jsmodules, context))
781 this.js.toplevel = context;
783 if (!isPrototypeOf.call(window, context))
784 this.js.window = context;
786 if (this.js.globals.slice(2).some(([global]) => global === context))
787 this.js.globals.splice(1);
789 this.repl = REPL(this.context);
792 open: function open(context) {
794 modules.mow.echo(this.repl);
795 this.widgets.message = null;
797 open.superapply(this, arguments);
801 complete: function complete(context) {
802 context.fork("js", 0, this.js, "complete");
805 historyKey: "javascript",
809 get completionList() this.widgets.statusbar.commandline.id,
811 accept: function accept() {
812 dactyl.trapErrors(function () { this.repl.addOutput(this.command); }, this);
814 this.completions.cleanup();
816 this.history.reset();
819 modules.mow.resize();
822 leave: function leave(params) {
823 leave.superapply(this, arguments);
825 modes.delay(function () { modes.pop(); });
826 Cu.nukeSandbox(this.context);
830 updatePrompt: function updatePrompt() {
832 this.prompt = ["REPL-R", "js" + (this.repl.count + 1) + "> "];
836 commands: function initCommands(dactyl, modules, window) {
837 const { commands } = modules;
839 commands.add(["javas[cript]", "js"],
840 "Evaluate a JavaScript string",
842 if (args[0] && !args.bang)
843 dactyl.userEval(args[0]);
846 modules.CommandREPLMode(args[0] ? dactyl.userEval(args[0]) : modules.userContext)
852 completer: function (context) modules.completion.javascript(context),
857 mappings: function initMappings(dactyl, modules, window) {
858 const { mappings, modes } = modules;
860 function bind(...args) mappings.add.apply(mappings, [[modes.REPL]].concat(args))
862 bind(["<Return>"], "Accept the current input",
863 function ({ self }) { self.accept(); });
865 bind(["<C-e>"], "Scroll down one line",
866 function ({ self }) { self.repl.scrollVertical("lines", 1); });
868 bind(["<C-y>"], "Scroll up one line",
869 function ({ self }) { self.repl.scrollVertical("lines", -1); });
871 bind(["<C-d>"], "Scroll down half a page",
872 function ({ self }) { self.repl.scrollVertical("pages", .5); });
874 bind(["<C-f>", "<PageDown>"], "Scroll down one page",
875 function ({ self }) { self.repl.scrollVertical("pages", 1); });
877 bind(["<C-u>"], "Scroll up half a page",
878 function ({ self }) { self.repl.scrollVertical("pages", -.5); });
880 bind(["<C-b>", "<PageUp>"], "Scroll up half a page",
881 function ({ self }) { self.repl.scrollVertical("pages", -1); });
883 options: function initOptions(dactyl, modules, window) {
884 modules.options.add(["jsdebugger", "jsd"],
885 "Enable the JavaScript debugger service for use in JavaScript completion",
887 setter: function (value) {
888 if (services.debugger.isOn != value)
890 (services.debugger.asyncOn || services.debugger.on)(null);
892 services.debugger.off();
894 getter: function () services.debugger.isOn
901 } catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
903 // vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: