author | L. David Baron <dbaron@dbaron.org> |
Sat, 11 May 2013 22:00:23 -0700 | |
changeset 131644 | d68224f5325b5c07ebad48a8b246a615b05d5150 |
parent 131601 | 9ec0ad6f7e09d1dc382d67da8878cb35e38df51b (current diff) |
parent 131643 | 4ff8aa4a7295f59159c31e5c45e5b7a3b9ff5dd2 (diff) |
child 131645 | e189550dffb48fae884af91a10669967f204c684 |
child 131728 | 0c854002b29f5940204a880baea2bbf3491faa72 |
push id | 27916 |
push user | dbaron@mozilla.com |
push date | Sun, 12 May 2013 05:05:28 +0000 |
treeherder | mozilla-inbound@d68224f5325b [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 23.0a1 |
first release with | nightly linux32
d68224f5325b
/
23.0a1
/
20130512030908
/
files
nightly linux64
d68224f5325b
/
23.0a1
/
20130512030908
/
files
nightly mac
d68224f5325b
/
23.0a1
/
20130512030908
/
files
nightly win32
d68224f5325b
/
23.0a1
/
20130512030908
/
files
nightly win64
d68224f5325b
/
23.0a1
/
20130512030908
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
23.0a1
/
20130512030908
/
pushlog to previous
nightly linux64
23.0a1
/
20130512030908
/
pushlog to previous
nightly mac
23.0a1
/
20130512030908
/
pushlog to previous
nightly win32
23.0a1
/
20130512030908
/
pushlog to previous
nightly win64
23.0a1
/
20130512030908
/
pushlog to previous
|
browser/devtools/commandline/gcli.jsm | file | annotate | diff | comparison | revisions | |
browser/devtools/shared/Browser.jsm | file | annotate | diff | comparison | revisions | |
browser/devtools/shared/Templater.jsm | file | annotate | diff | comparison | revisions | |
browser/devtools/shared/test/browser_browser_basic.js | file | annotate | diff | comparison | revisions | |
browser/devtools/webconsole/PropertyPanel.jsm | file | annotate | diff | comparison | revisions | |
browser/themes/linux/jar.mn | file | annotate | diff | comparison | revisions | |
browser/themes/windows/jar.mn | file | annotate | diff | comparison | revisions |
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1027,17 +1027,16 @@ pref("devtools.commands.dir", ""); // Toolbox preferences pref("devtools.toolbox.footer.height", 250); pref("devtools.toolbox.sidebar.width", 500); pref("devtools.toolbox.host", "bottom"); pref("devtools.toolbox.selectedTool", "webconsole"); pref("devtools.toolbox.toolbarSpec", '["paintflashing toggle","tilt toggle","scratchpad","resize toggle"]'); pref("devtools.toolbox.sideEnabled", true); -pref("devtools.toolbox.disabledTools", "[]"); // Enable the Inspector pref("devtools.inspector.enabled", true); pref("devtools.inspector.activeSidebar", "ruleview"); pref("devtools.inspector.markupPreview", false); // Enable the Layout View pref("devtools.layoutview.enabled", true);
--- a/browser/components/sessionstore/test/Makefile.in +++ b/browser/components/sessionstore/test/Makefile.in @@ -25,16 +25,17 @@ MOCHITEST_BROWSER_FILES = \ browser_dying_cache.js \ browser_form_restore_events.js \ browser_form_restore_events_sample.html \ browser_formdata_format.js \ browser_formdata_format_sample.html \ browser_input.js \ browser_input_sample.html \ browser_pageshow.js \ + browser_windowRestore_perwindowpb.js \ browser_248970_b_perwindowpb.js \ browser_248970_b_sample.html \ browser_339445.js \ browser_339445_sample.html \ browser_345898.js \ browser_346337.js \ browser_346337_sample.html \ browser_350525.js \
new file mode 100644 --- /dev/null +++ b/browser/components/sessionstore/test/browser_windowRestore_perwindowpb.js @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* 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/. */ + +// This test checks that closed private windows can't be restored + +function test() { + waitForExplicitFinish(); + + // Purging the list of closed windows + while(ss.getClosedWindowCount() > 0) + ss.forgetClosedWindow(0); + + // Load a private window, then close it + // and verify it doesn't get remembered for restoring + var win = OpenBrowserWindow({private: true}); + + whenWindowLoaded(win, function onload() { + info("The private window got loaded"); + win.addEventListener("SSWindowClosing", function onclosing() { + win.removeEventListener("SSWindowClosing", onclosing, false); + executeSoon(function () { + is (ss.getClosedWindowCount(), 0, + "The private window should not have been stored"); + finish(); + }); + }, false); + win.close(); + }); +}
--- a/browser/devtools/commandline/BuiltinCommands.jsm +++ b/browser/devtools/commandline/BuiltinCommands.jsm @@ -4,22 +4,24 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components; const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"] .getService(Ci.nsIStringBundleService) .createBundle("chrome://branding/locale/brand.properties") .GetStringFromName("brandShortName"); -this.EXPORTED_SYMBOLS = [ "CmdAddonFlags", "CmdCommands" ]; +this.EXPORTED_SYMBOLS = [ "CmdAddonFlags", "CmdCommands", "DEFAULT_DEBUG_PORT", "connect" ]; -Cu.import("resource:///modules/devtools/gcli.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); +Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js"); +Cu.import("resource://gre/modules/osfile.jsm") + +Cu.import("resource://gre/modules/devtools/gcli.jsm"); Cu.import("resource:///modules/devtools/shared/event-emitter.js"); XPCOMUtils.defineLazyModuleGetter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "devtools", "resource:///modules/devtools/gDevTools.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils", "resource:///modules/devtools/AppCacheUtils.jsm"); @@ -32,26 +34,26 @@ XPCOMUtils.defineLazyModuleGetter(this, // We need to use an object in which to store any flags because a primitive // would remain undefined. module.CmdAddonFlags = { addonsLoaded: false }; /** - * 'addon' command. - */ + * 'addon' command. + */ gcli.addCommand({ name: "addon", description: gcli.lookup("addonDesc") }); /** - * 'addon list' command. - */ + * 'addon list' command. + */ gcli.addCommand({ name: "addon list", description: gcli.lookup("addonListDesc"), returnType: "addonsInfo", params: [{ name: 'type', type: { name: 'selection', @@ -256,21 +258,21 @@ XPCOMUtils.defineLazyModuleGetter(this, * 'addon enable' command. */ gcli.addCommand({ name: "addon enable", description: gcli.lookup("addonEnableDesc"), params: [nameParameter], exec: function(aArgs, context) { /** - * Enables the addon in the passed list which has a name that matches - * according to the passed name comparer, and resolves the promise which - * is the scope (this) of this function to display the result of this - * enable attempt. - */ + * Enables the addon in the passed list which has a name that matches + * according to the passed name comparer, and resolves the promise which + * is the scope (this) of this function to display the result of this + * enable attempt. + */ function enable(aName, addons) { // Find the add-on. let addon = null; addons.some(function(candidate) { if (candidate.name == aName) { addon = candidate; return true; } else { @@ -293,18 +295,18 @@ XPCOMUtils.defineLazyModuleGetter(this, let deferred = context.defer(); // List the installed add-ons, enable one when done listing. AddonManager.getAllAddons(enable.bind(deferred, aArgs.name)); return deferred.promise; } }); /** - * 'addon disable' command. - */ + * 'addon disable' command. + */ gcli.addCommand({ name: "addon disable", description: gcli.lookup("addonDisableDesc"), params: [nameParameter], exec: function(aArgs, context) { /** * Like enable, but ... you know ... the exact opposite. */ @@ -405,32 +407,32 @@ XPCOMUtils.defineLazyModuleGetter(this, JsDebugger.addDebuggerToGlobal(global); return global.Debugger; }); let debuggers = []; /** - * 'calllog' command - */ + * 'calllog' command + */ gcli.addCommand({ name: "calllog", description: gcli.lookup("calllogDesc") }) /** - * 'calllog start' command - */ + * 'calllog start' command + */ gcli.addCommand({ name: "calllog start", description: gcli.lookup("calllogStartDesc"), exec: function(args, context) { - let contentWindow = context.environment.contentDocument.defaultView; + let contentWindow = context.environment.window; let dbg = new Debugger(contentWindow); dbg.onEnterFrame = function(frame) { // BUG 773652 - Make the output from the GCLI calllog command nicer contentWindow.console.log("Method call: " + this.callDescription(frame)); }.bind(this); debuggers.push(dbg); @@ -462,18 +464,18 @@ XPCOMUtils.defineLazyModuleGetter(this, if (typeof value !== "object" || value === null) { return uneval(value); } return "[object " + value.class + "]"; } }); /** - * 'calllog stop' command - */ + * 'calllog stop' command + */ gcli.addCommand({ name: "calllog stop", description: gcli.lookup("calllogStopDesc"), exec: function(args, context) { let numDebuggers = debuggers.length; if (numDebuggers == 0) { return gcli.lookup("calllogStopNoLogging"); @@ -501,18 +503,18 @@ XPCOMUtils.defineLazyModuleGetter(this, return global.Debugger; }); let debuggers = []; let sandboxes = []; /** - * 'calllog chromestart' command - */ + * 'calllog chromestart' command + */ gcli.addCommand({ name: "calllog chromestart", description: gcli.lookup("calllogChromeStartDesc"), get hidden() gcli.hiddenByChromePref(), params: [ { name: "sourceType", type: { @@ -524,17 +526,17 @@ XPCOMUtils.defineLazyModuleGetter(this, name: "source", type: "string", description: gcli.lookup("calllogChromeSourceTypeDesc"), manual: gcli.lookup("calllogChromeSourceTypeManual"), } ], exec: function(args, context) { let globalObj; - let contentWindow = context.environment.contentDocument.defaultView; + let contentWindow = context.environment.window; if (args.sourceType == "jsm") { try { globalObj = Cu.import(args.source); } catch (e) { return gcli.lookup("callLogChromeInvalidJSM"); } @@ -603,18 +605,18 @@ XPCOMUtils.defineLazyModuleGetter(this, callDescription: function(frame) { let name = frame.callee.name || gcli.lookup("callLogChromeAnonFunction"); let args = frame.arguments.map(this.valueToString).join(", "); return name + "(" + args + ")"; } }); /** - * 'calllog chromestop' command - */ + * 'calllog chromestop' command + */ gcli.addCommand({ name: "calllog chromestop", description: gcli.lookup("calllogChromeStopDesc"), get hidden() gcli.hiddenByChromePref(), exec: function(args, context) { let numDebuggers = debuggers.length; if (numDebuggers == 0) { return gcli.lookup("calllogChromeStopNoLogging"); @@ -647,32 +649,32 @@ XPCOMUtils.defineLazyModuleGetter(this, XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "console", "resource://gre/modules/devtools/Console.jsm"); const PREF_DIR = "devtools.commands.dir"; /** - * A place to store the names of the commands that we have added as a result of - * calling refreshAutoCommands(). Used by refreshAutoCommands to remove the - * added commands. - */ + * A place to store the names of the commands that we have added as a result of + * calling refreshAutoCommands(). Used by refreshAutoCommands to remove the + * added commands. + */ let commands = []; /** - * Exported API - */ + * Exported API + */ this.CmdCommands = { /** - * Called to look in a directory pointed at by the devtools.commands.dir pref - * for *.mozcmd files which are then loaded. - * @param nsIPrincipal aSandboxPrincipal Scope object for the Sandbox in which - * we eval the script from the .mozcmd file. This should be a chrome window. - */ + * Called to look in a directory pointed at by the devtools.commands.dir pref + * for *.mozcmd files which are then loaded. + * @param nsIPrincipal aSandboxPrincipal Scope object for the Sandbox in which + * we eval the script from the .mozcmd file. This should be a chrome window. + */ refreshAutoCommands: function GC_refreshAutoCommands(aSandboxPrincipal) { // First get rid of the last set of commands commands.forEach(function(name) { gcli.removeCommand(name); }); let dirName = prefBranch.getComplexValue(PREF_DIR, Ci.nsISupportsString).data.trim(); @@ -729,22 +731,22 @@ XPCOMUtils.defineLazyModuleGetter(this, } ); } ); } }; /** - * Load the commands from a single file - * @param OS.File.DirectoryIterator.Entry aFileEntry The DirectoryIterator - * Entry of the file containing the commands that we should read - * @param nsIPrincipal aSandboxPrincipal Scope object for the Sandbox in which - * we eval the script from the .mozcmd file. This should be a chrome window. - */ + * Load the commands from a single file + * @param OS.File.DirectoryIterator.Entry aFileEntry The DirectoryIterator + * Entry of the file containing the commands that we should read + * @param nsIPrincipal aSandboxPrincipal Scope object for the Sandbox in which + * we eval the script from the .mozcmd file. This should be a chrome window. + */ function loadCommandFile(aFileEntry, aSandboxPrincipal) { let promise = OS.File.read(aFileEntry.path); promise = promise.then( function onSuccess(array) { let decoder = new TextDecoder(); let source = decoder.decode(array); let sandbox = new Cu.Sandbox(aSandboxPrincipal, { @@ -768,27 +770,27 @@ XPCOMUtils.defineLazyModuleGetter(this, function onError(reason) { console.error("OS.File.read(" + aFileEntry.path + ") failed."); throw reason; } ); } /** - * 'cmd' command - */ + * 'cmd' command + */ gcli.addCommand({ name: "cmd", get hidden() { return !prefBranch.prefHasUserValue(PREF_DIR); }, description: gcli.lookup("cmdDesc") }); /** - * 'cmd refresh' command - */ + * 'cmd refresh' command + */ gcli.addCommand({ name: "cmd refresh", description: gcli.lookup("cmdRefreshDesc"), get hidden() { return !prefBranch.prefHasUserValue(PREF_DIR); }, exec: function Command_cmdRefresh(args, context) { let chromeWindow = context.environment.chromeDocument.defaultView; CmdCommands.refreshAutoCommands(chromeWindow); } @@ -797,56 +799,55 @@ XPCOMUtils.defineLazyModuleGetter(this, /* CmdConsole -------------------------------------------------------------- */ (function(module) { XPCOMUtils.defineLazyModuleGetter(this, "HUDService", "resource:///modules/HUDService.jsm"); /** - * 'console' command - */ + * 'console' command + */ gcli.addCommand({ name: "console", description: gcli.lookup("consoleDesc"), manual: gcli.lookup("consoleManual") }); /** - * 'console clear' command - */ + * 'console clear' command + */ gcli.addCommand({ name: "console clear", description: gcli.lookup("consoleclearDesc"), exec: function Command_consoleClear(args, context) { - let window = context.environment.contentDocument.defaultView; - let hud = HUDService.getHudByWindow(window); + let hud = HUDService.getHudByWindow(context.environment.window); // hud will be null if the web console has not been opened for this window if (hud) { hud.jsterm.clearOutput(); } } }); /** - * 'console close' command - */ + * 'console close' command + */ gcli.addCommand({ name: "console close", description: gcli.lookup("consolecloseDesc"), exec: function Command_consoleClose(args, context) { let gBrowser = context.environment.chromeDocument.defaultView.gBrowser; let target = devtools.TargetFactory.forTab(gBrowser.selectedTab); return gDevTools.closeToolbox(target); } }); /** - * 'console open' command - */ + * 'console open' command + */ gcli.addCommand({ name: "console open", description: gcli.lookup("consoleopenDesc"), exec: function Command_consoleOpen(args, context) { let gBrowser = context.environment.chromeDocument.defaultView.gBrowser; let target = devtools.TargetFactory.forTab(gBrowser.selectedTab); return gDevTools.showToolbox(target, "webconsole"); } @@ -971,17 +972,17 @@ XPCOMUtils.defineLazyModuleGetter(this, */ gcli.addCommand({ name: "cookie list", description: gcli.lookup("cookieListDesc"), manual: gcli.lookup("cookieListManual"), returnType: "cookies", exec: function Command_cookieList(args, context) { let host = context.environment.document.location.host; - if (host == null) { + if (host == null || host == "") { throw new Error(gcli.lookup("cookieListOutNonePage")); } let enm = cookieMgr.getCookiesFromHost(host); let cookies = []; while (enm.hasMoreElements()) { let cookie = enm.getNext().QueryInterface(Ci.nsICookie); @@ -1018,17 +1019,17 @@ XPCOMUtils.defineLazyModuleGetter(this, } ], exec: function Command_cookieRemove(args, context) { let host = context.environment.document.location.host; let enm = cookieMgr.getCookiesFromHost(host); let cookies = []; while (enm.hasMoreElements()) { - let cookie = enm.getNext().QueryInterface(Components.interfaces.nsICookie); + let cookie = enm.getNext().QueryInterface(Ci.nsICookie); if (isCookieAtHost(cookie, host)) { if (cookie.name == args.name) { cookieMgr.remove(cookie.host, cookie.name, cookie.path, false); } } } } }); @@ -1151,55 +1152,31 @@ XPCOMUtils.defineLazyModuleGetter(this, visible: true, typed: command }); }); } } }(this)); -/* CmdEcho ----------------------------------------------------------------- */ - -(function(module) { - /** - * 'echo' command - */ - gcli.addCommand({ - name: "echo", - description: gcli.lookup("echoDesc"), - params: [ - { - name: "message", - type: "string", - description: gcli.lookup("echoMessageDesc") - } - ], - returnType: "string", - hidden: true, - exec: function Command_echo(args, context) { - return args.message; - } - }); -}(this)); - /* CmdExport --------------------------------------------------------------- */ (function(module) { /** - * 'export' command - */ + * 'export' command + */ gcli.addCommand({ name: "export", description: gcli.lookup("exportDesc"), }); /** - * The 'export html' command. This command allows the user to export the page to - * HTML after they do DOM changes. - */ + * The 'export html' command. This command allows the user to export the page to + * HTML after they do DOM changes. + */ gcli.addCommand({ name: "export html", description: gcli.lookup("exportHtmlDesc"), exec: function(args, context) { let html = context.environment.document.documentElement.outerHTML; let url = 'data:text/plain;charset=utf8,' + encodeURIComponent(html); context.environment.window.open(url); } @@ -1211,18 +1188,18 @@ XPCOMUtils.defineLazyModuleGetter(this, (function(module) { const XMLHttpRequest = Components.Constructor("@mozilla.org/xmlextras/xmlhttprequest;1"); XPCOMUtils.defineLazyModuleGetter(this, "js_beautify", "resource:///modules/devtools/Jsbeautify.jsm"); /** - * jsb command. - */ + * jsb command. + */ gcli.addCommand({ name: 'jsb', description: gcli.lookup('jsbDesc'), returnValue:'string', params: [ { name: 'url', type: 'string', @@ -1338,27 +1315,27 @@ XPCOMUtils.defineLazyModuleGetter(this, } }); }(this)); /* CmdPagemod -------------------------------------------------------------- */ (function(module) { /** - * 'pagemod' command - */ + * 'pagemod' command + */ gcli.addCommand({ name: "pagemod", description: gcli.lookup("pagemodDesc"), }); /** - * The 'pagemod replace' command. This command allows the user to search and - * replace within text nodes and attributes. - */ + * The 'pagemod replace' command. This command allows the user to search and + * replace within text nodes and attributes. + */ gcli.addCommand({ name: "pagemod replace", description: gcli.lookup("pagemodReplaceDesc"), params: [ { name: "search", type: "string", description: gcli.lookup("pagemodReplaceSearchDesc"), @@ -1398,27 +1375,26 @@ XPCOMUtils.defineLazyModuleGetter(this, { name: "attributes", type: "string", description: gcli.lookup("pagemodReplaceAttributesDesc"), defaultValue: null, }, ], exec: function(args, context) { - let document = context.environment.contentDocument; let searchTextNodes = !args.attrOnly; let searchAttributes = !args.contentOnly; let regexOptions = args.ignoreCase ? 'ig' : 'g'; let search = new RegExp(escapeRegex(args.search), regexOptions); let attributeRegex = null; if (args.attributes) { attributeRegex = new RegExp(args.attributes, regexOptions); } - let root = args.root || document; + let root = args.root || context.environment.document; let elements = root.querySelectorAll(args.selector); elements = Array.prototype.slice.call(elements); let replacedTextNodes = 0; let replacedAttributes = 0; function replaceAttribute() { replacedAttributes++; @@ -1455,27 +1431,27 @@ XPCOMUtils.defineLazyModuleGetter(this, return gcli.lookupFormat("pagemodReplaceResult", [elements.length, replacedTextNodes, replacedAttributes]); } }); /** - * 'pagemod remove' command - */ + * 'pagemod remove' command + */ gcli.addCommand({ name: "pagemod remove", description: gcli.lookup("pagemodRemoveDesc"), }); /** - * The 'pagemod remove element' command. - */ + * The 'pagemod remove element' command. + */ gcli.addCommand({ name: "pagemod remove element", description: gcli.lookup("pagemodRemoveElementDesc"), params: [ { name: "search", type: "string", description: gcli.lookup("pagemodRemoveElementSearchDesc"), @@ -1493,18 +1469,17 @@ XPCOMUtils.defineLazyModuleGetter(this, }, { name: 'ifEmptyOnly', type: 'boolean', description: gcli.lookup("pagemodRemoveElementIfEmptyOnlyDesc"), }, ], exec: function(args, context) { - let document = context.environment.contentDocument; - let root = args.root || document; + let root = args.root || context.environment.document; let elements = Array.prototype.slice.call(root.querySelectorAll(args.search)); let removed = 0; for (let i = 0; i < elements.length; i++) { let element = elements[i]; let parentNode = element.parentNode; if (!parentNode || !element.removeChild) { continue; @@ -1521,18 +1496,18 @@ XPCOMUtils.defineLazyModuleGetter(this, } return gcli.lookupFormat("pagemodRemoveElementResultMatchedAndRemovedElements", [elements.length, removed]); } }); /** - * The 'pagemod remove attribute' command. - */ + * The 'pagemod remove attribute' command. + */ gcli.addCommand({ name: "pagemod remove attribute", description: gcli.lookup("pagemodRemoveAttributeDesc"), params: [ { name: "searchAttributes", type: "string", description: gcli.lookup("pagemodRemoveAttributeSearchAttributesDesc"), @@ -1550,19 +1525,17 @@ XPCOMUtils.defineLazyModuleGetter(this, }, { name: "ignoreCase", type: "boolean", description: gcli.lookup("pagemodRemoveAttributeIgnoreCaseDesc"), }, ], exec: function(args, context) { - let document = context.environment.contentDocument; - - let root = args.root || document; + let root = args.root || context.environment.document; let regexOptions = args.ignoreCase ? 'ig' : 'g'; let attributeRegex = new RegExp(args.searchAttributes, regexOptions); let elements = root.querySelectorAll(args.searchElements); elements = Array.prototype.slice.call(elements); let removed = 0; for (let i = 0; i < elements.length; i++) { let element = elements[i]; @@ -1581,23 +1554,23 @@ XPCOMUtils.defineLazyModuleGetter(this, } return gcli.lookupFormat("pagemodRemoveAttributeResult", [elements.length, removed]); } }); /** - * Make a given string safe to use in a regular expression. - * - * @param string aString - * The string you want to use in a regex. - * @return string - * The equivalent of |aString| but safe to use in a regex. - */ + * Make a given string safe to use in a regular expression. + * + * @param string aString + * The string you want to use in a regex. + * @return string + * The equivalent of |aString| but safe to use in a regex. + */ function escapeRegex(aString) { return aString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); } }(this)); /* CmdTools -------------------------------------------------------------- */ (function(module) { @@ -1617,32 +1590,31 @@ XPCOMUtils.defineLazyModuleGetter(this, { name: "srcdir", type: "string", description: gcli.lookup("toolsSrcdirDir") } ], returnType: "string", exec: function(args, context) { - let promise = context.createPromise(); - let existsPromise = OS.File.exists(args.srcdir + "/CLOBBER"); - existsPromise.then(function(exists) { + return OS.File.exists(args.srcdir + "/CLOBBER").then(function(exists) { if (exists) { - var str = Cc["@mozilla.org/supports-string;1"] - .createInstance(Ci.nsISupportsString); + let str = Cc["@mozilla.org/supports-string;1"] + .createInstance(Ci.nsISupportsString); str.data = args.srcdir; Services.prefs.setComplexValue("devtools.loader.srcdir", - Components.interfaces.nsISupportsString, str); + Ci.nsISupportsString, str); devtools.reload(); - promise.resolve(gcli.lookupFormat("toolsSrcdirReloaded", [args.srcdir])); - return; + + let msg = gcli.lookupFormat("toolsSrcdirReloaded", [args.srcdir]); + throw new Error(msg); } - promise.reject(gcli.lookupFormat("toolsSrcdirNotFound", [args.srcdir])); + + return gcli.lookupFormat("toolsSrcdirNotFound", [args.srcdir]); }); - return promise; } }); gcli.addCommand({ name: "tools builtin", description: gcli.lookup("toolsBuiltinDesc"), manual: gcli.lookup("toolsBuiltinManual"), get hidden() gcli.hiddenByChromePref(), @@ -1666,28 +1638,27 @@ XPCOMUtils.defineLazyModuleGetter(this, } }); }(this)); /* CmdRestart -------------------------------------------------------------- */ (function(module) { /** - * Restart command - * - * @param boolean nocache - * Disables loading content from cache upon restart. - * - * Examples : - * >> restart - * - restarts browser immediately - * >> restart --nocache - * - restarts immediately and starts Firefox without using cache - */ - + * Restart command + * + * @param boolean nocache + * Disables loading content from cache upon restart. + * + * Examples : + * >> restart + * - restarts browser immediately + * >> restart --nocache + * - restarts immediately and starts Firefox without using cache + */ gcli.addCommand({ name: "restart", description: gcli.lookupFormat("restartBrowserDesc", [BRAND_SHORT_NAME]), params: [ { name: "nocache", type: "boolean", description: gcli.lookup("restartBrowserNocacheDesc") @@ -1722,23 +1693,23 @@ XPCOMUtils.defineLazyModuleGetter(this, XPCOMUtils.defineLazyModuleGetter(this, "LayoutHelpers", "resource:///modules/devtools/LayoutHelpers.jsm"); // String used as an indication to generate default file name in the following // format: "Screen Shot yyyy-mm-dd at HH.MM.SS.png" const FILENAME_DEFAULT_VALUE = " "; /** - * 'screenshot' command - */ + * 'screenshot' command + */ gcli.addCommand({ name: "screenshot", description: gcli.lookup("screenshotDesc"), manual: gcli.lookup("screenshotManual"), - returnType: "html", + returnType: "dom", params: [ { name: "filename", type: "string", defaultValue: FILENAME_DEFAULT_VALUE, description: gcli.lookup("screenshotFilenameDesc"), manual: gcli.lookup("screenshotFilenameManual") }, @@ -1783,34 +1754,32 @@ XPCOMUtils.defineLazyModuleGetter(this, exec: function Command_screenshot(args, context) { if (args.chrome && args.selector) { // Node screenshot with chrome option does not work as inteded // Refer https://bugzilla.mozilla.org/show_bug.cgi?id=659268#c7 // throwing for now. throw new Error(gcli.lookup("screenshotSelectorChromeConflict")); } var document = args.chrome? context.environment.chromeDocument - : context.environment.contentDocument; + : context.environment.document; if (args.delay > 0) { var deferred = context.defer(); document.defaultView.setTimeout(function Command_screenshotDelay() { let reply = this.grabScreen(document, args.filename, args.clipboard, args.fullpage); deferred.resolve(reply); }.bind(this), args.delay * 1000); return deferred.promise; } else { return this.grabScreen(document, args.filename, args.clipboard, args.fullpage, args.selector); } }, - grabScreen: - function Command_screenshotGrabScreen(document, filename, clipboard, - fullpage, node) { + grabScreen: function(document, filename, clipboard, fullpage, node) { let window = document.defaultView; let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); let left = 0; let top = 0; let width; let height; let div = document.createElementNS("http://www.w3.org/1999/xhtml", "div"); @@ -1943,23 +1912,63 @@ XPCOMUtils.defineLazyModuleGetter(this, "background-size: 256px " + previewHeight + "px;" + "margin: 4px; display: block"); div.appendChild(image); return div; } }); }(this)); + +/* Remoting ----------------------------------------------------------- */ + +const { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {}); + +/** + * 'listen' command + */ +gcli.addCommand({ + name: "listen", + description: gcli.lookup("listenDesc"), + manual: gcli.lookup("listenManual"), + params: [ + { + name: "port", + type: "number", + get defaultValue() { + return Services.prefs.getIntPref("devtools.debugger.chrome-debugging-port"); + }, + description: gcli.lookup("listenPortDesc"), + } + ], + exec: function Command_screenshot(args, context) { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + var reply = DebuggerServer.openListener(args.port); + if (!reply) { + throw new Error(gcli.lookup("listenDisabledOutput")); + } + + if (DebuggerServer.initialized) { + return gcli.lookupFormat("listenInitOutput", [ '' + args.port ]); + } + + return gcli.lookup("listenNoInitOutput"); + }, +}); + + /* CmdPaintFlashing ------------------------------------------------------- */ (function(module) { /** - * 'paintflashing' command - */ - + * 'paintflashing' command + */ gcli.addCommand({ name: 'paintflashing', description: gcli.lookup('paintflashingDesc') }); gcli.addCommand({ name: 'paintflashing on', description: gcli.lookup('paintflashingOnDesc'), @@ -1971,25 +1980,23 @@ XPCOMUtils.defineLazyModuleGetter(this, type: "boolean", name: "chrome", get hidden() gcli.hiddenByChromePref(), description: gcli.lookup("paintflashingChromeDesc"), } ] }], exec: function(args, context) { - var window; - if (args.chrome) { - window = context.environment.chromeDocument.defaultView; - } else { - window = context.environment.contentDocument.defaultView; - } - window.QueryInterface(Ci.nsIInterfaceRequestor). - getInterface(Ci.nsIDOMWindowUtils). - paintFlashing = true; + var window = args.chrome ? + context.environment.chromeWindow : + context.environment.window; + + window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .paintFlashing = true; onPaintFlashingChanged(context); } }); gcli.addCommand({ name: 'paintflashing off', description: gcli.lookup('paintflashingOffDesc'), manual: gcli.lookup('paintflashingManual'), @@ -2000,24 +2007,23 @@ XPCOMUtils.defineLazyModuleGetter(this, type: "boolean", name: "chrome", get hidden() gcli.hiddenByChromePref(), description: gcli.lookup("paintflashingChromeDesc"), } ] }], exec: function(args, context) { - if (args.chrome) { - var window = context.environment.chromeDocument.defaultView; - } else { - var window = context.environment.contentDocument.defaultView; - } - window.QueryInterface(Ci.nsIInterfaceRequestor). - getInterface(Ci.nsIDOMWindowUtils). - paintFlashing = false; + var window = args.chrome ? + context.environment.chromeWindow : + context.environment.window; + + window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .paintFlashing = false; onPaintFlashingChanged(context); } }); gcli.addCommand({ name: 'paintflashing toggle', hidden: true, buttonId: "command-button-paintflashing", @@ -2036,17 +2042,17 @@ XPCOMUtils.defineLazyModuleGetter(this, onChange: function(aTarget, aChangeHandler) { eventEmitter.on("changed", aChangeHandler); }, offChange: function(aTarget, aChangeHandler) { eventEmitter.off("changed", aChangeHandler); }, }, tooltipText: gcli.lookup("paintflashingTooltip"), - description: gcli.lookup('paintflashingOnDesc'), + description: gcli.lookup('paintflashingToggleDesc'), manual: gcli.lookup('paintflashingManual'), exec: function(args, context) { var gBrowser = context.environment.chromeDocument.defaultView.gBrowser; var window = gBrowser.contentWindow; var wUtils = window.QueryInterface(Ci.nsIInterfaceRequestor). getInterface(Ci.nsIDOMWindowUtils); wUtils.paintFlashing = !wUtils.paintFlashing; onPaintFlashingChanged(context); @@ -2066,18 +2072,18 @@ XPCOMUtils.defineLazyModuleGetter(this, target.once("navigate", fireChange); } }(this)); /* CmdAppCache ------------------------------------------------------- */ (function(module) { /** - * 'appcache' command - */ + * 'appcache' command + */ gcli.addCommand({ name: 'appcache', description: gcli.lookup('appCacheDesc') }); gcli.addConverter({ from: "appcacheerrors", @@ -2120,101 +2126,88 @@ XPCOMUtils.defineLazyModuleGetter(this, name: "uri", description: gcli.lookup("appCacheValidateUriDesc"), defaultValue: null, } ] }], exec: function(args, context) { let utils; - let promise = context.createPromise(); + let deferred = context.defer(); if (args.uri) { utils = new AppCacheUtils(args.uri); } else { - let doc = context.environment.contentDocument; - utils = new AppCacheUtils(doc); + utils = new AppCacheUtils(context.environment.document); } utils.validateManifest().then(function(errors) { - promise.resolve([errors, utils.manifestURI || "-"]); + deferred.resolve([errors, utils.manifestURI || "-"]); }); - return promise; + return deferred.promise; } }); gcli.addCommand({ name: 'appcache clear', description: gcli.lookup('appCacheClearDesc'), manual: gcli.lookup('appCacheClearManual'), exec: function(args, context) { let utils = new AppCacheUtils(args.uri); utils.clearAll(); return gcli.lookup("appCacheClearCleared"); } }); + let appcacheListEntries = "" + + "<ul class='gcli-appcache-list'>" + + " <li foreach='entry in ${entries}'>" + + " <table class='gcli-appcache-detail'>" + + " <tr>" + + " <td>" + gcli.lookup("appCacheListKey") + "</td>" + + " <td>${entry.key}</td>" + + " </tr>" + + " <tr>" + + " <td>" + gcli.lookup("appCacheListFetchCount") + "</td>" + + " <td>${entry.fetchCount}</td>" + + " </tr>" + + " <tr>" + + " <td>" + gcli.lookup("appCacheListLastFetched") + "</td>" + + " <td>${entry.lastFetched}</td>" + + " </tr>" + + " <tr>" + + " <td>" + gcli.lookup("appCacheListLastModified") + "</td>" + + " <td>${entry.lastModified}</td>" + + " </tr>" + + " <tr>" + + " <td>" + gcli.lookup("appCacheListExpirationTime") + "</td>" + + " <td>${entry.expirationTime}</td>" + + " </tr>" + + " <tr>" + + " <td>" + gcli.lookup("appCacheListDataSize") + "</td>" + + " <td>${entry.dataSize}</td>" + + " </tr>" + + " <tr>" + + " <td>" + gcli.lookup("appCacheListDeviceID") + "</td>" + + " <td>${entry.deviceID} <span class='gcli-out-shortcut' " + + "onclick='${onclick}' ondblclick='${ondblclick}' " + + "data-command='appcache viewentry ${entry.key}'" + + ">" + gcli.lookup("appCacheListViewEntry") + "</span>" + + " </td>" + + " </tr>" + + " </table>" + + " </li>" + + "</ul>"; + gcli.addConverter({ from: "appcacheentries", to: "view", exec: function(entries, context) { - if (!entries) { - return context.createView({ - html: "<span>" + gcli.lookup("appCacheManifestContainsErrors") + "</span>" - }); - } - - if (entries.length == 0) { - return context.createView({ - html: "<span>" + gcli.lookup("appCacheNoResults") + "</span>" - }); - } - - let appcacheListEntries = "" + - "<ul class='gcli-appcache-list'>" + - " <li foreach='entry in ${entries}'>" + - " <table class='gcli-appcache-detail'>" + - " <tr>" + - " <td>" + gcli.lookup("appCacheListKey") + "</td>" + - " <td>${entry.key}</td>" + - " </tr>" + - " <tr>" + - " <td>" + gcli.lookup("appCacheListFetchCount") + "</td>" + - " <td>${entry.fetchCount}</td>" + - " </tr>" + - " <tr>" + - " <td>" + gcli.lookup("appCacheListLastFetched") + "</td>" + - " <td>${entry.lastFetched}</td>" + - " </tr>" + - " <tr>" + - " <td>" + gcli.lookup("appCacheListLastModified") + "</td>" + - " <td>${entry.lastModified}</td>" + - " </tr>" + - " <tr>" + - " <td>" + gcli.lookup("appCacheListExpirationTime") + "</td>" + - " <td>${entry.expirationTime}</td>" + - " </tr>" + - " <tr>" + - " <td>" + gcli.lookup("appCacheListDataSize") + "</td>" + - " <td>${entry.dataSize}</td>" + - " </tr>" + - " <tr>" + - " <td>" + gcli.lookup("appCacheListDeviceID") + "</td>" + - " <td>${entry.deviceID} <span class='gcli-out-shortcut' " + - "onclick='${onclick}' ondblclick='${ondblclick}' " + - "data-command='appcache viewentry ${entry.key}'" + - ">" + gcli.lookup("appCacheListViewEntry") + "</span>" + - " </td>" + - " </tr>" + - " </table>" + - " </li>" + - "</ul>"; - return context.createView({ html: appcacheListEntries, data: { entries: entries, onclick: createUpdateHandler(context), ondblclick: createExecuteHandler(context), } }); @@ -2233,44 +2226,36 @@ XPCOMUtils.defineLazyModuleGetter(this, type: "string", name: "search", description: gcli.lookup("appCacheListSearchDesc"), defaultValue: null, }, ] }], exec: function(args, context) { - let doc = context.environment.contentDocument; let utils = new AppCacheUtils(); - - let entries = utils.listEntries(args.search); - return entries; + return utils.listEntries(args.search); } }); gcli.addCommand({ name: 'appcache viewentry', description: gcli.lookup('appCacheViewEntryDesc'), manual: gcli.lookup('appCacheViewEntryManual'), params: [ { type: "string", name: "key", description: gcli.lookup("appCacheViewEntryKey"), defaultValue: null, } ], exec: function(args, context) { - let doc = context.environment.contentDocument; let utils = new AppCacheUtils(); - - let result = utils.viewEntry(args.key); - if (result) { - return result; - } + return utils.viewEntry(args.key); } }); /** * Helper to find the 'data-command' attribute and call some action on it. * @see |updateCommand()| and |executeCommand()| */ function withCommand(element, action) {
--- a/browser/devtools/commandline/test/Makefile.in +++ b/browser/devtools/commandline/test/Makefile.in @@ -26,16 +26,17 @@ MOCHITEST_BROWSER_FILES = \ browser_cmd_appcache_valid.js \ browser_cmd_appcache_valid_appcache.appcache \ browser_cmd_appcache_valid_appcache.appcache^headers^ \ browser_cmd_appcache_valid_index.html \ browser_cmd_appcache_valid_page1.html \ browser_cmd_appcache_valid_page2.html \ browser_cmd_appcache_valid_page3.html \ browser_cmd_commands.js \ + browser_cmd_cookie.html \ browser_cmd_cookie.js \ browser_cmd_jsb.js \ browser_cmd_jsb_script.jsi \ browser_cmd_pagemod_export.html \ browser_cmd_pagemod_export.js \ browser_cmd_pref.js \ browser_cmd_restart.js \ browser_cmd_screenshot.html \
--- a/browser/devtools/commandline/test/browser_cmd_addon.js +++ b/browser/devtools/commandline/test/browser_cmd_addon.js @@ -11,23 +11,18 @@ function test() { helpers.addTabWithToolbar("about:blank", function(options) { return helpers.runTests(options, tests); }).then(finish); } tests.gatTest = function(options) { let deferred = Promise.defer(); - // hack to reduce stack size as a result of bug 842347 - let onGatReadyInterjection = function() { - executeSoon(onGatReady); - }; - let onGatReady = function() { - Services.obs.removeObserver(onGatReadyInterjection, "gcli_addon_commands_ready"); + Services.obs.removeObserver(onGatReady, "gcli_addon_commands_ready"); info("gcli_addon_commands_ready notification received, running tests"); let auditDone = helpers.audit(options, [ { setup: 'addon list dictionary', check: { input: 'addon list dictionary', hints: '', @@ -116,17 +111,17 @@ tests.gatTest = function(options) { } ]); auditDone.then(function() { deferred.resolve(); }); }; - Services.obs.addObserver(onGatReadyInterjection, "gcli_addon_commands_ready", false); + Services.obs.addObserver(onGatReady, "gcli_addon_commands_ready", false); if (CmdAddonFlags.addonsLoaded) { info("The call to AddonManager.getAllAddons in BuiltinCommands.jsm is done."); info("Send the gcli_addon_commands_ready notification ourselves."); Services.obs.notifyObservers(null, "gcli_addon_commands_ready", null); } else { info("Waiting for gcli_addon_commands_ready notification.");
--- a/browser/devtools/commandline/test/browser_cmd_appcache_valid.js +++ b/browser/devtools/commandline/test/browser_cmd_appcache_valid.js @@ -26,16 +26,35 @@ function test() { input: 'appcache', markup: 'IIIIIIII', status: 'ERROR', args: {} }, }, { + setup: function() { + Services.prefs.setBoolPref("browser.cache.disk.enable", false); + helpers.setInput(options, 'appcache list', 13); + }, + check: { + input: 'appcache list', + markup: 'VVVVVVVVVVVVV', + status: 'VALID', + args: {}, + }, + exec: { + output: [ /cache is disabled/ ] + }, + post: function(output) { + Services.prefs.setBoolPref("browser.cache.disk.enable", true); + } + }, + + { setup: 'appcache list', check: { input: 'appcache list', markup: 'VVVVVVVVVVVVV', status: 'VALID', args: {}, }, exec: { @@ -51,18 +70,18 @@ function test() { status: 'VALID', args: { search: { value: 'page' }, } }, exec: { output: [ /page1/, /page2/, /page3/ ] }, - post: function(output) { - ok(!output.contains("index"), "index is not contained in output"); + post: function(output, text) { + ok(!text.contains("index"), "index is not contained in output"); } }, { setup: 'appcache validate', check: { input: 'appcache validate', markup: 'VVVVVVVVVVVVVVVVV', @@ -113,21 +132,21 @@ function test() { input: 'appcache list', markup: 'VVVVVVVVVVVVV', status: 'VALID', args: {}, }, exec: { output: [ /no results/ ] }, - post: function(output) { - ok(!output.contains("index"), "index is not contained in output"); - ok(!output.contains("page1"), "page1 is not contained in output"); - ok(!output.contains("page2"), "page1 is not contained in output"); - ok(!output.contains("page3"), "page1 is not contained in output"); + post: function(output, text) { + ok(!text.contains("index"), "index is not contained in output"); + ok(!text.contains("page1"), "page1 is not contained in output"); + ok(!text.contains("page2"), "page1 is not contained in output"); + ok(!text.contains("page3"), "page1 is not contained in output"); } }, { setup: 'appcache viewentry --key ' + TEST_URI, check: { input: 'appcache viewentry --key ' + TEST_URI, // appcache viewentry --key http://sub1.test2.example.com/browser/browser/devtools/commandline/test/browser_cmd_appcache_valid_index.html
--- a/browser/devtools/commandline/test/browser_cmd_commands.js +++ b/browser/devtools/commandline/test/browser_cmd_commands.js @@ -10,27 +10,16 @@ const TEST_URI = "data:text/html;charset let tests = {}; function test() { helpers.addTabWithToolbar(TEST_URI, function(options) { return helpers.runTests(options, tests); }).then(finish); } -tests.testEcho = function(options) { - return helpers.audit(options, [ - { - setup: "echo message", - exec: { - output: "message", - } - } - ]); -}; - tests.testConsole = function(options) { let deferred = Promise.defer(); let hud = null; let onWebConsoleOpen = function(subject) { Services.obs.removeObserver(onWebConsoleOpen, "web-console-created"); subject.QueryInterface(Ci.nsISupportsString); @@ -62,22 +51,17 @@ tests.testConsole = function(options) { exec: { output: "" }, post: function() { ok(!(hud.hudId in HUDService.hudReferences), "console closed"); } } ]).then(function() { - // FIXME: Remove this hack once bug 842347 is fixed - // Gak - our promises impl causes so many stack frames that we blow up the - // JS engine. Jumping to a new event with a truncated stack solves this. - executeSoon(function() { - deferred.resolve(); - }); + deferred.resolve(); }); }; helpers.audit(options, [ { setup: "console open", exec: { } }
new file mode 100644 --- /dev/null +++ b/browser/devtools/commandline/test/browser_cmd_cookie.html @@ -0,0 +1,18 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>GCLI cookie command test</title> +</head> +<body> + + <p>Cookie test</p> + <p id=result></p> + <script type="text/javascript"> + document.cookie = "zap=zep"; + document.cookie = "zip=zop"; + document.getElementById("result").innerHTML = document.cookie; + </script> + +</body> +</html>
--- a/browser/devtools/commandline/test/browser_cmd_cookie.js +++ b/browser/devtools/commandline/test/browser_cmd_cookie.js @@ -1,14 +1,15 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ // Tests that the cookie commands works as they should -const TEST_URI = "data:text/html;charset=utf-8,gcli-cookie"; +const TEST_URI = "http://example.com/browser/browser/devtools/commandline/"+ + "test/browser_cmd_cookie.html"; function test() { helpers.addTabWithToolbar(TEST_URI, function(options) { return helpers.audit(options, [ { setup: 'cookie', check: { input: 'cookie', @@ -74,49 +75,81 @@ function test() { value: { value: 'ban' }, secure: { value: false }, } }, }, { setup: "cookie list", exec: { - output: 'No cookies found for host' + output: [ /zap=zep/, /zip=zop/, /Edit/ ] } }, { - setup: "cookie set fruit banana", + setup: "cookie set zup banana", check: { args: { - name: { value: 'fruit' }, + name: { value: 'zup' }, value: { value: 'banana' }, } }, exec: { output: "" } }, { setup: "cookie list", exec: { - output: [ /fruit=banana/, /Expires:/, /Edit/ ] + output: [ /zap=zep/, /zip=zop/, /zup=banana/, /Edit/ ] } }, { - setup: "cookie remove fruit", - check: { - args: { - name: { value: 'fruit' }, - } + setup: "cookie remove zip", + exec: { }, + }, + { + setup: "cookie list", + exec: { + output: [ /zap=zep/, /zup=banana/, /Edit/ ] }, - exec: { - output: "" + post: function(output, text) { + ok(!text.contains("zip"), ""); + ok(!text.contains("zop"), ""); } }, { + setup: "cookie remove zap", + exec: { }, + }, + { setup: "cookie list", exec: { - output: 'No cookies found for host' + output: [ /zup=banana/, /Edit/ ] + }, + post: function(output, text) { + ok(!text.contains("zap"), ""); + ok(!text.contains("zep"), ""); + ok(!text.contains("zip"), ""); + ok(!text.contains("zop"), ""); + } + }, + { + setup: "cookie remove zup", + exec: { } + }, + { + setup: "cookie list", + exec: { + output: 'No cookies found for host example.com' + }, + post: function(output, text) { + ok(!text.contains("zap"), ""); + ok(!text.contains("zep"), ""); + ok(!text.contains("zip"), ""); + ok(!text.contains("zop"), ""); + ok(!text.contains("zup"), ""); + ok(!text.contains("banana"), ""); + ok(!text.contains("Edit"), ""); } }, ]); }).then(finish); }
--- a/browser/devtools/commandline/test/browser_gcli_canon.js +++ b/browser/devtools/commandline/test/browser_gcli_canon.js @@ -33,16 +33,17 @@ function test() { // <INJECTED SOURCE:END> 'use strict'; // var helpers = require('gclitest/helpers'); var canon = require('gcli/canon'); // var assert = require('test/assert'); +var Canon = canon.Canon; var startCount = undefined; var events = undefined; var canonChange = function(ev) { events++; }; @@ -152,18 +153,16 @@ exports.testAddRemove2 = function(option assert.is(canon.getCommands().length, startCount + 1, 'rereadd command success'); assert.is(events, 4, 'rereadd event'); return helpers.audit(options, [ { setup: 'testadd', - check: { - }, exec: { output: /^3$/ }, post: function() { canon.removeCommand({ name: 'testadd' }); @@ -194,9 +193,79 @@ exports.testAddRemove3 = function(option assert.is(canon.getCommands().length, startCount, 'nonexistant2 command success'); assert.is(events, 5, 'nonexistant2 event'); canon.onCanonChange.remove(canonChange); }; +exports.testAltCanon = function(options) { + var altCanon = new Canon(); + + var tss = { + name: 'tss', + params: [ + { name: 'str', type: 'string' }, + { name: 'num', type: 'number' }, + { name: 'opt', type: { name: 'selection', data: [ '1', '2', '3' ] } }, + ], + exec: function(args, context) { + return context.commandName + ':' + + args.str + ':' + args.num + ':' + args.opt; + } + }; + altCanon.addCommand(tss); + + var commandSpecs = altCanon.getCommandSpecs(); + assert.is(JSON.stringify(commandSpecs), + '{"tss":{"name":"tss","params":[' + + '{"name":"str","type":"string"},' + + '{"name":"num","type":"number"},' + + '{"name":"opt","type":{"name":"selection","data":["1","2","3"]}}]}}', + 'JSON.stringify(commandSpecs)'); + + var remoter = function(args, context) { + assert.is(context.commandName, 'tss', 'commandName is tss'); + + var cmd = altCanon.getCommand(context.commandName); + return cmd.exec(args, context); + }; + + canon.addProxyCommands('proxy', commandSpecs, remoter, 'test'); + + var parent = canon.getCommand('proxy'); + assert.is(parent.name, 'proxy', 'Parent command called proxy'); + + var child = canon.getCommand('proxy tss'); + assert.is(child.name, 'proxy tss', 'child command called proxy tss'); + + return helpers.audit(options, [ + { + setup: 'proxy tss foo 6 3', + check: { + input: 'proxy tss foo 6 3', + hints: '', + markup: 'VVVVVVVVVVVVVVVVV', + cursor: 17, + status: 'VALID', + args: { + str: { value: 'foo', status: 'VALID' }, + num: { value: 6, status: 'VALID' }, + opt: { value: '3', status: 'VALID' } + } + }, + exec: { + output: 'tss:foo:6:3' + }, + post: function() { + canon.removeCommand('proxy'); + canon.removeCommand('proxy tss'); + + assert.is(canon.getCommand('proxy'), undefined, 'remove proxy'); + assert.is(canon.getCommand('proxy tss'), undefined, 'remove proxy tss'); + } + } + ]); +}; + + // });
--- a/browser/devtools/commandline/test/browser_gcli_cli.js +++ b/browser/devtools/commandline/test/browser_gcli_cli.js @@ -753,17 +753,16 @@ exports.testSingleFloat = function(optio check: { input: 'tsf', hints: ' <num>', markup: 'VVV', cursor: 3, current: '__command', status: 'ERROR', error: '', - predictions: [ ], unassigned: [ ], args: { command: { name: 'tsf' }, num: { value: undefined, arg: '', status: 'INCOMPLETE', message: ''
--- a/browser/devtools/commandline/test/browser_gcli_completion.js +++ b/browser/devtools/commandline/test/browser_gcli_completion.js @@ -514,19 +514,19 @@ exports.testSpaceComplete = function(opt current: 'sel2', status: 'ERROR', tooltipState: 'true:importantFieldFlag', args: { command: { name: 'tslong' }, msg: { status: 'INCOMPLETE', message: '' }, num: { status: 'VALID' }, sel: { status: 'VALID' }, - bool: { value: false,status: 'VALID' }, + bool: { value: false, status: 'VALID' }, num2: { status: 'VALID' }, - bool2: { value: false,status: 'VALID' }, + bool2: { value: false, status: 'VALID' }, sel2: { value: 'with space', arg: ' --sel2 \'with space\' ', status: 'VALID' } } } }
new file mode 100644 --- /dev/null +++ b/browser/devtools/commandline/test/browser_gcli_context.js @@ -0,0 +1,276 @@ +/* + * Copyright 2012, Mozilla Foundation and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// define(function(require, exports, module) { + +// <INJECTED SOURCE:START> + +// THIS FILE IS GENERATED FROM SOURCE IN THE GCLI PROJECT +// DO NOT EDIT IT DIRECTLY + +var exports = {}; + +const TEST_URI = "data:text/html;charset=utf-8,<p id='gcli-input'>gcli-testContext.js</p>"; + +function test() { + helpers.addTabWithToolbar(TEST_URI, function(options) { + return helpers.runTests(options, exports); + }).then(finish); +} + +// <INJECTED SOURCE:END> + +'use strict'; + +// var helpers = require('gclitest/helpers'); +// var mockCommands = require('gclitest/mockCommands'); +var cli = require('gcli/cli'); + +var origLogErrors = undefined; + +exports.setup = function(options) { + mockCommands.setup(); + + origLogErrors = cli.logErrors; + cli.logErrors = false; +}; + +exports.shutdown = function(options) { + mockCommands.shutdown(); + + cli.logErrors = origLogErrors; + origLogErrors = undefined; +}; + +exports.testBaseline = function(options) { + helpers.audit(options, [ + // These 3 establish a baseline for comparison when we have used the + // context command + { + setup: 'ext', + check: { + input: 'ext', + hints: ' -> context', + markup: 'III', + message: '', + predictions: [ 'context', 'tsn ext', 'tsn exte', 'tsn exten', 'tsn extend' ], + unassigned: [ ], + } + }, + { + setup: 'ext test', + check: { + input: 'ext test', + hints: '', + markup: 'IIIVEEEE', + status: 'ERROR', + message: 'Too many arguments', + unassigned: [ ' test' ], + } + }, + { + setup: 'tsn', + check: { + input: 'tsn', + hints: '', + markup: 'III', + cursor: 3, + current: '__command', + status: 'ERROR', + predictionsContains: [ 'tsn', 'tsn deep', 'tsn ext', 'tsn exte' ], + args: { + command: { name: 'tsn' }, + } + } + } + ]); +}; + +exports.testContext = function(options) { + helpers.audit(options, [ + // Use the 'tsn' context + { + setup: 'context tsn', + check: { + input: 'context tsn', + hints: '', + markup: 'VVVVVVVVVVV', + message: '', + predictionsContains: [ 'tsn', 'tsn deep', 'tsn ext', 'tsn exte' ], + args: { + command: { name: 'context' }, + prefix: { + value: mockCommands.commands.tsn, + status: 'VALID', + message: '' + }, + } + }, + exec: { + output: 'Using tsn as a command prefix', + completed: true, + } + }, + // For comparison with earlier + { + setup: 'ext', + check: { + input: 'ext', + hints: ' <text>', + markup: 'VVV', + predictions: [ 'tsn ext', 'tsn exte', 'tsn exten', 'tsn extend' ], + args: { + command: { name: 'tsn ext' }, + text: { + value: undefined, + arg: '', + status: 'INCOMPLETE', + message: '' + }, + } + } + }, + { + setup: 'ext test', + check: { + input: 'ext test', + hints: '', + markup: 'VVVVVVVV', + args: { + command: { name: 'tsn ext' }, + text: { + value: 'test', + arg: ' test', + status: 'VALID', + message: '' + }, + } + }, + exec: { + output: 'Exec: tsnExt text=test', + completed: true, + } + }, + { + setup: 'tsn', + check: { + input: 'tsn', + hints: '', + markup: 'III', + message: '', + predictionsContains: [ 'tsn', 'tsn deep', 'tsn ext', 'tsn exte' ], + args: { + command: { name: 'tsn' }, + } + } + }, + // Does it actually work? + { + setup: 'tsb true', + check: { + input: 'tsb true', + hints: '', + markup: 'VVVVVVVV', + options: [ 'true' ], + message: '', + predictions: [ 'true' ], + unassigned: [ ], + args: { + command: { name: 'tsb' }, + toggle: { value: true, arg: ' true', status: 'VALID', message: '' }, + } + } + }, + { + // Bug 866710 - GCLI should allow argument merging for non-string parameters + setup: 'context tsn ext', + skip: true + }, + { + setup: 'context "tsn ext"', + check: { + input: 'context "tsn ext"', + hints: '', + markup: 'VVVVVVVVVVVVVVVVV', + message: '', + predictions: [ ], + unassigned: [ ], + args: { + command: { name: 'context' }, + prefix: { + value: mockCommands.commands.tsnExt, + status: 'VALID', + message: '' + } + } + }, + exec: { + output: 'Error: Can\'t use \'tsn ext\' as a prefix because it is not a parent command.', + completed: true, + error: true + } + }, + /* + { + setup: 'context "tsn deep"', + check: { + input: 'context "tsn deep"', + hints: '', + markup: 'VVVVVVVVVVVVVVVVVV', + status: 'ERROR', + message: '', + predictions: [ 'tsn deep' ], + unassigned: [ ], + args: { + command: { name: 'context' }, + prefix: { + value: mockCommands.commands.tsnDeep, + status: 'VALID', + message: '' + } + } + }, + exec: { + output: '', + completed: true, + } + }, + */ + { + setup: 'context', + check: { + input: 'context', + hints: ' [prefix]', + markup: 'VVVVVVV', + status: 'VALID', + unassigned: [ ], + args: { + command: { name: 'context' }, + prefix: { value: undefined, arg: '', status: 'VALID', message: '' }, + } + }, + exec: { + output: 'Command prefix is unset', + completed: true, + type: 'string', + error: false + } + } + ]); +}; + + +// });
new file mode 100644 --- /dev/null +++ b/browser/devtools/commandline/test/browser_gcli_fail.js @@ -0,0 +1,99 @@ +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +// define(function(require, exports, module) { + +// <INJECTED SOURCE:START> + +// THIS FILE IS GENERATED FROM SOURCE IN THE GCLI PROJECT +// DO NOT EDIT IT DIRECTLY + +var exports = {}; + +const TEST_URI = "data:text/html;charset=utf-8,<p id='gcli-input'>gcli-testFail.js</p>"; + +function test() { + helpers.addTabWithToolbar(TEST_URI, function(options) { + return helpers.runTests(options, exports); + }).then(finish); +} + +// <INJECTED SOURCE:END> + +'use strict'; + +// var helpers = require('gclitest/helpers'); +// var mockCommands = require('gclitest/mockCommands'); +var cli = require('gcli/cli'); + +var origLogErrors = undefined; + +exports.setup = function(options) { + mockCommands.setup(); + + origLogErrors = cli.logErrors; + cli.logErrors = false; +}; + +exports.shutdown = function(options) { + mockCommands.shutdown(); + + cli.logErrors = origLogErrors; + origLogErrors = undefined; +}; + +exports.testBasic = function(options) { + return helpers.audit(options, [ + { + setup: 'tsfail reject', + exec: { + completed: false, + output: 'rejected promise', + type: 'error', + error: true + } + }, + { + setup: 'tsfail rejecttyped', + exec: { + completed: false, + output: '54', + type: 'number', + error: true + } + }, + { + setup: 'tsfail throwerror', + exec: { + completed: true, + output: 'Error: thrown error', + type: 'error', + error: true + } + }, + { + setup: 'tsfail throwstring', + exec: { + completed: true, + output: 'thrown string', + type: 'error', + error: true + } + }, + { + setup: 'tsfail noerror', + exec: { + completed: true, + output: 'no error', + type: 'string', + error: false + } + } + ]); +}; + + +// });
--- a/browser/devtools/commandline/test/browser_gcli_inputter.js +++ b/browser/devtools/commandline/test/browser_gcli_inputter.js @@ -35,48 +35,40 @@ function test() { 'use strict'; var KeyEvent = require('util/util').KeyEvent; // var assert = require('test/assert'); // var mockCommands = require('gclitest/mockCommands'); var latestEvent = undefined; -var latestOutput = undefined; var latestData = undefined; var outputted = function(ev) { - function updateData() { - latestData = latestOutput.data; - } + latestEvent = ev; - if (latestOutput != null) { - ev.output.onChange.remove(updateData); - } - - latestEvent = ev; - latestOutput = ev.output; - - ev.output.onChange.add(updateData); + ev.output.promise.then(function() { + latestData = ev.output.data; + ev.output.onClose(); + }); }; exports.setup = function(options) { mockCommands.setup(); options.display.requisition.commandOutputManager.onOutput.add(outputted); }; exports.shutdown = function(options) { mockCommands.shutdown(); options.display.requisition.commandOutputManager.onOutput.remove(outputted); }; exports.testOutput = function(options) { latestEvent = undefined; - latestOutput = undefined; latestData = undefined; var inputter = options.display.inputter; var focusManager = options.display.focusManager; inputter.setInput('tss'); var ev0 = { keyCode: KeyEvent.DOM_VK_RETURN }; @@ -98,18 +90,16 @@ exports.testOutput = function(options) { var ev2 = { keyCode: KeyEvent.DOM_VK_F1 }; return inputter.handleKeyUp(ev2).then(function() { assert.ok(!focusManager._recentOutput, 'no recent output happened post F1'); assert.ok(focusManager._helpRequested, 'F1 = help'); var ev3 = { keyCode: KeyEvent.DOM_VK_ESCAPE }; return inputter.handleKeyUp(ev3).then(function() { assert.ok(!focusManager._helpRequested, 'ESCAPE = anti help'); - - latestOutput.onClose(); }); }); }); }; // });
--- a/browser/devtools/commandline/test/browser_gcli_resource.js +++ b/browser/devtools/commandline/test/browser_gcli_resource.js @@ -56,87 +56,87 @@ exports.shutdown = function(options) { }; exports.testAllPredictions1 = function(options) { if (options.isFirefox || options.isJsdom) { assert.log('Skipping checks due to jsdom/firefox document.stylsheets support.'); return; } - var resource = types.getType('resource'); + var resource = types.createType('resource'); return resource.getLookup().then(function(opts) { assert.ok(opts.length > 1, 'have all resources'); return util.promiseEach(opts, function(prediction) { return checkPrediction(resource, prediction); }); }); }; exports.testScriptPredictions = function(options) { if (options.isFirefox || options.isJsdom) { assert.log('Skipping checks due to jsdom/firefox document.stylsheets support.'); return; } - var resource = types.getType({ name: 'resource', include: 'text/javascript' }); + var resource = types.createType({ name: 'resource', include: 'text/javascript' }); return resource.getLookup().then(function(opts) { assert.ok(opts.length > 1, 'have js resources'); return util.promiseEach(opts, function(prediction) { return checkPrediction(resource, prediction); }); }); }; exports.testStylePredictions = function(options) { if (options.isFirefox || options.isJsdom) { assert.log('Skipping checks due to jsdom/firefox document.stylsheets support.'); return; } - var resource = types.getType({ name: 'resource', include: 'text/css' }); + var resource = types.createType({ name: 'resource', include: 'text/css' }); return resource.getLookup().then(function(opts) { assert.ok(opts.length >= 1, 'have css resources'); return util.promiseEach(opts, function(prediction) { return checkPrediction(resource, prediction); }); }); }; exports.testAllPredictions2 = function(options) { if (options.isJsdom) { assert.log('Skipping checks due to jsdom document.stylsheets support.'); return; } - var scriptRes = types.getType({ name: 'resource', include: 'text/javascript' }); + var scriptRes = types.createType({ name: 'resource', include: 'text/javascript' }); return scriptRes.getLookup().then(function(scriptOptions) { - var styleRes = types.getType({ name: 'resource', include: 'text/css' }); + var styleRes = types.createType({ name: 'resource', include: 'text/css' }); return styleRes.getLookup().then(function(styleOptions) { - var allRes = types.getType({ name: 'resource' }); + var allRes = types.createType({ name: 'resource' }); return allRes.getLookup().then(function(allOptions) { assert.is(scriptOptions.length + styleOptions.length, allOptions.length, 'split'); }); }); }); }; exports.testAllPredictions3 = function(options) { if (options.isJsdom) { assert.log('Skipping checks due to jsdom document.stylsheets support.'); return; } - var res1 = types.getType({ name: 'resource' }); + var res1 = types.createType({ name: 'resource' }); return res1.getLookup().then(function(options1) { - var res2 = types.getType('resource'); + var res2 = types.createType('resource'); return res2.getLookup().then(function(options2) { assert.is(options1.length, options2.length, 'type spec'); }); }); }; function checkPrediction(res, prediction) { var name = prediction.name;
--- a/browser/devtools/commandline/test/browser_gcli_split.js +++ b/browser/devtools/commandline/test/browser_gcli_split.js @@ -31,68 +31,69 @@ function test() { }).then(finish); } // <INJECTED SOURCE:END> 'use strict'; // var assert = require('test/assert'); +var cli = require('gcli/cli'); var Requisition = require('gcli/cli').Requisition; var canon = require('gcli/canon'); // var mockCommands = require('gclitest/mockCommands'); exports.setup = function(options) { mockCommands.setup(); }; exports.shutdown = function(options) { mockCommands.shutdown(); }; exports.testSplitSimple = function(options) { var args; - var requ = new Requisition(); + var requisition = new Requisition(); - args = requ._tokenize('s'); - requ._split(args); + args = cli.tokenize('s'); + requisition._split(args); assert.is(0, args.length); - assert.is('s', requ.commandAssignment.arg.text); + assert.is('s', requisition.commandAssignment.arg.text); }; exports.testFlatCommand = function(options) { var args; - var requ = new Requisition(); + var requisition = new Requisition(); - args = requ._tokenize('tsv'); - requ._split(args); + args = cli.tokenize('tsv'); + requisition._split(args); assert.is(0, args.length); - assert.is('tsv', requ.commandAssignment.value.name); + assert.is('tsv', requisition.commandAssignment.value.name); - args = requ._tokenize('tsv a b'); - requ._split(args); - assert.is('tsv', requ.commandAssignment.value.name); + args = cli.tokenize('tsv a b'); + requisition._split(args); + assert.is('tsv', requisition.commandAssignment.value.name); assert.is(2, args.length); assert.is('a', args[0].text); assert.is('b', args[1].text); }; exports.testJavascript = function(options) { if (!canon.getCommand('{')) { assert.log('Skipping testJavascript because { is not registered'); return; } var args; - var requ = new Requisition(); + var requisition = new Requisition(); - args = requ._tokenize('{'); - requ._split(args); + args = cli.tokenize('{'); + requisition._split(args); assert.is(1, args.length); assert.is('', args[0].text); - assert.is('', requ.commandAssignment.arg.text); - assert.is('{', requ.commandAssignment.value.name); + assert.is('', requisition.commandAssignment.arg.text); + assert.is('{', requisition.commandAssignment.value.name); }; // BUG 663081 - add tests for sub commands // });
--- a/browser/devtools/commandline/test/browser_gcli_tokenize.js +++ b/browser/devtools/commandline/test/browser_gcli_tokenize.js @@ -31,172 +31,167 @@ function test() { }).then(finish); } // <INJECTED SOURCE:END> 'use strict'; // var assert = require('test/assert'); -var Requisition = require('gcli/cli').Requisition; +var cli = require('gcli/cli'); exports.testBlanks = function(options) { var args; - var requ = new Requisition(); - args = requ._tokenize(''); + args = cli.tokenize(''); assert.is(1, args.length); assert.is('', args[0].text); assert.is('', args[0].prefix); assert.is('', args[0].suffix); - args = requ._tokenize(' '); + args = cli.tokenize(' '); assert.is(1, args.length); assert.is('', args[0].text); assert.is(' ', args[0].prefix); assert.is('', args[0].suffix); }; exports.testTokSimple = function(options) { var args; - var requ = new Requisition(); - args = requ._tokenize('s'); + args = cli.tokenize('s'); assert.is(1, args.length); assert.is('s', args[0].text); assert.is('', args[0].prefix); assert.is('', args[0].suffix); assert.is('Argument', args[0].type); - args = requ._tokenize('s s'); + args = cli.tokenize('s s'); assert.is(2, args.length); assert.is('s', args[0].text); assert.is('', args[0].prefix); assert.is('', args[0].suffix); assert.is('Argument', args[0].type); assert.is('s', args[1].text); assert.is(' ', args[1].prefix); assert.is('', args[1].suffix); assert.is('Argument', args[1].type); }; exports.testJavascript = function(options) { var args; - var requ = new Requisition(); - args = requ._tokenize('{x}'); + args = cli.tokenize('{x}'); assert.is(1, args.length); assert.is('x', args[0].text); assert.is('{', args[0].prefix); assert.is('}', args[0].suffix); assert.is('ScriptArgument', args[0].type); - args = requ._tokenize('{ x }'); + args = cli.tokenize('{ x }'); assert.is(1, args.length); assert.is('x', args[0].text); assert.is('{ ', args[0].prefix); assert.is(' }', args[0].suffix); assert.is('ScriptArgument', args[0].type); - args = requ._tokenize('{x} {y}'); + args = cli.tokenize('{x} {y}'); assert.is(2, args.length); assert.is('x', args[0].text); assert.is('{', args[0].prefix); assert.is('}', args[0].suffix); assert.is('ScriptArgument', args[0].type); assert.is('y', args[1].text); assert.is(' {', args[1].prefix); assert.is('}', args[1].suffix); assert.is('ScriptArgument', args[1].type); - args = requ._tokenize('{x}{y}'); + args = cli.tokenize('{x}{y}'); assert.is(2, args.length); assert.is('x', args[0].text); assert.is('{', args[0].prefix); assert.is('}', args[0].suffix); assert.is('ScriptArgument', args[0].type); assert.is('y', args[1].text); assert.is('{', args[1].prefix); assert.is('}', args[1].suffix); assert.is('ScriptArgument', args[1].type); - args = requ._tokenize('{'); + args = cli.tokenize('{'); assert.is(1, args.length); assert.is('', args[0].text); assert.is('{', args[0].prefix); assert.is('', args[0].suffix); assert.is('ScriptArgument', args[0].type); - args = requ._tokenize('{ '); + args = cli.tokenize('{ '); assert.is(1, args.length); assert.is('', args[0].text); assert.is('{ ', args[0].prefix); assert.is('', args[0].suffix); assert.is('ScriptArgument', args[0].type); - args = requ._tokenize('{x'); + args = cli.tokenize('{x'); assert.is(1, args.length); assert.is('x', args[0].text); assert.is('{', args[0].prefix); assert.is('', args[0].suffix); assert.is('ScriptArgument', args[0].type); }; exports.testRegularNesting = function(options) { var args; - var requ = new Requisition(); - args = requ._tokenize('{"x"}'); + args = cli.tokenize('{"x"}'); assert.is(1, args.length); assert.is('"x"', args[0].text); assert.is('{', args[0].prefix); assert.is('}', args[0].suffix); assert.is('ScriptArgument', args[0].type); - args = requ._tokenize('{\'x\'}'); + args = cli.tokenize('{\'x\'}'); assert.is(1, args.length); assert.is('\'x\'', args[0].text); assert.is('{', args[0].prefix); assert.is('}', args[0].suffix); assert.is('ScriptArgument', args[0].type); - args = requ._tokenize('"{x}"'); + args = cli.tokenize('"{x}"'); assert.is(1, args.length); assert.is('{x}', args[0].text); assert.is('"', args[0].prefix); assert.is('"', args[0].suffix); assert.is('Argument', args[0].type); - args = requ._tokenize('\'{x}\''); + args = cli.tokenize('\'{x}\''); assert.is(1, args.length); assert.is('{x}', args[0].text); assert.is('\'', args[0].prefix); assert.is('\'', args[0].suffix); assert.is('Argument', args[0].type); }; exports.testDeepNesting = function(options) { var args; - var requ = new Requisition(); - args = requ._tokenize('{{}}'); + args = cli.tokenize('{{}}'); assert.is(1, args.length); assert.is('{}', args[0].text); assert.is('{', args[0].prefix); assert.is('}', args[0].suffix); assert.is('ScriptArgument', args[0].type); - args = requ._tokenize('{{x} {y}}'); + args = cli.tokenize('{{x} {y}}'); assert.is(1, args.length); assert.is('{x} {y}', args[0].text); assert.is('{', args[0].prefix); assert.is('}', args[0].suffix); assert.is('ScriptArgument', args[0].type); - args = requ._tokenize('{{w} {{{x}}}} {y} {{{z}}}'); + args = cli.tokenize('{{w} {{{x}}}} {y} {{{z}}}'); assert.is(3, args.length); assert.is('{w} {{{x}}}', args[0].text); assert.is('{', args[0].prefix); assert.is('}', args[0].suffix); assert.is('ScriptArgument', args[0].type); @@ -205,65 +200,63 @@ exports.testDeepNesting = function(optio assert.is('}', args[1].suffix); assert.is('ScriptArgument', args[1].type); assert.is('{{z}}', args[2].text); assert.is(' {', args[2].prefix); assert.is('}', args[2].suffix); assert.is('ScriptArgument', args[2].type); - args = requ._tokenize('{{w} {{{x}}} {y} {{{z}}}'); + args = cli.tokenize('{{w} {{{x}}} {y} {{{z}}}'); assert.is(1, args.length); assert.is('{w} {{{x}}} {y} {{{z}}}', args[0].text); assert.is('{', args[0].prefix); assert.is('', args[0].suffix); assert.is('ScriptArgument', args[0].type); }; exports.testStrangeNesting = function(options) { var args; - var requ = new Requisition(); // Note: When we get real JS parsing this should break - args = requ._tokenize('{"x}"}'); + args = cli.tokenize('{"x}"}'); assert.is(2, args.length); assert.is('"x', args[0].text); assert.is('{', args[0].prefix); assert.is('}', args[0].suffix); assert.is('ScriptArgument', args[0].type); assert.is('}', args[1].text); assert.is('"', args[1].prefix); assert.is('', args[1].suffix); assert.is('Argument', args[1].type); }; exports.testComplex = function(options) { var args; - var requ = new Requisition(); - args = requ._tokenize(' 1234 \'12 34\''); + args = cli.tokenize(' 1234 \'12 34\''); assert.is(2, args.length); assert.is('1234', args[0].text); assert.is(' ', args[0].prefix); assert.is('', args[0].suffix); assert.is('Argument', args[0].type); assert.is('12 34', args[1].text); assert.is(' \'', args[1].prefix); assert.is('\'', args[1].suffix); assert.is('Argument', args[1].type); - args = requ._tokenize('12\'34 "12 34" \\'); // 12'34 "12 34" \ + args = cli.tokenize('12\'34 "12 34" \\'); // 12'34 "12 34" \ assert.is(3, args.length); assert.is('12\'34', args[0].text); assert.is('', args[0].prefix); assert.is('', args[0].suffix); assert.is('Argument', args[0].type); @@ -275,19 +268,18 @@ exports.testComplex = function(options) assert.is('\\', args[2].text); assert.is(' ', args[2].prefix); assert.is('', args[2].suffix); assert.is('Argument', args[2].type); }; exports.testPathological = function(options) { var args; - var requ = new Requisition(); - args = requ._tokenize('a\\ b \\t\\n\\r \\\'x\\\" \'d'); // a_b \t\n\r \'x\" 'd + args = cli.tokenize('a\\ b \\t\\n\\r \\\'x\\\" \'d'); // a_b \t\n\r \'x\" 'd assert.is(4, args.length); assert.is('a b', args[0].text); assert.is('', args[0].prefix); assert.is('', args[0].suffix); assert.is('Argument', args[0].type);
--- a/browser/devtools/commandline/test/browser_gcli_types.js +++ b/browser/devtools/commandline/test/browser_gcli_types.js @@ -44,25 +44,32 @@ function forEachType(options, typeSpec, typeSpec.requisition = options.display.requisition; // Provide some basic defaults to help selection/delegate/array work if (name === 'selection') { typeSpec.data = [ 'a', 'b' ]; } else if (name === 'delegate') { typeSpec.delegateType = function() { - return types.getType('string'); + return types.createType('string'); }; } else if (name === 'array') { typeSpec.subtype = 'string'; } - var type = types.getType(typeSpec); + var type = types.createType(typeSpec); callback(type); + + // Clean up + delete typeSpec.name; + delete typeSpec.requisition; + delete typeSpec.data; + delete typeSpec.delegateType; + delete typeSpec.subtype; }); } exports.testDefault = function(options) { if (options.isJsdom) { assert.log('Skipping tests due to issues with resource type.'); return; }
--- a/browser/devtools/commandline/test/helpers.js +++ b/browser/devtools/commandline/test/helpers.js @@ -16,21 +16,20 @@ // }; // Now GCLI will emit output on every keypress that both explains the state // of GCLI and can be run as a test case. this.EXPORTED_SYMBOLS = [ 'helpers' ]; var helpers = {}; this.helpers = helpers; let require = (Cu.import("resource://gre/modules/devtools/Require.jsm", {})).require; -Components.utils.import("resource:///modules/devtools/gcli.jsm", {}); +Components.utils.import("resource://gre/modules/devtools/gcli.jsm", {}); let console = (Cu.import("resource://gre/modules/devtools/Console.jsm", {})).console; -let devtools = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools; -let TargetFactory = devtools.TargetFactory; +let TargetFactory = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools.TargetFactory; let Promise = (Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {})).Promise; let assert = { ok: ok, is: is, log: info }; var util = require('util/util'); var converters = require('gcli/converters'); @@ -153,16 +152,17 @@ helpers.addTabWithToolbar = function(url * their return values resolved */ helpers.runTests = function(options, tests) { var testNames = Object.keys(tests).filter(function(test) { return test != "setup" && test != "shutdown"; }); var recover = function(error) { + ok(false, error); console.error(error); }; info("SETUP"); var setupDone = (tests.setup != null) ? Promise.resolve(tests.setup(options)) : Promise.resolve(); @@ -336,17 +336,17 @@ helpers._createDebugCheck = function(opt output += ' input: \'' + input + '\',\n'; output += ' hints: ' + padding + '\'' + hints + '\',\n'; output += ' markup: \'' + helpers._actual.markup(options) + '\',\n'; output += ' cursor: ' + cursor + ',\n'; output += ' current: \'' + helpers._actual.current(options) + '\',\n'; output += ' status: \'' + helpers._actual.status(options) + '\',\n'; output += ' options: ' + outputArray(helpers._actual.options(options)) + ',\n'; - output += ' error: \'' + helpers._actual.message(options) + '\',\n'; + output += ' message: \'' + helpers._actual.message(options) + '\',\n'; output += ' predictions: ' + outputArray(predictions) + ',\n'; output += ' unassigned: ' + outputArray(requisition._unassigned) + ',\n'; output += ' outputState: \'' + helpers._actual.outputState(options) + '\',\n'; output += ' tooltipState: \'' + helpers._actual.tooltipState(options) + '\'' + (command ? ',' : '') +'\n'; if (command) { output += ' args: {\n'; @@ -373,16 +373,18 @@ helpers._createDebugCheck = function(opt output += ' }\n'; } output += ' },\n'; output += ' exec: {\n'; output += ' output: \'\',\n'; output += ' completed: true,\n'; + output += ' type: \'string\',\n'; + output += ' error: false\n'; output += ' }\n'; output += ' }\n'; output += ']);'; return output; }.bind(this), util.errorHandler); }; @@ -697,43 +699,55 @@ helpers._check = function(options, name, * Helper for helpers.audit() to ensure that all the 'exec' properties work. * See helpers.audit for more information. * @param name The name to use in error messages * @param expected See helpers.audit for a list of available exec checks * @return A promise which resolves to undefined when the checks are complete */ helpers._exec = function(options, name, expected) { if (expected == null) { - return Promise.resolve(); + return Promise.resolve({}); } var output = options.display.requisition.exec({ hidden: true }); if ('completed' in expected) { assert.is(output.completed, expected.completed, 'output.completed false for: ' + name); } if (!options.window.document.createElement) { assert.log('skipping output tests (missing doc.createElement) for ' + name); - return Promise.resolve(); + return Promise.resolve({ output: output }); } if (!('output' in expected)) { - return Promise.resolve(); + return Promise.resolve({ output: output }); } - var deferred = Promise.defer(); - var checkOutput = function() { var div = options.window.document.createElement('div'); - var nodePromise = converters.convert(output.data, output.type, 'dom', - options.display.requisition.context); - nodePromise.then(function(node) { + var conversionContext = options.display.requisition.conversionContext; + + if ('type' in expected) { + assert.is(output.type, + expected.type, + 'output.type for: ' + name); + } + + if ('error' in expected) { + assert.is(output.error, + expected.error, + 'output.error for: ' + name); + } + + var convertPromise = converters.convert(output.data, output.type, 'dom', + conversionContext); + return convertPromise.then(function(node) { div.appendChild(node); var actualOutput = div.textContent.trim(); var doTest = function(match, against) { if (match.test(against)) { assert.ok(true, 'html output for ' + name + ' should match ' + match.source); } else { @@ -752,57 +766,44 @@ helpers._exec = function(options, name, expected.output.forEach(function(match) { doTest(match, actualOutput); }); } else { doTest(expected.output, actualOutput); } - deferred.resolve(actualOutput); + return { output: output, text: actualOutput }; }); }; - if (output.completed !== false) { - checkOutput(); - } - else { - var changed = function() { - if (output.completed !== false) { - checkOutput(); - output.onChange.remove(changed); - } - }; - output.onChange.add(changed); - } - - return deferred.promise; + return output.promise.then(checkOutput, checkOutput); }; /** * Helper to setup the test */ helpers._setup = function(options, name, action) { if (typeof action === 'string') { return helpers.setInput(options, action); } if (typeof action === 'function') { return Promise.resolve(action()); } - return Promise.reject('setup must be a string or a function'); + return Promise.reject('\'setup\' property must be a string or a function. Is ' + action); }; /** * Helper to shutdown the test */ -helpers._post = function(name, action, output) { +helpers._post = function(name, action, data) { if (typeof action === 'function') { - return Promise.resolve(action(output)); + return Promise.resolve(action(data.output, data.text)); } return Promise.resolve(action); }; /* * We do some basic response time stats so we can see if we're getting slow */ var totalResponseTime = 0; @@ -938,39 +939,41 @@ helpers.audit = function(options, audits var start = new Date().getTime(); var setupDone = helpers._setup(options, name, audit.setup); return setupDone.then(function(chunkLen) { if (typeof chunkLen !== 'number') { chunkLen = 1; } - var responseTime = (new Date().getTime() - start) / chunkLen; - totalResponseTime += responseTime; - if (responseTime > maxResponseTime) { - maxResponseTime = responseTime; - maxResponseCulprit = assert.currentTest + '/' + name; + + if (assert.currentTest) { + var responseTime = (new Date().getTime() - start) / chunkLen; + totalResponseTime += responseTime; + if (responseTime > maxResponseTime) { + maxResponseTime = responseTime; + maxResponseCulprit = assert.currentTest + '/' + name; + } + averageOver++; } - averageOver++; var checkDone = helpers._check(options, name, audit.check); return checkDone.then(function() { var execDone = helpers._exec(options, name, audit.exec); - return execDone.then(function(output) { - return helpers._post(name, audit.post, output).then(function() { + return execDone.then(function(data) { + return helpers._post(name, audit.post, data).then(function() { if (assert.testLogging) { log('- END \'' + name + '\' in ' + assert.currentTest); } }); }); }); }); - }).then(null, function(ex) { - console.error(ex.stack); - throw(ex); + }).then(function() { + return options.display.inputter.setInput(''); }); }; /** * Compare 2 arrays. */ helpers._arrayIs = function(actual, expected, message) { assert.ok(Array.isArray(actual), 'actual is not an array: ' + message);
--- a/browser/devtools/commandline/test/mockCommands.js +++ b/browser/devtools/commandline/test/mockCommands.js @@ -18,270 +18,193 @@ // <INJECTED SOURCE:START> // THIS FILE IS GENERATED FROM SOURCE IN THE GCLI PROJECT // DO NOT EDIT IT DIRECTLY const { classes: Cc, interfaces: Ci, utils: Cu } = Components; let { require: require, define: define } = Cu.import("resource://gre/modules/devtools/Require.jsm", {}); -Cu.import("resource:///modules/devtools/gcli.jsm", {}); +Cu.import("resource://gre/modules/devtools/gcli.jsm", {}); // <INJECTED SOURCE:END> var mockCommands = {}; +// We use an alias for exports here because this module is used in Firefox +// mochitests where we don't have define/require + +'use strict'; + var util = require('util/util'); var canon = require('gcli/canon'); - var types = require('gcli/types'); -var SelectionType = require('gcli/types/selection').SelectionType; -var DelegateType = require('gcli/types/basic').DelegateType; - - -/** - * Registration and de-registration. - */ -mockCommands.setup = function(opts) { - // setup/shutdown needs to register/unregister types, however that means we - // need to re-initialize mockCommands.option1 and mockCommands.option2 with - // the actual types - mockCommands.option1.type = types.getType('string'); - mockCommands.option2.type = types.getType('number'); - - types.registerType(mockCommands.optionType); - types.registerType(mockCommands.optionValue); - - canon.addCommand(mockCommands.tsv); - canon.addCommand(mockCommands.tsr); - canon.addCommand(mockCommands.tso); - canon.addCommand(mockCommands.tse); - canon.addCommand(mockCommands.tsj); - canon.addCommand(mockCommands.tsb); - canon.addCommand(mockCommands.tss); - canon.addCommand(mockCommands.tsu); - canon.addCommand(mockCommands.tsf); - canon.addCommand(mockCommands.tsn); - canon.addCommand(mockCommands.tsnDif); - canon.addCommand(mockCommands.tsnExt); - canon.addCommand(mockCommands.tsnExte); - canon.addCommand(mockCommands.tsnExten); - canon.addCommand(mockCommands.tsnExtend); - canon.addCommand(mockCommands.tsnDeep); - canon.addCommand(mockCommands.tsnDeepDown); - canon.addCommand(mockCommands.tsnDeepDownNested); - canon.addCommand(mockCommands.tsnDeepDownNestedCmd); - canon.addCommand(mockCommands.tselarr); - canon.addCommand(mockCommands.tsm); - canon.addCommand(mockCommands.tsg); - canon.addCommand(mockCommands.tshidden); - canon.addCommand(mockCommands.tscook); - canon.addCommand(mockCommands.tslong); -}; -mockCommands.shutdown = function(opts) { - canon.removeCommand(mockCommands.tsv); - canon.removeCommand(mockCommands.tsr); - canon.removeCommand(mockCommands.tso); - canon.removeCommand(mockCommands.tse); - canon.removeCommand(mockCommands.tsj); - canon.removeCommand(mockCommands.tsb); - canon.removeCommand(mockCommands.tss); - canon.removeCommand(mockCommands.tsu); - canon.removeCommand(mockCommands.tsf); - canon.removeCommand(mockCommands.tsn); - canon.removeCommand(mockCommands.tsnDif); - canon.removeCommand(mockCommands.tsnExt); - canon.removeCommand(mockCommands.tsnExte); - canon.removeCommand(mockCommands.tsnExten); - canon.removeCommand(mockCommands.tsnExtend); - canon.removeCommand(mockCommands.tsnDeep); - canon.removeCommand(mockCommands.tsnDeepDown); - canon.removeCommand(mockCommands.tsnDeepDownNested); - canon.removeCommand(mockCommands.tsnDeepDownNestedCmd); - canon.removeCommand(mockCommands.tselarr); - canon.removeCommand(mockCommands.tsm); - canon.removeCommand(mockCommands.tsg); - canon.removeCommand(mockCommands.tshidden); - canon.removeCommand(mockCommands.tscook); - canon.removeCommand(mockCommands.tslong); +mockCommands.option1 = { }; +mockCommands.option2 = { }; +mockCommands.option3 = { }; - types.deregisterType(mockCommands.optionType); - types.deregisterType(mockCommands.optionValue); -}; - - -mockCommands.option1 = { type: types.getType('string') }; -mockCommands.option2 = { type: types.getType('number') }; -mockCommands.option3 = { type: types.getType({ - name: 'selection', - lookup: [ - { name: 'one', value: 1 }, - { name: 'two', value: 2 }, - { name: 'three', value: 3 } - ] -})}; - -mockCommands.optionType = new SelectionType({ +mockCommands.optionType = { name: 'optionType', + parent: 'selection', lookup: [ { name: 'option1', value: mockCommands.option1 }, { name: 'option2', value: mockCommands.option2 }, { name: 'option3', value: mockCommands.option3 } ] -}); +}; -mockCommands.optionValue = new DelegateType({ +mockCommands.optionValue = { name: 'optionValue', - delegateType: function(context) { - if (context != null) { - var option = context.getArgsObject().optionType; + parent: 'delegate', + delegateType: function(executionContext) { + if (executionContext != null) { + var option = executionContext.getArgsObject().optionType; if (option != null) { return option.type; } } - return types.getType('blank'); + return types.createType('blank'); } -}); +}; mockCommands.onCommandExec = util.createEvent('commands.onCommandExec'); function createExec(name) { - return function(args, context) { + return function(args, executionContext) { var data = { - command: mockCommands[name], args: args, - context: context + context: executionContext }; mockCommands.onCommandExec(data); var argsOut = Object.keys(args).map(function(key) { return key + '=' + args[key]; }).join(', '); return 'Exec: ' + name + ' ' + argsOut; }; } -mockCommands.tsv = { +var tsv = { name: 'tsv', params: [ { name: 'optionType', type: 'optionType' }, { name: 'optionValue', type: 'optionValue' } ], exec: createExec('tsv') }; -mockCommands.tsr = { +var tsr = { name: 'tsr', params: [ { name: 'text', type: 'string' } ], exec: createExec('tsr') }; -mockCommands.tso = { +var tso = { name: 'tso', params: [ { name: 'text', type: 'string', defaultValue: null } ], exec: createExec('tso') }; -mockCommands.tse = { +var tse = { name: 'tse', params: [ { name: 'node', type: 'node' }, { group: 'options', params: [ { name: 'nodes', type: { name: 'nodelist' } }, { name: 'nodes2', type: { name: 'nodelist', allowEmpty: true } } ] } ], exec: createExec('tse') }; -mockCommands.tsj = { +var tsj = { name: 'tsj', params: [ { name: 'javascript', type: 'javascript' } ], exec: createExec('tsj') }; -mockCommands.tsb = { +var tsb = { name: 'tsb', params: [ { name: 'toggle', type: 'boolean' } ], exec: createExec('tsb') }; -mockCommands.tss = { +var tss = { name: 'tss', exec: createExec('tss') }; -mockCommands.tsu = { +var tsu = { name: 'tsu', params: [ { name: 'num', type: { name: 'number', max: 10, min: -5, step: 3 } } ], exec: createExec('tsu') }; -mockCommands.tsf = { +var tsf = { name: 'tsf', params: [ { name: 'num', type: { name: 'number', allowFloat: true, max: 11.5, min: -6.5, step: 1.5 } } ], exec: createExec('tsf') }; -mockCommands.tsn = { +var tsn = { name: 'tsn' }; -mockCommands.tsnDif = { +var tsnDif = { name: 'tsn dif', description: 'tsn dif', params: [ { name: 'text', type: 'string', description: 'tsn dif text' } ], exec: createExec('tsnDif') }; -mockCommands.tsnExt = { +var tsnExt = { name: 'tsn ext', params: [ { name: 'text', type: 'string' } ], exec: createExec('tsnExt') }; -mockCommands.tsnExte = { +var tsnExte = { name: 'tsn exte', params: [ { name: 'text', type: 'string' } ], exec: createExec('tsnExte') }; -mockCommands.tsnExten = { +var tsnExten = { name: 'tsn exten', params: [ { name: 'text', type: 'string' } ], exec: createExec('tsnExten') }; -mockCommands.tsnExtend = { +var tsnExtend = { name: 'tsn extend', params: [ { name: 'text', type: 'string' } ], exec: createExec('tsnExtend') }; -mockCommands.tsnDeep = { +var tsnDeep = { name: 'tsn deep' }; -mockCommands.tsnDeepDown = { +var tsnDeepDown = { name: 'tsn deep down' }; -mockCommands.tsnDeepDownNested = { +var tsnDeepDownNested = { name: 'tsn deep down nested' }; -mockCommands.tsnDeepDownNestedCmd = { +var tsnDeepDownNestedCmd = { name: 'tsn deep down nested cmd', exec: createExec('tsnDeepDownNestedCmd') }; -mockCommands.tshidden = { +var tshidden = { name: 'tshidden', hidden: true, params: [ { group: 'Options', params: [ { name: 'visible', @@ -303,37 +226,37 @@ mockCommands.tshidden = { hidden: true } ] } ], exec: createExec('tshidden') }; -mockCommands.tselarr = { +var tselarr = { name: 'tselarr', params: [ { name: 'num', type: { name: 'selection', data: [ '1', '2', '3' ] } }, { name: 'arr', type: { name: 'array', subtype: 'string' } } ], exec: createExec('tselarr') }; -mockCommands.tsm = { +var tsm = { name: 'tsm', description: 'a 3-param test selection|string|number', params: [ { name: 'abc', type: { name: 'selection', data: [ 'a', 'b', 'c' ] } }, { name: 'txt', type: 'string' }, { name: 'num', type: { name: 'number', max: 42, min: 0 } } ], exec: createExec('tsm') }; -mockCommands.tsg = { +var tsg = { name: 'tsg', description: 'a param group test', params: [ { name: 'solo', type: { name: 'selection', data: [ 'aaa', 'bbb', 'ccc' ] }, description: 'solo param' }, @@ -366,17 +289,17 @@ mockCommands.tsg = { defaultValue: 42, description: 'num param', option: 'Second' } ], exec: createExec('tsg') }; -mockCommands.tscook = { +var tscook = { name: 'tscook', description: 'param group test to catch problems with cookie command', params: [ { name: 'key', type: 'string', description: 'tscookKeyDesc' }, @@ -406,20 +329,19 @@ mockCommands.tscook = { description: 'tscookSecureDesc' } ] } ], exec: createExec('tscook') }; -mockCommands.tslong = { +var tslong = { name: 'tslong', description: 'long param tests to catch problems with the jsb command', - returnValue:'string', params: [ { name: 'msg', type: 'string', description: 'msg Desc' }, { group: "Options Desc", @@ -468,10 +390,151 @@ mockCommands.tslong = { defaultValue: "collapse" } ] } ], exec: createExec('tslong') }; +var tsfail = { + name: 'tsfail', + description: 'test errors', + params: [ + { + name: 'method', + type: { + name: 'selection', + data: [ + 'reject', 'rejecttyped', + 'throwerror', 'throwstring', 'throwinpromise', + 'noerror' + ] + } + } + ], + exec: function(args, context) { + if (args.method === 'reject') { + var deferred = context.defer(); + setTimeout(function() { + deferred.reject('rejected promise'); + }, 10); + return deferred.promise; + } + + if (args.method === 'rejecttyped') { + var deferred = context.defer(); + setTimeout(function() { + deferred.reject(context.typedData('number', 54)); + }, 10); + return deferred.promise; + } + + if (args.method === 'throwinpromise') { + var deferred = context.defer(); + setTimeout(function() { + deferred.resolve('should be lost'); + }, 10); + return deferred.promise.then(function() { + var t = null; + return t.foo; + }); + } + + if (args.method === 'throwerror') { + throw new Error('thrown error'); + } + + if (args.method === 'throwstring') { + throw 'thrown string'; + } + + return 'no error'; + } +}; + +mockCommands.commands = {}; + +/** + * Registration and de-registration. + */ +mockCommands.setup = function(opts) { + // setup/shutdown needs to register/unregister types, however that means we + // need to re-initialize mockCommands.option1 and mockCommands.option2 with + // the actual types + mockCommands.option1.type = types.createType('string'); + mockCommands.option2.type = types.createType('number'); + mockCommands.option3.type = types.createType({ + name: 'selection', + lookup: [ + { name: 'one', value: 1 }, + { name: 'two', value: 2 }, + { name: 'three', value: 3 } + ] + }); + + types.addType(mockCommands.optionType); + types.addType(mockCommands.optionValue); + + mockCommands.commands.tsv = canon.addCommand(tsv); + mockCommands.commands.tsr = canon.addCommand(tsr); + mockCommands.commands.tso = canon.addCommand(tso); + mockCommands.commands.tse = canon.addCommand(tse); + mockCommands.commands.tsj = canon.addCommand(tsj); + mockCommands.commands.tsb = canon.addCommand(tsb); + mockCommands.commands.tss = canon.addCommand(tss); + mockCommands.commands.tsu = canon.addCommand(tsu); + mockCommands.commands.tsf = canon.addCommand(tsf); + mockCommands.commands.tsn = canon.addCommand(tsn); + mockCommands.commands.tsnDif = canon.addCommand(tsnDif); + mockCommands.commands.tsnExt = canon.addCommand(tsnExt); + mockCommands.commands.tsnExte = canon.addCommand(tsnExte); + mockCommands.commands.tsnExten = canon.addCommand(tsnExten); + mockCommands.commands.tsnExtend = canon.addCommand(tsnExtend); + mockCommands.commands.tsnDeep = canon.addCommand(tsnDeep); + mockCommands.commands.tsnDeepDown = canon.addCommand(tsnDeepDown); + mockCommands.commands.tsnDeepDownNested = canon.addCommand(tsnDeepDownNested); + mockCommands.commands.tsnDeepDownNestedCmd = canon.addCommand(tsnDeepDownNestedCmd); + mockCommands.commands.tselarr = canon.addCommand(tselarr); + mockCommands.commands.tsm = canon.addCommand(tsm); + mockCommands.commands.tsg = canon.addCommand(tsg); + mockCommands.commands.tshidden = canon.addCommand(tshidden); + mockCommands.commands.tscook = canon.addCommand(tscook); + mockCommands.commands.tslong = canon.addCommand(tslong); + mockCommands.commands.tsfail = canon.addCommand(tsfail); +}; + +mockCommands.shutdown = function(opts) { + canon.removeCommand(tsv); + canon.removeCommand(tsr); + canon.removeCommand(tso); + canon.removeCommand(tse); + canon.removeCommand(tsj); + canon.removeCommand(tsb); + canon.removeCommand(tss); + canon.removeCommand(tsu); + canon.removeCommand(tsf); + canon.removeCommand(tsn); + canon.removeCommand(tsnDif); + canon.removeCommand(tsnExt); + canon.removeCommand(tsnExte); + canon.removeCommand(tsnExten); + canon.removeCommand(tsnExtend); + canon.removeCommand(tsnDeep); + canon.removeCommand(tsnDeepDown); + canon.removeCommand(tsnDeepDownNested); + canon.removeCommand(tsnDeepDownNestedCmd); + canon.removeCommand(tselarr); + canon.removeCommand(tsm); + canon.removeCommand(tsg); + canon.removeCommand(tshidden); + canon.removeCommand(tscook); + canon.removeCommand(tslong); + canon.removeCommand(tsfail); + + types.removeType(mockCommands.optionType); + types.removeType(mockCommands.optionValue); + + mockCommands.commands = {}; +}; + // });
--- a/browser/devtools/debugger/CmdDebugger.jsm +++ b/browser/devtools/debugger/CmdDebugger.jsm @@ -1,16 +1,16 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components; this.EXPORTED_SYMBOLS = [ ]; -Cu.import("resource:///modules/devtools/gcli.jsm"); +Cu.import("resource://gre/modules/devtools/gcli.jsm"); Cu.import('resource://gre/modules/XPCOMUtils.jsm'); XPCOMUtils.defineLazyModuleGetter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "console", "resource://gre/modules/devtools/Console.jsm"); @@ -152,17 +152,17 @@ gcli.addCommand({ description: gcli.lookup("breakaddlineFileDesc") }, { name: "line", type: { name: "number", min: 1, step: 10 }, description: gcli.lookup("breakaddlineLineDesc") } ], - returnType: "html", + returnType: "string", exec: function(args, context) { args.type = "line"; let dbg = getPanel(context, "jsdebugger"); if (!dbg) { return gcli.lookup("debuggerStopped"); } var deferred = context.defer(); @@ -196,17 +196,17 @@ gcli.addCommand({ return dbg == null ? null : Object.keys(dbg.getAllBreakpoints()).length - 1; }, }, description: gcli.lookup("breakdelBreakidDesc") } ], - returnType: "html", + returnType: "string", exec: function(args, context) { let dbg = getPanel(context, "jsdebugger"); if (!dbg) { return gcli.lookup("debuggerStopped"); } let breakpoints = dbg.getAllBreakpoints(); let id = Object.keys(breakpoints)[args.breakid]; @@ -376,17 +376,17 @@ gcli.addCommand({ /** * 'dbg list' command */ gcli.addCommand({ name: "dbg list", description: gcli.lookup("dbgListSourcesDesc"), params: [], - returnType: "html", + returnType: "dom", exec: function(args, context) { let dbg = getPanel(context, "jsdebugger"); let doc = context.environment.chromeDocument; if (!dbg) { return gcli.lookup("debuggerClosed"); } let sources = dbg._view.Sources.values; let div = createXHTMLElement(doc, "div");
--- a/browser/devtools/debugger/debugger-controller.js +++ b/browser/devtools/debugger/debugger-controller.js @@ -792,17 +792,19 @@ StackFrames.prototype = { aScope._fetched = true; let env = aScope._sourceEnvironment; switch (env.type) { case "with": case "object": // Add nodes for every variable in scope. this.activeThread.pauseGrip(env.object).getPrototypeAndProperties(function(aResponse) { - this._insertScopeVariables(aResponse.ownProperties, aScope); + let { ownProperties, safeGetterValues } = aResponse; + this._mergeSafeGetterValues(ownProperties, safeGetterValues); + this._insertScopeVariables(ownProperties, aScope); // Signal that variables have been fetched. window.dispatchEvent(document, "Debugger:FetchedVariables"); DebuggerView.Variables.commitHierarchy(); }.bind(this)); break; case "block": case "function": @@ -898,19 +900,21 @@ StackFrames.prototype = { // Fetch the properties only once. if (aVar._fetched) { return; } aVar._fetched = true; let grip = aVar._sourceGrip; this.activeThread.pauseGrip(grip).getPrototypeAndProperties(function(aResponse) { - let { ownProperties, prototype } = aResponse; + let { ownProperties, prototype, safeGetterValues } = aResponse; let sortable = VariablesView.NON_SORTABLE_CLASSES.indexOf(grip.class) == -1; + this._mergeSafeGetterValues(ownProperties, safeGetterValues); + // Add all the variable properties. if (ownProperties) { aVar.addProperties(ownProperties, { // Not all variables need to force sorted properties. sorted: sortable, // Expansion handlers must be set after the properties are added. callback: this._addVarExpander }); @@ -928,16 +932,43 @@ StackFrames.prototype = { // Signal that properties have been fetched. window.dispatchEvent(document, "Debugger:FetchedProperties"); DebuggerView.Variables.commitHierarchy(); }.bind(this)); }, /** + * Merge the safe getter values descriptors into the "own properties" object + * that comes from a "prototypeAndProperties" response packet. This is needed + * for Variables View. + * + * @private + * @param object aOwnProperties + * The |ownProperties| object that will get the new safe getter values. + * @param object aSafeGetterValues + * The |safeGetterValues| object. + */ + _mergeSafeGetterValues: + function SF__mergeSafeGetterValues(aOwnProperties, aSafeGetterValues) { + // Merge the safe getter values into one object such that we can use it + // in VariablesView. + for (let name of Object.keys(aSafeGetterValues)) { + if (name in aOwnProperties) { + aOwnProperties[name].getterValue = aSafeGetterValues[name].getterValue; + aOwnProperties[name].getterPrototypeLevel = aSafeGetterValues[name] + .getterPrototypeLevel; + } + else { + aOwnProperties[name] = aSafeGetterValues[name]; + } + } + }, + + /** * Adds the specified stack frame to the list. * * @param object aFrame * The new frame to add. */ _addFrame: function SF__addFrame(aFrame) { let depth = aFrame.depth; let { url, line } = aFrame.where;
--- a/browser/devtools/debugger/debugger-toolbar.js +++ b/browser/devtools/debugger/debugger-toolbar.js @@ -401,17 +401,16 @@ create({ constructor: StackFramesView, p addFrame: function DVSF_addFrame(aFrameTitle, aSourceLocation, aLineNumber, aDepth) { // Create the element node and menu entry for the stack frame item. let frameView = this._createFrameView.apply(this, arguments); let menuEntry = this._createMenuEntry.apply(this, arguments); // Append a stack frame item to this container. let stackframeItem = this.push(frameView, { index: 0, /* specifies on which position should the item be appended */ - relaxed: true, /* this container should allow dupes & degenerates */ attachment: { popup: menuEntry, depth: aDepth }, attributes: [ ["contextmenu", "stackframesMenupopup"], ["tooltiptext", aSourceLocation] ], @@ -941,17 +940,17 @@ FilterView.prototype = { } // Synchronize with the view's filtered sources container. DebuggerView.FilteredSources.syncFileSearch(); // Hide all the groups with no visible children. view.node.hideEmptyGroups(); // Ensure the currently selected item is visible. - view.node.ensureSelectionIsVisible(true); + view.node.ensureSelectionIsVisible({ withGroup: true }); // Remember the previously searched file to avoid redundant filtering. this._prevSearchedFile = aFile; }, /** * Performs a line search if necessary. * (Jump to lines in the currently visible source).
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-09.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-09.js @@ -51,20 +51,23 @@ function testFrameParameters() "Should have three frames."); is(globalNodes[1].querySelector(".name").getAttribute("value"), "SpecialPowers", "Should have the right property name for |SpecialPowers|."); is(globalNodes[1].querySelector(".value").getAttribute("value"), "[object Object]", "Should have the right property value for |SpecialPowers|."); - is(globalNodes[3].querySelector(".name").getAttribute("value"), "document", + let globalScopeObject = gDebugger.DebuggerView.Variables.getScopeForNode(globalScope); + let documentNode = globalScopeObject.get("document"); + + is(documentNode.target.querySelector(".name").getAttribute("value"), "document", "Should have the right property name for |document|."); - is(globalNodes[3].querySelector(".value").getAttribute("value"), "[object HTMLDocument]", + is(documentNode.target.querySelector(".value").getAttribute("value"), "[object HTMLDocument]", "Should have the right property value for |document|."); let len = globalNodes.length - 1; is(globalNodes[len].querySelector(".name").getAttribute("value"), "window", "Should have the right property name for |window|."); is(globalNodes[len].querySelector(".value").getAttribute("value"), "[object Window]", "Should have the right property value for |window|.");
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-10.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-10.js @@ -56,20 +56,23 @@ function testWithFrame() is(scopes.childNodes.length, 5, "Should have 5 variable scopes."); is(innerNodes[1].querySelector(".name").getAttribute("value"), "one", "Should have the right property name for |one|."); is(innerNodes[1].querySelector(".value").getAttribute("value"), "1", "Should have the right property value for |one|."); - is(globalNodes[3].querySelector(".name").getAttribute("value"), "document", + let globalScopeObject = gDebugger.DebuggerView.Variables.getScopeForNode(globalScope); + let documentNode = globalScopeObject.get("document"); + + is(documentNode.target.querySelector(".name").getAttribute("value"), "document", "Should have the right property name for |document|."); - is(globalNodes[3].querySelector(".value").getAttribute("value"), "[object HTMLDocument]", + is(documentNode.target.querySelector(".value").getAttribute("value"), "[object HTMLDocument]", "Should have the right property value for |document|."); let len = globalNodes.length - 1; is(globalNodes[len].querySelector(".name").getAttribute("value"), "window", "Should have the right property name for |window|."); is(globalNodes[len].querySelector(".value").getAttribute("value"), "[object Window]", "Should have the right property value for |window|.");
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-11.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-11.js @@ -56,25 +56,27 @@ function testFrameParameters() "Should have the right property value for |button|."); is(anonymousNodes[2].querySelector(".name").getAttribute("value"), "buttonAsProto", "Should have the right property name for |buttonAsProto|."); is(anonymousNodes[2].querySelector(".value").getAttribute("value"), "[object Object]", "Should have the right property value for |buttonAsProto|."); - is(globalNodes[3].querySelector(".name").getAttribute("value"), "document", + let globalScopeObject = gVars.getScopeForNode(globalScope); + let documentNode = globalScopeObject.get("document"); + + is(documentNode.target.querySelector(".name").getAttribute("value"), "document", "Should have the right property name for |document|."); - is(globalNodes[3].querySelector(".value").getAttribute("value"), "[object HTMLDocument]", + is(documentNode.target.querySelector(".value").getAttribute("value"), "[object HTMLDocument]", "Should have the right property value for |document|."); let buttonNode = gVars.getItemForNode(anonymousNodes[1]); let buttonAsProtoNode = gVars.getItemForNode(anonymousNodes[2]); - let documentNode = gVars.getItemForNode(globalNodes[3]); is(buttonNode.expanded, false, "The buttonNode should not be expanded at this point."); is(buttonAsProtoNode.expanded, false, "The buttonAsProtoNode should not be expanded at this point."); is(documentNode.expanded, false, "The documentNode should not be expanded at this point."); @@ -126,133 +128,90 @@ function testFrameParameters() is(documentNode.get("__proto__").target.querySelector(".name") .getAttribute("value"), "__proto__", "Should have the right property name for '__proto__' in documentNode."); ok(documentNode.get("__proto__").target.querySelector(".value") .getAttribute("value").search(/object/) != -1, "'__proto__' in documentNode should be an object."); - let buttonProtoNode = buttonNode.get("__proto__"); + // Now the main course: make sure that the native getters for WebIDL + // attributes have been called and a value has been returned. + is(buttonNode.get("type").target.querySelector(".name") + .getAttribute("value"), "type", + "Should have the right property name for 'type' in buttonProtoNode."); + is(buttonNode.get("type").target.querySelector(".value") + .getAttribute("value"), '"submit"', + "'type' in buttonProtoNode should have the right value."); + is(buttonNode.get("formMethod").target.querySelector(".name") + .getAttribute("value"), "formMethod", + "Should have the right property name for 'formMethod' in buttonProtoNode."); + is(buttonNode.get("formMethod").target.querySelector(".value") + .getAttribute("value"), '""', + "'formMethod' in buttonProtoNode should have the right value."); + + is(documentNode.get("domain").target.querySelector(".name") + .getAttribute("value"), "domain", + "Should have the right property name for 'domain' in documentProtoNode."); + is(documentNode.get("domain").target.querySelector(".value") + .getAttribute("value"), '"example.com"', + "'domain' in documentProtoNode should have the right value."); + is(documentNode.get("cookie").target.querySelector(".name") + .getAttribute("value"), "cookie", + "Should have the right property name for 'cookie' in documentProtoNode."); + is(documentNode.get("cookie").target.querySelector(".value") + .getAttribute("value"), '""', + "'cookie' in documentProtoNode should have the right value."); + let buttonAsProtoProtoNode = buttonAsProtoNode.get("__proto__"); - let documentProtoNode = documentNode.get("__proto__"); - is(buttonProtoNode.expanded, false, - "The buttonProtoNode should not be expanded at this point."); is(buttonAsProtoProtoNode.expanded, false, "The buttonAsProtoProtoNode should not be expanded at this point."); - is(documentProtoNode.expanded, false, - "The documentProtoNode should not be expanded at this point."); // Expand the prototypes of 'button', 'buttonAsProto' and 'document' // tree nodes. This causes their properties to be retrieved and // displayed. - buttonProtoNode.expand(); buttonAsProtoProtoNode.expand(); - documentProtoNode.expand(); - is(buttonProtoNode.expanded, true, - "The buttonProtoNode should be expanded at this point."); is(buttonAsProtoProtoNode.expanded, true, "The buttonAsProtoProtoNode should be expanded at this point."); - is(documentProtoNode.expanded, true, - "The documentProtoNode should be expanded at this point."); // Poll every few milliseconds until the properties are retrieved. // It's important to set the timer in the chrome window, because the // content window timers are disabled while the debuggee is paused. let count2 = 0; let intervalID1 = window.setInterval(function(){ info("count2: " + count2); if (++count2 > 50) { ok(false, "Timed out while polling for the properties."); window.clearInterval(intervalID1); return resumeAndFinish(); } - if (!buttonProtoNode._retrieved || - !buttonAsProtoProtoNode._retrieved || - !documentProtoNode._retrieved) { + if (!buttonAsProtoProtoNode._retrieved) { return; } window.clearInterval(intervalID1); - // Now the main course: make sure that the native getters for WebIDL - // attributes have been called and a value has been returned. - is(buttonProtoNode.get("type").target.querySelector(".name") + // Test this more involved case that reuses an object that is + // present in another cache line. + is(buttonAsProtoProtoNode.get("type").target.querySelector(".name") .getAttribute("value"), "type", - "Should have the right property name for 'type' in buttonProtoNode."); - is(buttonProtoNode.get("type").target.querySelector(".value") + "Should have the right property name for 'type' in buttonAsProtoProtoProtoNode."); + is(buttonAsProtoProtoNode.get("type").target.querySelector(".value") .getAttribute("value"), '"submit"', - "'type' in buttonProtoNode should have the right value."); - is(buttonProtoNode.get("formMethod").target.querySelector(".name") + "'type' in buttonAsProtoProtoProtoNode should have the right value."); + is(buttonAsProtoProtoNode.get("formMethod").target.querySelector(".name") .getAttribute("value"), "formMethod", - "Should have the right property name for 'formMethod' in buttonProtoNode."); - is(buttonProtoNode.get("formMethod").target.querySelector(".value") + "Should have the right property name for 'formMethod' in buttonAsProtoProtoProtoNode."); + is(buttonAsProtoProtoNode.get("formMethod").target.querySelector(".value") .getAttribute("value"), '""', - "'formMethod' in buttonProtoNode should have the right value."); - - is(documentProtoNode.get("domain").target.querySelector(".name") - .getAttribute("value"), "domain", - "Should have the right property name for 'domain' in documentProtoNode."); - is(documentProtoNode.get("domain").target.querySelector(".value") - .getAttribute("value"), '"example.com"', - "'domain' in documentProtoNode should have the right value."); - is(documentProtoNode.get("cookie").target.querySelector(".name") - .getAttribute("value"), "cookie", - "Should have the right property name for 'cookie' in documentProtoNode."); - is(documentProtoNode.get("cookie").target.querySelector(".value") - .getAttribute("value"), '""', - "'cookie' in documentProtoNode should have the right value."); - - let buttonAsProtoProtoProtoNode = buttonAsProtoProtoNode.get("__proto__"); - - is(buttonAsProtoProtoProtoNode.expanded, false, - "The buttonAsProtoProtoProtoNode should not be expanded at this point."); - - // Expand the prototype of the prototype of 'buttonAsProto' tree - // node. This causes its properties to be retrieved and displayed. - buttonAsProtoProtoProtoNode.expand(); + "'formMethod' in buttonAsProtoProtoProtoNode should have the right value."); - is(buttonAsProtoProtoProtoNode.expanded, true, - "The buttonAsProtoProtoProtoNode should be expanded at this point."); - - // Poll every few milliseconds until the properties are retrieved. - // It's important to set the timer in the chrome window, because the - // content window timers are disabled while the debuggee is paused. - let count3 = 0; - let intervalID2 = window.setInterval(function(){ - info("count3: " + count3); - if (++count3 > 50) { - ok(false, "Timed out while polling for the properties."); - window.clearInterval(intervalID2); - return resumeAndFinish(); - } - if (!buttonAsProtoProtoProtoNode._retrieved) { - return; - } - window.clearInterval(intervalID2); - - // Test this more involved case that reuses an object that is - // present in another cache line. - is(buttonAsProtoProtoProtoNode.get("type").target.querySelector(".name") - .getAttribute("value"), "type", - "Should have the right property name for 'type' in buttonAsProtoProtoProtoNode."); - is(buttonAsProtoProtoProtoNode.get("type").target.querySelector(".value") - .getAttribute("value"), '"submit"', - "'type' in buttonAsProtoProtoProtoNode should have the right value."); - is(buttonAsProtoProtoProtoNode.get("formMethod").target.querySelector(".name") - .getAttribute("value"), "formMethod", - "Should have the right property name for 'formMethod' in buttonAsProtoProtoProtoNode."); - is(buttonAsProtoProtoProtoNode.get("formMethod").target.querySelector(".value") - .getAttribute("value"), '""', - "'formMethod' in buttonAsProtoProtoProtoNode should have the right value."); - - resumeAndFinish(); - }, 100); + resumeAndFinish(); }, 100); }, 100); }}, 0); }, false); EventUtils.sendMouseEvent({ type: "click" }, content.document.querySelector("button"), content.window);
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-01.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-01.js @@ -144,47 +144,45 @@ function testVariablesFiltering() is(innerScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 1, "There should be 1 variable displayed in the inner scope"); is(mathScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0, "There should be 0 variables displayed in the math scope"); is(testScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0, "There should be 0 variables displayed in the test scope"); is(loadScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0, "There should be 0 variables displayed in the load scope"); - is(globalScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 1, - "There should be 1 variable displayed in the global scope"); + is(globalScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 3, + "There should be 3 variables displayed in the global scope"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length, 6, - "There should be 6 properties displayed in the inner scope"); + ok(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length > 6, + "There should be more than 6 properties displayed in the inner scope"); is(mathScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the math scope"); is(testScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the test scope"); is(loadScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the load scope"); is(globalScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the global scope"); is(innerScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[0].getAttribute("value"), "this", "The only inner variable displayed should be 'this'"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[0].getAttribute("value"), - "window", "The first inner property displayed should be 'window'"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[1].getAttribute("value"), - "document", "The second inner property displayed should be 'document'"); is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[2].getAttribute("value"), - "location", "The third inner property displayed should be 'location'"); + "window", "The third inner property displayed should be 'window'"); is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[3].getAttribute("value"), - "__proto__", "The fourth inner property displayed should be '__proto__'"); + "document", "The fourth inner property displayed should be 'document'"); is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[4].getAttribute("value"), - "Location", "The fifth inner property displayed should be 'Location'"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[5].getAttribute("value"), - "Location", "The sixth inner property displayed should be 'Location'"); + "location", "The fifth inner property displayed should be 'location'"); is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[0].getAttribute("value"), - "Location", "The only global variable displayed should be 'Location'"); + "location", "The first global variable displayed should be 'location'"); + is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[1].getAttribute("value"), + "locationbar", "The second global variable displayed should be 'locationbar'"); + is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[2].getAttribute("value"), + "Location", "The third global variable displayed should be 'Location'"); } function test2() { innerScopeItem.collapse(); mathScopeItem.collapse(); testScopeItem.collapse(); loadScopeItem.collapse(); @@ -232,47 +230,45 @@ function testVariablesFiltering() is(innerScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 1, "There should be 1 variable displayed in the inner scope"); is(mathScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0, "There should be 0 variables displayed in the math scope"); is(testScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0, "There should be 0 variables displayed in the test scope"); is(loadScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0, "There should be 0 variables displayed in the load scope"); - is(globalScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 1, - "There should be 1 variable displayed in the global scope"); + is(globalScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 3, + "There should be 3 variables displayed in the global scope"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length, 6, - "There should be 6 properties displayed in the inner scope"); + ok(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length > 6, + "There should be more than 6 properties displayed in the inner scope"); is(mathScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the math scope"); is(testScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the test scope"); is(loadScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the load scope"); is(globalScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the global scope"); is(innerScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[0].getAttribute("value"), "this", "The only inner variable displayed should be 'this'"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[0].getAttribute("value"), - "window", "The first inner property displayed should be 'window'"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[1].getAttribute("value"), - "document", "The second inner property displayed should be 'document'"); is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[2].getAttribute("value"), - "location", "The third inner property displayed should be 'location'"); + "window", "The third inner property displayed should be 'window'"); is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[3].getAttribute("value"), - "__proto__", "The fourth inner property displayed should be '__proto__'"); + "document", "The fourth inner property displayed should be 'document'"); is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[4].getAttribute("value"), - "Location", "The fifth inner property displayed should be 'Location'"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[5].getAttribute("value"), - "Location", "The sixth inner property displayed should be 'Location'"); + "location", "The fifth inner property displayed should be 'location'"); is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[0].getAttribute("value"), - "Location", "The only global variable displayed should be 'Location'"); + "location", "The first global variable displayed should be 'location'"); + is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[1].getAttribute("value"), + "locationbar", "The second global variable displayed should be 'locationbar'"); + is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[2].getAttribute("value"), + "Location", "The second global variable displayed should be 'Location'"); } var scopes = gDebugger.DebuggerView.Variables._list, innerScope = scopes.querySelectorAll(".variables-view-scope")[0], mathScope = scopes.querySelectorAll(".variables-view-scope")[1], testScope = scopes.querySelectorAll(".variables-view-scope")[2], loadScope = scopes.querySelectorAll(".variables-view-scope")[3], globalScope = scopes.querySelectorAll(".variables-view-scope")[4];
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-02.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-02.js @@ -87,18 +87,18 @@ function testVariablesFiltering() "There should be 0 variables displayed in the math scope"); is(testScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0, "There should be 0 variables displayed in the test scope"); is(loadScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0, "There should be 0 variables displayed in the load scope"); is(globalScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 2, "There should be 2 variables displayed in the global scope"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length, 8, - "There should be 8 properties displayed in the inner scope"); + ok(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length > 3, + "There should be more than 3 properties displayed in the inner scope"); is(mathScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the math scope"); is(testScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the test scope"); is(loadScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the load scope"); is(globalScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the global scope"); @@ -106,26 +106,16 @@ function testVariablesFiltering() is(innerScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[0].getAttribute("value"), "this", "The only inner variable displayed should be 'this'"); is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[0].getAttribute("value"), "document", "The first inner property displayed should be 'document'"); is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[1].getAttribute("value"), "window", "The second inner property displayed should be 'window'"); is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[2].getAttribute("value"), "document", "The third inner property displayed should be 'document'"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[3].getAttribute("value"), - "location", "The fourth inner property displayed should be 'location'"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[4].getAttribute("value"), - "__proto__", "The fifth inner property displayed should be '__proto__'"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[5].getAttribute("value"), - "__proto__", "The sixth inner property displayed should be '__proto__'"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[6].getAttribute("value"), - "HTMLDocument", "The seventh inner property displayed should be 'HTMLDocument'"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[7].getAttribute("value"), - "HTMLDocument", "The eight inner property displayed should be 'HTMLDocument'"); is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[0].getAttribute("value"), "document", "The first global variable displayed should be 'document'"); is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[1].getAttribute("value"), "HTMLDocument", "The first global variable displayed should be 'HTMLDocument'"); } function test2() @@ -163,36 +153,36 @@ function testVariablesFiltering() write("htmldocument"); is(thisItem.expanded, true, "The local scope 'this' should be expanded"); is(windowItem.expanded, true, "The local scope 'this.window' should be expanded"); is(documentItem.expanded, true, "The local scope 'this.window.document' should be expanded"); - is(locationItem.expanded, false, - "The local scope 'this.window.document.location' should not be expanded"); + is(locationItem.expanded, true, + "The local scope 'this.window.document.location' should be expanded"); documentItem.toggle(); documentItem.toggle(); locationItem.toggle(); is(innerScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 1, "There should be 1 variable displayed in the inner scope"); is(mathScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0, "There should be 0 variables displayed in the math scope"); is(testScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0, "There should be 0 variables displayed in the test scope"); is(loadScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0, "There should be 0 variables displayed in the load scope"); is(globalScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 2, "There should be 2 variables displayed in the global scope"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length, 8, - "There should be 8 properties displayed in the inner scope"); + ok(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length > 3, + "There should be more than 3 properties displayed in the inner scope"); is(mathScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the math scope"); is(testScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the test scope"); is(loadScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the load scope"); is(globalScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the global scope"); @@ -200,26 +190,16 @@ function testVariablesFiltering() is(innerScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[0].getAttribute("value"), "this", "The only inner variable displayed should be 'this'"); is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[0].getAttribute("value"), "document", "The first inner property displayed should be 'document'"); is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[1].getAttribute("value"), "window", "The second inner property displayed should be 'window'"); is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[2].getAttribute("value"), "document", "The third inner property displayed should be 'document'"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[3].getAttribute("value"), - "location", "The fourth inner property displayed should be 'location'"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[4].getAttribute("value"), - "__proto__", "The fifth inner property displayed should be '__proto__'"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[5].getAttribute("value"), - "__proto__", "The sixth inner property displayed should be '__proto__'"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[6].getAttribute("value"), - "HTMLDocument", "The seventh inner property displayed should be 'HTMLDocument'"); - is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[7].getAttribute("value"), - "HTMLDocument", "The eight inner property displayed should be 'HTMLDocument'"); is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[0].getAttribute("value"), "document", "The first global variable displayed should be 'document'"); is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[1].getAttribute("value"), "HTMLDocument", "The first global variable displayed should be 'HTMLDocument'"); } var scopes = gDebugger.DebuggerView.Variables._list,
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-05.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-05.js @@ -122,18 +122,18 @@ function testVariablesFiltering() "There should be some variables displayed in the global scope"); is(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the inner scope"); is(mathScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the math scope"); is(testScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the test scope"); - is(loadScope.querySelectorAll(".variables-view-property:not([non-match])").length, 1, - "There should be 1 property displayed in the load scope"); + ok(loadScope.querySelectorAll(".variables-view-property:not([non-match])").length > 1, + "There should be more than one property displayed in the load scope"); isnot(globalScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be some properties displayed in the global scope"); } function test4() { backspace(1); @@ -152,18 +152,18 @@ function testVariablesFiltering() "There should be some variables displayed in the global scope"); is(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the inner scope"); is(mathScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the math scope"); is(testScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be 0 properties displayed in the test scope"); - is(loadScope.querySelectorAll(".variables-view-property:not([non-match])").length, 1, - "There should be 1 property displayed in the load scope"); + ok(loadScope.querySelectorAll(".variables-view-property:not([non-match])").length > 1, + "There should be more than one properties displayed in the load scope"); isnot(globalScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0, "There should be some properties displayed in the global scope"); } var scopes = gDebugger.DebuggerView.Variables._list, innerScope = scopes.querySelectorAll(".variables-view-scope")[0], mathScope = scopes.querySelectorAll(".variables-view-scope")[1], testScope = scopes.querySelectorAll(".variables-view-scope")[2],
--- a/browser/devtools/debugger/test/head.js +++ b/browser/devtools/debugger/test/head.js @@ -178,16 +178,17 @@ function debug_tab_pane(aURL, aOnDebuggi gDevTools.showToolbox(target, "jsdebugger").then(function(toolbox) { let dbg = toolbox.getCurrentPanel(); // Wait for the initial resume... dbg.panelWin.gClient.addOneTimeListener("resumed", function() { info("Debugger has started"); dbg._view.Variables.lazyEmpty = false; dbg._view.Variables.lazyAppend = false; + dbg._view.Variables.lazyExpand = false; aOnDebugging(tab, debuggee, dbg); }); }); }); } function debug_remote(aURL, aOnDebugging, aBeforeTabAdded) { // Make any necessary preparations (start the debugger server etc.) @@ -201,16 +202,17 @@ function debug_remote(aURL, aOnDebugging info("Opening Remote Debugger"); let win = DebuggerUI.toggleRemoteDebugger(); // Wait for the initial resume... win.panelWin.gClient.addOneTimeListener("resumed", function() { info("Remote Debugger has started"); win._dbgwin.DebuggerView.Variables.lazyEmpty = false; win._dbgwin.DebuggerView.Variables.lazyAppend = false; + win._dbgwin.DebuggerView.Variables.lazyExpand = false; aOnDebugging(tab, debuggee, win); }); }); } function debug_chrome(aURL, aOnClosing, aOnDebugging) { let tab = addTab(aURL, function() { let debuggee = tab.linkedBrowser.contentWindow.wrappedJSObject;
--- a/browser/devtools/debugger/test/helpers.js +++ b/browser/devtools/debugger/test/helpers.js @@ -16,21 +16,20 @@ // }; // Now GCLI will emit output on every keypress that both explains the state // of GCLI and can be run as a test case. this.EXPORTED_SYMBOLS = [ 'helpers' ]; var helpers = {}; this.helpers = helpers; let require = (Cu.import("resource://gre/modules/devtools/Require.jsm", {})).require; -Components.utils.import("resource:///modules/devtools/gcli.jsm", {}); +Components.utils.import("resource://gre/modules/devtools/gcli.jsm", {}); let console = (Cu.import("resource://gre/modules/devtools/Console.jsm", {})).console; -let devtools = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools; -let TargetFactory = devtools.TargetFactory; +let TargetFactory = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools.TargetFactory; let Promise = (Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {})).Promise; let assert = { ok: ok, is: is, log: info }; var util = require('util/util'); var converters = require('gcli/converters'); @@ -153,16 +152,17 @@ helpers.addTabWithToolbar = function(url * their return values resolved */ helpers.runTests = function(options, tests) { var testNames = Object.keys(tests).filter(function(test) { return test != "setup" && test != "shutdown"; }); var recover = function(error) { + ok(false, error); console.error(error); }; info("SETUP"); var setupDone = (tests.setup != null) ? Promise.resolve(tests.setup(options)) : Promise.resolve(); @@ -336,17 +336,17 @@ helpers._createDebugCheck = function(opt output += ' input: \'' + input + '\',\n'; output += ' hints: ' + padding + '\'' + hints + '\',\n'; output += ' markup: \'' + helpers._actual.markup(options) + '\',\n'; output += ' cursor: ' + cursor + ',\n'; output += ' current: \'' + helpers._actual.current(options) + '\',\n'; output += ' status: \'' + helpers._actual.status(options) + '\',\n'; output += ' options: ' + outputArray(helpers._actual.options(options)) + ',\n'; - output += ' error: \'' + helpers._actual.message(options) + '\',\n'; + output += ' message: \'' + helpers._actual.message(options) + '\',\n'; output += ' predictions: ' + outputArray(predictions) + ',\n'; output += ' unassigned: ' + outputArray(requisition._unassigned) + ',\n'; output += ' outputState: \'' + helpers._actual.outputState(options) + '\',\n'; output += ' tooltipState: \'' + helpers._actual.tooltipState(options) + '\'' + (command ? ',' : '') +'\n'; if (command) { output += ' args: {\n'; @@ -373,16 +373,18 @@ helpers._createDebugCheck = function(opt output += ' }\n'; } output += ' },\n'; output += ' exec: {\n'; output += ' output: \'\',\n'; output += ' completed: true,\n'; + output += ' type: \'string\',\n'; + output += ' error: false\n'; output += ' }\n'; output += ' }\n'; output += ']);'; return output; }.bind(this), util.errorHandler); }; @@ -697,43 +699,55 @@ helpers._check = function(options, name, * Helper for helpers.audit() to ensure that all the 'exec' properties work. * See helpers.audit for more information. * @param name The name to use in error messages * @param expected See helpers.audit for a list of available exec checks * @return A promise which resolves to undefined when the checks are complete */ helpers._exec = function(options, name, expected) { if (expected == null) { - return Promise.resolve(); + return Promise.resolve({}); } var output = options.display.requisition.exec({ hidden: true }); if ('completed' in expected) { assert.is(output.completed, expected.completed, 'output.completed false for: ' + name); } if (!options.window.document.createElement) { assert.log('skipping output tests (missing doc.createElement) for ' + name); - return Promise.resolve(); + return Promise.resolve({ output: output }); } if (!('output' in expected)) { - return Promise.resolve(); + return Promise.resolve({ output: output }); } - var deferred = Promise.defer(); - var checkOutput = function() { var div = options.window.document.createElement('div'); - var nodePromise = converters.convert(output.data, output.type, 'dom', - options.display.requisition.context); - nodePromise.then(function(node) { + var conversionContext = options.display.requisition.conversionContext; + + if ('type' in expected) { + assert.is(output.type, + expected.type, + 'output.type for: ' + name); + } + + if ('error' in expected) { + assert.is(output.error, + expected.error, + 'output.error for: ' + name); + } + + var convertPromise = converters.convert(output.data, output.type, 'dom', + conversionContext); + return convertPromise.then(function(node) { div.appendChild(node); var actualOutput = div.textContent.trim(); var doTest = function(match, against) { if (match.test(against)) { assert.ok(true, 'html output for ' + name + ' should match ' + match.source); } else { @@ -752,57 +766,44 @@ helpers._exec = function(options, name, expected.output.forEach(function(match) { doTest(match, actualOutput); }); } else { doTest(expected.output, actualOutput); } - deferred.resolve(actualOutput); + return { output: output, text: actualOutput }; }); }; - if (output.completed !== false) { - checkOutput(); - } - else { - var changed = function() { - if (output.completed !== false) { - checkOutput(); - output.onChange.remove(changed); - } - }; - output.onChange.add(changed); - } - - return deferred.promise; + return output.promise.then(checkOutput, checkOutput); }; /** * Helper to setup the test */ helpers._setup = function(options, name, action) { if (typeof action === 'string') { return helpers.setInput(options, action); } if (typeof action === 'function') { return Promise.resolve(action()); } - return Promise.reject('setup must be a string or a function'); + return Promise.reject('\'setup\' property must be a string or a function. Is ' + action); }; /** * Helper to shutdown the test */ -helpers._post = function(name, action, output) { +helpers._post = function(name, action, data) { if (typeof action === 'function') { - return Promise.resolve(action(output)); + return Promise.resolve(action(data.output, data.text)); } return Promise.resolve(action); }; /* * We do some basic response time stats so we can see if we're getting slow */ var totalResponseTime = 0; @@ -938,39 +939,41 @@ helpers.audit = function(options, audits var start = new Date().getTime(); var setupDone = helpers._setup(options, name, audit.setup); return setupDone.then(function(chunkLen) { if (typeof chunkLen !== 'number') { chunkLen = 1; } - var responseTime = (new Date().getTime() - start) / chunkLen; - totalResponseTime += responseTime; - if (responseTime > maxResponseTime) { - maxResponseTime = responseTime; - maxResponseCulprit = assert.currentTest + '/' + name; + + if (assert.currentTest) { + var responseTime = (new Date().getTime() - start) / chunkLen; + totalResponseTime += responseTime; + if (responseTime > maxResponseTime) { + maxResponseTime = responseTime; + maxResponseCulprit = assert.currentTest + '/' + name; + } + averageOver++; } - averageOver++; var checkDone = helpers._check(options, name, audit.check); return checkDone.then(function() { var execDone = helpers._exec(options, name, audit.exec); - return execDone.then(function(output) { - return helpers._post(name, audit.post, output).then(function() { + return execDone.then(function(data) { + return helpers._post(name, audit.post, data).then(function() { if (assert.testLogging) { log('- END \'' + name + '\' in ' + assert.currentTest); } }); }); }); }); - }).then(null, function(ex) { - console.error(ex.stack); - throw(ex); + }).then(function() { + return options.display.inputter.setInput(''); }); }; /** * Compare 2 arrays. */ helpers._arrayIs = function(actual, expected, message) { assert.ok(Array.isArray(actual), 'actual is not an array: ' + message);
--- a/browser/devtools/framework/gDevTools.jsm +++ b/browser/devtools/framework/gDevTools.jsm @@ -217,18 +217,18 @@ DevTools.prototype = { * A definition is a light object that holds different information about a * developer tool. This object is not supposed to have any operational code. * See it as a "manifest". * The only actual code lives in the build() function, which will be used to * start an instance of this tool. * * Each toolDefinition has the following properties: * - id: Unique identifier for this tool (string|required) - * - killswitch: Property name to allow us to turn this tool on/off globally - * (string|required) (TODO: default to devtools.{id}.enabled?) + * - visibilityswitch: Property name to allow us to hide this tool from the + * DevTools Toolbox. * - icon: URL pointing to a graphic which will be used as the src for an * 16x16 img tag (string|required) * - url: URL pointing to a XUL/XHTML document containing the user interface * (string|required) * - label: Localized name for the tool to be displayed to the user * (string|required) * - build: Function that takes an iframe, which has been populated with the * markup from |url|, and also the toolbox containing the panel. @@ -236,17 +236,17 @@ DevTools.prototype = { */ registerTool: function DT_registerTool(toolDefinition) { let toolId = toolDefinition.id; if (!toolId || FORBIDDEN_IDS.has(toolId)) { throw new Error("Invalid definition.id"); } - toolDefinition.killswitch = toolDefinition.killswitch || + toolDefinition.visibilityswitch = toolDefinition.visibilityswitch || "devtools." + toolId + ".enabled"; this._tools.set(toolId, toolDefinition); this.emit("tool-registered", toolId); }, /** * Removes all tools that match the given |toolId| @@ -302,31 +302,27 @@ DevTools.prototype = { * Allow ToolBoxes to get at the list of tools that they should populate * themselves with. * * @return {Map} tools * A map of the the tool definitions registered in this instance */ getToolDefinitionMap: function DT_getToolDefinitionMap() { let tools = new Map(); - let disabledTools = []; - try { - disabledTools = JSON.parse(Services.prefs.getCharPref("devtools.toolbox.disabledTools")); - } catch(ex) {} for (let [key, value] of this._tools) { let enabled; try { - enabled = Services.prefs.getBoolPref(value.killswitch); + enabled = Services.prefs.getBoolPref(value.visibilityswitch); } catch(e) { enabled = true; } - if (enabled && disabledTools.indexOf(key) == -1) { + if (enabled || value.id == "options") { tools.set(key, value); } } return tools; }, /** * Tools have an inherent ordering that can't be represented in a Map so @@ -449,17 +445,17 @@ DevTools.prototype = { }, /** * All browser windows have been closed, tidy up remaining objects. */ destroy: function() { Services.obs.removeObserver(this.destroy, "quit-application"); - for (let [key, tool] of this._tools) { + for (let [key, tool] of this.getToolDefinitionMap()) { this.unregisterTool(key, true); } // Cleaning down the toolboxes: i.e. // for (let [target, toolbox] of this._toolboxes) toolbox.destroy(); // Is taken care of by the gDevToolsBrowser.forgetBrowserWindow }, }; @@ -573,25 +569,33 @@ let gDevToolsBrowser = { devtoolsKeyset.setAttribute("id", "devtoolsKeyset"); } devtoolsKeyset.appendChild(keys); let mainKeyset = doc.getElementById("mainKeyset"); mainKeyset.parentNode.insertBefore(devtoolsKeyset, mainKeyset); }, /** - * Add the menuitem for a tool to all open browser windows. Also toggles the - * kill switch preference of the tool. + * Add the menuitem for a tool to all open browser windows. * * @param {object} toolDefinition * properties of the tool to add */ _addToolToWindows: function DT_addToolToWindows(toolDefinition) { - // Set the kill switch pref boolean to true - Services.prefs.setBoolPref(toolDefinition.killswitch, true); + // No menu item or global shortcut is required for options panel. + if (toolDefinition.id == "options") { + return; + } + + // Skip if the tool is disabled. + try { + if (!Services.prefs.getBoolPref(toolDefinition.visibilityswitch)) { + return; + } + } catch(e) {} // We need to insert the new tool in the right place, which means knowing // the tool that comes before the tool that we're trying to add let allDefs = gDevTools.getToolDefinitionArray(); let prevDef; for (let def of allDefs) { if (def === toolDefinition) { break; @@ -637,16 +641,27 @@ let gDevToolsBrowser = { _addAllToolsToMenu: function DT_addAllToolsToMenu(doc) { let fragCommands = doc.createDocumentFragment(); let fragKeys = doc.createDocumentFragment(); let fragBroadcasters = doc.createDocumentFragment(); let fragAppMenuItems = doc.createDocumentFragment(); let fragMenuItems = doc.createDocumentFragment(); for (let toolDefinition of gDevTools.getToolDefinitionArray()) { + if (toolDefinition.id == "options") { + continue; + } + + // Skip if the tool is disabled. + try { + if (!Services.prefs.getBoolPref(toolDefinition.visibilityswitch)) { + continue; + } + } catch(e) {} + let elements = gDevToolsBrowser._createToolMenuElements(toolDefinition, doc); if (!elements) { return; } fragCommands.appendChild(elements.cmd); if (elements.key) { @@ -709,17 +724,17 @@ let gDevToolsBrowser = { } key.setAttribute("command", cmd.id); key.setAttribute("modifiers", toolDefinition.modifiers); } let bc = doc.createElement("broadcaster"); bc.id = "devtoolsMenuBroadcaster_" + id; - bc.setAttribute("label", toolDefinition.label); + bc.setAttribute("label", toolDefinition.menuLabel || toolDefinition.label); bc.setAttribute("command", cmd.id); if (key) { bc.setAttribute("key", "key_" + id); } let appmenuitem = doc.createElement("menuitem"); appmenuitem.id = "appmenuitem_" + id; @@ -762,26 +777,22 @@ let gDevToolsBrowser = { broadcaster.setAttribute("checked", "true"); } else { broadcaster.removeAttribute("checked"); } } }, /** - * Remove the menuitem for a tool to all open browser windows. Also sets the - * kill switch boolean pref to false. + * Remove the menuitem for a tool to all open browser windows. * - * @param {object} toolId + * @param {string} toolId * id of the tool to remove - * @param {string} killswitch - * The kill switch preference string of the tool */ - _removeToolFromWindows: function DT_removeToolFromWindows(toolId, killswitch) { - Services.prefs.setBoolPref(killswitch, false); + _removeToolFromWindows: function DT_removeToolFromWindows(toolId) { for (let win of gDevToolsBrowser._trackedBrowserWindows) { gDevToolsBrowser._removeToolFromMenu(toolId, win.document); } }, /** * Remove a tool's menuitem from a window * @@ -849,25 +860,20 @@ let gDevToolsBrowser = { this.gDevToolsBrowser = gDevToolsBrowser; gDevTools.on("tool-registered", function(ev, toolId) { let toolDefinition = gDevTools._tools.get(toolId); gDevToolsBrowser._addToolToWindows(toolDefinition); }); gDevTools.on("tool-unregistered", function(ev, toolId) { - let killswitch; - if (typeof toolId == "string") { - killswitch = "devtools." + toolId + ".enabled"; - } - else { - killswitch = toolId.killswitch; + if (typeof toolId != "string") { toolId = toolId.id; } - gDevToolsBrowser._removeToolFromWindows(toolId, killswitch); + gDevToolsBrowser._removeToolFromWindows(toolId); }); gDevTools.on("toolbox-ready", gDevToolsBrowser._updateMenuCheckbox); gDevTools.on("toolbox-destroyed", gDevToolsBrowser._updateMenuCheckbox); Services.obs.addObserver(gDevToolsBrowser.destroy, "quit-application", false); // Now load the tools.
--- a/browser/devtools/framework/test/browser_devtools_api.js +++ b/browser/devtools/framework/test/browser_devtools_api.js @@ -15,17 +15,17 @@ function test() { runTests(aTab); }); } function runTests(aTab) { let toolDefinition = { id: toolId, isTargetSupported: function() true, - killswitch: "devtools.test-tool.enabled", + visibilityswitch: "devtools.test-tool.enabled", url: "about:blank", label: "someLabel", build: function(iframeWindow, toolbox) { let panel = new DevToolPanel(iframeWindow, toolbox); return panel.open(); }, };
--- a/browser/devtools/framework/test/browser_toolbox_options.js +++ b/browser/devtools/framework/test/browser_toolbox_options.js @@ -21,34 +21,26 @@ function testSelectTool(aToolbox) { toolbox = aToolbox; doc = toolbox.doc; toolbox.once("options-selected", testOptionsShortcut); toolbox.selectTool("options"); } function testOptionsShortcut() { ok(true, "Toolbox selected via selectTool method"); - toolbox.once("options-selected", testOptionsButtonClick); + toolbox.once("options-selected", testOptions); toolbox.selectTool("webconsole") .then(() => synthesizeKeyFromKeyTag("toolbox-options-key", doc)); } -function testOptionsButtonClick() { - ok(true, "Toolbox selected via shortcut"); - toolbox.once("options-selected", testOptions); - toolbox.selectTool("webconsole") - .then(() => doc.getElementById("toolbox-tab-options").click()); -} - -function testOptions(event, iframe) { +function testOptions(event, tool) { ok(true, "Toolbox selected via button click"); - panelWin = iframe.contentWindow; - let panelDoc = iframe.contentDocument; + panelWin = tool.panelWin; // Testing pref changes - let prefCheckboxes = panelDoc.querySelectorAll("checkbox[data-pref]"); + let prefCheckboxes = tool.panelDoc.querySelectorAll("checkbox[data-pref]"); for (let checkbox of prefCheckboxes) { prefNodes.push(checkbox); prefValues.push(Services.prefs.getBoolPref(checkbox.getAttribute("data-pref"))); } // Do again with opposite values to reset prefs for (let checkbox of prefCheckboxes) { prefNodes.push(checkbox); prefValues.push(!Services.prefs.getBoolPref(checkbox.getAttribute("data-pref")));
--- a/browser/devtools/framework/test/browser_toolbox_sidebar.js +++ b/browser/devtools/framework/test/browser_toolbox_sidebar.js @@ -19,17 +19,17 @@ function test() { let panelDoc; let tab1Selected = false; let registeredTabs = {}; let readyTabs = {}; let toolDefinition = { id: "fakeTool4242", - killswitch: "devtools.fakeTool4242.enabled", + visibilityswitch: "devtools.fakeTool4242.enabled", url: toolURL, label: "FAKE TOOL!!!", isTargetSupported: function() true, build: function(iframeWindow, toolbox) { let deferred = Promise.defer(); executeSoon(function() { deferred.resolve({ target: toolbox.target,
--- a/browser/devtools/framework/test/browser_toolbox_window_title_changes.js +++ b/browser/devtools/framework/test/browser_toolbox_window_title_changes.js @@ -11,17 +11,17 @@ function test() { waitForExplicitFinish(); const URL_1 = "data:text/plain;charset=UTF-8,abcde"; const URL_2 = "data:text/plain;charset=UTF-8,12345"; const TOOL_ID_1 = "webconsole"; const TOOL_ID_2 = "jsdebugger"; - const LABEL_1 = "Web Console"; + const LABEL_1 = "Console"; const LABEL_2 = "Debugger"; let toolbox; addTab(URL_1, function () { let target = TargetFactory.forTab(gBrowser.selectedTab); gDevTools.showToolbox(target, null, Toolbox.HostType.BOTTOM) .then(function (aToolbox) { toolbox = aToolbox; })
--- a/browser/devtools/framework/toolbox-options.js +++ b/browser/devtools/framework/toolbox-options.js @@ -1,110 +1,142 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * 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/. */ - -"use strict"; - -const { utils: Cu } = Components; -const DISABLED_TOOLS = "devtools.toolbox.disabledTools"; -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource:///modules/devtools/gDevTools.jsm"); - -window.addEventListener("load", function onLoad() { - window.removeEventListener("load", onLoad); - setupToolsList(); - populatePreferences(); -}); - -function setupToolsList() { - let disabledTools = []; - try { - disabledTools = JSON.parse(Services.prefs.getCharPref(DISABLED_TOOLS)); - } catch(ex) { - Cu.reportError("Error parsing pref " + DISABLED_TOOLS + " as JSON."); - } - let defaultToolsBox = document.getElementById("default-tools-box"); - let additionalToolsBox = document.getElementById("additional-tools-box"); - - defaultToolsBox.textContent = ""; - additionalToolsBox.textContent = ""; - - let onCheckboxClick = function(id) { - if (disabledTools.indexOf(id) > -1) { - disabledTools.splice(disabledTools.indexOf(id), 1); - Services.prefs.setCharPref(DISABLED_TOOLS, JSON.stringify(disabledTools)); - gDevTools.emit("tool-registered", id); - } - else { - disabledTools.push(id); - Services.prefs.setCharPref(DISABLED_TOOLS, JSON.stringify(disabledTools)); - gDevTools.emit("tool-unregistered", gDevTools._tools.get(id)); - } - }; - - // Populating the default tools lists - for (let tool of gDevTools.getDefaultTools()) { - let checkbox = document.createElement("checkbox"); - checkbox.setAttribute("id", tool.id); - checkbox.setAttribute("label", tool.label); - checkbox.setAttribute("tooltiptext", tool.tooltip || ""); - checkbox.setAttribute("checked", disabledTools.indexOf(tool.id) == -1); - checkbox.addEventListener("command", onCheckboxClick.bind(null, tool.id)); - defaultToolsBox.appendChild(checkbox); - } - - // Populating the additional tools list that came from add-ons. - let atleastOneAddon = false; - for (let tool of gDevTools.getAdditionalTools()) { - atleastOneAddon = true; - let checkbox = document.createElement("checkbox"); - checkbox.setAttribute("id", tool.id); - checkbox.setAttribute("label", tool.label); - checkbox.setAttribute("tooltiptext", tool.tooltip || ""); - checkbox.setAttribute("checked", disabledTools.indexOf(tool.id) == -1); - checkbox.addEventListener("command", onCheckboxClick.bind(null, tool.id)); - additionalToolsBox.appendChild(checkbox); - } - - if (!atleastOneAddon) { - additionalToolsBox.style.display = "none"; - additionalToolsBox.previousSibling.style.display = "none"; - } - - window.focus(); -} - -function populatePreferences() { - let prefCheckboxes = document.querySelectorAll("checkbox[data-pref]"); - for (let checkbox of prefCheckboxes) { - checkbox.checked = Services.prefs.getBoolPref(checkbox.getAttribute("data-pref")); - checkbox.addEventListener("command", function() { - let data = { - pref: this.getAttribute("data-pref"), - newValue: this.checked - }; - data.oldValue = Services.prefs.getBoolPref(data.pref); - Services.prefs.setBoolPref(data.pref, data.newValue); - gDevTools.emit("pref-changed", data); - }.bind(checkbox)); - } - let prefRadiogroups = document.querySelectorAll("radiogroup[data-pref]"); - for (let radiogroup of prefRadiogroups) { - let selectedValue = Services.prefs.getCharPref(radiogroup.getAttribute("data-pref")); - for (let radio of radiogroup.childNodes) { - radiogroup.selectedIndex = -1; - if (radio.getAttribute("value") == selectedValue) { - radiogroup.selectedItem = radio; - break; - } - } - radiogroup.addEventListener("select", function() { - let data = { - pref: this.getAttribute("data-pref"), - newValue: this.selectedItem.getAttribute("value") - }; - data.oldValue = Services.prefs.getCharPref(data.pref); - Services.prefs.setCharPref(data.pref, data.newValue); - gDevTools.emit("pref-changed", data); - }.bind(radiogroup)); - } -} +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +"use strict"; + +const {Cu} = require("chrome"); + +let Promise = require("sdk/core/promise"); +let EventEmitter = require("devtools/shared/event-emitter"); + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource:///modules/devtools/gDevTools.jsm"); + +exports.OptionsPanel = OptionsPanel; + +/** + * Represents the Options Panel in the Toolbox. + */ +function OptionsPanel(iframeWindow, toolbox) { + this.panelDoc = iframeWindow.document; + this.panelWin = iframeWindow; + + EventEmitter.decorate(this); +}; + +OptionsPanel.prototype = { + + open: function OP_open() { + let deferred = Promise.defer(); + + this.setupToolsList(); + this.populatePreferences(); + + this.emit("ready"); + deferred.resolve(this); + return deferred.promise; + }, + + setupToolsList: function OP_setupToolsList() { + let defaultToolsBox = this.panelDoc.getElementById("default-tools-box"); + let additionalToolsBox = this.panelDoc.getElementById("additional-tools-box"); + + defaultToolsBox.textContent = ""; + additionalToolsBox.textContent = ""; + + let pref = function(key) { + try { + return Services.prefs.getBoolPref(key); + } + catch (ex) { + return true; + } + }; + + let onCheckboxClick = function(id) { + let toolDefinition = gDevTools._tools.get(id); + // Set the kill switch pref boolean to true + Services.prefs.setBoolPref(toolDefinition.visibilityswitch, this.checked); + if (this.checked) { + gDevTools.emit("tool-registered", id); + } + else { + gDevTools.emit("tool-unregistered", toolDefinition); + } + }; + + // Populating the default tools lists + for (let tool of gDevTools.getDefaultTools()) { + if (tool.id == "options") { + continue; + } + let checkbox = this.panelDoc.createElement("checkbox"); + checkbox.setAttribute("id", tool.id); + checkbox.setAttribute("label", tool.label); + checkbox.setAttribute("tooltiptext", tool.tooltip || ""); + checkbox.setAttribute("checked", pref(tool.visibilityswitch)); + checkbox.addEventListener("command", onCheckboxClick.bind(checkbox, tool.id)); + defaultToolsBox.appendChild(checkbox); + } + + // Populating the additional tools list that came from add-ons. + let atleastOneAddon = false; + for (let tool of gDevTools.getAdditionalTools()) { + atleastOneAddon = true; + let checkbox = this.panelDoc.createElement("checkbox"); + checkbox.setAttribute("id", tool.id); + checkbox.setAttribute("label", tool.label); + checkbox.setAttribute("tooltiptext", tool.tooltip || ""); + checkbox.setAttribute("checked", pref(tool.visibilityswitch)); + checkbox.addEventListener("command", onCheckboxClick.bind(checkbox, tool.id)); + additionalToolsBox.appendChild(checkbox); + } + + if (!atleastOneAddon) { + additionalToolsBox.style.display = "none"; + additionalToolsBox.previousSibling.style.display = "none"; + } + + this.panelWin.focus(); + }, + + populatePreferences: function OP_populatePreferences() { + let prefCheckboxes = this.panelDoc.querySelectorAll("checkbox[data-pref]"); + for (let checkbox of prefCheckboxes) { + checkbox.checked = Services.prefs.getBoolPref(checkbox.getAttribute("data-pref")); + checkbox.addEventListener("command", function() { + let data = { + pref: this.getAttribute("data-pref"), + newValue: this.checked + }; + data.oldValue = Services.prefs.getBoolPref(data.pref); + Services.prefs.setBoolPref(data.pref, data.newValue); + gDevTools.emit("pref-changed", data); + }.bind(checkbox)); + } + let prefRadiogroups = this.panelDoc.querySelectorAll("radiogroup[data-pref]"); + for (let radiogroup of prefRadiogroups) { + let selectedValue = Services.prefs.getCharPref(radiogroup.getAttribute("data-pref")); + for (let radio of radiogroup.childNodes) { + radiogroup.selectedIndex = -1; + if (radio.getAttribute("value") == selectedValue) { + radiogroup.selectedItem = radio; + break; + } + } + radiogroup.addEventListener("select", function() { + let data = { + pref: this.getAttribute("data-pref"), + newValue: this.selectedItem.getAttribute("value") + }; + data.oldValue = Services.prefs.getCharPref(data.pref); + Services.prefs.setCharPref(data.pref, data.newValue); + gDevTools.emit("pref-changed", data); + }.bind(radiogroup)); + } + }, + + destroy: function OP_destroy() { + this.panelWin = this.panelDoc = null; + } +};
--- a/browser/devtools/framework/toolbox-options.xul +++ b/browser/devtools/framework/toolbox-options.xul @@ -6,17 +6,16 @@ <!ENTITY % toolboxDTD SYSTEM "chrome://browser/locale/devtools/toolbox.dtd" > %toolboxDTD; ]> <?xml-stylesheet href="chrome://browser/skin/" type="text/css"?> <?xml-stylesheet rel="stylesheet" href="chrome://browser/content/devtools/framework/toolbox.css" type="text/css"?> <?xml-stylesheet rel="stylesheet" href="chrome://browser/skin/devtools/toolbox.css" type="text/css"?> <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - <script type="application/javascript;version=1.8" src="toolbox-options.js"></script> <hbox id="options-panel-container" flex="1"> <hbox id="options-panel" flex="1"> <vbox id="tools-box" class="options-vertical-pane" flex="1"> <label value="&options.selectDefaultTools.label;"/> <vbox id="default-tools-box" class="options-groupbox" tabindex="0"/> <label value="&options.selectAdditionalTools.label;"/> <vbox id="additional-tools-box" class="options-groupbox"/> </vbox>
--- a/browser/devtools/framework/toolbox.js +++ b/browser/devtools/framework/toolbox.js @@ -32,17 +32,17 @@ XPCOMUtils.defineLazyGetter(this, "toolb } }; return l10n; }); XPCOMUtils.defineLazyGetter(this, "Requisition", function() { let scope = {}; Cu.import("resource://gre/modules/devtools/Require.jsm", scope); - Cu.import("resource:///modules/devtools/gcli.jsm", scope); + Cu.import("resource://gre/modules/devtools/gcli.jsm", {}); let req = scope.require; return req('gcli/cli').Requisition; }); /** * A "Toolbox" is the component that holds all the tools for one specific * target. Visually, it's a document that includes the tools tabs and all @@ -215,24 +215,16 @@ Toolbox.prototype = { iframe.addEventListener("DOMContentLoaded", domReady, true); iframe.setAttribute("src", this._URL); }.bind(this)); return deferred.promise; }, _buildOptions: function TBOX__buildOptions() { - this.optionsButton = this.doc.getElementById("toolbox-tab-options"); - this.optionsButton.addEventListener("command", function() { - this.selectTool("options"); - }.bind(this), false); - - let iframe = this.doc.getElementById("toolbox-panel-iframe-options"); - this._toolPanels.set("options", iframe); - let key = this.doc.getElementById("toolbox-options-key"); key.addEventListener("command", function(toolId) { this.selectTool(toolId); }.bind(this, "options"), true); }, /** * Adds the keys and commands to the Toolbox Window in window mode. @@ -353,58 +345,64 @@ Toolbox.prototype = { let tabs = this.doc.getElementById("toolbox-tabs"); let deck = this.doc.getElementById("toolbox-deck"); let id = toolDefinition.id; let radio = this.doc.createElement("radio"); radio.className = "toolbox-tab devtools-tab"; radio.id = "toolbox-tab-" + id; - radio.setAttribute("flex", "1"); radio.setAttribute("toolid", id); if (toolDefinition.ordinal == undefined || toolDefinition.ordinal < 0) { toolDefinition.ordinal = MAX_ORDINAL; } radio.setAttribute("ordinal", toolDefinition.ordinal); radio.setAttribute("tooltiptext", toolDefinition.tooltip); radio.addEventListener("command", function(id) { this.selectTool(id); }.bind(this, id)); + // spacer lets us center the image and label, while allowing cropping + let spacer = this.doc.createElement("spacer"); + spacer.setAttribute("flex", "1"); + radio.appendChild(spacer); + if (toolDefinition.icon) { let image = this.doc.createElement("image"); image.setAttribute("src", toolDefinition.icon); radio.appendChild(image); } - let label = this.doc.createElement("label"); - label.setAttribute("value", toolDefinition.label) - label.setAttribute("crop", "end"); - label.setAttribute("flex", "1"); + if (toolDefinition.label) { + let label = this.doc.createElement("label"); + label.setAttribute("value", toolDefinition.label) + label.setAttribute("crop", "end"); + label.setAttribute("flex", "1"); + radio.appendChild(label); + radio.setAttribute("flex", "1"); + } let vbox = this.doc.createElement("vbox"); vbox.className = "toolbox-panel"; vbox.id = "toolbox-panel-" + id; - radio.appendChild(label); // If there is no tab yet, or the ordinal to be added is the largest one. if (tabs.childNodes.length == 0 || +tabs.lastChild.getAttribute("ordinal") <= toolDefinition.ordinal) { tabs.appendChild(radio); deck.appendChild(vbox); } // else, iterate over all the tabs to get the correct location. else { Array.some(tabs.childNodes, (node, i) => { if (+node.getAttribute("ordinal") > toolDefinition.ordinal) { tabs.insertBefore(radio, node); - deck.insertBefore(vbox, deck.childNodes[i + 1]); - // + 1 because of options panel. + deck.insertBefore(vbox, deck.childNodes[i]); return true; } }); } this._addKeysToWindow(); }, @@ -435,38 +433,31 @@ Toolbox.prototype = { let tab = this.doc.getElementById("toolbox-tab-" + id); if (!tab) { throw new Error("No tool found"); } let tabstrip = this.doc.getElementById("toolbox-tabs"); - // select the right tab - let index = -1; + // select the right tab, making 0th index the default tab if right tab not + // found + let index = 0; let tabs = tabstrip.childNodes; for (let i = 0; i < tabs.length; i++) { if (tabs[i] === tab) { index = i; break; } } tabstrip.selectedItem = tab; // and select the right iframe let deck = this.doc.getElementById("toolbox-deck"); - // offset by 1 due to options panel - if (id == "options") { - deck.selectedIndex = 0; - this.optionsButton.setAttribute("checked", true); - } - else { - deck.selectedIndex = index != -1 ? index + 1: -1; - this.optionsButton.removeAttribute("checked"); - } + deck.selectedIndex = index; let definition = gDevTools.getToolDefinitionMap().get(id); this._currentToolId = id; let resolveSelected = panel => { this.emit("select", id); this.emit(id + "-selected", panel); @@ -663,17 +654,17 @@ Toolbox.prototype = { } if (panel) { panel.parentNode.removeChild(panel); } if (this.hostType == Toolbox.HostType.WINDOW) { let doc = this.doc.defaultView.parent.document; - let key = doc.getElementById("key_" + id); + let key = doc.getElementById("key_" + toolId); if (key) { key.parentNode.removeChild(key); } } }, /** @@ -704,17 +695,16 @@ Toolbox.prototype = { this.off("select", this._refreshHostTitle); this.off("host-changed", this._refreshHostTitle); gDevTools.off("tool-registered", this._toolRegistered); gDevTools.off("tool-unregistered", this._toolUnregistered); let outstanding = []; - this._toolPanels.delete("options"); for (let [id, panel] of this._toolPanels) { outstanding.push(panel.destroy()); } let container = this.doc.getElementById("toolbox-buttons"); while(container.firstChild) { container.removeChild(container.firstChild); }
--- a/browser/devtools/framework/toolbox.xul +++ b/browser/devtools/framework/toolbox.xul @@ -29,36 +29,24 @@ <toolbar class="devtools-tabbar"> #ifdef XP_MACOSX <hbox id="toolbox-controls"> <toolbarbutton id="toolbox-close" tooltiptext="&toolboxCloseButton.tooltip;"/> <hbox id="toolbox-dock-buttons"/> </hbox> #endif - <toolbarbutton id="toolbox-tab-options" - autocheck="false" - class="command-button toolbox-tab devtools-tab" - tooltiptext="&toolboxOptionsButton.tooltip;"/> <hbox id="toolbox-tabs" flex="1"> </hbox> <hbox id="toolbox-buttons" pack="end"/> #ifndef XP_MACOSX <vbox id="toolbox-controls-separator"/> <hbox id="toolbox-controls"> <hbox id="toolbox-dock-buttons"/> <toolbarbutton id="toolbox-close" tooltiptext="&toolboxCloseButton.tooltip;"/> </hbox> #endif </toolbar> <deck id="toolbox-deck" flex="1"> - <vbox id="toolbox-panel-options" - class="toolbox-panel"> - <iframe id="toolbox-panel-iframe-options" - class="toolbox-panel-iframe" - flex="1" forceOwnRefreshDriver="" - src="chrome://browser/content/devtools/framework/toolbox-options.xul"> - </iframe> - </vbox> </deck> </notificationbox> </window>
--- a/browser/devtools/inspector/CmdInspect.jsm +++ b/browser/devtools/inspector/CmdInspect.jsm @@ -1,16 +1,16 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components; this.EXPORTED_SYMBOLS = [ ]; -Cu.import("resource:///modules/devtools/gcli.jsm"); +Cu.import("resource://gre/modules/devtools/gcli.jsm"); Cu.import('resource://gre/modules/XPCOMUtils.jsm'); XPCOMUtils.defineLazyModuleGetter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "devtools", "resource:///modules/devtools/gDevTools.jsm"); /**
--- a/browser/devtools/inspector/test/helpers.js +++ b/browser/devtools/inspector/test/helpers.js @@ -16,21 +16,20 @@ // }; // Now GCLI will emit output on every keypress that both explains the state // of GCLI and can be run as a test case. this.EXPORTED_SYMBOLS = [ 'helpers' ]; var helpers = {}; this.helpers = helpers; let require = (Cu.import("resource://gre/modules/devtools/Require.jsm", {})).require; -Components.utils.import("resource:///modules/devtools/gcli.jsm", {}); +Components.utils.import("resource://gre/modules/devtools/gcli.jsm", {}); let console = (Cu.import("resource://gre/modules/devtools/Console.jsm", {})).console; -let devtools = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools; -let TargetFactory = devtools.TargetFactory; +let TargetFactory = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools.TargetFactory; let Promise = (Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {})).Promise; let assert = { ok: ok, is: is, log: info }; var util = require('util/util'); var converters = require('gcli/converters'); @@ -153,16 +152,17 @@ helpers.addTabWithToolbar = function(url * their return values resolved */ helpers.runTests = function(options, tests) { var testNames = Object.keys(tests).filter(function(test) { return test != "setup" && test != "shutdown"; }); var recover = function(error) { + ok(false, error); console.error(error); }; info("SETUP"); var setupDone = (tests.setup != null) ? Promise.resolve(tests.setup(options)) : Promise.resolve(); @@ -336,17 +336,17 @@ helpers._createDebugCheck = function(opt output += ' input: \'' + input + '\',\n'; output += ' hints: ' + padding + '\'' + hints + '\',\n'; output += ' markup: \'' + helpers._actual.markup(options) + '\',\n'; output += ' cursor: ' + cursor + ',\n'; output += ' current: \'' + helpers._actual.current(options) + '\',\n'; output += ' status: \'' + helpers._actual.status(options) + '\',\n'; output += ' options: ' + outputArray(helpers._actual.options(options)) + ',\n'; - output += ' error: \'' + helpers._actual.message(options) + '\',\n'; + output += ' message: \'' + helpers._actual.message(options) + '\',\n'; output += ' predictions: ' + outputArray(predictions) + ',\n'; output += ' unassigned: ' + outputArray(requisition._unassigned) + ',\n'; output += ' outputState: \'' + helpers._actual.outputState(options) + '\',\n'; output += ' tooltipState: \'' + helpers._actual.tooltipState(options) + '\'' + (command ? ',' : '') +'\n'; if (command) { output += ' args: {\n'; @@ -373,16 +373,18 @@ helpers._createDebugCheck = function(opt output += ' }\n'; } output += ' },\n'; output += ' exec: {\n'; output += ' output: \'\',\n'; output += ' completed: true,\n'; + output += ' type: \'string\',\n'; + output += ' error: false\n'; output += ' }\n'; output += ' }\n'; output += ']);'; return output; }.bind(this), util.errorHandler); }; @@ -697,43 +699,55 @@ helpers._check = function(options, name, * Helper for helpers.audit() to ensure that all the 'exec' properties work. * See helpers.audit for more information. * @param name The name to use in error messages * @param expected See helpers.audit for a list of available exec checks * @return A promise which resolves to undefined when the checks are complete */ helpers._exec = function(options, name, expected) { if (expected == null) { - return Promise.resolve(); + return Promise.resolve({}); } var output = options.display.requisition.exec({ hidden: true }); if ('completed' in expected) { assert.is(output.completed, expected.completed, 'output.completed false for: ' + name); } if (!options.window.document.createElement) { assert.log('skipping output tests (missing doc.createElement) for ' + name); - return Promise.resolve(); + return Promise.resolve({ output: output }); } if (!('output' in expected)) { - return Promise.resolve(); + return Promise.resolve({ output: output }); } - var deferred = Promise.defer(); - var checkOutput = function() { var div = options.window.document.createElement('div'); - var nodePromise = converters.convert(output.data, output.type, 'dom', - options.display.requisition.context); - nodePromise.then(function(node) { + var conversionContext = options.display.requisition.conversionContext; + + if ('type' in expected) { + assert.is(output.type, + expected.type, + 'output.type for: ' + name); + } + + if ('error' in expected) { + assert.is(output.error, + expected.error, + 'output.error for: ' + name); + } + + var convertPromise = converters.convert(output.data, output.type, 'dom', + conversionContext); + return convertPromise.then(function(node) { div.appendChild(node); var actualOutput = div.textContent.trim(); var doTest = function(match, against) { if (match.test(against)) { assert.ok(true, 'html output for ' + name + ' should match ' + match.source); } else { @@ -752,57 +766,44 @@ helpers._exec = function(options, name, expected.output.forEach(function(match) { doTest(match, actualOutput); }); } else { doTest(expected.output, actualOutput); } - deferred.resolve(actualOutput); + return { output: output, text: actualOutput }; }); }; - if (output.completed !== false) { - checkOutput(); - } - else { - var changed = function() { - if (output.completed !== false) { - checkOutput(); - output.onChange.remove(changed); - } - }; - output.onChange.add(changed); - } - - return deferred.promise; + return output.promise.then(checkOutput, checkOutput); }; /** * Helper to setup the test */ helpers._setup = function(options, name, action) { if (typeof action === 'string') { return helpers.setInput(options, action); } if (typeof action === 'function') { return Promise.resolve(action()); } - return Promise.reject('setup must be a string or a function'); + return Promise.reject('\'setup\' property must be a string or a function. Is ' + action); }; /** * Helper to shutdown the test */ -helpers._post = function(name, action, output) { +helpers._post = function(name, action, data) { if (typeof action === 'function') { - return Promise.resolve(action(output)); + return Promise.resolve(action(data.output, data.text)); } return Promise.resolve(action); }; /* * We do some basic response time stats so we can see if we're getting slow */ var totalResponseTime = 0; @@ -938,39 +939,41 @@ helpers.audit = function(options, audits var start = new Date().getTime(); var setupDone = helpers._setup(options, name, audit.setup); return setupDone.then(function(chunkLen) { if (typeof chunkLen !== 'number') { chunkLen = 1; } - var responseTime = (new Date().getTime() - start) / chunkLen; - totalResponseTime += responseTime; - if (responseTime > maxResponseTime) { - maxResponseTime = responseTime; - maxResponseCulprit = assert.currentTest + '/' + name; + + if (assert.currentTest) { + var responseTime = (new Date().getTime() - start) / chunkLen; + totalResponseTime += responseTime; + if (responseTime > maxResponseTime) { + maxResponseTime = responseTime; + maxResponseCulprit = assert.currentTest + '/' + name; + } + averageOver++; } - averageOver++; var checkDone = helpers._check(options, name, audit.check); return checkDone.then(function() { var execDone = helpers._exec(options, name, audit.exec); - return execDone.then(function(output) { - return helpers._post(name, audit.post, output).then(function() { + return execDone.then(function(data) { + return helpers._post(name, audit.post, data).then(function() { if (assert.testLogging) { log('- END \'' + name + '\' in ' + assert.currentTest); } }); }); }); }); - }).then(null, function(ex) { - console.error(ex.stack); - throw(ex); + }).then(function() { + return options.display.inputter.setInput(''); }); }; /** * Compare 2 arrays. */ helpers._arrayIs = function(actual, expected, message) { assert.ok(Array.isArray(actual), 'actual is not an array: ' + message);
--- a/browser/devtools/main.js +++ b/browser/devtools/main.js @@ -24,108 +24,127 @@ Object.defineProperty(exports, "Toolbox" }); Object.defineProperty(exports, "TargetFactory", { get: () => require("devtools/framework/target").TargetFactory }); loader.lazyGetter(this, "osString", () => Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS); // Panels +loader.lazyGetter(this, "OptionsPanel", function() require("devtools/framework/toolbox-options").OptionsPanel); loader.lazyGetter(this, "InspectorPanel", function() require("devtools/inspector/inspector-panel").InspectorPanel); loader.lazyImporter(this, "WebConsolePanel", "resource:///modules/WebConsolePanel.jsm"); loader.lazyImporter(this, "DebuggerPanel", "resource:///modules/devtools/DebuggerPanel.jsm"); loader.lazyImporter(this, "StyleEditorPanel", "resource:///modules/devtools/StyleEditorPanel.jsm"); loader.lazyImporter(this, "ProfilerPanel", "resource:///modules/devtools/ProfilerPanel.jsm"); loader.lazyImporter(this, "NetMonitorPanel", "resource:///modules/devtools/NetMonitorPanel.jsm"); // Strings +const toolboxProps = "chrome://browser/locale/devtools/toolbox.properties"; const inspectorProps = "chrome://browser/locale/devtools/inspector.properties"; const debuggerProps = "chrome://browser/locale/devtools/debugger.properties"; const styleEditorProps = "chrome://browser/locale/devtools/styleeditor.properties"; const webConsoleProps = "chrome://browser/locale/devtools/webconsole.properties"; const profilerProps = "chrome://browser/locale/devtools/profiler.properties"; const netMonitorProps = "chrome://browser/locale/devtools/netmonitor.properties"; +loader.lazyGetter(this, "toolboxStrings", () => Services.strings.createBundle(toolboxProps)); loader.lazyGetter(this, "webConsoleStrings", () => Services.strings.createBundle(webConsoleProps)); loader.lazyGetter(this, "debuggerStrings", () => Services.strings.createBundle(debuggerProps)); loader.lazyGetter(this, "styleEditorStrings", () => Services.strings.createBundle(styleEditorProps)); loader.lazyGetter(this, "inspectorStrings", () => Services.strings.createBundle(inspectorProps)); loader.lazyGetter(this, "profilerStrings",() => Services.strings.createBundle(profilerProps)); loader.lazyGetter(this, "netMonitorStrings", () => Services.strings.createBundle(netMonitorProps)); let Tools = {}; exports.Tools = Tools; // Definitions +Tools.options = { + id: "options", + ordinal: 0, + url: "chrome://browser/content/devtools/framework/toolbox-options.xul", + icon: "chrome://browser/skin/devtools/tool-options.png", + tooltip: l10n("optionsButton.tooltip", toolboxStrings), + isTargetSupported: function(target) { + return true; + }, + build: function(iframeWindow, toolbox) { + let panel = new OptionsPanel(iframeWindow, toolbox); + return panel.open(); + } +} + Tools.webConsole = { id: "webconsole", key: l10n("cmd.commandkey", webConsoleStrings), accesskey: l10n("webConsoleCmd.accesskey", webConsoleStrings), modifiers: Services.appinfo.OS == "Darwin" ? "accel,alt" : "accel,shift", - ordinal: 0, + ordinal: 1, icon: "chrome://browser/skin/devtools/tool-webconsole.png", url: "chrome://browser/content/devtools/webconsole.xul", - label: l10n("ToolboxWebconsole.label", webConsoleStrings), + label: l10n("ToolboxTabWebconsole.label", webConsoleStrings), + menuLabel: l10n("MenuWebconsole.label", webConsoleStrings), tooltip: l10n("ToolboxWebconsole.tooltip", webConsoleStrings), isTargetSupported: function(target) { return true; }, build: function(iframeWindow, toolbox) { let panel = new WebConsolePanel(iframeWindow, toolbox); return panel.open(); } }; -Tools.jsdebugger = { - id: "jsdebugger", - key: l10n("open.commandkey", debuggerStrings), - accesskey: l10n("debuggerMenu.accesskey", debuggerStrings), - modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift", - ordinal: 2, - killswitch: "devtools.debugger.enabled", - icon: "chrome://browser/skin/devtools/tool-debugger.png", - url: "chrome://browser/content/devtools/debugger.xul", - label: l10n("ToolboxDebugger.label", debuggerStrings), - tooltip: l10n("ToolboxDebugger.tooltip", debuggerStrings), - - isTargetSupported: function(target) { - return true; - }, - - build: function(iframeWindow, toolbox) { - let panel = new DebuggerPanel(iframeWindow, toolbox); - return panel.open(); - } -}; - Tools.inspector = { id: "inspector", accesskey: l10n("inspector.accesskey", inspectorStrings), key: l10n("inspector.commandkey", inspectorStrings), - ordinal: 1, + ordinal: 2, modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift", icon: "chrome://browser/skin/devtools/tool-inspector.png", url: "chrome://browser/content/devtools/inspector/inspector.xul", label: l10n("inspector.label", inspectorStrings), tooltip: l10n("inspector.tooltip", inspectorStrings), isTargetSupported: function(target) { return !target.isRemote; }, build: function(iframeWindow, toolbox) { let panel = new InspectorPanel(iframeWindow, toolbox); return panel.open(); } }; +Tools.jsdebugger = { + id: "jsdebugger", + key: l10n("open.commandkey", debuggerStrings), + accesskey: l10n("debuggerMenu.accesskey", debuggerStrings), + modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift", + ordinal: 3, + visibilityswitch: "devtools.debugger.enabled", + icon: "chrome://browser/skin/devtools/tool-debugger.png", + url: "chrome://browser/content/devtools/debugger.xul", + label: l10n("ToolboxDebugger.label", debuggerStrings), + tooltip: l10n("ToolboxDebugger.tooltip", debuggerStrings), + + isTargetSupported: function(target) { + return true; + }, + + build: function(iframeWindow, toolbox) { + let panel = new DebuggerPanel(iframeWindow, toolbox); + return panel.open(); + } +}; + Tools.styleEditor = { id: "styleeditor", key: l10n("open.commandkey", styleEditorStrings), - ordinal: 3, + ordinal: 4, accesskey: l10n("open.accesskey", styleEditorStrings), modifiers: "shift", icon: "chrome://browser/skin/devtools/tool-styleeditor.png", url: "chrome://browser/content/devtools/styleeditor.xul", label: l10n("ToolboxStyleEditor.label", styleEditorStrings), tooltip: l10n("ToolboxStyleEditor.tooltip", styleEditorStrings), isTargetSupported: function(target) { @@ -137,19 +156,19 @@ Tools.styleEditor = { return panel.open(); } }; Tools.jsprofiler = { id: "jsprofiler", accesskey: l10n("profiler.accesskey", profilerStrings), key: l10n("profiler2.commandkey", profilerStrings), - ordinal: 4, + ordinal: 5, modifiers: "shift", - killswitch: "devtools.profiler.enabled", + visibilityswitch: "devtools.profiler.enabled", icon: "chrome://browser/skin/devtools/tool-profiler.png", url: "chrome://browser/content/devtools/profiler.xul", label: l10n("profiler.label", profilerStrings), tooltip: l10n("profiler.tooltip", profilerStrings), isTargetSupported: function (target) { return true; }, @@ -159,35 +178,36 @@ Tools.jsprofiler = { return panel.open(); } }; Tools.netMonitor = { id: "netmonitor", accesskey: l10n("netmonitor.accesskey", netMonitorStrings), key: l10n("netmonitor.commandkey", netMonitorStrings), - ordinal: 5, + ordinal: 6, modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift", - killswitch: "devtools.netmonitor.enabled", - icon: "chrome://browser/skin/devtools/tool-profiler.png", + visibilityswitch: "devtools.netmonitor.enabled", + icon: "chrome://browser/skin/devtools/tool-network.png", url: "chrome://browser/content/devtools/netmonitor.xul", label: l10n("netmonitor.label", netMonitorStrings), tooltip: l10n("netmonitor.tooltip", netMonitorStrings), isTargetSupported: function(target) { return true; }, build: function(iframeWindow, toolbox) { let panel = new NetMonitorPanel(iframeWindow, toolbox); return panel.open(); } }; let defaultTools = [ + Tools.options, Tools.styleEditor, Tools.webConsole, Tools.jsdebugger, Tools.inspector, Tools.jsprofiler, Tools.netMonitor ]; @@ -196,17 +216,17 @@ exports.defaultTools = defaultTools; for (let definition of defaultTools) { gDevTools.registerTool(definition); } var unloadObserver = { observe: function(subject, topic, data) { if (subject.wrappedJSObject === require("@loader/unload")) { Services.obs.removeObserver(unloadObserver, "sdk:loader:destroy"); - for (let definition of defaultTools) { + for (let definition of gDevTools.getToolDefinitionArray()) { gDevTools.unregisterTool(definition.id); } } } }; Services.obs.addObserver(unloadObserver, "sdk:loader:destroy", false); /**
--- a/browser/devtools/markupview/markup-view.js +++ b/browser/devtools/markupview/markup-view.js @@ -12,17 +12,17 @@ const PAGE_SIZE = 10; const PREVIEW_AREA = 700; const DEFAULT_MAX_CHILDREN = 100; let {UndoStack} = require("devtools/shared/undo"); let EventEmitter = require("devtools/shared/event-emitter"); let {editableField, InplaceEditor} = require("devtools/shared/inplace-editor"); Cu.import("resource:///modules/devtools/LayoutHelpers.jsm"); -Cu.import("resource:///modules/devtools/Templater.jsm"); +Cu.import("resource://gre/modules/devtools/Templater.jsm"); Cu.import("resource://gre/modules/Services.jsm"); /** * Vocabulary for the purposes of this file: * * MarkupContainer - the structure that holds an editor and its * immediate children in the markup panel. * Node - A content node.
--- a/browser/devtools/netmonitor/netmonitor-view.js +++ b/browser/devtools/netmonitor/netmonitor-view.js @@ -1,20 +1,28 @@ /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * 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/. */ "use strict"; +const HTML_NS = "http://www.w3.org/1999/xhtml"; const EPSILON = 0.001; +const RESIZE_REFRESH_RATE = 50; // ms const REQUESTS_REFRESH_RATE = 50; // ms const REQUESTS_HEADERS_SAFE_BOUNDS = 30; // px -const REQUESTS_WATERFALL_SAFE_BOUNDS = 100; // px -const REQUESTS_WATERFALL_BACKGROUND_PATTERN = [5, 250, 1000, 2000]; // ms +const REQUESTS_WATERFALL_SAFE_BOUNDS = 90; // px +const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms +const REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN = 60; // px +const REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms +const REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES = 3; +const REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px +const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 10; // byte +const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 16; // byte const DEFAULT_HTTP_VERSION = "HTTP/1.1"; const HEADERS_SIZE_DECIMALS = 3; const CONTENT_SIZE_DECIMALS = 2; const CONTENT_MIME_TYPE_ABBREVIATIONS = { "ecmascript": "js", "javascript": "js", "x-javascript": "js" }; @@ -43,18 +51,16 @@ const GENERIC_VARIABLES_VIEW_SETTINGS = descriptorTooltip: false, editableValueTooltip: "", editableNameTooltip: "", preventDisableOnChage: true, eval: () => {}, switch: () => {} }; -function $(aSelector, aTarget = document) aTarget.querySelector(aSelector); - /** * Object defining the network monitor view components. */ let NetMonitorView = { /** * Initializes the network monitor view. * * @param function aCallback @@ -152,16 +158,17 @@ let NetMonitorView = { button.setAttribute("pane-collapsed", ""); button.setAttribute("tooltiptext", this._expandPaneString); } if (aTabIndex !== undefined) { $("#details-pane").selectedIndex = aTabIndex; } }, + /** * Lazily initializes and returns a promise for a SourceEditor instance. * * @param string aId * The id of the editor placeholder node. * @return object * A Promise that is resolved when the editor is available. */ @@ -259,16 +266,17 @@ create({ constructor: RequestsMenuView, /** * Initialization function, called when the network monitor is started. */ initialize: function NVRM_initialize() { dumpn("Initializing the RequestsMenuView"); this.node = new SideMenuWidget($("#requests-menu-contents"), false); this.node.maintainSelectionVisible = false; + this.node.autoscrollWithAppendedItems = true; this.node.addEventListener("mousedown", this._onMouseDown, false); this.node.addEventListener("select", this._onSelect, false); window.addEventListener("resize", this._onResize, false); }, /** * Destruction function, called when the network monitor is closed. @@ -316,32 +324,154 @@ create({ constructor: RequestsMenuView, let menuView = this._createMenuView(aMethod, aUrl); // Remember the first and last event boundaries. this._registerFirstRequestStart(unixTime); this._registerLastRequestEnd(unixTime); // Append a network request item to this container. let requestItem = this.push(menuView, { - relaxed: true, /* this container should allow dupes & degenerates */ attachment: { id: aId, startedDeltaMillis: unixTime - this._firstRequestStartedMillis, startedMillis: unixTime, method: aMethod, url: aUrl }, finalize: this._onRequestItemRemoved }); + $("#details-pane-toggle").disabled = false; $(".requests-menu-empty-notice").hidden = true; + this._cache.set(aId, requestItem); }, /** + * Sorts all network requests in this container by a specified detail. + * + * @param string aType + * Either "status", "method", "file", "domain", "type" or "size". + */ + sortBy: function NVRM_sortBy(aType) { + let target = $("#requests-menu-" + aType + "-button"); + let headers = document.querySelectorAll(".requests-menu-header-button"); + + for (let header of headers) { + if (header != target) { + header.removeAttribute("sorted"); + header.removeAttribute("tooltiptext"); + } + } + + let direction = ""; + if (target) { + if (!target.hasAttribute("sorted")) { + target.setAttribute("sorted", direction = "ascending"); + target.setAttribute("tooltiptext", L10N.getStr("networkMenu.sortedAsc")); + } else if (target.getAttribute("sorted") == "ascending") { + target.setAttribute("sorted", direction = "descending"); + target.setAttribute("tooltiptext", L10N.getStr("networkMenu.sortedDesc")); + } else { + target.removeAttribute("sorted"); + target.removeAttribute("tooltiptext"); + } + } + + // Sort by timing. + if (!target || !direction) { + this.sortContents(this._byTiming); + } + // Sort by whatever was requested. + else switch (aType) { + case "status": + if (direction == "ascending") { + this.sortContents(this._byStatus); + } else { + this.sortContents((a, b) => !this._byStatus(a, b)); + } + break; + case "method": + if (direction == "ascending") { + this.sortContents(this._byMethod); + } else { + this.sortContents((a, b) => !this._byMethod(a, b)); + } + break; + case "file": + if (direction == "ascending") { + this.sortContents(this._byFile); + } else { + this.sortContents((a, b) => !this._byFile(a, b)); + } + break; + case "domain": + if (direction == "ascending") { + this.sortContents(this._byDomain); + } else { + this.sortContents((a, b) => !this._byDomain(a, b)); + } + break; + case "type": + if (direction == "ascending") { + this.sortContents(this._byType); + } else { + this.sortContents((a, b) => !this._byType(a, b)); + } + break; + case "size": + if (direction == "ascending") { + this.sortContents(this._bySize); + } else { + this.sortContents((a, b) => !this._bySize(a, b)); + } + break; + } + }, + + /** + * Predicates used when sorting items. + * + * @param MenuItem aFirst + * The first menu item used in the comparison. + * @param MenuItem aSecond + * The second menu item used in the comparison. + * @return number + * -1 to sort aFirst to a lower index than aSecond + * 0 to leave aFirst and aSecond unchanged with respect to each other + * 1 to sort aSecond to a lower index than aFirst + */ + _byTiming: (aFirst, aSecond) => + aFirst.attachment.startedMillis > aSecond.attachment.startedMillis, + + _byStatus: (aFirst, aSecond) => + aFirst.attachment.status > aSecond.attachment.status, + + _byMethod: (aFirst, aSecond) => + aFirst.attachment.method > aSecond.attachment.method, + + _byFile: (aFirst, aSecond) => + !aFirst.target || !aSecond.target ? -1 : + $(".requests-menu-file", aFirst.target).getAttribute("value").toLowerCase() > + $(".requests-menu-file", aSecond.target).getAttribute("value").toLowerCase(), + + _byDomain: (aFirst, aSecond) => + !aFirst.target || !aSecond.target ? -1 : + $(".requests-menu-domain", aFirst.target).getAttribute("value").toLowerCase() > + $(".requests-menu-domain", aSecond.target).getAttribute("value").toLowerCase(), + + _byType: (aFirst, aSecond) => + !aFirst.target || !aSecond.target ? -1 : + $(".requests-menu-type", aFirst.target).getAttribute("value").toLowerCase() > + $(".requests-menu-type", aSecond.target).getAttribute("value").toLowerCase(), + + _bySize: (aFirst, aSecond) => + aFirst.attachment.contentSize > aSecond.attachment.contentSize, + + /** * Schedules adding additional information to a network request. * * @param string aId * An identifier coming from the network monitor controller. * @param object aData * An object containing several { key: value } tuples of network info. * Supported keys are "httpVersion", "status", "statusText" etc. */ @@ -351,18 +481,18 @@ create({ constructor: RequestsMenuView, return; } this._updateQueue.push([aId, aData]); // Lazy updating is disabled in some tests. if (!this.lazyUpdate) { return void this._flushRequests(); } - window.clearTimeout(this._updateTimeout); - this._updateTimeout = window.setTimeout(this._flushRequests, REQUESTS_REFRESH_RATE); + // Allow requests to settle down first. + drain("update-requests", REQUESTS_REFRESH_RATE, () => this._flushRequests()); }, /** * Starts adding all queued additional information about network requests. */ _flushRequests: function NVRM__flushRequests() { // For each queued additional information packet, get the corresponding // request item in the view and update it based on the specified data. @@ -439,18 +569,22 @@ create({ constructor: RequestsMenuView, } // This update may have additional information about a request which // isn't shown yet in the network details pane. let selectedItem = this.selectedItem; if (selectedItem && selectedItem.attachment.id == id) { NetMonitorView.NetworkDetails.populate(selectedItem.attachment); } } + // We're done flushing all the requests, clear the update queue. this._updateQueue = []; + + // Make sure all the requests are sorted. + this.sortContents(); }, /** * Customization function for creating an item's UI. * * @param string aMethod * Specifies the request method (e.g. "GET", "POST", etc.) * @param string aUrl @@ -579,56 +713,42 @@ create({ constructor: RequestsMenuView, startCapNode.hidden = false; endCapNode.hidden = false; // Rescale all the waterfalls so that everything is visible at once. this._flushWaterfallViews(); }, /** - * Rescales and redraws all the waterfalls in this container. + * Rescales and redraws all the waterfall views in this container. * * @param boolean aReset * True if this container's width was changed. */ _flushWaterfallViews: function NVRM__flushWaterfallViews(aReset) { - // To avoid expensive operations like getBoundingClientRect(), the - // waterfalls width is cached. However, in certain scenarios like when - // the window is resized, this needs to be invalidated. + // To avoid expensive operations like getBoundingClientRect() and + // rebuilding the waterfall background each time a new request comes in, + // stuff is cached. However, in certain scenarios like when the window + // is resized, this needs to be invalidated. if (aReset) { this._cachedWaterfallWidth = 0; - - let table = $("#network-table"); - let toolbar = $("#requests-menu-toolbar"); - let columns = [ - [".requests-menu-waterfall", "waterfall-overflows"], - [".requests-menu-size", "size-overflows"], - [".requests-menu-type", "type-overflows"], - [".requests-menu-domain", "domain-overflows"] - ]; - - // Flush headers. - columns.forEach(([, attribute]) => table.removeAttribute(attribute)); - let availableWidth = toolbar.getBoundingClientRect().width; - - // Hide overflowing columns. - columns.forEach(([className, attribute]) => { - let bounds = $(".requests-menu-header" + className).getBoundingClientRect(); - if (bounds.right > availableWidth - REQUESTS_HEADERS_SAFE_BOUNDS) { - table.setAttribute(attribute, ""); - } - }); + this._hideOverflowingColumns(); } // Determine the scaling to be applied to all the waterfalls so that // everything is visible at once. One millisecond == one unscaled pixel. let availableWidth = this._waterfallWidth - REQUESTS_WATERFALL_SAFE_BOUNDS; let longestWidth = this._lastRequestEndedMillis - this._firstRequestStartedMillis; let scale = Math.min(Math.max(availableWidth / longestWidth, EPSILON), 1); + // Redraw and set the canvas background for each waterfall view. + this._showWaterfallDivisionLabels(scale); + this._drawWaterfallBackground(scale); + this._flushWaterfallBackgrounds(); + // Apply CSS transforms to each waterfall in this container totalTime // accurately translate and resize as needed. for (let [, { target, attachment }] of this._cache) { let timingsNode = $(".requests-menu-timings", target); let startCapNode = $(".requests-menu-timings-cap.start", target); let endCapNode = $(".requests-menu-timings-cap.end", target); let totalNode = $(".requests-menu-timings-total", target); @@ -647,16 +767,155 @@ create({ constructor: RequestsMenuView, timingsNode.style.transform = scaleX + " " + translateX; startCapNode.style.transform = revScaleX + " translateX(0.5px)"; endCapNode.style.transform = revScaleX + " translateX(-0.5px)"; totalNode.style.transform = revScaleX; } }, /** + * Creates the labels displayed on the waterfall header in this container. + * + * @param number aScale + * The current waterfall scale. + */ + _showWaterfallDivisionLabels: function NVRM__showWaterfallDivisionLabels(aScale) { + let container = $("#requests-menu-waterfall-header-box"); + let availableWidth = this._waterfallWidth - REQUESTS_WATERFALL_SAFE_BOUNDS; + + // Nuke all existing labels. + while (container.hasChildNodes()) { + container.firstChild.remove(); + } + + // Build new millisecond tick labels... + let timingStep = REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE; + let optimalTickIntervalFound = false; + + while (!optimalTickIntervalFound) { + // Ignore any divisions that would end up being too close to each other. + let scaledStep = aScale * timingStep; + if (scaledStep < REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN) { + timingStep <<= 1; + continue; + } + optimalTickIntervalFound = true; + + // Insert one label for each division on the current scale. + let fragment = document.createDocumentFragment(); + + for (let x = 0; x < availableWidth; x += scaledStep) { + let divisionMS = (x / aScale).toFixed(0); + let translateX = "translateX(" + (x | 0) + "px)"; + + let node = document.createElement("label"); + let text = L10N.getFormatStr("networkMenu.divisionMS", divisionMS); + node.className = "plain requests-menu-timings-division"; + node.style.transform = translateX; + + node.setAttribute("value", text); + fragment.appendChild(node); + } + container.appendChild(fragment); + } + }, + + /** + * Creates the background displayed on each waterfall view in this container. + * + * @param number aScale + * The current waterfall scale. + */ + _drawWaterfallBackground: function NVRM__drawWaterfallBackground(aScale) { + if (!this._canvas || !this._ctx) { + this._canvas = document.createElementNS(HTML_NS, "canvas"); + this._ctx = this._canvas.getContext("2d"); + } + let canvas = this._canvas; + let ctx = this._ctx; + + // Nuke the context. + let canvasWidth = canvas.width = this._waterfallWidth; + let canvasHeight = canvas.height = 1; // Awww yeah, 1px, repeats on Y axis. + + // Start over. + let imageData = ctx.createImageData(canvasWidth, canvasHeight); + let pixelArray = imageData.data; + + let buf = new ArrayBuffer(pixelArray.length); + let buf8 = new Uint8ClampedArray(buf); + let data32 = new Uint32Array(buf); + + // Build new millisecond tick lines... + let timingStep = REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE; + let alphaComponent = REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN; + let optimalTickIntervalFound = false; + + while (!optimalTickIntervalFound) { + // Ignore any divisions that would end up being too close to each other. + let scaledStep = aScale * timingStep; + if (scaledStep < REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN) { + timingStep <<= 1; + continue; + } + optimalTickIntervalFound = true; + + // Insert one pixel for each division on each scale. + for (let i = 1; i <= REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES; i++) { + let increment = scaledStep * Math.pow(2, i); + for (let x = 0; x < canvasWidth; x += increment) { + data32[x | 0] = (alphaComponent << 24) | (255 << 16) | (255 << 8) | 255; + } + alphaComponent += REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD; + } + } + + // Flush the image data and cache the waterfall background. + pixelArray.set(buf8); + ctx.putImageData(imageData, 0, 0); + this._cachedWaterfallBackground = "url(" + canvas.toDataURL() + ")"; + }, + + /** + * Reapplies the current waterfall background on all request items. + */ + _flushWaterfallBackgrounds: function NVRM__flushWaterfallBackgrounds() { + for (let [, { target }] of this._cache) { + let waterfallNode = $(".requests-menu-waterfall", target); + waterfallNode.style.backgroundImage = this._cachedWaterfallBackground; + } + }, + + /** + * Hides the overflowing columns in the requests table. + */ + _hideOverflowingColumns: function NVRM__hideOverflowingColumns() { + let table = $("#network-table"); + let toolbar = $("#requests-menu-toolbar"); + let columns = [ + ["#requests-menu-waterfall-header-box", "waterfall-overflows"], + ["#requests-menu-size-header-box", "size-overflows"], + ["#requests-menu-type-header-box", "type-overflows"], + ["#requests-menu-domain-header-box", "domain-overflows"] + ]; + + // Flush headers. + columns.forEach(([, attribute]) => table.removeAttribute(attribute)); + let availableWidth = toolbar.getBoundingClientRect().width; + + // Hide the columns. + columns.forEach(([id, attribute]) => { + let bounds = $(id).getBoundingClientRect(); + if (bounds.right > availableWidth - REQUESTS_HEADERS_SAFE_BOUNDS) { + table.setAttribute(attribute, ""); + } + }); + }, + + /** * Function called each time a network request item is removed. * * @param MenuItem aItem * The corresponding menu item. */ _onRequestItemRemoved: function NVRM__onRequestItemRemoved(aItem) { dumpn("Finalizing network request item: " + aItem); this._cache.delete(aItem.attachment.id); @@ -680,17 +939,18 @@ create({ constructor: RequestsMenuView, NetMonitorView.NetworkDetails.populate(this.selectedItem.attachment); NetMonitorView.NetworkDetails.toggle(true); }, /** * The resize listener for this container's window. */ _onResize: function NVRM__onResize(e) { - this._flushWaterfallViews(true); + // Allow requests to settle down first. + drain("resize-events", RESIZE_REFRESH_RATE, () => this._flushWaterfallViews(true)); }, /** * Checks if the specified unix time is the first one to be known of, * and saves it if so. * * @param number aUnixTime * The milliseconds to check and save. @@ -716,30 +976,34 @@ create({ constructor: RequestsMenuView, /** * Gets the available waterfall width in this container. * @return number */ get _waterfallWidth() { if (this._cachedWaterfallWidth == 0) { let container = $("#requests-menu-toolbar"); - let waterfall = $("#requests-menu-waterfall-label"); + let waterfall = $("#requests-menu-waterfall-header-box"); let containerBounds = container.getBoundingClientRect(); let waterfallBounds = waterfall.getBoundingClientRect(); this._cachedWaterfallWidth = containerBounds.width - waterfallBounds.left; } return this._cachedWaterfallWidth; }, _cache: null, + _canvas: null, + _ctx: null, _cachedWaterfallWidth: 0, + _cachedWaterfallBackground: null, _firstRequestStartedMillis: -1, _lastRequestEndedMillis: -1, _updateQueue: [], - _updateTimeout: null + _updateTimeout: null, + _resizeTimeout: null }); /** * Functions handling the requests details view. */ function NetworkDetailsView() { dumpn("NetworkDetailsView was instantiated"); }; @@ -1111,17 +1375,17 @@ create({ constructor: NetworkDetailsView $("#response-content-image-dimensions-value").setAttribute("value", dimensions); }; } // Handle anything else. else { $("#response-content-textarea-box").hidden = false; NetMonitorView.editor("#response-content-textarea").then((aEditor) => { aEditor.setMode(SourceEditor.MODES.TEXT); - aEditor.setText(aString); + aEditor.setText(NetworkHelper.convertToUnicode(aString, "UTF-8")); // Maybe set a more appropriate mode in the Source Editor if possible. for (let key in CONTENT_MIME_TYPE_MAPPINGS) { if (mimeType.contains(key)) { aEditor.setMode(CONTENT_MIME_TYPE_MAPPINGS[key]); break; } } @@ -1209,13 +1473,29 @@ create({ constructor: NetworkDetailsView _paramsPostPayload: "", _requestHeaders: "", _responseHeaders: "", _requestCookies: "", _responseCookies: "" }); /** + * DOM query helper. + */ +function $(aSelector, aTarget = document) aTarget.querySelector(aSelector); + +/** + * Helper for draining a rapid succession of events and invoking a callback + * once everything settles down. + */ +function drain(aId, aWait, aCallback) { + window.clearTimeout(drain.store.get(aId)); + drain.store.set(aId, window.setTimeout(aCallback, aWait)); +} + +drain.store = new Map(); + +/** * Preliminary setup for the NetMonitorView object. */ NetMonitorView.Toolbar = new ToolbarView(); NetMonitorView.RequestsMenu = new RequestsMenuView(); NetMonitorView.NetworkDetails = new NetworkDetailsView();
--- a/browser/devtools/netmonitor/netmonitor.css +++ b/browser/devtools/netmonitor/netmonitor.css @@ -1,13 +1,17 @@ /* vim:set ts=2 sw=2 sts=2 et: */ /* This Source Code Form is subject to the terms of the Mozilla Public * 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/. */ +#details-pane-toggle[disabled] { + visibility: hidden; +} + #response-content-image-box { overflow: auto; } #timings-summary-blocked { display: none; /* This doesn't work yet. */ }
--- a/browser/devtools/netmonitor/netmonitor.xul +++ b/browser/devtools/netmonitor/netmonitor.xul @@ -17,53 +17,94 @@ <script type="text/javascript" src="netmonitor-controller.js"/> <script type="text/javascript" src="netmonitor-view.js"/> <box id="body" flex="1" class="devtools-responsive-container"> <vbox id="network-table" flex="1"> <toolbar id="requests-menu-toolbar" class="devtools-toolbar" align="center"> - <label id="requests-menu-status-and-method-label" - class="plain requests-menu-header requests-menu-status-and-method" - value="&netmonitorUI.toolbar.method;" - crop="end"/> - <label id="requests-menu-file-label" - class="plain requests-menu-header requests-menu-file" - value="&netmonitorUI.toolbar.file;" - crop="end"/> - <label id="requests-menu-domain-label" - class="plain requests-menu-header requests-menu-domain" - value="&netmonitorUI.toolbar.domain;" - crop="end"/> - <label id="requests-menu-type-label" - class="plain requests-menu-header requests-menu-type" - value="&netmonitorUI.toolbar.type;" - crop="end"/> - <label id="requests-menu-size-label" - class="plain requests-menu-header requests-menu-size" - value="&netmonitorUI.toolbar.size;" - crop="end"/> - <label id="requests-menu-waterfall-label" - class="plain requests-menu-header requests-menu-waterfall" - value="&netmonitorUI.toolbar.waterfall;" - crop="end"/> + <hbox id="toolbar-labels" flex="1"> + <hbox id="requests-menu-status-and-method-header-box" + class="requests-menu-header requests-menu-status-and-method" + align="center"> + <button id="requests-menu-status-button" + class="requests-menu-header-button requests-menu-status" + onclick="NetMonitorView.RequestsMenu.sortBy('status')"> + &netmonitorUI.toolbar.status; + </button> + <button id="requests-menu-method-button" + class="requests-menu-header-button requests-menu-method" + onclick="NetMonitorView.RequestsMenu.sortBy('method')" + flex="1"> + &netmonitorUI.toolbar.method; + </button> + </hbox> + <hbox id="requests-menu-file-header-box" + class="requests-menu-header requests-menu-file" + align="center"> + <button id="requests-menu-file-button" + class="requests-menu-header-button requests-menu-file" + onclick="NetMonitorView.RequestsMenu.sortBy('file')" + flex="1"> + &netmonitorUI.toolbar.file; + </button> + </hbox> + <hbox id="requests-menu-domain-header-box" + class="requests-menu-header requests-menu-domain" + align="center"> + <button id="requests-menu-domain-button" + class="requests-menu-header-button requests-menu-domain" + onclick="NetMonitorView.RequestsMenu.sortBy('domain')" + flex="1"> + &netmonitorUI.toolbar.domain; + </button> + </hbox> + <hbox id="requests-menu-type-header-box" + class="requests-menu-header requests-menu-type" + align="center"> + <button id="requests-menu-type-button" + class="requests-menu-header-button requests-menu-type" + onclick="NetMonitorView.RequestsMenu.sortBy('type')" + flex="1"> + &netmonitorUI.toolbar.type; + </button> + </hbox> + <hbox id="requests-menu-size-header-box" + class="requests-menu-header requests-menu-size" + align="center"> + <button id="requests-menu-size-button" + class="requests-menu-header-button requests-menu-size" + onclick="NetMonitorView.RequestsMenu.sortBy('size')" + flex="1"> + &netmonitorUI.toolbar.size; + </button> + </hbox> + <hbox id="requests-menu-waterfall-header-box" + class="requests-menu-header requests-menu-waterfall" + align="center"> + <label id="requests-menu-waterfall-label" + class="plain requests-menu-waterfall" + value="&netmonitorUI.toolbar.waterfall;"/> + </hbox> + </hbox> <spacer id="toolbar-spacer" flex="1"/> <toolbarbutton id="details-pane-toggle" class="devtools-toolbarbutton" tooltiptext="&netmonitorUI.panesButton.tooltip;" + disabled="true" tabindex="0"/> </toolbar> <label class="plain requests-menu-empty-notice" value="&netmonitorUI.emptyNotice;"/> <vbox id="requests-menu-contents" flex="1"> <hbox id="requests-menu-item-template" hidden="true"> <hbox class="requests-menu-subitem requests-menu-status-and-method" align="center"> - <hbox class="requests-menu-status"/> + <box class="requests-menu-status"/> <label class="plain requests-menu-method" crop="end" flex="1"/> </hbox> <label class="plain requests-menu-subitem requests-menu-file" crop="end"/> <label class="plain requests-menu-subitem requests-menu-domain" crop="end"/> @@ -119,17 +160,17 @@ crop="end" flex="1"/> </hbox> <hbox id="headers-summary-status" class="tabpanel-summary-container" align="center"> <label class="plain tabpanel-summary-label" value="&netmonitorUI.summary.status;"/> - <hbox id="headers-summary-status-circle" + <box id="headers-summary-status-circle" class="requests-menu-status"/> <label id="headers-summary-status-value" class="plain tabpanel-summary-value" crop="end" flex="1"/> </hbox> <hbox id="headers-summary-version" class="tabpanel-summary-container"
--- a/browser/devtools/netmonitor/test/Makefile.in +++ b/browser/devtools/netmonitor/test/Makefile.in @@ -7,41 +7,51 @@ topsrcdir = @top_srcdir@ srcdir = @srcdir@ VPATH = @srcdir@ relativesrcdir = @relativesrcdir@ include $(DEPTH)/config/autoconf.mk MOCHITEST_BROWSER_TESTS = \ browser_net_aaa_leaktest.js \ + browser_net_autoscroll.js \ browser_net_simple-init.js \ browser_net_page-nav.js \ browser_net_prefs-and-l10n.js \ browser_net_prefs-reload.js \ browser_net_pane-collapse.js \ browser_net_simple-request.js \ browser_net_simple-request-data.js \ browser_net_simple-request-details.js \ browser_net_content-type.js \ + browser_net_cyrillic.js \ browser_net_status-codes.js \ browser_net_post-data.js \ browser_net_jsonp.js \ browser_net_json-long.js \ + browser_net_timeline_ticks.js \ + browser_net_sort-01.js \ + browser_net_sort-02.js \ + browser_net_sort-03.js \ head.js \ $(NULL) MOCHITEST_BROWSER_PAGES = \ test-image.png \ html_simple-test-page.html \ html_navigate-test-page.html \ html_content-type-test-page.html \ + html_cyrillic-test-page.html \ html_status-codes-test-page.html \ html_post-data-test-page.html \ html_jsonp-test-page.html \ html_json-long-test-page.html \ + html_sorting-test-page.html \ + html_infinite-get-page.html \ sjs_simple-test-server.sjs \ sjs_content-type-test-server.sjs \ sjs_status-codes-test-server.sjs \ + sjs_sorting-test-server.sjs \ $(NULL) MOCHITEST_BROWSER_FILES_PARTS = MOCHITEST_BROWSER_TESTS MOCHITEST_BROWSER_PAGES include $(topsrcdir)/config/rules.mk
new file mode 100644 --- /dev/null +++ b/browser/devtools/netmonitor/test/browser_net_autoscroll.js @@ -0,0 +1,77 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 863102 - Automatically scroll down upon new network requests. + */ + +function test() { + let monitor, debuggee, requestsContainer, scrollTop; + + initNetMonitor(INFINITE_GET_URL).then(([aTab, aDebuggee, aMonitor]) => { + monitor = aMonitor; + debuggee = aDebuggee; + let win = monitor.panelWin; + let topNode = win.document.getElementById("requests-menu-contents"); + requestsContainer = topNode.getElementsByTagName("scrollbox")[0]; + ok(!!requestsContainer, "Container element exists as expected."); + }) + + // (1) Check that the scroll position is maintained at the bottom + // when the requests overflow the vertical size of the container. + .then(() => { + debuggee.performRequests(); + return waitForRequestsToOverflowContainer(monitor, requestsContainer); + }).then(() => { + ok(scrolledToBottom(requestsContainer), "Scrolled to bottom on overflow."); + }) + + // (2) Now set the scroll position somewhere in the middle and check + // that additional requests do not change the scroll position. + .then(() => { + let children = requestsContainer.childNodes; + let middleNode = children.item(children.length / 2); + middleNode.scrollIntoView(); + ok(!scrolledToBottom(requestsContainer), "Not scrolled to bottom."); + scrollTop = requestsContainer.scrollTop; // save for comparison later + return waitForNetworkEvents(monitor, 8); + }).then(() => { + is(requestsContainer.scrollTop, scrollTop, "Did not scroll."); + }) + + // (3) Now set the scroll position back at the bottom and check that + // additional requests *do* cause the container to scroll down. + .then(() => { + requestsContainer.scrollTop = requestsContainer.scrollHeight; + ok(scrolledToBottom(requestsContainer), "Set scroll position to bottom."); + return waitForNetworkEvents(monitor, 8); + }).then(() => { + ok(scrolledToBottom(requestsContainer), "Still scrolled to bottom."); + }) + + // Done; clean up. + .then(() => { + return teardown(monitor).then(finish); + }) + + // Handle exceptions in the chain of promises. + .then(null, (err) => { + ok(false, err); + finish(); + }); + + function waitForRequestsToOverflowContainer (aMonitor, aContainer) { + return waitForNetworkEvents(aMonitor, 1).then(() => { + if (aContainer.scrollHeight > aContainer.clientHeight) { + // Wait for some more just for good measure. + return waitForNetworkEvents(aMonitor, 8); + } else { + return waitForRequestsToOverflowContainer(aMonitor, aContainer); + } + }); + } + + function scrolledToBottom(aElement) { + return aElement.scrollTop + aElement.clientHeight >= aElement.scrollHeight; + } +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/netmonitor/test/browser_net_cyrillic.js @@ -0,0 +1,41 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if cyrillic text is rendered correctly in the source editor. + */ + +function test() { + initNetMonitor(CYRILLIC_URL).then(([aTab, aDebuggee, aMonitor]) => { + info("Starting test... "); + + let { document, L10N, SourceEditor, NetMonitorView } = aMonitor.panelWin; + let { RequestsMenu } = NetMonitorView; + + RequestsMenu.lazyUpdate = false; + + waitForNetworkEvents(aMonitor, 1).then(() => { + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0), + "GET", CONTENT_TYPE_SJS + "?fmt=txt", { + status: 200, + statusText: "DA DA DA" + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + document.getElementById("details-pane-toggle")); + EventUtils.sendMouseEvent({ type: "mousedown" }, + document.querySelectorAll("#details-pane tab")[3]); + + NetMonitorView.editor("#response-content-textarea").then((aEditor) => { + is(aEditor.getText().indexOf("\u044F"), 26, // я + "The text shown in the source editor is incorrect."); + is(aEditor.getMode(), SourceEditor.MODES.TEXT, + "The mode active in the source editor is incorrect."); + + teardown(aMonitor).then(finish); + }); + }); + + aDebuggee.performRequests(); + }); +}
--- a/browser/devtools/netmonitor/test/browser_net_simple-request.js +++ b/browser/devtools/netmonitor/test/browser_net_simple-request.js @@ -9,34 +9,43 @@ function test() { initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => { info("Starting test... "); let { document, NetMonitorView } = aMonitor.panelWin; let { RequestsMenu } = NetMonitorView; RequestsMenu.lazyUpdate = false; + is(document.querySelector("#details-pane-toggle") + .hasAttribute("disabled"), true, + "The pane toggle button should be disabled when the frontend is opened."); is(document.querySelector(".requests-menu-empty-notice") .hasAttribute("hidden"), false, "An empty notice should be displayed when the frontend is opened."); is(RequestsMenu.itemCount, 0, "The requests menu should be empty when the frontend is opened."); is(NetMonitorView.detailsPaneHidden, true, "The details pane should be hidden when the frontend is opened."); aMonitor.panelWin.once("NetMonitor:NetworkEvent", () => { + is(document.querySelector("#details-pane-toggle") + .hasAttribute("disabled"), false, + "The pane toggle button should be enabled after the first request."); is(document.querySelector(".requests-menu-empty-notice") .hasAttribute("hidden"), true, "The empty notice should be hidden after the first request."); is(RequestsMenu.itemCount, 1, "The requests menu should not be empty after the first request."); is(NetMonitorView.detailsPaneHidden, true, "The details pane should still be hidden after the first request."); aMonitor.panelWin.once("NetMonitor:NetworkEvent", () => { + is(document.querySelector("#details-pane-toggle") + .hasAttribute("disabled"), false, + "The pane toggle button should be still be enabled after a reload."); is(document.querySelector(".requests-menu-empty-notice") .hasAttribute("hidden"), true, "The empty notice should be still hidden after a reload."); is(RequestsMenu.itemCount, 1, "The requests menu should not be empty after a reload."); is(NetMonitorView.detailsPaneHidden, true, "The details pane should still be hidden after a reload.");
new file mode 100644 --- /dev/null +++ b/browser/devtools/netmonitor/test/browser_net_sort-01.js @@ -0,0 +1,225 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test if the sorting mechanism works correctly. + */ + +function test() { + initNetMonitor(STATUS_CODES_URL).then(([aTab, aDebuggee, aMonitor]) => { + info("Starting test... "); + + let { L10N, NetMonitorView } = aMonitor.panelWin; + let { RequestsMenu } = NetMonitorView; + + RequestsMenu.lazyUpdate = false; + + waitForNetworkEvents(aMonitor, 5).then(() => { + testContents([0, 1, 2, 3, 4]) + .then(() => { + info("Testing swap(0, 0)"); + RequestsMenu.swapItemsAtIndices(0, 0); + return testContents([0, 1, 2, 3, 4]); + }) + .then(() => { + info("Testing swap(0, 1)"); + RequestsMenu.swapItemsAtIndices(0, 1); + return testContents([1, 0, 2, 3, 4]); + }) + .then(() => { + info("Testing swap(0, 2)"); + RequestsMenu.swapItemsAtIndices(0, 2); + return testContents([1, 2, 0, 3, 4]); + }) + .then(() => { + info("Testing swap(0, 3)"); + RequestsMenu.swapItemsAtIndices(0, 3); + return testContents([1, 2, 3, 0, 4]); + }) + .then(() => { + info("Testing swap(0, 4)"); + RequestsMenu.swapItemsAtIndices(0, 4); + return testContents([1, 2, 3, 4, 0]); + }) + .then(() => { + info("Testing swap(1, 0)"); + RequestsMenu.swapItemsAtIndices(1, 0); + return testContents([0, 2, 3, 4, 1]); + }) + .then(() => { + info("Testing swap(1, 1)"); + RequestsMenu.swapItemsAtIndices(1, 1); + return testContents([0, 2, 3, 4, 1]); + }) + .then(() => { + info("Testing swap(1, 2)"); + RequestsMenu.swapItemsAtIndices(1, 2); + return testContents([0, 1, 3, 4, 2]); + }) + .then(() => { + info("Testing swap(1, 3)"); + RequestsMenu.swapItemsAtIndices(1, 3); + return testContents([0, 3, 1, 4, 2]); + }) + .then(() => { + info("Testing swap(1, 4)"); + RequestsMenu.swapItemsAtIndices(1, 4); + return testContents([0, 3, 4, 1, 2]); + }) + .then(() => { + info("Testing swap(2, 0)"); + RequestsMenu.swapItemsAtIndices(2, 0); + return testContents([2, 3, 4, 1, 0]); + }) + .then(() => { + info("Testing swap(2, 1)"); + RequestsMenu.swapItemsAtIndices(2, 1); + return testContents([1, 3, 4, 2, 0]); + }) + .then(() => { + info("Testing swap(2, 2)"); + RequestsMenu.swapItemsAtIndices(2, 2); + return testContents([1, 3, 4, 2, 0]); + }) + .then(() => { + info("Testing swap(2, 3)"); + RequestsMenu.swapItemsAtIndices(2, 3); + return testContents([1, 2, 4, 3, 0]); + }) + .then(() => { + info("Testing swap(2, 4)"); + RequestsMenu.swapItemsAtIndices(2, 4); + return testContents([1, 4, 2, 3, 0]); + }) + .then(() => { + info("Testing swap(3, 0)"); + RequestsMenu.swapItemsAtIndices(3, 0); + return testContents([1, 4, 2, 0, 3]); + }) + .then(() => { + info("Testing swap(3, 1)"); + RequestsMenu.swapItemsAtIndices(3, 1); + return testContents([3, 4, 2, 0, 1]); + }) + .then(() => { + info("Testing swap(3, 2)"); + RequestsMenu.swapItemsAtIndices(3, 2); + return testContents([2, 4, 3, 0, 1]); + }) + .then(() => { + info("Testing swap(3, 3)"); + RequestsMenu.swapItemsAtIndices(3, 3); + return testContents([2, 4, 3, 0, 1]); + }) + .then(() => { + info("Testing swap(3, 4)"); + RequestsMenu.swapItemsAtIndices(3, 4); + return testContents([2, 3, 4, 0, 1]); + }) + .then(() => { + info("Testing swap(4, 0)"); + RequestsMenu.swapItemsAtIndices(4, 0); + return testContents([2, 3, 0, 4, 1]); + }) + .then(() => { + info("Testing swap(4, 1)"); + RequestsMenu.swapItemsAtIndices(4, 1); + return testContents([2, 3, 0, 1, 4]); + }) + .then(() => { + info("Testing swap(4, 2)"); + RequestsMenu.swapItemsAtIndices(4, 2); + return testContents([4, 3, 0, 1, 2]); + }) + .then(() => { + info("Testing swap(4, 3)"); + RequestsMenu.swapItemsAtIndices(4, 3); + return testContents([3, 4, 0, 1, 2]); + }) + .then(() => { + info("Testing swap(4, 4)"); + RequestsMenu.swapItemsAtIndices(4, 4); + return testContents([3, 4, 0, 1, 2]); + }) + .then(() => { + RequestsMenu.sortBy(null); + return testContents([0, 1, 2, 3, 4]); + }) + .then(() => { + return teardown(aMonitor); + }) + .then(finish); + }); + + function testContents([a, b, c, d, e]) { + let deferred = Promise.defer(); + + is(RequestsMenu.allItems.length, 5, + "There should be a total of 5 items in the requests menu."); + is(RequestsMenu.visibleItems.length, 5, + "There should be a total of 5 visbile items in the requests menu."); + + is(RequestsMenu.getItemAtIndex(0), RequestsMenu.allItems[0], + "The requests menu items aren't ordered correctly. First item is misplaced."); + is(RequestsMenu.getItemAtIndex(1), RequestsMenu.allItems[1], + "The requests menu items aren't ordered correctly. Second item is misplaced."); + is(RequestsMenu.getItemAtIndex(2), RequestsMenu.allItems[2], + "The requests menu items aren't ordered correctly. Third item is misplaced."); + is(RequestsMenu.getItemAtIndex(3), RequestsMenu.allItems[3], + "The requests menu items aren't ordered correctly. Fourth item is misplaced."); + is(RequestsMenu.getItemAtIndex(4), RequestsMenu.allItems[4], + "The requests menu items aren't ordered correctly. Fifth item is misplaced."); + + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(a), + "GET", STATUS_CODES_SJS + "?sts=100", { + status: 101, + statusText: "Switching Protocols", + type: "plain", + fullMimeType: "text/plain; charset=utf-8", + size: L10N.getFormatStr("networkMenu.sizeKB", 0), + time: true + }); + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(b), + "GET", STATUS_CODES_SJS + "?sts=200", { + status: 202, + statusText: "Created", + type: "plain", + fullMimeType: "text/plain; charset=utf-8", + size: L10N.getFormatStr("networkMenu.sizeKB", 0.02), + time: true + }); + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(c), + "GET", STATUS_CODES_SJS + "?sts=300", { + status: 303, + statusText: "See Other", + type: "plain", + fullMimeType: "text/plain; charset=utf-8", + size: L10N.getFormatStr("networkMenu.sizeKB", 0), + time: true + }); + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(d), + "GET", STATUS_CODES_SJS + "?sts=400", { + status: 404, + statusText: "Not Found", + type: "plain", + fullMimeType: "text/plain; charset=utf-8", + size: L10N.getFormatStr("networkMenu.sizeKB", 0.02), + time: true + }); + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(e), + "GET", STATUS_CODES_SJS + "?sts=500", { + status: 501, + statusText: "Not Implemented", + type: "plain", + fullMimeType: "text/plain; charset=utf-8", + size: L10N.getFormatStr("networkMenu.sizeKB", 0.02), + time: true + }); + + executeSoon(deferred.resolve); + return deferred.promise; + } + + aDebuggee.performRequests(); + }); +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/netmonitor/test/browser_net_sort-02.js @@ -0,0 +1,225 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test if sorting columns in the network table works correctly. + */ + +function test() { + initNetMonitor(SORTING_URL).then(([aTab, aDebuggee, aMonitor]) => { + info("Starting test... "); + + let { $, L10N, NetMonitorView } = aMonitor.panelWin; + let { RequestsMenu } = NetMonitorView; + + RequestsMenu.lazyUpdate = false; + + waitForNetworkEvents(aMonitor, 5).then(() => { + EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle")); + + isnot(RequestsMenu.selectedItem, null, + "There should be a selected item in the requests menu."); + is(RequestsMenu.selectedIndex, 0, + "The first item should be selected in the requests menu."); + is(NetMonitorView.detailsPaneHidden, false, + "The details pane should not be hidden after toggle button was pressed."); + + testHeaders(); + testContents([0, 2, 4, 3, 1]) + .then(() => { + info("Testing status sort, ascending."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button")); + testHeaders("status", "ascending"); + return testContents([0, 1, 2, 3, 4]); + }) + .then(() => { + info("Testing status sort, descending."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button")); + testHeaders("status", "descending"); + return testContents([4, 3, 2, 1, 0]); + }) + .then(() => { + info("Clearing status sort."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button")); + testHeaders(); + return testContents([0, 2, 4, 3, 1]); + }) + .then(() => { + info("Testing method sort, ascending."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-method-button")); + testHeaders("method", "ascending"); + return testContents([0, 1, 2, 3, 4]); + }) + .then(() => { + info("Testing method sort, descending."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-method-button")); + testHeaders("method", "descending"); + return testContents([4, 3, 2, 1, 0]); + }) + .then(() => { + info("Clearing method sort."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-method-button")); + testHeaders(); + return testContents([0, 2, 4, 3, 1]); + }) + .then(() => { + info("Testing file sort, ascending."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-file-button")); + testHeaders("file", "ascending"); + return testContents([0, 1, 2, 3, 4]); + }) + .then(() => { + info("Testing file sort, descending."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-file-button")); + testHeaders("file", "descending"); + return testContents([4, 3, 2, 1, 0]); + }) + .then(() => { + info("Clearing file sort."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-file-button")); + testHeaders(); + return testContents([0, 2, 4, 3, 1]); + }) + .then(() => { + info("Testing type sort, ascending."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-type-button")); + testHeaders("type", "ascending"); + return testContents([0, 1, 2, 3, 4]); + }) + .then(() => { + info("Testing type sort, descending."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-type-button")); + testHeaders("type", "descending"); + return testContents([4, 3, 2, 1, 0]); + }) + .then(() => { + info("Clearing type sort."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-type-button")); + testHeaders(); + return testContents([0, 2, 4, 3, 1]); + }) + .then(() => { + info("Testing size sort, ascending."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button")); + testHeaders("size", "ascending"); + return testContents([0, 1, 2, 3, 4]); + }) + .then(() => { + info("Testing size sort, descending."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button")); + testHeaders("size", "descending"); + return testContents([4, 3, 2, 1, 0]); + }) + .then(() => { + info("Clearing size sort."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button")); + testHeaders(); + return testContents([0, 2, 4, 3, 1]); + }) + .then(() => { + return teardown(aMonitor); + }) + .then(finish); + }); + + function testHeaders(aSortType, aDirection) { + let doc = aMonitor.panelWin.document; + let target = doc.querySelector("#requests-menu-" + aSortType + "-button"); + let headers = doc.querySelectorAll(".requests-menu-header-button"); + + for (let header of headers) { + if (header != target) { + is(header.hasAttribute("sorted"), false, + "The " + header.id + " header should not have a 'sorted' attribute."); + is(header.hasAttribute("tooltiptext"), false, + "The " + header.id + " header should not have a 'tooltiptext' attribute."); + } else { + is(header.getAttribute("sorted"), aDirection, + "The " + header.id + " header has an incorrect 'sorted' attribute."); + is(header.getAttribute("tooltiptext"), aDirection == "ascending" + ? L10N.getStr("networkMenu.sortedAsc") + : L10N.getStr("networkMenu.sortedDesc"), + "The " + header.id + " has an incorrect 'tooltiptext' attribute."); + } + } + } + + function testContents([a, b, c, d, e]) { + let deferred = Promise.defer(); + + isnot(RequestsMenu.selectedItem, null, + "There should still be a selected item after sorting."); + is(RequestsMenu.selectedIndex, a, + "The first item should be still selected after sorting."); + is(NetMonitorView.detailsPaneHidden, false, + "The details pane should still be visible after sorting."); + + is(RequestsMenu.allItems.length, 5, + "There should be a total of 5 items in the requests menu."); + is(RequestsMenu.visibleItems.length, 5, + "There should be a total of 5 visbile items in the requests menu."); + + is(RequestsMenu.getItemAtIndex(0), RequestsMenu.allItems[0], + "The requests menu items aren't ordered correctly. First item is misplaced."); + is(RequestsMenu.getItemAtIndex(1), RequestsMenu.allItems[1], + "The requests menu items aren't ordered correctly. Second item is misplaced."); + is(RequestsMenu.getItemAtIndex(2), RequestsMenu.allItems[2], + "The requests menu items aren't ordered correctly. Third item is misplaced."); + is(RequestsMenu.getItemAtIndex(3), RequestsMenu.allItems[3], + "The requests menu items aren't ordered correctly. Fourth item is misplaced."); + is(RequestsMenu.getItemAtIndex(4), RequestsMenu.allItems[4], + "The requests menu items aren't ordered correctly. Fifth item is misplaced."); + + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(a), + "GET1", SORTING_SJS + "?index=1", { + status: 101, + statusText: "Meh", + type: "1", + fullMimeType: "text/1", + size: L10N.getFormatStr("networkMenu.sizeKB", 0), + time: true + }); + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(b), + "GET2", SORTING_SJS + "?index=2", { + status: 200, + statusText: "Meh", + type: "2", + fullMimeType: "text/2", + size: L10N.getFormatStr("networkMenu.sizeKB", 0.01), + time: true + }); + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(c), + "GET3", SORTING_SJS + "?index=3", { + status: 300, + statusText: "Meh", + type: "3", + fullMimeType: "text/3", + size: L10N.getFormatStr("networkMenu.sizeKB", 0.02), + time: true + }); + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(d), + "GET4", SORTING_SJS + "?index=4", { + status: 400, + statusText: "Meh", + type: "4", + fullMimeType: "text/4", + size: L10N.getFormatStr("networkMenu.sizeKB", 0.03), + time: true + }); + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(e), + "GET5", SORTING_SJS + "?index=5", { + status: 500, + statusText: "Meh", + type: "5", + fullMimeType: "text/5", + size: L10N.getFormatStr("networkMenu.sizeKB", 0.04), + time: true + }); + + executeSoon(deferred.resolve); + return deferred.promise; + } + + aDebuggee.performRequests(); + }); +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/netmonitor/test/browser_net_sort-03.js @@ -0,0 +1,171 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test if sorting columns in the network table works correctly with new requests. + */ + +function test() { + initNetMonitor(SORTING_URL).then(([aTab, aDebuggee, aMonitor]) => { + info("Starting test... "); + + let { $, L10N, NetMonitorView } = aMonitor.panelWin; + let { RequestsMenu } = NetMonitorView; + + RequestsMenu.lazyUpdate = false; + + waitForNetworkEvents(aMonitor, 5).then(() => { + EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle")); + + isnot(RequestsMenu.selectedItem, null, + "There should be a selected item in the requests menu."); + is(RequestsMenu.selectedIndex, 0, + "The first item should be selected in the requests menu."); + is(NetMonitorView.detailsPaneHidden, false, + "The details pane should not be hidden after toggle button was pressed."); + + testHeaders(); + testContents([0, 2, 4, 3, 1], 0) + .then(() => { + info("Testing status sort, ascending."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button")); + testHeaders("status", "ascending"); + return testContents([0, 1, 2, 3, 4], 0); + }) + .then(() => { + info("Performing more requests."); + aDebuggee.performRequests(); + return waitForNetworkEvents(aMonitor, 5); + }) + .then(() => { + info("Testing status sort again, ascending."); + testHeaders("status", "ascending"); + return testContents([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0); + }) + .then(() => { + info("Testing status sort, descending."); + EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button")); + testHeaders("status", "descending"); + return testContents([9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 9); + }) + .then(() => { + info("Performing more requests."); + aDebuggee.performRequests(); + return waitForNetworkEvents(aMonitor, 5); + }) + .then(() => { + info("Testing status sort again, descending."); + testHeaders("status", "descending"); + return testContents([14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 12); + }) + .then(() => { + return teardown(aMonitor); + }) + .then(finish); + }); + + function testHeaders(aSortType, aDirection) { + let doc = aMonitor.panelWin.document; + let target = doc.querySelector("#requests-menu-" + aSortType + "-button"); + let headers = doc.querySelectorAll(".requests-menu-header-button"); + + for (let header of headers) { + if (header != target) { + is(header.hasAttribute("sorted"), false, + "The " + header.id + " header should not have a 'sorted' attribute."); + is(header.hasAttribute("tooltiptext"), false, + "The " + header.id + " header should not have a 'tooltiptext' attribute."); + } else { + is(header.getAttribute("sorted"), aDirection, + "The " + header.id + " header has an incorrect 'sorted' attribute."); + is(header.getAttribute("tooltiptext"), aDirection == "ascending" + ? L10N.getStr("networkMenu.sortedAsc") + : L10N.getStr("networkMenu.sortedDesc"), + "The " + header.id + " has an incorrect 'tooltiptext' attribute."); + } + } + } + + function testContents(aOrder, aSelection) { + let deferred = Promise.defer(); + + isnot(RequestsMenu.selectedItem, null, + "There should still be a selected item after sorting."); + is(RequestsMenu.selectedIndex, aSelection, + "The first item should be still selected after sorting."); + is(NetMonitorView.detailsPaneHidden, false, + "The details pane should still be visible after sorting."); + + is(RequestsMenu.allItems.length, aOrder.length, + "There should be a specific number of items in the requests menu."); + is(RequestsMenu.visibleItems.length, aOrder.length, + "There should be a specific number of visbile items in the requests menu."); + + for (let i = 0; i < aOrder.length; i++) { + is(RequestsMenu.getItemAtIndex(i), RequestsMenu.allItems[i], + "The requests menu items aren't ordered correctly. Misplaced item " + i + "."); + } + + for (let i = 0, len = aOrder.length / 5; i < len; i++) { + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i]), + "GET1", SORTING_SJS + "?index=1", { + status: 101, + statusText: "Meh", + type: "1", + fullMimeType: "text/1", + size: L10N.getFormatStr("networkMenu.sizeKB", 0), + time: true + }); + } + for (let i = 0, len = aOrder.length / 5; i < len; i++) { + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len]), + "GET2", SORTING_SJS + "?index=2", { + status: 200, + statusText: "Meh", + type: "2", + fullMimeType: "text/2", + size: L10N.getFormatStr("networkMenu.sizeKB", 0.01), + time: true + }); + } + for (let i = 0, len = aOrder.length / 5; i < len; i++) { + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 2]), + "GET3", SORTING_SJS + "?index=3", { + status: 300, + statusText: "Meh", + type: "3", + fullMimeType: "text/3", + size: L10N.getFormatStr("networkMenu.sizeKB", 0.02), + time: true + }); + } + for (let i = 0, len = aOrder.length / 5; i < len; i++) { + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 3]), + "GET4", SORTING_SJS + "?index=4", { + status: 400, + statusText: "Meh", + type: "4", + fullMimeType: "text/4", + size: L10N.getFormatStr("networkMenu.sizeKB", 0.03), + time: true + }); + } + for (let i = 0, len = aOrder.length / 5; i < len; i++) { + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 4]), + "GET5", SORTING_SJS + "?index=5", { + status: 500, + statusText: "Meh", + type: "5", + fullMimeType: "text/5", + size: L10N.getFormatStr("networkMenu.sizeKB", 0.04), + time: true + }); + } + + executeSoon(deferred.resolve); + return deferred.promise; + } + + aDebuggee.performRequests(); + }); +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/netmonitor/test/browser_net_timeline_ticks.js @@ -0,0 +1,137 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if timeline correctly displays interval divisions. + */ + +function test() { + initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => { + info("Starting test... "); + + let { document, L10N, NetMonitorView } = aMonitor.panelWin; + let { RequestsMenu } = NetMonitorView; + + RequestsMenu.lazyUpdate = false; + + is(document.querySelector(".requests-menu-empty-notice") + .hasAttribute("hidden"), false, + "An timeline label should be displayed when the frontend is opened."); + ok(document.querySelectorAll(".requests-menu-timings-division").length == 0, + "No tick labels should be displayed when the frontend is opened."); + + ok(!RequestsMenu._canvas, + "No canvas should be created when the frontend is opened."); + ok(!RequestsMenu._ctx, + "No 2d context should be created when the frontend is opened."); + + waitForNetworkEvents(aMonitor, 1).then(() => { + is(document.querySelector(".requests-menu-empty-notice") + .hasAttribute("hidden"), true, + "The timeline label should be hidden after the first request."); + ok(document.querySelectorAll(".requests-menu-timings-division").length >= 3, + "There should be at least 3 tick labels in the network requests header."); + + is(document.querySelectorAll(".requests-menu-timings-division")[0] + .getAttribute("value"), L10N.getFormatStr("networkMenu.divisionMS", 0), + "The first tick label has an incorrect value"); + is(document.querySelectorAll(".requests-menu-timings-division")[1] + .getAttribute("value"), L10N.getFormatStr("networkMenu.divisionMS", 80), + "The second tick label has an incorrect value"); + is(document.querySelectorAll(".requests-menu-timings-division")[2] + .getAttribute("value"), L10N.getFormatStr("networkMenu.divisionMS", 160), + "The third tick label has an incorrect value"); + + is(document.querySelectorAll(".requests-menu-timings-division")[0] + .style.transform, "translateX(0px)", + "The first tick label has an incorrect translation"); + is(document.querySelectorAll(".requests-menu-timings-division")[1] + .style.transform, "translateX(80px)", + "The second tick label has an incorrect translation"); + is(document.querySelectorAll(".requests-menu-timings-division")[2] + .style.transform, "translateX(160px)", + "The third tick label has an incorrect translation"); + + ok(RequestsMenu._canvas, + "A canvas should be created after the first request."); + ok(RequestsMenu._ctx, + "A 2d context should be created after the first request."); + + let imageData = RequestsMenu._ctx.getImageData(0, 0, 161, 1); + ok(imageData, "The image data should have been created."); + + let data = imageData.data; + ok(data, "The image data should contain a pixel array."); + + ok( hasPixelAt(0), "The tick at 0 is should not be empty."); + ok(!hasPixelAt(1), "The tick at 1 is should be empty."); + ok(!hasPixelAt(19), "The tick at 19 is should be empty."); + ok( hasPixelAt(20), "The tick at 20 is should not be empty."); + ok(!hasPixelAt(21), "The tick at 21 is should be empty."); + ok(!hasPixelAt(39), "The tick at 39 is should be empty."); + ok( hasPixelAt(40), "The tick at 40 is should not be empty."); + ok(!hasPixelAt(41), "The tick at 41 is should be empty."); + ok(!hasPixelAt(59), "The tick at 59 is should be empty."); + ok( hasPixelAt(60), "The tick at 60 is should not be empty."); + ok(!hasPixelAt(61), "The tick at 61 is should be empty."); + ok(!hasPixelAt(79), "The tick at 79 is should be empty."); + ok( hasPixelAt(80), "The tick at 80 is should not be empty."); + ok(!hasPixelAt(81), "The tick at 81 is should be empty."); + ok(!hasPixelAt(159), "The tick at 159 is should be empty."); + ok( hasPixelAt(160), "The tick at 160 is should not be empty."); + ok(!hasPixelAt(161), "The tick at 161 is should be empty."); + + ok(isPixelBrighterAtThan(0, 20), + "The tick at 0 should be brighter than the one at 20"); + ok(isPixelBrighterAtThan(40, 20), + "The tick at 40 should be brighter than the one at 20"); + ok(isPixelBrighterAtThan(40, 60), + "The tick at 40 should be brighter than the one at 60"); + ok(isPixelBrighterAtThan(80, 60), + "The tick at 80 should be brighter than the one at 60"); + + ok(isPixelBrighterAtThan(80, 100), + "The tick at 80 should be brighter than the one at 100"); + ok(isPixelBrighterAtThan(120, 100), + "The tick at 120 should be brighter than the one at 100"); + ok(isPixelBrighterAtThan(120, 140), + "The tick at 120 should be brighter than the one at 140"); + ok(isPixelBrighterAtThan(160, 140), + "The tick at 160 should be brighter than the one at 140"); + + ok(isPixelEquallyBright(20, 60), + "The tick at 20 should be equally bright to the one at 60"); + ok(isPixelEquallyBright(100, 140), + "The tick at 100 should be equally bright to the one at 140"); + + ok(isPixelEquallyBright(40, 120), + "The tick at 40 should be equally bright to the one at 120"); + + ok(isPixelEquallyBright(0, 80), + "The tick at 80 should be equally bright to the one at 160"); + ok(isPixelEquallyBright(80, 160), + "The tick at 80 should be equally bright to the one at 160"); + + function hasPixelAt(x) { + let i = (x | 0) * 4; + return data[i] && data[i + 1] && data[i + 2] && data[i + 3]; + } + + function isPixelBrighterAtThan(x1, x2) { + let i = (x1 | 0) * 4; + let j = (x2 | 0) * 4; + return data[i + 3] > data [j + 3]; + } + + function isPixelEquallyBright(x1, x2) { + let i = (x1 | 0) * 4; + let j = (x2 | 0) * 4; + return data[i + 3] == data [j + 3]; + } + + teardown(aMonitor).then(finish); + }); + + aDebuggee.location.reload(); + }); +}
--- a/browser/devtools/netmonitor/test/head.js +++ b/browser/devtools/netmonitor/test/head.js @@ -10,24 +10,28 @@ let { gDevTools, devtools } = Cu.import( let TargetFactory = devtools.TargetFactory; let Toolbox = devtools.Toolbox; const EXAMPLE_URL = "http://example.com/browser/browser/devtools/netmonitor/test/"; const SIMPLE_URL = EXAMPLE_URL + "html_simple-test-page.html"; const NAVIGATE_URL = EXAMPLE_URL + "html_navigate-test-page.html"; const CONTENT_TYPE_URL = EXAMPLE_URL + "html_content-type-test-page.html"; +const CYRILLIC_URL = EXAMPLE_URL + "html_cyrillic-test-page.html"; const STATUS_CODES_URL = EXAMPLE_URL + "html_status-codes-test-page.html"; const POST_DATA_URL = EXAMPLE_URL + "html_post-data-test-page.html"; const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html"; const JSON_LONG_URL = EXAMPLE_URL + "html_json-long-test-page.html"; +const SORTING_URL = EXAMPLE_URL + "html_sorting-test-page.html"; +const INFINITE_GET_URL = EXAMPLE_URL + "html_infinite-get-page.html"; const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs"; const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs"; const STATUS_CODES_SJS = EXAMPLE_URL + "sjs_status-codes-test-server.sjs"; +const SORTING_SJS = EXAMPLE_URL + "sjs_sorting-test-server.sjs"; const TEST_IMAGE = EXAMPLE_URL + "test-image.png"; // All tests are asynchronous. waitForExplicitFinish(); // Enable logging for all the relevant tests. let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
new file mode 100644 --- /dev/null +++ b/browser/devtools/netmonitor/test/html_cyrillic-test-page.html @@ -0,0 +1,33 @@ +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Network Monitor test page</title> + </head> + + <body> + <p>Cyrillic type test</p> + + <script type="text/javascript"> + function get(aAddress, aCallback) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", aAddress, true); + + xhr.onreadystatechange = function() { + if (this.readyState == this.DONE) { + aCallback(); + } + }; + xhr.send(null); + } + + function performRequests() { + get("sjs_content-type-test-server.sjs?fmt=txt", function() { + // Done. + }); + } + </script> + </body> + +</html>
new file mode 100644 --- /dev/null +++ b/browser/devtools/netmonitor/test/html_infinite-get-page.html @@ -0,0 +1,36 @@ +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Network Monitor test page</title> + </head> + + <body> + <p>Infinite GETs</p> + + <script type="text/javascript"> + function get(aAddress, aCallback) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", aAddress, true); + + xhr.onreadystatechange = function() { + if (this.readyState == this.DONE) { + aCallback(); + } + }; + xhr.send(null); + } + + // Use a count parameter to defeat caching. + var count = 0; + + function performRequests() { + get("request_" + (count++), function() { + setTimeout(performRequests, 0); + }); + } + </script> + </body> + +</html>
new file mode 100644 --- /dev/null +++ b/browser/devtools/netmonitor/test/html_sorting-test-page.html @@ -0,0 +1,41 @@ +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Network Monitor test page</title> + </head> + + <body> + <p>Sorting test</p> + + <script type="text/javascript"> + function get(aAddress, aIndex, aCallback) { + var xhr = new XMLHttpRequest(); + xhr.open("GET" + aIndex, aAddress + "?index=" + aIndex, true); + + xhr.onreadystatechange = function() { + if (this.readyState == this.DONE) { + aCallback(); + } + }; + xhr.send(null); + } + + function performRequests() { + get("sjs_sorting-test-server.sjs", 1, function() { + get("sjs_sorting-test-server.sjs", 5, function() { + get("sjs_sorting-test-server.sjs", 2, function() { + get("sjs_sorting-test-server.sjs", 4, function() { + get("sjs_sorting-test-server.sjs", 3, function() { + // Done. + }); + }); + }); + }); + }); + } + </script> + </body> + +</html>
--- a/browser/devtools/netmonitor/test/sjs_content-type-test-server.sjs +++ b/browser/devtools/netmonitor/test/sjs_content-type-test-server.sjs @@ -6,16 +6,23 @@ const { classes: Cc, interfaces: Ci } = function handleRequest(request, response) { response.processAsync(); let params = request.queryString.split("&"); let format = params.filter((s) => s.contains("fmt="))[0].split("=")[1]; Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer).initWithCallback(() => { switch (format) { + case "txt": { + response.setStatusLine(request.httpVersion, 200, "DA DA DA"); + response.setHeader("Content-Type", "text/plain", false); + response.write("Братан, ты вообще качаешься?"); + response.finish(); + break; + } case "xml": { response.setStatusLine(request.httpVersion, 200, "OK"); response.setHeader("Content-Type", "text/xml; charset=utf-8", false); response.write("<label value='greeting'>Hello XML!</label>"); response.finish(); break; } case "css": {
new file mode 100644 --- /dev/null +++ b/browser/devtools/netmonitor/test/sjs_sorting-test-server.sjs @@ -0,0 +1,18 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { classes: Cc, interfaces: Ci } = Components; + +function handleRequest(request, response) { + response.processAsync(); + + let params = request.queryString.split("&"); + let index = params.filter((s) => s.contains("index="))[0].split("=")[1]; + + Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer).initWithCallback(() => { + response.setStatusLine(request.httpVersion, index == 1 ? 101 : index * 100, "Meh"); + response.setHeader("Content-Type", "text/" + index, false); + response.write(new Array(index * 10).join(index)); // + 0.01 KB + response.finish(); + }, 50, Ci.nsITimer.TYPE_ONE_SHOT); // Make sure this request takes a few ms. +}
--- a/browser/devtools/profiler/cmd-profiler.jsm +++ b/browser/devtools/profiler/cmd-profiler.jsm @@ -1,16 +1,16 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components; this.EXPORTED_SYMBOLS = []; -Cu.import("resource:///modules/devtools/gcli.jsm"); +Cu.import("resource://gre/modules/devtools/gcli.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/devtools/Require.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "console", "resource://gre/modules/devtools/Console.jsm"); @@ -75,17 +75,17 @@ gcli.addCommand({ let panel = getPanel(context, "jsprofiler"); let profile = panel.getProfileByName(name) || panel.createProfile(name); if (profile.isStarted) { throw gcli.lookup("profilerAlreadyStarted"); } if (profile.isFinished) { - throw gcli.lookup("profilerAlradyFinished"); + throw gcli.lookup("profilerAlreadyFinished"); } panel.switchToProfile(profile, function () profile.start()); return gcli.lookup("profilerStarting2"); } return gDevTools.showToolbox(context.environment.target, "jsprofiler") .then(start);
--- a/browser/devtools/profiler/test/browser_profiler_cmd.js +++ b/browser/devtools/profiler/test/browser_profiler_cmd.js @@ -1,14 +1,14 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>"; -let gcli = Cu.import("resource:///modules/devtools/gcli.jsm", {}).gcli; +let gcli = Cu.import("resource://gre/modules/devtools/gcli.jsm", {}).gcli; let gTarget, gPanel, gOptions; function cmd(typed, expected="") { helpers.audit(gOptions, [{ setup: typed, exec: { output: expected } }]); }
--- a/browser/devtools/responsivedesign/CmdResize.jsm +++ b/browser/devtools/responsivedesign/CmdResize.jsm @@ -6,17 +6,17 @@ const { classes: Cc, interfaces: Ci, uti const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]. getService(Ci.nsIStringBundleService). createBundle("chrome://branding/locale/brand.properties"). GetStringFromName("brandShortName"); this.EXPORTED_SYMBOLS = [ ]; -Cu.import("resource:///modules/devtools/gcli.jsm"); +Cu.import("resource://gre/modules/devtools/gcli.jsm"); /* Responsive Mode commands */ gcli.addCommand({ name: 'resize', description: gcli.lookup('resizeModeDesc') }); gcli.addCommand({
--- a/browser/devtools/responsivedesign/test/helpers.js +++ b/browser/devtools/responsivedesign/test/helpers.js @@ -16,21 +16,20 @@ // }; // Now GCLI will emit output on every keypress that both explains the state // of GCLI and can be run as a test case. this.EXPORTED_SYMBOLS = [ 'helpers' ]; var helpers = {}; this.helpers = helpers; let require = (Cu.import("resource://gre/modules/devtools/Require.jsm", {})).require; -Components.utils.import("resource:///modules/devtools/gcli.jsm", {}); +Components.utils.import("resource://gre/modules/devtools/gcli.jsm", {}); let console = (Cu.import("resource://gre/modules/devtools/Console.jsm", {})).console; -let devtools = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools; -let TargetFactory = devtools.TargetFactory; +let TargetFactory = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools.TargetFactory; let Promise = (Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {})).Promise; let assert = { ok: ok, is: is, log: info }; var util = require('util/util'); var converters = require('gcli/converters'); @@ -153,16 +152,17 @@ helpers.addTabWithToolbar = function(url * their return values resolved */ helpers.runTests = function(options, tests) { var testNames = Object.keys(tests).filter(function(test) { return test != "setup" && test != "shutdown"; }); var recover = function(error) { + ok(false, error); console.error(error); }; info("SETUP"); var setupDone = (tests.setup != null) ? Promise.resolve(tests.setup(options)) : Promise.resolve(); @@ -336,17 +336,17 @@ helpers._createDebugCheck = function(opt output += ' input: \'' + input + '\',\n'; output += ' hints: ' + padding + '\'' + hints + '\',\n'; output += ' markup: \'' + helpers._actual.markup(options) + '\',\n'; output += ' cursor: ' + cursor + ',\n'; output += ' current: \'' + helpers._actual.current(options) + '\',\n'; output += ' status: \'' + helpers._actual.status(options) + '\',\n'; output += ' options: ' + outputArray(helpers._actual.options(options)) + ',\n'; - output += ' error: \'' + helpers._actual.message(options) + '\',\n'; + output += ' message: \'' + helpers._actual.message(options) + '\',\n'; output += ' predictions: ' + outputArray(predictions) + ',\n'; output += ' unassigned: ' + outputArray(requisition._unassigned) + ',\n'; output += ' outputState: \'' + helpers._actual.outputState(options) + '\',\n'; output += ' tooltipState: \'' + helpers._actual.tooltipState(options) + '\'' + (command ? ',' : '') +'\n'; if (command) { output += ' args: {\n'; @@ -373,16 +373,18 @@ helpers._createDebugCheck = function(opt output += ' }\n'; } output += ' },\n'; output += ' exec: {\n'; output += ' output: \'\',\n'; output += ' completed: true,\n'; + output += ' type: \'string\',\n'; + output += ' error: false\n'; output += ' }\n'; output += ' }\n'; output += ']);'; return output; }.bind(this), util.errorHandler); }; @@ -697,43 +699,55 @@ helpers._check = function(options, name, * Helper for helpers.audit() to ensure that all the 'exec' properties work. * See helpers.audit for more information. * @param name The name to use in error messages * @param expected See helpers.audit for a list of available exec checks * @return A promise which resolves to undefined when the checks are complete */ helpers._exec = function(options, name, expected) { if (expected == null) { - return Promise.resolve(); + return Promise.resolve({}); } var output = options.display.requisition.exec({ hidden: true }); if ('completed' in expected) { assert.is(output.completed, expected.completed, 'output.completed false for: ' + name); } if (!options.window.document.createElement) { assert.log('skipping output tests (missing doc.createElement) for ' + name); - return Promise.resolve(); + return Promise.resolve({ output: output }); } if (!('output' in expected)) { - return Promise.resolve(); + return Promise.resolve({ output: output }); } - var deferred = Promise.defer(); - var checkOutput = function() { var div = options.window.document.createElement('div'); - var nodePromise = converters.convert(output.data, output.type, 'dom', - options.display.requisition.context); - nodePromise.then(function(node) { + var conversionContext = options.display.requisition.conversionContext; + + if ('type' in expected) { + assert.is(output.type, + expected.type, + 'output.type for: ' + name); + } + + if ('error' in expected) { + assert.is(output.error, + expected.error, + 'output.error for: ' + name); + } + + var convertPromise = converters.convert(output.data, output.type, 'dom', + conversionContext); + return convertPromise.then(function(node) { div.appendChild(node); var actualOutput = div.textContent.trim(); var doTest = function(match, against) { if (match.test(against)) { assert.ok(true, 'html output for ' + name + ' should match ' + match.source); } else { @@ -752,57 +766,44 @@ helpers._exec = function(options, name, expected.output.forEach(function(match) { doTest(match, actualOutput); }); } else { doTest(expected.output, actualOutput); } - deferred.resolve(actualOutput); + return { output: output, text: actualOutput }; }); }; - if (output.completed !== false) { - checkOutput(); - } - else { - var changed = function() { - if (output.completed !== false) { - checkOutput(); - output.onChange.remove(changed); - } - }; - output.onChange.add(changed); - } - - return deferred.promise; + return output.promise.then(checkOutput, checkOutput); }; /** * Helper to setup the test */ helpers._setup = function(options, name, action) { if (typeof action === 'string') { return helpers.setInput(options, action); } if (typeof action === 'function') { return Promise.resolve(action()); } - return Promise.reject('setup must be a string or a function'); + return Promise.reject('\'setup\' property must be a string or a function. Is ' + action); }; /** * Helper to shutdown the test */ -helpers._post = function(name, action, output) { +helpers._post = function(name, action, data) { if (typeof action === 'function') { - return Promise.resolve(action(output)); + return Promise.resolve(action(data.output, data.text)); } return Promise.resolve(action); }; /* * We do some basic response time stats so we can see if we're getting slow */ var totalResponseTime = 0; @@ -938,39 +939,41 @@ helpers.audit = function(options, audits var start = new Date().getTime(); var setupDone = helpers._setup(options, name, audit.setup); return setupDone.then(function(chunkLen) { if (typeof chunkLen !== 'number') { chunkLen = 1; } - var responseTime = (new Date().getTime() - start) / chunkLen; - totalResponseTime += responseTime; - if (responseTime > maxResponseTime) { - maxResponseTime = responseTime; - maxResponseCulprit = assert.currentTest + '/' + name; + + if (assert.currentTest) { + var responseTime = (new Date().getTime() - start) / chunkLen; + totalResponseTime += responseTime; + if (responseTime > maxResponseTime) { + maxResponseTime = responseTime; + maxResponseCulprit = assert.currentTest + '/' + name; + } + averageOver++; } - averageOver++; var checkDone = helpers._check(options, name, audit.check); return checkDone.then(function() { var execDone = helpers._exec(options, name, audit.exec); - return execDone.then(function(output) { - return helpers._post(name, audit.post, output).then(function() { + return execDone.then(function(data) { + return helpers._post(name, audit.post, data).then(function() { if (assert.testLogging) { log('- END \'' + name + '\' in ' + assert.currentTest); } }); }); }); }); - }).then(null, function(ex) { - console.error(ex.stack); - throw(ex); + }).then(function() { + return options.display.inputter.setInput(''); }); }; /** * Compare 2 arrays. */ helpers._arrayIs = function(actual, expected, message) { assert.ok(Array.isArray(actual), 'actual is not an array: ' + message);
--- a/browser/devtools/scratchpad/CmdScratchpad.jsm +++ b/browser/devtools/scratchpad/CmdScratchpad.jsm @@ -1,15 +1,15 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * 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/. */ this.EXPORTED_SYMBOLS = [ ]; -Components.utils.import("resource:///modules/devtools/gcli.jsm"); +Components.utils.import("resource://gre/modules/devtools/gcli.jsm"); /** * 'scratchpad' command */ gcli.addCommand({ name: "scratchpad", buttonId: "command-button-scratchpad", buttonClass: "command-button",
--- a/browser/devtools/scratchpad/scratchpad.js +++ b/browser/devtools/scratchpad/scratchpad.js @@ -16,17 +16,16 @@ const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/NetUtil.jsm"); -Cu.import("resource:///modules/PropertyPanel.jsm"); Cu.import("resource:///modules/source-editor.jsm"); Cu.import("resource:///modules/devtools/LayoutHelpers.jsm"); Cu.import("resource:///modules/devtools/scratchpad-manager.jsm"); Cu.import("resource://gre/modules/jsdebugger.jsm"); Cu.import("resource:///modules/devtools/gDevTools.jsm"); Cu.import("resource://gre/modules/osfile.jsm"); Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js"); @@ -74,17 +73,17 @@ var Scratchpad = { return obj; } aLine = aLine .replace(/^\/\//, "") .replace(/^\/\*/, "") .replace(/\*\/$/, ""); - aLine.split(",").forEach(function (pair) { + aLine.split(",").forEach(pair => { let [key, val] = pair.split(":"); if (key && val) { obj[key.trim()] = val.trim(); } }); return obj; @@ -616,28 +615,28 @@ var Scratchpad = { !window.confirm(this.strings. GetStringFromName("export.fileOverwriteConfirmation"))) { return; } let encoder = new TextEncoder(); let buffer = encoder.encode(this.getText()); let promise = OS.File.writeAtomic(aFile.path, buffer,{tmpPath: aFile.path + ".tmp"}); - promise.then(function success(value) { + promise.then(value => { if (aCallback) { aCallback.call(this, Components.results.NS_OK); } - }.bind(this), function failure(reason) { + }, reason => { if (!aSilentError) { window.alert(this.strings.GetStringFromName("saveFile.failed")); } if (aCallback) { aCallback.call(this, Components.results.NS_ERROR_UNEXPECTED); } - }.bind(this)); + }); }, /** * Read the content of a file and put it into the textbox. * * @param nsILocalFile aFile * The file you want to save the textbox content into. @@ -651,60 +650,59 @@ var Scratchpad = { * 2) the data that was read from the file, if any. */ importFromFile: function SP_importFromFile(aFile, aSilentError, aCallback) { // Prevent file type detection. let channel = NetUtil.newChannel(aFile); channel.contentType = "application/javascript"; - let self = this; - NetUtil.asyncFetch(channel, function(aInputStream, aStatus) { + NetUtil.asyncFetch(channel, (aInputStream, aStatus) => { let content = null; if (Components.isSuccessCode(aStatus)) { let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. createInstance(Ci.nsIScriptableUnicodeConverter); converter.charset = "UTF-8"; content = NetUtil.readInputStreamToString(aInputStream, aInputStream.available()); content = converter.ConvertToUnicode(content); // Check to see if the first line is a mode-line comment. let line = content.split("\n")[0]; - let modeline = self._scanModeLine(line); + let modeline = this._scanModeLine(line); let chrome = Services.prefs.getBoolPref(DEVTOOLS_CHROME_ENABLED); if (chrome && modeline["-sp-context"] === "browser") { - self.setBrowserContext(); + this.setBrowserContext(); } - self.setText(content); - self.editor.resetUndo(); + this.setText(content); + this.editor.resetUndo(); } else if (!aSilentError) { - window.alert(self.strings.GetStringFromName("openFile.failed")); + window.alert(this.strings.GetStringFromName("openFile.failed")); } if (aCallback) { - aCallback.call(self, aStatus, content); + aCallback.call(this, aStatus, content); } }); }, /** * Open a file to edit in the Scratchpad. * * @param integer aIndex * Optional integer: clicked menuitem in the 'Open Recent'-menu. */ openFile: function SP_openFile(aIndex) { - let promptCallback = function(aFile) { - this.promptSave(function(aCloseFile, aSaved, aStatus) { + let promptCallback = aFile => { + this.promptSave((aCloseFile, aSaved, aStatus) => { let shouldOpen = aCloseFile; if (aSaved && !Components.isSuccessCode(aStatus)) { shouldOpen = false; } if (shouldOpen) { let file; if (aFile) { @@ -727,33 +725,31 @@ var Scratchpad = { this.clearFiles(aIndex, 1); return; } this.setFilename(file.path); this.importFromFile(file, false); this.setRecentFile(file); } - }.bind(this)); - }.bind(this); + }); + }; if (aIndex > -1) { promptCallback(); } else { let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); - let fpCallback = function fpCallback_done(aResult) { + fp.init(window, this.strings.GetStringFromName("openFile.title"), + Ci.nsIFilePicker.modeOpen); + fp.defaultString = ""; + fp.open(aResult => { if (aResult != Ci.nsIFilePicker.returnCancel) { promptCallback(fp.file); } - }; - - fp.init(window, this.strings.GetStringFromName("openFile.title"), - Ci.nsIFilePicker.modeOpen); - fp.defaultString = ""; - fp.open(fpCallback); + }); } }, /** * Get recent files. * * @return Array * File paths. @@ -948,17 +944,17 @@ var Scratchpad = { { if (!this.filename) { return this.saveFileAs(aCallback); } let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); file.initWithPath(this.filename); - this.exportToFile(file, true, false, function(aStatus) { + this.exportToFile(file, true, false, aStatus => { if (Components.isSuccessCode(aStatus)) { this.editor.dirty = false; this.setRecentFile(file); } if (aCallback) { aCallback(aStatus); } }); @@ -968,30 +964,30 @@ var Scratchpad = { * Save the textbox content to a new file. * * @param function aCallback * Optional function you want to call when file is saved */ saveFileAs: function SP_saveFileAs(aCallback) { let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); - let fpCallback = function fpCallback_done(aResult) { + let fpCallback = aResult => { if (aResult != Ci.nsIFilePicker.returnCancel) { this.setFilename(fp.file.path); - this.exportToFile(fp.file, true, false, function(aStatus) { + this.exportToFile(fp.file, true, false, aStatus => { if (Components.isSuccessCode(aStatus)) { this.editor.dirty = false; this.setRecentFile(fp.file); } if (aCallback) { aCallback(aStatus); } }); } - }.bind(this); + }; fp.init(window, this.strings.GetStringFromName("saveFileAs"), Ci.nsIFilePicker.modeSave); fp.defaultString = "scratchpad.js"; fp.open(fpCallback); }, /** @@ -1004,17 +1000,17 @@ var Scratchpad = { { let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); file.initWithPath(this.filename); if (!file.exists()) { return; } - this.importFromFile(file, false, function(aStatus, aContent) { + this.importFromFile(file, false, (aStatus, aContent) => { if (aCallback) { aCallback(aStatus); } }); }, /** * Prompt to revert scratchpad if it has unsaved changes. @@ -1040,18 +1036,18 @@ var Scratchpad = { if (button == BUTTON_POSITION_CANCEL) { if (aCallback) { aCallback(false); } return; } if (button == BUTTON_POSITION_REVERT) { - this.revertFile(function(aStatus) { - if(aCallback){ + this.revertFile(aStatus => { + if (aCallback) { aCallback(true, aStatus); } }); return; } } if (aCallback) { @@ -1343,17 +1339,17 @@ var Scratchpad = { if (button == BUTTON_POSITION_CANCEL) { if (aCallback) { aCallback(false, false); } return false; } if (button == BUTTON_POSITION_SAVE) { - this.saveFile(function(aStatus) { + this.saveFile(aStatus => { if (aCallback) { aCallback(true, true, aStatus); } }); return true; } } @@ -1382,29 +1378,29 @@ var Scratchpad = { * Close the scratchpad window. Prompts before closing if the scratchpad * has unsaved changes. * * @param function aCallback * Optional function you want to call when file is saved */ close: function SP_close(aCallback) { - this.promptSave(function(aShouldClose, aSaved, aStatus) { + this.promptSave((aShouldClose, aSaved, aStatus) => { let shouldClose = aShouldClose; if (aSaved && !Components.isSuccessCode(aStatus)) { shouldClose = false; } if (shouldClose) { window.close(); } if (aCallback) { aCallback(); } - }.bind(this)); + }); }, _observers: [], /** * Add an observer for Scratchpad events. * * The observer implements IScratchpadObserver := {
--- a/browser/devtools/scratchpad/scratchpad.xul +++ b/browser/devtools/scratchpad/scratchpad.xul @@ -48,16 +48,17 @@ <command id="sp-cmd-display" oncommand="Scratchpad.display();"/> <command id="sp-cmd-contentContext" oncommand="Scratchpad.setContentContext();"/> <command id="sp-cmd-browserContext" oncommand="Scratchpad.setBrowserContext();" disabled="true"/> <command id="sp-cmd-reloadAndRun" oncommand="Scratchpad.reloadAndRun();"/> <command id="sp-cmd-resetContext" oncommand="Scratchpad.resetContext();"/> <command id="sp-cmd-errorConsole" oncommand="Scratchpad.openErrorConsole();" disabled="true"/> <command id="sp-cmd-webConsole" oncommand="Scratchpad.openWebConsole();"/> <command id="sp-cmd-documentationLink" oncommand="Scratchpad.openDocumentationPage();"/> + <command id="sp-cmd-hideSidebar" oncommand="Scratchpad.sidebar.hide();"/> </commandset> <keyset id="sourceEditorKeys"/> <keyset id="sp-keyset"> <key id="sp-key-window" key="&newWindowCmd.commandkey;" command="sp-cmd-newWindow" @@ -97,16 +98,19 @@ <key id="sp-key-reloadAndRun" key="&reloadAndRun.key;" command="sp-cmd-reloadAndRun" modifiers="accel,shift"/> <key id="sp-key-errorConsole" key="&errorConsoleCmd.commandkey;" command="sp-cmd-errorConsole" modifiers="accel,shift"/> + <key id="sp-key-hideSidebar" + keycode="VK_ESCAPE" + command="sp-cmd-hideSidebar"/> <key id="key_openHelp" keycode="VK_F1" command="sp-cmd-documentationLink"/> </keyset> <menubar id="sp-menubar"> <menu id="sp-file-menu" label="&fileMenu.label;"
--- a/browser/devtools/shared/AppCacheUtils.jsm +++ b/browser/devtools/shared/AppCacheUtils.jsm @@ -236,16 +236,20 @@ AppCacheUtils.prototype = { }); } } }, null); return deferred.promise; }, listEntries: function ACU_show(searchTerm) { + if (!Services.prefs.getBoolPref("browser.cache.disk.enable")) { + throw new Error(l10n.GetStringFromName("cacheDisabled")); + } + let entries = []; Services.cache.visitEntries({ visitDevice: function(deviceID, deviceInfo) { return true; }, visitEntry: function(deviceID, entryInfo) { @@ -270,16 +274,19 @@ AppCacheUtils.prototype = { entry[key] = value; } entries.push(entry); } return true; } }); + if (entries.length == 0) { + throw new Error(l10n.GetStringFromName("noResults")); + } return entries; }, viewEntry: function ACU_viewEntry(key) { let uri; Services.cache.visitEntries({ visitDevice: function(deviceID, deviceInfo) { @@ -331,17 +338,17 @@ AppCacheUtils.prototype = { let html = uriInfo.text; let parser = _DOMParser; this.doc = parser.parseFromString(html, "text/html"); let uri = getURI(this.doc); deferred.resolve(uri); } else { this.errors.push({ line: 0, - msg: "The URI passed to AppCacheUtils is invalid." + msg: l10n.GetStringFromName("invalidURI") }); } }); } return deferred.promise; }, _addError: function ACU__addError(line, l10nString, ...params) {
deleted file mode 100644 --- a/browser/devtools/shared/Browser.jsm +++ /dev/null @@ -1,28 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * 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/. */ - -"use strict"; - -/** - * Define various constants to match the globals provided by the browser. - * This module helps cases where code is shared between the web and Firefox. - * See also Console.jsm for an implementation of the Firefox console that - * forwards to dump(); - */ - -this.EXPORTED_SYMBOLS = [ "Node", "HTMLElement", "setTimeout", "clearTimeout" ]; - -/** - * Expose Node/HTMLElement objects. This allows us to use the Node constants - * without resorting to hardcoded numbers - */ -this.Node = Components.interfaces.nsIDOMNode; -this.HTMLElement = Components.interfaces.nsIDOMHTMLElement; - -/* - * Import and re-export the timeout functions from Timer.jsm. - */ -let Timer = Components.utils.import("resource://gre/modules/Timer.jsm", {}); -this.setTimeout = Timer.setTimeout; -this.clearTimeout = Timer.clearTimeout;
--- a/browser/devtools/shared/DeveloperToolbar.jsm +++ b/browser/devtools/shared/DeveloperToolbar.jsm @@ -14,17 +14,17 @@ Components.utils.import("resource://gre/ Components.utils.import("resource:///modules/devtools/Commands.jsm"); const Node = Components.interfaces.nsIDOMNode; XPCOMUtils.defineLazyModuleGetter(this, "console", "resource://gre/modules/devtools/Console.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "gcli", - "resource:///modules/devtools/gcli.jsm"); + "resource://gre/modules/devtools/gcli.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "CmdCommands", "resource:///modules/devtools/BuiltinCommands.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PageErrorListener", "resource://gre/modules/devtools/WebConsoleUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", @@ -148,17 +148,17 @@ let CommandUtils = { /** * A helper function to create the environment object that is passed to * GCLI commands. */ createEnvironment: function(chromeDocument, contentDocument) { let environment = { chromeDocument: chromeDocument, - contentDocument: contentDocument, // Use of contentDocument is deprecated + chromeWindow: chromeDocument.defaultView, document: contentDocument, window: contentDocument.defaultView }; Object.defineProperty(environment, "target", { get: function() { let tab = chromeDocument.defaultView.getBrowser().selectedTab; @@ -176,19 +176,23 @@ this.CommandUtils = CommandUtils; /** * Due to a number of panel bugs we need a way to check if we are running on * Linux. See the comments for TooltipPanel and OutputPanel for further details. * * When bug 780102 is fixed all isLinux checks can be removed and we can revert * to using panels. */ XPCOMUtils.defineLazyGetter(this, "isLinux", function () { + return OS == "Linux"; +}); + +XPCOMUtils.defineLazyGetter(this, "OS", function () { let os = Components.classes["@mozilla.org/xre/app-info;1"] .getService(Components.interfaces.nsIXULRuntime).OS; - return os == "Linux"; + return os; }); /** * A component to manage the global developer toolbar, which contains a GCLI * and buttons for various developer tools. * @param aChromeWindow The browser window to which this toolbar is attached * @param aToolbarElement See browser.xul:<toolbar id="developer-toolbar"> */ @@ -756,16 +760,17 @@ function OutputPanel(aDevToolbar, aLoadC this._frame.id = "gcli-output-frame"; this._frame.setAttribute("src", "chrome://browser/content/devtools/commandlineoutput.xhtml"); this._frame.setAttribute("sandbox", "allow-same-origin"); this._panel.appendChild(this._frame); this.displayedOutput = undefined; this._onload = this._onload.bind(this); + this._update = this._update.bind(this); this._frame.addEventListener("load", this._onload, true); this.loaded = false; } /** * Wire up the element from the iframe, and inform the _loadCallback. */ @@ -787,43 +792,16 @@ OutputPanel.prototype._onload = function this.loaded = true; if (this._loadCallback) { this._loadCallback(); delete this._loadCallback; } }; /** - * Determine the scrollbar width in the current document. - * - * @private - */ -Object.defineProperty(OutputPanel.prototype, 'scrollbarWidth', { - get: function() { - if (this.__scrollbarWidth) { - return this.__scrollbarWidth; - } - - let hbox = this.document.createElementNS(XUL_NS, "hbox"); - hbox.setAttribute("style", "height: 0%; overflow: hidden"); - - let scrollbar = this.document.createElementNS(XUL_NS, "scrollbar"); - scrollbar.setAttribute("orient", "vertical"); - hbox.appendChild(scrollbar); - - this.document.documentElement.appendChild(hbox); - this.__scrollbarWidth = scrollbar.clientWidth; - this.document.documentElement.removeChild(hbox); - - return this.__scrollbarWidth; - }, - enumerable: true -}); - -/** * Prevent the popup from hiding if it is not permitted via this.canHide. */ OutputPanel.prototype._onpopuphiding = function OP_onpopuphiding(aEvent) { // TODO: When we switch back from tooltip to panel we can remove this hack: // https://bugzilla.mozilla.org/show_bug.cgi?id=780102 if (isLinux && !this.canHide) { aEvent.preventDefault(); @@ -858,23 +836,42 @@ OutputPanel.prototype._resize = function { if (this._panel == null || this.document == null || !this._panel.state == "closed") { return } // Set max panel width to match any content with a max of the width of the // browser window. let maxWidth = this._panel.ownerDocument.documentElement.clientWidth; - let width = Math.min(maxWidth, this.document.documentElement.scrollWidth); - // Add scrollbar width to content size in case a scrollbar is needed. - width += this.scrollbarWidth; + // Adjust max width according to OS. + // We'd like to put this in CSS but we can't: + // body { width: calc(min(-5px, max-content)); } + // #_panel { max-width: -5px; } + switch(OS) { + case "Linux": + maxWidth -= 5; + break; + case "Darwin": + maxWidth -= 25; + break; + case "WINNT": + maxWidth -= 5; + break; + } + + this.document.body.style.width = "-moz-max-content"; + let style = this._frame.contentWindow.getComputedStyle(this.document.body); + let frameWidth = parseInt(style.width, 10); + let width = Math.min(maxWidth, frameWidth); + this.document.body.style.width = width + "px"; // Set the width of the iframe. this._frame.style.minWidth = width + "px"; + this._panel.style.maxWidth = maxWidth + "px"; // browserAdjustment is used to correct the panel height according to the // browsers borders etc. const browserAdjustment = 15; // Set max panel height to match any content with a max of the height of the // browser window. let maxHeight = @@ -901,38 +898,48 @@ OutputPanel.prototype._outputChanged = f { if (aEvent.output.hidden) { return; } this.remove(); this.displayedOutput = aEvent.output; - this.update(); + this.displayedOutput.onClose.add(this.remove, this); - this.displayedOutput.onChange.add(this.update, this); - this.displayedOutput.onClose.add(this.remove, this); + if (this.displayedOutput.completed) { + this._update(); + } + else { + this.displayedOutput.promise.then(this._update, this._update) + .then(null, console.error); + } }; /** * Called when displayed Output says it's changed or from outputChanged, which * happens when there is a new displayed Output. */ -OutputPanel.prototype.update = function OP_update() +OutputPanel.prototype._update = function OP_update() { + // destroy has been called, bail out + if (this._div == null) { + return; + } + // Empty this._div while (this._div.hasChildNodes()) { this._div.removeChild(this._div.firstChild); } if (this.displayedOutput.data != null) { let requisition = this._devtoolbar.display.requisition; let nodePromise = converters.convert(this.displayedOutput.data, this.displayedOutput.type, 'dom', - requisition.context); + requisition.conversionContext); nodePromise.then(function(node) { while (this._div.hasChildNodes()) { this._div.removeChild(this._div.firstChild); } var links = node.ownerDocument.querySelectorAll('*[href]'); for (var i = 0; i < links.length; i++) { links[i].setAttribute('target', '_blank'); @@ -953,17 +960,16 @@ OutputPanel.prototype.remove = function this.canHide = true; } if (this._panel && this._panel.hidePopup) { this._panel.hidePopup(); } if (this.displayedOutput) { - this.displayedOutput.onChange.remove(this.update, this); this.displayedOutput.onClose.remove(this.remove, this); delete this.displayedOutput; } }; /** * Detach listeners from the currently displayed Output. */
--- a/browser/devtools/shared/test/Makefile.in +++ b/browser/devtools/shared/test/Makefile.in @@ -9,17 +9,16 @@ srcdir = @srcdir@ VPATH = @srcdir@ relativesrcdir = @relativesrcdir@ include $(DEPTH)/config/autoconf.mk DISABLED_XPCSHELL_TESTS = unit MOCHITEST_BROWSER_FILES = \ - browser_browser_basic.js \ browser_require_basic.js \ browser_templater_basic.js \ browser_toolbar_basic.js \ browser_toolbar_tooltip.js \ browser_toolbar_webconsole_errors_count.js \ browser_layoutHelpers.js \ browser_eventemitter_basic.js \ head.js \
deleted file mode 100644 --- a/browser/devtools/shared/test/browser_browser_basic.js +++ /dev/null @@ -1,28 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -// Tests exports from Browser.jsm - -const TEST_URI = "data:text/html;charset=utf-8,<p id=id>Text</p>"; - -let imported = {}; -Components.utils.import("resource:///modules/devtools/Browser.jsm", imported); - -registerCleanupFunction(function tearDown() { - imported = undefined; -}); - -function test() { - addTab(TEST_URI, function(browser, tab, document) { - runTest(browser, tab, document); - }); -} - -function runTest(browser, tab, document) { - var p = document.getElementById("id"); - - ok(p instanceof imported.Node, "Node correctly defined"); - ok(p instanceof imported.HTMLElement, "HTMLElement correctly defined"); - - finish(); -}
--- a/browser/devtools/shared/test/browser_templater_basic.js +++ b/browser/devtools/shared/test/browser_templater_basic.js @@ -5,17 +5,17 @@ /* * These tests run both in Mozilla/Mochitest and plain browsers (as does * domtemplate) * We should endevour to keep the source in sync. */ var Promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise; -var template = Cu.import("resource:///modules/devtools/Templater.jsm", {}).template; +var template = Cu.import("resource://gre/modules/devtools/Templater.jsm", {}).template; const TEST_URI = "http://example.com/browser/browser/devtools/shared/test/browser_templater_basic.html"; function test() { addTab(TEST_URI, function() { info("Starting DOM Templater Tests"); runTest(0); });
--- a/browser/devtools/shared/widgets/BreadcrumbsWidget.jsm +++ b/browser/devtools/shared/widgets/BreadcrumbsWidget.jsm @@ -30,16 +30,18 @@ this.EXPORTED_SYMBOLS = ["BreadcrumbsWid * myMethod: function() {}, * ... * }); * * @param nsIDOMNode aNode * The element associated with the widget. */ this.BreadcrumbsWidget = function BreadcrumbsWidget(aNode) { + this.document = aNode.ownerDocument; + this.window = this.document.defaultView; this._parent = aNode; // Create an internal arrowscrollbox container. this._list = this.document.createElement("arrowscrollbox"); this._list.className = "breadcrumbs-widget-container"; this._list.setAttribute("flex", "1"); this._list.setAttribute("orient", "horizontal"); this._list.setAttribute("clicktoscroll", "true") @@ -54,19 +56,16 @@ this.BreadcrumbsWidget = function Breadc // Delegate some of the associated node's methods to satisfy the interface // required by MenuContainer instances. ViewHelpers.delegateWidgetAttributeMethods(this, aNode); ViewHelpers.delegateWidgetEventMethods(this, aNode); }; BreadcrumbsWidget.prototype = { - get document() this._parent.ownerDocument, - get window() this.document.defaultView, - /** * Inserts an item in this container at the specified index. * * @param number aIndex * The position in the container intended for this item. * @param string | nsIDOMNode aContents * The string or node displayed in the container. * @return nsIDOMNode @@ -103,23 +102,22 @@ BreadcrumbsWidget.prototype = { this._selectedItem = null; } }, /** * Removes all of the child nodes from this container. */ removeAllItems: function BCW_removeAllItems() { - let parent = this._parent; let list = this._list; - let firstChild; - while (firstChild = list.firstChild) { - list.removeChild(firstChild); + while (list.hasChildNodes()) { + list.firstChild.remove(); } + this._selectedItem = null; }, /** * Gets the currently selected child node in this container. * @return nsIDOMNode */ get selectedItem() this._selectedItem, @@ -141,21 +139,21 @@ BreadcrumbsWidget.prototype = { } else { node.removeAttribute("checked"); } } // Repeated calls to ensureElementIsVisible would interfere with each other // and may sometimes result in incorrect scroll positions. this.window.clearTimeout(this._ensureVisibleTimeout); - this._ensureVisibleTimeout = this.window.setTimeout(function() { + this._ensureVisibleTimeout = this.window.setTimeout(() => { if (this._selectedItem) { this._list.ensureElementIsVisible(this._selectedItem); } - }.bind(this), ENSURE_SELECTION_VISIBLE_DELAY); + }, ENSURE_SELECTION_VISIBLE_DELAY); }, /** * The underflow and overflow listener for the arrowscrollbox container. */ _onUnderflow: function BCW__onUnderflow({target}) { if (target != this._list) { return; @@ -172,43 +170,44 @@ BreadcrumbsWidget.prototype = { if (target != this._list) { return; } target._scrollButtonUp.collapsed = false; target._scrollButtonDown.collapsed = false; target.setAttribute("overflows", ""); }, + window: null, + document: null, _parent: null, _list: null, _selectedItem: null, _ensureVisibleTimeout: null }; /** * A Breadcrumb constructor for the BreadcrumbsWidget. * * @param BreadcrumbsWidget aWidget * The widget to contain this breadcrumb. * @param string | nsIDOMNode aContents * The string or node displayed in the container. */ function Breadcrumb(aWidget, aContents) { + this.document = aWidget.document; + this.window = aWidget.window; this.ownerView = aWidget; this._target = this.document.createElement("hbox"); this._target.className = "breadcrumbs-widget-item"; this._target.setAttribute("align", "center"); this.contents = aContents; } Breadcrumb.prototype = { - get document() this.ownerView.document, - get window() this.document.defaultView, - /** * Sets the contents displayed in this item's view. * * @param string | nsIDOMNode aContents * The string or node displayed in the container. */ set contents(aContents) { // If this item's view contents are a string, then create a label to hold @@ -223,11 +222,13 @@ Breadcrumb.prototype = { if (this._target.hasChildNodes()) { this._target.replaceChild(aContents, this._target.firstChild); return; } // These are the first contents ever displayed. this._target.appendChild(aContents); }, + window: null, + document: null, ownerView: null, _target: null };
--- a/browser/devtools/shared/widgets/SideMenuWidget.jsm +++ b/browser/devtools/shared/widgets/SideMenuWidget.jsm @@ -32,16 +32,18 @@ this.EXPORTED_SYMBOLS = ["SideMenuWidget * }); * * @param nsIDOMNode aNode * The element associated with the widget. * @param boolean aShowArrows * Specifies if items in this container should display horizontal arrows. */ this.SideMenuWidget = function SideMenuWidget(aNode, aShowArrows = true) { + this.document = aNode.ownerDocument; + this.window = this.document.defaultView; this._parent = aNode; this._showArrows = aShowArrows; // Create an internal scrollbox container. this._list = this.document.createElement("scrollbox"); this._list.className = "side-menu-widget-container"; this._list.setAttribute("flex", "1"); this._list.setAttribute("orient", "vertical"); @@ -55,29 +57,34 @@ this.SideMenuWidget = function SideMenuW this._orderedMenuElementsArray = []; // Delegate some of the associated node's methods to satisfy the interface // required by MenuContainer instances. ViewHelpers.delegateWidgetEventMethods(this, aNode); }; SideMenuWidget.prototype = { - get document() this._parent.ownerDocument, - get window() this.document.defaultView, + /** + * Specifies if groups in this container should be sorted alphabetically. + */ + sortedGroups: true, /** * Specifies if this container should try to keep the selected item visible. * (For example, when new items are added the selection is brought into view). */ maintainSelectionVisible: true, /** - * Specifies if groups in this container should be sorted alphabetically. + * Specifies that the container viewport should be "stuck" to the + * bottom. That is, the container is automatically scrolled down to + * keep appended items visible, but only when the scroll position is + * already at the bottom. */ - sortedGroups: true, + autoscrollWithAppendedItems: false, /** * Inserts an item in this container at the specified index, optionally * grouping by name. * * @param number aIndex * The position in the container intended for this item. * @param string | nsIDOMNode aContents @@ -85,22 +92,36 @@ SideMenuWidget.prototype = { * @param string aTooltip [optional] * A tooltip attribute for the displayed item. * @param string aGroup [optional] * The group to place the displayed item into. * @return nsIDOMNode * The element associated with the displayed item. */ insertItemAt: function SMW_insertItemAt(aIndex, aContents, aTooltip = "", aGroup = "") { + // Invalidate any notices set on this widget. + this.removeAttribute("notice"); + + let maintainScrollAtBottom = + this.autoscrollWithAppendedItems && + (aIndex < 0 || aIndex >= this._orderedMenuElementsArray.length) && + (this._list.scrollTop + this._list.clientHeight >= this._list.scrollHeight); + + let group = this._getGroupForName(aGroup); + let item = this._getItemForGroup(group, aContents, aTooltip); + let element = item.insertSelfAt(aIndex); + if (this.maintainSelectionVisible) { - this.ensureSelectionIsVisible(true, true); // Don't worry, it's delayed. + this.ensureSelectionIsVisible({ withGroup: true, delayed: true }); + } + if (maintainScrollAtBottom) { + this._list.scrollTop = this._list.scrollHeight; } - let group = this._getGroupForName(aGroup); - return group.insertItemAt(aIndex, aContents, aTooltip, this._showArrows); + return element; }, /** * Returns the child node in this container situated at the specified index. * * @param number aIndex * The position in the container intended for this item. * @return nsIDOMNode @@ -112,38 +133,43 @@ SideMenuWidget.prototype = { /** * Removes the specified child node from this container. * * @param nsIDOMNode aChild * The element associated with the displayed item. */ removeChild: function SMW_removeChild(aChild) { - // Remove the item itself, not the contents. - let item = aChild.parentNode; - item.parentNode.removeChild(item); + if (aChild.className == "side-menu-widget-item-contents") { + // Remove the item itself, not the contents. + aChild.parentNode.remove(); + } else { + // Groups with no title don't have any special internal structure. + aChild.remove(); + } + this._orderedMenuElementsArray.splice( this._orderedMenuElementsArray.indexOf(aChild), 1); if (this._selectedItem == aChild) { this._selectedItem = null; } }, /** * Removes all of the child nodes from this container. */ removeAllItems: function SMW_removeAllItems() { let parent = this._parent; let list = this._list; - let firstChild; - while (firstChild = list.firstChild) { - list.removeChild(firstChild); + while (list.hasChildNodes()) { + list.firstChild.remove(); } + this._selectedItem = null; this._groupsByName.clear(); this._orderedGroupElementsArray.length = 0; this._orderedMenuElementsArray.length = 0; }, /** @@ -152,72 +178,75 @@ SideMenuWidget.prototype = { */ get selectedItem() this._selectedItem, /** * Sets the currently selected child node in this container. * @param nsIDOMNode aChild */ set selectedItem(aChild) { - let menuElementsArray = this._orderedMenuElementsArray; + let menuArray = this._orderedMenuElementsArray; if (!aChild) { this._selectedItem = null; } - for (let node of menuElementsArray) { + for (let node of menuArray) { if (node == aChild) { node.classList.add("selected"); node.parentNode.classList.add("selected"); this._selectedItem = node; } else { node.classList.remove("selected"); node.parentNode.classList.remove("selected"); } } + // Repeated calls to ensureElementIsVisible would interfere with each other // and may sometimes result in incorrect scroll positions. - this.ensureSelectionIsVisible(false, true); + this.ensureSelectionIsVisible({ delayed: true }); }, /** * Ensures the selected element is visible. * @see SideMenuWidget.prototype.ensureElementIsVisible. */ - ensureSelectionIsVisible: - function SMW_ensureSelectionIsVisible(aGroupFlag, aDelayedFlag) { - this.ensureElementIsVisible(this.selectedItem, aGroupFlag, aDelayedFlag); + ensureSelectionIsVisible: function SMW_ensureSelectionIsVisible(aFlags) { + this.ensureElementIsVisible(this.selectedItem, aFlags); }, /** * Ensures the specified element is visible. * * @param nsIDOMNode aElement * The element to make visible. - * @param boolean aGroupFlag - * True if the group header should also be made visible, if possible. - * @param boolean aDelayedFlag - * True to wait a few cycles before ensuring the selection is visible. + * @param object aFlags [optional] + * An object containing some of the following flags: + * - withGroup: true if the group header should also be made visible, if possible + * - delayed: wait a few cycles before ensuring the selection is visible */ - ensureElementIsVisible: - function SMW_ensureElementIsVisible(aElement, aGroupFlag, aDelayedFlag) { + ensureElementIsVisible: function SMW_ensureElementIsVisible(aElement, aFlags = {}) { if (!aElement) { return; } - if (aDelayedFlag) { + + if (aFlags.delayed) { + delete aFlags.delayed; this.window.clearTimeout(this._ensureVisibleTimeout); - this._ensureVisibleTimeout = this.window.setTimeout(function() { - this.ensureElementIsVisible(aElement, aGroupFlag, false); - }.bind(this), ENSURE_SELECTION_VISIBLE_DELAY); + this._ensureVisibleTimeout = this.window.setTimeout(() => { + this.ensureElementIsVisible(aElement, aFlags); + }, ENSURE_SELECTION_VISIBLE_DELAY); return; } - if (aGroupFlag) { + + if (aFlags.withGroup) { let groupList = aElement.parentNode; let groupContainer = groupList.parentNode; groupContainer.scrollIntoView(true); // Align with the top. } + this._boxObject.ensureElementIsVisible(aElement); }, /** * Shows all the groups, even the ones with no visible children. */ showEmptyGroups: function SMW_showEmptyGroups() { for (let group of this._orderedGroupElementsArray) { @@ -344,16 +373,34 @@ SideMenuWidget.prototype = { } let group = new SideMenuGroup(this, aName); this._groupsByName.set(aName, group); group.insertSelfAt(this.sortedGroups ? group.findExpectedIndexForSelf() : -1); return group; }, + /** + * Gets a menu item to be displayed inside a group. + * @see SideMenuWidget.prototype._getGroupForName + * + * @param SideMenuGroup aGroup + * The group to contain the menu item. + * @param string | nsIDOMNode aContents + * The string or node displayed in the container. + * @param string aTooltip [optional] + * A tooltip attribute for the displayed item. + */ + _getItemForGroup: function SMW__getItemForGroup(aGroup, aContents, aTooltip) { + return new SideMenuItem(aGroup, aContents, aTooltip, this._showArrows); + }, + + window: null, + document: null, + _showArrows: false, _parent: null, _list: null, _boxObject: null, _selectedItem: null, _groupsByName: null, _orderedGroupElementsArray: null, _orderedMenuElementsArray: null, _ensureVisibleTimeout: null, @@ -367,90 +414,64 @@ SideMenuWidget.prototype = { * Represents a group which should contain SideMenuItems. * * @param SideMenuWidget aWidget * The widget to contain this menu item. * @param string aName * The string displayed in the container. */ function SideMenuGroup(aWidget, aName) { + this.document = aWidget.document; + this.window = aWidget.window; this.ownerView = aWidget; this.identifier = aName; - let document = this.document; - let title = this._title = document.createElement("hbox"); - title.className = "side-menu-widget-group-title"; + // Create an internal title and list container. + if (aName) { + let target = this._target = this.document.createElement("vbox"); + target.className = "side-menu-widget-group"; + target.setAttribute("name", aName); + target.setAttribute("tooltiptext", aName); - let name = this._name = document.createElement("label"); - name.className = "plain name"; - name.setAttribute("value", aName); - name.setAttribute("crop", "end"); - name.setAttribute("flex", "1"); + let list = this._list = this.document.createElement("vbox"); + list.className = "side-menu-widget-group-list"; + + let title = this._title = this.document.createElement("hbox"); + title.className = "side-menu-widget-group-title"; - let list = this._list = document.createElement("vbox"); - list.className = "side-menu-widget-group-list"; + let name = this._name = this.document.createElement("label"); + name.className = "plain name"; + name.setAttribute("value", aName); + name.setAttribute("crop", "end"); + name.setAttribute("flex", "1"); - let target = this._target = document.createElement("vbox"); - target.className = "side-menu-widget-group side-menu-widget-item-or-group"; - target.setAttribute("name", aName); - target.setAttribute("tooltiptext", aName); - - title.appendChild(name); - target.appendChild(title); - target.appendChild(list); + title.appendChild(name); + target.appendChild(title); + target.appendChild(list); + } + // Skip a few redundant nodes when no title is shown. + else { + let target = this._target = this._list = this.document.createElement("vbox"); + target.className = "side-menu-widget-group side-menu-widget-group-list"; + } } SideMenuGroup.prototype = { - get document() this.ownerView.document, - get window() this.document.defaultView, - get _groupElementsArray() this.ownerView._orderedGroupElementsArray, - get _menuElementsArray() this.ownerView._orderedMenuElementsArray, - - /** - * Inserts an item in this group at the specified index. - * - * @param number aIndex - * The position in the container intended for this item. - * @param string | nsIDOMNode aContents - * The string or node displayed in the container. - * @param string aTooltip [optional] - * A tooltip attribute for the displayed item. - * @param boolean aArrowFlag - * True if a horizontal arrow should be shown. - * @return nsIDOMNode - * The element associated with the displayed item. - */ - insertItemAt: function SMG_insertItemAt(aIndex, aContents, aTooltip, aArrowFlag) { - let list = this._list; - let menuArray = this._menuElementsArray; - let item = new SideMenuItem(this, aContents, aTooltip, aArrowFlag); - - // Invalidate any notices set on the owner widget. - this.ownerView.removeAttribute("notice"); - - if (aIndex >= 0) { - list.insertBefore(item._container, list.childNodes[aIndex]); - menuArray.splice(aIndex, 0, item._target); - } else { - list.appendChild(item._container); - menuArray.push(item._target); - } - - return item._target; - }, + get _orderedGroupElementsArray() this.ownerView._orderedGroupElementsArray, + get _orderedMenuElementsArray() this.ownerView._orderedMenuElementsArray, /** * Inserts this group in the parent container at the specified index. * * @param number aIndex * The position in the container intended for this group. */ insertSelfAt: function SMG_insertSelfAt(aIndex) { let ownerList = this.ownerView._list; - let groupsArray = this._groupElementsArray; + let groupsArray = this._orderedGroupElementsArray; if (aIndex >= 0) { ownerList.insertBefore(this._target, groupsArray[aIndex]); groupsArray.splice(aIndex, 0, this._target); } else { ownerList.appendChild(this._target); groupsArray.push(this._target); } @@ -459,28 +480,30 @@ SideMenuGroup.prototype = { /** * Finds the expected index of this group based on its name. * * @return number * The expected index. */ findExpectedIndexForSelf: function SMG_findExpectedIndexForSelf() { let identifier = this.identifier; - let groupsArray = this._groupElementsArray; + let groupsArray = this._orderedGroupElementsArray; for (let group of groupsArray) { let name = group.getAttribute("name"); if (name > identifier && // Insertion sort at its best :) !name.contains(identifier)) { // Least significat group should be last. return groupsArray.indexOf(group); } } return -1; }, + window: null, + document: null, ownerView: null, identifier: "", _target: null, _title: null, _name: null, _list: null }; @@ -492,50 +515,71 @@ SideMenuGroup.prototype = { * @param string aTooltip [optional] * A tooltip attribute for the displayed item. * @param string | nsIDOMNode aContents * The string or node displayed in the container. * @param boolean aArrowFlag * True if a horizontal arrow should be shown. */ function SideMenuItem(aGroup, aContents, aTooltip, aArrowFlag) { + this.document = aGroup.document; + this.window = aGroup.window; this.ownerView = aGroup; - let document = this.document; - // Show a horizontal arrow towards the content. if (aArrowFlag) { - let target = this._target = document.createElement("vbox"); + let container = this._container = this.document.createElement("hbox"); + container.className = "side-menu-widget-item"; + container.setAttribute("tooltiptext", aTooltip); + + let target = this._target = this.document.createElement("vbox"); target.className = "side-menu-widget-item-contents"; - let arrow = this._arrow = document.createElement("hbox"); + let arrow = this._arrow = this.document.createElement("hbox"); arrow.className = "side-menu-widget-item-arrow"; - let container = this._container = document.createElement("hbox"); - container.className = "side-menu-widget-item side-menu-widget-item-or-group"; - container.setAttribute("tooltiptext", aTooltip); container.appendChild(target); container.appendChild(arrow); } // Skip a few redundant nodes when no horizontal arrow is shown. else { - let target = this._target = this._container = document.createElement("hbox"); - target.className = - "side-menu-widget-item " + - "side-menu-widget-item-or-group " + - "side-menu-widget-item-contents"; + let target = this._target = this._container = this.document.createElement("hbox"); + target.className = "side-menu-widget-item side-menu-widget-item-contents"; } this._target.setAttribute("flex", "1"); this.contents = aContents; } SideMenuItem.prototype = { - get document() this.ownerView.document, - get window() this.document.defaultView, + get _orderedGroupElementsArray() this.ownerView._orderedGroupElementsArray, + get _orderedMenuElementsArray() this.ownerView._orderedMenuElementsArray, + + /** + * Inserts this item in the parent group at the specified index. + * + * @param number aIndex + * The position in the container intended for this item. + * @return nsIDOMNode + * The element associated with the displayed item. + */ + insertSelfAt: function SMI_insertSelfAt(aIndex) { + let ownerList = this.ownerView._list; + let menuArray = this._orderedMenuElementsArray; + + if (aIndex >= 0) { + ownerList.insertBefore(this._container, ownerList.childNodes[aIndex]); + menuArray.splice(aIndex, 0, this._target); + } else { + ownerList.appendChild(this._container); + menuArray.push(this._target); + } + + return this._target; + }, /** * Sets the contents displayed in this item's view. * * @param string | nsIDOMNode aContents * The string or node displayed in the container. */ set contents(aContents) { @@ -554,13 +598,15 @@ SideMenuItem.prototype = { if (this._target.hasChildNodes()) { this._target.replaceChild(aContents, this._target.firstChild); return; } // These are the first contents ever displayed. this._target.appendChild(aContents); }, + window: null, + document: null, ownerView: null, _target: null, _container: null, _arrow: null };
--- a/browser/devtools/shared/widgets/VariablesView.jsm +++ b/browser/devtools/shared/widgets/VariablesView.jsm @@ -123,20 +123,19 @@ VariablesView.prototype = { } // Check if this empty operation may be executed lazily. if (this.lazyEmpty && aTimeout > 0) { this._emptySoon(aTimeout); return; } let list = this._list; - let firstChild; - - while (firstChild = list.firstChild) { - list.removeChild(firstChild); + + while (list.hasChildNodes()) { + list.firstChild.remove(); } this._store.length = 0; this._itemsByElement.clear(); this._appendEmptyNotice(); this._toggleSearchVisibility(false); }, @@ -158,32 +157,32 @@ VariablesView.prototype = { */ _emptySoon: function VV__emptySoon(aTimeout) { let prevList = this._list; let currList = this._list = this.document.createElement("scrollbox"); this._store.length = 0; this._itemsByElement.clear(); - this._emptyTimeout = this.window.setTimeout(function() { + this._emptyTimeout = this.window.setTimeout(() => { this._emptyTimeout = null; prevList.removeEventListener("keypress", this._onViewKeyPress, false); currList.addEventListener("keypress", this._onViewKeyPress, false); currList.setAttribute("orient", "vertical"); this._parent.removeChild(prevList); this._parent.appendChild(currList); this._boxObject = currList.boxObject.QueryInterface(Ci.nsIScrollBoxObject); if (!this._store.length) { this._appendEmptyNotice(); this._toggleSearchVisibility(false); } - }.bind(this), aTimeout); + }, aTimeout); }, /** * The amount of time (in milliseconds) it takes to empty this view lazily. */ lazyEmptyDelay: LAZY_EMPTY_DELAY, /** @@ -194,16 +193,22 @@ VariablesView.prototype = { /** * Specifies if nodes in this view may be added lazily. * @see Scope.prototype._lazyAppend */ lazyAppend: true, /** + * Specifies if nodes in this view may be expanded lazily. + * @see Scope.prototype.expand + */ + lazyExpand: true, + + /** * Function called each time a variable or property's value is changed via * user interaction. If null, then value changes are disabled. * * This property is applied recursively onto each scope in this view and * affects only the child nodes when they're created. */ eval: null, @@ -398,17 +403,17 @@ VariablesView.prototype = { * Disables variable and property searching in this view. * Use the "searchEnabled" setter to disable searching. */ _disableSearch: function VV__disableSearch() { // If searching was already disabled, no need to re-disable it again. if (!this._searchboxContainer) { return; } - this._searchboxContainer.parentNode.removeChild(this._searchboxContainer); + this._searchboxContainer.remove(); this._searchboxNode.removeEventListener("input", this._onSearchboxInput, false); this._searchboxNode.removeEventListener("keypress", this._onSearchboxKeyPress, false); this._searchboxContainer = null; this._searchboxNode = null; }, /** @@ -637,25 +642,27 @@ VariablesView.prototype = { let focused = this.document.commandDispatcher.focusedElement; return this.getItemForNode(focused); }, /** * Focuses the next scope, variable or property in this view. * @see VariablesView.prototype._focusChange */ - focusNextItem: function VV_focusNextItem(aMaintainViewFocusedFlag) - this._focusChange("advanceFocus", aMaintainViewFocusedFlag), + focusNextItem: function VV_focusNextItem(aMaintainViewFocusedFlag) { + this._focusChange("advanceFocus", aMaintainViewFocusedFlag) + }, /** * Focuses the previous scope, variable or property in this view. * @see VariablesView.prototype._focusChange */ - focusPrevItem: function VV_focusPrevItem(aMaintainViewFocusedFlag) - this._focusChange("rewindFocus", aMaintainViewFocusedFlag), + focusPrevItem: function VV_focusPrevItem(aMaintainViewFocusedFlag) { + this._focusChange("rewindFocus", aMaintainViewFocusedFlag) + }, /** * Focuses the next or previous scope, variable or property in this view. * * @param string aDirection * Either "advanceFocus" or "rewindFocus". * @param boolean aMaintainViewFocusedFlag * True too keep this view focused if the element is out of bounds. @@ -756,17 +763,16 @@ VariablesView.prototype = { } return; case e.DOM_VK_RIGHT: // Nothing to do here if this item never expands. if (!item._isArrowVisible) { return; } - // Expand scopes, variables and properties before advancing focus. if (!item._isExpanded) { item.expand(); } else { this.focusNextItem(true); } return; @@ -1046,17 +1052,18 @@ VariablesView.getterOrSetterEvalMacro = * Function invoked when a getter or setter is deleted. * * @param Property aItem * The current getter or setter property. */ VariablesView.getterOrSetterDeleteCallback = function(aItem) { aItem._disable(); - // Make sure the right getter/setter to value override macro is applied to the target object. + // Make sure the right getter/setter to value override macro is applied + // to the target object. aItem.ownerView.eval(aItem.evaluationMacro(aItem, "")); return true; // Don't hide the element. }; /** * A Scope is an object holding Variable instances. * Iterable via "for (let [name, variable] in instance) { }". @@ -1232,17 +1239,17 @@ Scope.prototype = { if (this._isExpanded || this._locked) { return; } // If there's a large number of enumerable or non-enumerable items // contained in this scope, painting them may take several seconds, // even if they were already displayed before. In this case, show a throbber // to suggest that this scope is expanding. if (!this._isExpanding && - this._variablesView.lazyAppend && + this._variablesView.lazyExpand && this._store.size > LAZY_APPEND_BATCH) { this._isExpanding = true; // Start spinning a throbber in this scope's title and allow a few // milliseconds for it to be painted. this._startThrobber(); this.window.setTimeout(this.expand.bind(this), LAZY_EXPAND_DELAY); return; @@ -1966,16 +1973,23 @@ Scope.prototype = { * @param object aDescriptor * The variable's descriptor. */ function Variable(aScope, aName, aDescriptor) { this._displayTooltip = this._displayTooltip.bind(this); this._activateNameInput = this._activateNameInput.bind(this); this._activateValueInput = this._activateValueInput.bind(this); + // Treat safe getter descriptors as descriptors with a value. + if ("getterValue" in aDescriptor) { + aDescriptor.value = aDescriptor.getterValue; + delete aDescriptor.get; + delete aDescriptor.set; + } + Scope.call(this, aScope, aName, this._initialDescriptor = aDescriptor); this.setGrip(aDescriptor.value); this._symbolicName = aName; this._absoluteName = aScope.name + "[\"" + aName + "\"]"; } ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, { /** @@ -1990,16 +2004,18 @@ ViewHelpers.create({ constructor: Variab * e.g. - { value: 42 } * - { value: true } * - { value: "nasu" } * - { value: { type: "undefined" } } * - { value: { type: "null" } } * - { value: { type: "object", class: "Object" } } * - { get: { type: "object", class: "Function" }, * set: { type: "undefined" } } + * - { get: { type "object", class: "Function" }, + * getterValue: "foo", getterPrototypeLevel: 2 } * @param boolean aRelaxed * True if name duplicates should be allowed. * @return Property * The newly created Property instance, null if it already exists. */ addProperty: function V_addProperty(aName = "", aDescriptor = {}, aRelaxed = false) { if (this._store.has(aName) && !aRelaxed) { return null; @@ -2369,24 +2385,27 @@ ViewHelpers.create({ constructor: Variab let document = this.document; let tooltip = document.createElement("tooltip"); tooltip.id = "tooltip-" + this._idString; let configurableLabel = document.createElement("label"); let enumerableLabel = document.createElement("label"); let writableLabel = document.createElement("label"); + let safeGetterLabel = document.createElement("label"); configurableLabel.setAttribute("value", "configurable"); enumerableLabel.setAttribute("value", "enumerable"); writableLabel.setAttribute("value", "writable"); + safeGetterLabel.setAttribute("value", "native-getter"); tooltip.setAttribute("orient", "horizontal"); tooltip.appendChild(configurableLabel); tooltip.appendChild(enumerableLabel); tooltip.appendChild(writableLabel); + tooltip.appendChild(safeGetterLabel); this._target.appendChild(tooltip); this._target.setAttribute("tooltip", tooltip.id); } if (this.ownerView.eval && !this._isUndefined && (this.getter || this.setter)) { this._editNode.setAttribute("tooltiptext", this.ownerView.editButtonTooltip); } if (this.ownerView.eval) { @@ -2415,16 +2434,19 @@ ViewHelpers.create({ constructor: Variab this._target.setAttribute("non-configurable", ""); } if (!descriptor.null && !descriptor.enumerable) { this._target.setAttribute("non-enumerable", ""); } if (!descriptor.null && !descriptor.writable && !this.ownerView.getter && !this.ownerView.setter) { this._target.setAttribute("non-writable", ""); } + if (descriptor && "getterValue" in descriptor) { + this._target.setAttribute("safe-getter", ""); + } if (name == "this") { this._target.setAttribute("self", ""); } else if (name == "<exception>") { this._target.setAttribute("exception", ""); } else if (name == "__proto__") { this._target.setAttribute("proto", "");
--- a/browser/devtools/shared/widgets/ViewHelpers.jsm +++ b/browser/devtools/shared/widgets/ViewHelpers.jsm @@ -300,53 +300,49 @@ ViewHelpers.Prefs.prototype = { /** * A generic MenuItem is used to describe elements present in a MenuContainer. * The label, value and description properties are necessarily strings. * Iterable via "for (let childItem in parentItem) { }". * * @param any aAttachment * Some attached primitive/object. - * @param string aLabel - * The label displayed in the container. - * @param string aValue - * The actual internal value of the item. - * @param string aDescription [optional] - * An optional description of the item. + * @param nsIDOMNode | nsIDOMDocumentFragment | array aContents [optional] + * A prebuilt node, or an array containing the following properties: + * - aLabel: the label displayed in the container + * - aValue: the actual internal value of the item + * - aDescription: an optional description of the item */ -this.MenuItem = function MenuItem(aAttachment, aLabel, aValue, aDescription) { +this.MenuItem = function MenuItem(aAttachment, aContents = []) { this.attachment = aAttachment; - this._label = aLabel + ""; - this._value = aValue + ""; - this._description = (aDescription || "") + ""; + + // Allow the insertion of prebuilt nodes. + if (aContents instanceof Ci.nsIDOMNode || + aContents instanceof Ci.nsIDOMDocumentFragment) { + this._prebuiltTarget = aContents; + } + // Delegate the item view creation to a container widget. + else { + let [aLabel, aValue, aDescription] = aContents; + this._label = aLabel + ""; + this._value = aValue + ""; + this._description = (aDescription || "") + ""; + } }; MenuItem.prototype = { - /** - * Gets the label set for this item. - * @return string - */ get label() this._label, - - /** - * Gets the value set for this item. - * @return string - */ get value() this._value, - - /** - * Gets the description set for this item. - * @return string - */ get description() this._description, + get target() this._target, /** * Immediately appends a child item to this menu item. * - * @param nsIDOMNode + * @param nsIDOMNode aElement * An nsIDOMNode representing the child element to append. * @param object aOptions [optional] * Additional options or flags supported by this operation: * - attachment: some attached primitive/object for the item * - attributes: a batch of attributes set to the displayed element * - finalize: function called when the child node is removed * @return MenuItem * The item associated with the displayed element. @@ -358,65 +354,65 @@ MenuItem.prototype = { if (aOptions.attributes) { this.setAttributes(aOptions.attributes); } if (aOptions.finalize) { item.finalize = aOptions.finalize; } // Entangle the item with the newly inserted child node. - this._entangleItem(item, this.target.appendChild(aElement)); + this._entangleItem(item, this._target.appendChild(aElement)); // Return the item associated with the displayed element. return item; }, /** * Immediately removes the specified child item from this menu item. * * @param MenuItem aItem * The item associated with the element to remove. */ remove: function MI_remove(aItem) { if (!aItem) { return; } - this.target.removeChild(aItem.target); + this._target.removeChild(aItem._target); this._untangleItem(aItem); }, /** * Visually marks this menu item as selected. */ markSelected: function MI_markSelected() { - if (!this.target) { + if (!this._target) { return; } - this.target.classList.add("selected"); + this._target.classList.add("selected"); }, /** * Visually marks this menu item as deselected. */ markDeselected: function MI_markDeselected() { - if (!this.target) { + if (!this._target) { return; } - this.target.classList.remove("selected"); + this._target.classList.remove("selected"); }, /** * Batch sets attributes on an element. * * @param array aAttributes * An array of [name, value] tuples representing the attributes. * @param nsIDOMNode aElement [optional] * A custom element to set the attributes to. */ - setAttributes: function MI_setAttributes(aAttributes, aElement = this.target) { + setAttributes: function MI_setAttributes(aAttributes, aElement = this._target) { for (let [name, value] of aAttributes) { aElement.setAttribute(name, value); } }, /** * Entangles an item (model) with a displayed node element (view). * @@ -426,35 +422,46 @@ MenuItem.prototype = { * The element displaying the item. */ _entangleItem: function MI__entangleItem(aItem, aElement) { if (!this._itemsByElement) { this._itemsByElement = new Map(); // This needs to be iterable. } this._itemsByElement.set(aElement, aItem); - aItem.target = aElement; + aItem._target = aElement; }, /** * Untangles an item (model) from a displayed node element (view). * * @param MenuItem aItem * The item describing the element. */ _untangleItem: function MI__untangleItem(aItem) { if (aItem.finalize) { aItem.finalize(aItem); } for (let childItem in aItem) { aItem.remove(childItem); } - this._itemsByElement.delete(aItem.target); - aItem.target = null; + this._unlinkItem(aItem); + aItem._prebuiltTarget = null; + aItem._target = null; + }, + + /** + * Deletes an item from the its parent's storage maps. + * + * @param MenuItem aItem + * The item to forget. + */ + _unlinkItem: function MC__unlinkItem(aItem) { + this._itemsByElement.delete(aItem._target); }, /** * Returns a string representing the object. * @return string */ toString: function MI_toString() { if (this._label && this._value) { @@ -464,17 +471,18 @@ MenuItem.prototype = { return this.attachment.toString(); } return "(null)"; }, _label: "", _value: "", _description: "", - target: null, + _prebuiltTarget: null, + _target: null, finalize: null, attachment: null }; /** * A generic MenuContainer is used for displaying MenuItem instances. * Iterable via "for (let item in menuContainer) { }". * @@ -532,71 +540,65 @@ MenuContainer.prototype = { * sorted by their label. This can be overridden with the "index" flag, * specifying on which position should an item be appended. * * Furthermore, this container makes sure that all the items are unique * (two items with the same label or value are not allowed) and non-degenerate * (items with "undefined" or "null" labels/values). This can, as well, be * overridden via the "relaxed" flag. * - * @param nsIDOMNode | object aContents - * An nsIDOMNode, or an array containing the following properties: + * @param nsIDOMNode | nsIDOMDocumentFragment array aContents + * A prebuilt node, or an array containing the following properties: * - label: the label displayed in the container * - value: the actual internal value of the item * - description: an optional description of the item * @param object aOptions [optional] * Additional options or flags supported by this operation: * - staged: true to stage the item to be appended later * - index: specifies on which position should the item be appended * - relaxed: true if this container should allow dupes & degenerates * - attachment: some attached primitive/object for the item * - attributes: a batch of attributes set to the displayed element * - finalize: function called when the item is untangled (removed) * @return MenuItem * The item associated with the displayed element if an unstaged push, * undefined if the item was staged for a later commit. */ push: function MC_push(aContents, aOptions = {}) { - if (aContents instanceof Ci.nsIDOMNode || - aContents instanceof Ci.nsIDOMElement) { - // Allow the insertion of prebuilt nodes. - aOptions.node = aContents; - aContents = ["", "", ""]; - } - - let [label, value, description] = aContents; - let item = new MenuItem(aOptions.attachment, label, value, description); + let item = new MenuItem(aOptions.attachment, aContents); // Batch the item to be added later. if (aOptions.staged) { + // Commit operations will ignore any specified index. + delete aOptions.index; return void this._stagedItems.push({ item: item, options: aOptions }); } // Find the target position in this container and insert the item there. if (!("index" in aOptions)) { - return this._insertItemAt(this._findExpectedIndex(label), item, aOptions); + return this._insertItemAt(this._findExpectedIndex(item), item, aOptions); } // Insert the item at the specified index. If negative or out of bounds, // the item will be simply appended. return this._insertItemAt(aOptions.index, item, aOptions); }, /** * Flushes all the prepared items into this container. + * Any specified index on the items will be ignored. Everything is appended. * * @param object aOptions [optional] * Additional options or flags supported by this operation: * - sorted: true to sort all the items before adding them */ commit: function MC_commit(aOptions = {}) { let stagedItems = this._stagedItems; // Sort the items before adding them to this container, if preferred. if (aOptions.sorted) { - stagedItems.sort(function(a, b) a.item._label.toLowerCase() > - b.item._label.toLowerCase()); + stagedItems.sort((a, b) => this._sortPredicate(a.item, b.item)); } // Append the prepared items to this container. for (let { item, options } of stagedItems) { this._insertItemAt(-1, item, options); } // Recreate the temporary items list for ulterior pushes. this._stagedItems.length = 0; }, @@ -625,17 +627,17 @@ MenuContainer.prototype = { * * @param MenuItem aItem * The item associated with the element to remove. */ remove: function MC_remove(aItem) { if (!aItem) { return; } - this._container.removeChild(aItem.target); + this._container.removeChild(aItem._target); this._untangleItem(aItem); }, /** * Removes all items from this container. */ empty: function MC_empty() { this._preferredValue = this.selectedValue; @@ -679,18 +681,105 @@ MenuContainer.prototype = { /** * Toggles all the items in this container hidden or visible. * * @param boolean aVisibleFlag * Specifies the intended visibility. */ toggleContents: function MC_toggleContents(aVisibleFlag) { for (let [, item] of this._itemsByElement) { - item.target.hidden = !aVisibleFlag; + item._target.hidden = !aVisibleFlag; + } + }, + + /** + * Sorts all the items in this container based on a predicate. + * + * @param function aPredicate [optional] + * Items are sorted according to the return value of the function, which + * will become the new default sorting predicate in this container. + * If unspecified, all items will be sorted by their label. + */ + sortContents: function MC_sortContents(aPredicate = this._sortPredicate) { + let sortedItems = this.allItems.sort(this._sortPredicate = aPredicate); + + for (let i = 0, len = sortedItems.length; i < len; i++) { + this.swapItems(this.getItemAtIndex(i), sortedItems[i]); + } + }, + + /** + * Visually swaps two items in this container. + * + * @param MenuItem aFirst + * The first menu item to be swapped. + * @param MenuItem aSecond + * The second menu item to be swapped. + */ + swapItems: function MC_swapItems(aFirst, aSecond) { + if (aFirst == aSecond) { // We're just dandy, thank you. + return; + } + let { _prebuiltTarget: firstPrebuiltTarget, target: firstTarget } = aFirst; + let { _prebuiltTarget: secondPrebuiltTarget, target: secondTarget } = aSecond; + + // If the two items were constructed with prebuilt nodes as DocumentFragments, + // then those DocumentFragments are now empty and need to be reassembled. + if (firstPrebuiltTarget instanceof Ci.nsIDOMDocumentFragment) { + for (let node of firstTarget.childNodes) { + firstPrebuiltTarget.appendChild(node.cloneNode(true)); + } } + if (secondPrebuiltTarget instanceof Ci.nsIDOMDocumentFragment) { + for (let node of secondTarget.childNodes) { + secondPrebuiltTarget.appendChild(node.cloneNode(true)); + } + } + + // 1. Get the indices of the two items to swap. + let i = this._indexOfElement(firstTarget); + let j = this._indexOfElement(secondTarget); + + // 2. Remeber the selection index, to reselect an item, if necessary. + let selectedTarget = this._container.selectedItem; + let selectedIndex = -1; + if (selectedTarget == firstTarget) { + selectedIndex = i; + } else if (selectedTarget == secondTarget) { + selectedIndex = j; + } + + // 3. Silently nuke both items, nobody needs to know about this. + this._container.removeChild(firstTarget); + this._container.removeChild(secondTarget); + this._unlinkItem(aFirst); + this._unlinkItem(aSecond); + + // 4. Add the items again, but reversing their indices. + this._insertItemAt.apply(this, i < j ? [i, aSecond] : [j, aFirst]); + this._insertItemAt.apply(this, i < j ? [j, aFirst] : [i, aSecond]); + + // 5. Restore the previous selection, if necessary. + if (selectedIndex == i) { + this._container.selectedItem = aFirst._target; + } else if (selectedIndex == j) { + this._container.selectedItem = aSecond._target; + } + }, + + /** + * Visually swaps two items in this container at specific indices. + * + * @param number aFirst + * The index of the first menu item to be swapped. + * @param number aSecond + * The index of the second menu item to be swapped. + */ + swapItemsAtIndices: function MC_swapItemsAtIndices(aFirst, aSecond) { + this.swapItems(this.getItemAtIndex(aFirst), this.getItemAtIndex(aSecond)); }, /** * Checks whether an item with the specified label is among the elements * shown in this container. * * @param string aLabel * The item's label. @@ -771,24 +860,25 @@ MenuContainer.prototype = { }, /** * Selects the element with the entangled item in this container. * @param MenuItem aItem */ set selectedItem(aItem) { // A falsy item is allowed to invalidate the current selection. - let targetNode = aItem ? aItem.target : null; + let targetElement = aItem ? aItem._target : null; // Prevent selecting the same item again, so return early. - if (this._container.selectedItem == targetNode) { + if (this._container.selectedItem == targetElement) { return; } - this._container.selectedItem = targetNode; - ViewHelpers.dispatchEvent(targetNode, "select", aItem); + + this._container.selectedItem = targetElement; + ViewHelpers.dispatchEvent(targetElement, "select", aItem); }, /** * Selects the element at the specified index in this container. * @param number aIndex */ set selectedIndex(aIndex) { let targetElement = this._container.getItemAtIndex(aIndex); @@ -872,17 +962,17 @@ MenuContainer.prototype = { * Finds the index of an item in the container. * * @param MenuItem aItem * The item get the index for. * @return number * The index of the matched item, or -1 if nothing is found. */ indexOfItem: function MC_indexOfItem(aItem) { - return this._indexOfElement(aItem.target); + return this._indexOfElement(aItem._target); }, /** * Finds the index of an element in the container. * * @param nsIDOMNode aElement * The element get the index for. * @return number @@ -926,17 +1016,30 @@ MenuContainer.prototype = { /** * Gets the total number of items in this container. * @return number */ get itemCount() this._itemsByElement.size, /** - * Returns a list of all the visible (non-hidden) items in this container. + * Returns a list of all items in this container, in the displayed order. + * @return array + */ + get allItems() { + let items = []; + for (let i = 0; i < this.itemCount; i++) { + items.push(this.getItemAtIndex(i)); + } + return items; + }, + + /** + * Returns a list of all the visible (non-hidden) items in this container, + * in no particular order. * @return array */ get visibleItems() { let items = []; for (let [element, item] of this._itemsByElement) { if (!element.hidden) { items.push(item); } @@ -982,35 +1085,36 @@ MenuContainer.prototype = { * Checks if an item's label and value are eligible for this container. * * @param MenuItem aItem * An object containing a label and a value property (at least). * @return boolean * True if the element is eligible, false otherwise. */ isEligible: function MC_isEligible(aItem) { - return this.isUnique(aItem) && + return aItem._prebuiltTarget || (this.isUnique(aItem) && aItem._label != "undefined" && aItem._label != "null" && - aItem._value != "undefined" && aItem._value != "null"; + aItem._value != "undefined" && aItem._value != "null"); }, /** - * Finds the expected item index in this container based on its label.