/* 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; 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", "DEFAULT_DEBUG_PORT", "connect" ]; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise; Cu.import("resource://gre/modules/osfile.jsm"); Cu.import("resource://gre/modules/devtools/gcli.jsm"); Cu.import("resource:///modules/devtools/shared/event-emitter.js"); let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools; let Telemetry = devtools.require("devtools/shared/telemetry"); let telemetry = new Telemetry(); XPCOMUtils.defineLazyModuleGetter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils", "resource:///modules/devtools/AppCacheUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Downloads", "resource://gre/modules/Downloads.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); /* CmdAddon ---------------------------------------------------------------- */ (function(module) { XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); // We need to use an object in which to store any flags because a primitive // would remain undefined. module.CmdAddonFlags = { addonsLoaded: false }; /** * 'addon' command. */ gcli.addCommand({ name: "addon", description: gcli.lookup("addonDesc") }); /** * 'addon list' command. */ gcli.addCommand({ name: "addon list", description: gcli.lookup("addonListDesc"), returnType: "addonsInfo", params: [{ name: 'type', type: { name: 'selection', data: ["dictionary", "extension", "locale", "plugin", "theme", "all"] }, defaultValue: 'all', description: gcli.lookup("addonListTypeDesc") }], exec: function(aArgs, context) { let deferred = context.defer(); function pendingOperations(aAddon) { let allOperations = ["PENDING_ENABLE", "PENDING_DISABLE", "PENDING_UNINSTALL", "PENDING_INSTALL", "PENDING_UPGRADE"]; return allOperations.reduce(function(operations, opName) { return aAddon.pendingOperations & AddonManager[opName] ? operations.concat(opName) : operations; }, []); } let types = aArgs.type === "all" ? null : [aArgs.type]; AddonManager.getAddonsByTypes(types, function(addons) { deferred.resolve({ addons: addons.map(function(addon) { return { name: addon.name, version: addon.version, isActive: addon.isActive, pendingOperations: pendingOperations(addon) }; }), type: aArgs.type }); }); return deferred.promise; } }); gcli.addConverter({ from: "addonsInfo", to: "view", exec: function(addonsInfo, context) { if (!addonsInfo.addons.length) { return context.createView({ html: "

${message}

", data: { message: gcli.lookup("addonNoneOfType") } }); } let headerLookups = { "dictionary": "addonListDictionaryHeading", "extension": "addonListExtensionHeading", "locale": "addonListLocaleHeading", "plugin": "addonListPluginHeading", "theme": "addonListThemeHeading", "all": "addonListAllHeading" }; let header = gcli.lookup(headerLookups[addonsInfo.type] || "addonListUnknownHeading"); let operationLookups = { "PENDING_ENABLE": "addonPendingEnable", "PENDING_DISABLE": "addonPendingDisable", "PENDING_UNINSTALL": "addonPendingUninstall", "PENDING_INSTALL": "addonPendingInstall", "PENDING_UPGRADE": "addonPendingUpgrade" }; function lookupOperation(opName) { let lookupName = operationLookups[opName]; return lookupName ? gcli.lookup(lookupName) : opName; } function arrangeAddons(addons) { let enabledAddons = []; let disabledAddons = []; addons.forEach(function(aAddon) { if (aAddon.isActive) { enabledAddons.push(aAddon); } else { disabledAddons.push(aAddon); } }); function compareAddonNames(aNameA, aNameB) { return String.localeCompare(aNameA.name, aNameB.name); } enabledAddons.sort(compareAddonNames); disabledAddons.sort(compareAddonNames); return enabledAddons.concat(disabledAddons); } function isActiveForToggle(addon) { return (addon.isActive && ~~addon.pendingOperations.indexOf("PENDING_DISABLE")); } return context.createView({ html: addonsListHtml, data: { header: header, addons: arrangeAddons(addonsInfo.addons).map(function(addon) { return { name: addon.name, label: addon.name.replace(/\s/g, "_") + (addon.version ? "_" + addon.version : ""), status: addon.isActive ? "enabled" : "disabled", version: addon.version, pendingOperations: addon.pendingOperations.length ? (" (" + gcli.lookup("addonPending") + ": " + addon.pendingOperations.map(lookupOperation).join(", ") + ")") : "", toggleActionName: isActiveForToggle(addon) ? "disable": "enable", toggleActionMessage: isActiveForToggle(addon) ? gcli.lookup("addonListOutDisable") : gcli.lookup("addonListOutEnable") }; }), onclick: context.update, ondblclick: context.updateExec } }); } }); var addonsListHtml = "" + "" + " " + " " + " " + " " + " " + " " + " " + " " + "
${header}
${addon.name} ${addon.version}${addon.pendingOperations}" + " ${addon.toggleActionMessage}" + "
" + ""; // We need a list of addon names for the enable and disable commands. Because // getting the name list is async we do not add the commands until we have the // list. AddonManager.getAllAddons(function addonAsync(aAddons) { // We listen for installs to keep our addon list up to date. There is no need // to listen for uninstalls because uninstalled addons are simply disabled // until restart (to enable undo functionality). AddonManager.addAddonListener({ onInstalled: function(aAddon) { addonNameCache.push({ name: representAddon(aAddon).replace(/\s/g, "_"), value: aAddon.name }); }, onUninstalled: function(aAddon) { let name = representAddon(aAddon).replace(/\s/g, "_"); for (let i = 0; i < addonNameCache.length; i++) { if(addonNameCache[i].name == name) { addonNameCache.splice(i, 1); break; } } }, }); /** * Returns a string that represents the passed add-on. */ function representAddon(aAddon) { let name = aAddon.name + " " + aAddon.version; return name.trim(); } let addonNameCache = []; // The name parameter, used in "addon enable" and "addon disable." let nameParameter = { name: "name", type: { name: "selection", lookup: addonNameCache }, description: gcli.lookup("addonNameDesc") }; for (let addon of aAddons) { addonNameCache.push({ name: representAddon(addon).replace(/\s/g, "_"), value: addon.name }); } /** * '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. */ function enable(aName, addons) { // Find the add-on. let addon = null; addons.some(function(candidate) { if (candidate.name == aName) { addon = candidate; return true; } else { return false; } }); let name = representAddon(addon); let message = ""; if (!addon.userDisabled) { message = gcli.lookupFormat("addonAlreadyEnabled", [name]); } else { addon.userDisabled = false; message = gcli.lookupFormat("addonEnabled", [name]); } this.resolve(message); } 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. */ gcli.addCommand({ name: "addon disable", description: gcli.lookup("addonDisableDesc"), params: [nameParameter], exec: function(aArgs, context) { /** * Like enable, but ... you know ... the exact opposite. */ function disable(aName, addons) { // Find the add-on. let addon = null; addons.some(function(candidate) { if (candidate.name == aName) { addon = candidate; return true; } else { return false; } }); let name = representAddon(addon); let message = ""; // If the addon is not disabled or is set to "click to play" then // disable it. Otherwise display the message "Add-on is already // disabled." if (!addon.userDisabled || addon.userDisabled === AddonManager.STATE_ASK_TO_ACTIVATE) { addon.userDisabled = true; message = gcli.lookupFormat("addonDisabled", [name]); } else { message = gcli.lookupFormat("addonAlreadyDisabled", [name]); } this.resolve(message); } let deferred = context.defer(); // List the installed add-ons, disable one when done listing. AddonManager.getAllAddons(disable.bind(deferred, aArgs.name)); return deferred.promise; } }); module.CmdAddonFlags.addonsLoaded = true; Services.obs.notifyObservers(null, "gcli_addon_commands_ready", null); }); }(this)); /* CmdCalllog -------------------------------------------------------------- */ (function(module) { XPCOMUtils.defineLazyGetter(this, "Debugger", function() { let JsDebugger = {}; Components.utils.import("resource://gre/modules/jsdebugger.jsm", JsDebugger); let global = Components.utils.getGlobalForObject({}); JsDebugger.addDebuggerToGlobal(global); return global.Debugger; }); let debuggers = []; /** * 'calllog' command */ gcli.addCommand({ name: "calllog", description: gcli.lookup("calllogDesc") }) /** * 'calllog start' command */ gcli.addCommand({ name: "calllog start", description: gcli.lookup("calllogStartDesc"), exec: function(args, context) { 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); let gBrowser = context.environment.chromeDocument.defaultView.gBrowser; let target = devtools.TargetFactory.forTab(gBrowser.selectedTab); gDevTools.showToolbox(target, "webconsole"); return gcli.lookup("calllogStartReply"); }, callDescription: function(frame) { let name = ""; if (frame.callee.name) { name = frame.callee.name; } else { let desc = frame.callee.getOwnPropertyDescriptor("displayName"); if (desc && desc.value && typeof desc.value == "string") { name = desc.value; } } let args = frame.arguments.map(this.valueToString).join(", "); return name + "(" + args + ")"; }, valueToString: function(value) { if (typeof value !== "object" || value === null) { return uneval(value); } return "[object " + value.class + "]"; } }); /** * '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"); } for (let dbg of debuggers) { dbg.onEnterFrame = undefined; } debuggers = []; return gcli.lookupFormat("calllogStopReply", [ numDebuggers ]); } }); }(this)); /* CmdCalllogChrome -------------------------------------------------------- */ (function(module) { XPCOMUtils.defineLazyGetter(this, "Debugger", function() { let JsDebugger = {}; Cu.import("resource://gre/modules/jsdebugger.jsm", JsDebugger); let global = Components.utils.getGlobalForObject({}); JsDebugger.addDebuggerToGlobal(global); return global.Debugger; }); let debuggers = []; let sandboxes = []; /** * 'calllog chromestart' command */ gcli.addCommand({ name: "calllog chromestart", description: gcli.lookup("calllogChromeStartDesc"), get hidden() gcli.hiddenByChromePref(), params: [ { name: "sourceType", type: { name: "selection", data: ["content-variable", "chrome-variable", "jsm", "javascript"] } }, { name: "source", type: "string", description: gcli.lookup("calllogChromeSourceTypeDesc"), manual: gcli.lookup("calllogChromeSourceTypeManual"), } ], exec: function(args, context) { let globalObj; let contentWindow = context.environment.window; if (args.sourceType == "jsm") { try { globalObj = Cu.import(args.source); } catch (e) { return gcli.lookup("callLogChromeInvalidJSM"); } } else if (args.sourceType == "content-variable") { if (args.source in contentWindow) { globalObj = Cu.getGlobalForObject(contentWindow[args.source]); } else { throw new Error(gcli.lookup("callLogChromeVarNotFoundContent")); } } else if (args.sourceType == "chrome-variable") { let chromeWin = context.environment.chromeDocument.defaultView; if (args.source in chromeWin) { globalObj = Cu.getGlobalForObject(chromeWin[args.source]); } else { return gcli.lookup("callLogChromeVarNotFoundChrome"); } } else { let chromeWin = context.environment.chromeDocument.defaultView; let sandbox = new Cu.Sandbox(chromeWin, { sandboxPrototype: chromeWin, wantXrays: false, sandboxName: "gcli-cmd-calllog-chrome" }); let returnVal; try { returnVal = Cu.evalInSandbox(args.source, sandbox, "ECMAv5"); sandboxes.push(sandbox); } catch(e) { // We need to save the message before cleaning up else e contains a dead // object. let msg = gcli.lookup("callLogChromeEvalException") + ": " + e; Cu.nukeSandbox(sandbox); return msg; } if (typeof returnVal == "undefined") { return gcli.lookup("callLogChromeEvalNeedsObject"); } globalObj = Cu.getGlobalForObject(returnVal); } let dbg = new Debugger(globalObj); debuggers.push(dbg); dbg.onEnterFrame = function(frame) { // BUG 773652 - Make the output from the GCLI calllog command nicer contentWindow.console.log(gcli.lookup("callLogChromeMethodCall") + ": " + this.callDescription(frame)); }.bind(this); let gBrowser = context.environment.chromeDocument.defaultView.gBrowser; let target = devtools.TargetFactory.forTab(gBrowser.selectedTab); gDevTools.showToolbox(target, "webconsole"); return gcli.lookup("calllogChromeStartReply"); }, valueToString: function(value) { if (typeof value !== "object" || value === null) return uneval(value); return "[object " + value.class + "]"; }, 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 */ 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"); } for (let dbg of debuggers) { dbg.onEnterFrame = undefined; dbg.enabled = false; } for (let sandbox of sandboxes) { Cu.nukeSandbox(sandbox); } debuggers = []; sandboxes = []; return gcli.lookupFormat("calllogChromeStopReply", [ numDebuggers ]); } }); }(this)); /* CmdCmd ------------------------------------------------------------------ */ (function(module) { let prefSvc = "@mozilla.org/preferences-service;1"; XPCOMUtils.defineLazyGetter(this, "prefBranch", function() { let prefService = Cc[prefSvc].getService(Ci.nsIPrefService); return prefService.getBranch(null).QueryInterface(Ci.nsIPrefBranch2); }); XPCOMUtils.defineLazyGetter(this, 'supportsString', function() { return Cc["@mozilla.org/supports-string;1"] .createInstance(Ci.nsISupportsString); }); 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. */ let commands = []; /** * 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. */ 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(); if (dirName == "") { return; } // replaces ~ with the home directory path in unix and windows if (dirName.indexOf("~") == 0) { let dirService = Cc["@mozilla.org/file/directory_service;1"] .getService(Ci.nsIProperties); let homeDirFile = dirService.get("Home", Ci.nsIFile); let homeDir = homeDirFile.path; dirName = dirName.substr(1); dirName = homeDir + dirName; } let statPromise = OS.File.stat(dirName); statPromise = statPromise.then( function onSuccess(stat) { if (!stat.isDir) { throw new Error('\'' + dirName + '\' is not a directory.'); } else { return dirName; } }, function onFailure(reason) { if (reason instanceof OS.File.Error && reason.becauseNoSuchFile) { throw new Error('\'' + dirName + '\' does not exist.'); } else { throw reason; } } ); statPromise.then( function onSuccess() { let iterator = new OS.File.DirectoryIterator(dirName); let iterPromise = iterator.forEach( function onEntry(entry) { if (entry.name.match(/.*\.mozcmd$/) && !entry.isDir) { loadCommandFile(entry, aSandboxPrincipal); } } ); iterPromise.then( function onSuccess() { iterator.close(); }, function onFailure(reason) { iterator.close(); throw reason; } ); } ); } }; /** * 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 readPromise = OS.File.read(aFileEntry.path); readPromise = readPromise.then( function onSuccess(array) { let decoder = new TextDecoder(); let source = decoder.decode(array); let sandbox = new Cu.Sandbox(aSandboxPrincipal, { sandboxPrototype: aSandboxPrincipal, wantXrays: false, sandboxName: aFileEntry.path }); let data = Cu.evalInSandbox(source, sandbox, "1.8", aFileEntry.name, 1); if (!Array.isArray(data)) { console.error("Command file '" + aFileEntry.name + "' does not have top level array."); return; } data.forEach(function(commandSpec) { gcli.addCommand(commandSpec); commands.push(commandSpec.name); }); }, function onError(reason) { console.error("OS.File.read(" + aFileEntry.path + ") failed."); throw reason; } ); } /** * 'cmd' command */ gcli.addCommand({ name: "cmd", get hidden() { return !prefBranch.prefHasUserValue(PREF_DIR); }, description: gcli.lookup("cmdDesc") }); /** * 'cmd refresh' command */ gcli.addCommand({ name: "cmd refresh", description: gcli.lookup("cmdRefreshDesc"), get hidden() { return !prefBranch.prefHasUserValue(PREF_DIR); }, exec: function(args, context) { let chromeWindow = context.environment.chromeDocument.defaultView; CmdCommands.refreshAutoCommands(chromeWindow); let dirName = prefBranch.getComplexValue(PREF_DIR, Ci.nsISupportsString).data.trim(); return gcli.lookupFormat("cmdStatus", [ commands.length, dirName ]); } }); /** * 'cmd setdir' command */ gcli.addCommand({ name: "cmd setdir", description: gcli.lookup("cmdSetdirDesc"), params: [ { name: "directory", description: gcli.lookup("cmdSetdirDirectoryDesc"), type: { name: "file", filetype: "directory", existing: "yes" }, defaultValue: null } ], returnType: "string", get hidden() { return true; // !prefBranch.prefHasUserValue(PREF_DIR); }, exec: function(args, context) { supportsString.data = args.directory; prefBranch.setComplexValue(PREF_DIR, Ci.nsISupportsString, supportsString); let chromeWindow = context.environment.chromeDocument.defaultView; CmdCommands.refreshAutoCommands(chromeWindow); return gcli.lookupFormat("cmdStatus", [ commands.length, args.directory ]); } }); }(this)); /* CmdConsole -------------------------------------------------------------- */ (function(module) { Object.defineProperty(this, "HUDService", { get: function() { return devtools.require("devtools/webconsole/hudservice"); }, configurable: true, enumerable: true }); /** * 'console' command */ gcli.addCommand({ name: "console", description: gcli.lookup("consoleDesc"), manual: gcli.lookup("consoleManual") }); /** * 'console clear' command */ gcli.addCommand({ name: "console clear", description: gcli.lookup("consoleclearDesc"), exec: function Command_consoleClear(args, context) { 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 */ 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 */ 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"); } }); }(this)); /* CmdCookie --------------------------------------------------------------- */ (function(module) { XPCOMUtils.defineLazyModuleGetter(this, "console", "resource://gre/modules/devtools/Console.jsm"); const cookieMgr = Cc["@mozilla.org/cookiemanager;1"] .getService(Ci.nsICookieManager2); /** * The template for the 'cookie list' command. */ let cookieListHtml = "" + "" + ""; gcli.addConverter({ from: "cookies", to: "view", exec: function(cookies, context) { if (cookies.length == 0) { let host = context.environment.document.location.host; let msg = gcli.lookupFormat("cookieListOutNoneHost", [ host ]); return context.createView({ html: "" + msg + "" }); } for (let cookie of cookies) { cookie.expires = translateExpires(cookie.expires); let noAttrs = !cookie.secure && !cookie.httpOnly && !cookie.sameDomain; cookie.attrs = (cookie.secure ? 'secure' : ' ') + (cookie.httpOnly ? 'httpOnly' : ' ') + (cookie.sameDomain ? 'sameDomain' : ' ') + (noAttrs ? gcli.lookup("cookieListOutNone") : ' '); } return context.createView({ html: cookieListHtml, data: { options: { allowEval: true }, cookies: cookies, onclick: context.update, ondblclick: context.updateExec } }); } }); /** * The cookie 'expires' value needs converting into something more readable */ function translateExpires(expires) { if (expires == 0) { return gcli.lookup("cookieListOutSession"); } return new Date(expires).toLocaleString(); } /** * Check if a given cookie matches a given host */ function isCookieAtHost(cookie, host) { if (cookie.host == null) { return host == null; } if (cookie.host.startsWith(".")) { return cookie.host === "." + host; } else { return cookie.host == host; } } /** * 'cookie' command */ gcli.addCommand({ name: "cookie", description: gcli.lookup("cookieDesc"), manual: gcli.lookup("cookieManual") }); /** * 'cookie list' command */ gcli.addCommand({ name: "cookie list", description: gcli.lookup("cookieListDesc"), manual: gcli.lookup("cookieListManual"), returnType: "cookies", exec: function(args, context) { let host = context.environment.document.location.host; 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); if (isCookieAtHost(cookie, host)) { cookies.push({ host: cookie.host, name: cookie.name, value: cookie.value, path: cookie.path, expires: cookie.expires, secure: cookie.secure, httpOnly: cookie.httpOnly, sameDomain: cookie.sameDomain }); } } return cookies; } }); /** * 'cookie remove' command */ gcli.addCommand({ name: "cookie remove", description: gcli.lookup("cookieRemoveDesc"), manual: gcli.lookup("cookieRemoveManual"), params: [ { name: "name", type: "string", description: gcli.lookup("cookieRemoveKeyDesc"), } ], exec: function(args, context) { let host = context.environment.document.location.host; let enm = cookieMgr.getCookiesFromHost(host); let cookies = []; while (enm.hasMoreElements()) { 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); } } } } }); /** * 'cookie set' command */ gcli.addCommand({ name: "cookie set", description: gcli.lookup("cookieSetDesc"), manual: gcli.lookup("cookieSetManual"), params: [ { name: "name", type: "string", description: gcli.lookup("cookieSetKeyDesc") }, { name: "value", type: "string", description: gcli.lookup("cookieSetValueDesc") }, { group: gcli.lookup("cookieSetOptionsDesc"), params: [ { name: "path", type: { name: "string", allowBlank: true }, defaultValue: "/", description: gcli.lookup("cookieSetPathDesc") }, { name: "domain", type: "string", defaultValue: null, description: gcli.lookup("cookieSetDomainDesc") }, { name: "secure", type: "boolean", description: gcli.lookup("cookieSetSecureDesc") }, { name: "httpOnly", type: "boolean", description: gcli.lookup("cookieSetHttpOnlyDesc") }, { name: "session", type: "boolean", description: gcli.lookup("cookieSetSessionDesc") }, { name: "expires", type: "string", defaultValue: "Jan 17, 2038", description: gcli.lookup("cookieSetExpiresDesc") }, ] } ], exec: function(args, context) { let host = context.environment.document.location.host; let time = Date.parse(args.expires) / 1000; cookieMgr.add(args.domain ? "." + args.domain : host, args.path ? args.path : "/", args.name, args.value, args.secure, args.httpOnly, args.session, time); } }); }(this)); /* CmdExport --------------------------------------------------------------- */ (function(module) { /** * '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. */ 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); } }); }(this)); /* CmdJsb ------------------------------------------------------------------ */ (function(module) { const XMLHttpRequest = Components.Constructor("@mozilla.org/xmlextras/xmlhttprequest;1"); XPCOMUtils.defineLazyModuleGetter(this, "js_beautify", "resource:///modules/devtools/Jsbeautify.jsm"); /** * jsb command. */ gcli.addCommand({ name: 'jsb', description: gcli.lookup('jsbDesc'), returnValue:'string', params: [ { name: 'url', type: 'string', description: gcli.lookup('jsbUrlDesc') }, { group: gcli.lookup("jsbOptionsDesc"), params: [ { name: 'indentSize', type: 'number', description: gcli.lookup('jsbIndentSizeDesc'), manual: gcli.lookup('jsbIndentSizeManual'), defaultValue: 2 }, { name: 'indentChar', type: { name: 'selection', lookup: [ { name: "space", value: " " }, { name: "tab", value: "\t" } ] }, description: gcli.lookup('jsbIndentCharDesc'), manual: gcli.lookup('jsbIndentCharManual'), defaultValue: ' ', }, { name: 'doNotPreserveNewlines', type: 'boolean', description: gcli.lookup('jsbDoNotPreserveNewlinesDesc') }, { name: 'preserveMaxNewlines', type: 'number', description: gcli.lookup('jsbPreserveMaxNewlinesDesc'), manual: gcli.lookup('jsbPreserveMaxNewlinesManual'), defaultValue: -1 }, { name: 'jslintHappy', type: 'boolean', description: gcli.lookup('jsbJslintHappyDesc'), manual: gcli.lookup('jsbJslintHappyManual') }, { name: 'braceStyle', type: { name: 'selection', data: ['collapse', 'expand', 'end-expand', 'expand-strict'] }, description: gcli.lookup('jsbBraceStyleDesc2'), manual: gcli.lookup('jsbBraceStyleManual2'), defaultValue: "collapse" }, { name: 'noSpaceBeforeConditional', type: 'boolean', description: gcli.lookup('jsbNoSpaceBeforeConditionalDesc') }, { name: 'unescapeStrings', type: 'boolean', description: gcli.lookup('jsbUnescapeStringsDesc'), manual: gcli.lookup('jsbUnescapeStringsManual') } ] } ], exec: function(args, context) { let opts = { indent_size: args.indentSize, indent_char: args.indentChar, preserve_newlines: !args.doNotPreserveNewlines, max_preserve_newlines: args.preserveMaxNewlines == -1 ? undefined : args.preserveMaxNewlines, jslint_happy: args.jslintHappy, brace_style: args.braceStyle, space_before_conditional: !args.noSpaceBeforeConditional, unescape_strings: args.unescapeStrings }; let xhr = new XMLHttpRequest(); try { xhr.open("GET", args.url, true); } catch(e) { return gcli.lookup('jsbInvalidURL'); } let deferred = context.defer(); xhr.onreadystatechange = function(aEvt) { if (xhr.readyState == 4) { if (xhr.status == 200 || xhr.status == 0) { let browserDoc = context.environment.chromeDocument; let browserWindow = browserDoc.defaultView; let gBrowser = browserWindow.gBrowser; let result = js_beautify(xhr.responseText, opts); browserWindow.Scratchpad.ScratchpadManager.openScratchpad({text: result}); deferred.resolve(); } else { deferred.resolve("Unable to load page to beautify: " + args.url + " " + xhr.status + " " + xhr.statusText); } }; } xhr.send(null); return deferred.promise; } }); }(this)); /* CmdPagemod -------------------------------------------------------------- */ (function(module) { /** * '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. */ gcli.addCommand({ name: "pagemod replace", description: gcli.lookup("pagemodReplaceDesc"), params: [ { name: "search", type: "string", description: gcli.lookup("pagemodReplaceSearchDesc"), }, { name: "replace", type: "string", description: gcli.lookup("pagemodReplaceReplaceDesc"), }, { name: "ignoreCase", type: "boolean", description: gcli.lookup("pagemodReplaceIgnoreCaseDesc"), }, { name: "selector", type: "string", description: gcli.lookup("pagemodReplaceSelectorDesc"), defaultValue: "*:not(script):not(style):not(embed):not(object):not(frame):not(iframe):not(frameset)", }, { name: "root", type: "node", description: gcli.lookup("pagemodReplaceRootDesc"), defaultValue: null, }, { name: "attrOnly", type: "boolean", description: gcli.lookup("pagemodReplaceAttrOnlyDesc"), }, { name: "contentOnly", type: "boolean", description: gcli.lookup("pagemodReplaceContentOnlyDesc"), }, { name: "attributes", type: "string", description: gcli.lookup("pagemodReplaceAttributesDesc"), defaultValue: null, }, ], exec: function(args, context) { 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 || context.environment.document; let elements = root.querySelectorAll(args.selector); elements = Array.prototype.slice.call(elements); let replacedTextNodes = 0; let replacedAttributes = 0; function replaceAttribute() { replacedAttributes++; return args.replace; } function replaceTextNode() { replacedTextNodes++; return args.replace; } for (let i = 0; i < elements.length; i++) { let element = elements[i]; if (searchTextNodes) { for (let y = 0; y < element.childNodes.length; y++) { let node = element.childNodes[y]; if (node.nodeType == node.TEXT_NODE) { node.textContent = node.textContent.replace(search, replaceTextNode); } } } if (searchAttributes) { if (!element.attributes) { continue; } for (let y = 0; y < element.attributes.length; y++) { let attr = element.attributes[y]; if (!attributeRegex || attributeRegex.test(attr.name)) { attr.value = attr.value.replace(search, replaceAttribute); } } } } return gcli.lookupFormat("pagemodReplaceResult", [elements.length, replacedTextNodes, replacedAttributes]); } }); /** * 'pagemod remove' command */ gcli.addCommand({ name: "pagemod remove", description: gcli.lookup("pagemodRemoveDesc"), }); /** * The 'pagemod remove element' command. */ gcli.addCommand({ name: "pagemod remove element", description: gcli.lookup("pagemodRemoveElementDesc"), params: [ { name: "search", type: "string", description: gcli.lookup("pagemodRemoveElementSearchDesc"), }, { name: "root", type: "node", description: gcli.lookup("pagemodRemoveElementRootDesc"), defaultValue: null, }, { name: 'stripOnly', type: 'boolean', description: gcli.lookup("pagemodRemoveElementStripOnlyDesc"), }, { name: 'ifEmptyOnly', type: 'boolean', description: gcli.lookup("pagemodRemoveElementIfEmptyOnlyDesc"), }, ], exec: function(args, context) { 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; } if (args.stripOnly) { while (element.hasChildNodes()) { parentNode.insertBefore(element.childNodes[0], element); } } if (!args.ifEmptyOnly || !element.hasChildNodes()) { element.parentNode.removeChild(element); removed++; } } return gcli.lookupFormat("pagemodRemoveElementResultMatchedAndRemovedElements", [elements.length, removed]); } }); /** * The 'pagemod remove attribute' command. */ gcli.addCommand({ name: "pagemod remove attribute", description: gcli.lookup("pagemodRemoveAttributeDesc"), params: [ { name: "searchAttributes", type: "string", description: gcli.lookup("pagemodRemoveAttributeSearchAttributesDesc"), }, { name: "searchElements", type: "string", description: gcli.lookup("pagemodRemoveAttributeSearchElementsDesc"), }, { name: "root", type: "node", description: gcli.lookup("pagemodRemoveAttributeRootDesc"), defaultValue: null, }, { name: "ignoreCase", type: "boolean", description: gcli.lookup("pagemodRemoveAttributeIgnoreCaseDesc"), }, ], exec: function(args, context) { 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]; if (!element.attributes) { continue; } var attrs = Array.prototype.slice.call(element.attributes); for (let y = 0; y < attrs.length; y++) { let attr = attrs[y]; if (attributeRegex.test(attr.name)) { element.removeAttribute(attr.name); removed++; } } } 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. */ function escapeRegex(aString) { return aString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); } }(this)); /* CmdTools -------------------------------------------------------------- */ (function(module) { gcli.addCommand({ name: "tools", description: gcli.lookupFormat("toolsDesc2", [BRAND_SHORT_NAME]), manual: gcli.lookupFormat("toolsManual2", [BRAND_SHORT_NAME]), get hidden() gcli.hiddenByChromePref(), }); gcli.addCommand({ name: "tools srcdir", description: gcli.lookup("toolsSrcdirDesc"), manual: gcli.lookupFormat("toolsSrcdirManual2", [BRAND_SHORT_NAME]), get hidden() gcli.hiddenByChromePref(), params: [ { name: "srcdir", type: "string" /* { name: "file", filetype: "directory", existing: "yes" } */, description: gcli.lookup("toolsSrcdirDir") } ], returnType: "string", exec: function(args, context) { let clobber = OS.Path.join(args.srcdir, "CLOBBER"); return OS.File.exists(clobber).then(function(exists) { if (exists) { let str = Cc["@mozilla.org/supports-string;1"] .createInstance(Ci.nsISupportsString); str.data = args.srcdir; Services.prefs.setComplexValue("devtools.loader.srcdir", Ci.nsISupportsString, str); devtools.reload(); let msg = gcli.lookupFormat("toolsSrcdirReloaded", [args.srcdir]); throw new Error(msg); } return gcli.lookupFormat("toolsSrcdirNotFound", [args.srcdir]); }); } }); gcli.addCommand({ name: "tools builtin", description: gcli.lookup("toolsBuiltinDesc"), manual: gcli.lookup("toolsBuiltinManual"), get hidden() gcli.hiddenByChromePref(), returnType: "string", exec: function(args, context) { Services.prefs.clearUserPref("devtools.loader.srcdir"); devtools.reload(); return gcli.lookup("toolsBuiltinReloaded"); } }); gcli.addCommand({ name: "tools reload", description: gcli.lookup("toolsReloadDesc"), get hidden() gcli.hiddenByChromePref() || !Services.prefs.prefHasUserValue("devtools.loader.srcdir"), returnType: "string", exec: function(args, context) { devtools.reload(); return gcli.lookup("toolsReloaded2"); } }); }(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 */ gcli.addCommand({ name: "restart", description: gcli.lookupFormat("restartBrowserDesc", [BRAND_SHORT_NAME]), params: [ { name: "nocache", type: "boolean", description: gcli.lookup("restartBrowserNocacheDesc") } ], returnType: "string", exec: function Restart(args, context) { let canceled = Cc["@mozilla.org/supports-PRBool;1"] .createInstance(Ci.nsISupportsPRBool); Services.obs.notifyObservers(canceled, "quit-application-requested", "restart"); if (canceled.data) { return gcli.lookup("restartBrowserRequestCancelled"); } // disable loading content from cache. if (args.nocache) { Services.appinfo.invalidateCachesOnRestart(); } // restart Cc['@mozilla.org/toolkit/app-startup;1'] .getService(Ci.nsIAppStartup) .quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart); return gcli.lookupFormat("restartBrowserRestarting", [BRAND_SHORT_NAME]); } }); }(this)); /* CmdScreenshot ----------------------------------------------------------- */ (function(module) { XPCOMUtils.defineLazyModuleGetter(this, "LayoutHelpers", "resource://gre/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 */ gcli.addCommand({ name: "screenshot", description: gcli.lookup("screenshotDesc"), manual: gcli.lookup("screenshotManual"), returnType: "dom", params: [ { name: "filename", type: "string", defaultValue: FILENAME_DEFAULT_VALUE, description: gcli.lookup("screenshotFilenameDesc"), manual: gcli.lookup("screenshotFilenameManual") }, { group: gcli.lookup("screenshotGroupOptions"), params: [ { name: "clipboard", type: "boolean", description: gcli.lookup("screenshotClipboardDesc"), manual: gcli.lookup("screenshotClipboardManual") }, { name: "chrome", type: "boolean", description: gcli.lookupFormat("screenshotChromeDesc2", [BRAND_SHORT_NAME]), manual: gcli.lookupFormat("screenshotChromeManual2", [BRAND_SHORT_NAME]) }, { name: "delay", type: { name: "number", min: 0 }, defaultValue: 0, description: gcli.lookup("screenshotDelayDesc"), manual: gcli.lookup("screenshotDelayManual") }, { name: "fullpage", type: "boolean", description: gcli.lookup("screenshotFullPageDesc"), manual: gcli.lookup("screenshotFullPageManual") }, { name: "selector", type: "node", defaultValue: null, description: gcli.lookup("inspectNodeDesc"), manual: gcli.lookup("inspectNodeManual") } ] } ], 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.document; var deferred = context.defer(); if (args.delay > 0) { document.defaultView.setTimeout(function Command_screenshotDelay() { let promise = this.grabScreen(document, args.filename, args.clipboard, args.fullpage); promise.then(deferred.resolve, deferred.reject); }.bind(this), args.delay * 1000); } else { let promise = this.grabScreen(document, args.filename, args.clipboard, args.fullpage, args.selector); promise.then(deferred.resolve, deferred.reject); } return deferred.promise; }, grabScreen: function(document, filename, clipboard, fullpage, node) { return Task.spawn(function() { 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"); if (!fullpage) { if (!node) { left = window.scrollX; top = window.scrollY; width = window.innerWidth; height = window.innerHeight; } else { let lh = new LayoutHelpers(window); let rect = lh.getRect(node, window); top = rect.top; left = rect.left; width = rect.width; height = rect.height; } } else { width = window.innerWidth + window.scrollMaxX; height = window.innerHeight + window.scrollMaxY; } canvas.width = width; canvas.height = height; let ctx = canvas.getContext("2d"); ctx.drawWindow(window, left, top, width, height, "#fff"); let data = canvas.toDataURL("image/png", ""); let loadContext = document.defaultView .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsILoadContext); if (clipboard) { try { let io = Cc["@mozilla.org/network/io-service;1"] .getService(Ci.nsIIOService); let channel = io.newChannel(data, null, null); let input = channel.open(); let imgTools = Cc["@mozilla.org/image/tools;1"] .getService(Ci.imgITools); let container = {}; imgTools.decodeImageData(input, channel.contentType, container); let wrapped = Cc["@mozilla.org/supports-interface-pointer;1"] .createInstance(Ci.nsISupportsInterfacePointer); wrapped.data = container.value; let trans = Cc["@mozilla.org/widget/transferable;1"] .createInstance(Ci.nsITransferable); trans.init(loadContext); trans.addDataFlavor(channel.contentType); trans.setTransferData(channel.contentType, wrapped, -1); let clipid = Ci.nsIClipboard; let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid); clip.setData(trans, null, clipid.kGlobalClipboard); div.textContent = gcli.lookup("screenshotCopied"); } catch (ex) { div.textContent = gcli.lookup("screenshotErrorCopying"); } throw new Task.Result(div); } let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); // Create a name for the file if not present if (filename == FILENAME_DEFAULT_VALUE) { let date = new Date(); let dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate(); dateString = dateString.split("-").map(function(part) { if (part.length == 1) { part = "0" + part; } return part; }).join("-"); let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0]; filename = gcli.lookupFormat("screenshotGeneratedFilename", [dateString, timeString]) + ".png"; } // Check there is a .png extension to filename else if (!filename.match(/.png$/i)) { filename += ".png"; } // If the filename is relative, tack it onto the download directory if (!filename.match(/[\\\/]/)) { let preferredDir = yield Downloads.getPreferredDownloadsDirectory(); filename = OS.Path.join(preferredDir, filename); } try { file.initWithPath(filename); } catch (ex) { div.textContent = gcli.lookup("screenshotErrorSavingToFile") + " " + filename; throw new Task.Result(div); } let ioService = Cc["@mozilla.org/network/io-service;1"] .getService(Ci.nsIIOService); let Persist = Ci.nsIWebBrowserPersist; let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] .createInstance(Persist); persist.persistFlags = Persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | Persist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; let source = ioService.newURI(data, "UTF8", null); persist.saveURI(source, null, null, null, null, file, loadContext); div.textContent = gcli.lookup("screenshotSavedToFile") + " \"" + filename + "\""; div.addEventListener("click", function openFile() { div.removeEventListener("click", openFile); file.reveal(); }); div.style.cursor = "pointer"; let image = document.createElement("div"); let previewHeight = parseInt(256*height/width); image.setAttribute("style", "width:256px; height:" + previewHeight + "px;" + "max-height: 256px;" + "background-image: url('" + data + "');" + "background-size: 256px " + previewHeight + "px;" + "margin: 4px; display: block"); div.appendChild(image); throw new Task.Result(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.lookupFormat("listenManual2", [BRAND_SHORT_NAME]), 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 */ gcli.addCommand({ name: 'paintflashing', description: gcli.lookup('paintflashingDesc') }); gcli.addCommand({ name: 'paintflashing on', description: gcli.lookup('paintflashingOnDesc'), manual: gcli.lookup('paintflashingManual'), params: [{ group: "options", params: [ { type: "boolean", name: "chrome", get hidden() gcli.hiddenByChromePref(), description: gcli.lookup("paintflashingChromeDesc"), } ] }], exec: function(args, context) { 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'), params: [{ group: "options", params: [ { type: "boolean", name: "chrome", get hidden() gcli.hiddenByChromePref(), description: gcli.lookup("paintflashingChromeDesc"), } ] }], exec: function(args, context) { 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", buttonClass: "command-button", state: { isChecked: function(aTarget) { if (aTarget.isLocalTab) { let window = aTarget.tab.linkedBrowser.contentWindow; let wUtils = window.QueryInterface(Ci.nsIInterfaceRequestor). getInterface(Ci.nsIDOMWindowUtils); return wUtils.paintFlashing; } else { throw new Error("Unsupported target"); } }, onChange: function(aTarget, aChangeHandler) { eventEmitter.on("changed", aChangeHandler); }, offChange: function(aTarget, aChangeHandler) { eventEmitter.off("changed", aChangeHandler); }, }, tooltipText: gcli.lookup("paintflashingTooltip"), description: gcli.lookup('paintflashingToggleDesc'), manual: gcli.lookup('paintflashingManual'), exec: function(args, context) { var window = context.environment.window; var wUtils = window.QueryInterface(Ci.nsIInterfaceRequestor). getInterface(Ci.nsIDOMWindowUtils); wUtils.paintFlashing = !wUtils.paintFlashing; onPaintFlashingChanged(context); } }); let eventEmitter = new EventEmitter(); function onPaintFlashingChanged(context) { var gBrowser = context.environment.chromeDocument.defaultView.gBrowser; var tab = gBrowser.selectedTab; eventEmitter.emit("changed", tab); function fireChange() { eventEmitter.emit("changed", tab); } var target = devtools.TargetFactory.forTab(tab); target.off("navigate", fireChange); target.once("navigate", fireChange); var window = context.environment.window; var wUtils = window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); if (wUtils.paintFlashing) { telemetry.toolOpened("paintflashing"); } else { telemetry.toolClosed("paintflashing"); } } }(this)); /* CmdAppCache ------------------------------------------------------- */ (function(module) { /** * 'appcache' command */ gcli.addCommand({ name: 'appcache', description: gcli.lookup('appCacheDesc') }); gcli.addConverter({ from: "appcacheerrors", to: "view", exec: function([errors, manifestURI], context) { if (errors.length == 0) { return context.createView({ html: "" + gcli.lookup("appCacheValidatedSuccessfully") + "" }); } let appcacheValidateHtml = "

Manifest URI: ${manifestURI}

" + "
    " + "
  1. " + " ${error.msg}" + "
  2. " + "
"; return context.createView({ html: "
" + appcacheValidateHtml + "
", data: { errors: errors, manifestURI: manifestURI } }); } }); gcli.addCommand({ name: 'appcache validate', description: gcli.lookup('appCacheValidateDesc'), manual: gcli.lookup('appCacheValidateManual'), returnType: 'appcacheerrors', params: [{ group: "options", params: [ { type: "string", name: "uri", description: gcli.lookup("appCacheValidateUriDesc"), defaultValue: null, } ] }], exec: function(args, context) { let utils; let deferred = context.defer(); if (args.uri) { utils = new AppCacheUtils(args.uri); } else { utils = new AppCacheUtils(context.environment.document); } utils.validateManifest().then(function(errors) { deferred.resolve([errors, utils.manifestURI || "-"]); }); 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 = "" + ""; gcli.addConverter({ from: "appcacheentries", to: "view", exec: function(entries, context) { return context.createView({ html: appcacheListEntries, data: { entries: entries, onclick: context.update, ondblclick: context.updateExec } }); } }); gcli.addCommand({ name: 'appcache list', description: gcli.lookup('appCacheListDesc'), manual: gcli.lookup('appCacheListManual'), returnType: "appcacheentries", params: [{ group: "options", params: [ { type: "string", name: "search", description: gcli.lookup("appCacheListSearchDesc"), defaultValue: null, }, ] }], exec: function(args, context) { let utils = new AppCacheUtils(); 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 utils = new AppCacheUtils(); return utils.viewEntry(args.key); } }); }(this)); /* CmdMedia ------------------------------------------------------- */ (function(module) { /** * 'media' command */ gcli.addCommand({ name: "media", description: gcli.lookup("mediaDesc") }); gcli.addCommand({ name: "media emulate", description: gcli.lookup("mediaEmulateDesc"), manual: gcli.lookup("mediaEmulateManual"), params: [ { name: "type", description: gcli.lookup("mediaEmulateType"), type: { name: "selection", data: ["braille", "embossed", "handheld", "print", "projection", "screen", "speech", "tty", "tv"] } } ], exec: function(args, context) { let markupDocumentViewer = context.environment.chromeWindow .gBrowser.markupDocumentViewer; markupDocumentViewer.emulateMedium(args.type); } }); gcli.addCommand({ name: "media reset", description: gcli.lookup("mediaResetDesc"), manual: gcli.lookup("mediaEmulateManual"), exec: function(args, context) { let markupDocumentViewer = context.environment.chromeWindow .gBrowser.markupDocumentViewer; markupDocumentViewer.stopEmulatingMedium(); } }); }(this)); /* CmdSplitConsole ------------------------------------------------------- */ (function(module) { /** * 'splitconsole' command (hidden) */ gcli.addCommand({ name: 'splitconsole', hidden: true, buttonId: "command-button-splitconsole", buttonClass: "command-button", tooltipText: gcli.lookup("splitconsoleTooltip"), state: { isChecked: function(aTarget) { let toolbox = gDevTools.getToolbox(aTarget); return toolbox && toolbox.splitConsole; }, onChange: function(aTarget, aChangeHandler) { eventEmitter.on("changed", aChangeHandler); }, offChange: function(aTarget, aChangeHandler) { eventEmitter.off("changed", aChangeHandler); }, }, exec: function(args, context) { toggleSplitConsole(context); } }); function toggleSplitConsole(context) { let gBrowser = context.environment.chromeDocument.defaultView.gBrowser; let target = devtools.TargetFactory.forTab(gBrowser.selectedTab); let toolbox = gDevTools.getToolbox(target); if (!toolbox) { gDevTools.showToolbox(target, "inspector").then((toolbox) => { toolbox.toggleSplitConsole(); }); } else { toolbox.toggleSplitConsole(); } } let eventEmitter = new EventEmitter(); function fireChange(tab) { eventEmitter.emit("changed", tab); } gDevTools.on("toolbox-ready", (e, toolbox) => { if (!toolbox.target) { return; } let fireChangeForTab = fireChange.bind(this, toolbox.target.tab); toolbox.on("split-console", fireChangeForTab); toolbox.on("select", fireChangeForTab); toolbox.once("destroyed", () => { toolbox.off("split-console", fireChangeForTab); toolbox.off("select", fireChangeForTab); }); }); }(this));