Merge fx-team to m-c

This commit is contained in:
Dave Camp 2012-05-30 19:21:18 -07:00
commit e3f37996ee
61 changed files with 4586 additions and 2591 deletions

View File

@ -1013,8 +1013,9 @@ pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
// Disable the error console
pref("devtools.errorconsole.enabled", false);
// Enable the developer toolbar
// Developer toolbar and GCLI preferences
pref("devtools.toolbar.enabled", false);
pref("devtools.gcli.allowSet", false);
// Enable the Inspector
pref("devtools.inspector.enabled", true);

View File

@ -292,7 +292,7 @@ let TabView = {
// if group has title, it's not hidden and there is no active group or
// the active group id doesn't match the group id, a group menu item
// would be added.
if (groupItem.getTitle().length > 0 && !groupItem.hidden &&
if (!groupItem.hidden && groupItem.getChildren().length &&
(!activeGroup || activeGroup.id != groupItem.id)) {
let menuItem = self._createGroupMenuItem(groupItem);
popup.insertBefore(menuItem, separator);
@ -305,10 +305,27 @@ let TabView = {
// ----------
_createGroupMenuItem: function TabView__createGroupMenuItem(groupItem) {
let menuItem = document.createElement("menuitem")
menuItem.setAttribute("label", groupItem.getTitle());
let menuItem = document.createElement("menuitem");
let title = groupItem.getTitle();
if (!title.trim()) {
let topChildLabel = groupItem.getTopChild().tab.label;
if (groupItem.getChildren().length > 1) {
title =
gNavigatorBundle.getFormattedString("tabview2.moveToUnnamedGroup.label",
[topChildLabel, groupItem.getChildren().length - 1]);
} else {
title = topChildLabel;
}
}
menuItem.setAttribute("label", title);
menuItem.setAttribute("tooltiptext", title);
menuItem.setAttribute("crop", "center");
menuItem.setAttribute("class", "tabview-menuitem");
menuItem.setAttribute(
"oncommand",
"oncommand",
"TabView.moveTabTo(TabContextMenu.contextTab,'" + groupItem.id + "')");
return menuItem;

View File

@ -545,5 +545,10 @@ statuspanel[inactive][previoustype=overLink] {
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#promobox");
}
/* tabview menus */
.tabview-menuitem {
max-width: 32em;
}
/* highlighter */
%include highlighter.css

View File

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
let EXPORTED_SYMBOLS = [ "GcliCommands" ];
let EXPORTED_SYMBOLS = [ ];
Components.utils.import("resource:///modules/devtools/gcli.jsm");
Components.utils.import("resource:///modules/HUDService.jsm");

View File

@ -65,7 +65,7 @@ var mozl10n = {};
})(mozl10n);
define('gcli/index', ['require', 'exports', 'module' , 'gcli/types/basic', 'gcli/types/command', 'gcli/types/javascript', 'gcli/types/node', 'gcli/types/resource', 'gcli/types/setting', 'gcli/types/selection', 'gcli/settings', 'gcli/ui/intro', 'gcli/ui/focus', 'gcli/ui/fields/basic', 'gcli/ui/fields/javascript', 'gcli/ui/fields/selection', 'gcli/commands/help', 'gcli/canon', 'gcli/ui/ffdisplay'], function(require, exports, module) {
define('gcli/index', ['require', 'exports', 'module' , 'gcli/types/basic', 'gcli/types/command', 'gcli/types/javascript', 'gcli/types/node', 'gcli/types/resource', 'gcli/types/setting', 'gcli/types/selection', 'gcli/settings', 'gcli/ui/intro', 'gcli/ui/focus', 'gcli/ui/fields/basic', 'gcli/ui/fields/javascript', 'gcli/ui/fields/selection', 'gcli/commands/help', 'gcli/commands/pref', 'gcli/canon', 'gcli/ui/ffdisplay'], function(require, exports, module) {
// Internal startup process. Not exported
require('gcli/types/basic').startup();
@ -84,6 +84,7 @@ define('gcli/index', ['require', 'exports', 'module' , 'gcli/types/basic', 'gcli
require('gcli/ui/fields/selection').startup();
require('gcli/commands/help').startup();
require('gcli/commands/pref').startup();
// The API for use by command authors
exports.addCommand = require('gcli/canon').addCommand;
@ -1674,7 +1675,7 @@ exports.Speller = Speller;
* http://opensource.org/licenses/BSD-3-Clause
*/
define('gcli/types/selection', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types', 'gcli/types/spell', 'gcli/argument'], function(require, exports, module) {
define('gcli/types/selection', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types', 'gcli/types/spell'], function(require, exports, module) {
var l10n = require('gcli/l10n');
@ -1683,7 +1684,6 @@ var Type = require('gcli/types').Type;
var Status = require('gcli/types').Status;
var Conversion = require('gcli/types').Conversion;
var Speller = require('gcli/types/spell').Speller;
var Argument = require('gcli/argument').Argument;
/**
@ -1714,6 +1714,9 @@ exports.shutdown = function() {
* the associated name. However the name maybe available directly from the
* value using a property lookup. Setting 'stringifyProperty' allows
* SelectionType to take this shortcut.
* - cacheable : If lookup is a function, then we normally assume that
* the values fetched can change. Setting 'cacheable' enables internal
* caching.
*/
function SelectionType(typeSpec) {
if (typeSpec) {
@ -1743,14 +1746,30 @@ SelectionType.prototype.stringify = function(value) {
return name;
};
/**
* If typeSpec contained cacheable:true then calls to parse() work on cached
* data. clearCache() enables the cache to be cleared.
*/
SelectionType.prototype.clearCache = function() {
delete this._cachedLookup;
};
/**
* There are several ways to get selection data. This unifies them into one
* single function.
* @return An array of objects with name and value properties.
*/
SelectionType.prototype.getLookup = function() {
if (this._cachedLookup) {
return this._cachedLookup;
}
if (this.lookup) {
if (typeof this.lookup === 'function') {
if (this.cacheable) {
this._cachedLookup = this.lookup();
return this._cachedLookup;
}
return this.lookup();
}
return this.lookup;
@ -2257,8 +2276,6 @@ Parameter.prototype.isKnownAs = function(name) {
* parseString on an empty string
*/
Parameter.prototype.getBlank = function() {
var conversion;
if (this.type.getBlank) {
return this.type.getBlank();
}
@ -2357,13 +2374,19 @@ canon.addCommand = function addCommand(commandSpec) {
/**
* Remove an individual command. The opposite of #addCommand().
* Removing a non-existent command is a no-op.
* @param commandOrName Either a command name or the command itself.
* @return true if a command was removed, false otherwise.
*/
canon.removeCommand = function removeCommand(commandOrName) {
var name = typeof commandOrName === 'string' ?
commandOrName :
commandOrName.name;
if (!commands[name]) {
return false;
}
// See start of canon.addCommand if changing this code
delete commands[name];
delete commandSpecs[name];
@ -2372,6 +2395,7 @@ canon.removeCommand = function removeCommand(commandOrName) {
});
canon.onCanonChange();
return true;
};
/**
@ -2941,6 +2965,56 @@ exports.createUrlLookup = function(callingModule) {
};
};
/**
* Helper to find the 'data-command' attribute and call some action on it.
* @see |updateCommand()| and |executeCommand()|
*/
function withCommand(element, action) {
var command = element.getAttribute('data-command');
if (!command) {
command = element.querySelector('*[data-command]')
.getAttribute('data-command');
}
if (command) {
action(command);
}
else {
console.warn('Missing data-command for ' + util.findCssSelector(element));
}
}
/**
* Update the requisition to contain the text of the clicked element
* @param element The clicked element, containing either a data-command
* attribute directly or in a nested element, from which we get the command
* to be executed.
* @param context Either a Requisition or an ExecutionContext or another object
* that contains an |update()| function that follows a similar contract.
*/
exports.updateCommand = function(element, context) {
withCommand(element, function(command) {
context.update(command);
});
};
/**
* Execute the text contained in the element that was clicked
* @param element The clicked element, containing either a data-command
* attribute directly or in a nested element, from which we get the command
* to be executed.
* @param context Either a Requisition or an ExecutionContext or another object
* that contains an |update()| function that follows a similar contract.
*/
exports.executeCommand = function(element, context) {
withCommand(element, function(command) {
context.exec({
visible: true,
typed: command
});
});
};
//------------------------------------------------------------------------------
@ -4136,9 +4210,13 @@ function SettingType(typeSpec) {
if (Object.keys(typeSpec).length > 0) {
throw new Error('SettingType can not be customized');
}
settings.onChange.add(function(ev) {
this.clearCache();
}, this);
}
SettingType.prototype = Object.create(SelectionType.prototype);
SettingType.prototype = new SelectionType({ cacheable: true });
SettingType.prototype.lookup = function() {
return settings.getAll().map(function(setting) {
@ -4219,8 +4297,7 @@ var types = require('gcli/types');
var allSettings = [];
/**
* No setup required because settings are pre-loaded with Mozilla,
* but match API with main settings.js
* Cache existing settings on startup
*/
exports.startup = function() {
imports.prefBranch.getChildList('').forEach(function(name) {
@ -4304,9 +4381,8 @@ Object.defineProperty(Setting.prototype, 'value', {
case imports.prefBranch.PREF_STRING:
var value = imports.prefBranch.getComplexValue(this.name,
Components.interfaces.nsISupportsString).data;
// Try in case it's a localized string (will throw an exception if not)
var isL10n = /^chrome:\/\/.+\/locale\/.+\.properties/.test(value);
if (!this.changed && isL10n) {
// In case of a localized string
if (/^chrome:\/\/.+\/locale\/.+\.properties/.test(value)) {
value = imports.prefBranch.getComplexValue(this.name,
Components.interfaces.nsIPrefLocalizedString).data;
}
@ -4348,6 +4424,13 @@ Object.defineProperty(Setting.prototype, 'value', {
enumerable: true
});
/**
* Reset this setting to it's initial default value
*/
Setting.prototype.setDefault = function() {
imports.prefBranch.clearUserPref(this.name);
Services.prefs.savePrefFile(null);
};
/**
* 'static' function to get an array containing all known Settings
@ -4371,9 +4454,36 @@ exports.addSetting = function(prefSpec) {
allSettings[i] = setting;
}
}
exports.onChange({ added: setting.name });
return setting;
};
/**
* Getter for an existing setting. Generally use of this function should be
* avoided. Systems that define a setting should export it if they wish it to
* be available to the outside, or not otherwise. Use of this function breaks
* that boundary and also hides dependencies. Acceptable uses include testing
* and embedded uses of GCLI that pre-define all settings (e.g. Firefox)
* @param name The name of the setting to fetch
* @return The found Setting object, or undefined if the setting was not found
*/
exports.getSetting = function(name) {
var found = undefined;
allSettings.some(function(setting) {
if (setting.name === name) {
found = setting;
return true;
}
return false;
});
return found;
};
/**
* Event for use to detect when the list of settings changes
*/
exports.onChange = util.createEvent('Settings.onChange');
/**
* Remove a setting. A no-op in this case
*/
@ -4388,10 +4498,11 @@ exports.removeSetting = function(nameOrSpec) {
* http://opensource.org/licenses/BSD-3-Clause
*/
define('gcli/ui/intro', ['require', 'exports', 'module' , 'gcli/settings', 'gcli/l10n', 'gcli/ui/view', 'gcli/cli', 'text!gcli/ui/intro.html'], function(require, exports, module) {
define('gcli/ui/intro', ['require', 'exports', 'module' , 'gcli/settings', 'gcli/l10n', 'gcli/util', 'gcli/ui/view', 'gcli/cli', 'text!gcli/ui/intro.html'], function(require, exports, module) {
var settings = require('gcli/settings');
var l10n = require('gcli/l10n');
var util = require('gcli/util');
var view = require('gcli/ui/view');
var Output = require('gcli/cli').Output;
@ -4421,7 +4532,7 @@ define('gcli/ui/intro', ['require', 'exports', 'module' , 'gcli/settings', 'gcli
/**
* Called when the UI is ready to add a welcome message to the output
*/
exports.maybeShowIntro = function(commandOutputManager) {
exports.maybeShowIntro = function(commandOutputManager, context) {
if (hideIntro.value) {
return;
}
@ -4429,18 +4540,33 @@ define('gcli/ui/intro', ['require', 'exports', 'module' , 'gcli/settings', 'gcli
var output = new Output();
commandOutputManager.onOutput({ output: output });
var viewData = view.createView({
var viewData = this.createView(context, output);
output.complete(viewData);
};
/**
* Called when the UI is ready to add a welcome message to the output
*/
exports.createView = function(context, output) {
return view.createView({
html: require('text!gcli/ui/intro.html'),
options: { stack: 'intro.html' },
data: {
showHideButton: true,
l10n: l10n.propertyLookup,
onclick: function(ev) {
util.updateCommand(ev.currentTarget, context);
},
ondblclick: function(ev) {
util.executeCommand(ev.currentTarget, context);
},
showHideButton: (output != null),
onGotIt: function(ev) {
hideIntro.value = true;
output.onClose();
}
}
});
output.complete(viewData);
};
});
/*
@ -6097,6 +6223,12 @@ Output.prototype.toDom = function(element) {
util.setContents(node, output.toString());
}
// Make sure that links open in a new window.
var links = node.querySelectorAll('*[href]');
for (var i = 0; i < links.length; i++) {
links[i].setAttribute('target', '_blank');
}
element.appendChild(node);
};
@ -6155,17 +6287,16 @@ define('gcli/promise', ['require', 'exports', 'module' ], function(require, expo
});
define("text!gcli/ui/intro.html", [], "\n" +
"<div>\n" +
" <p>${l10n.introTextOpening}</p>\n" +
"\n" +
" <p>\n" +
" GCLI is an experiment to create a highly usable <strong>graphical command\n" +
" line</strong> for developers. It's not a JavaScript\n" +
" <a href=\"https://en.wikipedia.org/wiki/Read<61>eval<61>print_loop\">REPL</a>, so\n" +
" it focuses on speed of input over JavaScript syntax and a rich display over\n" +
" monospace output.</p>\n" +
" ${l10n.introTextCommands}\n" +
" <span class=\"gcli-out-shortcut\" onclick=\"${onclick}\"\n" +
" ondblclick=\"${ondblclick}\" data-command=\"help\">help</span>,\n" +
" ${l10n.introTextKeys} <code>${l10n.introTextF1Escape}</code>.\n" +
" </p>\n" +
"\n" +
" <p>Type <span class=\"gcli-out-shortcut\">help</span> for a list of commands,\n" +
" or press <code>F1/Escape</code> to show/hide command hints.</p>\n" +
"\n" +
" <button onclick=\"${onGotIt}\" if=\"${showHideButton}\">Got it!</button>\n" +
" <button onclick=\"${onGotIt}\" if=\"${showHideButton}\">${l10n.introTextGo}</button>\n" +
"</div>\n" +
"");
@ -7729,12 +7860,13 @@ SelectionTooltipField.DEFAULT_VALUE = '__SelectionTooltipField.DEFAULT_VALUE';
* http://opensource.org/licenses/BSD-3-Clause
*/
define('gcli/commands/help', ['require', 'exports', 'module' , 'gcli/canon', 'gcli/l10n', 'gcli/ui/view', 'text!gcli/commands/help_man.html', 'text!gcli/commands/help_list.html', 'text!gcli/commands/help.css'], function(require, exports, module) {
define('gcli/commands/help', ['require', 'exports', 'module' , 'gcli/canon', 'gcli/l10n', 'gcli/util', 'gcli/ui/view', 'text!gcli/commands/help_man.html', 'text!gcli/commands/help_list.html', 'text!gcli/commands/help.css'], function(require, exports, module) {
var help = exports;
var canon = require('gcli/canon');
var l10n = require('gcli/l10n');
var util = require('gcli/util');
var view = require('gcli/ui/view');
// Storing the HTML on exports allows other builds to alter the help template
@ -7794,26 +7926,6 @@ help.shutdown = function() {
canon.removeCommand(helpCommandSpec);
};
/**
* Find an element within the passed element with the class gcli-help-command
* and update the requisition to contain this text.
*/
function updateCommand(element, context) {
var typed = element.querySelector('.gcli-help-command').textContent;
context.update(typed);
}
/**
* Find an element within the passed element with the class gcli-help-command
* and execute this text.
*/
function executeCommand(element, context) {
context.exec({
visible: true,
typed: element.querySelector('.gcli-help-command').textContent
});
}
/**
* Create a block of data suitable to be passed to the help_list.html template
*/
@ -7823,11 +7935,11 @@ function getListTemplateData(args, context) {
includeIntro: args.search == null,
onclick: function(ev) {
updateCommand(ev.currentTarget, context);
util.updateCommand(ev.currentTarget, context);
},
ondblclick: function(ev) {
executeCommand(ev.currentTarget, context);
util.executeCommand(ev.currentTarget, context);
},
getHeading: function() {
@ -7867,11 +7979,18 @@ function getManTemplateData(command, context) {
command: command,
onclick: function(ev) {
updateCommand(ev.currentTarget, context);
util.updateCommand(ev.currentTarget, context);
},
ondblclick: function(ev) {
executeCommand(ev.currentTarget, context);
util.executeCommand(ev.currentTarget, context);
},
describe: function(item, element) {
var text = item.manual || item.description;
var parent = element.ownerDocument.createElement('div');
util.setContents(parent, text);
return parent.childNodes;
},
getTypeDescription: function(param) {
@ -7911,8 +8030,8 @@ define("text!gcli/commands/help_man.html", [], "\n" +
"\n" +
" <h4 class=\"gcli-help-header\">\n" +
" ${l10n.helpManSynopsis}:\n" +
" <span class=\"gcli-help-synopsis\" onclick=\"${onclick}\">\n" +
" <span class=\"gcli-help-command\">${command.name}</span>\n" +
" <span class=\"gcli-out-shortcut\" onclick=\"${onclick}\" data-command=\"${command.name}\">\n" +
" ${command.name}\n" +
" <span foreach=\"param in ${command.params}\">\n" +
" ${param.defaultValue !== undefined ? '[' + param.name + ']' : param.name}\n" +
" </span>\n" +
@ -7921,9 +8040,7 @@ define("text!gcli/commands/help_man.html", [], "\n" +
"\n" +
" <h4 class=\"gcli-help-header\">${l10n.helpManDescription}:</h4>\n" +
"\n" +
" <p class=\"gcli-help-description\">\n" +
" ${command.manual || command.description}\n" +
" </p>\n" +
" <p class=\"gcli-help-description\">${describe(command, __element)}</p>\n" +
"\n" +
" <div if=\"${command.exec}\">\n" +
" <h4 class=\"gcli-help-header\">${l10n.helpManParameters}:</h4>\n" +
@ -7931,9 +8048,9 @@ define("text!gcli/commands/help_man.html", [], "\n" +
" <ul class=\"gcli-help-parameter\">\n" +
" <li if=\"${command.params.length === 0}\">${l10n.helpManNone}</li>\n" +
" <li foreach=\"param in ${command.params}\">\n" +
" <tt>${param.name}</tt> ${getTypeDescription(param)}\n" +
" ${param.name} <em>${getTypeDescription(param)}</em>\n" +
" <br/>\n" +
" ${param.manual || param.description}\n" +
" ${describe(param, __element)}\n" +
" </li>\n" +
" </ul>\n" +
" </div>\n" +
@ -7946,8 +8063,9 @@ define("text!gcli/commands/help_man.html", [], "\n" +
" <li foreach=\"subcommand in ${subcommands}\">\n" +
" <strong>${subcommand.name}</strong>:\n" +
" ${subcommand.description}\n" +
" <span class=\"gcli-help-synopsis\" onclick=\"${onclick}\" ondblclick=\"${ondblclick}\">\n" +
" <span class=\"gcli-help-command\">help ${subcommand.name}</span>\n" +
" <span class=\"gcli-out-shortcut\" data-command=\"help ${subcommand.name}\"\n" +
" onclick=\"${onclick}\" ondblclick=\"${ondblclick}\">\n" +
" help ${subcommand.name}\n" +
" </span>\n" +
" </li>\n" +
" </ul>\n" +
@ -7967,7 +8085,7 @@ define("text!gcli/commands/help_list.html", [], "\n" +
" <td class=\"gcli-help-arrow\">&#x2192;</td>\n" +
" <td>\n" +
" ${command.description}\n" +
" <span class=\"gcli-out-shortcut gcli-help-command\">help ${command.name}</span>\n" +
" <span class=\"gcli-out-shortcut\" data-command=\"help ${command.name}\">help ${command.name}</span>\n" +
" </td>\n" +
" </tr>\n" +
" </table>\n" +
@ -7976,6 +8094,151 @@ define("text!gcli/commands/help_list.html", [], "\n" +
define("text!gcli/commands/help.css", [], "");
/*
* 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('gcli/commands/pref', ['require', 'exports', 'module' , 'gcli/canon', 'gcli/l10n', 'gcli/settings', 'text!gcli/commands/pref_set_check.html'], function(require, exports, module) {
var canon = require('gcli/canon');
var l10n = require('gcli/l10n');
var settings = require('gcli/settings');
/**
* Record if the user has clicked on 'Got It!'
*/
var allowSetSettingSpec = {
name: 'allowSet',
type: 'boolean',
description: l10n.lookup('allowSetDesc'),
defaultValue: false
};
exports.allowSet = undefined;
/**
* 'pref' command
*/
var prefCmdSpec = {
name: 'pref',
description: l10n.lookup('prefDesc'),
manual: l10n.lookup('prefManual')
};
/**
* 'pref show' command
*/
var prefShowCmdSpec = {
name: 'pref show',
description: l10n.lookup('prefShowDesc'),
manual: l10n.lookup('prefShowManual'),
params: [
{
name: 'setting',
type: 'setting',
description: l10n.lookup('prefShowSettingDesc'),
manual: l10n.lookup('prefShowSettingManual')
}
],
exec: function Command_prefShow(args, context) {
return args.setting.value;
}
};
/**
* 'pref set' command
*/
var prefSetCmdSpec = {
name: 'pref set',
description: l10n.lookup('prefSetDesc'),
manual: l10n.lookup('prefSetManual'),
params: [
{
name: 'setting',
type: 'setting',
description: l10n.lookup('prefSetSettingDesc'),
manual: l10n.lookup('prefSetSettingManual')
},
{
name: 'value',
type: 'settingValue',
description: l10n.lookup('prefSetValueDesc'),
manual: l10n.lookup('prefSetValueManual')
}
],
exec: function Command_prefSet(args, context) {
if (!exports.allowSet.value &&
args.setting.name !== exports.allowSet.name) {
return context.createView({
html: require('text!gcli/commands/pref_set_check.html'),
options: { allowEval: true, stack: 'pref_set_check.html' },
data: {
l10n: l10n.propertyLookup,
activate: function() {
context.exec('pref set ' + exports.allowSet.name + ' true');
}
},
});
}
args.setting.value = args.value;
return null;
}
};
/**
* 'pref reset' command
*/
var prefResetCmdSpec = {
name: 'pref reset',
description: l10n.lookup('prefResetDesc'),
manual: l10n.lookup('prefResetManual'),
params: [
{
name: 'setting',
type: 'setting',
description: l10n.lookup('prefResetSettingDesc'),
manual: l10n.lookup('prefResetSettingManual')
}
],
exec: function Command_prefReset(args, context) {
args.setting.setDefault();
return null;
}
};
/**
* Registration and de-registration.
*/
exports.startup = function() {
exports.allowSet = settings.addSetting(allowSetSettingSpec);
canon.addCommand(prefCmdSpec);
canon.addCommand(prefShowCmdSpec);
canon.addCommand(prefSetCmdSpec);
canon.addCommand(prefResetCmdSpec);
};
exports.shutdown = function() {
canon.removeCommand(prefCmdSpec);
canon.removeCommand(prefShowCmdSpec);
canon.removeCommand(prefSetCmdSpec);
canon.removeCommand(prefResetCmdSpec);
settings.removeSetting(allowSetSettingSpec);
exports.allowSet = undefined;
};
});
define("text!gcli/commands/pref_set_check.html", [], "<div>\n" +
" <p><strong>${l10n.prefSetCheckHeading}</strong></p>\n" +
" <p>${l10n.prefSetCheckBody}</p>\n" +
" <button onclick=\"${activate}\">${l10n.prefSetCheckGo}</button>\n" +
"</div>\n" +
"");
/*
* Copyright 2009-2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE.txt or:

View File

@ -1,18 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"
[
<!ENTITY % webConsoleDTD SYSTEM "chrome://browser/locale/devtools/webConsole.dtd">
%webConsoleDTD;
]
>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<!-- 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/. -->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" href="chrome://global/skin/global.css" type="text/css"/>

View File

@ -1,18 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"
[
<!ENTITY % webConsoleDTD SYSTEM "chrome://browser/locale/devtools/webConsole.dtd">
%webConsoleDTD;
]
>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<!-- 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/. -->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" href="chrome://global/skin/global.css" type="text/css"/>

View File

@ -17,6 +17,8 @@ _BROWSER_TEST_FILES = \
browser_gcli_commands.js \
browser_gcli_inspect.js \
browser_gcli_integrate.js \
browser_gcli_pref.js \
browser_gcli_settings.js \
browser_gcli_web.js \
head.js \
$(NULL)

View File

@ -0,0 +1,414 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that the pref commands work
let imports = {};
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm", imports);
imports.XPCOMUtils.defineLazyGetter(imports, "prefBranch", function() {
let prefService = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
return prefService.getBranch(null)
.QueryInterface(Components.interfaces.nsIPrefBranch2);
});
imports.XPCOMUtils.defineLazyGetter(imports, "supportsString", function() {
return Components.classes["@mozilla.org/supports-string;1"]
.createInstance(Components.interfaces.nsISupportsString);
});
const TEST_URI = "data:text/html;charset=utf-8,gcli-pref";
function test() {
DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
setup();
testPrefSetEnable();
testPrefStatus();
testPrefBoolExec();
testPrefNumberExec();
testPrefStringExec();
testPrefSetDisable();
shutdown();
finish();
});
}
let tiltEnabledOrig = undefined;
let tabSizeOrig = undefined;
let remoteHostOrig = undefined;
function setup() {
Components.utils.import("resource:///modules/devtools/Require.jsm", imports);
imports.settings = imports.require("gcli/settings");
tiltEnabledOrig = imports.prefBranch.getBoolPref("devtools.tilt.enabled");
tabSizeOrig = imports.prefBranch.getIntPref("devtools.editor.tabsize");
remoteHostOrig = imports.prefBranch.getComplexValue(
"devtools.debugger.remote-host",
Components.interfaces.nsISupportsString).data;
info("originally: devtools.tilt.enabled = " + tiltEnabledOrig);
info("originally: devtools.editor.tabsize = " + tabSizeOrig);
info("originally: devtools.debugger.remote-host = " + remoteHostOrig);
}
function shutdown() {
imports.prefBranch.setBoolPref("devtools.tilt.enabled", tiltEnabledOrig);
imports.prefBranch.setIntPref("devtools.editor.tabsize", tabSizeOrig);
imports.supportsString.data = remoteHostOrig;
imports.prefBranch.setComplexValue("devtools.debugger.remote-host",
Components.interfaces.nsISupportsString,
imports.supportsString);
tiltEnabledOrig = undefined;
tabSizeOrig = undefined;
remoteHostOrig = undefined;
imports = undefined;
}
function testPrefStatus() {
DeveloperToolbarTest.checkInputStatus({
typed: "pref s",
markup: "IIIIVI",
status: "ERROR",
directTabText: "et"
});
DeveloperToolbarTest.checkInputStatus({
typed: "pref show",
markup: "VVVVVVVVV",
status: "ERROR",
emptyParameters: [ " <setting>" ]
});
DeveloperToolbarTest.checkInputStatus({
typed: "pref show tempTBo",
markup: "VVVVVVVVVVEEEEEEE",
status: "ERROR",
emptyParameters: [ ]
});
DeveloperToolbarTest.checkInputStatus({
typed: "pref show devtools.toolbar.ena",
markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
directTabText: "bled",
status: "ERROR",
emptyParameters: [ ]
});
DeveloperToolbarTest.checkInputStatus({
typed: "pref show hideIntro",
markup: "VVVVVVVVVVVVVVVVVVV",
directTabText: "",
arrowTabText: "devtools.gcli.hideIntro",
status: "ERROR",
emptyParameters: [ ]
});
DeveloperToolbarTest.checkInputStatus({
typed: "pref show devtools.toolbar.enabled",
markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
status: "VALID",
emptyParameters: [ ]
});
DeveloperToolbarTest.checkInputStatus({
typed: "pref show devtools.tilt.enabled 4",
markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVE",
directTabText: "",
status: "ERROR",
emptyParameters: [ ]
});
DeveloperToolbarTest.checkInputStatus({
typed: "pref show devtools.tilt.enabled",
markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
status: "VALID",
emptyParameters: [ ]
});
DeveloperToolbarTest.checkInputStatus({
typed: "pref reset devtools.tilt.enabled",
markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
status: "VALID",
emptyParameters: [ ]
});
DeveloperToolbarTest.checkInputStatus({
typed: "pref set devtools.tilt.enabled 4",
markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVE",
status: "ERROR",
emptyParameters: [ ]
});
DeveloperToolbarTest.checkInputStatus({
typed: "pref set devtools.editor.tabsize 4",
markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
status: "VALID",
emptyParameters: [ ]
});
DeveloperToolbarTest.checkInputStatus({
typed: "pref list",
markup: "EEEEVEEEE",
status: "ERROR",
emptyParameters: [ ]
});
}
function testPrefSetEnable() {
DeveloperToolbarTest.exec({
typed: "pref set devtools.editor.tabsize 9",
args: {
setting: imports.settings.getSetting("devtools.editor.tabsize"),
value: 9
},
completed: true,
outputMatch: [ /void your warranty/, /I promise/ ],
});
is(imports.prefBranch.getIntPref("devtools.editor.tabsize"),
tabSizeOrig,
"devtools.editor.tabsize is unchanged");
DeveloperToolbarTest.exec({
typed: "pref set devtools.gcli.allowSet true",
args: {
setting: imports.settings.getSetting("devtools.gcli.allowSet"),
value: true
},
completed: true,
blankOutput: true,
});
is(imports.prefBranch.getBoolPref("devtools.gcli.allowSet"), true,
"devtools.gcli.allowSet is true");
DeveloperToolbarTest.exec({
typed: "pref set devtools.editor.tabsize 10",
args: {
setting: imports.settings.getSetting("devtools.editor.tabsize"),
value: 10
},
completed: true,
blankOutput: true,
});
is(imports.prefBranch.getIntPref("devtools.editor.tabsize"),
10,
"devtools.editor.tabsize is 10");
}
function testPrefBoolExec() {
DeveloperToolbarTest.exec({
typed: "pref show devtools.tilt.enabled",
args: {
setting: imports.settings.getSetting("devtools.tilt.enabled")
},
completed: true,
outputMatch: new RegExp("^" + tiltEnabledOrig + "$"),
});
DeveloperToolbarTest.exec({
typed: "pref set devtools.tilt.enabled true",
args: {
setting: imports.settings.getSetting("devtools.tilt.enabled"),
value: true
},
completed: true,
blankOutput: true,
});
is(imports.prefBranch.getBoolPref("devtools.tilt.enabled"), true,
"devtools.tilt.enabled is true");
DeveloperToolbarTest.exec({
typed: "pref show devtools.tilt.enabled",
args: {
setting: imports.settings.getSetting("devtools.tilt.enabled")
},
completed: true,
outputMatch: new RegExp("^true$"),
});
DeveloperToolbarTest.exec({
typed: "pref set devtools.tilt.enabled false",
args: {
setting: imports.settings.getSetting("devtools.tilt.enabled"),
value: false
},
completed: true,
blankOutput: true,
});
DeveloperToolbarTest.exec({
typed: "pref show devtools.tilt.enabled",
args: {
setting: imports.settings.getSetting("devtools.tilt.enabled")
},
completed: true,
outputMatch: new RegExp("^false$"),
});
is(imports.prefBranch.getBoolPref("devtools.tilt.enabled"), false,
"devtools.tilt.enabled is false");
}
function testPrefNumberExec() {
DeveloperToolbarTest.exec({
typed: "pref show devtools.editor.tabsize",
args: {
setting: imports.settings.getSetting("devtools.editor.tabsize")
},
completed: true,
outputMatch: new RegExp("^10$"),
});
DeveloperToolbarTest.exec({
typed: "pref set devtools.editor.tabsize 20",
args: {
setting: imports.settings.getSetting("devtools.editor.tabsize"),
value: 20
},
completed: true,
blankOutput: true,
});
DeveloperToolbarTest.exec({
typed: "pref show devtools.editor.tabsize",
args: {
setting: imports.settings.getSetting("devtools.editor.tabsize")
},
completed: true,
outputMatch: new RegExp("^20$"),
});
is(imports.prefBranch.getIntPref("devtools.editor.tabsize"), 20,
"devtools.editor.tabsize is 20");
DeveloperToolbarTest.exec({
typed: "pref set devtools.editor.tabsize 1",
args: {
setting: imports.settings.getSetting("devtools.editor.tabsize"),
value: true
},
completed: true,
blankOutput: true,
});
DeveloperToolbarTest.exec({
typed: "pref show devtools.editor.tabsize",
args: {
setting: imports.settings.getSetting("devtools.editor.tabsize")
},
completed: true,
outputMatch: new RegExp("^1$"),
});
is(imports.prefBranch.getIntPref("devtools.editor.tabsize"), 1,
"devtools.editor.tabsize is 1");
}
function testPrefStringExec() {
DeveloperToolbarTest.exec({
typed: "pref show devtools.debugger.remote-host",
args: {
setting: imports.settings.getSetting("devtools.debugger.remote-host")
},
completed: true,
outputMatch: new RegExp("^" + remoteHostOrig + "$"),
});
DeveloperToolbarTest.exec({
typed: "pref set devtools.debugger.remote-host e.com",
args: {
setting: imports.settings.getSetting("devtools.debugger.remote-host"),
value: "e.com"
},
completed: true,
blankOutput: true,
});
DeveloperToolbarTest.exec({
typed: "pref show devtools.debugger.remote-host",
args: {
setting: imports.settings.getSetting("devtools.debugger.remote-host")
},
completed: true,
outputMatch: new RegExp("^e.com$"),
});
var ecom = imports.prefBranch.getComplexValue(
"devtools.debugger.remote-host",
Components.interfaces.nsISupportsString).data;
is(ecom, "e.com", "devtools.debugger.remote-host is e.com");
DeveloperToolbarTest.exec({
typed: "pref set devtools.debugger.remote-host moz.foo",
args: {
setting: imports.settings.getSetting("devtools.debugger.remote-host"),
value: "moz.foo"
},
completed: true,
blankOutput: true,
});
DeveloperToolbarTest.exec({
typed: "pref show devtools.debugger.remote-host",
args: {
setting: imports.settings.getSetting("devtools.debugger.remote-host")
},
completed: true,
outputMatch: new RegExp("^moz.foo$"),
});
var mozfoo = imports.prefBranch.getComplexValue(
"devtools.debugger.remote-host",
Components.interfaces.nsISupportsString).data;
is(mozfoo, "moz.foo", "devtools.debugger.remote-host is moz.foo");
}
function testPrefSetDisable() {
DeveloperToolbarTest.exec({
typed: "pref set devtools.editor.tabsize 32",
args: {
setting: imports.settings.getSetting("devtools.editor.tabsize"),
value: 32
},
completed: true,
blankOutput: true,
});
is(imports.prefBranch.getIntPref("devtools.editor.tabsize"), 32,
"devtools.editor.tabsize is 32");
DeveloperToolbarTest.exec({
typed: "pref reset devtools.gcli.allowSet",
args: {
setting: imports.settings.getSetting("devtools.gcli.allowSet")
},
completed: true,
blankOutput: true,
});
is(imports.prefBranch.getBoolPref("devtools.gcli.allowSet"), false,
"devtools.gcli.allowSet is false");
DeveloperToolbarTest.exec({
typed: "pref set devtools.editor.tabsize 33",
args: {
setting: imports.settings.getSetting("devtools.editor.tabsize"),
value: 33
},
completed: true,
outputMatch: [ /void your warranty/, /I promise/ ],
});
is(imports.prefBranch.getIntPref("devtools.editor.tabsize"), 32,
"devtools.editor.tabsize is still 32");
}

View File

@ -0,0 +1,149 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that the pref commands work
let imports = {};
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm", imports);
imports.XPCOMUtils.defineLazyGetter(imports, "prefBranch", function() {
let prefService = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
return prefService.getBranch(null)
.QueryInterface(Components.interfaces.nsIPrefBranch2);
});
imports.XPCOMUtils.defineLazyGetter(imports, "supportsString", function() {
return Components.classes["@mozilla.org/supports-string;1"]
.createInstance(Components.interfaces.nsISupportsString);
});
const TEST_URI = "data:text/html;charset=utf-8,gcli-settings";
function test() {
DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
setup();
testSettings();
shutdown();
finish();
});
}
let tiltEnabled = undefined;
let tabSize = undefined;
let remoteHost = undefined;
let tiltEnabledOrig = undefined;
let tabSizeOrig = undefined;
let remoteHostOrig = undefined;
function setup() {
Components.utils.import("resource:///modules/devtools/Require.jsm", imports);
imports.settings = imports.require("gcli/settings");
tiltEnabled = imports.settings.getSetting("devtools.tilt.enabled");
tabSize = imports.settings.getSetting("devtools.editor.tabsize");
remoteHost = imports.settings.getSetting("devtools.debugger.remote-host");
tiltEnabledOrig = imports.prefBranch.getBoolPref("devtools.tilt.enabled");
tabSizeOrig = imports.prefBranch.getIntPref("devtools.editor.tabsize");
remoteHostOrig = imports.prefBranch.getComplexValue(
"devtools.debugger.remote-host",
Components.interfaces.nsISupportsString).data;
info("originally: devtools.tilt.enabled = " + tiltEnabledOrig);
info("originally: devtools.editor.tabsize = " + tabSizeOrig);
info("originally: devtools.debugger.remote-host = " + remoteHostOrig);
}
function shutdown() {
imports.prefBranch.setBoolPref("devtools.tilt.enabled", tiltEnabledOrig);
imports.prefBranch.setIntPref("devtools.editor.tabsize", tabSizeOrig);
imports.supportsString.data = remoteHostOrig;
imports.prefBranch.setComplexValue("devtools.debugger.remote-host",
Components.interfaces.nsISupportsString,
imports.supportsString);
tiltEnabled = undefined;
tabSize = undefined;
remoteHost = undefined;
tiltEnabledOrig = undefined;
tabSizeOrig = undefined;
remoteHostOrig = undefined;
imports = undefined;
}
function testSettings() {
is(tiltEnabled.value, tiltEnabledOrig, "tiltEnabled default");
is(tabSize.value, tabSizeOrig, "tabSize default");
is(remoteHost.value, remoteHostOrig, "remoteHost default");
tiltEnabled.setDefault();
tabSize.setDefault();
remoteHost.setDefault();
let tiltEnabledDefault = tiltEnabled.value;
let tabSizeDefault = tabSize.value;
let remoteHostDefault = remoteHost.value;
tiltEnabled.value = false;
tabSize.value = 42;
remoteHost.value = "example.com"
is(tiltEnabled.value, false, "tiltEnabled basic");
is(tabSize.value, 42, "tabSize basic");
is(remoteHost.value, "example.com", "remoteHost basic");
function tiltEnabledCheck(ev) {
is(ev.setting, tiltEnabled, "tiltEnabled event setting");
is(ev.value, true, "tiltEnabled event value");
is(ev.setting.value, true, "tiltEnabled event setting value");
}
tiltEnabled.onChange.add(tiltEnabledCheck);
tiltEnabled.value = true;
is(tiltEnabled.value, true, "tiltEnabled change");
function tabSizeCheck(ev) {
is(ev.setting, tabSize, "tabSize event setting");
is(ev.value, 1, "tabSize event value");
is(ev.setting.value, 1, "tabSize event setting value");
}
tabSize.onChange.add(tabSizeCheck);
tabSize.value = 1;
is(tabSize.value, 1, "tabSize change");
function remoteHostCheck(ev) {
is(ev.setting, remoteHost, "remoteHost event setting");
is(ev.value, "y.com", "remoteHost event value");
is(ev.setting.value, "y.com", "remoteHost event setting value");
}
remoteHost.onChange.add(remoteHostCheck);
remoteHost.value = "y.com";
is(remoteHost.value, "y.com", "remoteHost change");
tiltEnabled.onChange.remove(tiltEnabledCheck);
tabSize.onChange.remove(tabSizeCheck);
remoteHost.onChange.remove(remoteHostCheck);
function remoteHostReCheck(ev) {
is(ev.setting, remoteHost, "remoteHost event reset");
is(ev.value, null, "remoteHost event revalue");
is(ev.setting.value, null, "remoteHost event setting revalue");
}
remoteHost.onChange.add(remoteHostReCheck);
tiltEnabled.setDefault();
tabSize.setDefault();
remoteHost.setDefault();
remoteHost.onChange.remove(remoteHostReCheck);
is(tiltEnabled.value, tiltEnabledDefault, "tiltEnabled reset");
is(tabSize.value, tabSizeDefault, "tabSize reset");
is(remoteHost.value, remoteHostDefault, "remoteHost reset");
}

View File

@ -156,10 +156,11 @@ define('gclitest/suite', ['require', 'exports', 'module' , 'gcli/index', 'test/e
* http://opensource.org/licenses/BSD-3-Clause
*/
define('test/examiner', ['require', 'exports', 'module' , 'test/assert'], function(require, exports, module) {
define('test/examiner', ['require', 'exports', 'module' , 'test/assert', 'test/status'], function(require, exports, module) {
var examiner = exports;
var assert = require('test/assert');
var stati = require('test/status').stati;
/**
* Test harness data
@ -171,14 +172,6 @@ examiner.suites = {};
*/
var delay = 10;
var stati = {
notrun: { index: 0, name: 'Skipped' },
executing: { index: 1, name: 'Executing' },
asynchronous: { index: 2, name: 'Waiting' },
pass: { index: 3, name: 'Pass' },
fail: { index: 4, name: 'Fail' }
};
/**
* Add a test suite. Generally used like:
* test.addSuite('foo', require('path/to/foo'));
@ -580,6 +573,28 @@ define('test/assert', ['require', 'exports', 'module' ], function(require, expor
* http://opensource.org/licenses/BSD-3-Clause
*/
define('test/status', ['require', 'exports', 'module' ], function(require, exports, module) {
/**
* This should really be inside assert.js, however that is over-ridden by
* a custom assert.js for mozilla, so we keep it separate to avoid
* duplicating it in 2 places.
*/
exports.stati = {
notrun: { index: 0, name: 'Skipped' },
executing: { index: 1, name: 'Executing' },
asynchronous: { index: 2, name: 'Waiting' },
pass: { index: 3, name: 'Pass' },
fail: { index: 4, name: 'Fail' }
};
});
/*
* 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('gclitest/testCanon', ['require', 'exports', 'module' , 'gclitest/helpers', 'gcli/canon', 'test/assert'], function(require, exports, module) {
var helpers = require('gclitest/helpers');
@ -657,6 +672,14 @@ define('gclitest/testCanon', ['require', 'exports', 'module' , 'gclitest/helpers
status: 'ERROR'
});
canon.removeCommand({ name: 'nonexistant' });
test.is(canon.getCommands().length, startCount, 'nonexistant1 command success');
test.is(events, 5, 'nonexistant1 event');
canon.removeCommand('nonexistant');
test.is(canon.getCommands().length, startCount, 'nonexistant2 command success');
test.is(events, 5, 'nonexistant2 event');
canon.onCanonChange.remove(canonChange);
};
@ -2215,8 +2238,7 @@ define('gclitest/testIntro', ['require', 'exports', 'module' , 'gclitest/helpers
typed: 'intro',
args: { },
outputMatch: [
/graphical\s*command\s*line/,
/GCLI/,
/command\s*line/,
/help/,
/F1/,
/Escape/
@ -2610,9 +2632,69 @@ exports.shutdown = function(options) {
}
};
exports.testPrefShowStatus = function(options) {
if (options.isFirefox) {
test.log('Skipping testPrefShowStatus in Firefox.');
return;
}
helpers.status(options, {
typed: 'pref s',
markup: 'IIIIVI',
status: 'ERROR',
directTabText: 'et'
});
helpers.status(options, {
typed: 'pref show',
markup: 'VVVVVVVVV',
status: 'ERROR',
emptyParameters: [ ' <setting>' ]
});
helpers.status(options, {
typed: 'pref show ',
markup: 'VVVVVVVVVV',
status: 'ERROR',
emptyParameters: [ ]
});
helpers.status(options, {
typed: 'pref show tempTBo',
markup: 'VVVVVVVVVVIIIIIII',
directTabText: 'ol',
status: 'ERROR',
emptyParameters: [ ]
});
helpers.status(options, {
typed: 'pref show tempTBool',
markup: 'VVVVVVVVVVVVVVVVVVV',
directTabText: '',
status: 'VALID',
emptyParameters: [ ]
});
helpers.status(options, {
typed: 'pref show tempTBool 4',
markup: 'VVVVVVVVVVVVVVVVVVVVE',
directTabText: '',
status: 'ERROR',
emptyParameters: [ ]
});
helpers.status(options, {
typed: 'pref show tempNumber 4',
markup: 'VVVVVVVVVVVVVVVVVVVVVE',
directTabText: '',
status: 'ERROR',
emptyParameters: [ ]
});
};
exports.testPrefSetStatus = function(options) {
if (options.isFirefox) {
test.log('Skipping testPref in Firefox.');
test.log('Skipping testPrefSetStatus in Firefox.');
return;
}
@ -2643,13 +2725,6 @@ exports.testPrefSetStatus = function(options) {
emptyParameters: [ ' <value>' ]
});
helpers.status(options, {
typed: 'pref set ',
markup: 'VVVVVVVVV',
status: 'ERROR',
emptyParameters: [ ' <value>' ]
});
helpers.status(options, {
typed: 'pref set tempTBo',
markup: 'VVVVVVVVVIIIIIII',
@ -2677,7 +2752,7 @@ exports.testPrefSetStatus = function(options) {
exports.testPrefExec = function(options) {
if (options.isFirefox) {
test.log('Skipping testPref in Firefox.');
test.log('Skipping testPrefExec in Firefox.');
return;
}
@ -2738,313 +2813,6 @@ exports.testPrefExec = function(options) {
* http://opensource.org/licenses/BSD-3-Clause
*/
define('gcli/commands/pref', ['require', 'exports', 'module' , 'gcli/index', 'gcli/l10n', 'gcli/util', 'gcli/settings', 'gcli/promise', 'text!gcli/commands/pref_list_outer.html', 'text!gcli/commands/pref_list.css', 'text!gcli/commands/pref_set_check.html', 'text!gcli/commands/pref_list_inner.html'], function(require, exports, module) {
var gcli = require('gcli/index');
var l10n = require('gcli/l10n');
var util = require('gcli/util');
var settings = require('gcli/settings');
var Promise = require('gcli/promise').Promise;
/**
* Record if the user has clicked on 'Got It!'
*/
var allowSetSettingSpec = {
name: 'allowSet',
type: 'boolean',
description: l10n.lookup('allowSetDesc'),
defaultValue: false
};
exports.allowSet = undefined;
/**
* 'pref' command
*/
var prefCmdSpec = {
name: 'pref',
description: l10n.lookup('prefDesc'),
manual: l10n.lookup('prefManual')
};
/**
* 'pref list' command
*/
var prefListCmdSpec = {
name: 'pref list',
description: l10n.lookup('prefListDesc'),
manual: l10n.lookup('prefListManual'),
params: [
{
name: 'search',
type: 'string',
defaultValue: null,
description: l10n.lookup('prefListSearchDesc'),
manual: l10n.lookup('prefListSearchManual')
}
],
exec: function Command_prefList(args, context) {
return context.createView({
html: require('text!gcli/commands/pref_list_outer.html'),
data: new PrefList(args, context),
options: {
blankNullUndefined: true,
allowEval: true,
stack: 'pref_list_outer.html'
},
css: require('text!gcli/commands/pref_list.css'),
cssId: 'gcli-pref-list'
});
}
};
/**
* 'pref set' command
*/
var prefSetCmdSpec = {
name: 'pref set',
description: l10n.lookup('prefSetDesc'),
manual: l10n.lookup('prefSetManual'),
params: [
{
name: 'setting',
type: 'setting',
description: l10n.lookup('prefSetSettingDesc'),
manual: l10n.lookup('prefSetSettingManual')
},
{
name: 'value',
type: 'settingValue',
description: l10n.lookup('prefSetValueDesc'),
manual: l10n.lookup('prefSetValueManual')
}
],
exec: function Command_prefSet(args, context) {
if (!exports.allowSet.value &&
args.setting.name !== exports.allowSet.name) {
return context.createView({
html: require('text!gcli/commands/pref_set_check.html'),
options: { allowEval: true, stack: 'pref_set_check.html' },
data: {
l10n: l10n.propertyLookup,
activate: function() {
context.exec('pref set allowSet true');
}
},
});
}
args.setting.value = args.value;
return null;
}
};
/**
* 'pref reset' command
*/
var prefResetCmdSpec = {
name: 'pref reset',
description: l10n.lookup('prefResetDesc'),
manual: l10n.lookup('prefResetManual'),
params: [
{
name: 'setting',
type: 'setting',
description: l10n.lookup('prefResetSettingDesc'),
manual: l10n.lookup('prefResetSettingManual')
}
],
exec: function Command_prefReset(args, context) {
args.setting.setDefault();
return null;
}
};
/**
* Registration and de-registration.
*/
exports.startup = function() {
exports.allowSet = settings.addSetting(allowSetSettingSpec);
gcli.addCommand(prefCmdSpec);
gcli.addCommand(prefListCmdSpec);
gcli.addCommand(prefSetCmdSpec);
gcli.addCommand(prefResetCmdSpec);
};
exports.shutdown = function() {
gcli.removeCommand(prefCmdSpec);
gcli.removeCommand(prefListCmdSpec);
gcli.removeCommand(prefSetCmdSpec);
gcli.removeCommand(prefResetCmdSpec);
settings.removeSetting(allowSetSettingSpec);
exports.allowSet = undefined;
};
/**
* A manager for our version of about:config
*/
function PrefList(args, context) {
this.search = args.search;
this.context = context;
this.url = util.createUrlLookup(module);
this.edit = this.url('pref_list_edit.png');
}
/**
*
*/
PrefList.prototype.onLoad = function(element) {
var table = element.querySelector('.gcli-pref-list-table');
this.updateTable(table);
return '';
};
/**
* Forward localization lookups
*/
PrefList.prototype.l10n = l10n.propertyLookup;
/**
* Called from the template onkeyup for the filter element
*/
PrefList.prototype.updateTable = function(table) {
util.clearElement(table);
var view = this.context.createView({
html: require('text!gcli/commands/pref_list_inner.html'),
options: { blankNullUndefined: true, stack: 'pref_list_inner.html' },
data: this
});
var child = view.toDom(table.ownerDocument);
util.setContents(table, child);
};
/**
* Which preferences match the filter?
*/
Object.defineProperty(PrefList.prototype, 'preferences', {
get: function() {
return settings.getAll(this.search);
},
enumerable: true
});
/**
* Which preferences match the filter?
*/
Object.defineProperty(PrefList.prototype, 'promisePreferences', {
get: function() {
var promise = new Promise();
setTimeout(function() {
promise.resolve(settings.getAll(this.search));
}.bind(this), 10);
return promise;
},
enumerable: true
});
PrefList.prototype.onFilterChange = function(ev) {
if (ev.target.value !== this.search) {
this.search = ev.target.value;
var root = ev.target.parentNode.parentNode;
var table = root.querySelector('.gcli-pref-list-table');
this.updateTable(table);
}
};
PrefList.prototype.onSetClick = function(ev) {
var typed = ev.currentTarget.getAttribute('data-command');
this.context.update(typed);
};
});
define("text!gcli/commands/pref_list_outer.html", [], "<div ignore=\"${onLoad(__element)}\">\n" +
" <div class=\"gcli-pref-list-filter\">\n" +
" ${l10n.prefOutputFilter}:\n" +
" <input onKeyUp=\"${onFilterChange}\" value=\"${search}\"/>\n" +
" </div>\n" +
" <table class=\"gcli-pref-list-table\">\n" +
" <colgroup>\n" +
" <col class=\"gcli-pref-list-name\"/>\n" +
" <col class=\"gcli-pref-list-value\"/>\n" +
" </colgroup>\n" +
" <tr>\n" +
" <th>${l10n.prefOutputName}</th>\n" +
" <th>${l10n.prefOutputValue}</th>\n" +
" </tr>\n" +
" </table>\n" +
" <div class=\"gcli-pref-list-scroller\">\n" +
" <table class=\"gcli-pref-list-table\" save=\"${table}\">\n" +
" </table>\n" +
" </div>\n" +
"</div>\n" +
"");
define("text!gcli/commands/pref_list.css", [], "\n" +
".gcli-pref-list-scroller {\n" +
" max-height: 200px;\n" +
" overflow-y: auto;\n" +
" overflow-x: hidden;\n" +
" display: inline-block;\n" +
"}\n" +
"\n" +
".gcli-pref-list-table {\n" +
" width: 500px;\n" +
" table-layout: fixed;\n" +
"}\n" +
"\n" +
".gcli-pref-list-table tr > th {\n" +
" text-align: left;\n" +
"}\n" +
"\n" +
".gcli-pref-list-table tr > td {\n" +
" text-overflow: elipsis;\n" +
" word-wrap: break-word;\n" +
"}\n" +
"\n" +
".gcli-pref-list-name {\n" +
" width: 70%;\n" +
"}\n" +
"\n" +
".gcli-pref-list-command {\n" +
" display: none;\n" +
"}\n" +
"\n" +
".gcli-pref-list-row:hover .gcli-pref-list-command {\n" +
" display: inline-block;\n" +
"}\n" +
"");
define("text!gcli/commands/pref_set_check.html", [], "<div>\n" +
" <p><strong>${l10n.prefSetCheckHeading}</strong></p>\n" +
" <p>${l10n.prefSetCheckBody}</p>\n" +
" <button onclick=\"${activate}\">${l10n.prefSetCheckGo}</button>\n" +
"</div>\n" +
"");
define("text!gcli/commands/pref_list_inner.html", [], "<table>\n" +
" <colgroup>\n" +
" <col class=\"gcli-pref-list-name\"/>\n" +
" <col class=\"gcli-pref-list-value\"/>\n" +
" </colgroup>\n" +
" <tr class=\"gcli-pref-list-row\" foreach=\"preference in ${promisePreferences}\">\n" +
" <td>${preference.name}</td>\n" +
" <td onclick=\"${onSetClick}\" data-command=\"pref set ${preference.name} \">\n" +
" ${preference.value}\n" +
" <img class=\"gcli-pref-list-command\" _src=\"${edit}\"/>\n" +
" </td>\n" +
" </tr>\n" +
"</table>\n" +
"");
/*
* 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('gclitest/mockSettings', ['require', 'exports', 'module' , 'gcli/settings'], function(require, exports, module) {
@ -4109,6 +3877,7 @@ let testModuleNames = [
'gclitest/suite',
'test/examiner',
'test/assert',
'test/status',
'gclitest/testCanon',
'gclitest/helpers',
'gclitest/testCli',
@ -4122,11 +3891,6 @@ let testModuleNames = [
'gclitest/testJs',
'gclitest/testKeyboard',
'gclitest/testPref',
'gcli/commands/pref',
'text!gcli/commands/pref_list_outer.html',
'text!gcli/commands/pref_list.css',
'text!gcli/commands/pref_set_check.html',
'text!gcli/commands/pref_list_inner.html',
'gclitest/mockSettings',
'gclitest/testRequire',
'gclitest/requirable',

View File

@ -146,7 +146,8 @@ let DeveloperToolbarTest = {
*
* // Thing to check
* args: { message: "hi" }, // Check that the args were understood properly
* outputMatch: /^hi$/, // Regex to test against textContent of output
* outputMatch: /^hi$/, // RegExp to test against textContent of output
* // (can also be array of RegExps)
* blankOutput: true, // Special checks when there is no output
* });
*/
@ -201,10 +202,21 @@ let DeveloperToolbarTest = {
let displayed = DeveloperToolbar.outputPanel._div.textContent;
if (test.outputMatch) {
if (!test.outputMatch.test(displayed)) {
ok(false, "html output for " + typed + " (textContent sent to info)");
info("Actual textContent");
info(displayed);
function doTest(match, against) {
if (!match.test(against)) {
ok(false, "html output for " + typed + " against " + match.source +
" (textContent sent to info)");
info("Actual textContent");
info(against);
}
}
if (Array.isArray(test.outputMatch)) {
test.outputMatch.forEach(function(match) {
doTest(match, displayed);
});
}
else {
doTest(test.outputMatch, displayed);
}
}

View File

@ -418,9 +418,11 @@ StackFrames.prototype = {
_onFrames: function SF__onFrames() {
if (!this.activeThread.cachedFrames.length) {
DebuggerView.StackFrames.emptyText();
DebuggerView.Properties.emptyText();
return;
}
DebuggerView.StackFrames.empty();
DebuggerView.Properties.empty();
for each (let frame in this.activeThread.cachedFrames) {
this._addFrame(frame);
@ -451,8 +453,7 @@ StackFrames.prototype = {
_afterFramesCleared: function SF__afterFramesCleared() {
if (!this.activeThread.cachedFrames.length) {
DebuggerView.StackFrames.emptyText();
DebuggerView.Properties.localScope.empty();
DebuggerView.Properties.globalScope.empty();
DebuggerView.Properties.emptyText();
DebuggerController.dispatchEvent("Debugger:AfterFramesCleared");
}
},
@ -521,39 +522,77 @@ StackFrames.prototype = {
// Start recording any added variables or properties in any scope.
DebuggerView.Properties.createHierarchyStore();
// Display the local variables.
let localScope = DebuggerView.Properties.localScope;
localScope.empty();
// Add "this".
if (frame.this) {
let thisVar = localScope.addVar("this");
thisVar.setGrip({
type: frame.this.type,
class: frame.this.class
});
this._addExpander(thisVar, frame.this);
}
// Clear existing scopes and create each one dynamically.
DebuggerView.Properties.empty();
if (frame.environment) {
// Add nodes for every argument.
let variables = frame.environment.bindings.arguments;
for each (let variable in variables) {
let name = Object.getOwnPropertyNames(variable)[0];
let paramVar = localScope.addVar(name);
let paramVal = variable[name].value;
paramVar.setGrip(paramVal);
this._addExpander(paramVar, paramVal);
}
let env = frame.environment;
do {
// Construct the scope name.
let name = env.type.charAt(0).toUpperCase() + env.type.slice(1);
// Call the outermost scope Global.
if (!env.parent) {
name = L10N.getStr("globalScopeLabel");
}
let label = L10N.getFormatStr("scopeLabel", [name]);
switch (env.type) {
case "with":
case "object":
label += " [" + env.object.class + "]";
break;
case "function":
if (env.functionName) {
label += " [" + env.functionName + "]";
}
break;
default:
break;
}
// Add nodes for every other variable in scope.
variables = frame.environment.bindings.variables;
for (let variable in variables) {
let paramVar = localScope.addVar(variable);
let paramVal = variables[variable].value;
paramVar.setGrip(paramVal);
this._addExpander(paramVar, paramVal);
}
let scope = DebuggerView.Properties.addScope(label);
// Add "this" to the innermost scope.
if (frame.this && env == frame.environment) {
let thisVar = scope.addVar("this");
thisVar.setGrip({
type: frame.this.type,
class: frame.this.class
});
this._addExpander(thisVar, frame.this);
// Expand the innermost scope by default.
scope.expand(true);
scope.addToHierarchy();
}
switch (env.type) {
case "with":
case "object":
let objClient = this.activeThread.pauseGrip(env.object);
objClient.getPrototypeAndProperties(function SF_getProps(aResponse) {
this._addScopeVariables(aResponse.ownProperties, scope);
// Signal that variables have been fetched.
DebuggerController.dispatchEvent("Debugger:FetchedVariables");
}.bind(this));
break;
case "block":
case "function":
// Add nodes for every argument.
let variables = env.bindings.arguments;
for each (let variable in variables) {
let name = Object.getOwnPropertyNames(variable)[0];
let paramVar = scope.addVar(name);
let paramVal = variable[name].value;
paramVar.setGrip(paramVal);
this._addExpander(paramVar, paramVal);
}
// Add nodes for every other variable in scope.
this._addScopeVariables(env.bindings.variables, scope);
break;
default:
Cu.reportError("Unknown Debugger.Environment type: " + env.type);
break;
}
} while (env = env.parent);
}
// Signal that variables have been fetched.
@ -567,6 +606,31 @@ StackFrames.prototype = {
DebuggerView.Properties.commitHierarchy();
},
/**
* Add nodes for every variable in scope.
*
* @param object aVariables
* The map of names to variables, as specified in the Remote
* Debugging Protocol.
* @param object aScope
* The scope where the nodes will be placed into.
*/
_addScopeVariables: function SF_addScopeVariables(aVariables, aScope) {
// Sort all of the variables before adding them, for better UX.
let variables = {};
for each (let prop in Object.keys(aVariables).sort()) {
variables[prop] = aVariables[prop];
}
// Add the sorted variables to the specified scope.
for (let variable in variables) {
let paramVar = aScope.addVar(variable);
let paramVal = variables[variable].value;
paramVar.setGrip(paramVal);
this._addExpander(paramVar, paramVal);
}
},
/**
* Adds an 'onexpand' callback for a variable, lazily handling the addition of
* new properties.

View File

@ -760,12 +760,17 @@ StackFramesView.prototype = {
* Functions handling the properties view.
*/
function PropertiesView() {
this._addScope = this._addScope.bind(this);
this.addScope = this._addScope.bind(this);
this._addVar = this._addVar.bind(this);
this._addProperties = this._addProperties.bind(this);
}
PropertiesView.prototype = {
/**
* A monotonically-increasing counter, that guarantees the uniqueness of scope
* IDs.
*/
_idCount: 1,
/**
* Adds a scope to contain any inspected variables.
@ -786,8 +791,8 @@ PropertiesView.prototype = {
return null;
}
// Compute the id of the element if not specified.
aId = aId || (aName.toLowerCase().trim().replace(" ", "-") + "-scope");
// Generate a unique id for the element, if not specified.
aId = aId || aName.toLowerCase().trim().replace(/\s+/g, "-") + this._idCount++;
// Contains generic nodes and functionality.
let element = this._createPropertyElement(aName, aId, "scope", this._vars);
@ -796,16 +801,48 @@ PropertiesView.prototype = {
if (!element) {
return null;
}
element._identifier = aName;
/**
* @see DebuggerView.Properties._addVar
*/
element.addVar = this._addVar.bind(this, element);
/**
* @see DebuggerView.Properties.addScopeToHierarchy
*/
element.addToHierarchy = this.addScopeToHierarchy.bind(this, element);
// Return the element for later use if necessary.
return element;
},
/**
* Removes all added scopes in the property container tree.
*/
empty: function DVP_empty() {
while (this._vars.firstChild) {
this._vars.removeChild(this._vars.firstChild);
}
},
/**
* Removes all elements from the variables container, and adds a child node
* with an empty text note attached.
*/
emptyText: function DVP_emptyText() {
// Make sure the container is empty first.
this.empty();
let item = document.createElement("label");
// The empty node should look grayed out to avoid confusion.
item.className = "list-item empty";
item.setAttribute("value", L10N.getStr("emptyVariablesText"));
this._vars.appendChild(item);
},
/**
* Adds a variable to a specified scope.
* If the optional id is not specified, the variable html node will have a
@ -837,6 +874,7 @@ PropertiesView.prototype = {
if (!element) {
return null;
}
element._identifier = aName;
/**
* @see DebuggerView.Properties._setGrip
@ -1050,6 +1088,7 @@ PropertiesView.prototype = {
if (!element) {
return null;
}
element._identifier = aName;
/**
* @see DebuggerView.Properties._setGrip
@ -1382,16 +1421,22 @@ PropertiesView.prototype = {
/**
* Expands the element, showing all the added details.
*
* @param boolean aSkipAnimationFlag
* Pass true to not show an opening animation.
* @return object
* The same element.
*/
element.expand = function DVP_element_expand() {
element.expand = function DVP_element_expand(aSkipAnimationFlag) {
if (element._preventExpand) {
return;
}
arrow.setAttribute("open", "");
details.setAttribute("open", "");
if (!aSkipAnimationFlag) {
details.setAttribute("animated", "");
}
if ("function" === typeof element.onexpand) {
element.onexpand(element);
}
@ -1409,6 +1454,7 @@ PropertiesView.prototype = {
}
arrow.removeAttribute("open");
details.removeAttribute("open");
details.removeAttribute("animated");
if ("function" === typeof element.oncollapse) {
element.oncollapse(element);
@ -1621,7 +1667,7 @@ PropertiesView.prototype = {
children: {}
};
store[element.id] = relation;
store[element._identifier] = relation;
element._root = relation.root;
element._children = relation.children;
},
@ -1633,11 +1679,16 @@ PropertiesView.prototype = {
createHierarchyStore: function DVP_createHierarchyStore() {
this._prevHierarchy = this._currHierarchy;
this._currHierarchy = {};
},
this._saveHierarchy({ element: this._globalScope, store: this._currHierarchy });
this._saveHierarchy({ element: this._localScope, store: this._currHierarchy });
this._saveHierarchy({ element: this._withScope, store: this._currHierarchy });
this._saveHierarchy({ element: this._closureScope, store: this._currHierarchy });
/**
* Creates a hierarchy holder for a scope.
*
* @param object aScope
* The designated scope to track.
*/
addScopeToHierarchy: function DVP_addScopeToHierarchy(aScope) {
this._saveHierarchy({ element: aScope, store: this._currHierarchy });
},
/**
@ -1648,6 +1699,10 @@ PropertiesView.prototype = {
let currScope = this._currHierarchy[i];
let prevScope = this._prevHierarchy[i];
if (!prevScope) {
continue;
}
for (let v in currScope.children) {
let currVar = currScope.children[v];
let prevVar = prevScope.children[v];
@ -1672,7 +1727,7 @@ PropertiesView.prototype = {
window.setTimeout(function() {
currVar.element.removeAttribute(action);
}, PROPERTY_VIEW_FLASH_DURATION);
}, PROPERTY_VIEW_FLASH_DURATION);
}
}
}
@ -1685,103 +1740,11 @@ PropertiesView.prototype = {
_currHierarchy: null,
_prevHierarchy: null,
/**
* Returns the global scope container.
*/
get globalScope() {
return this._globalScope;
},
/**
* Sets the display mode for the global scope container.
*
* @param boolean aFlag
* False to hide the container, true to show.
*/
set globalScope(aFlag) {
if (aFlag) {
this._globalScope.show();
} else {
this._globalScope.hide();
}
},
/**
* Returns the local scope container.
*/
get localScope() {
return this._localScope;
},
/**
* Sets the display mode for the local scope container.
*
* @param boolean aFlag
* False to hide the container, true to show.
*/
set localScope(aFlag) {
if (aFlag) {
this._localScope.show();
} else {
this._localScope.hide();
}
},
/**
* Returns the with block scope container.
*/
get withScope() {
return this._withScope;
},
/**
* Sets the display mode for the with block scope container.
*
* @param boolean aFlag
* False to hide the container, true to show.
*/
set withScope(aFlag) {
if (aFlag) {
this._withScope.show();
} else {
this._withScope.hide();
}
},
/**
* Returns the closure scope container.
*/
get closureScope() {
return this._closureScope;
},
/**
* Sets the display mode for the with block scope container.
*
* @param boolean aFlag
* False to hide the container, true to show.
*/
set closureScope(aFlag) {
if (aFlag) {
this._closureScope.show();
} else {
this._closureScope.hide();
}
},
/**
* The cached variable properties container.
*/
_vars: null,
/**
* Auto-created global, local, with block and closure scopes containing vars.
*/
_globalScope: null,
_localScope: null,
_withScope: null,
_closureScope: null,
/**
* Initialization function, called when the debugger is initialized.
*/
@ -1789,10 +1752,6 @@ PropertiesView.prototype = {
this.createHierarchyStore();
this._vars = document.getElementById("variables");
this._localScope = this._addScope(L10N.getStr("localScope")).expand();
this._withScope = this._addScope(L10N.getStr("withScope")).hide();
this._closureScope = this._addScope(L10N.getStr("closureScope")).hide();
this._globalScope = this._addScope(L10N.getStr("globalScope"));
},
/**
@ -1802,10 +1761,6 @@ PropertiesView.prototype = {
this._currHierarchy = null;
this._prevHierarchy = null;
this._vars = null;
this._globalScope = null;
this._localScope = null;
this._withScope = null;
this._closureScope = null;
}
};

View File

@ -30,6 +30,8 @@ _BROWSER_TEST_FILES = \
browser_dbg_propertyview-06.js \
browser_dbg_propertyview-07.js \
browser_dbg_propertyview-08.js \
browser_dbg_propertyview-09.js \
browser_dbg_propertyview-10.js \
browser_dbg_propertyview-edit.js \
browser_dbg_panesize.js \
browser_dbg_stack-01.js \
@ -66,6 +68,7 @@ _BROWSER_TEST_PAGES = \
test-editor-mode \
browser_dbg_displayName.html \
browser_dbg_iframes.html \
browser_dbg_with-frame.html \
$(NULL)
libs:: $(_BROWSER_TEST_FILES)

View File

@ -23,65 +23,14 @@ function testSimpleCall() {
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
Services.tm.currentThread.dispatch({ run: function() {
let globalScope = gDebugger.DebuggerView.Properties._globalScope;
let localScope = gDebugger.DebuggerView.Properties._localScope;
let withScope = gDebugger.DebuggerView.Properties._withScope;
let closureScope = gDebugger.DebuggerView.Properties._closureScope;
ok(globalScope,
"Should have a globalScope container for the variables property view.");
ok(localScope,
"Should have a localScope container for the variables property view.");
ok(withScope,
"Should have a withScope container for the variables property view.");
ok(closureScope,
"Should have a closureScope container for the variables property view.");
is(globalScope, gDebugger.DebuggerView.Properties.globalScope,
"The globalScope object should be equal to the corresponding getter.");
is(localScope, gDebugger.DebuggerView.Properties.localScope,
"The localScope object should be equal to the corresponding getter.");
is(withScope, gDebugger.DebuggerView.Properties.withScope,
"The withScope object should be equal to the corresponding getter.");
is(closureScope, gDebugger.DebuggerView.Properties.closureScope,
"The closureScope object should be equal to the corresponding getter.");
ok(!globalScope.expanded,
"The globalScope should be initially collapsed.");
ok(localScope.expanded,
"The localScope should be initially expanded.");
ok(!withScope.expanded,
"The withScope should be initially collapsed.");
ok(!withScope.visible,
"The withScope should be initially hidden.");
ok(!closureScope.expanded,
"The closureScope should be initially collapsed.");
ok(!closureScope.visible,
"The closureScope should be initially hidden.");
is(gDebugger.DebuggerView.Properties._vars.childNodes.length, 4,
"Should have only 4 scopes created: global, local, with and scope.");
resumeAndFinish();
testScriptLabelShortening();
}}, 0);
});
gDebuggee.simpleCall();
}
function resumeAndFinish() {
function testScriptLabelShortening() {
gDebugger.DebuggerController.activeThread.resume(function() {
let vs = gDebugger.DebuggerView.Scripts;
let ss = gDebugger.DebuggerController.SourceScripts;

View File

@ -28,7 +28,7 @@ function testSimpleCall() {
ok(testScope,
"Should have created a scope.");
is(testScope.id, "test-scope",
is(testScope.id.substring(0, 4), "test",
"The newly created scope should have the default id set.");
is(testScope.querySelector(".name").getAttribute("value"), "test",
@ -37,8 +37,8 @@ function testSimpleCall() {
is(testScope.querySelector(".details").childNodes.length, 0,
"Any new scope should have a container with no child nodes.");
is(gDebugger.DebuggerView.Properties._vars.childNodes.length, 5,
"Should have 5 scopes created: global, local, with, closure and test.");
is(gDebugger.DebuggerView.Properties._vars.childNodes.length, 3,
"Should have 3 scopes created.");
ok(!testScope.expanded,

View File

@ -23,7 +23,7 @@ function testSimpleCall() {
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
Services.tm.currentThread.dispatch({ run: function() {
let testScope = gDebugger.DebuggerView.Properties._addScope("test");
let testScope = gDebugger.DebuggerView.Properties._addScope("test-scope");
let testVar = testScope.addVar("something");
let duplVar = testScope.addVar("something");
@ -33,9 +33,6 @@ function testSimpleCall() {
is(duplVar, null,
"Shouldn't be able to duplicate variables in the same scope.");
is(testVar.id, "test-scope->something-variable",
"The newly created scope should have the default id set.");
is(testVar.querySelector(".name").getAttribute("value"), "something",
"Any new variable should have the designated title.");
@ -188,7 +185,7 @@ function testSimpleCall() {
is(removeCallbackSender, testScope,
"The removeCallback wasn't called as it should.");
is(gDebugger.DebuggerView.Properties._vars.childNodes.length, 4,
is(gDebugger.DebuggerView.Properties._vars.childNodes.length, 2,
"The scope should have been removed from the parent container tree.");
gDebugger.DebuggerController.activeThread.resume(function() {

View File

@ -23,8 +23,8 @@ function testSimpleCall() {
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
Services.tm.currentThread.dispatch({ run: function() {
let globalScope = gDebugger.DebuggerView.Properties.globalScope;
let localScope = gDebugger.DebuggerView.Properties.localScope;
let globalScope = gDebugger.DebuggerView.Properties.addScope("Global");
let localScope = gDebugger.DebuggerView.Properties.addScope("Local");
globalScope.empty();
localScope.empty();

View File

@ -37,7 +37,7 @@ function testFrameParameters()
var frames = gDebugger.DebuggerView.StackFrames._frames,
childNodes = frames.childNodes,
localScope = gDebugger.DebuggerView.Properties.localScope,
localScope = gDebugger.DebuggerView.Properties._vars.firstChild,
localNodes = localScope.querySelector(".details").childNodes;
dump("Got our variables:\n");
@ -79,14 +79,14 @@ function testFrameParameters()
is(localNodes[7].querySelector(".value").getAttribute("value"), "1",
"Should have the right property value for 'a'.");
is(localNodes[8].querySelector(".value").getAttribute("value"), "[object Object]",
"Should have the right property value for 'b'.");
is(localNodes[8].querySelector(".value").getAttribute("value"), "[object Arguments]",
"Should have the right property value for 'arguments'.");
is(localNodes[9].querySelector(".value").getAttribute("value"), "[object Object]",
"Should have the right property value for 'c'.");
"Should have the right property value for 'b'.");
is(localNodes[10].querySelector(".value").getAttribute("value"), "[object Arguments]",
"Should have the right property value for 'arguments'.");
is(localNodes[10].querySelector(".value").getAttribute("value"), "[object Object]",
"Should have the right property value for 'c'.");
resumeAndFinish();
}}, 0);

View File

@ -36,7 +36,7 @@ function testFrameParameters()
dump("After currentThread.dispatch!\n");
var frames = gDebugger.DebuggerView.StackFrames._frames,
localScope = gDebugger.DebuggerView.Properties.localScope,
localScope = gDebugger.DebuggerView.Properties._vars.firstChild,
localNodes = localScope.querySelector(".details").childNodes;
dump("Got our variables:\n");
@ -59,7 +59,7 @@ function testFrameParameters()
// Expand the 'this', 'arguments' and 'c' tree nodes. This causes
// their properties to be retrieved and displayed.
localNodes[0].expand();
localNodes[9].expand();
localNodes[8].expand();
localNodes[10].expand();
// Poll every few milliseconds until the properties are retrieved.
@ -73,7 +73,7 @@ function testFrameParameters()
resumeAndFinish();
}
if (!localNodes[0].fetched ||
!localNodes[9].fetched ||
!localNodes[8].fetched ||
!localNodes[10].fetched) {
return;
}
@ -86,29 +86,29 @@ function testFrameParameters()
.getAttribute("value").search(/object/) != -1,
"__proto__ should be an object.");
is(localNodes[9].querySelector(".value").getAttribute("value"), "[object Object]",
"Should have the right property value for 'c'.");
is(localNodes[9].querySelectorAll(".property > .title > .key")[1]
.getAttribute("value"), "a",
"Should have the right property name for 'a'.");
is(localNodes[9].querySelectorAll(".property > .title > .value")[1]
.getAttribute("value"), 1,
"Should have the right value for 'c.a'.");
is(localNodes[10].querySelector(".value").getAttribute("value"),
is(localNodes[8].querySelector(".value").getAttribute("value"),
"[object Arguments]",
"Should have the right property value for 'arguments'.");
is(localNodes[10].querySelectorAll(".property > .title > .key")[7]
is(localNodes[8].querySelectorAll(".property > .title > .key")[7]
.getAttribute("value"), "length",
"Should have the right property name for 'length'.");
is(localNodes[10].querySelectorAll(".property > .title > .value")[7]
is(localNodes[8].querySelectorAll(".property > .title > .value")[7]
.getAttribute("value"), 5,
"Should have the right argument length.");
is(localNodes[10].querySelector(".value").getAttribute("value"), "[object Object]",
"Should have the right property value for 'c'.");
is(localNodes[10].querySelectorAll(".property > .title > .key")[1]
.getAttribute("value"), "a",
"Should have the right property name for 'a'.");
is(localNodes[10].querySelectorAll(".property > .title > .value")[1]
.getAttribute("value"), 1,
"Should have the right value for 'c.a'.");
resumeAndFinish();
}, 100);
}}, 0);

View File

@ -0,0 +1,94 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that the property view populates the global scope pane.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
var gPane = null;
var gTab = null;
var gDebugger = null;
function test()
{
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gPane = aPane;
gDebugger = gPane.contentWindow;
testFrameParameters();
});
}
function testFrameParameters()
{
let count = 0;
gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
// We expect 2 Debugger:FetchedVariables events, one from the global object
// scope and the regular one.
if (++count <2) {
info("Number of received Debugger:FetchedVariables events: " + count);
return;
}
gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
Services.tm.currentThread.dispatch({ run: function() {
var frames = gDebugger.DebuggerView.StackFrames._frames,
globalScope = gDebugger.DebuggerView.Properties._vars.lastChild,
globalNodes = globalScope.querySelector(".details").childNodes;
globalScope.expand();
is(gDebugger.DebuggerController.activeThread.state, "paused",
"Should only be getting stack frames while paused.");
is(frames.querySelectorAll(".dbg-stackframe").length, 3,
"Should have three frames.");
is(globalNodes[0].querySelector(".name").getAttribute("value"), "Array",
"Should have the right property name for |Array|.");
is(globalNodes[0].querySelector(".value").getAttribute("value"), "[object Function]",
"Should have the right property value for |Array|.");
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 Proxy]",
"Should have the right property value for |window|.");
resumeAndFinish();
}}, 0);
}, false);
EventUtils.sendMouseEvent({ type: "click" },
content.document.querySelector("button"),
content.window);
}
function resumeAndFinish() {
gDebugger.addEventListener("Debugger:AfterFramesCleared", function listener() {
gDebugger.removeEventListener("Debugger:AfterFramesCleared", listener, true);
Services.tm.currentThread.dispatch({ run: function() {
var frames = gDebugger.DebuggerView.StackFrames._frames;
is(frames.querySelectorAll(".dbg-stackframe").length, 0,
"Should have no frames.");
closeDebuggerAndFinish(gTab);
}}, 0);
}, true);
gDebugger.DebuggerController.activeThread.resume();
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebugger = null;
});

View File

@ -0,0 +1,105 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that the property view is correctly populated in |with| frames.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_with-frame.html";
var gPane = null;
var gTab = null;
var gDebugger = null;
function test()
{
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gPane = aPane;
gDebugger = gPane.contentWindow;
testWithFrame();
});
}
function testWithFrame()
{
let count = 0;
gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
// We expect 4 Debugger:FetchedVariables events, one from the global object
// scope, two from the |with| scopes and the regular one.
if (++count <3) {
info("Number of received Debugger:FetchedVariables events: " + count);
return;
}
gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
Services.tm.currentThread.dispatch({ run: function() {
var frames = gDebugger.DebuggerView.StackFrames._frames,
scopes = gDebugger.DebuggerView.Properties._vars,
globalScope = scopes.lastChild,
innerScope = scopes.firstChild,
globalNodes = globalScope.querySelector(".details").childNodes,
innerNodes = innerScope.querySelector(".details").childNodes;
globalScope.expand();
is(gDebugger.DebuggerController.activeThread.state, "paused",
"Should only be getting stack frames while paused.");
is(frames.querySelectorAll(".dbg-stackframe").length, 2,
"Should have three frames.");
is(scopes.children.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[0].querySelector(".name").getAttribute("value"), "Array",
"Should have the right property name for |Array|.");
is(globalNodes[0].querySelector(".value").getAttribute("value"), "[object Function]",
"Should have the right property value for |Array|.");
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 Proxy]",
"Should have the right property value for |window|.");
resumeAndFinish();
}}, 0);
}, false);
EventUtils.sendMouseEvent({ type: "click" },
content.document.querySelector("button"),
content.window);
}
function resumeAndFinish() {
gDebugger.addEventListener("Debugger:AfterFramesCleared", function listener() {
gDebugger.removeEventListener("Debugger:AfterFramesCleared", listener, true);
Services.tm.currentThread.dispatch({ run: function() {
var frames = gDebugger.DebuggerView.StackFrames._frames;
is(frames.querySelectorAll(".dbg-stackframe").length, 0,
"Should have no frames.");
closeDebuggerAndFinish(gTab);
}}, 0);
}, true);
gDebugger.DebuggerController.activeThread.resume();
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebugger = null;
});

View File

@ -29,7 +29,7 @@ function testFrameEval() {
is(gDebugger.DebuggerController.activeThread.state, "paused",
"Should only be getting stack frames while paused.");
var localScope = gDebugger.DebuggerView.Properties.localScope,
var localScope = gDebugger.DebuggerView.Properties._vars.firstChild,
localNodes = localScope.querySelector(".details").childNodes,
varA = localNodes[7];
@ -68,11 +68,18 @@ function testModification(aVar, aCallback, aNewValue, aNewResult) {
ok(aVar.querySelector(".element-input"),
"There should be an input element created.");
let count = 0;
gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
// We expect 2 Debugger:FetchedVariables events, one from the global
// object scope and the regular one.
if (++count <2) {
info("Number of received Debugger:FetchedVariables events: " + count);
return;
}
gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
// Get the variable reference anew, since the old ones were discarded when
// we resumed.
var localScope = gDebugger.DebuggerView.Properties.localScope,
var localScope = gDebugger.DebuggerView.Properties._vars.firstChild,
localNodes = localScope.querySelector(".details").childNodes,
varA = localNodes[7];

View File

@ -0,0 +1,33 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset='utf-8'/>
<title>Debugger Function Call Parameter Test</title>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<script type="text/javascript">
window.addEventListener("load", function() {
function test(aNumber) {
var a, obj = { one: 1, two: 2 };
var r = aNumber;
with (Math) {
a = PI * r * r;
with (obj) {
var foo = two * PI;
debugger;
}
}
};
function load() {
test(10);
}
var button = document.querySelector("button");
button.addEventListener("click", load, false);
});
</script>
</head>
<body>
<button>Click me!</button>
</body>
</html>

View File

@ -31,9 +31,7 @@ const console = (function() {
*/
function define(moduleName, deps, payload) {
if (typeof moduleName != "string") {
console.error(this.depth + " Error: Module name is not a string.");
console.trace();
return;
throw new Error("Error: Module name is not a string");
}
if (arguments.length == 2) {
@ -49,8 +47,11 @@ function define(moduleName, deps, payload) {
}
if (moduleName in define.modules) {
console.error(this.depth + " Error: Redefining module: " + moduleName);
throw new Error("Error: Redefining module: " + moduleName);
}
// Mark the payload so we know we need to call it to get the real module
payload.__uncompiled = true;
define.modules[moduleName] = payload;
}
@ -65,14 +66,6 @@ define.modules = {};
define.debugDependencies = false;
/**
* Self executing function in which Domain is defined, and attached to define
*/
var Syntax = {
COMMON_JS: 'commonjs',
AMD: 'amd'
};
/**
* We invoke require() in the context of a Domain so we can have multiple
* sets of modules running separate from each other.
@ -83,7 +76,6 @@ var Syntax = {
*/
function Domain() {
this.modules = {};
this.syntax = Syntax.COMMON_JS;
if (define.debugDependencies) {
this.depth = "";
@ -111,7 +103,6 @@ Domain.prototype.require = function(config, deps, callback) {
}
if (Array.isArray(deps)) {
this.syntax = Syntax.AMD;
var params = deps.map(function(dep) {
return this.lookup(dep);
}, this);
@ -141,8 +132,7 @@ Domain.prototype.lookup = function(moduleName) {
}
if (!(moduleName in define.modules)) {
console.error(this.depth + " Missing module: " + moduleName);
return null;
throw new Error("Missing module: " + moduleName);
}
var module = define.modules[moduleName];
@ -151,29 +141,33 @@ Domain.prototype.lookup = function(moduleName) {
console.log(this.depth + " Compiling module: " + moduleName);
}
if (typeof module == "function") {
if (module.__uncompiled) {
if (define.debugDependencies) {
this.depth += ".";
}
var exports;
var exports = {};
try {
if (this.syntax === Syntax.COMMON_JS) {
exports = {};
module(this.require.bind(this), exports, { id: moduleName, uri: "" });
}
else {
var modules = module.deps.map(function(dep) {
return this.lookup(dep);
}.bind(this));
exports = module.apply(null, modules);
}
var params = module.deps.map(function(dep) {
if (dep === "require") {
return this.require.bind(this);
}
if (dep === "exports") {
return exports;
}
if (dep === "module") {
return { id: moduleName, uri: "" };
}
return this.lookup(dep);
}.bind(this));
var reply = module.apply(null, params);
module = (reply !== undefined) ? reply : exports;
}
catch (ex) {
console.error("Error using module: " + moduleName, ex);
dump("Error using module '" + moduleName + "' - " + ex + "\n");
throw ex;
}
module = exports;
if (define.debugDependencies) {
this.depth = this.depth.slice(0, -1);

View File

@ -56,6 +56,7 @@ function Templater(options) {
else {
this.stack = [];
}
this.nodes = [];
}
/**
@ -93,6 +94,7 @@ Templater.prototype.processNode = function(node, data) {
data = {};
}
this.stack.push(node.nodeName + (node.id ? '#' + node.id : ''));
var pushedNode = false;
try {
// Process attributes
if (node.attributes && node.attributes.length) {
@ -109,7 +111,9 @@ Templater.prototype.processNode = function(node, data) {
}
}
// Only make the node available once we know it's not going away
this.nodes.push(data.__element);
data.__element = node;
pushedNode = true;
// It's good to clean up the attributes when we've processed them,
// but if we do it straight away, we mess up the array index
var attrs = Array.prototype.slice.call(node.attributes);
@ -172,7 +176,9 @@ Templater.prototype.processNode = function(node, data) {
this._processTextNode(node, data);
}
} finally {
delete data.__element;
if (pushedNode) {
data.__element = this.nodes.pop();
}
this.stack.pop();
}
};
@ -347,11 +353,14 @@ Templater.prototype._processTextNode = function(node, data) {
reply = this._maybeImportNode(reply, doc);
siblingNode.parentNode.insertBefore(reply, siblingNode);
} else if (typeof reply.item === 'function' && reply.length) {
// if thing is a NodeList, then import the children
for (var i = 0; i < reply.length; i++) {
var child = this._maybeImportNode(reply.item(i), doc);
siblingNode.parentNode.insertBefore(child, siblingNode);
}
// NodeLists can be live, in which case _maybeImportNode can
// remove them from the document, and thus the NodeList, which in
// turn breaks iteration. So first we clone the list
var list = Array.prototype.slice.call(reply, 0);
list.forEach(function(child) {
var imported = this._maybeImportNode(child, doc);
siblingNode.parentNode.insertBefore(imported, siblingNode);
}.bind(this));
}
else {
// if thing isn't a DOM element then wrap its string value in one

View File

@ -21,13 +21,14 @@ function test() {
testMultiImport();
testRecursive();
testUncompilable();
testFirebug();
shutdown();
});
}
function setup() {
define('gclitest/requirable', [], function(require, exports, module) {
define('gclitest/requirable', [ 'require', 'exports', 'module' ], function(require, exports, module) {
exports.thing1 = 'thing1';
exports.thing2 = 2;
@ -36,13 +37,17 @@ function setup() {
exports.getStatus = function() { return status; };
});
define('gclitest/unrequirable', [], function(require, exports, module) {
define('gclitest/unrequirable', [ 'require', 'exports', 'module' ], function(require, exports, module) {
null.throwNPE();
});
define('gclitest/recurse', [], function(require, exports, module) {
define('gclitest/recurse', [ 'require', 'exports', 'module', 'gclitest/recurse' ], function(require, exports, module) {
require('gclitest/recurse');
});
define('gclitest/firebug', [ 'gclitest/requirable' ], function(requirable) {
return { requirable: requirable, fb: true };
});
}
function shutdown() {
@ -52,6 +57,8 @@ function shutdown() {
delete define.globalDomain.modules['gclitest/unrequirable'];
delete define.modules['gclitest/recurse'];
delete define.globalDomain.modules['gclitest/recurse'];
delete define.modules['gclitest/firebug'];
delete define.globalDomain.modules['gclitest/firebug'];
define = undefined;
require = undefined;
@ -124,3 +131,10 @@ function testRecursive() {
// require('gclitest/recurse');
// Also see the comments in the testRecursive() function
}
function testFirebug() {
let requirable = require('gclitest/requirable');
let firebug = require('gclitest/firebug');
ok(firebug.fb, 'firebug.fb is true');
is(requirable, firebug.requirable, 'requirable pass-through');
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,7 @@ include $(DEPTH)/config/autoconf.mk
EXTRA_JS_MODULES = \
PropertyPanel.jsm \
NetworkHelper.jsm \
NetworkPanel.jsm \
AutocompletePopup.jsm \
WebConsoleUtils.jsm \
$(NULL)

View File

@ -49,6 +49,7 @@
* Austin Andrews
* Christoph Dorn
* Steven Roussey (AppCenter Inc, Network54)
* Mihai Sucan (Mozilla Corp.)
*/
const Cc = Components.classes;
@ -68,7 +69,7 @@ var EXPORTED_SYMBOLS = ["NetworkHelper"];
/**
* Helper object for networking stuff.
*
* All of the following functions have been taken from the Firebug source. They
* Most of the following functions have been taken from the Firebug source. They
* have been modified to match the Firefox coding rules.
*/
@ -128,12 +129,13 @@ var NetworkHelper =
* Reads the posted text from aRequest.
*
* @param nsIHttpChannel aRequest
* @param nsIDOMNode aBrowser
* @param string aCharset
* The content document charset, used when reading the POSTed data.
* @returns string or null
* Returns the posted string if it was possible to read from aRequest
* otherwise null.
*/
readPostTextFromRequest: function NH_readPostTextFromRequest(aRequest, aBrowser)
readPostTextFromRequest: function NH_readPostTextFromRequest(aRequest, aCharset)
{
if (aRequest instanceof Ci.nsIUploadChannel) {
let iStream = aRequest.uploadStream;
@ -150,8 +152,7 @@ var NetworkHelper =
}
// Read data from the stream.
let charset = aBrowser.contentWindow.document.characterSet;
let text = this.readAndConvertFromStream(iStream, charset);
let text = this.readAndConvertFromStream(iStream, aCharset);
// Seek locks the file, so seek to the beginning only if necko hasn't
// read it yet, since necko doesn't seek to 0 before reading (at lest
@ -167,14 +168,15 @@ var NetworkHelper =
/**
* Reads the posted text from the page's cache.
*
* @param nsIDOMNode aBrowser
* @param nsIDocShell aDocShell
* @param string aCharset
* @returns string or null
* Returns the posted string if it was possible to read from aBrowser
* otherwise null.
* Returns the posted string if it was possible to read from
* aDocShell otherwise null.
*/
readPostTextFromPage: function NH_readPostTextFromPage(aBrowser)
readPostTextFromPage: function NH_readPostTextFromPage(aDocShell, aCharset)
{
let webNav = aBrowser.webNavigation;
let webNav = aDocShell.QueryInterface(Ci.nsIWebNavigation);
if (webNav instanceof Ci.nsIWebPageDescriptor) {
let descriptor = webNav.currentDescriptor;
@ -182,8 +184,7 @@ var NetworkHelper =
descriptor instanceof Ci.nsISeekableStream) {
descriptor.seek(NS_SEEK_SET, 0);
let charset = browser.contentWindow.document.characterSet;
return this.readAndConvertFromStream(descriptor, charset);
return this.readAndConvertFromStream(descriptor, aCharset);
}
}
return null;
@ -266,6 +267,81 @@ var NetworkHelper =
});
},
/**
* Parse a raw Cookie header value.
*
* @param string aHeader
* The raw Cookie header value.
* @return array
* Array holding an object for each cookie. Each object holds the
* following properties: name and value.
*/
parseCookieHeader: function NH_parseCookieHeader(aHeader)
{
let cookies = aHeader.split(";");
let result = [];
cookies.forEach(function(aCookie) {
let [name, value] = aCookie.split("=");
result.push({name: unescape(name.trim()),
value: unescape(value.trim())});
});
return result;
},
/**
* Parse a raw Set-Cookie header value.
*
* @param string aHeader
* The raw Set-Cookie header value.
* @return array
* Array holding an object for each cookie. Each object holds the
* following properties: name, value, secure (boolean), httpOnly
* (boolean), path, domain and expires (ISO date string).
*/
parseSetCookieHeader: function NH_parseSetCookieHeader(aHeader)
{
let rawCookies = aHeader.split(/\r\n|\n|\r/);
let cookies = [];
rawCookies.forEach(function(aCookie) {
let name = unescape(aCookie.substr(0, aCookie.indexOf("=")).trim());
let parts = aCookie.substr(aCookie.indexOf("=") + 1).split(";");
let value = unescape(parts.shift().trim());
let cookie = {name: name, value: value};
parts.forEach(function(aPart) {
let part = aPart.trim();
if (part.toLowerCase() == "secure") {
cookie.secure = true;
}
else if (part.toLowerCase() == "httponly") {
cookie.httpOnly = true;
}
else if (part.indexOf("=") > -1) {
let pair = part.split("=");
pair[0] = pair[0].toLowerCase();
if (pair[0] == "path" || pair[0] == "domain") {
cookie[pair[0]] = pair[1];
}
else if (pair[0] == "expires") {
try {
pair[1] = pair[1].replace(/-/g, ' ');
cookie.expires = new Date(pair[1]).toISOString();
}
catch (ex) { }
}
}
});
cookies.push(cookie);
});
return cookies;
},
// This is a list of all the mime category maps jviereck could find in the
// firebug code base.
mimeCategoryMap: {
@ -333,6 +409,7 @@ var NetworkHelper =
"audio/x-wav": "media",
"text/json": "json",
"application/x-json": "json",
"application/json-rpc": "json"
"application/json-rpc": "json",
"application/x-web-app-manifest+json": "json",
}
}

View File

@ -0,0 +1,668 @@
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
/* 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 Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "mimeService", "@mozilla.org/mime;1",
"nsIMIMEService");
XPCOMUtils.defineLazyModuleGetter(this, "NetworkHelper",
"resource:///modules/NetworkHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource:///modules/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "l10n", function() {
return WebConsoleUtils.l10n;
});
var EXPORTED_SYMBOLS = ["NetworkPanel"];
/**
* Creates a new NetworkPanel.
*
* @param nsIDOMNode aParent
* Parent node to append the created panel to.
* @param object aHttpActivity
* HttpActivity to display in the panel.
*/
function NetworkPanel(aParent, aHttpActivity)
{
let doc = aParent.ownerDocument;
this.httpActivity = aHttpActivity;
// Create the underlaying panel
this.panel = createElement(doc, "panel", {
label: l10n.getStr("NetworkPanel.label"),
titlebar: "normal",
noautofocus: "true",
noautohide: "true",
close: "true"
});
// Create the iframe that displays the NetworkPanel XHTML.
this.iframe = createAndAppendElement(this.panel, "iframe", {
src: "chrome://browser/content/NetworkPanel.xhtml",
type: "content",
flex: "1"
});
let self = this;
// Destroy the panel when it's closed.
this.panel.addEventListener("popuphidden", function onPopupHide() {
self.panel.removeEventListener("popuphidden", onPopupHide, false);
self.panel.parentNode.removeChild(self.panel);
self.panel = null;
self.iframe = null;
self.document = null;
self.httpActivity = null;
if (self.linkNode) {
self.linkNode._panelOpen = false;
self.linkNode = null;
}
}, false);
// Set the document object and update the content once the panel is loaded.
this.panel.addEventListener("load", function onLoad() {
self.panel.removeEventListener("load", onLoad, true);
self.document = self.iframe.contentWindow.document;
self.update();
}, true);
// Create the footer.
let footer = createElement(doc, "hbox", { align: "end" });
createAndAppendElement(footer, "spacer", { flex: 1 });
createAndAppendElement(footer, "resizer", { dir: "bottomend" });
this.panel.appendChild(footer);
aParent.appendChild(this.panel);
}
NetworkPanel.prototype =
{
/**
* Callback is called once the NetworkPanel is processed completely. Used by
* unit tests.
*/
isDoneCallback: null,
/**
* The current state of the output.
*/
_state: 0,
/**
* State variables.
*/
_INIT: 0,
_DISPLAYED_REQUEST_HEADER: 1,
_DISPLAYED_REQUEST_BODY: 2,
_DISPLAYED_RESPONSE_HEADER: 3,
_TRANSITION_CLOSED: 4,
_fromDataRegExp: /Content-Type\:\s*application\/x-www-form-urlencoded/,
_contentType: null,
/**
* Small helper function that is nearly equal to l10n.getFormatStr
* except that it prefixes aName with "NetworkPanel.".
*
* @param string aName
* The name of an i10n string to format. This string is prefixed with
* "NetworkPanel." before calling the HUDService.getFormatStr function.
* @param array aArray
* Values used as placeholder for the i10n string.
* @returns string
* The i10n formated string.
*/
_format: function NP_format(aName, aArray)
{
return l10n.getFormatStr("NetworkPanel." + aName, aArray);
},
/**
* Returns the content type of the response body. This is based on the
* response.content.mimeType property. If this value is not available, then
* the content type is guessed by the file extension of the request URL.
*
* @return string
* Content type or empty string if no content type could be figured
* out.
*/
get contentType()
{
if (this._contentType) {
return this._contentType;
}
let entry = this.httpActivity.log.entries[0];
let request = entry.request;
let response = entry.response;
let contentType = "";
let types = response.content ?
(response.content.mimeType || "").split(/,|;/) : [];
for (let i = 0; i < types.length; i++) {
if (types[i] in NetworkHelper.mimeCategoryMap) {
contentType = types[i];
break;
}
}
if (contentType) {
this._contentType = contentType;
return contentType;
}
// Try to get the content type from the request file extension.
let uri = NetUtil.newURI(request.url);
if ((uri instanceof Ci.nsIURL) && uri.fileExtension) {
try {
contentType = mimeService.getTypeFromExtension(uri.fileExtension);
}
catch(ex) {
// Added to prevent failures on OS X 64. No Flash?
Cu.reportError(ex);
}
}
this._contentType = contentType;
return contentType;
},
/**
*
* @returns boolean
* True if the response is an image, false otherwise.
*/
get _responseIsImage()
{
return this.contentType &&
NetworkHelper.mimeCategoryMap[this.contentType] == "image";
},
/**
*
* @returns boolean
* True if the response body contains text, false otherwise.
*/
get _isResponseBodyTextData()
{
let contentType = this.contentType;
if (!contentType)
return false;
if (contentType.indexOf("text/") == 0) {
return true;
}
switch (NetworkHelper.mimeCategoryMap[contentType]) {
case "txt":
case "js":
case "json":
case "css":
case "html":
case "svg":
case "xml":
return true;
default:
return false;
}
},
/**
* Tells if the server response is cached.
*
* @returns boolean
* Returns true if the server responded that the request is already
* in the browser's cache, false otherwise.
*/
get _isResponseCached()
{
return this.httpActivity.log.entries[0].response.status == 304;
},
/**
* Tells if the request body includes form data.
*
* @returns boolean
* Returns true if the posted body contains form data.
*/
get _isRequestBodyFormData()
{
let requestBody = this.httpActivity.log.entries[0].request.postData.text;
return this._fromDataRegExp.test(requestBody);
},
/**
* Appends the node with id=aId by the text aValue.
*
* @param string aId
* @param string aValue
* @returns void
*/
_appendTextNode: function NP_appendTextNode(aId, aValue)
{
let textNode = this.document.createTextNode(aValue);
this.document.getElementById(aId).appendChild(textNode);
},
/**
* Generates some HTML to display the key-value pair of the aList data. The
* generated HTML is added to node with id=aParentId.
*
* @param string aParentId
* Id of the parent node to append the list to.
* @oaram array aList
* Array that holds the objects you want to display. Each object must
* have two properties: name and value.
* @param boolean aIgnoreCookie
* If true, the key-value named "Cookie" is not added to the list.
* @returns void
*/
_appendList: function NP_appendList(aParentId, aList, aIgnoreCookie)
{
let parent = this.document.getElementById(aParentId);
let doc = this.document;
aList.sort(function(a, b) {
return a.name.toLowerCase() < b.name.toLowerCase();
});
aList.forEach(function(aItem) {
let name = aItem.name;
let value = aItem.value;
if (aIgnoreCookie && name == "Cookie") {
return;
}
/**
* The following code creates the HTML:
* <tr>
* <th scope="row" class="property-name">${line}:</th>
* <td class="property-value">${aList[line]}</td>
* </tr>
* and adds it to parent.
*/
let row = doc.createElement("tr");
let textNode = doc.createTextNode(name + ":");
let th = doc.createElement("th");
th.setAttribute("scope", "row");
th.setAttribute("class", "property-name");
th.appendChild(textNode);
row.appendChild(th);
textNode = doc.createTextNode(value);
let td = doc.createElement("td");
td.setAttribute("class", "property-value");
td.appendChild(textNode);
row.appendChild(td);
parent.appendChild(row);
});
},
/**
* Displays the node with id=aId.
*
* @param string aId
* @returns void
*/
_displayNode: function NP_displayNode(aId)
{
this.document.getElementById(aId).style.display = "block";
},
/**
* Sets the request URL, request method, the timing information when the
* request started and the request header content on the NetworkPanel.
* If the request header contains cookie data, a list of sent cookies is
* generated and a special sent cookie section is displayed + the cookie list
* added to it.
*
* @returns void
*/
_displayRequestHeader: function NP__displayRequestHeader()
{
let entry = this.httpActivity.log.entries[0];
let request = entry.request;
let requestTime = new Date(entry.startedDateTime);
this._appendTextNode("headUrl", request.url);
this._appendTextNode("headMethod", request.method);
this._appendTextNode("requestHeadersInfo",
l10n.timestampString(requestTime));
this._appendList("requestHeadersContent", request.headers, true);
if (request.cookies.length > 0) {
this._displayNode("requestCookie");
this._appendList("requestCookieContent", request.cookies);
}
},
/**
* Displays the request body section of the NetworkPanel and set the request
* body content on the NetworkPanel.
*
* @returns void
*/
_displayRequestBody: function NP__displayRequestBody() {
let postData = this.httpActivity.log.entries[0].request.postData;
this._displayNode("requestBody");
this._appendTextNode("requestBodyContent", postData.text);
},
/*
* Displays the `sent form data` section. Parses the request header for the
* submitted form data displays it inside of the `sent form data` section.
*
* @returns void
*/
_displayRequestForm: function NP__processRequestForm() {
let postData = this.httpActivity.log.entries[0].request.postData.text;
let requestBodyLines = postData.split("\n");
let formData = requestBodyLines[requestBodyLines.length - 1].
replace(/\+/g, " ").split("&");
function unescapeText(aText)
{
try {
return decodeURIComponent(aText);
}
catch (ex) {
return decodeURIComponent(unescape(aText));
}
}
let formDataArray = [];
for (let i = 0; i < formData.length; i++) {
let data = formData[i];
let idx = data.indexOf("=");
let key = data.substring(0, idx);
let value = data.substring(idx + 1);
formDataArray.push({
name: unescapeText(key),
value: unescapeText(value)
});
}
this._appendList("requestFormDataContent", formDataArray);
this._displayNode("requestFormData");
},
/**
* Displays the response section of the NetworkPanel, sets the response status,
* the duration between the start of the request and the receiving of the
* response header as well as the response header content on the the NetworkPanel.
*
* @returns void
*/
_displayResponseHeader: function NP__displayResponseHeader()
{
let entry = this.httpActivity.log.entries[0];
let timing = entry.timings;
let response = entry.response;
this._appendTextNode("headStatus",
[response.httpVersion, response.status,
response.statusText].join(" "));
// Calculate how much time it took from the request start, until the
// response started to be received.
let deltaDuration = 0;
["dns", "connect", "send", "wait"].forEach(function (aValue) {
let ms = timing[aValue];
if (ms > -1) {
deltaDuration += ms;
}
});
this._appendTextNode("responseHeadersInfo",
this._format("durationMS", [deltaDuration]));
this._displayNode("responseContainer");
this._appendList("responseHeadersContent", response.headers);
},
/**
* Displays the respones image section, sets the source of the image displayed
* in the image response section to the request URL and the duration between
* the receiving of the response header and the end of the request. Once the
* image is loaded, the size of the requested image is set.
*
* @returns void
*/
_displayResponseImage: function NP__displayResponseImage()
{
let self = this;
let entry = this.httpActivity.log.entries[0];
let timing = entry.timings;
let request = entry.request;
let cached = "";
if (this._isResponseCached) {
cached = "Cached";
}
let imageNode = this.document.getElementById("responseImage" + cached +"Node");
imageNode.setAttribute("src", request.url);
// This function is called to set the imageInfo.
function setImageInfo() {
self._appendTextNode("responseImage" + cached + "Info",
self._format("imageSizeDeltaDurationMS",
[ imageNode.width, imageNode.height, timing.receive ]
)
);
}
// Check if the image is already loaded.
if (imageNode.width != 0) {
setImageInfo();
}
else {
// Image is not loaded yet therefore add a load event.
imageNode.addEventListener("load", function imageNodeLoad() {
imageNode.removeEventListener("load", imageNodeLoad, false);
setImageInfo();
}, false);
}
this._displayNode("responseImage" + cached);
},
/**
* Displays the response body section, sets the the duration between
* the receiving of the response header and the end of the request as well as
* the content of the response body on the NetworkPanel.
*
* @returns void
*/
_displayResponseBody: function NP__displayResponseBody()
{
let entry = this.httpActivity.log.entries[0];
let timing = entry.timings;
let response = entry.response;
let cached = this._isResponseCached ? "Cached" : "";
this._appendTextNode("responseBody" + cached + "Info",
this._format("durationMS", [timing.receive]));
this._displayNode("responseBody" + cached);
this._appendTextNode("responseBody" + cached + "Content",
response.content.text);
},
/**
* Displays the `Unknown Content-Type hint` and sets the duration between the
* receiving of the response header on the NetworkPanel.
*
* @returns void
*/
_displayResponseBodyUnknownType: function NP__displayResponseBodyUnknownType()
{
let timing = this.httpActivity.log.entries[0].timings;
this._displayNode("responseBodyUnknownType");
this._appendTextNode("responseBodyUnknownTypeInfo",
this._format("durationMS", [timing.receive]));
this._appendTextNode("responseBodyUnknownTypeContent",
this._format("responseBodyUnableToDisplay.content", [this.contentType]));
},
/**
* Displays the `no response body` section and sets the the duration between
* the receiving of the response header and the end of the request.
*
* @returns void
*/
_displayNoResponseBody: function NP_displayNoResponseBody()
{
let timing = this.httpActivity.log.entries[0].timings;
this._displayNode("responseNoBody");
this._appendTextNode("responseNoBodyInfo",
this._format("durationMS", [timing.receive]));
},
/**
* Updates the content of the NetworkPanel's iframe.
*
* @returns void
*/
update: function NP_update()
{
// After the iframe's contentWindow is ready, the document object is set.
// If the document object is not available yet nothing needs to be updated.
if (!this.document) {
return;
}
let stages = this.httpActivity.meta.stages;
let entry = this.httpActivity.log.entries[0];
let timing = entry.timings;
let request = entry.request;
let response = entry.response;
switch (this._state) {
case this._INIT:
this._displayRequestHeader();
this._state = this._DISPLAYED_REQUEST_HEADER;
// FALL THROUGH
case this._DISPLAYED_REQUEST_HEADER:
// Process the request body if there is one.
if (!this.httpActivity.meta.discardRequestBody && request.postData) {
// Check if we send some form data. If so, display the form data special.
if (this._isRequestBodyFormData) {
this._displayRequestForm();
}
else {
this._displayRequestBody();
}
this._state = this._DISPLAYED_REQUEST_BODY;
}
// FALL THROUGH
case this._DISPLAYED_REQUEST_BODY:
// There is always a response header. Therefore we can skip here if
// we don't have a response header yet and don't have to try updating
// anything else in the NetworkPanel.
if (!response.headers.length || !Object.keys(timing).length) {
break;
}
this._displayResponseHeader();
this._state = this._DISPLAYED_RESPONSE_HEADER;
// FALL THROUGH
case this._DISPLAYED_RESPONSE_HEADER:
if (stages.indexOf("REQUEST_STOP") == -1 ||
stages.indexOf("TRANSACTION_CLOSE") == -1) {
break;
}
this._state = this._TRANSITION_CLOSED;
if (this.httpActivity.meta.discardResponseBody) {
break;
}
if (!response.content || !response.content.text) {
this._displayNoResponseBody();
}
else if (this._responseIsImage) {
this._displayResponseImage();
}
else if (!this._isResponseBodyTextData) {
this._displayResponseBodyUnknownType();
}
else if (response.content.text) {
this._displayResponseBody();
}
break;
}
}
}
/**
* Creates a DOMNode and sets all the attributes of aAttributes on the created
* element.
*
* @param nsIDOMDocument aDocument
* Document to create the new DOMNode.
* @param string aTag
* Name of the tag for the DOMNode.
* @param object aAttributes
* Attributes set on the created DOMNode.
*
* @returns nsIDOMNode
*/
function createElement(aDocument, aTag, aAttributes)
{
let node = aDocument.createElement(aTag);
if (aAttributes) {
for (let attr in aAttributes) {
node.setAttribute(attr, aAttributes[attr]);
}
}
return node;
}
/**
* Creates a new DOMNode and appends it to aParent.
*
* @param nsIDOMNode aParent
* A parent node to append the created element.
* @param string aTag
* Name of the tag for the DOMNode.
* @param object aAttributes
* Attributes set on the created DOMNode.
*
* @returns nsIDOMNode
*/
function createAndAppendElement(aParent, aTag, aAttributes)
{
let node = createElement(aParent.ownerDocument, aTag, aAttributes);
aParent.appendChild(node);
return node;
}

View File

@ -11,17 +11,12 @@ const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGetter(this, "WebConsoleUtils", function () {
let obj = {};
Cu.import("resource:///modules/WebConsoleUtils.jsm", obj);
return obj.WebConsoleUtils;
});
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource:///modules/WebConsoleUtils.jsm");
var EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView"];
///////////////////////////////////////////////////////////////////////////
//// PropertyTreeView.

View File

@ -11,7 +11,9 @@ const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
var EXPORTED_SYMBOLS = ["WebConsoleUtils", "JSPropertyProvider"];

View File

@ -5,31 +5,17 @@
const TEST_FILE = "test-network.html";
function tabLoad(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);
openConsole();
let hudId = HUDService.getHudIdByWindow(content);
hud = HUDService.hudReferences[hudId];
browser.addEventListener("load", tabReload, true);
content.location.reload();
}
function tabReload(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);
browser.removeEventListener(aEvent.type, tabReload, true);
let textContent = hud.outputNode.textContent;
isnot(textContent.indexOf("test-network.html"), -1,
"found test-network.html");
isnot(textContent.indexOf("test-image.png"), -1, "found test-image.png");
isnot(textContent.indexOf("testscript.js"), -1, "found testscript.js");
isnot(textContent.indexOf("running network console logging tests"), -1,
outputNode = hud.outputNode;
findLogEntry("test-network.html");
findLogEntry("test-image.png");
findLogEntry("testscript.js");
isnot(outputNode.textContent.indexOf("running network console logging tests"), -1,
"found the console.log() message from testscript.js");
finishTest();
executeSoon(finishTest);
}
function test() {
@ -42,5 +28,12 @@ function test() {
let uri = Services.io.newFileURI(dir);
addTab(uri.spec);
browser.addEventListener("load", tabLoad, true);
browser.addEventListener("load", function tabLoad() {
browser.removeEventListener("load", tabLoad, true);
openConsole(null, function(aHud) {
hud = aHud;
browser.addEventListener("load", tabReload, true);
content.location.reload();
});
}, true);
}

View File

@ -10,25 +10,27 @@
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-599725-response-headers.sjs";
let lastFinishedRequest = null;
function requestDoneCallback(aHttpRequest)
{
lastFinishedRequest = aHttpRequest;
}
function performTest()
function performTest(lastFinishedRequest)
{
ok(lastFinishedRequest, "page load was logged");
let headers = lastFinishedRequest.response.header;
ok(headers, "we have the response headers");
ok(!headers["Content-Type"], "we do not have the Content-Type header");
ok(headers["Content-Length"] != 60, "Content-Length != 60");
function readHeader(aName)
{
for (let header of headers) {
if (header.name == aName) {
return header.value;
}
}
return null;
}
let headers = lastFinishedRequest.log.entries[0].response.headers;
ok(headers, "we have the response headers");
ok(!readHeader("Content-Type"), "we do not have the Content-Type header");
isnot(readHeader("Content-Length"), 60, "Content-Length != 60");
lastFinishedRequest = null;
HUDService.lastFinishedRequestCallback = null;
finishTest();
executeSoon(finishTest);
}
function test()
@ -37,15 +39,15 @@ function test()
let initialLoad = true;
browser.addEventListener("load", function () {
browser.addEventListener("load", function onLoad() {
if (initialLoad) {
openConsole();
HUDService.lastFinishedRequestCallback = requestDoneCallback;
content.location.reload();
openConsole(null, function() {
HUDService.lastFinishedRequestCallback = performTest;
content.location.reload();
});
initialLoad = false;
} else {
browser.removeEventListener("load", arguments.callee, true);
performTest();
browser.removeEventListener("load", onLoad, true);
}
}, true);
}

View File

@ -10,28 +10,19 @@
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-600183-charset.html";
let lastFinishedRequest = null;
function requestDoneCallback(aHttpRequest)
{
lastFinishedRequest = aHttpRequest;
}
function performTest()
function performTest(lastFinishedRequest)
{
ok(lastFinishedRequest, "charset test page was loaded and logged");
let body = lastFinishedRequest.response.body;
let body = lastFinishedRequest.log.entries[0].response.content.text;
ok(body, "we have the response body");
let chars = "\u7684\u95ee\u5019!"; // 的问候!
isnot(body.indexOf("<p>" + chars + "</p>"), -1,
"found the chinese simplified string");
lastFinishedRequest = null;
HUDService.saveRequestAndResponseBodies = false;
HUDService.lastFinishedRequestCallback = null;
finishTest();
executeSoon(finishTest);
}
function test()
@ -40,20 +31,18 @@ function test()
let initialLoad = true;
browser.addEventListener("load", function () {
browser.addEventListener("load", function onLoad() {
if (initialLoad) {
waitForFocus(function() {
openConsole();
openConsole(null, function(hud) {
HUDService.saveRequestAndResponseBodies = true;
HUDService.lastFinishedRequestCallback = requestDoneCallback;
hud.saveRequestAndResponseBodies = true;
HUDService.lastFinishedRequestCallback = performTest;
content.location = TEST_URI;
}, content);
});
initialLoad = false;
} else {
browser.removeEventListener("load", arguments.callee, true);
performTest();
browser.removeEventListener("load", onLoad, true);
}
}, true);
}

View File

@ -57,10 +57,10 @@ function onpopupshown2(aEvent)
isnot(menuitems[1].getAttribute("checked"), "true",
"menuitems[1] is not checked");
ok(!HUDService.saveRequestAndResponseBodies, "bodies are not logged");
ok(!huds[1].saveRequestAndResponseBodies, "bodies are not logged");
// Enable body logging.
HUDService.saveRequestAndResponseBodies = true;
huds[1].saveRequestAndResponseBodies = true;
menupopups[1].addEventListener("popuphidden", function _onhidden(aEvent) {
menupopups[1].removeEventListener(aEvent.type, _onhidden, false);
@ -103,11 +103,12 @@ function onpopupshown1(aEvent)
{
menupopups[0].removeEventListener(aEvent.type, onpopupshown1, false);
// The menuitem checkbox must be in sync with the other tabs.
is(menuitems[0].getAttribute("checked"), "true", "menuitems[0] is checked");
// The menuitem checkbox must not be in sync with the other tabs.
isnot(menuitems[0].getAttribute("checked"), "true",
"menuitems[0] is not checked");
// Disable body logging.
HUDService.saveRequestAndResponseBodies = false;
// Enable body logging for tab 1 as well.
huds[0].saveRequestAndResponseBodies = true;
// Close the menu, and switch back to tab 2.
menupopups[0].addEventListener("popuphidden", function _onhidden(aEvent) {
@ -127,8 +128,7 @@ function onpopupshown2c(aEvent)
{
menupopups[1].removeEventListener(aEvent.type, onpopupshown2c, false);
isnot(menuitems[1].getAttribute("checked"), "true",
"menuitems[1] is not checked");
is(menuitems[1].getAttribute("checked"), "true", "menuitems[1] is checked");
menupopups[1].addEventListener("popuphidden", function _onhidden(aEvent) {
menupopups[1].removeEventListener(aEvent.type, _onhidden, false);

View File

@ -41,14 +41,11 @@ let TestObserver = {
function tabLoad(aEvent) {
browser.removeEventListener(aEvent.type, tabLoad, true);
openConsole();
let hudId = HUDService.getHudIdByWindow(content);
hud = HUDService.hudReferences[hudId];
Services.console.registerListener(TestObserver);
content.location = TEST_URI;
openConsole(null, function(aHud) {
hud = aHud;
Services.console.registerListener(TestObserver);
content.location = TEST_URI;
});
}
function performTest() {

View File

@ -92,8 +92,8 @@ function tabLoaded() {
successFn: function()
{
let jstermMessage = HUD.outputNode.querySelector(".webconsole-msg-output");
EventUtils.synthesizeMouse(networkLink, 2, 2, {});
EventUtils.synthesizeMouse(jstermMessage, 2, 2, {});
EventUtils.synthesizeMouse(networkLink, 2, 2, {});
},
failureFn: finishTest,
});

View File

@ -118,8 +118,8 @@ function tabLoaded() {
successFn: function()
{
let jstermMessage = HUD.outputNode.querySelector(".webconsole-msg-output");
EventUtils.synthesizeMouse(networkLink, 2, 2, {});
EventUtils.synthesizeMouse(jstermMessage, 2, 2, {});
EventUtils.synthesizeMouse(networkLink, 2, 2, {});
},
failureFn: finishTest,
});

View File

@ -13,57 +13,63 @@ let lastFinishedRequests = {};
function requestDoneCallback(aHttpRequest)
{
let status = aHttpRequest.response.status.
replace(/^HTTP\/\d\.\d (\d+).+$/, "$1");
let status = aHttpRequest.log.entries[0].response.status;
lastFinishedRequests[status] = aHttpRequest;
}
function performTest(aEvent)
{
HUDService.saveRequestAndResponseBodies = false;
HUDService.lastFinishedRequestCallback = null;
ok("301" in lastFinishedRequests, "request 1: 301 Moved Permanently");
ok("404" in lastFinishedRequests, "request 2: 404 Not found");
let headers0 = lastFinishedRequests["301"].response.header;
is(headers0["Content-Type"], "text/html",
function readHeader(aName)
{
for (let header of headers) {
if (header.name == aName) {
return header.value;
}
}
return null;
}
let headers = lastFinishedRequests["301"].log.entries[0].response.headers;
is(readHeader("Content-Type"), "text/html",
"we do have the Content-Type header");
is(headers0["Content-Length"], 71, "Content-Length is correct");
is(headers0["Location"], "/redirect-from-bug-630733",
is(readHeader("Content-Length"), 71, "Content-Length is correct");
is(readHeader("Location"), "/redirect-from-bug-630733",
"Content-Length is correct");
is(headers0["x-foobar-bug630733"], "bazbaz",
is(readHeader("x-foobar-bug630733"), "bazbaz",
"X-Foobar-bug630733 is correct");
let body = lastFinishedRequests["301"].response.body;
ok(!body, "body discarded for request 1");
let body = lastFinishedRequests["301"].log.entries[0].response.content;
ok(!body.text, "body discarded for request 1");
let headers1 = lastFinishedRequests["404"].response.header;
ok(!headers1["Location"], "no Location header");
ok(!headers1["x-foobar-bug630733"], "no X-Foobar-bug630733 header");
headers = lastFinishedRequests["404"].log.entries[0].response.headers;
ok(!readHeader("Location"), "no Location header");
ok(!readHeader("x-foobar-bug630733"), "no X-Foobar-bug630733 header");
body = lastFinishedRequests["404"].response.body;
body = lastFinishedRequests["404"].log.entries[0].response.content.text;
isnot(body.indexOf("404"), -1,
"body is correct for request 2");
lastFinishedRequests = null;
finishTest();
executeSoon(finishTest);
}
function test()
{
addTab("data:text/html;charset=utf-8,<p>Web Console test for bug 630733");
browser.addEventListener("load", function(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);
browser.addEventListener("load", function onLoad1(aEvent) {
browser.removeEventListener(aEvent.type, onLoad1, true);
executeSoon(function() {
openConsole();
HUDService.saveRequestAndResponseBodies = true;
openConsole(null, function(hud) {
hud.saveRequestAndResponseBodies = true;
HUDService.lastFinishedRequestCallback = requestDoneCallback;
browser.addEventListener("load", function(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);
browser.addEventListener("load", function onLoad2(aEvent) {
browser.removeEventListener(aEvent.type, onLoad2, true);
executeSoon(performTest);
}, true);

View File

@ -18,38 +18,36 @@ function test()
{
addTab("data:text/html;charset=utf-8,Web Console network logging tests");
browser.addEventListener("load", function() {
browser.removeEventListener("load", arguments.callee, true);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole();
openConsole(null, function(aHud) {
hud = aHud;
hud = HUDService.getHudByWindow(content);
ok(hud, "Web Console is now open");
HUDService.lastFinishedRequestCallback = function(aRequest) {
lastRequest = aRequest.log.entries[0];
if (requestCallback) {
requestCallback();
}
};
HUDService.lastFinishedRequestCallback = function(aRequest) {
lastRequest = aRequest;
if (requestCallback) {
requestCallback();
}
};
executeSoon(testPageLoad);
executeSoon(testPageLoad);
});
}, true);
}
function testPageLoad()
{
browser.addEventListener("load", function(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);
requestCallback = function() {
// Check if page load was logged correctly.
ok(lastRequest, "Page load was logged");
is(lastRequest.url, TEST_NETWORK_REQUEST_URI,
is(lastRequest.request.url, TEST_NETWORK_REQUEST_URI,
"Logged network entry is page load");
is(lastRequest.method, "GET", "Method is correct");
is(lastRequest.request.method, "GET", "Method is correct");
lastRequest = null;
requestCallback = null;
executeSoon(testPageLoadBody);
}, true);
};
content.location = TEST_NETWORK_REQUEST_URI;
}
@ -57,12 +55,12 @@ function testPageLoad()
function testPageLoadBody()
{
// Turn off logging of request bodies and check again.
browser.addEventListener("load", function(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);
requestCallback = function() {
ok(lastRequest, "Page load was logged again");
lastRequest = null;
requestCallback = null;
executeSoon(testXhrGet);
}, true);
};
content.location.reload();
}
@ -71,7 +69,7 @@ function testXhrGet()
{
requestCallback = function() {
ok(lastRequest, "testXhrGet() was logged");
is(lastRequest.method, "GET", "Method is correct");
is(lastRequest.request.method, "GET", "Method is correct");
lastRequest = null;
requestCallback = null;
executeSoon(testXhrPost);
@ -85,7 +83,7 @@ function testXhrPost()
{
requestCallback = function() {
ok(lastRequest, "testXhrPost() was logged");
is(lastRequest.method, "POST", "Method is correct");
is(lastRequest.request.method, "POST", "Method is correct");
lastRequest = null;
requestCallback = null;
executeSoon(testFormSubmission);
@ -99,12 +97,11 @@ function testFormSubmission()
{
// Start the form submission test. As the form is submitted, the page is
// loaded again. Bind to the load event to catch when this is done.
browser.addEventListener("load", function(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);
requestCallback = function() {
ok(lastRequest, "testFormSubmission() was logged");
is(lastRequest.method, "POST", "Method is correct");
is(lastRequest.request.method, "POST", "Method is correct");
executeSoon(testLiveFilteringOnSearchStrings);
}, true);
};
let form = content.document.querySelector("form");
ok(form, "we have the HTML form");
@ -112,9 +109,6 @@ function testFormSubmission()
}
function testLiveFilteringOnSearchStrings() {
browser.removeEventListener("DOMContentLoaded",
testLiveFilteringOnSearchStrings, false);
setStringFilter("http");
isnot(countMessageNodes(), 0, "the log nodes are not hidden when the " +
"search string is set to \"http\"");
@ -146,6 +140,9 @@ function testLiveFilteringOnSearchStrings() {
is(countMessageNodes(), 0, "the log nodes are hidden when searching for " +
"the string \"foo\"bar'baz\"boo'\"");
HUDService.lastFinishedRequestCallback = null;
lastRequest = null;
requestCallback = null;
finishTest();
}

View File

@ -21,47 +21,47 @@ const TEST_DATA_JSON_CONTENT =
let lastRequest = null;
let requestCallback = null;
let lastActivity = null;
function test()
{
addTab("data:text/html;charset=utf-8,Web Console network logging tests");
browser.addEventListener("load", function() {
browser.removeEventListener("load", arguments.callee, true);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole();
openConsole(null, function(aHud) {
hud = aHud;
hud = HUDService.getHudByWindow(content);
ok(hud, "Web Console is now open");
HUDService.lastFinishedRequestCallback = function(aRequest) {
lastRequest = aRequest.log.entries[0];
lastActivity = aRequest;
if (requestCallback) {
requestCallback();
}
};
HUDService.lastFinishedRequestCallback = function(aRequest) {
lastRequest = aRequest;
if (requestCallback) {
requestCallback();
}
};
executeSoon(testPageLoad);
executeSoon(testPageLoad);
});
}, true);
}
function testPageLoad()
{
browser.addEventListener("load", function(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);
requestCallback = function() {
// Check if page load was logged correctly.
ok(lastRequest, "Page load was logged");
is(lastRequest.url, TEST_NETWORK_REQUEST_URI,
is(lastRequest.request.url, TEST_NETWORK_REQUEST_URI,
"Logged network entry is page load");
is(lastRequest.method, "GET", "Method is correct");
ok(!("body" in lastRequest.request), "No request body was stored");
ok(!("body" in lastRequest.response), "No response body was stored");
ok(!lastRequest.response.listener, "No response listener is stored");
is(lastRequest.request.method, "GET", "Method is correct");
ok(!lastRequest.request.postData, "No request body was stored");
ok(!lastRequest.response.content.text, "No response body was stored");
lastRequest = null;
requestCallback = null;
executeSoon(testPageLoadBody);
}, true);
};
content.location = TEST_NETWORK_REQUEST_URI;
}
@ -69,17 +69,16 @@ function testPageLoad()
function testPageLoadBody()
{
// Turn on logging of request bodies and check again.
HUDService.saveRequestAndResponseBodies = true;
browser.addEventListener("load", function(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);
hud.saveRequestAndResponseBodies = true;
requestCallback = function() {
ok(lastRequest, "Page load was logged again");
is(lastRequest.response.body.indexOf("<!DOCTYPE HTML>"), 0,
is(lastRequest.response.content.text.indexOf("<!DOCTYPE HTML>"), 0,
"Response body's beginning is okay");
lastRequest = null;
requestCallback = null;
executeSoon(testXhrGet);
}, true);
};
content.location.reload();
}
@ -88,9 +87,9 @@ function testXhrGet()
{
requestCallback = function() {
ok(lastRequest, "testXhrGet() was logged");
is(lastRequest.method, "GET", "Method is correct");
is(lastRequest.request.body, null, "No request body was sent");
is(lastRequest.response.body, TEST_DATA_JSON_CONTENT,
is(lastRequest.request.method, "GET", "Method is correct");
ok(!lastRequest.request.postData, "No request body was sent");
is(lastRequest.response.content.text, TEST_DATA_JSON_CONTENT,
"Response is correct");
lastRequest = null;
@ -106,10 +105,10 @@ function testXhrPost()
{
requestCallback = function() {
ok(lastRequest, "testXhrPost() was logged");
is(lastRequest.method, "POST", "Method is correct");
is(lastRequest.request.body, "Hello world!",
is(lastRequest.request.method, "POST", "Method is correct");
is(lastRequest.request.postData.text, "Hello world!",
"Request body was logged");
is(lastRequest.response.body, TEST_DATA_JSON_CONTENT,
is(lastRequest.response.content.text, TEST_DATA_JSON_CONTENT,
"Response is correct");
lastRequest = null;
@ -125,23 +124,21 @@ function testFormSubmission()
{
// Start the form submission test. As the form is submitted, the page is
// loaded again. Bind to the load event to catch when this is done.
browser.addEventListener("load", function(aEvent) {
browser.removeEventListener(aEvent.type, arguments.callee, true);
requestCallback = function() {
ok(lastRequest, "testFormSubmission() was logged");
is(lastRequest.method, "POST", "Method is correct");
isnot(lastRequest.request.body.
is(lastRequest.request.method, "POST", "Method is correct");
isnot(lastRequest.request.postData.text.
indexOf("Content-Type: application/x-www-form-urlencoded"), -1,
"Content-Type is correct");
isnot(lastRequest.request.body.
isnot(lastRequest.request.postData.text.
indexOf("Content-Length: 20"), -1, "Content-length is correct");
isnot(lastRequest.request.body.
isnot(lastRequest.request.postData.text.
indexOf("name=foo+bar&age=144"), -1, "Form data is correct");
ok(lastRequest.response.body.indexOf("<!DOCTYPE HTML>") == 0,
is(lastRequest.response.content.text.indexOf("<!DOCTYPE HTML>"), 0,
"Response body's beginning is okay");
executeSoon(testNetworkPanel);
}, true);
};
let form = content.document.querySelector("form");
ok(form, "we have the HTML form");
@ -152,19 +149,19 @@ function testNetworkPanel()
{
// Open the NetworkPanel. The functionality of the NetworkPanel is tested
// within separate test files.
let networkPanel = HUDService.openNetworkPanel(hud.filterBox, lastRequest);
is(networkPanel, lastRequest.panels[0].get(),
"Network panel stored on lastRequest object");
let networkPanel = HUDService.openNetworkPanel(hud.filterBox, lastActivity);
is(networkPanel, hud.filterBox._netPanel,
"Network panel stored on anchor node");
networkPanel.panel.addEventListener("load", function(aEvent) {
networkPanel.panel.removeEventListener(aEvent.type, arguments.callee,
true);
networkPanel.panel.addEventListener("load", function onLoad(aEvent) {
networkPanel.panel.removeEventListener(aEvent.type, onLoad, true);
ok(true, "NetworkPanel was opened");
// All tests are done. Shutdown.
networkPanel.panel.hidePopup();
lastRequest = null;
lastActivity = null;
HUDService.lastFinishedRequestCallback = null;
executeSoon(finishTest);
}, true);

View File

@ -68,25 +68,39 @@ function testGen() {
let l10n = tempScope.WebConsoleUtils.l10n;
tempScope = null;
var httpActivity = {
url: "http://www.testpage.com",
method: "GET",
panels: [],
request: {
header: {
foo: "bar"
}
let httpActivity = {
meta: {
stages: [],
discardRequestBody: true,
discardResponseBody: true,
},
log: {
entries: [{
startedDateTime: (new Date()).toISOString(),
request: {
url: "http://www.testpage.com",
method: "GET",
cookies: [],
headers: [
{ name: "foo", value: "bar" },
],
},
response: {
headers: [],
content: {},
},
timings: {},
}],
},
response: { },
timing: {
"REQUEST_HEADER": 0
}
};
let entry = httpActivity.log.entries[0];
let networkPanel = HUDService.openNetworkPanel(filterBox, httpActivity);
is (networkPanel, httpActivity.panels[0].get(), "Network panel stored on httpActivity object");
is(filterBox._netPanel, networkPanel,
"Network panel stored on the anchor object");
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
testDriver.next();
@ -94,6 +108,8 @@ function testGen() {
yield;
info("test 1");
checkIsVisible(networkPanel, {
requestCookie: false,
requestFormData: false,
@ -110,8 +126,11 @@ function testGen() {
checkNodeKeyValue(networkPanel, "requestHeadersContent", "foo", "bar");
// Test request body.
httpActivity.request.body = "hello world";
info("test 2: request body");
httpActivity.meta.discardRequestBody = false;
entry.request.postData = { text: "hello world" };
networkPanel.update();
checkIsVisible(networkPanel, {
requestBody: true,
requestFormData: false,
@ -125,13 +144,19 @@ function testGen() {
checkNodeContent(networkPanel, "requestBodyContent", "hello world");
// Test response header.
httpActivity.timing.RESPONSE_HEADER = 1000;
httpActivity.response.status = "999 earthquake win";
httpActivity.response.header = {
"Content-Type": "text/html",
leaveHouses: "true"
}
info("test 3: response header");
entry.timings.wait = 10;
entry.response.httpVersion = "HTTP/3.14";
entry.response.status = 999;
entry.response.statusText = "earthquake win";
entry.response.content.mimeType = "text/html";
entry.response.headers.push(
{ name: "Content-Type", value: "text/html" },
{ name: "leaveHouses", value: "true" }
);
networkPanel.update();
checkIsVisible(networkPanel, {
requestBody: true,
requestFormData: false,
@ -143,13 +168,14 @@ function testGen() {
responseImageCached: false
});
checkNodeContent(networkPanel, "header", "999 earthquake win");
checkNodeContent(networkPanel, "header", "HTTP/3.14 999 earthquake win");
checkNodeKeyValue(networkPanel, "responseHeadersContent", "leaveHouses", "true");
checkNodeContent(networkPanel, "responseHeadersInfo", "1ms");
checkNodeContent(networkPanel, "responseHeadersInfo", "10ms");
httpActivity.timing.RESPONSE_COMPLETE = 2500;
// This is necessary to show that the request is done.
httpActivity.timing.TRANSACTION_CLOSE = 2500;
info("test 4");
httpActivity.meta.discardResponseBody = false;
entry.timings.receive = 2;
networkPanel.update();
checkIsVisible(networkPanel, {
@ -163,7 +189,9 @@ function testGen() {
responseImageCached: false
});
httpActivity.response.isDone = true;
info("test 5");
httpActivity.meta.stages.push("REQUEST_STOP", "TRANSACTION_CLOSE");
networkPanel.update();
checkNodeContent(networkPanel, "responseNoBodyInfo", "2ms");
@ -180,11 +208,17 @@ function testGen() {
networkPanel.panel.hidePopup();
// Second run: Test for cookies and response body.
httpActivity.request.header.Cookie = "foo=bar; hello=world";
httpActivity.response.body = "get out here";
info("test 6: cookies and response body");
entry.request.cookies.push(
{ name: "foo", value: "bar" },
{ name: "hello", value: "world" }
);
entry.response.content.text = "get out here";
networkPanel = HUDService.openNetworkPanel(filterBox, httpActivity);
is (networkPanel, httpActivity.panels[1].get(), "Network panel stored on httpActivity object");
is(filterBox._netPanel, networkPanel,
"Network panel stored on httpActivity object");
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
testDriver.next();
@ -192,7 +226,6 @@ function testGen() {
yield;
checkIsVisible(networkPanel, {
requestBody: true,
requestFormData: false,
@ -212,8 +245,10 @@ function testGen() {
networkPanel.panel.hidePopup();
// Check image request.
httpActivity.response.header["Content-Type"] = "image/png";
httpActivity.url = TEST_IMG;
info("test 7: image request");
entry.response.headers[1].value = "image/png";
entry.response.content.mimeType = "image/png";
entry.request.url = TEST_IMG;
networkPanel = HUDService.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() {
@ -259,7 +294,10 @@ function testGen() {
}
// Check cached image request.
httpActivity.response.status = "HTTP/1.1 304 Not Modified";
info("test 8: cached image request");
entry.response.httpVersion = "HTTP/1.1";
entry.response.status = 304;
entry.response.statusText = "Not Modified";
networkPanel = HUDService.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() {
@ -286,11 +324,12 @@ function testGen() {
networkPanel.panel.hidePopup();
// Test sent form data.
httpActivity.request.body = [
"Content-Type: application/x-www-form-urlencoded\n" +
"Content-Length: 59\n" +
info("test 9: sent form data");
entry.request.postData.text = [
"Content-Type: application/x-www-form-urlencoded",
"Content-Length: 59",
"name=rob&age=20"
].join("");
].join("\n");
networkPanel = HUDService.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() {
@ -316,7 +355,8 @@ function testGen() {
networkPanel.panel.hidePopup();
// Test no space after Content-Type:
httpActivity.request.body = "Content-Type:application/x-www-form-urlencoded\n";
info("test 10: no space after Content-Type header in post data");
entry.request.postData.text = "Content-Type:application/x-www-form-urlencoded\n";
networkPanel = HUDService.openNetworkPanel(filterBox, httpActivity);
networkPanel.panel.addEventListener("load", function onLoad() {
@ -341,25 +381,18 @@ function testGen() {
// Test cached data.
// Load a Latin-1 encoded page.
browser.addEventListener("load", function onLoad () {
browser.removeEventListener("load", onLoad, true);
httpActivity.charset = content.document.characterSet;
testDriver.next();
}, true);
browser.contentWindow.wrappedJSObject.document.location = TEST_ENCODING_ISO_8859_1;
info("test 11: cached data");
yield;
httpActivity.url = TEST_ENCODING_ISO_8859_1;
httpActivity.response.header["Content-Type"] = "application/json";
httpActivity.response.body = "";
entry.request.url = TEST_ENCODING_ISO_8859_1;
entry.response.headers[1].value = "application/json";
entry.response.content.mimeType = "application/json";
entry.response.content.text = "my cached data is here!";
networkPanel = HUDService.openNetworkPanel(filterBox, httpActivity);
networkPanel.isDoneCallback = function NP_doneCallback() {
networkPanel.isDoneCallback = null;
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
testDriver.next();
}
}, true);
yield;
@ -375,21 +408,22 @@ function testGen() {
responseImageCached: false
});
checkNodeContent(networkPanel, "responseBodyCachedContent", "<body>\u00fc\u00f6\u00E4</body>");
checkNodeContent(networkPanel, "responseBodyCachedContent",
"my cached data is here!");
networkPanel.panel.hidePopup();
// Test a response with a content type that can't be displayed in the
// NetworkPanel.
httpActivity.response.header["Content-Type"] = "application/x-shockwave-flash";
info("test 12: unknown content type");
entry.response.headers[1].value = "application/x-shockwave-flash";
entry.response.content.mimeType = "application/x-shockwave-flash";
networkPanel = HUDService.openNetworkPanel(filterBox, httpActivity);
networkPanel.isDoneCallback = function NP_doneCallback() {
networkPanel.isDoneCallback = null;
try {
testDriver.next();
} catch (e if e instanceof StopIteration) {
}
}
networkPanel.panel.addEventListener("load", function onLoad() {
networkPanel.panel.removeEventListener("load", onLoad, true);
testDriver.next();
}, true);
yield;
@ -453,4 +487,6 @@ function testGen() {
// All done!
testDriver = null;
executeSoon(finishTest);
yield;
}

View File

@ -306,6 +306,7 @@ addKeywordTitleAutoFill=Search %S
# TabView
tabView2.title=%S - Group Your Tabs
tabview2.moveToUnnamedGroup.label=%S and %S more
extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.name=Default
extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.description=The default theme.

View File

@ -42,22 +42,6 @@ pauseTooltip=Click to pause
# button when the debugger is in a paused state.
resumeTooltip=Click to resume
# LOCALIZATION NOTE (localScope): The label that is displayed in the variables
# pane as a header on the local scope container.
localScope=Local
# LOCALIZATION NOTE (globalScope): The label that is displayed in the variables
# pane as a header on the globel scope container.
globalScope=Global
# LOCALIZATION NOTE (withScope): The label that is displayed in the variables
# pane as a header on the container for identifiers in a with block.
withScope=With block
# LOCALIZATION NOTE (closureScope): The label that is displayed in the variables
# pane as a header on container for identifiers in a closure scope.
closureScope=Closure
# LOCALIZATION NOTE (emptyStackText): The text that is displayed in the stack
# frames list when there are no frames to display.
emptyStackText=No stacks to display.
@ -72,3 +56,17 @@ loadingText=Loading\u2026
# external resource file.
# %1$S=URL, %2$S=status code
loadingError=Error loading %1$S: %2$S
# LOCALIZATION NOTE (emptyVariablesText): The text that is displayed in the
# variables pane when there are no variables to display.
emptyVariablesText=No variables to display.
# LOCALIZATION NOTE (scopeLabel): The text that is displayed in the variables
# pane as a header for each variable scope (e.g. "Global scope, "With scope",
# etc.).
scopeLabel=%S scope
# LOCALIZATION NOTE (globalScopeLabel): The name of the global scope. This text
# is added to scopeLabel and displayed in the variables pane as a header for
# the global scope.
globalScopeLabel=Global

View File

@ -178,6 +178,28 @@ prefListSearchDesc=Filter the list of settings displayed
# for help on what it does.
prefListSearchManual=Search for the given string in the list of available preferences
# LOCALIZATION NOTE (prefShowDesc): A very short description of the 'pref
# show' command. This string is designed to be shown in a menu alongside the
# command name, which is why it should be as short as possible. See
# prefShowManual for a fuller description of what it does.
prefShowDesc=Display setting value
# LOCALIZATION NOTE (prefShowManual): A fuller description of the 'pref show'
# command. Displayed when the user asks for help on what it does.
prefShowManual=Display the value of a given preference
# LOCALIZATION NOTE (prefShowSettingDesc): A short description of the
# 'setting' parameter to the 'pref show' command. See prefShowSettingManual
# for a fuller description of what it does. This string is designed to be
# shown in a dialog with restricted space, which is why it should be as short
# as possible.
prefShowSettingDesc=Setting to display
# LOCALIZATION NOTE (prefShowSettingManual): A fuller description of the
# 'setting' parameter to the 'pref show' command. Displayed when the user asks
# for help on what it does.
prefShowSettingManual=The name of the setting to display
# LOCALIZATION NOTE (prefSetDesc): A very short description of the 'pref set'
# command. This string is designed to be shown in a menu alongside the command
# name, which is why it should be as short as possible. See prefSetManual for
@ -272,6 +294,31 @@ introDesc=Show the opening message
# command. Displayed when the user asks for help on what it does.
introManual=Redisplay the message that is shown to new users until they click the 'Got it!' button
# LOCALIZATION NOTE (introTextOpening): The 'intro text' opens when the user
# first opens the developer toolbar to explain the command line, and is shown
# each time it is opened until the user clicks the 'Got it!' button. This
# string is the opening paragraph of the intro text.
introTextOpening=The Firefox command line is designed for developers. It focuses on speed of input over JavaScript syntax and a rich display over monospace output.
# LOCALIZATION NOTE (introTextCommands): For information about the 'intro
# text' see introTextOpening. The second paragraph is in 2 sections, the first
# section points the user to the 'help' command.
introTextCommands=For a list of commands type
# LOCALIZATION NOTE (introTextKeys): For information about the 'intro text'
# see introTextOpening. The second section in the second paragraph points the
# user to the F1/Escape keys which show and hide hints.
introTextKeys=or to show/hide command hints press
# LOCALIZATION NOTE (introTextF1Escape): For information about the 'intro
# text' see introTextOpening. This string is used with introTextKeys, and
# contains the keys that are pressed to open and close hints.
introTextF1Escape=F1/Escape
# LOCALIZATION NOTE (introTextGo): For information about the 'intro text' see
# introTextOpening. The text on the button that dismisses the intro text.
introTextGo=Got it!
# LOCALIZATION NOTE (hideIntroDesc): Short description of the 'hideIntro'
# setting. Displayed when the user asks for help on the settings.
hideIntroDesc=Show the initial welcome message

View File

@ -195,7 +195,7 @@
* Animations
*/
.details[open] {
.details[open][animated] {
-moz-animation-duration: 0.25s;
-moz-animation-name: showblock;
}

View File

@ -194,7 +194,7 @@
* Animations
*/
.details[open] {
.details[open][animated] {
-moz-animation-duration: 0.25s;
-moz-animation-name: showblock;
}

View File

@ -198,7 +198,7 @@
* Animations
*/
.details[open] {
.details[open][animated] {
-moz-animation-duration: 0.25s;
-moz-animation-name: showblock;
}

View File

@ -1078,6 +1078,10 @@ ObjectActor.prototype = {
message: "cannot access the environment of this function." };
}
// XXX: the following call of env.form() won't work until bug 747514 lands.
// We can't get to the frame that defined this function's environment,
// neither here, nor during ObjectActor's construction. Luckily, we don't
// use the 'scope' request in the debugger frontend.
return { name: this.obj.name || null,
scope: envActor.form(this.obj) };
},
@ -1197,14 +1201,7 @@ FrameActor.prototype = {
type: this.frame.type };
if (this.frame.type === "call") {
form.callee = this.threadActor.createValueGrip(this.frame.callee);
if (this.frame.callee.name) {
form.calleeName = this.frame.callee.name;
} else {
let desc = this.frame.callee.getOwnPropertyDescriptor("displayName");
if (desc && desc.value && typeof desc.value == "string") {
form.calleeName = desc.value;
}
}
form.calleeName = getFunctionName(this.frame.callee);
}
let envActor = this.threadActor
@ -1335,13 +1332,13 @@ EnvironmentActor.prototype = {
/**
* Returns an environment form for use in a protocol message. Note that the
* requirement of passing the frame or function as a parameter is only
* temporary, since when bug 747514 lands, the environment will have a callee
* property that will contain it.
* requirement of passing the frame as a parameter is only temporary, since
* when bug 747514 lands, the environment will have a callee property that
* will contain it.
*
* @param object aObject
* The stack frame or function object whose environment bindings are
* being generated.
* @param Debugger.Frame aObject
* The stack frame object whose environment bindings are being
* generated.
*/
form: function EA_form(aObject) {
// Debugger.Frame might be dead by the time we get here, which will cause
@ -1353,24 +1350,29 @@ EnvironmentActor.prototype = {
let parent;
if (this.obj.parent) {
let thread = this.threadActor;
parent = thread.createEnvironmentActor(this.obj.parent.environment,
parent = thread.createEnvironmentActor(this.obj.parent,
this.registeredPool);
}
// Deduce the frame that created the parent scope in order to pass it to
// parent.form(). TODO: this can be removed after bug 747514 is done.
let parentFrame = aObject;
if (this.obj.type == "declarative" && aObject.older) {
parentFrame = aObject.older;
}
let form = { actor: this.actorID,
parent: parent ? parent.form(this.obj.parent) : parent };
parent: parent ? parent.form(parentFrame) : parent };
if (aObject.type == "object") {
if (this.obj.parent) {
form.type = "with";
} else {
form.type = "object";
}
form.object = this.threadActor.createValueGrip(aObject.object);
} else {
if (aObject.class == "Function") {
if (this.obj.type == "with") {
form.type = "with";
form.object = this.threadActor.createValueGrip(this.obj.object);
} else if (this.obj.type == "object") {
form.type = "object";
form.object = this.threadActor.createValueGrip(this.obj.object);
} else { // this.obj.type == "declarative"
if (aObject.callee) {
form.type = "function";
form.function = this.threadActor.createValueGrip(aObject);
form.functionName = aObject.name;
form.function = this.threadActor.createValueGrip(aObject.callee);
form.functionName = getFunctionName(aObject.callee);
} else {
form.type = "block";
}
@ -1382,14 +1384,14 @@ EnvironmentActor.prototype = {
/**
* Return the identifier bindings object as required by the remote protocol
* specification. Note that the requirement of passing the frame or function
* as a parameter is only temporary, since when bug 747514 lands, the
* environment will have a callee property that will contain it.
* specification. Note that the requirement of passing the frame as a
* parameter is only temporary, since when bug 747514 lands, the environment
* will have a callee property that will contain it.
*
* @param object aObject [optional]
* The stack frame or function object whose environment bindings are
* being generated. When left unspecified, the bindings do not contain
* an 'arguments' property.
* @param Debugger.Frame aObject [optional]
* The stack frame whose environment bindings are being generated. When
* left unspecified, the bindings do not contain an 'arguments'
* property.
*/
_bindings: function EA_bindings(aObject) {
let bindings = { arguments: [], variables: {} };
@ -1514,3 +1516,22 @@ EnvironmentActor.prototype.requestTypes = {
"assign": EnvironmentActor.prototype.onAssign,
"bindings": EnvironmentActor.prototype.onBindings
};
/**
* Helper function to deduce the name of the provided function.
*
* @param Debugger.Object aFunction
* The function whose name will be returned.
*/
function getFunctionName(aFunction) {
let name;
if (aFunction.name) {
name = aFunction.name;
} else {
let desc = aFunction.getOwnPropertyDescriptor("displayName");
if (desc && desc.value && typeof desc.value == "string") {
name = desc.value;
}
}
return name;
}

View File

@ -0,0 +1,62 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check a frame actor's parent bindings.
*/
var gDebuggee;
var gClient;
var gThreadClient;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-stack");
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient.connect(function() {
attachTestGlobalClientAndResume(gClient, "test-stack", function(aResponse, aThreadClient) {
gThreadClient = aThreadClient;
test_pause_frame();
});
});
do_test_pending();
}
function test_pause_frame()
{
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
let parentEnv = aPacket.frame.environment.parent;
let bindings = parentEnv.bindings;
let args = bindings.arguments;
let vars = bindings.variables;
do_check_neq(parentEnv, undefined);
do_check_eq(args.length, 0);
do_check_eq(vars.stopMe.value.type, "object");
do_check_eq(vars.stopMe.value.class, "Function");
do_check_true(!!vars.stopMe.value.actor);
parentEnv = parentEnv.parent;
do_check_neq(parentEnv, undefined);
let objClient = gThreadClient.pauseGrip(parentEnv.object);
objClient.getPrototypeAndProperties(function(aResponse) {
do_check_eq(aResponse.ownProperties.Object.value.type, "object");
do_check_eq(aResponse.ownProperties.Object.value.class, "Function");
do_check_true(!!aResponse.ownProperties.Object.value.actor);
gThreadClient.resume(function() {
finishClient(gClient);
});
});
});
gDebuggee.eval("(" + function() {
function stopMe(aNumber, aBool, aString, aNull, aUndefined, aObject) {
var a = 1;
var b = true;
var c = { a: "a" };
debugger;
};
stopMe(42, true, "nasu", null, undefined, { foo: "bar" });
} + ")()");
}

View File

@ -0,0 +1,69 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check a |with| frame actor's bindings.
*/
var gDebuggee;
var gClient;
var gThreadClient;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-stack");
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient.connect(function() {
attachTestGlobalClientAndResume(gClient, "test-stack", function(aResponse, aThreadClient) {
gThreadClient = aThreadClient;
test_pause_frame();
});
});
do_test_pending();
}
function test_pause_frame()
{
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
let env = aPacket.frame.environment;
do_check_neq(env, undefined);
let parentEnv = env.parent;
do_check_neq(parentEnv, undefined);
let bindings = parentEnv.bindings;
let args = bindings.arguments;
let vars = bindings.variables;
do_check_eq(args.length, 1);
do_check_eq(args[0].aNumber.value, 10);
do_check_eq(vars.r.value, 10);
do_check_eq(vars.a.value, Math.PI * 100);
do_check_eq(vars.arguments.value.class, "Arguments");
do_check_true(!!vars.arguments.value.actor);
let objClient = gThreadClient.pauseGrip(env.object);
objClient.getPrototypeAndProperties(function(aResponse) {
do_check_eq(aResponse.ownProperties.PI.value, Math.PI);
do_check_eq(aResponse.ownProperties.cos.value.type, "object");
do_check_eq(aResponse.ownProperties.cos.value.class, "Function");
do_check_true(!!aResponse.ownProperties.cos.value.actor);
gThreadClient.resume(function() {
finishClient(gClient);
});
});
});
gDebuggee.eval("(" + function() {
function stopMe(aNumber) {
var a;
var r = aNumber;
with (Math) {
a = PI * r * r;
debugger;
}
};
stopMe(10);
} + ")()");
}

View File

@ -0,0 +1,84 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check the environment bindongs of a |with| within a |with|.
*/
var gDebuggee;
var gClient;
var gThreadClient;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-stack");
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient.connect(function() {
attachTestGlobalClientAndResume(gClient, "test-stack", function(aResponse, aThreadClient) {
gThreadClient = aThreadClient;
test_pause_frame();
});
});
do_test_pending();
}
function test_pause_frame()
{
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
let env = aPacket.frame.environment;
do_check_neq(env, undefined);
let objClient = gThreadClient.pauseGrip(env.object);
objClient.getPrototypeAndProperties(function(aResponse) {
do_check_eq(aResponse.ownProperties.one.value, 1);
do_check_eq(aResponse.ownProperties.two.value, 2);
do_check_eq(aResponse.ownProperties.foo, undefined);
let parentEnv = env.parent;
do_check_neq(parentEnv, undefined);
let parentClient = gThreadClient.pauseGrip(parentEnv.object);
parentClient.getPrototypeAndProperties(function(aResponse) {
do_check_eq(aResponse.ownProperties.PI.value, Math.PI);
do_check_eq(aResponse.ownProperties.cos.value.type, "object");
do_check_eq(aResponse.ownProperties.cos.value.class, "Function");
do_check_true(!!aResponse.ownProperties.cos.value.actor);
parentEnv = parentEnv.parent;
do_check_neq(parentEnv, undefined);
let bindings = parentEnv.bindings;
let args = bindings.arguments;
let vars = bindings.variables;
do_check_eq(args.length, 1);
do_check_eq(args[0].aNumber.value, 10);
do_check_eq(vars.r.value, 10);
do_check_eq(vars.a.value, Math.PI * 100);
do_check_eq(vars.arguments.value.class, "Arguments");
do_check_true(!!vars.arguments.value.actor);
do_check_eq(vars.foo.value, 2 * Math.PI);
gThreadClient.resume(function() {
finishClient(gClient);
});
});
});
});
gDebuggee.eval("(" + function() {
function stopMe(aNumber) {
var a, obj = { one: 1, two: 2 };
var r = aNumber;
with (Math) {
a = PI * r * r;
with (obj) {
var foo = two * PI;
debugger;
}
}
};
stopMe(10);
} + ")()");
}

View File

@ -0,0 +1,62 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check the environment bindings of a |with| in global scope.
*/
var gDebuggee;
var gClient;
var gThreadClient;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-stack");
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient.connect(function() {
attachTestGlobalClientAndResume(gClient, "test-stack", function(aResponse, aThreadClient) {
gThreadClient = aThreadClient;
test_pause_frame();
});
});
do_test_pending();
}
function test_pause_frame()
{
gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
let env = aPacket.frame.environment;
do_check_neq(env, undefined);
let objClient = gThreadClient.pauseGrip(env.object);
objClient.getPrototypeAndProperties(function(aResponse) {
do_check_eq(aResponse.ownProperties.PI.value, Math.PI);
do_check_eq(aResponse.ownProperties.cos.value.type, "object");
do_check_eq(aResponse.ownProperties.cos.value.class, "Function");
do_check_true(!!aResponse.ownProperties.cos.value.actor);
let parentEnv = env.parent;
do_check_neq(parentEnv, undefined);
let parentClient = gThreadClient.pauseGrip(parentEnv.object);
parentClient.getPrototypeAndProperties(function(aResponse) {
do_check_eq(aResponse.ownProperties.a.value, Math.PI * 100);
do_check_eq(aResponse.ownProperties.r.value, 10);
do_check_eq(aResponse.ownProperties.Object.value.type, "object");
do_check_eq(aResponse.ownProperties.Object.value.class, "Function");
do_check_true(!!aResponse.ownProperties.Object.value.actor);
gThreadClient.resume(function() {
finishClient(gClient);
});
});
});
});
gDebuggee.eval("var a, r = 10;\n" +
"with (Math) {\n" +
" a = PI * r * r;\n" +
" debugger;\n" +
"}");
}

View File

@ -54,3 +54,7 @@ tail =
[test_stepping-03.js]
[test_stepping-04.js]
[test_framebindings-01.js]
[test_framebindings-02.js]
[test_framebindings-03.js]
[test_framebindings-04.js]
[test_framebindings-05.js]

View File

@ -322,7 +322,7 @@ AddonCompatibilityOverride.prototype = {
* A type of add-on, used by the UI to determine how to display different types
* of add-ons.
*
* @param aId
* @param aID
* The add-on type ID
* @param aLocaleURI
* The URI of a localized properties file to get the displayable name
@ -340,15 +340,15 @@ AddonCompatibilityOverride.prototype = {
* An option set of flags that customize the display of the add-on in
* the UI.
*/
function AddonType(aId, aLocaleURI, aLocaleKey, aViewType, aUIPriority, aFlags) {
if (!aId)
function AddonType(aID, aLocaleURI, aLocaleKey, aViewType, aUIPriority, aFlags) {
if (!aID)
throw new Error("An AddonType must have an ID");
if (aViewType && aUIPriority === undefined)
throw new Error("An AddonType with a defined view must have a set UI priority");
if (!aLocaleKey)
throw new Error("An AddonType must have a displayable name");
this.id = aId;
this.id = aID;
this.uiPriority = aUIPriority;
this.viewType = aViewType;
this.flags = aFlags;
@ -357,7 +357,7 @@ function AddonType(aId, aLocaleURI, aLocaleKey, aViewType, aUIPriority, aFlags)
this.__defineGetter__("name", function() {
delete this.name;
let bundle = Services.strings.createBundle(aLocaleURI);
this.name = bundle.GetStringFromName(aLocaleKey.replace("%ID%", aId));
this.name = bundle.GetStringFromName(aLocaleKey.replace("%ID%", aID));
return this.name;
});
}
@ -555,9 +555,17 @@ var AddonManagerInternal = {
* @param aProvider
* The provider to register
* @param aTypes
* An array of add-on types
* An optional array of add-on types
*/
registerProvider: function AMI_registerProvider(aProvider, aTypes) {
if (!aProvider || typeof aProvider != "object")
throw Components.Exception("aProvider must be specified",
Cr.NS_ERROR_INVALID_ARG);
if (aTypes && !Array.isArray(aTypes))
throw Components.Exception("aTypes must be an array or null",
Cr.NS_ERROR_INVALID_ARG);
this.providers.push(aProvider);
if (aTypes) {
@ -597,6 +605,10 @@ var AddonManagerInternal = {
* The provider to unregister
*/
unregisterProvider: function AMI_unregisterProvider(aProvider) {
if (!aProvider || typeof aProvider != "object")
throw Components.Exception("aProvider must be specified",
Cr.NS_ERROR_INVALID_ARG);
let pos = 0;
while (pos < this.providers.length) {
if (this.providers[pos] == aProvider)
@ -739,15 +751,27 @@ var AddonManagerInternal = {
* appropriate values.
*
* @param aAddon
* The AddonInternal representing the add-on
* The Addon representing the add-on
* @param aUri
* The uri to escape
* The string representation of the URI to escape
* @param aAppVersion
* The optional application version to use for %APP_VERSION%
* @return the appropriately escaped uri.
* @return The appropriately escaped URI.
*/
escapeAddonURI: function AMI_escapeAddonURI(aAddon, aUri, aAppVersion)
{
if (!aAddon || typeof aAddon != "object")
throw Components.Exception("aAddon must be an Addon object",
Cr.NS_ERROR_INVALID_ARG);
if (!aUri || typeof aUri != "string")
throw Components.Exception("aUri must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (aAppVersion && typeof aAppVersion != "string")
throw Components.Exception("aAppVersion must be a string or null",
Cr.NS_ERROR_INVALID_ARG);
var addonStatus = aAddon.userDisabled || aAddon.softDisabled ? "userDisabled"
: "userEnabled";
@ -977,6 +1001,14 @@ var AddonManagerInternal = {
* The ID of the add-on
*/
addStartupChange: function AMI_addStartupChange(aType, aID) {
if (!aType || typeof aType != "string")
throw Components.Exception("aType must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (!aID || typeof aID != "string")
throw Components.Exception("aID must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (gStarted)
return;
@ -998,6 +1030,14 @@ var AddonManagerInternal = {
* The ID of the add-on
*/
removeStartupChange: function AMI_removeStartupChange(aType, aID) {
if (!aType || typeof aType != "string")
throw Components.Exception("aType must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (!aID || typeof aID != "string")
throw Components.Exception("aID must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (gStarted)
return;
@ -1015,6 +1055,10 @@ var AddonManagerInternal = {
* The method on the listeners to call
*/
callManagerListeners: function AMI_callManagerListeners(aMethod) {
if (!aMethod || typeof aMethod != "string")
throw Components.Exception("aMethod must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
var args = Array.slice(arguments, 1);
this.managerListeners.forEach(function(listener) {
try {
@ -1034,10 +1078,18 @@ var AddonManagerInternal = {
* @param aMethod
* The method on the listeners to call
* @param aExtraListeners
* An array of extra InstallListeners to also call
* An optional array of extra InstallListeners to also call
* @return false if any of the listeners returned false, true otherwise
*/
callInstallListeners: function AMI_callInstallListeners(aMethod, aExtraListeners) {
if (!aMethod || typeof aMethod != "string")
throw Components.Exception("aMethod must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (aExtraListeners && !Array.isArray(aExtraListeners))
throw Components.Exception("aExtraListeners must be an array or null",
Cr.NS_ERROR_INVALID_ARG);
let result = true;
let listeners = this.installListeners;
if (aExtraListeners)
@ -1066,6 +1118,10 @@ var AddonManagerInternal = {
* The method on the listeners to call
*/
callAddonListeners: function AMI_callAddonListeners(aMethod) {
if (!aMethod || typeof aMethod != "string")
throw Components.Exception("aMethod must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
var args = Array.slice(arguments, 1);
this.addonListeners.forEach(function(listener) {
try {
@ -1083,17 +1139,25 @@ var AddonManagerInternal = {
* add-on only supports a single add-on being enabled at a time. This allows
* the providers to disable theirs if necessary.
*
* @param aId
* The id of the enabled add-on
* @param aID
* The ID of the enabled add-on
* @param aType
* The type of the enabled add-on
* @param aPendingRestart
* A boolean indicating if the change will only take place the next
* time the application is restarted
*/
notifyAddonChanged: function AMI_notifyAddonChanged(aId, aType, aPendingRestart) {
notifyAddonChanged: function AMI_notifyAddonChanged(aID, aType, aPendingRestart) {
if (aID && typeof aID != "string")
throw Components.Exception("aID must be a string or null",
Cr.NS_ERROR_INVALID_ARG);
if (!aType || typeof aType != "string")
throw Components.Exception("aType must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
this.providers.forEach(function(provider) {
callProvider(provider, "addonChanged", null, aId, aType, aPendingRestart);
callProvider(provider, "addonChanged", null, aID, aType, aPendingRestart);
});
},
@ -1116,8 +1180,8 @@ var AddonManagerInternal = {
* Function to call when operation is complete.
*/
updateAddonRepositoryData: function AMI_updateAddonRepositoryData(aCallback) {
if (!aCallback)
throw Components.Exception("Must specify aCallback",
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
new AsyncObjectCaller(this.providers, "updateAddonRepositoryData", {
@ -1132,11 +1196,12 @@ var AddonManagerInternal = {
}
});
},
/**
* Asynchronously gets an AddonInstall for a URL.
*
* @param aUrl
* The url the add-on is located at
* The string represenation of the URL the add-on is located at
* @param aCallback
* A callback to pass the AddonInstall to
* @param aMimetype
@ -1156,8 +1221,37 @@ var AddonManagerInternal = {
getInstallForURL: function AMI_getInstallForURL(aUrl, aCallback, aMimetype,
aHash, aName, aIconURL,
aVersion, aLoadGroup) {
if (!aUrl || !aMimetype || !aCallback)
throw new TypeError("Invalid arguments");
if (!aUrl || typeof aUrl != "string")
throw Components.Exception("aURL must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
if (!aMimetype || typeof aMimetype != "string")
throw Components.Exception("aMimetype must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (aHash && typeof aHash != "string")
throw Components.Exception("aHash must be a string or null",
Cr.NS_ERROR_INVALID_ARG);
if (aName && typeof aName != "string")
throw Components.Exception("aName must be a string or null",
Cr.NS_ERROR_INVALID_ARG);
if (aIconURL && typeof aIconURL != "string")
throw Components.Exception("aIconURL must be a string or null",
Cr.NS_ERROR_INVALID_ARG);
if (aVersion && typeof aVersion != "string")
throw Components.Exception("aVersion must be a string or null",
Cr.NS_ERROR_INVALID_ARG);
if (aLoadGroup && (!(aLoadGroup instanceof Ci.nsILoadGroup)))
throw Components.Exception("aLoadGroup must be a nsILoadGroup or null",
Cr.NS_ERROR_INVALID_ARG);
for (let provider of this.providers) {
if (callProvider(provider, "supportsMimetype", false, aMimetype)) {
@ -1176,7 +1270,7 @@ var AddonManagerInternal = {
* Asynchronously gets an AddonInstall for an nsIFile.
*
* @param aFile
* the nsIFile where the add-on is located
* The nsIFile where the add-on is located
* @param aCallback
* A callback to pass the AddonInstall to
* @param aMimetype
@ -1184,8 +1278,17 @@ var AddonManagerInternal = {
* @throws if the aFile or aCallback arguments are not specified
*/
getInstallForFile: function AMI_getInstallForFile(aFile, aCallback, aMimetype) {
if (!aFile || !aCallback)
throw Cr.NS_ERROR_INVALID_ARG;
if (!(aFile instanceof Ci.nsIFile))
throw Components.Exception("aFile must be a nsIFile",
Cr.NS_ERROR_INVALID_ARG);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
if (aMimetype && typeof aMimetype != "string")
throw Components.Exception("aMimetype must be a string or null",
Cr.NS_ERROR_INVALID_ARG);
new AsyncObjectCaller(this.providers, "getInstallForFile", {
nextObject: function(aCaller, aProvider) {
@ -1212,11 +1315,16 @@ var AddonManagerInternal = {
* An optional array of types to retrieve. Each type is a string name
* @param aCallback
* A callback which will be passed an array of AddonInstalls
* @throws if the aCallback argument is not specified
* @throws If the aCallback argument is not specified
*/
getInstallsByTypes: function AMI_getInstallsByTypes(aTypes, aCallback) {
if (!aCallback)
throw Cr.NS_ERROR_INVALID_ARG;
if (aTypes && !Array.isArray(aTypes))
throw Components.Exception("aTypes must be an array or null",
Cr.NS_ERROR_INVALID_ARG);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
let installs = [];
@ -1253,6 +1361,10 @@ var AddonManagerInternal = {
* @return true if installation is enabled for the mimetype
*/
isInstallEnabled: function AMI_isInstallEnabled(aMimetype) {
if (!aMimetype || typeof aMimetype != "string")
throw Components.Exception("aMimetype must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
for (let provider of this.providers) {
if (callProvider(provider, "supportsMimetype", false, aMimetype) &&
callProvider(provider, "isInstallEnabled"))
@ -1268,10 +1380,18 @@ var AddonManagerInternal = {
* @param aMimetype
* The mimetype of the add-on
* @param aURI
* The uri of the source, may be null
* The optional nsIURI of the source
* @return true if the source is allowed to install this mimetype
*/
isInstallAllowed: function AMI_isInstallAllowed(aMimetype, aURI) {
if (!aMimetype || typeof aMimetype != "string")
throw Components.Exception("aMimetype must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (aURI && !(aURI instanceof Ci.nsIURI))
throw Components.Exception("aURI must be a nsIURI or null",
Cr.NS_ERROR_INVALID_ARG);
for (let provider of this.providers) {
if (callProvider(provider, "supportsMimetype", false, aMimetype) &&
callProvider(provider, "isInstallAllowed", null, aURI))
@ -1287,9 +1407,9 @@ var AddonManagerInternal = {
* @param aMimetype
* The mimetype of add-ons being installed
* @param aSource
* The nsIDOMWindow that started the installs
* The optional nsIDOMWindow that started the installs
* @param aURI
* the nsIURI that started the installs
* The optional nsIURI that started the installs
* @param aInstalls
* The array of AddonInstalls to be installed
*/
@ -1297,6 +1417,22 @@ var AddonManagerInternal = {
aSource,
aURI,
aInstalls) {
if (!aMimetype || typeof aMimetype != "string")
throw Components.Exception("aMimetype must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (aSource && !(aSource instanceof Ci.nsIDOMWindow))
throw Components.Exception("aSource must be a nsIDOMWindow or null",
Cr.NS_ERROR_INVALID_ARG);
if (aURI && !(aURI instanceof Ci.nsIURI))
throw Components.Exception("aURI must be a nsIURI or null",
Cr.NS_ERROR_INVALID_ARG);
if (!Array.isArray(aInstalls))
throw Components.Exception("aInstalls must be an array",
Cr.NS_ERROR_INVALID_ARG);
if (!("@mozilla.org/addons/web-install-listener;1" in Cc)) {
WARN("No web installer available, cancelling all installs");
aInstalls.forEach(function(aInstall) {
@ -1346,6 +1482,10 @@ var AddonManagerInternal = {
* The InstallListener to add
*/
addInstallListener: function AMI_addInstallListener(aListener) {
if (!aListener || typeof aListener != "object")
throw Components.Exception("aListener must be a InstallListener object",
Cr.NS_ERROR_INVALID_ARG);
if (!this.installListeners.some(function(i) { return i == aListener; }))
this.installListeners.push(aListener);
},
@ -1357,6 +1497,10 @@ var AddonManagerInternal = {
* The InstallListener to remove
*/
removeInstallListener: function AMI_removeInstallListener(aListener) {
if (!aListener || typeof aListener != "object")
throw Components.Exception("aListener must be a InstallListener object",
Cr.NS_ERROR_INVALID_ARG);
let pos = 0;
while (pos < this.installListeners.length) {
if (this.installListeners[pos] == aListener)
@ -1369,19 +1513,24 @@ var AddonManagerInternal = {
/**
* Asynchronously gets an add-on with a specific ID.
*
* @param aId
* @param aID
* The ID of the add-on to retrieve
* @param aCallback
* The callback to pass the retrieved add-on to
* @throws if the aId or aCallback arguments are not specified
* @throws if the aID or aCallback arguments are not specified
*/
getAddonByID: function AMI_getAddonByID(aId, aCallback) {
if (!aId || !aCallback)
throw Cr.NS_ERROR_INVALID_ARG;
getAddonByID: function AMI_getAddonByID(aID, aCallback) {
if (!aID || typeof aID != "string")
throw Components.Exception("aID must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
new AsyncObjectCaller(this.providers, "getAddonByID", {
nextObject: function(aCaller, aProvider) {
callProvider(aProvider, "getAddonByID", null, aId, function(aAddon) {
callProvider(aProvider, "getAddonByID", null, aID, function(aAddon) {
if (aAddon)
safeCall(aCallback, aAddon);
else
@ -1405,9 +1554,13 @@ var AddonManagerInternal = {
* @throws if the aGUID or aCallback arguments are not specified
*/
getAddonBySyncGUID: function AMI_getAddonBySyncGUID(aGUID, aCallback) {
if (!aGUID || !aCallback) {
throw Cr.NS_ERROR_INVALID_ARG;
}
if (!aGUID || typeof aGUID != "string")
throw Components.Exception("aGUID must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
new AsyncObjectCaller(this.providers, "getAddonBySyncGUID", {
nextObject: function(aCaller, aProvider) {
@ -1429,21 +1582,26 @@ var AddonManagerInternal = {
/**
* Asynchronously gets an array of add-ons.
*
* @param aIds
* @param aIDs
* The array of IDs to retrieve
* @param aCallback
* The callback to pass an array of Addons to
* @throws if the aId or aCallback arguments are not specified
* @throws if the aID or aCallback arguments are not specified
*/
getAddonsByIDs: function AMI_getAddonsByIDs(aIds, aCallback) {
if (!aIds || !aCallback)
throw Cr.NS_ERROR_INVALID_ARG;
getAddonsByIDs: function AMI_getAddonsByIDs(aIDs, aCallback) {
if (!Array.isArray(aIDs))
throw Components.Exception("aIDs must be an array",
Cr.NS_ERROR_INVALID_ARG);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
let addons = [];
new AsyncObjectCaller(aIds, null, {
nextObject: function(aCaller, aId) {
AddonManagerInternal.getAddonByID(aId, function(aAddon) {
new AsyncObjectCaller(aIDs, null, {
nextObject: function(aCaller, aID) {
AddonManagerInternal.getAddonByID(aID, function(aAddon) {
addons.push(aAddon);
aCaller.callNext();
});
@ -1465,8 +1623,13 @@ var AddonManagerInternal = {
* @throws if the aCallback argument is not specified
*/
getAddonsByTypes: function AMI_getAddonsByTypes(aTypes, aCallback) {
if (!aCallback)
throw Cr.NS_ERROR_INVALID_ARG;
if (aTypes && !Array.isArray(aTypes))
throw Components.Exception("aTypes must be an array or null",
Cr.NS_ERROR_INVALID_ARG);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
let addons = [];
@ -1507,8 +1670,13 @@ var AddonManagerInternal = {
*/
getAddonsWithOperationsByTypes:
function AMI_getAddonsWithOperationsByTypes(aTypes, aCallback) {
if (!aCallback)
throw Cr.NS_ERROR_INVALID_ARG;
if (aTypes && !Array.isArray(aTypes))
throw Components.Exception("aTypes must be an array or null",
Cr.NS_ERROR_INVALID_ARG);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
let addons = [];
@ -1534,6 +1702,10 @@ var AddonManagerInternal = {
* The listener to add
*/
addManagerListener: function AMI_addManagerListener(aListener) {
if (!aListener || typeof aListener != "object")
throw Components.Exception("aListener must be an AddonManagerListener object",
Cr.NS_ERROR_INVALID_ARG);
if (!this.managerListeners.some(function(i) { return i == aListener; }))
this.managerListeners.push(aListener);
},
@ -1545,6 +1717,10 @@ var AddonManagerInternal = {
* The listener to remove
*/
removeManagerListener: function AMI_removeManagerListener(aListener) {
if (!aListener || typeof aListener != "object")
throw Components.Exception("aListener must be an AddonManagerListener object",
Cr.NS_ERROR_INVALID_ARG);
let pos = 0;
while (pos < this.managerListeners.length) {
if (this.managerListeners[pos] == aListener)
@ -1558,9 +1734,13 @@ var AddonManagerInternal = {
* Adds a new AddonListener if the listener is not already registered.
*
* @param aListener
* The listener to add
* The AddonListener to add
*/
addAddonListener: function AMI_addAddonListener(aListener) {
if (!aListener || typeof aListener != "object")
throw Components.Exception("aListener must be an AddonListener object",
Cr.NS_ERROR_INVALID_ARG);
if (!this.addonListeners.some(function(i) { return i == aListener; }))
this.addonListeners.push(aListener);
},
@ -1569,9 +1749,14 @@ var AddonManagerInternal = {
* Removes an AddonListener if the listener is registered.
*
* @param aListener
* The listener to remove
* The AddonListener to remove
*/
removeAddonListener: function AMI_removeAddonListener(aListener) {
if (!aListener || typeof aListener != "object")
throw Components.Exception("aListener must be an AddonListener object",
Cr.NS_ERROR_INVALID_ARG);
let pos = 0;
while (pos < this.addonListeners.length) {
if (this.addonListeners[pos] == aListener)
@ -1581,12 +1766,32 @@ var AddonManagerInternal = {
}
},
/**
* Adds a new TypeListener if the listener is not already registered.
*
* @param aListener
* The TypeListener to add
*/
addTypeListener: function AMI_addTypeListener(aListener) {
if (!aListener || typeof aListener != "object")
throw Components.Exception("aListener must be a TypeListener object",
Cr.NS_ERROR_INVALID_ARG);
if (!this.typeListeners.some(function(i) { return i == aListener; }))
this.typeListeners.push(aListener);
},
/**
* Removes an TypeListener if the listener is registered.
*
* @param aListener
* The TypeListener to remove
*/
removeTypeListener: function AMI_removeTypeListener(aListener) {
if (!aListener || typeof aListener != "object")
throw Components.Exception("aListener must be a TypeListener object",
Cr.NS_ERROR_INVALID_ARG);
let pos = 0;
while (pos < this.typeListeners.length) {
if (this.typeListeners[pos] == aListener)
@ -1707,8 +1912,8 @@ var AddonManagerPrivate = {
AddonManagerInternal.removeStartupChange(aType, aID);
},
notifyAddonChanged: function AMP_notifyAddonChanged(aId, aType, aPendingRestart) {
AddonManagerInternal.notifyAddonChanged(aId, aType, aPendingRestart);
notifyAddonChanged: function AMP_notifyAddonChanged(aID, aType, aPendingRestart) {
AddonManagerInternal.notifyAddonChanged(aID, aType, aPendingRestart);
},
updateAddonAppDisabledStates: function AMP_updateAddonAppDisabledStates() {
@ -1913,16 +2118,16 @@ var AddonManager = {
return AddonManagerInternal.startupChanges[aType].slice(0);
},
getAddonByID: function AM_getAddonByID(aId, aCallback) {
AddonManagerInternal.getAddonByID(aId, aCallback);
getAddonByID: function AM_getAddonByID(aID, aCallback) {
AddonManagerInternal.getAddonByID(aID, aCallback);
},
getAddonBySyncGUID: function AM_getAddonBySyncGUID(aId, aCallback) {
AddonManagerInternal.getAddonBySyncGUID(aId, aCallback);
getAddonBySyncGUID: function AM_getAddonBySyncGUID(aGUID, aCallback) {
AddonManagerInternal.getAddonBySyncGUID(aGUID, aCallback);
},
getAddonsByIDs: function AM_getAddonsByIDs(aIds, aCallback) {
AddonManagerInternal.getAddonsByIDs(aIds, aCallback);
getAddonsByIDs: function AM_getAddonsByIDs(aIDs, aCallback) {
AddonManagerInternal.getAddonsByIDs(aIDs, aCallback);
},
getAddonsWithOperationsByTypes:
@ -1995,7 +2200,18 @@ var AddonManager = {
return AddonManagerInternal.addonTypes;
},
/**
* Determines whether an Addon should auto-update or not.
*
* @param aAddon
* The Addon representing the add-on
* @return true if the addon should auto-update, false otherwise.
*/
shouldAutoUpdate: function AM_shouldAutoUpdate(aAddon) {
if (!aAddon || typeof aAddon != "object")
throw Components.Exception("aAddon must be specified",
Cr.NS_ERROR_INVALID_ARG);
if (!("applyBackgroundUpdates" in aAddon))
return false;
if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE)

View File

@ -443,10 +443,10 @@ var gInstallingPage = {
actionItem.value = label;
},
onInstallEnded: function(aInstall) {
onInstallEnded: function(aInstall, aAddon) {
// Remember that this add-on was updated during startup
AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
aInstall.id);
aAddon.id);
this.startNextInstall();
},

View File

@ -61,7 +61,7 @@ function run_test_1() {
var testPlugin = get_test_plugin();
do_check_neq(testPlugin, null);
AddonManager.getAddonsByTypes("plugin", function(addons) {
AddonManager.getAddonsByTypes(["plugin"], function(addons) {
do_check_true(addons.length > 0);
addons.forEach(function(p) {