From b6f52dbe1b0093d3829ddbbf02f5d54cd3f11ecd Mon Sep 17 00:00:00 2001 From: Joe Walker Date: Fri, 1 Jun 2012 08:24:19 +0100 Subject: [PATCH] Bug 755388 - "hud is null" when using the edit gcli command; r=dcamp --- browser/devtools/commandline/GcliCommands.jsm | 5 +- browser/devtools/commandline/gcli.jsm | 2 +- browser/devtools/commandline/test/Makefile.in | 5 + .../commandline/test/browser_gcli_edit.js | 145 +++++++++ .../commandline/test/browser_gcli_pref.js | 4 +- browser/devtools/commandline/test/head.js | 285 ++++++++++++++---- .../devtools/commandline/test/resources.html | 50 +++ .../commandline/test/resources_inpage.js | 12 + .../commandline/test/resources_inpage1.css | 11 + .../commandline/test/resources_inpage2.css | 11 + 10 files changed, 470 insertions(+), 60 deletions(-) create mode 100644 browser/devtools/commandline/test/browser_gcli_edit.js mode change 100755 => 100644 browser/devtools/commandline/test/browser_gcli_pref.js create mode 100644 browser/devtools/commandline/test/resources.html create mode 100644 browser/devtools/commandline/test/resources_inpage.js create mode 100644 browser/devtools/commandline/test/resources_inpage1.css create mode 100644 browser/devtools/commandline/test/resources_inpage2.css diff --git a/browser/devtools/commandline/GcliCommands.jsm b/browser/devtools/commandline/GcliCommands.jsm index f38cd3601d04..b61e3fa0a13a 100644 --- a/browser/devtools/commandline/GcliCommands.jsm +++ b/browser/devtools/commandline/GcliCommands.jsm @@ -135,9 +135,8 @@ gcli.addCommand({ } ], exec: function(args, context) { - let hud = HUDService.getHudReferenceById(context.environment.hudId); - let StyleEditor = hud.gcliterm.document.defaultView.StyleEditor; - StyleEditor.openChrome(args.resource.element, args.line); + let win = HUDService.currentContext(); + win.StyleEditor.openChrome(args.resource.element, args.line); } }); diff --git a/browser/devtools/commandline/gcli.jsm b/browser/devtools/commandline/gcli.jsm index 702e15d10468..41647a0fed54 100644 --- a/browser/devtools/commandline/gcli.jsm +++ b/browser/devtools/commandline/gcli.jsm @@ -4157,7 +4157,7 @@ var ResourceCache = { * Drop all cache entries. Helpful to prevent memory leaks */ clear: function() { - ResourceCache._cached = {}; + ResourceCache._cached = []; } }; diff --git a/browser/devtools/commandline/test/Makefile.in b/browser/devtools/commandline/test/Makefile.in index 43a751fd7965..c8abbbf93e2f 100644 --- a/browser/devtools/commandline/test/Makefile.in +++ b/browser/devtools/commandline/test/Makefile.in @@ -15,6 +15,7 @@ include $(topsrcdir)/config/rules.mk _BROWSER_TEST_FILES = \ browser_gcli_break.js \ browser_gcli_commands.js \ + browser_gcli_edit.js \ browser_gcli_inspect.js \ browser_gcli_integrate.js \ browser_gcli_pref.js \ @@ -26,6 +27,10 @@ _BROWSER_TEST_FILES = \ _BROWSER_TEST_PAGES = \ browser_gcli_break.html \ browser_gcli_inspect.html \ + resources_inpage.js \ + resources_inpage1.css \ + resources_inpage2.css \ + resources.html \ $(NULL) libs:: $(_BROWSER_TEST_FILES) diff --git a/browser/devtools/commandline/test/browser_gcli_edit.js b/browser/devtools/commandline/test/browser_gcli_edit.js new file mode 100644 index 000000000000..c1605d71fd7e --- /dev/null +++ b/browser/devtools/commandline/test/browser_gcli_edit.js @@ -0,0 +1,145 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that the edit command works + +const TEST_URI = TEST_BASE_HTTP + "resources.html"; + +function test() { + DeveloperToolbarTest.test(TEST_URI, function(browser, tab) { + testEditStatus(browser, tab); + // Bug 759853 + // testEditExec(browser, tab); // calls finish() + finish(); + }); +} + +function testEditStatus(browser, tab) { + DeveloperToolbarTest.checkInputStatus({ + typed: "edit", + markup: "VVVV", + status: "ERROR", + emptyParameters: [ " ", " [line]" ], + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "edit i", + markup: "VVVVVI", + status: "ERROR", + directTabText: "nline-css", + emptyParameters: [ " [line]" ], + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "edit c", + markup: "VVVVVI", + status: "ERROR", + directTabText: "ss#style2", + emptyParameters: [ " [line]" ], + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "edit http", + markup: "VVVVVIIII", + status: "ERROR", + directTabText: "://example.com/browser/browser/devtools/commandline/test/resources_inpage1.css", + arrowTabText: "", + emptyParameters: [ " [line]" ], + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "edit page1", + markup: "VVVVVIIIII", + status: "ERROR", + directTabText: "", + arrowTabText: "http://example.com/browser/browser/devtools/commandline/test/resources_inpage1.css", + emptyParameters: [ " [line]" ], + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "edit page2", + markup: "VVVVVIIIII", + status: "ERROR", + directTabText: "", + arrowTabText: "http://example.com/browser/browser/devtools/commandline/test/resources_inpage2.css", + emptyParameters: [ " [line]" ], + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "edit stylez", + markup: "VVVVVEEEEEE", + status: "ERROR", + directTabText: "", + arrowTabText: "", + emptyParameters: [ " [line]" ], + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "edit css#style2", + markup: "VVVVVVVVVVVVVVV", + status: "VALID", + directTabText: "", + emptyParameters: [ " [line]" ], + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "edit css#style2 5", + markup: "VVVVVVVVVVVVVVVVV", + status: "VALID", + directTabText: "", + emptyParameters: [ ], + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "edit css#style2 0", + markup: "VVVVVVVVVVVVVVVVE", + status: "ERROR", + directTabText: "", + emptyParameters: [ ], + }); + + DeveloperToolbarTest.checkInputStatus({ + typed: "edit css#style2 -1", + markup: "VVVVVVVVVVVVVVVVEE", + status: "ERROR", + directTabText: "", + emptyParameters: [ ], + }); +} + +var windowListener = { + onOpenWindow: function(win) { + // Wait for the window to finish loading + let win = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow); + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad, false); + win.close(); + }, false); + win.addEventListener("unload", function onUnload() { + win.removeEventListener("unload", onUnload, false); + Services.wm.removeListener(windowListener); + finish(); + }, false); + }, + onCloseWindow: function(win) { }, + onWindowTitleChange: function(win, title) { } +}; + +function testEditExec(browser, tab) { + + Services.wm.addListener(windowListener); + + var style2 = browser.contentDocument.getElementById("style2"); + DeveloperToolbarTest.exec({ + typed: "edit css#style2", + args: { + resource: function(resource) { + return resource.element.ownerNode == style2; + }, + line: 1 + }, + completed: true, + blankOutput: true, + }); +} diff --git a/browser/devtools/commandline/test/browser_gcli_pref.js b/browser/devtools/commandline/test/browser_gcli_pref.js old mode 100755 new mode 100644 index ef3cdbdda0c9..f6ca207ea0a1 --- a/browser/devtools/commandline/test/browser_gcli_pref.js +++ b/browser/devtools/commandline/test/browser_gcli_pref.js @@ -95,7 +95,7 @@ function testPrefStatus() { DeveloperToolbarTest.checkInputStatus({ typed: "pref show devtools.toolbar.ena", - markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVV", + markup: "VVVVVVVVVVIIIIIIIIIIIIIIIIIIII", directTabText: "bled", status: "ERROR", emptyParameters: [ ] @@ -103,7 +103,7 @@ function testPrefStatus() { DeveloperToolbarTest.checkInputStatus({ typed: "pref show hideIntro", - markup: "VVVVVVVVVVVVVVVVVVV", + markup: "VVVVVVVVVVIIIIIIIII", directTabText: "", arrowTabText: "devtools.gcli.hideIntro", status: "ERROR", diff --git a/browser/devtools/commandline/test/head.js b/browser/devtools/commandline/test/head.js index 7935ade831b9..893efe43111b 100644 --- a/browser/devtools/commandline/test/head.js +++ b/browser/devtools/commandline/test/head.js @@ -2,6 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +const TEST_BASE_HTTP = "http://example.com/browser/browser/devtools/commandline/test/"; +const TEST_BASE_HTTPS = "https://example.com/browser/browser/devtools/commandline/test/"; + let console = (function() { let tempScope = {}; Components.utils.import("resource:///modules/devtools/Console.jsm", tempScope); @@ -83,57 +86,69 @@ let DeveloperToolbarTest = { * emptyParameters: [ "" ], // Still to type * directTabText: "o", // Simple completion text * arrowTabText: "", // When the completion is not an extension + * markup: "VVVIIIEEE", // What state should the error markup be in * }); */ - checkInputStatus: function DTT_checkInputStatus(test) { - if (test.typed) { - DeveloperToolbar.display.inputter.setInput(test.typed); + checkInputStatus: function DTT_checkInputStatus(tests) { + let display = DeveloperToolbar.display; + + if (tests.typed) { + display.inputter.setInput(tests.typed); } else { - ok(false, "Missing typed for " + JSON.stringify(test)); - return; + ok(false, "Missing typed for " + JSON.stringify(tests)); + return; } - if (test.cursor) { - DeveloperToolbar.display.inputter.setCursor(test.cursor) + if (tests.cursor) { + display.inputter.setCursor(tests.cursor) } - if (test.status) { - is(DeveloperToolbar.display.requisition.getStatus().toString(), - test.status, - "status for " + test.typed); + if (tests.status) { + is(display.requisition.getStatus().toString(), + tests.status, "status for " + tests.typed); } - if (test.emptyParameters == null) { - test.emptyParameters = []; + if (tests.emptyParameters == null) { + tests.emptyParameters = []; } - let completer = DeveloperToolbar.display.completer; - let realParams = completer.emptyParameters; - is(realParams.length, test.emptyParameters.length, - 'emptyParameters.length for \'' + test.typed + '\''); + let realParams = display.completer.emptyParameters; + is(realParams.length, tests.emptyParameters.length, + 'emptyParameters.length for \'' + tests.typed + '\''); - if (realParams.length === test.emptyParameters.length) { + if (realParams.length === tests.emptyParameters.length) { for (let i = 0; i < realParams.length; i++) { - is(realParams[i].replace(/\u00a0/g, ' '), test.emptyParameters[i], - 'emptyParameters[' + i + '] for \'' + test.typed + '\''); + is(realParams[i].replace(/\u00a0/g, ' '), tests.emptyParameters[i], + 'emptyParameters[' + i + '] for \'' + tests.typed + '\''); } } - if (test.directTabText) { - is(completer.directTabText, test.directTabText, - 'directTabText for \'' + test.typed + '\''); + if (tests.directTabText) { + is(display.completer.directTabText, tests.directTabText, + 'directTabText for \'' + tests.typed + '\''); } else { - is(completer.directTabText, '', 'directTabText for \'' + test.typed + '\''); + is(display.completer.directTabText, '', + 'directTabText for \'' + tests.typed + '\''); } - if (test.arrowTabText) { - is(completer.arrowTabText, ' \u00a0\u21E5 ' + test.arrowTabText, - 'arrowTabText for \'' + test.typed + '\''); + if (tests.arrowTabText) { + is(display.completer.arrowTabText, ' \u00a0\u21E5 ' + tests.arrowTabText, + 'arrowTabText for \'' + tests.typed + '\''); } else { - is(completer.arrowTabText, '', 'arrowTabText for \'' + test.typed + '\''); + is(display.completer.arrowTabText, '', + 'arrowTabText for \'' + tests.typed + '\''); + } + + if (tests.markup) { + let cursor = tests.cursor ? tests.cursor.start : tests.typed.length; + let statusMarkup = display.requisition.getInputStatusMarkup(cursor); + let actualMarkup = statusMarkup.map(function(s) { + return Array(s.string.length + 1).join(s.status.toString()[0]); + }).join(''); + is(tests.markup, actualMarkup, 'markup for ' + tests.typed); } }, @@ -151,11 +166,11 @@ let DeveloperToolbarTest = { * blankOutput: true, // Special checks when there is no output * }); */ - exec: function DTT_exec(test) { - test = test || {}; + exec: function DTT_exec(tests) { + tests = tests || {}; - if (test.typed) { - DeveloperToolbar.display.inputter.setInput(test.typed); + if (tests.typed) { + DeveloperToolbar.display.inputter.setInput(tests.typed); } let typed = DeveloperToolbar.display.inputter.getInputState().typed; @@ -163,7 +178,7 @@ let DeveloperToolbarTest = { is(typed, output.typed, 'output.command for: ' + typed); - if (test.completed !== false) { + if (tests.completed !== false) { ok(output.completed, 'output.completed false for: ' + typed); } else { @@ -172,36 +187,41 @@ let DeveloperToolbarTest = { // ok(!output.completed, 'output.completed true for: ' + typed); } - if (test.args != null) { - is(Object.keys(test.args).length, Object.keys(output.args).length, + if (tests.args != null) { + is(Object.keys(tests.args).length, Object.keys(output.args).length, 'arg count for ' + typed); Object.keys(output.args).forEach(function(arg) { - let expectedArg = test.args[arg]; + let expectedArg = tests.args[arg]; let actualArg = output.args[arg]; - if (Array.isArray(expectedArg)) { - if (!Array.isArray(actualArg)) { - ok(false, 'actual is not an array. ' + typed + '/' + arg); - return; - } - - is(expectedArg.length, actualArg.length, - 'array length: ' + typed + '/' + arg); - for (let i = 0; i < expectedArg.length; i++) { - is(expectedArg[i], actualArg[i], - 'member: "' + typed + '/' + arg + '/' + i); - } + if (typeof expectedArg === 'function') { + ok(expectedArg(actualArg), 'failed test func. ' + typed + '/' + arg); } else { - is(expectedArg, actualArg, 'typed: "' + typed + '" arg: ' + arg); + if (Array.isArray(expectedArg)) { + if (!Array.isArray(actualArg)) { + ok(false, 'actual is not an array. ' + typed + '/' + arg); + return; + } + + is(expectedArg.length, actualArg.length, + 'array length: ' + typed + '/' + arg); + for (let i = 0; i < expectedArg.length; i++) { + is(expectedArg[i], actualArg[i], + 'member: "' + typed + '/' + arg + '/' + i); + } + } + else { + is(expectedArg, actualArg, 'typed: "' + typed + '" arg: ' + arg); + } } }); } let displayed = DeveloperToolbar.outputPanel._div.textContent; - if (test.outputMatch) { + if (tests.outputMatch) { function doTest(match, against) { if (!match.test(against)) { ok(false, "html output for " + typed + " against " + match.source + @@ -210,17 +230,17 @@ let DeveloperToolbarTest = { info(against); } } - if (Array.isArray(test.outputMatch)) { - test.outputMatch.forEach(function(match) { + if (Array.isArray(tests.outputMatch)) { + tests.outputMatch.forEach(function(match) { doTest(match, displayed); }); } else { - doTest(test.outputMatch, displayed); + doTest(tests.outputMatch, displayed); } } - if (test.blankOutput != null) { + if (tests.blankOutput != null) { if (!/^$/.test(displayed)) { ok(false, "html output for " + typed + " (textContent sent to info)"); info("Actual textContent"); @@ -260,6 +280,8 @@ let DeveloperToolbarTest = { if (appMenuItem) { appMenuItem.hidden = true; } + + // leakHunt({ DeveloperToolbar: DeveloperToolbar }); }); // a.k.a: Services.prefs.setBoolPref("devtools.toolbar.enabled", true); @@ -289,3 +311,158 @@ let DeveloperToolbarTest = { }); }, }; + + +/** + * Memory leak hunter. Walks a tree of objects looking for DOM nodes. + * Usage: + * leakHunt({ + * thing: thing, + * otherthing: otherthing + * }); + */ + +var noRecurse = [ + /^string$/, /^number$/, /^boolean$/, /^null/, /^undefined/, + /^Window$/, /^Document$/, + /^XULDocument$/, /^XULElement$/, + /^DOMWindow$/, /^HTMLDocument$/, /^HTML.*Element$/ +]; + +var hide = [ /^string$/, /^number$/, /^boolean$/, /^null/, /^undefined/ ]; + +function leakHunt(root, path, seen) { + path = path || []; + seen = seen || []; + + try { + var output = leakHuntInner(root, path, seen); + output.forEach(function(line) { + dump(line + '\n'); + }); + } + catch (ex) { + dump(ex + '\n'); + } +} + +function leakHuntInner(root, path, seen) { + var prefix = new Array(path.length).join(' '); + + var reply = []; + function log(msg) { + reply.push(msg); + } + + var direct + try { + direct = Object.keys(root); + } + catch (ex) { + log(prefix + ' Error enumerating: ' + ex); + return reply; + } + + for (var prop in root) { + var newPath = path.slice(); + newPath.push(prop); + prefix = new Array(newPath.length).join(' '); + + var data; + try { + data = root[prop]; + } + catch (ex) { + log(prefix + prop + ' Error reading: ' + ex); + continue; + } + + var recurse = true; + var message = getType(data); + + if (matchesAnyPattern(message, hide)) { + continue; + } + + if (message === 'function' && direct.indexOf(prop) == -1) { + continue; + } + + if (message === 'string') { + var extra = data.length > 10 ? data.substring(0, 9) + '_' : data; + message += ' "' + extra.replace(/\n/g, "|") + '"'; + recurse = false; + } + else if (matchesAnyPattern(message, noRecurse)) { + message += ' (no recurse)' + recurse = false; + } + else if (seen.indexOf(data) !== -1) { + message += ' (already seen)'; + recurse = false; + } + + if (recurse) { + seen.push(data); + var lines = leakHuntInner(data, newPath, seen); + if (lines.length == 0) { + if (message !== 'function') { + log(prefix + prop + ' = ' + message + ' { }'); + } + } + else { + log(prefix + prop + ' = ' + message + ' {'); + lines.forEach(function(line) { + reply.push(line); + }); + log(prefix + '}'); + } + } + else { + log(prefix + prop + ' = ' + message); + } + } + + return reply; +} + +function matchesAnyPattern(str, patterns) { + var match = false; + patterns.forEach(function(pattern) { + if (str.match(pattern)) { + match = true; + } + }); + return match; +} + +function getType(data) { + if (data === null) { + return 'null'; + } + if (data === undefined) { + return 'undefined'; + } + + var type = typeof data; + if (type === 'object' || type === 'Object') { + type = getCtorName(data); + } + + return type; +} + +function getCtorName(aObj) { + try { + if (aObj.constructor && aObj.constructor.name) { + return aObj.constructor.name; + } + } + catch (ex) { + return 'UnknownObject'; + } + + // If that fails, use Objects toString which sometimes gives something + // better than 'Object', and at least defaults to Object if nothing better + return Object.prototype.toString.call(aObj).slice(8, -1); +} diff --git a/browser/devtools/commandline/test/resources.html b/browser/devtools/commandline/test/resources.html new file mode 100644 index 000000000000..5470a550f52b --- /dev/null +++ b/browser/devtools/commandline/test/resources.html @@ -0,0 +1,50 @@ + + + + + + Resources + + + + + + + + + +

paragraph

+ + + + + diff --git a/browser/devtools/commandline/test/resources_inpage.js b/browser/devtools/commandline/test/resources_inpage.js new file mode 100644 index 000000000000..7dce83856f67 --- /dev/null +++ b/browser/devtools/commandline/test/resources_inpage.js @@ -0,0 +1,12 @@ + +// This script is used from within browser_gcli_edit.html + +window.addEventListener('load', function() { + var pid = document.getElementById('pid'); + var h3 = document.createElement('h3'); + h3.id = 'h3id'; + h3.classList.add('h3class'); + h3.appendChild(document.createTextNode('h3')); + h3.setAttribute('data-a1', 'h3'); + pid.parentNode.appendChild(h3); +}); diff --git a/browser/devtools/commandline/test/resources_inpage1.css b/browser/devtools/commandline/test/resources_inpage1.css new file mode 100644 index 000000000000..644deaaea7e0 --- /dev/null +++ b/browser/devtools/commandline/test/resources_inpage1.css @@ -0,0 +1,11 @@ +@charset "utf-8"; + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#pid { border-top: 2px dotted #F00; } +#divid { border-top: 2px dotted #00F; } +#h4id { border-top: 2px dotted #0F0; } +#h3id { border-top: 2px dotted #FF0; } diff --git a/browser/devtools/commandline/test/resources_inpage2.css b/browser/devtools/commandline/test/resources_inpage2.css new file mode 100644 index 000000000000..e4fa48e5305f --- /dev/null +++ b/browser/devtools/commandline/test/resources_inpage2.css @@ -0,0 +1,11 @@ +@charset "utf-8"; + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +*[data-a1=p] { border-left: 4px solid #F00; } +*[data-a1=div] { border-left: 4px solid #00F; } +*[data-a1=h4] { border-left: 4px solid #0F0; } +*[data-a1=h3] { border-left: 4px solid #FF0; }