Merge mozilla-inbound and mozilla-central.

(The mozilla-central side of the merge is entirely a merge from fx-team into mozilla-central.)
This commit is contained in:
L. David Baron 2013-05-11 22:00:23 -07:00
Родитель 7fe4255fb8 594771a12d
Коммит 3d0109705b
152 изменённых файлов: 6531 добавлений и 3653 удалений

Просмотреть файл

@ -1032,7 +1032,6 @@ pref("devtools.toolbox.host", "bottom");
pref("devtools.toolbox.selectedTool", "webconsole");
pref("devtools.toolbox.toolbarSpec", '["paintflashing toggle","tilt toggle","scratchpad","resize toggle"]');
pref("devtools.toolbox.sideEnabled", true);
pref("devtools.toolbox.disabledTools", "[]");
// Enable the Inspector
pref("devtools.inspector.enabled", true);

Просмотреть файл

@ -30,6 +30,7 @@ MOCHITEST_BROWSER_FILES = \
browser_input.js \
browser_input_sample.html \
browser_pageshow.js \
browser_windowRestore_perwindowpb.js \
browser_248970_b_perwindowpb.js \
browser_248970_b_sample.html \
browser_339445.js \

Просмотреть файл

@ -0,0 +1,30 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// This test checks that closed private windows can't be restored
function test() {
waitForExplicitFinish();
// Purging the list of closed windows
while(ss.getClosedWindowCount() > 0)
ss.forgetClosedWindow(0);
// Load a private window, then close it
// and verify it doesn't get remembered for restoring
var win = OpenBrowserWindow({private: true});
whenWindowLoaded(win, function onload() {
info("The private window got loaded");
win.addEventListener("SSWindowClosing", function onclosing() {
win.removeEventListener("SSWindowClosing", onclosing, false);
executeSoon(function () {
is (ss.getClosedWindowCount(), 0,
"The private window should not have been stored");
finish();
});
}, false);
win.close();
});
}

Просмотреть файл

@ -9,12 +9,14 @@ const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]
.createBundle("chrome://branding/locale/brand.properties")
.GetStringFromName("brandShortName");
this.EXPORTED_SYMBOLS = [ "CmdAddonFlags", "CmdCommands" ];
this.EXPORTED_SYMBOLS = [ "CmdAddonFlags", "CmdCommands", "DEFAULT_DEBUG_PORT", "connect" ];
Cu.import("resource:///modules/devtools/gcli.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
Cu.import("resource://gre/modules/osfile.jsm")
Cu.import("resource://gre/modules/devtools/gcli.jsm");
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
@ -425,7 +427,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
description: gcli.lookup("calllogStartDesc"),
exec: function(args, context) {
let contentWindow = context.environment.contentDocument.defaultView;
let contentWindow = context.environment.window;
let dbg = new Debugger(contentWindow);
dbg.onEnterFrame = function(frame) {
@ -529,7 +531,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
],
exec: function(args, context) {
let globalObj;
let contentWindow = context.environment.contentDocument.defaultView;
let contentWindow = context.environment.window;
if (args.sourceType == "jsm") {
try {
@ -817,8 +819,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
name: "console clear",
description: gcli.lookup("consoleclearDesc"),
exec: function Command_consoleClear(args, context) {
let window = context.environment.contentDocument.defaultView;
let hud = HUDService.getHudByWindow(window);
let hud = HUDService.getHudByWindow(context.environment.window);
// hud will be null if the web console has not been opened for this window
if (hud) {
hud.jsterm.clearOutput();
@ -976,7 +977,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
returnType: "cookies",
exec: function Command_cookieList(args, context) {
let host = context.environment.document.location.host;
if (host == null) {
if (host == null || host == "") {
throw new Error(gcli.lookup("cookieListOutNonePage"));
}
@ -1023,7 +1024,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
let cookies = [];
while (enm.hasMoreElements()) {
let cookie = enm.getNext().QueryInterface(Components.interfaces.nsICookie);
let cookie = enm.getNext().QueryInterface(Ci.nsICookie);
if (isCookieAtHost(cookie, host)) {
if (cookie.name == args.name) {
cookieMgr.remove(cookie.host, cookie.name, cookie.path, false);
@ -1156,30 +1157,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
}
}(this));
/* CmdEcho ----------------------------------------------------------------- */
(function(module) {
/**
* 'echo' command
*/
gcli.addCommand({
name: "echo",
description: gcli.lookup("echoDesc"),
params: [
{
name: "message",
type: "string",
description: gcli.lookup("echoMessageDesc")
}
],
returnType: "string",
hidden: true,
exec: function Command_echo(args, context) {
return args.message;
}
});
}(this));
/* CmdExport --------------------------------------------------------------- */
(function(module) {
@ -1403,7 +1380,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
},
],
exec: function(args, context) {
let document = context.environment.contentDocument;
let searchTextNodes = !args.attrOnly;
let searchAttributes = !args.contentOnly;
let regexOptions = args.ignoreCase ? 'ig' : 'g';
@ -1413,7 +1389,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
attributeRegex = new RegExp(args.attributes, regexOptions);
}
let root = args.root || document;
let root = args.root || context.environment.document;
let elements = root.querySelectorAll(args.selector);
elements = Array.prototype.slice.call(elements);
@ -1498,8 +1474,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
},
],
exec: function(args, context) {
let document = context.environment.contentDocument;
let root = args.root || document;
let root = args.root || context.environment.document;
let elements = Array.prototype.slice.call(root.querySelectorAll(args.search));
let removed = 0;
@ -1555,9 +1530,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
},
],
exec: function(args, context) {
let document = context.environment.contentDocument;
let root = args.root || document;
let root = args.root || context.environment.document;
let regexOptions = args.ignoreCase ? 'ig' : 'g';
let attributeRegex = new RegExp(args.searchAttributes, regexOptions);
let elements = root.querySelectorAll(args.searchElements);
@ -1622,22 +1595,21 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
],
returnType: "string",
exec: function(args, context) {
let promise = context.createPromise();
let existsPromise = OS.File.exists(args.srcdir + "/CLOBBER");
existsPromise.then(function(exists) {
return OS.File.exists(args.srcdir + "/CLOBBER").then(function(exists) {
if (exists) {
var str = Cc["@mozilla.org/supports-string;1"]
let str = Cc["@mozilla.org/supports-string;1"]
.createInstance(Ci.nsISupportsString);
str.data = args.srcdir;
Services.prefs.setComplexValue("devtools.loader.srcdir",
Components.interfaces.nsISupportsString, str);
Ci.nsISupportsString, str);
devtools.reload();
promise.resolve(gcli.lookupFormat("toolsSrcdirReloaded", [args.srcdir]));
return;
let msg = gcli.lookupFormat("toolsSrcdirReloaded", [args.srcdir]);
throw new Error(msg);
}
promise.reject(gcli.lookupFormat("toolsSrcdirNotFound", [args.srcdir]));
return gcli.lookupFormat("toolsSrcdirNotFound", [args.srcdir]);
});
return promise;
}
});
@ -1682,7 +1654,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
* >> restart --nocache
* - restarts immediately and starts Firefox without using cache
*/
gcli.addCommand({
name: "restart",
description: gcli.lookupFormat("restartBrowserDesc", [BRAND_SHORT_NAME]),
@ -1733,7 +1704,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
name: "screenshot",
description: gcli.lookup("screenshotDesc"),
manual: gcli.lookup("screenshotManual"),
returnType: "html",
returnType: "dom",
params: [
{
name: "filename",
@ -1788,7 +1759,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
throw new Error(gcli.lookup("screenshotSelectorChromeConflict"));
}
var document = args.chrome? context.environment.chromeDocument
: context.environment.contentDocument;
: context.environment.document;
if (args.delay > 0) {
var deferred = context.defer();
document.defaultView.setTimeout(function Command_screenshotDelay() {
@ -1803,9 +1774,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
args.fullpage, args.selector);
}
},
grabScreen:
function Command_screenshotGrabScreen(document, filename, clipboard,
fullpage, node) {
grabScreen: function(document, filename, clipboard, fullpage, node) {
let window = document.defaultView;
let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
let left = 0;
@ -1948,13 +1917,53 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
});
}(this));
/* Remoting ----------------------------------------------------------- */
const { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
/**
* 'listen' command
*/
gcli.addCommand({
name: "listen",
description: gcli.lookup("listenDesc"),
manual: gcli.lookup("listenManual"),
params: [
{
name: "port",
type: "number",
get defaultValue() {
return Services.prefs.getIntPref("devtools.debugger.chrome-debugging-port");
},
description: gcli.lookup("listenPortDesc"),
}
],
exec: function Command_screenshot(args, context) {
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
var reply = DebuggerServer.openListener(args.port);
if (!reply) {
throw new Error(gcli.lookup("listenDisabledOutput"));
}
if (DebuggerServer.initialized) {
return gcli.lookupFormat("listenInitOutput", [ '' + args.port ]);
}
return gcli.lookup("listenNoInitOutput");
},
});
/* CmdPaintFlashing ------------------------------------------------------- */
(function(module) {
/**
* 'paintflashing' command
*/
gcli.addCommand({
name: 'paintflashing',
description: gcli.lookup('paintflashingDesc')
@ -1976,15 +1985,13 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
]
}],
exec: function(args, context) {
var window;
if (args.chrome) {
window = context.environment.chromeDocument.defaultView;
} else {
window = context.environment.contentDocument.defaultView;
}
window.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindowUtils).
paintFlashing = true;
var window = args.chrome ?
context.environment.chromeWindow :
context.environment.window;
window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.paintFlashing = true;
onPaintFlashingChanged(context);
}
});
@ -2005,14 +2012,13 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
]
}],
exec: function(args, context) {
if (args.chrome) {
var window = context.environment.chromeDocument.defaultView;
} else {
var window = context.environment.contentDocument.defaultView;
}
window.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindowUtils).
paintFlashing = false;
var window = args.chrome ?
context.environment.chromeWindow :
context.environment.window;
window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.paintFlashing = false;
onPaintFlashingChanged(context);
}
});
@ -2041,7 +2047,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
},
},
tooltipText: gcli.lookup("paintflashingTooltip"),
description: gcli.lookup('paintflashingOnDesc'),
description: gcli.lookup('paintflashingToggleDesc'),
manual: gcli.lookup('paintflashingManual'),
exec: function(args, context) {
var gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
@ -2125,20 +2131,19 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
}],
exec: function(args, context) {
let utils;
let promise = context.createPromise();
let deferred = context.defer();
if (args.uri) {
utils = new AppCacheUtils(args.uri);
} else {
let doc = context.environment.contentDocument;
utils = new AppCacheUtils(doc);
utils = new AppCacheUtils(context.environment.document);
}
utils.validateManifest().then(function(errors) {
promise.resolve([errors, utils.manifestURI || "-"]);
deferred.resolve([errors, utils.manifestURI || "-"]);
});
return promise;
return deferred.promise;
}
});
@ -2154,22 +2159,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
}
});
gcli.addConverter({
from: "appcacheentries",
to: "view",
exec: function(entries, context) {
if (!entries) {
return context.createView({
html: "<span>" + gcli.lookup("appCacheManifestContainsErrors") + "</span>"
});
}
if (entries.length == 0) {
return context.createView({
html: "<span>" + gcli.lookup("appCacheNoResults") + "</span>"
});
}
let appcacheListEntries = "" +
"<ul class='gcli-appcache-list'>" +
" <li foreach='entry in ${entries}'>" +
@ -2210,6 +2199,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
" </li>" +
"</ul>";
gcli.addConverter({
from: "appcacheentries",
to: "view",
exec: function(entries, context) {
return context.createView({
html: appcacheListEntries,
data: {
@ -2238,11 +2231,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
]
}],
exec: function(args, context) {
let doc = context.environment.contentDocument;
let utils = new AppCacheUtils();
let entries = utils.listEntries(args.search);
return entries;
return utils.listEntries(args.search);
}
});
@ -2259,13 +2249,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
}
],
exec: function(args, context) {
let doc = context.environment.contentDocument;
let utils = new AppCacheUtils();
let result = utils.viewEntry(args.key);
if (result) {
return result;
}
return utils.viewEntry(args.key);
}
});

Просмотреть файл

@ -31,6 +31,7 @@ MOCHITEST_BROWSER_FILES = \
browser_cmd_appcache_valid_page2.html \
browser_cmd_appcache_valid_page3.html \
browser_cmd_commands.js \
browser_cmd_cookie.html \
browser_cmd_cookie.js \
browser_cmd_jsb.js \
browser_cmd_jsb_script.jsi \

Просмотреть файл

@ -16,13 +16,8 @@ function test() {
tests.gatTest = function(options) {
let deferred = Promise.defer();
// hack to reduce stack size as a result of bug 842347
let onGatReadyInterjection = function() {
executeSoon(onGatReady);
};
let onGatReady = function() {
Services.obs.removeObserver(onGatReadyInterjection, "gcli_addon_commands_ready");
Services.obs.removeObserver(onGatReady, "gcli_addon_commands_ready");
info("gcli_addon_commands_ready notification received, running tests");
let auditDone = helpers.audit(options, [
@ -121,7 +116,7 @@ tests.gatTest = function(options) {
});
};
Services.obs.addObserver(onGatReadyInterjection, "gcli_addon_commands_ready", false);
Services.obs.addObserver(onGatReady, "gcli_addon_commands_ready", false);
if (CmdAddonFlags.addonsLoaded) {
info("The call to AddonManager.getAllAddons in BuiltinCommands.jsm is done.");

Просмотреть файл

@ -30,6 +30,25 @@ function test() {
},
},
{
setup: function() {
Services.prefs.setBoolPref("browser.cache.disk.enable", false);
helpers.setInput(options, 'appcache list', 13);
},
check: {
input: 'appcache list',
markup: 'VVVVVVVVVVVVV',
status: 'VALID',
args: {},
},
exec: {
output: [ /cache is disabled/ ]
},
post: function(output) {
Services.prefs.setBoolPref("browser.cache.disk.enable", true);
}
},
{
setup: 'appcache list',
check: {
@ -56,8 +75,8 @@ function test() {
exec: {
output: [ /page1/, /page2/, /page3/ ]
},
post: function(output) {
ok(!output.contains("index"), "index is not contained in output");
post: function(output, text) {
ok(!text.contains("index"), "index is not contained in output");
}
},
@ -118,11 +137,11 @@ function test() {
exec: {
output: [ /no results/ ]
},
post: function(output) {
ok(!output.contains("index"), "index is not contained in output");
ok(!output.contains("page1"), "page1 is not contained in output");
ok(!output.contains("page2"), "page1 is not contained in output");
ok(!output.contains("page3"), "page1 is not contained in output");
post: function(output, text) {
ok(!text.contains("index"), "index is not contained in output");
ok(!text.contains("page1"), "page1 is not contained in output");
ok(!text.contains("page2"), "page1 is not contained in output");
ok(!text.contains("page3"), "page1 is not contained in output");
}
},

Просмотреть файл

@ -15,17 +15,6 @@ function test() {
}).then(finish);
}
tests.testEcho = function(options) {
return helpers.audit(options, [
{
setup: "echo message",
exec: {
output: "message",
}
}
]);
};
tests.testConsole = function(options) {
let deferred = Promise.defer();
let hud = null;
@ -67,13 +56,8 @@ tests.testConsole = function(options) {
}
}
]).then(function() {
// FIXME: Remove this hack once bug 842347 is fixed
// Gak - our promises impl causes so many stack frames that we blow up the
// JS engine. Jumping to a new event with a truncated stack solves this.
executeSoon(function() {
deferred.resolve();
});
});
};
helpers.audit(options, [

Просмотреть файл

@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>GCLI cookie command test</title>
</head>
<body>
<p>Cookie test</p>
<p id=result></p>
<script type="text/javascript">
document.cookie = "zap=zep";
document.cookie = "zip=zop";
document.getElementById("result").innerHTML = document.cookie;
</script>
</body>
</html>

Просмотреть файл

@ -3,7 +3,8 @@
// Tests that the cookie commands works as they should
const TEST_URI = "data:text/html;charset=utf-8,gcli-cookie";
const TEST_URI = "http://example.com/browser/browser/devtools/commandline/"+
"test/browser_cmd_cookie.html";
function test() {
helpers.addTabWithToolbar(TEST_URI, function(options) {
@ -79,14 +80,14 @@ function test() {
{
setup: "cookie list",
exec: {
output: 'No cookies found for host'
output: [ /zap=zep/, /zip=zop/, /Edit/ ]
}
},
{
setup: "cookie set fruit banana",
setup: "cookie set zup banana",
check: {
args: {
name: { value: 'fruit' },
name: { value: 'zup' },
value: { value: 'banana' },
}
},
@ -97,24 +98,56 @@ function test() {
{
setup: "cookie list",
exec: {
output: [ /fruit=banana/, /Expires:/, /Edit/ ]
output: [ /zap=zep/, /zip=zop/, /zup=banana/, /Edit/ ]
}
},
{
setup: "cookie remove fruit",
check: {
args: {
name: { value: 'fruit' },
}
},
exec: {
output: ""
}
setup: "cookie remove zip",
exec: { },
},
{
setup: "cookie list",
exec: {
output: 'No cookies found for host'
output: [ /zap=zep/, /zup=banana/, /Edit/ ]
},
post: function(output, text) {
ok(!text.contains("zip"), "");
ok(!text.contains("zop"), "");
}
},
{
setup: "cookie remove zap",
exec: { },
},
{
setup: "cookie list",
exec: {
output: [ /zup=banana/, /Edit/ ]
},
post: function(output, text) {
ok(!text.contains("zap"), "");
ok(!text.contains("zep"), "");
ok(!text.contains("zip"), "");
ok(!text.contains("zop"), "");
}
},
{
setup: "cookie remove zup",
exec: { }
},
{
setup: "cookie list",
exec: {
output: 'No cookies found for host example.com'
},
post: function(output, text) {
ok(!text.contains("zap"), "");
ok(!text.contains("zep"), "");
ok(!text.contains("zip"), "");
ok(!text.contains("zop"), "");
ok(!text.contains("zup"), "");
ok(!text.contains("banana"), "");
ok(!text.contains("Edit"), "");
}
},
]);

Просмотреть файл

@ -38,6 +38,7 @@ function test() {
// var helpers = require('gclitest/helpers');
var canon = require('gcli/canon');
// var assert = require('test/assert');
var Canon = canon.Canon;
var startCount = undefined;
var events = undefined;
@ -157,8 +158,6 @@ exports.testAddRemove2 = function(options) {
return helpers.audit(options, [
{
setup: 'testadd',
check: {
},
exec: {
output: /^3$/
},
@ -199,4 +198,74 @@ exports.testAddRemove3 = function(options) {
canon.onCanonChange.remove(canonChange);
};
exports.testAltCanon = function(options) {
var altCanon = new Canon();
var tss = {
name: 'tss',
params: [
{ name: 'str', type: 'string' },
{ name: 'num', type: 'number' },
{ name: 'opt', type: { name: 'selection', data: [ '1', '2', '3' ] } },
],
exec: function(args, context) {
return context.commandName + ':' +
args.str + ':' + args.num + ':' + args.opt;
}
};
altCanon.addCommand(tss);
var commandSpecs = altCanon.getCommandSpecs();
assert.is(JSON.stringify(commandSpecs),
'{"tss":{"name":"tss","params":[' +
'{"name":"str","type":"string"},' +
'{"name":"num","type":"number"},' +
'{"name":"opt","type":{"name":"selection","data":["1","2","3"]}}]}}',
'JSON.stringify(commandSpecs)');
var remoter = function(args, context) {
assert.is(context.commandName, 'tss', 'commandName is tss');
var cmd = altCanon.getCommand(context.commandName);
return cmd.exec(args, context);
};
canon.addProxyCommands('proxy', commandSpecs, remoter, 'test');
var parent = canon.getCommand('proxy');
assert.is(parent.name, 'proxy', 'Parent command called proxy');
var child = canon.getCommand('proxy tss');
assert.is(child.name, 'proxy tss', 'child command called proxy tss');
return helpers.audit(options, [
{
setup: 'proxy tss foo 6 3',
check: {
input: 'proxy tss foo 6 3',
hints: '',
markup: 'VVVVVVVVVVVVVVVVV',
cursor: 17,
status: 'VALID',
args: {
str: { value: 'foo', status: 'VALID' },
num: { value: 6, status: 'VALID' },
opt: { value: '3', status: 'VALID' }
}
},
exec: {
output: 'tss:foo:6:3'
},
post: function() {
canon.removeCommand('proxy');
canon.removeCommand('proxy tss');
assert.is(canon.getCommand('proxy'), undefined, 'remove proxy');
assert.is(canon.getCommand('proxy tss'), undefined, 'remove proxy tss');
}
}
]);
};
// });

Просмотреть файл

@ -758,7 +758,6 @@ exports.testSingleFloat = function(options) {
current: '__command',
status: 'ERROR',
error: '',
predictions: [ ],
unassigned: [ ],
args: {
command: { name: 'tsf' },

Просмотреть файл

@ -519,9 +519,9 @@ exports.testSpaceComplete = function(options) {
msg: { status: 'INCOMPLETE', message: '' },
num: { status: 'VALID' },
sel: { status: 'VALID' },
bool: { value: false,status: 'VALID' },
bool: { value: false, status: 'VALID' },
num2: { status: 'VALID' },
bool2: { value: false,status: 'VALID' },
bool2: { value: false, status: 'VALID' },
sel2: {
value: 'with space',
arg: ' --sel2 \'with space\' ',

Просмотреть файл

@ -0,0 +1,276 @@
/*
* Copyright 2012, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// define(function(require, exports, module) {
// <INJECTED SOURCE:START>
// THIS FILE IS GENERATED FROM SOURCE IN THE GCLI PROJECT
// DO NOT EDIT IT DIRECTLY
var exports = {};
const TEST_URI = "data:text/html;charset=utf-8,<p id='gcli-input'>gcli-testContext.js</p>";
function test() {
helpers.addTabWithToolbar(TEST_URI, function(options) {
return helpers.runTests(options, exports);
}).then(finish);
}
// <INJECTED SOURCE:END>
'use strict';
// var helpers = require('gclitest/helpers');
// var mockCommands = require('gclitest/mockCommands');
var cli = require('gcli/cli');
var origLogErrors = undefined;
exports.setup = function(options) {
mockCommands.setup();
origLogErrors = cli.logErrors;
cli.logErrors = false;
};
exports.shutdown = function(options) {
mockCommands.shutdown();
cli.logErrors = origLogErrors;
origLogErrors = undefined;
};
exports.testBaseline = function(options) {
helpers.audit(options, [
// These 3 establish a baseline for comparison when we have used the
// context command
{
setup: 'ext',
check: {
input: 'ext',
hints: ' -> context',
markup: 'III',
message: '',
predictions: [ 'context', 'tsn ext', 'tsn exte', 'tsn exten', 'tsn extend' ],
unassigned: [ ],
}
},
{
setup: 'ext test',
check: {
input: 'ext test',
hints: '',
markup: 'IIIVEEEE',
status: 'ERROR',
message: 'Too many arguments',
unassigned: [ ' test' ],
}
},
{
setup: 'tsn',
check: {
input: 'tsn',
hints: '',
markup: 'III',
cursor: 3,
current: '__command',
status: 'ERROR',
predictionsContains: [ 'tsn', 'tsn deep', 'tsn ext', 'tsn exte' ],
args: {
command: { name: 'tsn' },
}
}
}
]);
};
exports.testContext = function(options) {
helpers.audit(options, [
// Use the 'tsn' context
{
setup: 'context tsn',
check: {
input: 'context tsn',
hints: '',
markup: 'VVVVVVVVVVV',
message: '',
predictionsContains: [ 'tsn', 'tsn deep', 'tsn ext', 'tsn exte' ],
args: {
command: { name: 'context' },
prefix: {
value: mockCommands.commands.tsn,
status: 'VALID',
message: ''
},
}
},
exec: {
output: 'Using tsn as a command prefix',
completed: true,
}
},
// For comparison with earlier
{
setup: 'ext',
check: {
input: 'ext',
hints: ' <text>',
markup: 'VVV',
predictions: [ 'tsn ext', 'tsn exte', 'tsn exten', 'tsn extend' ],
args: {
command: { name: 'tsn ext' },
text: {
value: undefined,
arg: '',
status: 'INCOMPLETE',
message: ''
},
}
}
},
{
setup: 'ext test',
check: {
input: 'ext test',
hints: '',
markup: 'VVVVVVVV',
args: {
command: { name: 'tsn ext' },
text: {
value: 'test',
arg: ' test',
status: 'VALID',
message: ''
},
}
},
exec: {
output: 'Exec: tsnExt text=test',
completed: true,
}
},
{
setup: 'tsn',
check: {
input: 'tsn',
hints: '',
markup: 'III',
message: '',
predictionsContains: [ 'tsn', 'tsn deep', 'tsn ext', 'tsn exte' ],
args: {
command: { name: 'tsn' },
}
}
},
// Does it actually work?
{
setup: 'tsb true',
check: {
input: 'tsb true',
hints: '',
markup: 'VVVVVVVV',
options: [ 'true' ],
message: '',
predictions: [ 'true' ],
unassigned: [ ],
args: {
command: { name: 'tsb' },
toggle: { value: true, arg: ' true', status: 'VALID', message: '' },
}
}
},
{
// Bug 866710 - GCLI should allow argument merging for non-string parameters
setup: 'context tsn ext',
skip: true
},
{
setup: 'context "tsn ext"',
check: {
input: 'context "tsn ext"',
hints: '',
markup: 'VVVVVVVVVVVVVVVVV',
message: '',
predictions: [ ],
unassigned: [ ],
args: {
command: { name: 'context' },
prefix: {
value: mockCommands.commands.tsnExt,
status: 'VALID',
message: ''
}
}
},
exec: {
output: 'Error: Can\'t use \'tsn ext\' as a prefix because it is not a parent command.',
completed: true,
error: true
}
},
/*
{
setup: 'context "tsn deep"',
check: {
input: 'context "tsn deep"',
hints: '',
markup: 'VVVVVVVVVVVVVVVVVV',
status: 'ERROR',
message: '',
predictions: [ 'tsn deep' ],
unassigned: [ ],
args: {
command: { name: 'context' },
prefix: {
value: mockCommands.commands.tsnDeep,
status: 'VALID',
message: ''
}
}
},
exec: {
output: '',
completed: true,
}
},
*/
{
setup: 'context',
check: {
input: 'context',
hints: ' [prefix]',
markup: 'VVVVVVV',
status: 'VALID',
unassigned: [ ],
args: {
command: { name: 'context' },
prefix: { value: undefined, arg: '', status: 'VALID', message: '' },
}
},
exec: {
output: 'Command prefix is unset',
completed: true,
type: 'string',
error: false
}
}
]);
};
// });

Просмотреть файл

@ -0,0 +1,99 @@
/*
* Copyright 2009-2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE.txt or:
* http://opensource.org/licenses/BSD-3-Clause
*/
// define(function(require, exports, module) {
// <INJECTED SOURCE:START>
// THIS FILE IS GENERATED FROM SOURCE IN THE GCLI PROJECT
// DO NOT EDIT IT DIRECTLY
var exports = {};
const TEST_URI = "data:text/html;charset=utf-8,<p id='gcli-input'>gcli-testFail.js</p>";
function test() {
helpers.addTabWithToolbar(TEST_URI, function(options) {
return helpers.runTests(options, exports);
}).then(finish);
}
// <INJECTED SOURCE:END>
'use strict';
// var helpers = require('gclitest/helpers');
// var mockCommands = require('gclitest/mockCommands');
var cli = require('gcli/cli');
var origLogErrors = undefined;
exports.setup = function(options) {
mockCommands.setup();
origLogErrors = cli.logErrors;
cli.logErrors = false;
};
exports.shutdown = function(options) {
mockCommands.shutdown();
cli.logErrors = origLogErrors;
origLogErrors = undefined;
};
exports.testBasic = function(options) {
return helpers.audit(options, [
{
setup: 'tsfail reject',
exec: {
completed: false,
output: 'rejected promise',
type: 'error',
error: true
}
},
{
setup: 'tsfail rejecttyped',
exec: {
completed: false,
output: '54',
type: 'number',
error: true
}
},
{
setup: 'tsfail throwerror',
exec: {
completed: true,
output: 'Error: thrown error',
type: 'error',
error: true
}
},
{
setup: 'tsfail throwstring',
exec: {
completed: true,
output: 'thrown string',
type: 'error',
error: true
}
},
{
setup: 'tsfail noerror',
exec: {
completed: true,
output: 'no error',
type: 'string',
error: false
}
}
]);
};
// });

Просмотреть файл

@ -40,22 +40,15 @@ var KeyEvent = require('util/util').KeyEvent;
// var mockCommands = require('gclitest/mockCommands');
var latestEvent = undefined;
var latestOutput = undefined;
var latestData = undefined;
var outputted = function(ev) {
function updateData() {
latestData = latestOutput.data;
}
if (latestOutput != null) {
ev.output.onChange.remove(updateData);
}
latestEvent = ev;
latestOutput = ev.output;
ev.output.onChange.add(updateData);
ev.output.promise.then(function() {
latestData = ev.output.data;
ev.output.onClose();
});
};
@ -71,7 +64,6 @@ exports.shutdown = function(options) {
exports.testOutput = function(options) {
latestEvent = undefined;
latestOutput = undefined;
latestData = undefined;
var inputter = options.display.inputter;
@ -103,8 +95,6 @@ exports.testOutput = function(options) {
var ev3 = { keyCode: KeyEvent.DOM_VK_ESCAPE };
return inputter.handleKeyUp(ev3).then(function() {
assert.ok(!focusManager._helpRequested, 'ESCAPE = anti help');
latestOutput.onClose();
});
});

Просмотреть файл

@ -61,7 +61,7 @@ exports.testAllPredictions1 = function(options) {
return;
}
var resource = types.getType('resource');
var resource = types.createType('resource');
return resource.getLookup().then(function(opts) {
assert.ok(opts.length > 1, 'have all resources');
@ -77,7 +77,7 @@ exports.testScriptPredictions = function(options) {
return;
}
var resource = types.getType({ name: 'resource', include: 'text/javascript' });
var resource = types.createType({ name: 'resource', include: 'text/javascript' });
return resource.getLookup().then(function(opts) {
assert.ok(opts.length > 1, 'have js resources');
@ -93,7 +93,7 @@ exports.testStylePredictions = function(options) {
return;
}
var resource = types.getType({ name: 'resource', include: 'text/css' });
var resource = types.createType({ name: 'resource', include: 'text/css' });
return resource.getLookup().then(function(opts) {
assert.ok(opts.length >= 1, 'have css resources');
@ -109,11 +109,11 @@ exports.testAllPredictions2 = function(options) {
return;
}
var scriptRes = types.getType({ name: 'resource', include: 'text/javascript' });
var scriptRes = types.createType({ name: 'resource', include: 'text/javascript' });
return scriptRes.getLookup().then(function(scriptOptions) {
var styleRes = types.getType({ name: 'resource', include: 'text/css' });
var styleRes = types.createType({ name: 'resource', include: 'text/css' });
return styleRes.getLookup().then(function(styleOptions) {
var allRes = types.getType({ name: 'resource' });
var allRes = types.createType({ name: 'resource' });
return allRes.getLookup().then(function(allOptions) {
assert.is(scriptOptions.length + styleOptions.length,
allOptions.length,
@ -129,9 +129,9 @@ exports.testAllPredictions3 = function(options) {
return;
}
var res1 = types.getType({ name: 'resource' });
var res1 = types.createType({ name: 'resource' });
return res1.getLookup().then(function(options1) {
var res2 = types.getType('resource');
var res2 = types.createType('resource');
return res2.getLookup().then(function(options2) {
assert.is(options1.length, options2.length, 'type spec');
});

Просмотреть файл

@ -36,6 +36,7 @@ function test() {
'use strict';
// var assert = require('test/assert');
var cli = require('gcli/cli');
var Requisition = require('gcli/cli').Requisition;
var canon = require('gcli/canon');
// var mockCommands = require('gclitest/mockCommands');
@ -51,26 +52,26 @@ exports.shutdown = function(options) {
exports.testSplitSimple = function(options) {
var args;
var requ = new Requisition();
var requisition = new Requisition();
args = requ._tokenize('s');
requ._split(args);
args = cli.tokenize('s');
requisition._split(args);
assert.is(0, args.length);
assert.is('s', requ.commandAssignment.arg.text);
assert.is('s', requisition.commandAssignment.arg.text);
};
exports.testFlatCommand = function(options) {
var args;
var requ = new Requisition();
var requisition = new Requisition();
args = requ._tokenize('tsv');
requ._split(args);
args = cli.tokenize('tsv');
requisition._split(args);
assert.is(0, args.length);
assert.is('tsv', requ.commandAssignment.value.name);
assert.is('tsv', requisition.commandAssignment.value.name);
args = requ._tokenize('tsv a b');
requ._split(args);
assert.is('tsv', requ.commandAssignment.value.name);
args = cli.tokenize('tsv a b');
requisition._split(args);
assert.is('tsv', requisition.commandAssignment.value.name);
assert.is(2, args.length);
assert.is('a', args[0].text);
assert.is('b', args[1].text);
@ -83,14 +84,14 @@ exports.testJavascript = function(options) {
}
var args;
var requ = new Requisition();
var requisition = new Requisition();
args = requ._tokenize('{');
requ._split(args);
args = cli.tokenize('{');
requisition._split(args);
assert.is(1, args.length);
assert.is('', args[0].text);
assert.is('', requ.commandAssignment.arg.text);
assert.is('{', requ.commandAssignment.value.name);
assert.is('', requisition.commandAssignment.arg.text);
assert.is('{', requisition.commandAssignment.value.name);
};
// BUG 663081 - add tests for sub commands

Просмотреть файл

@ -36,19 +36,18 @@ function test() {
'use strict';
// var assert = require('test/assert');
var Requisition = require('gcli/cli').Requisition;
var cli = require('gcli/cli');
exports.testBlanks = function(options) {
var args;
var requ = new Requisition();
args = requ._tokenize('');
args = cli.tokenize('');
assert.is(1, args.length);
assert.is('', args[0].text);
assert.is('', args[0].prefix);
assert.is('', args[0].suffix);
args = requ._tokenize(' ');
args = cli.tokenize(' ');
assert.is(1, args.length);
assert.is('', args[0].text);
assert.is(' ', args[0].prefix);
@ -57,16 +56,15 @@ exports.testBlanks = function(options) {
exports.testTokSimple = function(options) {
var args;
var requ = new Requisition();
args = requ._tokenize('s');
args = cli.tokenize('s');
assert.is(1, args.length);
assert.is('s', args[0].text);
assert.is('', args[0].prefix);
assert.is('', args[0].suffix);
assert.is('Argument', args[0].type);
args = requ._tokenize('s s');
args = cli.tokenize('s s');
assert.is(2, args.length);
assert.is('s', args[0].text);
assert.is('', args[0].prefix);
@ -80,23 +78,22 @@ exports.testTokSimple = function(options) {
exports.testJavascript = function(options) {
var args;
var requ = new Requisition();
args = requ._tokenize('{x}');
args = cli.tokenize('{x}');
assert.is(1, args.length);
assert.is('x', args[0].text);
assert.is('{', args[0].prefix);
assert.is('}', args[0].suffix);
assert.is('ScriptArgument', args[0].type);
args = requ._tokenize('{ x }');
args = cli.tokenize('{ x }');
assert.is(1, args.length);
assert.is('x', args[0].text);
assert.is('{ ', args[0].prefix);
assert.is(' }', args[0].suffix);
assert.is('ScriptArgument', args[0].type);
args = requ._tokenize('{x} {y}');
args = cli.tokenize('{x} {y}');
assert.is(2, args.length);
assert.is('x', args[0].text);
assert.is('{', args[0].prefix);
@ -107,7 +104,7 @@ exports.testJavascript = function(options) {
assert.is('}', args[1].suffix);
assert.is('ScriptArgument', args[1].type);
args = requ._tokenize('{x}{y}');
args = cli.tokenize('{x}{y}');
assert.is(2, args.length);
assert.is('x', args[0].text);
assert.is('{', args[0].prefix);
@ -118,21 +115,21 @@ exports.testJavascript = function(options) {
assert.is('}', args[1].suffix);
assert.is('ScriptArgument', args[1].type);
args = requ._tokenize('{');
args = cli.tokenize('{');
assert.is(1, args.length);
assert.is('', args[0].text);
assert.is('{', args[0].prefix);
assert.is('', args[0].suffix);
assert.is('ScriptArgument', args[0].type);
args = requ._tokenize('{ ');
args = cli.tokenize('{ ');
assert.is(1, args.length);
assert.is('', args[0].text);
assert.is('{ ', args[0].prefix);
assert.is('', args[0].suffix);
assert.is('ScriptArgument', args[0].type);
args = requ._tokenize('{x');
args = cli.tokenize('{x');
assert.is(1, args.length);
assert.is('x', args[0].text);
assert.is('{', args[0].prefix);
@ -142,30 +139,29 @@ exports.testJavascript = function(options) {
exports.testRegularNesting = function(options) {
var args;
var requ = new Requisition();
args = requ._tokenize('{"x"}');
args = cli.tokenize('{"x"}');
assert.is(1, args.length);
assert.is('"x"', args[0].text);
assert.is('{', args[0].prefix);
assert.is('}', args[0].suffix);
assert.is('ScriptArgument', args[0].type);
args = requ._tokenize('{\'x\'}');
args = cli.tokenize('{\'x\'}');
assert.is(1, args.length);
assert.is('\'x\'', args[0].text);
assert.is('{', args[0].prefix);
assert.is('}', args[0].suffix);
assert.is('ScriptArgument', args[0].type);
args = requ._tokenize('"{x}"');
args = cli.tokenize('"{x}"');
assert.is(1, args.length);
assert.is('{x}', args[0].text);
assert.is('"', args[0].prefix);
assert.is('"', args[0].suffix);
assert.is('Argument', args[0].type);
args = requ._tokenize('\'{x}\'');
args = cli.tokenize('\'{x}\'');
assert.is(1, args.length);
assert.is('{x}', args[0].text);
assert.is('\'', args[0].prefix);
@ -175,23 +171,22 @@ exports.testRegularNesting = function(options) {
exports.testDeepNesting = function(options) {
var args;
var requ = new Requisition();
args = requ._tokenize('{{}}');
args = cli.tokenize('{{}}');
assert.is(1, args.length);
assert.is('{}', args[0].text);
assert.is('{', args[0].prefix);
assert.is('}', args[0].suffix);
assert.is('ScriptArgument', args[0].type);
args = requ._tokenize('{{x} {y}}');
args = cli.tokenize('{{x} {y}}');
assert.is(1, args.length);
assert.is('{x} {y}', args[0].text);
assert.is('{', args[0].prefix);
assert.is('}', args[0].suffix);
assert.is('ScriptArgument', args[0].type);
args = requ._tokenize('{{w} {{{x}}}} {y} {{{z}}}');
args = cli.tokenize('{{w} {{{x}}}} {y} {{{z}}}');
assert.is(3, args.length);
@ -210,7 +205,7 @@ exports.testDeepNesting = function(options) {
assert.is('}', args[2].suffix);
assert.is('ScriptArgument', args[2].type);
args = requ._tokenize('{{w} {{{x}}} {y} {{{z}}}');
args = cli.tokenize('{{w} {{{x}}} {y} {{{z}}}');
assert.is(1, args.length);
@ -222,10 +217,9 @@ exports.testDeepNesting = function(options) {
exports.testStrangeNesting = function(options) {
var args;
var requ = new Requisition();
// Note: When we get real JS parsing this should break
args = requ._tokenize('{"x}"}');
args = cli.tokenize('{"x}"}');
assert.is(2, args.length);
@ -242,9 +236,8 @@ exports.testStrangeNesting = function(options) {
exports.testComplex = function(options) {
var args;
var requ = new Requisition();
args = requ._tokenize(' 1234 \'12 34\'');
args = cli.tokenize(' 1234 \'12 34\'');
assert.is(2, args.length);
@ -258,7 +251,7 @@ exports.testComplex = function(options) {
assert.is('\'', args[1].suffix);
assert.is('Argument', args[1].type);
args = requ._tokenize('12\'34 "12 34" \\'); // 12'34 "12 34" \
args = cli.tokenize('12\'34 "12 34" \\'); // 12'34 "12 34" \
assert.is(3, args.length);
@ -280,9 +273,8 @@ exports.testComplex = function(options) {
exports.testPathological = function(options) {
var args;
var requ = new Requisition();
args = requ._tokenize('a\\ b \\t\\n\\r \\\'x\\\" \'d'); // a_b \t\n\r \'x\" 'd
args = cli.tokenize('a\\ b \\t\\n\\r \\\'x\\\" \'d'); // a_b \t\n\r \'x\" 'd
assert.is(4, args.length);

Просмотреть файл

@ -49,15 +49,22 @@ function forEachType(options, typeSpec, callback) {
}
else if (name === 'delegate') {
typeSpec.delegateType = function() {
return types.getType('string');
return types.createType('string');
};
}
else if (name === 'array') {
typeSpec.subtype = 'string';
}
var type = types.getType(typeSpec);
var type = types.createType(typeSpec);
callback(type);
// Clean up
delete typeSpec.name;
delete typeSpec.requisition;
delete typeSpec.data;
delete typeSpec.delegateType;
delete typeSpec.subtype;
});
}

Просмотреть файл

@ -21,11 +21,10 @@ this.EXPORTED_SYMBOLS = [ 'helpers' ];
var helpers = {};
this.helpers = helpers;
let require = (Cu.import("resource://gre/modules/devtools/Require.jsm", {})).require;
Components.utils.import("resource:///modules/devtools/gcli.jsm", {});
Components.utils.import("resource://gre/modules/devtools/gcli.jsm", {});
let console = (Cu.import("resource://gre/modules/devtools/Console.jsm", {})).console;
let devtools = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools;
let TargetFactory = devtools.TargetFactory;
let TargetFactory = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools.TargetFactory;
let Promise = (Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {})).Promise;
let assert = { ok: ok, is: is, log: info };
@ -158,6 +157,7 @@ helpers.runTests = function(options, tests) {
});
var recover = function(error) {
ok(false, error);
console.error(error);
};
@ -341,7 +341,7 @@ helpers._createDebugCheck = function(options) {
output += ' current: \'' + helpers._actual.current(options) + '\',\n';
output += ' status: \'' + helpers._actual.status(options) + '\',\n';
output += ' options: ' + outputArray(helpers._actual.options(options)) + ',\n';
output += ' error: \'' + helpers._actual.message(options) + '\',\n';
output += ' message: \'' + helpers._actual.message(options) + '\',\n';
output += ' predictions: ' + outputArray(predictions) + ',\n';
output += ' unassigned: ' + outputArray(requisition._unassigned) + ',\n';
output += ' outputState: \'' + helpers._actual.outputState(options) + '\',\n';
@ -378,6 +378,8 @@ helpers._createDebugCheck = function(options) {
output += ' exec: {\n';
output += ' output: \'\',\n';
output += ' completed: true,\n';
output += ' type: \'string\',\n';
output += ' error: false\n';
output += ' }\n';
output += ' }\n';
output += ']);';
@ -702,7 +704,7 @@ helpers._check = function(options, name, checks) {
*/
helpers._exec = function(options, name, expected) {
if (expected == null) {
return Promise.resolve();
return Promise.resolve({});
}
var output = options.display.requisition.exec({ hidden: true });
@ -715,20 +717,32 @@ helpers._exec = function(options, name, expected) {
if (!options.window.document.createElement) {
assert.log('skipping output tests (missing doc.createElement) for ' + name);
return Promise.resolve();
return Promise.resolve({ output: output });
}
if (!('output' in expected)) {
return Promise.resolve();
return Promise.resolve({ output: output });
}
var deferred = Promise.defer();
var checkOutput = function() {
var div = options.window.document.createElement('div');
var nodePromise = converters.convert(output.data, output.type, 'dom',
options.display.requisition.context);
nodePromise.then(function(node) {
var conversionContext = options.display.requisition.conversionContext;
if ('type' in expected) {
assert.is(output.type,
expected.type,
'output.type for: ' + name);
}
if ('error' in expected) {
assert.is(output.error,
expected.error,
'output.error for: ' + name);
}
var convertPromise = converters.convert(output.data, output.type, 'dom',
conversionContext);
return convertPromise.then(function(node) {
div.appendChild(node);
var actualOutput = div.textContent.trim();
@ -757,24 +771,11 @@ helpers._exec = function(options, name, expected) {
doTest(expected.output, actualOutput);
}
deferred.resolve(actualOutput);
return { output: output, text: actualOutput };
});
};
if (output.completed !== false) {
checkOutput();
}
else {
var changed = function() {
if (output.completed !== false) {
checkOutput();
output.onChange.remove(changed);
}
};
output.onChange.add(changed);
}
return deferred.promise;
return output.promise.then(checkOutput, checkOutput);
};
/**
@ -789,15 +790,15 @@ helpers._setup = function(options, name, action) {
return Promise.resolve(action());
}
return Promise.reject('setup must be a string or a function');
return Promise.reject('\'setup\' property must be a string or a function. Is ' + action);
};
/**
* Helper to shutdown the test
*/
helpers._post = function(name, action, output) {
helpers._post = function(name, action, data) {
if (typeof action === 'function') {
return Promise.resolve(action(output));
return Promise.resolve(action(data.output, data.text));
}
return Promise.resolve(action);
};
@ -943,6 +944,8 @@ helpers.audit = function(options, audits) {
if (typeof chunkLen !== 'number') {
chunkLen = 1;
}
if (assert.currentTest) {
var responseTime = (new Date().getTime() - start) / chunkLen;
totalResponseTime += responseTime;
if (responseTime > maxResponseTime) {
@ -950,12 +953,13 @@ helpers.audit = function(options, audits) {
maxResponseCulprit = assert.currentTest + '/' + name;
}
averageOver++;
}
var checkDone = helpers._check(options, name, audit.check);
return checkDone.then(function() {
var execDone = helpers._exec(options, name, audit.exec);
return execDone.then(function(output) {
return helpers._post(name, audit.post, output).then(function() {
return execDone.then(function(data) {
return helpers._post(name, audit.post, data).then(function() {
if (assert.testLogging) {
log('- END \'' + name + '\' in ' + assert.currentTest);
}
@ -963,9 +967,8 @@ helpers.audit = function(options, audits) {
});
});
});
}).then(null, function(ex) {
console.error(ex.stack);
throw(ex);
}).then(function() {
return options.display.inputter.setInput('');
});
};

Просмотреть файл

@ -23,133 +23,56 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
let { require: require, define: define } = Cu.import("resource://gre/modules/devtools/Require.jsm", {});
Cu.import("resource:///modules/devtools/gcli.jsm", {});
Cu.import("resource://gre/modules/devtools/gcli.jsm", {});
// <INJECTED SOURCE:END>
var mockCommands = {};
// We use an alias for exports here because this module is used in Firefox
// mochitests where we don't have define/require
'use strict';
var util = require('util/util');
var canon = require('gcli/canon');
var types = require('gcli/types');
var SelectionType = require('gcli/types/selection').SelectionType;
var DelegateType = require('gcli/types/basic').DelegateType;
mockCommands.option1 = { };
mockCommands.option2 = { };
mockCommands.option3 = { };
/**
* Registration and de-registration.
*/
mockCommands.setup = function(opts) {
// setup/shutdown needs to register/unregister types, however that means we
// need to re-initialize mockCommands.option1 and mockCommands.option2 with
// the actual types
mockCommands.option1.type = types.getType('string');
mockCommands.option2.type = types.getType('number');
types.registerType(mockCommands.optionType);
types.registerType(mockCommands.optionValue);
canon.addCommand(mockCommands.tsv);
canon.addCommand(mockCommands.tsr);
canon.addCommand(mockCommands.tso);
canon.addCommand(mockCommands.tse);
canon.addCommand(mockCommands.tsj);
canon.addCommand(mockCommands.tsb);
canon.addCommand(mockCommands.tss);
canon.addCommand(mockCommands.tsu);
canon.addCommand(mockCommands.tsf);
canon.addCommand(mockCommands.tsn);
canon.addCommand(mockCommands.tsnDif);
canon.addCommand(mockCommands.tsnExt);
canon.addCommand(mockCommands.tsnExte);
canon.addCommand(mockCommands.tsnExten);
canon.addCommand(mockCommands.tsnExtend);
canon.addCommand(mockCommands.tsnDeep);
canon.addCommand(mockCommands.tsnDeepDown);
canon.addCommand(mockCommands.tsnDeepDownNested);
canon.addCommand(mockCommands.tsnDeepDownNestedCmd);
canon.addCommand(mockCommands.tselarr);
canon.addCommand(mockCommands.tsm);
canon.addCommand(mockCommands.tsg);
canon.addCommand(mockCommands.tshidden);
canon.addCommand(mockCommands.tscook);
canon.addCommand(mockCommands.tslong);
};
mockCommands.shutdown = function(opts) {
canon.removeCommand(mockCommands.tsv);
canon.removeCommand(mockCommands.tsr);
canon.removeCommand(mockCommands.tso);
canon.removeCommand(mockCommands.tse);
canon.removeCommand(mockCommands.tsj);
canon.removeCommand(mockCommands.tsb);
canon.removeCommand(mockCommands.tss);
canon.removeCommand(mockCommands.tsu);
canon.removeCommand(mockCommands.tsf);
canon.removeCommand(mockCommands.tsn);
canon.removeCommand(mockCommands.tsnDif);
canon.removeCommand(mockCommands.tsnExt);
canon.removeCommand(mockCommands.tsnExte);
canon.removeCommand(mockCommands.tsnExten);
canon.removeCommand(mockCommands.tsnExtend);
canon.removeCommand(mockCommands.tsnDeep);
canon.removeCommand(mockCommands.tsnDeepDown);
canon.removeCommand(mockCommands.tsnDeepDownNested);
canon.removeCommand(mockCommands.tsnDeepDownNestedCmd);
canon.removeCommand(mockCommands.tselarr);
canon.removeCommand(mockCommands.tsm);
canon.removeCommand(mockCommands.tsg);
canon.removeCommand(mockCommands.tshidden);
canon.removeCommand(mockCommands.tscook);
canon.removeCommand(mockCommands.tslong);
types.deregisterType(mockCommands.optionType);
types.deregisterType(mockCommands.optionValue);
};
mockCommands.option1 = { type: types.getType('string') };
mockCommands.option2 = { type: types.getType('number') };
mockCommands.option3 = { type: types.getType({
name: 'selection',
lookup: [
{ name: 'one', value: 1 },
{ name: 'two', value: 2 },
{ name: 'three', value: 3 }
]
})};
mockCommands.optionType = new SelectionType({
mockCommands.optionType = {
name: 'optionType',
parent: 'selection',
lookup: [
{ name: 'option1', value: mockCommands.option1 },
{ name: 'option2', value: mockCommands.option2 },
{ name: 'option3', value: mockCommands.option3 }
]
});
};
mockCommands.optionValue = new DelegateType({
mockCommands.optionValue = {
name: 'optionValue',
delegateType: function(context) {
if (context != null) {
var option = context.getArgsObject().optionType;
parent: 'delegate',
delegateType: function(executionContext) {
if (executionContext != null) {
var option = executionContext.getArgsObject().optionType;
if (option != null) {
return option.type;
}
}
return types.getType('blank');
return types.createType('blank');
}
});
};
mockCommands.onCommandExec = util.createEvent('commands.onCommandExec');
function createExec(name) {
return function(args, context) {
return function(args, executionContext) {
var data = {
command: mockCommands[name],
args: args,
context: context
context: executionContext
};
mockCommands.onCommandExec(data);
var argsOut = Object.keys(args).map(function(key) {
@ -159,7 +82,7 @@ function createExec(name) {
};
}
mockCommands.tsv = {
var tsv = {
name: 'tsv',
params: [
{ name: 'optionType', type: 'optionType' },
@ -168,19 +91,19 @@ mockCommands.tsv = {
exec: createExec('tsv')
};
mockCommands.tsr = {
var tsr = {
name: 'tsr',
params: [ { name: 'text', type: 'string' } ],
exec: createExec('tsr')
};
mockCommands.tso = {
var tso = {
name: 'tso',
params: [ { name: 'text', type: 'string', defaultValue: null } ],
exec: createExec('tso')
};
mockCommands.tse = {
var tse = {
name: 'tse',
params: [
{ name: 'node', type: 'node' },
@ -195,88 +118,88 @@ mockCommands.tse = {
exec: createExec('tse')
};
mockCommands.tsj = {
var tsj = {
name: 'tsj',
params: [ { name: 'javascript', type: 'javascript' } ],
exec: createExec('tsj')
};
mockCommands.tsb = {
var tsb = {
name: 'tsb',
params: [ { name: 'toggle', type: 'boolean' } ],
exec: createExec('tsb')
};
mockCommands.tss = {
var tss = {
name: 'tss',
exec: createExec('tss')
};
mockCommands.tsu = {
var tsu = {
name: 'tsu',
params: [ { name: 'num', type: { name: 'number', max: 10, min: -5, step: 3 } } ],
exec: createExec('tsu')
};
mockCommands.tsf = {
var tsf = {
name: 'tsf',
params: [ { name: 'num', type: { name: 'number', allowFloat: true, max: 11.5, min: -6.5, step: 1.5 } } ],
exec: createExec('tsf')
};
mockCommands.tsn = {
var tsn = {
name: 'tsn'
};
mockCommands.tsnDif = {
var tsnDif = {
name: 'tsn dif',
description: 'tsn dif',
params: [ { name: 'text', type: 'string', description: 'tsn dif text' } ],
exec: createExec('tsnDif')
};
mockCommands.tsnExt = {
var tsnExt = {
name: 'tsn ext',
params: [ { name: 'text', type: 'string' } ],
exec: createExec('tsnExt')
};
mockCommands.tsnExte = {
var tsnExte = {
name: 'tsn exte',
params: [ { name: 'text', type: 'string' } ],
exec: createExec('tsnExte')
};
mockCommands.tsnExten = {
var tsnExten = {
name: 'tsn exten',
params: [ { name: 'text', type: 'string' } ],
exec: createExec('tsnExten')
};
mockCommands.tsnExtend = {
var tsnExtend = {
name: 'tsn extend',
params: [ { name: 'text', type: 'string' } ],
exec: createExec('tsnExtend')
};
mockCommands.tsnDeep = {
var tsnDeep = {
name: 'tsn deep'
};
mockCommands.tsnDeepDown = {
var tsnDeepDown = {
name: 'tsn deep down'
};
mockCommands.tsnDeepDownNested = {
var tsnDeepDownNested = {
name: 'tsn deep down nested'
};
mockCommands.tsnDeepDownNestedCmd = {
var tsnDeepDownNestedCmd = {
name: 'tsn deep down nested cmd',
exec: createExec('tsnDeepDownNestedCmd')
};
mockCommands.tshidden = {
var tshidden = {
name: 'tshidden',
hidden: true,
params: [
@ -308,7 +231,7 @@ mockCommands.tshidden = {
exec: createExec('tshidden')
};
mockCommands.tselarr = {
var tselarr = {
name: 'tselarr',
params: [
{ name: 'num', type: { name: 'selection', data: [ '1', '2', '3' ] } },
@ -317,7 +240,7 @@ mockCommands.tselarr = {
exec: createExec('tselarr')
};
mockCommands.tsm = {
var tsm = {
name: 'tsm',
description: 'a 3-param test selection|string|number',
params: [
@ -328,7 +251,7 @@ mockCommands.tsm = {
exec: createExec('tsm')
};
mockCommands.tsg = {
var tsg = {
name: 'tsg',
description: 'a param group test',
params: [
@ -371,7 +294,7 @@ mockCommands.tsg = {
exec: createExec('tsg')
};
mockCommands.tscook = {
var tscook = {
name: 'tscook',
description: 'param group test to catch problems with cookie command',
params: [
@ -411,10 +334,9 @@ mockCommands.tscook = {
exec: createExec('tscook')
};
mockCommands.tslong = {
var tslong = {
name: 'tslong',
description: 'long param tests to catch problems with the jsb command',
returnValue:'string',
params: [
{
name: 'msg',
@ -473,5 +395,146 @@ mockCommands.tslong = {
exec: createExec('tslong')
};
var tsfail = {
name: 'tsfail',
description: 'test errors',
params: [
{
name: 'method',
type: {
name: 'selection',
data: [
'reject', 'rejecttyped',
'throwerror', 'throwstring', 'throwinpromise',
'noerror'
]
}
}
],
exec: function(args, context) {
if (args.method === 'reject') {
var deferred = context.defer();
setTimeout(function() {
deferred.reject('rejected promise');
}, 10);
return deferred.promise;
}
if (args.method === 'rejecttyped') {
var deferred = context.defer();
setTimeout(function() {
deferred.reject(context.typedData('number', 54));
}, 10);
return deferred.promise;
}
if (args.method === 'throwinpromise') {
var deferred = context.defer();
setTimeout(function() {
deferred.resolve('should be lost');
}, 10);
return deferred.promise.then(function() {
var t = null;
return t.foo;
});
}
if (args.method === 'throwerror') {
throw new Error('thrown error');
}
if (args.method === 'throwstring') {
throw 'thrown string';
}
return 'no error';
}
};
mockCommands.commands = {};
/**
* Registration and de-registration.
*/
mockCommands.setup = function(opts) {
// setup/shutdown needs to register/unregister types, however that means we
// need to re-initialize mockCommands.option1 and mockCommands.option2 with
// the actual types
mockCommands.option1.type = types.createType('string');
mockCommands.option2.type = types.createType('number');
mockCommands.option3.type = types.createType({
name: 'selection',
lookup: [
{ name: 'one', value: 1 },
{ name: 'two', value: 2 },
{ name: 'three', value: 3 }
]
});
types.addType(mockCommands.optionType);
types.addType(mockCommands.optionValue);
mockCommands.commands.tsv = canon.addCommand(tsv);
mockCommands.commands.tsr = canon.addCommand(tsr);
mockCommands.commands.tso = canon.addCommand(tso);
mockCommands.commands.tse = canon.addCommand(tse);
mockCommands.commands.tsj = canon.addCommand(tsj);
mockCommands.commands.tsb = canon.addCommand(tsb);
mockCommands.commands.tss = canon.addCommand(tss);
mockCommands.commands.tsu = canon.addCommand(tsu);
mockCommands.commands.tsf = canon.addCommand(tsf);
mockCommands.commands.tsn = canon.addCommand(tsn);
mockCommands.commands.tsnDif = canon.addCommand(tsnDif);
mockCommands.commands.tsnExt = canon.addCommand(tsnExt);
mockCommands.commands.tsnExte = canon.addCommand(tsnExte);
mockCommands.commands.tsnExten = canon.addCommand(tsnExten);
mockCommands.commands.tsnExtend = canon.addCommand(tsnExtend);
mockCommands.commands.tsnDeep = canon.addCommand(tsnDeep);
mockCommands.commands.tsnDeepDown = canon.addCommand(tsnDeepDown);
mockCommands.commands.tsnDeepDownNested = canon.addCommand(tsnDeepDownNested);
mockCommands.commands.tsnDeepDownNestedCmd = canon.addCommand(tsnDeepDownNestedCmd);
mockCommands.commands.tselarr = canon.addCommand(tselarr);
mockCommands.commands.tsm = canon.addCommand(tsm);
mockCommands.commands.tsg = canon.addCommand(tsg);
mockCommands.commands.tshidden = canon.addCommand(tshidden);
mockCommands.commands.tscook = canon.addCommand(tscook);
mockCommands.commands.tslong = canon.addCommand(tslong);
mockCommands.commands.tsfail = canon.addCommand(tsfail);
};
mockCommands.shutdown = function(opts) {
canon.removeCommand(tsv);
canon.removeCommand(tsr);
canon.removeCommand(tso);
canon.removeCommand(tse);
canon.removeCommand(tsj);
canon.removeCommand(tsb);
canon.removeCommand(tss);
canon.removeCommand(tsu);
canon.removeCommand(tsf);
canon.removeCommand(tsn);
canon.removeCommand(tsnDif);
canon.removeCommand(tsnExt);
canon.removeCommand(tsnExte);
canon.removeCommand(tsnExten);
canon.removeCommand(tsnExtend);
canon.removeCommand(tsnDeep);
canon.removeCommand(tsnDeepDown);
canon.removeCommand(tsnDeepDownNested);
canon.removeCommand(tsnDeepDownNestedCmd);
canon.removeCommand(tselarr);
canon.removeCommand(tsm);
canon.removeCommand(tsg);
canon.removeCommand(tshidden);
canon.removeCommand(tscook);
canon.removeCommand(tslong);
canon.removeCommand(tsfail);
types.removeType(mockCommands.optionType);
types.removeType(mockCommands.optionValue);
mockCommands.commands = {};
};
// });

Просмотреть файл

@ -5,7 +5,7 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
this.EXPORTED_SYMBOLS = [ ];
Cu.import("resource:///modules/devtools/gcli.jsm");
Cu.import("resource://gre/modules/devtools/gcli.jsm");
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
@ -157,7 +157,7 @@ gcli.addCommand({
description: gcli.lookup("breakaddlineLineDesc")
}
],
returnType: "html",
returnType: "string",
exec: function(args, context) {
args.type = "line";
@ -201,7 +201,7 @@ gcli.addCommand({
description: gcli.lookup("breakdelBreakidDesc")
}
],
returnType: "html",
returnType: "string",
exec: function(args, context) {
let dbg = getPanel(context, "jsdebugger");
if (!dbg) {
@ -381,7 +381,7 @@ gcli.addCommand({
name: "dbg list",
description: gcli.lookup("dbgListSourcesDesc"),
params: [],
returnType: "html",
returnType: "dom",
exec: function(args, context) {
let dbg = getPanel(context, "jsdebugger");
let doc = context.environment.chromeDocument;

Просмотреть файл

@ -797,7 +797,9 @@ StackFrames.prototype = {
case "object":
// Add nodes for every variable in scope.
this.activeThread.pauseGrip(env.object).getPrototypeAndProperties(function(aResponse) {
this._insertScopeVariables(aResponse.ownProperties, aScope);
let { ownProperties, safeGetterValues } = aResponse;
this._mergeSafeGetterValues(ownProperties, safeGetterValues);
this._insertScopeVariables(ownProperties, aScope);
// Signal that variables have been fetched.
window.dispatchEvent(document, "Debugger:FetchedVariables");
@ -903,9 +905,11 @@ StackFrames.prototype = {
let grip = aVar._sourceGrip;
this.activeThread.pauseGrip(grip).getPrototypeAndProperties(function(aResponse) {
let { ownProperties, prototype } = aResponse;
let { ownProperties, prototype, safeGetterValues } = aResponse;
let sortable = VariablesView.NON_SORTABLE_CLASSES.indexOf(grip.class) == -1;
this._mergeSafeGetterValues(ownProperties, safeGetterValues);
// Add all the variable properties.
if (ownProperties) {
aVar.addProperties(ownProperties, {
@ -932,6 +936,33 @@ StackFrames.prototype = {
}.bind(this));
},
/**
* Merge the safe getter values descriptors into the "own properties" object
* that comes from a "prototypeAndProperties" response packet. This is needed
* for Variables View.
*
* @private
* @param object aOwnProperties
* The |ownProperties| object that will get the new safe getter values.
* @param object aSafeGetterValues
* The |safeGetterValues| object.
*/
_mergeSafeGetterValues:
function SF__mergeSafeGetterValues(aOwnProperties, aSafeGetterValues) {
// Merge the safe getter values into one object such that we can use it
// in VariablesView.
for (let name of Object.keys(aSafeGetterValues)) {
if (name in aOwnProperties) {
aOwnProperties[name].getterValue = aSafeGetterValues[name].getterValue;
aOwnProperties[name].getterPrototypeLevel = aSafeGetterValues[name]
.getterPrototypeLevel;
}
else {
aOwnProperties[name] = aSafeGetterValues[name];
}
}
},
/**
* Adds the specified stack frame to the list.
*

Просмотреть файл

@ -406,7 +406,6 @@ create({ constructor: StackFramesView, proto: MenuContainer.prototype }, {
// Append a stack frame item to this container.
let stackframeItem = this.push(frameView, {
index: 0, /* specifies on which position should the item be appended */
relaxed: true, /* this container should allow dupes & degenerates */
attachment: {
popup: menuEntry,
depth: aDepth
@ -946,7 +945,7 @@ FilterView.prototype = {
view.node.hideEmptyGroups();
// Ensure the currently selected item is visible.
view.node.ensureSelectionIsVisible(true);
view.node.ensureSelectionIsVisible({ withGroup: true });
// Remember the previously searched file to avoid redundant filtering.
this._prevSearchedFile = aFile;

Просмотреть файл

@ -56,10 +56,13 @@ function testFrameParameters()
is(globalNodes[1].querySelector(".value").getAttribute("value"), "[object Object]",
"Should have the right property value for |SpecialPowers|.");
is(globalNodes[3].querySelector(".name").getAttribute("value"), "document",
let globalScopeObject = gDebugger.DebuggerView.Variables.getScopeForNode(globalScope);
let documentNode = globalScopeObject.get("document");
is(documentNode.target.querySelector(".name").getAttribute("value"), "document",
"Should have the right property name for |document|.");
is(globalNodes[3].querySelector(".value").getAttribute("value"), "[object HTMLDocument]",
is(documentNode.target.querySelector(".value").getAttribute("value"), "[object HTMLDocument]",
"Should have the right property value for |document|.");
let len = globalNodes.length - 1;

Просмотреть файл

@ -61,10 +61,13 @@ function testWithFrame()
is(innerNodes[1].querySelector(".value").getAttribute("value"), "1",
"Should have the right property value for |one|.");
is(globalNodes[3].querySelector(".name").getAttribute("value"), "document",
let globalScopeObject = gDebugger.DebuggerView.Variables.getScopeForNode(globalScope);
let documentNode = globalScopeObject.get("document");
is(documentNode.target.querySelector(".name").getAttribute("value"), "document",
"Should have the right property name for |document|.");
is(globalNodes[3].querySelector(".value").getAttribute("value"), "[object HTMLDocument]",
is(documentNode.target.querySelector(".value").getAttribute("value"), "[object HTMLDocument]",
"Should have the right property value for |document|.");
let len = globalNodes.length - 1;

Просмотреть файл

@ -61,15 +61,17 @@ function testFrameParameters()
is(anonymousNodes[2].querySelector(".value").getAttribute("value"), "[object Object]",
"Should have the right property value for |buttonAsProto|.");
is(globalNodes[3].querySelector(".name").getAttribute("value"), "document",
let globalScopeObject = gVars.getScopeForNode(globalScope);
let documentNode = globalScopeObject.get("document");
is(documentNode.target.querySelector(".name").getAttribute("value"), "document",
"Should have the right property name for |document|.");
is(globalNodes[3].querySelector(".value").getAttribute("value"), "[object HTMLDocument]",
is(documentNode.target.querySelector(".value").getAttribute("value"), "[object HTMLDocument]",
"Should have the right property value for |document|.");
let buttonNode = gVars.getItemForNode(anonymousNodes[1]);
let buttonAsProtoNode = gVars.getItemForNode(anonymousNodes[2]);
let documentNode = gVars.getItemForNode(globalNodes[3]);
is(buttonNode.expanded, false,
"The buttonNode should not be expanded at this point.");
@ -131,30 +133,46 @@ function testFrameParameters()
.getAttribute("value").search(/object/) != -1,
"'__proto__' in documentNode should be an object.");
let buttonProtoNode = buttonNode.get("__proto__");
let buttonAsProtoProtoNode = buttonAsProtoNode.get("__proto__");
let documentProtoNode = documentNode.get("__proto__");
// Now the main course: make sure that the native getters for WebIDL
// attributes have been called and a value has been returned.
is(buttonNode.get("type").target.querySelector(".name")
.getAttribute("value"), "type",
"Should have the right property name for 'type' in buttonProtoNode.");
is(buttonNode.get("type").target.querySelector(".value")
.getAttribute("value"), '"submit"',
"'type' in buttonProtoNode should have the right value.");
is(buttonNode.get("formMethod").target.querySelector(".name")
.getAttribute("value"), "formMethod",
"Should have the right property name for 'formMethod' in buttonProtoNode.");
is(buttonNode.get("formMethod").target.querySelector(".value")
.getAttribute("value"), '""',
"'formMethod' in buttonProtoNode should have the right value.");
is(documentNode.get("domain").target.querySelector(".name")
.getAttribute("value"), "domain",
"Should have the right property name for 'domain' in documentProtoNode.");
is(documentNode.get("domain").target.querySelector(".value")
.getAttribute("value"), '"example.com"',
"'domain' in documentProtoNode should have the right value.");
is(documentNode.get("cookie").target.querySelector(".name")
.getAttribute("value"), "cookie",
"Should have the right property name for 'cookie' in documentProtoNode.");
is(documentNode.get("cookie").target.querySelector(".value")
.getAttribute("value"), '""',
"'cookie' in documentProtoNode should have the right value.");
let buttonAsProtoProtoNode = buttonAsProtoNode.get("__proto__");
is(buttonProtoNode.expanded, false,
"The buttonProtoNode should not be expanded at this point.");
is(buttonAsProtoProtoNode.expanded, false,
"The buttonAsProtoProtoNode should not be expanded at this point.");
is(documentProtoNode.expanded, false,
"The documentProtoNode should not be expanded at this point.");
// Expand the prototypes of 'button', 'buttonAsProto' and 'document'
// tree nodes. This causes their properties to be retrieved and
// displayed.
buttonProtoNode.expand();
buttonAsProtoProtoNode.expand();
documentProtoNode.expand();
is(buttonProtoNode.expanded, true,
"The buttonProtoNode should be expanded at this point.");
is(buttonAsProtoProtoNode.expanded, true,
"The buttonAsProtoProtoNode should be expanded at this point.");
is(documentProtoNode.expanded, true,
"The documentProtoNode should be expanded at this point.");
// Poll every few milliseconds until the properties are retrieved.
@ -168,88 +186,29 @@ function testFrameParameters()
window.clearInterval(intervalID1);
return resumeAndFinish();
}
if (!buttonProtoNode._retrieved ||
!buttonAsProtoProtoNode._retrieved ||
!documentProtoNode._retrieved) {
if (!buttonAsProtoProtoNode._retrieved) {
return;
}
window.clearInterval(intervalID1);
// Now the main course: make sure that the native getters for WebIDL
// attributes have been called and a value has been returned.
is(buttonProtoNode.get("type").target.querySelector(".name")
.getAttribute("value"), "type",
"Should have the right property name for 'type' in buttonProtoNode.");
is(buttonProtoNode.get("type").target.querySelector(".value")
.getAttribute("value"), '"submit"',
"'type' in buttonProtoNode should have the right value.");
is(buttonProtoNode.get("formMethod").target.querySelector(".name")
.getAttribute("value"), "formMethod",
"Should have the right property name for 'formMethod' in buttonProtoNode.");
is(buttonProtoNode.get("formMethod").target.querySelector(".value")
.getAttribute("value"), '""',
"'formMethod' in buttonProtoNode should have the right value.");
is(documentProtoNode.get("domain").target.querySelector(".name")
.getAttribute("value"), "domain",
"Should have the right property name for 'domain' in documentProtoNode.");
is(documentProtoNode.get("domain").target.querySelector(".value")
.getAttribute("value"), '"example.com"',
"'domain' in documentProtoNode should have the right value.");
is(documentProtoNode.get("cookie").target.querySelector(".name")
.getAttribute("value"), "cookie",
"Should have the right property name for 'cookie' in documentProtoNode.");
is(documentProtoNode.get("cookie").target.querySelector(".value")
.getAttribute("value"), '""',
"'cookie' in documentProtoNode should have the right value.");
let buttonAsProtoProtoProtoNode = buttonAsProtoProtoNode.get("__proto__");
is(buttonAsProtoProtoProtoNode.expanded, false,
"The buttonAsProtoProtoProtoNode should not be expanded at this point.");
// Expand the prototype of the prototype of 'buttonAsProto' tree
// node. This causes its properties to be retrieved and displayed.
buttonAsProtoProtoProtoNode.expand();
is(buttonAsProtoProtoProtoNode.expanded, true,
"The buttonAsProtoProtoProtoNode should be expanded at this point.");
// Poll every few milliseconds until the properties are retrieved.
// It's important to set the timer in the chrome window, because the
// content window timers are disabled while the debuggee is paused.
let count3 = 0;
let intervalID2 = window.setInterval(function(){
info("count3: " + count3);
if (++count3 > 50) {
ok(false, "Timed out while polling for the properties.");
window.clearInterval(intervalID2);
return resumeAndFinish();
}
if (!buttonAsProtoProtoProtoNode._retrieved) {
return;
}
window.clearInterval(intervalID2);
// Test this more involved case that reuses an object that is
// present in another cache line.
is(buttonAsProtoProtoProtoNode.get("type").target.querySelector(".name")
is(buttonAsProtoProtoNode.get("type").target.querySelector(".name")
.getAttribute("value"), "type",
"Should have the right property name for 'type' in buttonAsProtoProtoProtoNode.");
is(buttonAsProtoProtoProtoNode.get("type").target.querySelector(".value")
is(buttonAsProtoProtoNode.get("type").target.querySelector(".value")
.getAttribute("value"), '"submit"',
"'type' in buttonAsProtoProtoProtoNode should have the right value.");
is(buttonAsProtoProtoProtoNode.get("formMethod").target.querySelector(".name")
is(buttonAsProtoProtoNode.get("formMethod").target.querySelector(".name")
.getAttribute("value"), "formMethod",
"Should have the right property name for 'formMethod' in buttonAsProtoProtoProtoNode.");
is(buttonAsProtoProtoProtoNode.get("formMethod").target.querySelector(".value")
is(buttonAsProtoProtoNode.get("formMethod").target.querySelector(".value")
.getAttribute("value"), '""',
"'formMethod' in buttonAsProtoProtoProtoNode should have the right value.");
resumeAndFinish();
}, 100);
}, 100);
}, 100);
}}, 0);
}, false);

Просмотреть файл

@ -149,11 +149,11 @@ function testVariablesFiltering()
"There should be 0 variables displayed in the test scope");
is(loadScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0,
"There should be 0 variables displayed in the load scope");
is(globalScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 1,
"There should be 1 variable displayed in the global scope");
is(globalScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 3,
"There should be 3 variables displayed in the global scope");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length, 6,
"There should be 6 properties displayed in the inner scope");
ok(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length > 6,
"There should be more than 6 properties displayed in the inner scope");
is(mathScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0,
"There should be 0 properties displayed in the math scope");
is(testScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0,
@ -165,21 +165,19 @@ function testVariablesFiltering()
is(innerScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[0].getAttribute("value"),
"this", "The only inner variable displayed should be 'this'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[0].getAttribute("value"),
"window", "The first inner property displayed should be 'window'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[1].getAttribute("value"),
"document", "The second inner property displayed should be 'document'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[2].getAttribute("value"),
"location", "The third inner property displayed should be 'location'");
"window", "The third inner property displayed should be 'window'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[3].getAttribute("value"),
"__proto__", "The fourth inner property displayed should be '__proto__'");
"document", "The fourth inner property displayed should be 'document'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[4].getAttribute("value"),
"Location", "The fifth inner property displayed should be 'Location'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[5].getAttribute("value"),
"Location", "The sixth inner property displayed should be 'Location'");
"location", "The fifth inner property displayed should be 'location'");
is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[0].getAttribute("value"),
"Location", "The only global variable displayed should be 'Location'");
"location", "The first global variable displayed should be 'location'");
is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[1].getAttribute("value"),
"locationbar", "The second global variable displayed should be 'locationbar'");
is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[2].getAttribute("value"),
"Location", "The third global variable displayed should be 'Location'");
}
function test2()
@ -237,11 +235,11 @@ function testVariablesFiltering()
"There should be 0 variables displayed in the test scope");
is(loadScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0,
"There should be 0 variables displayed in the load scope");
is(globalScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 1,
"There should be 1 variable displayed in the global scope");
is(globalScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 3,
"There should be 3 variables displayed in the global scope");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length, 6,
"There should be 6 properties displayed in the inner scope");
ok(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length > 6,
"There should be more than 6 properties displayed in the inner scope");
is(mathScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0,
"There should be 0 properties displayed in the math scope");
is(testScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0,
@ -253,21 +251,19 @@ function testVariablesFiltering()
is(innerScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[0].getAttribute("value"),
"this", "The only inner variable displayed should be 'this'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[0].getAttribute("value"),
"window", "The first inner property displayed should be 'window'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[1].getAttribute("value"),
"document", "The second inner property displayed should be 'document'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[2].getAttribute("value"),
"location", "The third inner property displayed should be 'location'");
"window", "The third inner property displayed should be 'window'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[3].getAttribute("value"),
"__proto__", "The fourth inner property displayed should be '__proto__'");
"document", "The fourth inner property displayed should be 'document'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[4].getAttribute("value"),
"Location", "The fifth inner property displayed should be 'Location'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[5].getAttribute("value"),
"Location", "The sixth inner property displayed should be 'Location'");
"location", "The fifth inner property displayed should be 'location'");
is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[0].getAttribute("value"),
"Location", "The only global variable displayed should be 'Location'");
"location", "The first global variable displayed should be 'location'");
is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[1].getAttribute("value"),
"locationbar", "The second global variable displayed should be 'locationbar'");
is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[2].getAttribute("value"),
"Location", "The second global variable displayed should be 'Location'");
}
var scopes = gDebugger.DebuggerView.Variables._list,

Просмотреть файл

@ -92,8 +92,8 @@ function testVariablesFiltering()
is(globalScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 2,
"There should be 2 variables displayed in the global scope");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length, 8,
"There should be 8 properties displayed in the inner scope");
ok(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length > 3,
"There should be more than 3 properties displayed in the inner scope");
is(mathScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0,
"There should be 0 properties displayed in the math scope");
is(testScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0,
@ -111,16 +111,6 @@ function testVariablesFiltering()
"window", "The second inner property displayed should be 'window'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[2].getAttribute("value"),
"document", "The third inner property displayed should be 'document'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[3].getAttribute("value"),
"location", "The fourth inner property displayed should be 'location'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[4].getAttribute("value"),
"__proto__", "The fifth inner property displayed should be '__proto__'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[5].getAttribute("value"),
"__proto__", "The sixth inner property displayed should be '__proto__'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[6].getAttribute("value"),
"HTMLDocument", "The seventh inner property displayed should be 'HTMLDocument'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[7].getAttribute("value"),
"HTMLDocument", "The eight inner property displayed should be 'HTMLDocument'");
is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[0].getAttribute("value"),
"document", "The first global variable displayed should be 'document'");
@ -168,8 +158,8 @@ function testVariablesFiltering()
"The local scope 'this.window' should be expanded");
is(documentItem.expanded, true,
"The local scope 'this.window.document' should be expanded");
is(locationItem.expanded, false,
"The local scope 'this.window.document.location' should not be expanded");
is(locationItem.expanded, true,
"The local scope 'this.window.document.location' should be expanded");
documentItem.toggle();
documentItem.toggle();
@ -186,8 +176,8 @@ function testVariablesFiltering()
is(globalScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 2,
"There should be 2 variables displayed in the global scope");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length, 8,
"There should be 8 properties displayed in the inner scope");
ok(innerScope.querySelectorAll(".variables-view-property:not([non-match])").length > 3,
"There should be more than 3 properties displayed in the inner scope");
is(mathScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0,
"There should be 0 properties displayed in the math scope");
is(testScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0,
@ -205,16 +195,6 @@ function testVariablesFiltering()
"window", "The second inner property displayed should be 'window'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[2].getAttribute("value"),
"document", "The third inner property displayed should be 'document'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[3].getAttribute("value"),
"location", "The fourth inner property displayed should be 'location'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[4].getAttribute("value"),
"__proto__", "The fifth inner property displayed should be '__proto__'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[5].getAttribute("value"),
"__proto__", "The sixth inner property displayed should be '__proto__'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[6].getAttribute("value"),
"HTMLDocument", "The seventh inner property displayed should be 'HTMLDocument'");
is(innerScope.querySelectorAll(".variables-view-property:not([non-match]) > .title > .name")[7].getAttribute("value"),
"HTMLDocument", "The eight inner property displayed should be 'HTMLDocument'");
is(globalScope.querySelectorAll(".variables-view-variable:not([non-match]) > .title > .name")[0].getAttribute("value"),
"document", "The first global variable displayed should be 'document'");

Просмотреть файл

@ -127,8 +127,8 @@ function testVariablesFiltering()
"There should be 0 properties displayed in the math scope");
is(testScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0,
"There should be 0 properties displayed in the test scope");
is(loadScope.querySelectorAll(".variables-view-property:not([non-match])").length, 1,
"There should be 1 property displayed in the load scope");
ok(loadScope.querySelectorAll(".variables-view-property:not([non-match])").length > 1,
"There should be more than one property displayed in the load scope");
isnot(globalScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0,
"There should be some properties displayed in the global scope");
}
@ -157,8 +157,8 @@ function testVariablesFiltering()
"There should be 0 properties displayed in the math scope");
is(testScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0,
"There should be 0 properties displayed in the test scope");
is(loadScope.querySelectorAll(".variables-view-property:not([non-match])").length, 1,
"There should be 1 property displayed in the load scope");
ok(loadScope.querySelectorAll(".variables-view-property:not([non-match])").length > 1,
"There should be more than one properties displayed in the load scope");
isnot(globalScope.querySelectorAll(".variables-view-property:not([non-match])").length, 0,
"There should be some properties displayed in the global scope");
}

Просмотреть файл

@ -183,6 +183,7 @@ function debug_tab_pane(aURL, aOnDebugging, aBeforeTabAdded) {
info("Debugger has started");
dbg._view.Variables.lazyEmpty = false;
dbg._view.Variables.lazyAppend = false;
dbg._view.Variables.lazyExpand = false;
aOnDebugging(tab, debuggee, dbg);
});
});
@ -206,6 +207,7 @@ function debug_remote(aURL, aOnDebugging, aBeforeTabAdded) {
info("Remote Debugger has started");
win._dbgwin.DebuggerView.Variables.lazyEmpty = false;
win._dbgwin.DebuggerView.Variables.lazyAppend = false;
win._dbgwin.DebuggerView.Variables.lazyExpand = false;
aOnDebugging(tab, debuggee, win);
});
});

Просмотреть файл

@ -21,11 +21,10 @@ this.EXPORTED_SYMBOLS = [ 'helpers' ];
var helpers = {};
this.helpers = helpers;
let require = (Cu.import("resource://gre/modules/devtools/Require.jsm", {})).require;
Components.utils.import("resource:///modules/devtools/gcli.jsm", {});
Components.utils.import("resource://gre/modules/devtools/gcli.jsm", {});
let console = (Cu.import("resource://gre/modules/devtools/Console.jsm", {})).console;
let devtools = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools;
let TargetFactory = devtools.TargetFactory;
let TargetFactory = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools.TargetFactory;
let Promise = (Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {})).Promise;
let assert = { ok: ok, is: is, log: info };
@ -158,6 +157,7 @@ helpers.runTests = function(options, tests) {
});
var recover = function(error) {
ok(false, error);
console.error(error);
};
@ -341,7 +341,7 @@ helpers._createDebugCheck = function(options) {
output += ' current: \'' + helpers._actual.current(options) + '\',\n';
output += ' status: \'' + helpers._actual.status(options) + '\',\n';
output += ' options: ' + outputArray(helpers._actual.options(options)) + ',\n';
output += ' error: \'' + helpers._actual.message(options) + '\',\n';
output += ' message: \'' + helpers._actual.message(options) + '\',\n';
output += ' predictions: ' + outputArray(predictions) + ',\n';
output += ' unassigned: ' + outputArray(requisition._unassigned) + ',\n';
output += ' outputState: \'' + helpers._actual.outputState(options) + '\',\n';
@ -378,6 +378,8 @@ helpers._createDebugCheck = function(options) {
output += ' exec: {\n';
output += ' output: \'\',\n';
output += ' completed: true,\n';
output += ' type: \'string\',\n';
output += ' error: false\n';
output += ' }\n';
output += ' }\n';
output += ']);';
@ -702,7 +704,7 @@ helpers._check = function(options, name, checks) {
*/
helpers._exec = function(options, name, expected) {
if (expected == null) {
return Promise.resolve();
return Promise.resolve({});
}
var output = options.display.requisition.exec({ hidden: true });
@ -715,20 +717,32 @@ helpers._exec = function(options, name, expected) {
if (!options.window.document.createElement) {
assert.log('skipping output tests (missing doc.createElement) for ' + name);
return Promise.resolve();
return Promise.resolve({ output: output });
}
if (!('output' in expected)) {
return Promise.resolve();
return Promise.resolve({ output: output });
}
var deferred = Promise.defer();
var checkOutput = function() {
var div = options.window.document.createElement('div');
var nodePromise = converters.convert(output.data, output.type, 'dom',
options.display.requisition.context);
nodePromise.then(function(node) {
var conversionContext = options.display.requisition.conversionContext;
if ('type' in expected) {
assert.is(output.type,
expected.type,
'output.type for: ' + name);
}
if ('error' in expected) {
assert.is(output.error,
expected.error,
'output.error for: ' + name);
}
var convertPromise = converters.convert(output.data, output.type, 'dom',
conversionContext);
return convertPromise.then(function(node) {
div.appendChild(node);
var actualOutput = div.textContent.trim();
@ -757,24 +771,11 @@ helpers._exec = function(options, name, expected) {
doTest(expected.output, actualOutput);
}
deferred.resolve(actualOutput);
return { output: output, text: actualOutput };
});
};
if (output.completed !== false) {
checkOutput();
}
else {
var changed = function() {
if (output.completed !== false) {
checkOutput();
output.onChange.remove(changed);
}
};
output.onChange.add(changed);
}
return deferred.promise;
return output.promise.then(checkOutput, checkOutput);
};
/**
@ -789,15 +790,15 @@ helpers._setup = function(options, name, action) {
return Promise.resolve(action());
}
return Promise.reject('setup must be a string or a function');
return Promise.reject('\'setup\' property must be a string or a function. Is ' + action);
};
/**
* Helper to shutdown the test
*/
helpers._post = function(name, action, output) {
helpers._post = function(name, action, data) {
if (typeof action === 'function') {
return Promise.resolve(action(output));
return Promise.resolve(action(data.output, data.text));
}
return Promise.resolve(action);
};
@ -943,6 +944,8 @@ helpers.audit = function(options, audits) {
if (typeof chunkLen !== 'number') {
chunkLen = 1;
}
if (assert.currentTest) {
var responseTime = (new Date().getTime() - start) / chunkLen;
totalResponseTime += responseTime;
if (responseTime > maxResponseTime) {
@ -950,12 +953,13 @@ helpers.audit = function(options, audits) {
maxResponseCulprit = assert.currentTest + '/' + name;
}
averageOver++;
}
var checkDone = helpers._check(options, name, audit.check);
return checkDone.then(function() {
var execDone = helpers._exec(options, name, audit.exec);
return execDone.then(function(output) {
return helpers._post(name, audit.post, output).then(function() {
return execDone.then(function(data) {
return helpers._post(name, audit.post, data).then(function() {
if (assert.testLogging) {
log('- END \'' + name + '\' in ' + assert.currentTest);
}
@ -963,9 +967,8 @@ helpers.audit = function(options, audits) {
});
});
});
}).then(null, function(ex) {
console.error(ex.stack);
throw(ex);
}).then(function() {
return options.display.inputter.setInput('');
});
};

Просмотреть файл

@ -222,8 +222,8 @@ DevTools.prototype = {
*
* Each toolDefinition has the following properties:
* - id: Unique identifier for this tool (string|required)
* - killswitch: Property name to allow us to turn this tool on/off globally
* (string|required) (TODO: default to devtools.{id}.enabled?)
* - visibilityswitch: Property name to allow us to hide this tool from the
* DevTools Toolbox.
* - icon: URL pointing to a graphic which will be used as the src for an
* 16x16 img tag (string|required)
* - url: URL pointing to a XUL/XHTML document containing the user interface
@ -241,7 +241,7 @@ DevTools.prototype = {
throw new Error("Invalid definition.id");
}
toolDefinition.killswitch = toolDefinition.killswitch ||
toolDefinition.visibilityswitch = toolDefinition.visibilityswitch ||
"devtools." + toolId + ".enabled";
this._tools.set(toolId, toolDefinition);
@ -307,21 +307,17 @@ DevTools.prototype = {
*/
getToolDefinitionMap: function DT_getToolDefinitionMap() {
let tools = new Map();
let disabledTools = [];
try {
disabledTools = JSON.parse(Services.prefs.getCharPref("devtools.toolbox.disabledTools"));
} catch(ex) {}
for (let [key, value] of this._tools) {
let enabled;
try {
enabled = Services.prefs.getBoolPref(value.killswitch);
enabled = Services.prefs.getBoolPref(value.visibilityswitch);
} catch(e) {
enabled = true;
}
if (enabled && disabledTools.indexOf(key) == -1) {
if (enabled || value.id == "options") {
tools.set(key, value);
}
}
@ -454,7 +450,7 @@ DevTools.prototype = {
destroy: function() {
Services.obs.removeObserver(this.destroy, "quit-application");
for (let [key, tool] of this._tools) {
for (let [key, tool] of this.getToolDefinitionMap()) {
this.unregisterTool(key, true);
}
@ -578,15 +574,23 @@ let gDevToolsBrowser = {
},
/**
* Add the menuitem for a tool to all open browser windows. Also toggles the
* kill switch preference of the tool.
* Add the menuitem for a tool to all open browser windows.
*
* @param {object} toolDefinition
* properties of the tool to add
*/
_addToolToWindows: function DT_addToolToWindows(toolDefinition) {
// Set the kill switch pref boolean to true
Services.prefs.setBoolPref(toolDefinition.killswitch, true);
// No menu item or global shortcut is required for options panel.
if (toolDefinition.id == "options") {
return;
}
// Skip if the tool is disabled.
try {
if (!Services.prefs.getBoolPref(toolDefinition.visibilityswitch)) {
return;
}
} catch(e) {}
// We need to insert the new tool in the right place, which means knowing
// the tool that comes before the tool that we're trying to add
@ -642,6 +646,17 @@ let gDevToolsBrowser = {
let fragMenuItems = doc.createDocumentFragment();
for (let toolDefinition of gDevTools.getToolDefinitionArray()) {
if (toolDefinition.id == "options") {
continue;
}
// Skip if the tool is disabled.
try {
if (!Services.prefs.getBoolPref(toolDefinition.visibilityswitch)) {
continue;
}
} catch(e) {}
let elements = gDevToolsBrowser._createToolMenuElements(toolDefinition, doc);
if (!elements) {
@ -714,7 +729,7 @@ let gDevToolsBrowser = {
let bc = doc.createElement("broadcaster");
bc.id = "devtoolsMenuBroadcaster_" + id;
bc.setAttribute("label", toolDefinition.label);
bc.setAttribute("label", toolDefinition.menuLabel || toolDefinition.label);
bc.setAttribute("command", cmd.id);
if (key) {
@ -767,16 +782,12 @@ let gDevToolsBrowser = {
},
/**
* Remove the menuitem for a tool to all open browser windows. Also sets the
* kill switch boolean pref to false.
* Remove the menuitem for a tool to all open browser windows.
*
* @param {object} toolId
* @param {string} toolId
* id of the tool to remove
* @param {string} killswitch
* The kill switch preference string of the tool
*/
_removeToolFromWindows: function DT_removeToolFromWindows(toolId, killswitch) {
Services.prefs.setBoolPref(killswitch, false);
_removeToolFromWindows: function DT_removeToolFromWindows(toolId) {
for (let win of gDevToolsBrowser._trackedBrowserWindows) {
gDevToolsBrowser._removeToolFromMenu(toolId, win.document);
}
@ -854,15 +865,10 @@ gDevTools.on("tool-registered", function(ev, toolId) {
});
gDevTools.on("tool-unregistered", function(ev, toolId) {
let killswitch;
if (typeof toolId == "string") {
killswitch = "devtools." + toolId + ".enabled";
}
else {
killswitch = toolId.killswitch;
if (typeof toolId != "string") {
toolId = toolId.id;
}
gDevToolsBrowser._removeToolFromWindows(toolId, killswitch);
gDevToolsBrowser._removeToolFromWindows(toolId);
});
gDevTools.on("toolbox-ready", gDevToolsBrowser._updateMenuCheckbox);

Просмотреть файл

@ -20,7 +20,7 @@ function runTests(aTab) {
let toolDefinition = {
id: toolId,
isTargetSupported: function() true,
killswitch: "devtools.test-tool.enabled",
visibilityswitch: "devtools.test-tool.enabled",
url: "about:blank",
label: "someLabel",
build: function(iframeWindow, toolbox) {

Просмотреть файл

@ -26,24 +26,16 @@ function testSelectTool(aToolbox) {
function testOptionsShortcut() {
ok(true, "Toolbox selected via selectTool method");
toolbox.once("options-selected", testOptionsButtonClick);
toolbox.once("options-selected", testOptions);
toolbox.selectTool("webconsole")
.then(() => synthesizeKeyFromKeyTag("toolbox-options-key", doc));
}
function testOptionsButtonClick() {
ok(true, "Toolbox selected via shortcut");
toolbox.once("options-selected", testOptions);
toolbox.selectTool("webconsole")
.then(() => doc.getElementById("toolbox-tab-options").click());
}
function testOptions(event, iframe) {
function testOptions(event, tool) {
ok(true, "Toolbox selected via button click");
panelWin = iframe.contentWindow;
let panelDoc = iframe.contentDocument;
panelWin = tool.panelWin;
// Testing pref changes
let prefCheckboxes = panelDoc.querySelectorAll("checkbox[data-pref]");
let prefCheckboxes = tool.panelDoc.querySelectorAll("checkbox[data-pref]");
for (let checkbox of prefCheckboxes) {
prefNodes.push(checkbox);
prefValues.push(Services.prefs.getBoolPref(checkbox.getAttribute("data-pref")));

Просмотреть файл

@ -24,7 +24,7 @@ function test() {
let toolDefinition = {
id: "fakeTool4242",
killswitch: "devtools.fakeTool4242.enabled",
visibilityswitch: "devtools.fakeTool4242.enabled",
url: toolURL,
label: "FAKE TOOL!!!",
isTargetSupported: function() true,

Просмотреть файл

@ -16,7 +16,7 @@ function test() {
const TOOL_ID_1 = "webconsole";
const TOOL_ID_2 = "jsdebugger";
const LABEL_1 = "Web Console";
const LABEL_1 = "Console";
const LABEL_2 = "Debugger";
let toolbox;

Просмотреть файл

@ -4,51 +4,78 @@
"use strict";
const { utils: Cu } = Components;
const DISABLED_TOOLS = "devtools.toolbox.disabledTools";
const {Cu} = require("chrome");
let Promise = require("sdk/core/promise");
let EventEmitter = require("devtools/shared/event-emitter");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource:///modules/devtools/gDevTools.jsm");
window.addEventListener("load", function onLoad() {
window.removeEventListener("load", onLoad);
setupToolsList();
populatePreferences();
});
exports.OptionsPanel = OptionsPanel;
function setupToolsList() {
let disabledTools = [];
try {
disabledTools = JSON.parse(Services.prefs.getCharPref(DISABLED_TOOLS));
} catch(ex) {
Cu.reportError("Error parsing pref " + DISABLED_TOOLS + " as JSON.");
}
let defaultToolsBox = document.getElementById("default-tools-box");
let additionalToolsBox = document.getElementById("additional-tools-box");
/**
* Represents the Options Panel in the Toolbox.
*/
function OptionsPanel(iframeWindow, toolbox) {
this.panelDoc = iframeWindow.document;
this.panelWin = iframeWindow;
EventEmitter.decorate(this);
};
OptionsPanel.prototype = {
open: function OP_open() {
let deferred = Promise.defer();
this.setupToolsList();
this.populatePreferences();
this.emit("ready");
deferred.resolve(this);
return deferred.promise;
},
setupToolsList: function OP_setupToolsList() {
let defaultToolsBox = this.panelDoc.getElementById("default-tools-box");
let additionalToolsBox = this.panelDoc.getElementById("additional-tools-box");
defaultToolsBox.textContent = "";
additionalToolsBox.textContent = "";
let pref = function(key) {
try {
return Services.prefs.getBoolPref(key);
}
catch (ex) {
return true;
}
};
let onCheckboxClick = function(id) {
if (disabledTools.indexOf(id) > -1) {
disabledTools.splice(disabledTools.indexOf(id), 1);
Services.prefs.setCharPref(DISABLED_TOOLS, JSON.stringify(disabledTools));
let toolDefinition = gDevTools._tools.get(id);
// Set the kill switch pref boolean to true
Services.prefs.setBoolPref(toolDefinition.visibilityswitch, this.checked);
if (this.checked) {
gDevTools.emit("tool-registered", id);
}
else {
disabledTools.push(id);
Services.prefs.setCharPref(DISABLED_TOOLS, JSON.stringify(disabledTools));
gDevTools.emit("tool-unregistered", gDevTools._tools.get(id));
gDevTools.emit("tool-unregistered", toolDefinition);
}
};
// Populating the default tools lists
for (let tool of gDevTools.getDefaultTools()) {
let checkbox = document.createElement("checkbox");
if (tool.id == "options") {
continue;
}
let checkbox = this.panelDoc.createElement("checkbox");
checkbox.setAttribute("id", tool.id);
checkbox.setAttribute("label", tool.label);
checkbox.setAttribute("tooltiptext", tool.tooltip || "");
checkbox.setAttribute("checked", disabledTools.indexOf(tool.id) == -1);
checkbox.addEventListener("command", onCheckboxClick.bind(null, tool.id));
checkbox.setAttribute("checked", pref(tool.visibilityswitch));
checkbox.addEventListener("command", onCheckboxClick.bind(checkbox, tool.id));
defaultToolsBox.appendChild(checkbox);
}
@ -56,12 +83,12 @@ function setupToolsList() {
let atleastOneAddon = false;
for (let tool of gDevTools.getAdditionalTools()) {
atleastOneAddon = true;
let checkbox = document.createElement("checkbox");
let checkbox = this.panelDoc.createElement("checkbox");
checkbox.setAttribute("id", tool.id);
checkbox.setAttribute("label", tool.label);
checkbox.setAttribute("tooltiptext", tool.tooltip || "");
checkbox.setAttribute("checked", disabledTools.indexOf(tool.id) == -1);
checkbox.addEventListener("command", onCheckboxClick.bind(null, tool.id));
checkbox.setAttribute("checked", pref(tool.visibilityswitch));
checkbox.addEventListener("command", onCheckboxClick.bind(checkbox, tool.id));
additionalToolsBox.appendChild(checkbox);
}
@ -70,11 +97,11 @@ function setupToolsList() {
additionalToolsBox.previousSibling.style.display = "none";
}
window.focus();
}
this.panelWin.focus();
},
function populatePreferences() {
let prefCheckboxes = document.querySelectorAll("checkbox[data-pref]");
populatePreferences: function OP_populatePreferences() {
let prefCheckboxes = this.panelDoc.querySelectorAll("checkbox[data-pref]");
for (let checkbox of prefCheckboxes) {
checkbox.checked = Services.prefs.getBoolPref(checkbox.getAttribute("data-pref"));
checkbox.addEventListener("command", function() {
@ -87,7 +114,7 @@ function populatePreferences() {
gDevTools.emit("pref-changed", data);
}.bind(checkbox));
}
let prefRadiogroups = document.querySelectorAll("radiogroup[data-pref]");
let prefRadiogroups = this.panelDoc.querySelectorAll("radiogroup[data-pref]");
for (let radiogroup of prefRadiogroups) {
let selectedValue = Services.prefs.getCharPref(radiogroup.getAttribute("data-pref"));
for (let radio of radiogroup.childNodes) {
@ -107,4 +134,9 @@ function populatePreferences() {
gDevTools.emit("pref-changed", data);
}.bind(radiogroup));
}
}
},
destroy: function OP_destroy() {
this.panelWin = this.panelDoc = null;
}
};

Просмотреть файл

@ -11,7 +11,6 @@
<?xml-stylesheet rel="stylesheet" href="chrome://browser/skin/devtools/toolbox.css" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript;version=1.8" src="toolbox-options.js"></script>
<hbox id="options-panel-container" flex="1">
<hbox id="options-panel" flex="1">
<vbox id="tools-box" class="options-vertical-pane" flex="1">

Просмотреть файл

@ -37,7 +37,7 @@ XPCOMUtils.defineLazyGetter(this, "toolboxStrings", function() {
XPCOMUtils.defineLazyGetter(this, "Requisition", function() {
let scope = {};
Cu.import("resource://gre/modules/devtools/Require.jsm", scope);
Cu.import("resource:///modules/devtools/gcli.jsm", scope);
Cu.import("resource://gre/modules/devtools/gcli.jsm", {});
let req = scope.require;
return req('gcli/cli').Requisition;
@ -220,14 +220,6 @@ Toolbox.prototype = {
},
_buildOptions: function TBOX__buildOptions() {
this.optionsButton = this.doc.getElementById("toolbox-tab-options");
this.optionsButton.addEventListener("command", function() {
this.selectTool("options");
}.bind(this), false);
let iframe = this.doc.getElementById("toolbox-panel-iframe-options");
this._toolPanels.set("options", iframe);
let key = this.doc.getElementById("toolbox-options-key");
key.addEventListener("command", function(toolId) {
this.selectTool(toolId);
@ -358,7 +350,6 @@ Toolbox.prototype = {
let radio = this.doc.createElement("radio");
radio.className = "toolbox-tab devtools-tab";
radio.id = "toolbox-tab-" + id;
radio.setAttribute("flex", "1");
radio.setAttribute("toolid", id);
if (toolDefinition.ordinal == undefined || toolDefinition.ordinal < 0) {
toolDefinition.ordinal = MAX_ORDINAL;
@ -370,22 +361,30 @@ Toolbox.prototype = {
this.selectTool(id);
}.bind(this, id));
// spacer lets us center the image and label, while allowing cropping
let spacer = this.doc.createElement("spacer");
spacer.setAttribute("flex", "1");
radio.appendChild(spacer);
if (toolDefinition.icon) {
let image = this.doc.createElement("image");
image.setAttribute("src", toolDefinition.icon);
radio.appendChild(image);
}
if (toolDefinition.label) {
let label = this.doc.createElement("label");
label.setAttribute("value", toolDefinition.label)
label.setAttribute("crop", "end");
label.setAttribute("flex", "1");
radio.appendChild(label);
radio.setAttribute("flex", "1");
}
let vbox = this.doc.createElement("vbox");
vbox.className = "toolbox-panel";
vbox.id = "toolbox-panel-" + id;
radio.appendChild(label);
// If there is no tab yet, or the ordinal to be added is the largest one.
if (tabs.childNodes.length == 0 ||
@ -398,8 +397,7 @@ Toolbox.prototype = {
Array.some(tabs.childNodes, (node, i) => {
if (+node.getAttribute("ordinal") > toolDefinition.ordinal) {
tabs.insertBefore(radio, node);
deck.insertBefore(vbox, deck.childNodes[i + 1]);
// + 1 because of options panel.
deck.insertBefore(vbox, deck.childNodes[i]);
return true;
}
});
@ -440,8 +438,9 @@ Toolbox.prototype = {
let tabstrip = this.doc.getElementById("toolbox-tabs");
// select the right tab
let index = -1;
// select the right tab, making 0th index the default tab if right tab not
// found
let index = 0;
let tabs = tabstrip.childNodes;
for (let i = 0; i < tabs.length; i++) {
if (tabs[i] === tab) {
@ -453,15 +452,7 @@ Toolbox.prototype = {
// and select the right iframe
let deck = this.doc.getElementById("toolbox-deck");
// offset by 1 due to options panel
if (id == "options") {
deck.selectedIndex = 0;
this.optionsButton.setAttribute("checked", true);
}
else {
deck.selectedIndex = index != -1 ? index + 1: -1;
this.optionsButton.removeAttribute("checked");
}
deck.selectedIndex = index;
let definition = gDevTools.getToolDefinitionMap().get(id);
@ -668,7 +659,7 @@ Toolbox.prototype = {
if (this.hostType == Toolbox.HostType.WINDOW) {
let doc = this.doc.defaultView.parent.document;
let key = doc.getElementById("key_" + id);
let key = doc.getElementById("key_" + toolId);
if (key) {
key.parentNode.removeChild(key);
}
@ -709,7 +700,6 @@ Toolbox.prototype = {
let outstanding = [];
this._toolPanels.delete("options");
for (let [id, panel] of this._toolPanels) {
outstanding.push(panel.destroy());
}

Просмотреть файл

@ -34,10 +34,6 @@
<hbox id="toolbox-dock-buttons"/>
</hbox>
#endif
<toolbarbutton id="toolbox-tab-options"
autocheck="false"
class="command-button toolbox-tab devtools-tab"
tooltiptext="&toolboxOptionsButton.tooltip;"/>
<hbox id="toolbox-tabs" flex="1">
</hbox>
<hbox id="toolbox-buttons" pack="end"/>
@ -51,14 +47,6 @@
#endif
</toolbar>
<deck id="toolbox-deck" flex="1">
<vbox id="toolbox-panel-options"
class="toolbox-panel">
<iframe id="toolbox-panel-iframe-options"
class="toolbox-panel-iframe"
flex="1" forceOwnRefreshDriver=""
src="chrome://browser/content/devtools/framework/toolbox-options.xul">
</iframe>
</vbox>
</deck>
</notificationbox>
</window>

Просмотреть файл

@ -5,7 +5,7 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
this.EXPORTED_SYMBOLS = [ ];
Cu.import("resource:///modules/devtools/gcli.jsm");
Cu.import("resource://gre/modules/devtools/gcli.jsm");
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",

Просмотреть файл

@ -21,11 +21,10 @@ this.EXPORTED_SYMBOLS = [ 'helpers' ];
var helpers = {};
this.helpers = helpers;
let require = (Cu.import("resource://gre/modules/devtools/Require.jsm", {})).require;
Components.utils.import("resource:///modules/devtools/gcli.jsm", {});
Components.utils.import("resource://gre/modules/devtools/gcli.jsm", {});
let console = (Cu.import("resource://gre/modules/devtools/Console.jsm", {})).console;
let devtools = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools;
let TargetFactory = devtools.TargetFactory;
let TargetFactory = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools.TargetFactory;
let Promise = (Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {})).Promise;
let assert = { ok: ok, is: is, log: info };
@ -158,6 +157,7 @@ helpers.runTests = function(options, tests) {
});
var recover = function(error) {
ok(false, error);
console.error(error);
};
@ -341,7 +341,7 @@ helpers._createDebugCheck = function(options) {
output += ' current: \'' + helpers._actual.current(options) + '\',\n';
output += ' status: \'' + helpers._actual.status(options) + '\',\n';
output += ' options: ' + outputArray(helpers._actual.options(options)) + ',\n';
output += ' error: \'' + helpers._actual.message(options) + '\',\n';
output += ' message: \'' + helpers._actual.message(options) + '\',\n';
output += ' predictions: ' + outputArray(predictions) + ',\n';
output += ' unassigned: ' + outputArray(requisition._unassigned) + ',\n';
output += ' outputState: \'' + helpers._actual.outputState(options) + '\',\n';
@ -378,6 +378,8 @@ helpers._createDebugCheck = function(options) {
output += ' exec: {\n';
output += ' output: \'\',\n';
output += ' completed: true,\n';
output += ' type: \'string\',\n';
output += ' error: false\n';
output += ' }\n';
output += ' }\n';
output += ']);';
@ -702,7 +704,7 @@ helpers._check = function(options, name, checks) {
*/
helpers._exec = function(options, name, expected) {
if (expected == null) {
return Promise.resolve();
return Promise.resolve({});
}
var output = options.display.requisition.exec({ hidden: true });
@ -715,20 +717,32 @@ helpers._exec = function(options, name, expected) {
if (!options.window.document.createElement) {
assert.log('skipping output tests (missing doc.createElement) for ' + name);
return Promise.resolve();
return Promise.resolve({ output: output });
}
if (!('output' in expected)) {
return Promise.resolve();
return Promise.resolve({ output: output });
}
var deferred = Promise.defer();
var checkOutput = function() {
var div = options.window.document.createElement('div');
var nodePromise = converters.convert(output.data, output.type, 'dom',
options.display.requisition.context);
nodePromise.then(function(node) {
var conversionContext = options.display.requisition.conversionContext;
if ('type' in expected) {
assert.is(output.type,
expected.type,
'output.type for: ' + name);
}
if ('error' in expected) {
assert.is(output.error,
expected.error,
'output.error for: ' + name);
}
var convertPromise = converters.convert(output.data, output.type, 'dom',
conversionContext);
return convertPromise.then(function(node) {
div.appendChild(node);
var actualOutput = div.textContent.trim();
@ -757,24 +771,11 @@ helpers._exec = function(options, name, expected) {
doTest(expected.output, actualOutput);
}
deferred.resolve(actualOutput);
return { output: output, text: actualOutput };
});
};
if (output.completed !== false) {
checkOutput();
}
else {
var changed = function() {
if (output.completed !== false) {
checkOutput();
output.onChange.remove(changed);
}
};
output.onChange.add(changed);
}
return deferred.promise;
return output.promise.then(checkOutput, checkOutput);
};
/**
@ -789,15 +790,15 @@ helpers._setup = function(options, name, action) {
return Promise.resolve(action());
}
return Promise.reject('setup must be a string or a function');
return Promise.reject('\'setup\' property must be a string or a function. Is ' + action);
};
/**
* Helper to shutdown the test
*/
helpers._post = function(name, action, output) {
helpers._post = function(name, action, data) {
if (typeof action === 'function') {
return Promise.resolve(action(output));
return Promise.resolve(action(data.output, data.text));
}
return Promise.resolve(action);
};
@ -943,6 +944,8 @@ helpers.audit = function(options, audits) {
if (typeof chunkLen !== 'number') {
chunkLen = 1;
}
if (assert.currentTest) {
var responseTime = (new Date().getTime() - start) / chunkLen;
totalResponseTime += responseTime;
if (responseTime > maxResponseTime) {
@ -950,12 +953,13 @@ helpers.audit = function(options, audits) {
maxResponseCulprit = assert.currentTest + '/' + name;
}
averageOver++;
}
var checkDone = helpers._check(options, name, audit.check);
return checkDone.then(function() {
var execDone = helpers._exec(options, name, audit.exec);
return execDone.then(function(output) {
return helpers._post(name, audit.post, output).then(function() {
return execDone.then(function(data) {
return helpers._post(name, audit.post, data).then(function() {
if (assert.testLogging) {
log('- END \'' + name + '\' in ' + assert.currentTest);
}
@ -963,9 +967,8 @@ helpers.audit = function(options, audits) {
});
});
});
}).then(null, function(ex) {
console.error(ex.stack);
throw(ex);
}).then(function() {
return options.display.inputter.setInput('');
});
};

Просмотреть файл

@ -29,6 +29,7 @@ Object.defineProperty(exports, "TargetFactory", {
loader.lazyGetter(this, "osString", () => Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS);
// Panels
loader.lazyGetter(this, "OptionsPanel", function() require("devtools/framework/toolbox-options").OptionsPanel);
loader.lazyGetter(this, "InspectorPanel", function() require("devtools/inspector/inspector-panel").InspectorPanel);
loader.lazyImporter(this, "WebConsolePanel", "resource:///modules/WebConsolePanel.jsm");
loader.lazyImporter(this, "DebuggerPanel", "resource:///modules/devtools/DebuggerPanel.jsm");
@ -37,12 +38,14 @@ loader.lazyImporter(this, "ProfilerPanel", "resource:///modules/devtools/Profile
loader.lazyImporter(this, "NetMonitorPanel", "resource:///modules/devtools/NetMonitorPanel.jsm");
// Strings
const toolboxProps = "chrome://browser/locale/devtools/toolbox.properties";
const inspectorProps = "chrome://browser/locale/devtools/inspector.properties";
const debuggerProps = "chrome://browser/locale/devtools/debugger.properties";
const styleEditorProps = "chrome://browser/locale/devtools/styleeditor.properties";
const webConsoleProps = "chrome://browser/locale/devtools/webconsole.properties";
const profilerProps = "chrome://browser/locale/devtools/profiler.properties";
const netMonitorProps = "chrome://browser/locale/devtools/netmonitor.properties";
loader.lazyGetter(this, "toolboxStrings", () => Services.strings.createBundle(toolboxProps));
loader.lazyGetter(this, "webConsoleStrings", () => Services.strings.createBundle(webConsoleProps));
loader.lazyGetter(this, "debuggerStrings", () => Services.strings.createBundle(debuggerProps));
loader.lazyGetter(this, "styleEditorStrings", () => Services.strings.createBundle(styleEditorProps));
@ -54,15 +57,31 @@ let Tools = {};
exports.Tools = Tools;
// Definitions
Tools.options = {
id: "options",
ordinal: 0,
url: "chrome://browser/content/devtools/framework/toolbox-options.xul",
icon: "chrome://browser/skin/devtools/tool-options.png",
tooltip: l10n("optionsButton.tooltip", toolboxStrings),
isTargetSupported: function(target) {
return true;
},
build: function(iframeWindow, toolbox) {
let panel = new OptionsPanel(iframeWindow, toolbox);
return panel.open();
}
}
Tools.webConsole = {
id: "webconsole",
key: l10n("cmd.commandkey", webConsoleStrings),
accesskey: l10n("webConsoleCmd.accesskey", webConsoleStrings),
modifiers: Services.appinfo.OS == "Darwin" ? "accel,alt" : "accel,shift",
ordinal: 0,
ordinal: 1,
icon: "chrome://browser/skin/devtools/tool-webconsole.png",
url: "chrome://browser/content/devtools/webconsole.xul",
label: l10n("ToolboxWebconsole.label", webConsoleStrings),
label: l10n("ToolboxTabWebconsole.label", webConsoleStrings),
menuLabel: l10n("MenuWebconsole.label", webConsoleStrings),
tooltip: l10n("ToolboxWebconsole.tooltip", webConsoleStrings),
isTargetSupported: function(target) {
@ -74,33 +93,11 @@ Tools.webConsole = {
}
};
Tools.jsdebugger = {
id: "jsdebugger",
key: l10n("open.commandkey", debuggerStrings),
accesskey: l10n("debuggerMenu.accesskey", debuggerStrings),
modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
ordinal: 2,
killswitch: "devtools.debugger.enabled",
icon: "chrome://browser/skin/devtools/tool-debugger.png",
url: "chrome://browser/content/devtools/debugger.xul",
label: l10n("ToolboxDebugger.label", debuggerStrings),
tooltip: l10n("ToolboxDebugger.tooltip", debuggerStrings),
isTargetSupported: function(target) {
return true;
},
build: function(iframeWindow, toolbox) {
let panel = new DebuggerPanel(iframeWindow, toolbox);
return panel.open();
}
};
Tools.inspector = {
id: "inspector",
accesskey: l10n("inspector.accesskey", inspectorStrings),
key: l10n("inspector.commandkey", inspectorStrings),
ordinal: 1,
ordinal: 2,
modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
icon: "chrome://browser/skin/devtools/tool-inspector.png",
url: "chrome://browser/content/devtools/inspector/inspector.xul",
@ -117,10 +114,32 @@ Tools.inspector = {
}
};
Tools.jsdebugger = {
id: "jsdebugger",
key: l10n("open.commandkey", debuggerStrings),
accesskey: l10n("debuggerMenu.accesskey", debuggerStrings),
modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
ordinal: 3,
visibilityswitch: "devtools.debugger.enabled",
icon: "chrome://browser/skin/devtools/tool-debugger.png",
url: "chrome://browser/content/devtools/debugger.xul",
label: l10n("ToolboxDebugger.label", debuggerStrings),
tooltip: l10n("ToolboxDebugger.tooltip", debuggerStrings),
isTargetSupported: function(target) {
return true;
},
build: function(iframeWindow, toolbox) {
let panel = new DebuggerPanel(iframeWindow, toolbox);
return panel.open();
}
};
Tools.styleEditor = {
id: "styleeditor",
key: l10n("open.commandkey", styleEditorStrings),
ordinal: 3,
ordinal: 4,
accesskey: l10n("open.accesskey", styleEditorStrings),
modifiers: "shift",
icon: "chrome://browser/skin/devtools/tool-styleeditor.png",
@ -142,9 +161,9 @@ Tools.jsprofiler = {
id: "jsprofiler",
accesskey: l10n("profiler.accesskey", profilerStrings),
key: l10n("profiler2.commandkey", profilerStrings),
ordinal: 4,
ordinal: 5,
modifiers: "shift",
killswitch: "devtools.profiler.enabled",
visibilityswitch: "devtools.profiler.enabled",
icon: "chrome://browser/skin/devtools/tool-profiler.png",
url: "chrome://browser/content/devtools/profiler.xul",
label: l10n("profiler.label", profilerStrings),
@ -164,10 +183,10 @@ Tools.netMonitor = {
id: "netmonitor",
accesskey: l10n("netmonitor.accesskey", netMonitorStrings),
key: l10n("netmonitor.commandkey", netMonitorStrings),
ordinal: 5,
ordinal: 6,
modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
killswitch: "devtools.netmonitor.enabled",
icon: "chrome://browser/skin/devtools/tool-profiler.png",
visibilityswitch: "devtools.netmonitor.enabled",
icon: "chrome://browser/skin/devtools/tool-network.png",
url: "chrome://browser/content/devtools/netmonitor.xul",
label: l10n("netmonitor.label", netMonitorStrings),
tooltip: l10n("netmonitor.tooltip", netMonitorStrings),
@ -183,6 +202,7 @@ Tools.netMonitor = {
};
let defaultTools = [
Tools.options,
Tools.styleEditor,
Tools.webConsole,
Tools.jsdebugger,
@ -201,7 +221,7 @@ var unloadObserver = {
observe: function(subject, topic, data) {
if (subject.wrappedJSObject === require("@loader/unload")) {
Services.obs.removeObserver(unloadObserver, "sdk:loader:destroy");
for (let definition of defaultTools) {
for (let definition of gDevTools.getToolDefinitionArray()) {
gDevTools.unregisterTool(definition.id);
}
}

Просмотреть файл

@ -17,7 +17,7 @@ let EventEmitter = require("devtools/shared/event-emitter");
let {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
Cu.import("resource:///modules/devtools/Templater.jsm");
Cu.import("resource://gre/modules/devtools/Templater.jsm");
Cu.import("resource://gre/modules/Services.jsm");
/**

Просмотреть файл

@ -5,11 +5,19 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const HTML_NS = "http://www.w3.org/1999/xhtml";
const EPSILON = 0.001;
const RESIZE_REFRESH_RATE = 50; // ms
const REQUESTS_REFRESH_RATE = 50; // ms
const REQUESTS_HEADERS_SAFE_BOUNDS = 30; // px
const REQUESTS_WATERFALL_SAFE_BOUNDS = 100; // px
const REQUESTS_WATERFALL_BACKGROUND_PATTERN = [5, 250, 1000, 2000]; // ms
const REQUESTS_WATERFALL_SAFE_BOUNDS = 90; // px
const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
const REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN = 60; // px
const REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms
const REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES = 3;
const REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px
const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 10; // byte
const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 16; // byte
const DEFAULT_HTTP_VERSION = "HTTP/1.1";
const HEADERS_SIZE_DECIMALS = 3;
const CONTENT_SIZE_DECIMALS = 2;
@ -48,8 +56,6 @@ const GENERIC_VARIABLES_VIEW_SETTINGS = {
switch: () => {}
};
function $(aSelector, aTarget = document) aTarget.querySelector(aSelector);
/**
* Object defining the network monitor view components.
*/
@ -157,6 +163,7 @@ let NetMonitorView = {
$("#details-pane").selectedIndex = aTabIndex;
}
},
/**
* Lazily initializes and returns a promise for a SourceEditor instance.
*
@ -264,6 +271,7 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
this.node = new SideMenuWidget($("#requests-menu-contents"), false);
this.node.maintainSelectionVisible = false;
this.node.autoscrollWithAppendedItems = true;
this.node.addEventListener("mousedown", this._onMouseDown, false);
this.node.addEventListener("select", this._onSelect, false);
@ -321,7 +329,6 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
// Append a network request item to this container.
let requestItem = this.push(menuView, {
relaxed: true, /* this container should allow dupes & degenerates */
attachment: {
id: aId,
startedDeltaMillis: unixTime - this._firstRequestStartedMillis,
@ -332,10 +339,133 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
finalize: this._onRequestItemRemoved
});
$("#details-pane-toggle").disabled = false;
$(".requests-menu-empty-notice").hidden = true;
this._cache.set(aId, requestItem);
},
/**
* Sorts all network requests in this container by a specified detail.
*
* @param string aType
* Either "status", "method", "file", "domain", "type" or "size".
*/
sortBy: function NVRM_sortBy(aType) {
let target = $("#requests-menu-" + aType + "-button");
let headers = document.querySelectorAll(".requests-menu-header-button");
for (let header of headers) {
if (header != target) {
header.removeAttribute("sorted");
header.removeAttribute("tooltiptext");
}
}
let direction = "";
if (target) {
if (!target.hasAttribute("sorted")) {
target.setAttribute("sorted", direction = "ascending");
target.setAttribute("tooltiptext", L10N.getStr("networkMenu.sortedAsc"));
} else if (target.getAttribute("sorted") == "ascending") {
target.setAttribute("sorted", direction = "descending");
target.setAttribute("tooltiptext", L10N.getStr("networkMenu.sortedDesc"));
} else {
target.removeAttribute("sorted");
target.removeAttribute("tooltiptext");
}
}
// Sort by timing.
if (!target || !direction) {
this.sortContents(this._byTiming);
}
// Sort by whatever was requested.
else switch (aType) {
case "status":
if (direction == "ascending") {
this.sortContents(this._byStatus);
} else {
this.sortContents((a, b) => !this._byStatus(a, b));
}
break;
case "method":
if (direction == "ascending") {
this.sortContents(this._byMethod);
} else {
this.sortContents((a, b) => !this._byMethod(a, b));
}
break;
case "file":
if (direction == "ascending") {
this.sortContents(this._byFile);
} else {
this.sortContents((a, b) => !this._byFile(a, b));
}
break;
case "domain":
if (direction == "ascending") {
this.sortContents(this._byDomain);
} else {
this.sortContents((a, b) => !this._byDomain(a, b));
}
break;
case "type":
if (direction == "ascending") {
this.sortContents(this._byType);
} else {
this.sortContents((a, b) => !this._byType(a, b));
}
break;
case "size":
if (direction == "ascending") {
this.sortContents(this._bySize);
} else {
this.sortContents((a, b) => !this._bySize(a, b));
}
break;
}
},
/**
* Predicates used when sorting items.
*
* @param MenuItem aFirst
* The first menu item used in the comparison.
* @param MenuItem aSecond
* The second menu item used in the comparison.
* @return number
* -1 to sort aFirst to a lower index than aSecond
* 0 to leave aFirst and aSecond unchanged with respect to each other
* 1 to sort aSecond to a lower index than aFirst
*/
_byTiming: (aFirst, aSecond) =>
aFirst.attachment.startedMillis > aSecond.attachment.startedMillis,
_byStatus: (aFirst, aSecond) =>
aFirst.attachment.status > aSecond.attachment.status,
_byMethod: (aFirst, aSecond) =>
aFirst.attachment.method > aSecond.attachment.method,
_byFile: (aFirst, aSecond) =>
!aFirst.target || !aSecond.target ? -1 :
$(".requests-menu-file", aFirst.target).getAttribute("value").toLowerCase() >
$(".requests-menu-file", aSecond.target).getAttribute("value").toLowerCase(),
_byDomain: (aFirst, aSecond) =>
!aFirst.target || !aSecond.target ? -1 :
$(".requests-menu-domain", aFirst.target).getAttribute("value").toLowerCase() >
$(".requests-menu-domain", aSecond.target).getAttribute("value").toLowerCase(),
_byType: (aFirst, aSecond) =>
!aFirst.target || !aSecond.target ? -1 :
$(".requests-menu-type", aFirst.target).getAttribute("value").toLowerCase() >
$(".requests-menu-type", aSecond.target).getAttribute("value").toLowerCase(),
_bySize: (aFirst, aSecond) =>
aFirst.attachment.contentSize > aSecond.attachment.contentSize,
/**
* Schedules adding additional information to a network request.
*
@ -356,8 +486,8 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
if (!this.lazyUpdate) {
return void this._flushRequests();
}
window.clearTimeout(this._updateTimeout);
this._updateTimeout = window.setTimeout(this._flushRequests, REQUESTS_REFRESH_RATE);
// Allow requests to settle down first.
drain("update-requests", REQUESTS_REFRESH_RATE, () => this._flushRequests());
},
/**
@ -444,8 +574,12 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
NetMonitorView.NetworkDetails.populate(selectedItem.attachment);
}
}
// We're done flushing all the requests, clear the update queue.
this._updateQueue = [];
// Make sure all the requests are sorted.
this.sortContents();
},
/**
@ -584,38 +718,19 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
},
/**
* Rescales and redraws all the waterfalls in this container.
* Rescales and redraws all the waterfall views in this container.
*
* @param boolean aReset
* True if this container's width was changed.
*/
_flushWaterfallViews: function NVRM__flushWaterfallViews(aReset) {
// To avoid expensive operations like getBoundingClientRect(), the
// waterfalls width is cached. However, in certain scenarios like when
// the window is resized, this needs to be invalidated.
// To avoid expensive operations like getBoundingClientRect() and
// rebuilding the waterfall background each time a new request comes in,
// stuff is cached. However, in certain scenarios like when the window
// is resized, this needs to be invalidated.
if (aReset) {
this._cachedWaterfallWidth = 0;
let table = $("#network-table");
let toolbar = $("#requests-menu-toolbar");
let columns = [
[".requests-menu-waterfall", "waterfall-overflows"],
[".requests-menu-size", "size-overflows"],
[".requests-menu-type", "type-overflows"],
[".requests-menu-domain", "domain-overflows"]
];
// Flush headers.
columns.forEach(([, attribute]) => table.removeAttribute(attribute));
let availableWidth = toolbar.getBoundingClientRect().width;
// Hide overflowing columns.
columns.forEach(([className, attribute]) => {
let bounds = $(".requests-menu-header" + className).getBoundingClientRect();
if (bounds.right > availableWidth - REQUESTS_HEADERS_SAFE_BOUNDS) {
table.setAttribute(attribute, "");
}
});
this._hideOverflowingColumns();
}
// Determine the scaling to be applied to all the waterfalls so that
@ -624,6 +739,11 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
let longestWidth = this._lastRequestEndedMillis - this._firstRequestStartedMillis;
let scale = Math.min(Math.max(availableWidth / longestWidth, EPSILON), 1);
// Redraw and set the canvas background for each waterfall view.
this._showWaterfallDivisionLabels(scale);
this._drawWaterfallBackground(scale);
this._flushWaterfallBackgrounds();
// Apply CSS transforms to each waterfall in this container totalTime
// accurately translate and resize as needed.
for (let [, { target, attachment }] of this._cache) {
@ -651,6 +771,145 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
}
},
/**
* Creates the labels displayed on the waterfall header in this container.
*
* @param number aScale
* The current waterfall scale.
*/
_showWaterfallDivisionLabels: function NVRM__showWaterfallDivisionLabels(aScale) {
let container = $("#requests-menu-waterfall-header-box");
let availableWidth = this._waterfallWidth - REQUESTS_WATERFALL_SAFE_BOUNDS;
// Nuke all existing labels.
while (container.hasChildNodes()) {
container.firstChild.remove();
}
// Build new millisecond tick labels...
let timingStep = REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE;
let optimalTickIntervalFound = false;
while (!optimalTickIntervalFound) {
// Ignore any divisions that would end up being too close to each other.
let scaledStep = aScale * timingStep;
if (scaledStep < REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN) {
timingStep <<= 1;
continue;
}
optimalTickIntervalFound = true;
// Insert one label for each division on the current scale.
let fragment = document.createDocumentFragment();
for (let x = 0; x < availableWidth; x += scaledStep) {
let divisionMS = (x / aScale).toFixed(0);
let translateX = "translateX(" + (x | 0) + "px)";
let node = document.createElement("label");
let text = L10N.getFormatStr("networkMenu.divisionMS", divisionMS);
node.className = "plain requests-menu-timings-division";
node.style.transform = translateX;
node.setAttribute("value", text);
fragment.appendChild(node);
}
container.appendChild(fragment);
}
},
/**
* Creates the background displayed on each waterfall view in this container.
*
* @param number aScale
* The current waterfall scale.
*/
_drawWaterfallBackground: function NVRM__drawWaterfallBackground(aScale) {
if (!this._canvas || !this._ctx) {
this._canvas = document.createElementNS(HTML_NS, "canvas");
this._ctx = this._canvas.getContext("2d");
}
let canvas = this._canvas;
let ctx = this._ctx;
// Nuke the context.
let canvasWidth = canvas.width = this._waterfallWidth;
let canvasHeight = canvas.height = 1; // Awww yeah, 1px, repeats on Y axis.
// Start over.
let imageData = ctx.createImageData(canvasWidth, canvasHeight);
let pixelArray = imageData.data;
let buf = new ArrayBuffer(pixelArray.length);
let buf8 = new Uint8ClampedArray(buf);
let data32 = new Uint32Array(buf);
// Build new millisecond tick lines...
let timingStep = REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE;
let alphaComponent = REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN;
let optimalTickIntervalFound = false;
while (!optimalTickIntervalFound) {
// Ignore any divisions that would end up being too close to each other.
let scaledStep = aScale * timingStep;
if (scaledStep < REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN) {
timingStep <<= 1;
continue;
}
optimalTickIntervalFound = true;
// Insert one pixel for each division on each scale.
for (let i = 1; i <= REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES; i++) {
let increment = scaledStep * Math.pow(2, i);
for (let x = 0; x < canvasWidth; x += increment) {
data32[x | 0] = (alphaComponent << 24) | (255 << 16) | (255 << 8) | 255;
}
alphaComponent += REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD;
}
}
// Flush the image data and cache the waterfall background.
pixelArray.set(buf8);
ctx.putImageData(imageData, 0, 0);
this._cachedWaterfallBackground = "url(" + canvas.toDataURL() + ")";
},
/**
* Reapplies the current waterfall background on all request items.
*/
_flushWaterfallBackgrounds: function NVRM__flushWaterfallBackgrounds() {
for (let [, { target }] of this._cache) {
let waterfallNode = $(".requests-menu-waterfall", target);
waterfallNode.style.backgroundImage = this._cachedWaterfallBackground;
}
},
/**
* Hides the overflowing columns in the requests table.
*/
_hideOverflowingColumns: function NVRM__hideOverflowingColumns() {
let table = $("#network-table");
let toolbar = $("#requests-menu-toolbar");
let columns = [
["#requests-menu-waterfall-header-box", "waterfall-overflows"],
["#requests-menu-size-header-box", "size-overflows"],
["#requests-menu-type-header-box", "type-overflows"],
["#requests-menu-domain-header-box", "domain-overflows"]
];
// Flush headers.
columns.forEach(([, attribute]) => table.removeAttribute(attribute));
let availableWidth = toolbar.getBoundingClientRect().width;
// Hide the columns.
columns.forEach(([id, attribute]) => {
let bounds = $(id).getBoundingClientRect();
if (bounds.right > availableWidth - REQUESTS_HEADERS_SAFE_BOUNDS) {
table.setAttribute(attribute, "");
}
});
},
/**
* Function called each time a network request item is removed.
*
@ -685,7 +944,8 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
* The resize listener for this container's window.
*/
_onResize: function NVRM__onResize(e) {
this._flushWaterfallViews(true);
// Allow requests to settle down first.
drain("resize-events", RESIZE_REFRESH_RATE, () => this._flushWaterfallViews(true));
},
/**
@ -721,7 +981,7 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
get _waterfallWidth() {
if (this._cachedWaterfallWidth == 0) {
let container = $("#requests-menu-toolbar");
let waterfall = $("#requests-menu-waterfall-label");
let waterfall = $("#requests-menu-waterfall-header-box");
let containerBounds = container.getBoundingClientRect();
let waterfallBounds = waterfall.getBoundingClientRect();
this._cachedWaterfallWidth = containerBounds.width - waterfallBounds.left;
@ -730,11 +990,15 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
},
_cache: null,
_canvas: null,
_ctx: null,
_cachedWaterfallWidth: 0,
_cachedWaterfallBackground: null,
_firstRequestStartedMillis: -1,
_lastRequestEndedMillis: -1,
_updateQueue: [],
_updateTimeout: null
_updateTimeout: null,
_resizeTimeout: null
});
/**
@ -1116,7 +1380,7 @@ create({ constructor: NetworkDetailsView, proto: MenuContainer.prototype }, {
$("#response-content-textarea-box").hidden = false;
NetMonitorView.editor("#response-content-textarea").then((aEditor) => {
aEditor.setMode(SourceEditor.MODES.TEXT);
aEditor.setText(aString);
aEditor.setText(NetworkHelper.convertToUnicode(aString, "UTF-8"));
// Maybe set a more appropriate mode in the Source Editor if possible.
for (let key in CONTENT_MIME_TYPE_MAPPINGS) {
@ -1213,6 +1477,22 @@ create({ constructor: NetworkDetailsView, proto: MenuContainer.prototype }, {
_responseCookies: ""
});
/**
* DOM query helper.
*/
function $(aSelector, aTarget = document) aTarget.querySelector(aSelector);
/**
* Helper for draining a rapid succession of events and invoking a callback
* once everything settles down.
*/
function drain(aId, aWait, aCallback) {
window.clearTimeout(drain.store.get(aId));
drain.store.set(aId, window.setTimeout(aCallback, aWait));
}
drain.store = new Map();
/**
* Preliminary setup for the NetMonitorView object.
*/

Просмотреть файл

@ -3,6 +3,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#details-pane-toggle[disabled] {
visibility: hidden;
}
#response-content-image-box {
overflow: auto;
}

Просмотреть файл

@ -22,34 +22,75 @@
<toolbar id="requests-menu-toolbar"
class="devtools-toolbar"
align="center">
<label id="requests-menu-status-and-method-label"
class="plain requests-menu-header requests-menu-status-and-method"
value="&netmonitorUI.toolbar.method;"
crop="end"/>
<label id="requests-menu-file-label"
class="plain requests-menu-header requests-menu-file"
value="&netmonitorUI.toolbar.file;"
crop="end"/>
<label id="requests-menu-domain-label"
class="plain requests-menu-header requests-menu-domain"
value="&netmonitorUI.toolbar.domain;"
crop="end"/>
<label id="requests-menu-type-label"
class="plain requests-menu-header requests-menu-type"
value="&netmonitorUI.toolbar.type;"
crop="end"/>
<label id="requests-menu-size-label"
class="plain requests-menu-header requests-menu-size"
value="&netmonitorUI.toolbar.size;"
crop="end"/>
<hbox id="toolbar-labels" flex="1">
<hbox id="requests-menu-status-and-method-header-box"
class="requests-menu-header requests-menu-status-and-method"
align="center">
<button id="requests-menu-status-button"
class="requests-menu-header-button requests-menu-status"
onclick="NetMonitorView.RequestsMenu.sortBy('status')">
&netmonitorUI.toolbar.status;
</button>
<button id="requests-menu-method-button"
class="requests-menu-header-button requests-menu-method"
onclick="NetMonitorView.RequestsMenu.sortBy('method')"
flex="1">
&netmonitorUI.toolbar.method;
</button>
</hbox>
<hbox id="requests-menu-file-header-box"
class="requests-menu-header requests-menu-file"
align="center">
<button id="requests-menu-file-button"
class="requests-menu-header-button requests-menu-file"
onclick="NetMonitorView.RequestsMenu.sortBy('file')"
flex="1">
&netmonitorUI.toolbar.file;
</button>
</hbox>
<hbox id="requests-menu-domain-header-box"
class="requests-menu-header requests-menu-domain"
align="center">
<button id="requests-menu-domain-button"
class="requests-menu-header-button requests-menu-domain"
onclick="NetMonitorView.RequestsMenu.sortBy('domain')"
flex="1">
&netmonitorUI.toolbar.domain;
</button>
</hbox>
<hbox id="requests-menu-type-header-box"
class="requests-menu-header requests-menu-type"
align="center">
<button id="requests-menu-type-button"
class="requests-menu-header-button requests-menu-type"
onclick="NetMonitorView.RequestsMenu.sortBy('type')"
flex="1">
&netmonitorUI.toolbar.type;
</button>
</hbox>
<hbox id="requests-menu-size-header-box"
class="requests-menu-header requests-menu-size"
align="center">
<button id="requests-menu-size-button"
class="requests-menu-header-button requests-menu-size"
onclick="NetMonitorView.RequestsMenu.sortBy('size')"
flex="1">
&netmonitorUI.toolbar.size;
</button>
</hbox>
<hbox id="requests-menu-waterfall-header-box"
class="requests-menu-header requests-menu-waterfall"
align="center">
<label id="requests-menu-waterfall-label"
class="plain requests-menu-header requests-menu-waterfall"
value="&netmonitorUI.toolbar.waterfall;"
crop="end"/>
class="plain requests-menu-waterfall"
value="&netmonitorUI.toolbar.waterfall;"/>
</hbox>
</hbox>
<spacer id="toolbar-spacer" flex="1"/>
<toolbarbutton id="details-pane-toggle"
class="devtools-toolbarbutton"
tooltiptext="&netmonitorUI.panesButton.tooltip;"
disabled="true"
tabindex="0"/>
</toolbar>
<label class="plain requests-menu-empty-notice"
@ -58,7 +99,7 @@
<hbox id="requests-menu-item-template" hidden="true">
<hbox class="requests-menu-subitem requests-menu-status-and-method"
align="center">
<hbox class="requests-menu-status"/>
<box class="requests-menu-status"/>
<label class="plain requests-menu-method"
crop="end"
flex="1"/>
@ -124,7 +165,7 @@
align="center">
<label class="plain tabpanel-summary-label"
value="&netmonitorUI.summary.status;"/>
<hbox id="headers-summary-status-circle"
<box id="headers-summary-status-circle"
class="requests-menu-status"/>
<label id="headers-summary-status-value"
class="plain tabpanel-summary-value"

Просмотреть файл

@ -12,6 +12,7 @@ include $(DEPTH)/config/autoconf.mk
MOCHITEST_BROWSER_TESTS = \
browser_net_aaa_leaktest.js \
browser_net_autoscroll.js \
browser_net_simple-init.js \
browser_net_page-nav.js \
browser_net_prefs-and-l10n.js \
@ -21,10 +22,15 @@ MOCHITEST_BROWSER_TESTS = \
browser_net_simple-request-data.js \
browser_net_simple-request-details.js \
browser_net_content-type.js \
browser_net_cyrillic.js \
browser_net_status-codes.js \
browser_net_post-data.js \
browser_net_jsonp.js \
browser_net_json-long.js \
browser_net_timeline_ticks.js \
browser_net_sort-01.js \
browser_net_sort-02.js \
browser_net_sort-03.js \
head.js \
$(NULL)
@ -33,13 +39,17 @@ MOCHITEST_BROWSER_PAGES = \
html_simple-test-page.html \
html_navigate-test-page.html \
html_content-type-test-page.html \
html_cyrillic-test-page.html \
html_status-codes-test-page.html \
html_post-data-test-page.html \
html_jsonp-test-page.html \
html_json-long-test-page.html \
html_sorting-test-page.html \
html_infinite-get-page.html \
sjs_simple-test-server.sjs \
sjs_content-type-test-server.sjs \
sjs_status-codes-test-server.sjs \
sjs_sorting-test-server.sjs \
$(NULL)
MOCHITEST_BROWSER_FILES_PARTS = MOCHITEST_BROWSER_TESTS MOCHITEST_BROWSER_PAGES

Просмотреть файл

@ -0,0 +1,77 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Bug 863102 - Automatically scroll down upon new network requests.
*/
function test() {
let monitor, debuggee, requestsContainer, scrollTop;
initNetMonitor(INFINITE_GET_URL).then(([aTab, aDebuggee, aMonitor]) => {
monitor = aMonitor;
debuggee = aDebuggee;
let win = monitor.panelWin;
let topNode = win.document.getElementById("requests-menu-contents");
requestsContainer = topNode.getElementsByTagName("scrollbox")[0];
ok(!!requestsContainer, "Container element exists as expected.");
})
// (1) Check that the scroll position is maintained at the bottom
// when the requests overflow the vertical size of the container.
.then(() => {
debuggee.performRequests();
return waitForRequestsToOverflowContainer(monitor, requestsContainer);
}).then(() => {
ok(scrolledToBottom(requestsContainer), "Scrolled to bottom on overflow.");
})
// (2) Now set the scroll position somewhere in the middle and check
// that additional requests do not change the scroll position.
.then(() => {
let children = requestsContainer.childNodes;
let middleNode = children.item(children.length / 2);
middleNode.scrollIntoView();
ok(!scrolledToBottom(requestsContainer), "Not scrolled to bottom.");
scrollTop = requestsContainer.scrollTop; // save for comparison later
return waitForNetworkEvents(monitor, 8);
}).then(() => {
is(requestsContainer.scrollTop, scrollTop, "Did not scroll.");
})
// (3) Now set the scroll position back at the bottom and check that
// additional requests *do* cause the container to scroll down.
.then(() => {
requestsContainer.scrollTop = requestsContainer.scrollHeight;
ok(scrolledToBottom(requestsContainer), "Set scroll position to bottom.");
return waitForNetworkEvents(monitor, 8);
}).then(() => {
ok(scrolledToBottom(requestsContainer), "Still scrolled to bottom.");
})
// Done; clean up.
.then(() => {
return teardown(monitor).then(finish);
})
// Handle exceptions in the chain of promises.
.then(null, (err) => {
ok(false, err);
finish();
});
function waitForRequestsToOverflowContainer (aMonitor, aContainer) {
return waitForNetworkEvents(aMonitor, 1).then(() => {
if (aContainer.scrollHeight > aContainer.clientHeight) {
// Wait for some more just for good measure.
return waitForNetworkEvents(aMonitor, 8);
} else {
return waitForRequestsToOverflowContainer(aMonitor, aContainer);
}
});
}
function scrolledToBottom(aElement) {
return aElement.scrollTop + aElement.clientHeight >= aElement.scrollHeight;
}
}

Просмотреть файл

@ -0,0 +1,41 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if cyrillic text is rendered correctly in the source editor.
*/
function test() {
initNetMonitor(CYRILLIC_URL).then(([aTab, aDebuggee, aMonitor]) => {
info("Starting test... ");
let { document, L10N, SourceEditor, NetMonitorView } = aMonitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
waitForNetworkEvents(aMonitor, 1).then(() => {
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
"GET", CONTENT_TYPE_SJS + "?fmt=txt", {
status: 200,
statusText: "DA DA DA"
});
EventUtils.sendMouseEvent({ type: "mousedown" },
document.getElementById("details-pane-toggle"));
EventUtils.sendMouseEvent({ type: "mousedown" },
document.querySelectorAll("#details-pane tab")[3]);
NetMonitorView.editor("#response-content-textarea").then((aEditor) => {
is(aEditor.getText().indexOf("\u044F"), 26, // я
"The text shown in the source editor is incorrect.");
is(aEditor.getMode(), SourceEditor.MODES.TEXT,
"The mode active in the source editor is incorrect.");
teardown(aMonitor).then(finish);
});
});
aDebuggee.performRequests();
});
}

Просмотреть файл

@ -14,6 +14,9 @@ function test() {
RequestsMenu.lazyUpdate = false;
is(document.querySelector("#details-pane-toggle")
.hasAttribute("disabled"), true,
"The pane toggle button should be disabled when the frontend is opened.");
is(document.querySelector(".requests-menu-empty-notice")
.hasAttribute("hidden"), false,
"An empty notice should be displayed when the frontend is opened.");
@ -23,6 +26,9 @@ function test() {
"The details pane should be hidden when the frontend is opened.");
aMonitor.panelWin.once("NetMonitor:NetworkEvent", () => {
is(document.querySelector("#details-pane-toggle")
.hasAttribute("disabled"), false,
"The pane toggle button should be enabled after the first request.");
is(document.querySelector(".requests-menu-empty-notice")
.hasAttribute("hidden"), true,
"The empty notice should be hidden after the first request.");
@ -32,6 +38,9 @@ function test() {
"The details pane should still be hidden after the first request.");
aMonitor.panelWin.once("NetMonitor:NetworkEvent", () => {
is(document.querySelector("#details-pane-toggle")
.hasAttribute("disabled"), false,
"The pane toggle button should be still be enabled after a reload.");
is(document.querySelector(".requests-menu-empty-notice")
.hasAttribute("hidden"), true,
"The empty notice should be still hidden after a reload.");

Просмотреть файл

@ -0,0 +1,225 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test if the sorting mechanism works correctly.
*/
function test() {
initNetMonitor(STATUS_CODES_URL).then(([aTab, aDebuggee, aMonitor]) => {
info("Starting test... ");
let { L10N, NetMonitorView } = aMonitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
waitForNetworkEvents(aMonitor, 5).then(() => {
testContents([0, 1, 2, 3, 4])
.then(() => {
info("Testing swap(0, 0)");
RequestsMenu.swapItemsAtIndices(0, 0);
return testContents([0, 1, 2, 3, 4]);
})
.then(() => {
info("Testing swap(0, 1)");
RequestsMenu.swapItemsAtIndices(0, 1);
return testContents([1, 0, 2, 3, 4]);
})
.then(() => {
info("Testing swap(0, 2)");
RequestsMenu.swapItemsAtIndices(0, 2);
return testContents([1, 2, 0, 3, 4]);
})
.then(() => {
info("Testing swap(0, 3)");
RequestsMenu.swapItemsAtIndices(0, 3);
return testContents([1, 2, 3, 0, 4]);
})
.then(() => {
info("Testing swap(0, 4)");
RequestsMenu.swapItemsAtIndices(0, 4);
return testContents([1, 2, 3, 4, 0]);
})
.then(() => {
info("Testing swap(1, 0)");
RequestsMenu.swapItemsAtIndices(1, 0);
return testContents([0, 2, 3, 4, 1]);
})
.then(() => {
info("Testing swap(1, 1)");
RequestsMenu.swapItemsAtIndices(1, 1);
return testContents([0, 2, 3, 4, 1]);
})
.then(() => {
info("Testing swap(1, 2)");
RequestsMenu.swapItemsAtIndices(1, 2);
return testContents([0, 1, 3, 4, 2]);
})
.then(() => {
info("Testing swap(1, 3)");
RequestsMenu.swapItemsAtIndices(1, 3);
return testContents([0, 3, 1, 4, 2]);
})
.then(() => {
info("Testing swap(1, 4)");
RequestsMenu.swapItemsAtIndices(1, 4);
return testContents([0, 3, 4, 1, 2]);
})
.then(() => {
info("Testing swap(2, 0)");
RequestsMenu.swapItemsAtIndices(2, 0);
return testContents([2, 3, 4, 1, 0]);
})
.then(() => {
info("Testing swap(2, 1)");
RequestsMenu.swapItemsAtIndices(2, 1);
return testContents([1, 3, 4, 2, 0]);
})
.then(() => {
info("Testing swap(2, 2)");
RequestsMenu.swapItemsAtIndices(2, 2);
return testContents([1, 3, 4, 2, 0]);
})
.then(() => {
info("Testing swap(2, 3)");
RequestsMenu.swapItemsAtIndices(2, 3);
return testContents([1, 2, 4, 3, 0]);
})
.then(() => {
info("Testing swap(2, 4)");
RequestsMenu.swapItemsAtIndices(2, 4);
return testContents([1, 4, 2, 3, 0]);
})
.then(() => {
info("Testing swap(3, 0)");
RequestsMenu.swapItemsAtIndices(3, 0);
return testContents([1, 4, 2, 0, 3]);
})
.then(() => {
info("Testing swap(3, 1)");
RequestsMenu.swapItemsAtIndices(3, 1);
return testContents([3, 4, 2, 0, 1]);
})
.then(() => {
info("Testing swap(3, 2)");
RequestsMenu.swapItemsAtIndices(3, 2);
return testContents([2, 4, 3, 0, 1]);
})
.then(() => {
info("Testing swap(3, 3)");
RequestsMenu.swapItemsAtIndices(3, 3);
return testContents([2, 4, 3, 0, 1]);
})
.then(() => {
info("Testing swap(3, 4)");
RequestsMenu.swapItemsAtIndices(3, 4);
return testContents([2, 3, 4, 0, 1]);
})
.then(() => {
info("Testing swap(4, 0)");
RequestsMenu.swapItemsAtIndices(4, 0);
return testContents([2, 3, 0, 4, 1]);
})
.then(() => {
info("Testing swap(4, 1)");
RequestsMenu.swapItemsAtIndices(4, 1);
return testContents([2, 3, 0, 1, 4]);
})
.then(() => {
info("Testing swap(4, 2)");
RequestsMenu.swapItemsAtIndices(4, 2);
return testContents([4, 3, 0, 1, 2]);
})
.then(() => {
info("Testing swap(4, 3)");
RequestsMenu.swapItemsAtIndices(4, 3);
return testContents([3, 4, 0, 1, 2]);
})
.then(() => {
info("Testing swap(4, 4)");
RequestsMenu.swapItemsAtIndices(4, 4);
return testContents([3, 4, 0, 1, 2]);
})
.then(() => {
RequestsMenu.sortBy(null);
return testContents([0, 1, 2, 3, 4]);
})
.then(() => {
return teardown(aMonitor);
})
.then(finish);
});
function testContents([a, b, c, d, e]) {
let deferred = Promise.defer();
is(RequestsMenu.allItems.length, 5,
"There should be a total of 5 items in the requests menu.");
is(RequestsMenu.visibleItems.length, 5,
"There should be a total of 5 visbile items in the requests menu.");
is(RequestsMenu.getItemAtIndex(0), RequestsMenu.allItems[0],
"The requests menu items aren't ordered correctly. First item is misplaced.");
is(RequestsMenu.getItemAtIndex(1), RequestsMenu.allItems[1],
"The requests menu items aren't ordered correctly. Second item is misplaced.");
is(RequestsMenu.getItemAtIndex(2), RequestsMenu.allItems[2],
"The requests menu items aren't ordered correctly. Third item is misplaced.");
is(RequestsMenu.getItemAtIndex(3), RequestsMenu.allItems[3],
"The requests menu items aren't ordered correctly. Fourth item is misplaced.");
is(RequestsMenu.getItemAtIndex(4), RequestsMenu.allItems[4],
"The requests menu items aren't ordered correctly. Fifth item is misplaced.");
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(a),
"GET", STATUS_CODES_SJS + "?sts=100", {
status: 101,
statusText: "Switching Protocols",
type: "plain",
fullMimeType: "text/plain; charset=utf-8",
size: L10N.getFormatStr("networkMenu.sizeKB", 0),
time: true
});
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(b),
"GET", STATUS_CODES_SJS + "?sts=200", {
status: 202,
statusText: "Created",
type: "plain",
fullMimeType: "text/plain; charset=utf-8",
size: L10N.getFormatStr("networkMenu.sizeKB", 0.02),
time: true
});
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(c),
"GET", STATUS_CODES_SJS + "?sts=300", {
status: 303,
statusText: "See Other",
type: "plain",
fullMimeType: "text/plain; charset=utf-8",
size: L10N.getFormatStr("networkMenu.sizeKB", 0),
time: true
});
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(d),
"GET", STATUS_CODES_SJS + "?sts=400", {
status: 404,
statusText: "Not Found",
type: "plain",
fullMimeType: "text/plain; charset=utf-8",
size: L10N.getFormatStr("networkMenu.sizeKB", 0.02),
time: true
});
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(e),
"GET", STATUS_CODES_SJS + "?sts=500", {
status: 501,
statusText: "Not Implemented",
type: "plain",
fullMimeType: "text/plain; charset=utf-8",
size: L10N.getFormatStr("networkMenu.sizeKB", 0.02),
time: true
});
executeSoon(deferred.resolve);
return deferred.promise;
}
aDebuggee.performRequests();
});
}

Просмотреть файл

@ -0,0 +1,225 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test if sorting columns in the network table works correctly.
*/
function test() {
initNetMonitor(SORTING_URL).then(([aTab, aDebuggee, aMonitor]) => {
info("Starting test... ");
let { $, L10N, NetMonitorView } = aMonitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
waitForNetworkEvents(aMonitor, 5).then(() => {
EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
isnot(RequestsMenu.selectedItem, null,
"There should be a selected item in the requests menu.");
is(RequestsMenu.selectedIndex, 0,
"The first item should be selected in the requests menu.");
is(NetMonitorView.detailsPaneHidden, false,
"The details pane should not be hidden after toggle button was pressed.");
testHeaders();
testContents([0, 2, 4, 3, 1])
.then(() => {
info("Testing status sort, ascending.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button"));
testHeaders("status", "ascending");
return testContents([0, 1, 2, 3, 4]);
})
.then(() => {
info("Testing status sort, descending.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button"));
testHeaders("status", "descending");
return testContents([4, 3, 2, 1, 0]);
})
.then(() => {
info("Clearing status sort.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button"));
testHeaders();
return testContents([0, 2, 4, 3, 1]);
})
.then(() => {
info("Testing method sort, ascending.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-method-button"));
testHeaders("method", "ascending");
return testContents([0, 1, 2, 3, 4]);
})
.then(() => {
info("Testing method sort, descending.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-method-button"));
testHeaders("method", "descending");
return testContents([4, 3, 2, 1, 0]);
})
.then(() => {
info("Clearing method sort.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-method-button"));
testHeaders();
return testContents([0, 2, 4, 3, 1]);
})
.then(() => {
info("Testing file sort, ascending.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-file-button"));
testHeaders("file", "ascending");
return testContents([0, 1, 2, 3, 4]);
})
.then(() => {
info("Testing file sort, descending.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-file-button"));
testHeaders("file", "descending");
return testContents([4, 3, 2, 1, 0]);
})
.then(() => {
info("Clearing file sort.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-file-button"));
testHeaders();
return testContents([0, 2, 4, 3, 1]);
})
.then(() => {
info("Testing type sort, ascending.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-type-button"));
testHeaders("type", "ascending");
return testContents([0, 1, 2, 3, 4]);
})
.then(() => {
info("Testing type sort, descending.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-type-button"));
testHeaders("type", "descending");
return testContents([4, 3, 2, 1, 0]);
})
.then(() => {
info("Clearing type sort.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-type-button"));
testHeaders();
return testContents([0, 2, 4, 3, 1]);
})
.then(() => {
info("Testing size sort, ascending.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
testHeaders("size", "ascending");
return testContents([0, 1, 2, 3, 4]);
})
.then(() => {
info("Testing size sort, descending.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
testHeaders("size", "descending");
return testContents([4, 3, 2, 1, 0]);
})
.then(() => {
info("Clearing size sort.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
testHeaders();
return testContents([0, 2, 4, 3, 1]);
})
.then(() => {
return teardown(aMonitor);
})
.then(finish);
});
function testHeaders(aSortType, aDirection) {
let doc = aMonitor.panelWin.document;
let target = doc.querySelector("#requests-menu-" + aSortType + "-button");
let headers = doc.querySelectorAll(".requests-menu-header-button");
for (let header of headers) {
if (header != target) {
is(header.hasAttribute("sorted"), false,
"The " + header.id + " header should not have a 'sorted' attribute.");
is(header.hasAttribute("tooltiptext"), false,
"The " + header.id + " header should not have a 'tooltiptext' attribute.");
} else {
is(header.getAttribute("sorted"), aDirection,
"The " + header.id + " header has an incorrect 'sorted' attribute.");
is(header.getAttribute("tooltiptext"), aDirection == "ascending"
? L10N.getStr("networkMenu.sortedAsc")
: L10N.getStr("networkMenu.sortedDesc"),
"The " + header.id + " has an incorrect 'tooltiptext' attribute.");
}
}
}
function testContents([a, b, c, d, e]) {
let deferred = Promise.defer();
isnot(RequestsMenu.selectedItem, null,
"There should still be a selected item after sorting.");
is(RequestsMenu.selectedIndex, a,
"The first item should be still selected after sorting.");
is(NetMonitorView.detailsPaneHidden, false,
"The details pane should still be visible after sorting.");
is(RequestsMenu.allItems.length, 5,
"There should be a total of 5 items in the requests menu.");
is(RequestsMenu.visibleItems.length, 5,
"There should be a total of 5 visbile items in the requests menu.");
is(RequestsMenu.getItemAtIndex(0), RequestsMenu.allItems[0],
"The requests menu items aren't ordered correctly. First item is misplaced.");
is(RequestsMenu.getItemAtIndex(1), RequestsMenu.allItems[1],
"The requests menu items aren't ordered correctly. Second item is misplaced.");
is(RequestsMenu.getItemAtIndex(2), RequestsMenu.allItems[2],
"The requests menu items aren't ordered correctly. Third item is misplaced.");
is(RequestsMenu.getItemAtIndex(3), RequestsMenu.allItems[3],
"The requests menu items aren't ordered correctly. Fourth item is misplaced.");
is(RequestsMenu.getItemAtIndex(4), RequestsMenu.allItems[4],
"The requests menu items aren't ordered correctly. Fifth item is misplaced.");
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(a),
"GET1", SORTING_SJS + "?index=1", {
status: 101,
statusText: "Meh",
type: "1",
fullMimeType: "text/1",
size: L10N.getFormatStr("networkMenu.sizeKB", 0),
time: true
});
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(b),
"GET2", SORTING_SJS + "?index=2", {
status: 200,
statusText: "Meh",
type: "2",
fullMimeType: "text/2",
size: L10N.getFormatStr("networkMenu.sizeKB", 0.01),
time: true
});
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(c),
"GET3", SORTING_SJS + "?index=3", {
status: 300,
statusText: "Meh",
type: "3",
fullMimeType: "text/3",
size: L10N.getFormatStr("networkMenu.sizeKB", 0.02),
time: true
});
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(d),
"GET4", SORTING_SJS + "?index=4", {
status: 400,
statusText: "Meh",
type: "4",
fullMimeType: "text/4",
size: L10N.getFormatStr("networkMenu.sizeKB", 0.03),
time: true
});
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(e),
"GET5", SORTING_SJS + "?index=5", {
status: 500,
statusText: "Meh",
type: "5",
fullMimeType: "text/5",
size: L10N.getFormatStr("networkMenu.sizeKB", 0.04),
time: true
});
executeSoon(deferred.resolve);
return deferred.promise;
}
aDebuggee.performRequests();
});
}

Просмотреть файл

@ -0,0 +1,171 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test if sorting columns in the network table works correctly with new requests.
*/
function test() {
initNetMonitor(SORTING_URL).then(([aTab, aDebuggee, aMonitor]) => {
info("Starting test... ");
let { $, L10N, NetMonitorView } = aMonitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
waitForNetworkEvents(aMonitor, 5).then(() => {
EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
isnot(RequestsMenu.selectedItem, null,
"There should be a selected item in the requests menu.");
is(RequestsMenu.selectedIndex, 0,
"The first item should be selected in the requests menu.");
is(NetMonitorView.detailsPaneHidden, false,
"The details pane should not be hidden after toggle button was pressed.");
testHeaders();
testContents([0, 2, 4, 3, 1], 0)
.then(() => {
info("Testing status sort, ascending.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button"));
testHeaders("status", "ascending");
return testContents([0, 1, 2, 3, 4], 0);
})
.then(() => {
info("Performing more requests.");
aDebuggee.performRequests();
return waitForNetworkEvents(aMonitor, 5);
})
.then(() => {
info("Testing status sort again, ascending.");
testHeaders("status", "ascending");
return testContents([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0);
})
.then(() => {
info("Testing status sort, descending.");
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button"));
testHeaders("status", "descending");
return testContents([9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 9);
})
.then(() => {
info("Performing more requests.");
aDebuggee.performRequests();
return waitForNetworkEvents(aMonitor, 5);
})
.then(() => {
info("Testing status sort again, descending.");
testHeaders("status", "descending");
return testContents([14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 12);
})
.then(() => {
return teardown(aMonitor);
})
.then(finish);
});
function testHeaders(aSortType, aDirection) {
let doc = aMonitor.panelWin.document;
let target = doc.querySelector("#requests-menu-" + aSortType + "-button");
let headers = doc.querySelectorAll(".requests-menu-header-button");
for (let header of headers) {
if (header != target) {
is(header.hasAttribute("sorted"), false,
"The " + header.id + " header should not have a 'sorted' attribute.");
is(header.hasAttribute("tooltiptext"), false,
"The " + header.id + " header should not have a 'tooltiptext' attribute.");
} else {
is(header.getAttribute("sorted"), aDirection,
"The " + header.id + " header has an incorrect 'sorted' attribute.");
is(header.getAttribute("tooltiptext"), aDirection == "ascending"
? L10N.getStr("networkMenu.sortedAsc")
: L10N.getStr("networkMenu.sortedDesc"),
"The " + header.id + " has an incorrect 'tooltiptext' attribute.");
}
}
}
function testContents(aOrder, aSelection) {
let deferred = Promise.defer();
isnot(RequestsMenu.selectedItem, null,
"There should still be a selected item after sorting.");
is(RequestsMenu.selectedIndex, aSelection,
"The first item should be still selected after sorting.");
is(NetMonitorView.detailsPaneHidden, false,
"The details pane should still be visible after sorting.");
is(RequestsMenu.allItems.length, aOrder.length,
"There should be a specific number of items in the requests menu.");
is(RequestsMenu.visibleItems.length, aOrder.length,
"There should be a specific number of visbile items in the requests menu.");
for (let i = 0; i < aOrder.length; i++) {
is(RequestsMenu.getItemAtIndex(i), RequestsMenu.allItems[i],
"The requests menu items aren't ordered correctly. Misplaced item " + i + ".");
}
for (let i = 0, len = aOrder.length / 5; i < len; i++) {
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i]),
"GET1", SORTING_SJS + "?index=1", {
status: 101,
statusText: "Meh",
type: "1",
fullMimeType: "text/1",
size: L10N.getFormatStr("networkMenu.sizeKB", 0),
time: true
});
}
for (let i = 0, len = aOrder.length / 5; i < len; i++) {
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len]),
"GET2", SORTING_SJS + "?index=2", {
status: 200,
statusText: "Meh",
type: "2",
fullMimeType: "text/2",
size: L10N.getFormatStr("networkMenu.sizeKB", 0.01),
time: true
});
}
for (let i = 0, len = aOrder.length / 5; i < len; i++) {
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 2]),
"GET3", SORTING_SJS + "?index=3", {
status: 300,
statusText: "Meh",
type: "3",
fullMimeType: "text/3",
size: L10N.getFormatStr("networkMenu.sizeKB", 0.02),
time: true
});
}
for (let i = 0, len = aOrder.length / 5; i < len; i++) {
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 3]),
"GET4", SORTING_SJS + "?index=4", {
status: 400,
statusText: "Meh",
type: "4",
fullMimeType: "text/4",
size: L10N.getFormatStr("networkMenu.sizeKB", 0.03),
time: true
});
}
for (let i = 0, len = aOrder.length / 5; i < len; i++) {
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 4]),
"GET5", SORTING_SJS + "?index=5", {
status: 500,
statusText: "Meh",
type: "5",
fullMimeType: "text/5",
size: L10N.getFormatStr("networkMenu.sizeKB", 0.04),
time: true
});
}
executeSoon(deferred.resolve);
return deferred.promise;
}
aDebuggee.performRequests();
});
}

Просмотреть файл

@ -0,0 +1,137 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if timeline correctly displays interval divisions.
*/
function test() {
initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => {
info("Starting test... ");
let { document, L10N, NetMonitorView } = aMonitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
is(document.querySelector(".requests-menu-empty-notice")
.hasAttribute("hidden"), false,
"An timeline label should be displayed when the frontend is opened.");
ok(document.querySelectorAll(".requests-menu-timings-division").length == 0,
"No tick labels should be displayed when the frontend is opened.");
ok(!RequestsMenu._canvas,
"No canvas should be created when the frontend is opened.");
ok(!RequestsMenu._ctx,
"No 2d context should be created when the frontend is opened.");
waitForNetworkEvents(aMonitor, 1).then(() => {
is(document.querySelector(".requests-menu-empty-notice")
.hasAttribute("hidden"), true,
"The timeline label should be hidden after the first request.");
ok(document.querySelectorAll(".requests-menu-timings-division").length >= 3,
"There should be at least 3 tick labels in the network requests header.");
is(document.querySelectorAll(".requests-menu-timings-division")[0]
.getAttribute("value"), L10N.getFormatStr("networkMenu.divisionMS", 0),
"The first tick label has an incorrect value");
is(document.querySelectorAll(".requests-menu-timings-division")[1]
.getAttribute("value"), L10N.getFormatStr("networkMenu.divisionMS", 80),
"The second tick label has an incorrect value");
is(document.querySelectorAll(".requests-menu-timings-division")[2]
.getAttribute("value"), L10N.getFormatStr("networkMenu.divisionMS", 160),
"The third tick label has an incorrect value");
is(document.querySelectorAll(".requests-menu-timings-division")[0]
.style.transform, "translateX(0px)",
"The first tick label has an incorrect translation");
is(document.querySelectorAll(".requests-menu-timings-division")[1]
.style.transform, "translateX(80px)",
"The second tick label has an incorrect translation");
is(document.querySelectorAll(".requests-menu-timings-division")[2]
.style.transform, "translateX(160px)",
"The third tick label has an incorrect translation");
ok(RequestsMenu._canvas,
"A canvas should be created after the first request.");
ok(RequestsMenu._ctx,
"A 2d context should be created after the first request.");
let imageData = RequestsMenu._ctx.getImageData(0, 0, 161, 1);
ok(imageData, "The image data should have been created.");
let data = imageData.data;
ok(data, "The image data should contain a pixel array.");
ok( hasPixelAt(0), "The tick at 0 is should not be empty.");
ok(!hasPixelAt(1), "The tick at 1 is should be empty.");
ok(!hasPixelAt(19), "The tick at 19 is should be empty.");
ok( hasPixelAt(20), "The tick at 20 is should not be empty.");
ok(!hasPixelAt(21), "The tick at 21 is should be empty.");
ok(!hasPixelAt(39), "The tick at 39 is should be empty.");
ok( hasPixelAt(40), "The tick at 40 is should not be empty.");
ok(!hasPixelAt(41), "The tick at 41 is should be empty.");
ok(!hasPixelAt(59), "The tick at 59 is should be empty.");
ok( hasPixelAt(60), "The tick at 60 is should not be empty.");
ok(!hasPixelAt(61), "The tick at 61 is should be empty.");
ok(!hasPixelAt(79), "The tick at 79 is should be empty.");
ok( hasPixelAt(80), "The tick at 80 is should not be empty.");
ok(!hasPixelAt(81), "The tick at 81 is should be empty.");
ok(!hasPixelAt(159), "The tick at 159 is should be empty.");
ok( hasPixelAt(160), "The tick at 160 is should not be empty.");
ok(!hasPixelAt(161), "The tick at 161 is should be empty.");
ok(isPixelBrighterAtThan(0, 20),
"The tick at 0 should be brighter than the one at 20");
ok(isPixelBrighterAtThan(40, 20),
"The tick at 40 should be brighter than the one at 20");
ok(isPixelBrighterAtThan(40, 60),
"The tick at 40 should be brighter than the one at 60");
ok(isPixelBrighterAtThan(80, 60),
"The tick at 80 should be brighter than the one at 60");
ok(isPixelBrighterAtThan(80, 100),
"The tick at 80 should be brighter than the one at 100");
ok(isPixelBrighterAtThan(120, 100),
"The tick at 120 should be brighter than the one at 100");
ok(isPixelBrighterAtThan(120, 140),
"The tick at 120 should be brighter than the one at 140");
ok(isPixelBrighterAtThan(160, 140),
"The tick at 160 should be brighter than the one at 140");
ok(isPixelEquallyBright(20, 60),
"The tick at 20 should be equally bright to the one at 60");
ok(isPixelEquallyBright(100, 140),
"The tick at 100 should be equally bright to the one at 140");
ok(isPixelEquallyBright(40, 120),
"The tick at 40 should be equally bright to the one at 120");
ok(isPixelEquallyBright(0, 80),
"The tick at 80 should be equally bright to the one at 160");
ok(isPixelEquallyBright(80, 160),
"The tick at 80 should be equally bright to the one at 160");
function hasPixelAt(x) {
let i = (x | 0) * 4;
return data[i] && data[i + 1] && data[i + 2] && data[i + 3];
}
function isPixelBrighterAtThan(x1, x2) {
let i = (x1 | 0) * 4;
let j = (x2 | 0) * 4;
return data[i + 3] > data [j + 3];
}
function isPixelEquallyBright(x1, x2) {
let i = (x1 | 0) * 4;
let j = (x2 | 0) * 4;
return data[i + 3] == data [j + 3];
}
teardown(aMonitor).then(finish);
});
aDebuggee.location.reload();
});
}

Просмотреть файл

@ -15,14 +15,18 @@ const EXAMPLE_URL = "http://example.com/browser/browser/devtools/netmonitor/test
const SIMPLE_URL = EXAMPLE_URL + "html_simple-test-page.html";
const NAVIGATE_URL = EXAMPLE_URL + "html_navigate-test-page.html";
const CONTENT_TYPE_URL = EXAMPLE_URL + "html_content-type-test-page.html";
const CYRILLIC_URL = EXAMPLE_URL + "html_cyrillic-test-page.html";
const STATUS_CODES_URL = EXAMPLE_URL + "html_status-codes-test-page.html";
const POST_DATA_URL = EXAMPLE_URL + "html_post-data-test-page.html";
const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html";
const JSON_LONG_URL = EXAMPLE_URL + "html_json-long-test-page.html";
const SORTING_URL = EXAMPLE_URL + "html_sorting-test-page.html";
const INFINITE_GET_URL = EXAMPLE_URL + "html_infinite-get-page.html";
const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs";
const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs";
const STATUS_CODES_SJS = EXAMPLE_URL + "sjs_status-codes-test-server.sjs";
const SORTING_SJS = EXAMPLE_URL + "sjs_sorting-test-server.sjs";
const TEST_IMAGE = EXAMPLE_URL + "test-image.png";

Просмотреть файл

@ -0,0 +1,33 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Network Monitor test page</title>
</head>
<body>
<p>Cyrillic type test</p>
<script type="text/javascript">
function get(aAddress, aCallback) {
var xhr = new XMLHttpRequest();
xhr.open("GET", aAddress, true);
xhr.onreadystatechange = function() {
if (this.readyState == this.DONE) {
aCallback();
}
};
xhr.send(null);
}
function performRequests() {
get("sjs_content-type-test-server.sjs?fmt=txt", function() {
// Done.
});
}
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,36 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Network Monitor test page</title>
</head>
<body>
<p>Infinite GETs</p>
<script type="text/javascript">
function get(aAddress, aCallback) {
var xhr = new XMLHttpRequest();
xhr.open("GET", aAddress, true);
xhr.onreadystatechange = function() {
if (this.readyState == this.DONE) {
aCallback();
}
};
xhr.send(null);
}
// Use a count parameter to defeat caching.
var count = 0;
function performRequests() {
get("request_" + (count++), function() {
setTimeout(performRequests, 0);
});
}
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,41 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Network Monitor test page</title>
</head>
<body>
<p>Sorting test</p>
<script type="text/javascript">
function get(aAddress, aIndex, aCallback) {
var xhr = new XMLHttpRequest();
xhr.open("GET" + aIndex, aAddress + "?index=" + aIndex, true);
xhr.onreadystatechange = function() {
if (this.readyState == this.DONE) {
aCallback();
}
};
xhr.send(null);
}
function performRequests() {
get("sjs_sorting-test-server.sjs", 1, function() {
get("sjs_sorting-test-server.sjs", 5, function() {
get("sjs_sorting-test-server.sjs", 2, function() {
get("sjs_sorting-test-server.sjs", 4, function() {
get("sjs_sorting-test-server.sjs", 3, function() {
// Done.
});
});
});
});
});
}
</script>
</body>
</html>

Просмотреть файл

@ -11,6 +11,13 @@ function handleRequest(request, response) {
Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer).initWithCallback(() => {
switch (format) {
case "txt": {
response.setStatusLine(request.httpVersion, 200, "DA DA DA");
response.setHeader("Content-Type", "text/plain", false);
response.write("Братан, ты вообще качаешься?");
response.finish();
break;
}
case "xml": {
response.setStatusLine(request.httpVersion, 200, "OK");
response.setHeader("Content-Type", "text/xml; charset=utf-8", false);

Просмотреть файл

@ -0,0 +1,18 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const { classes: Cc, interfaces: Ci } = Components;
function handleRequest(request, response) {
response.processAsync();
let params = request.queryString.split("&");
let index = params.filter((s) => s.contains("index="))[0].split("=")[1];
Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer).initWithCallback(() => {
response.setStatusLine(request.httpVersion, index == 1 ? 101 : index * 100, "Meh");
response.setHeader("Content-Type", "text/" + index, false);
response.write(new Array(index * 10).join(index)); // + 0.01 KB
response.finish();
}, 50, Ci.nsITimer.TYPE_ONE_SHOT); // Make sure this request takes a few ms.
}

Просмотреть файл

@ -5,7 +5,7 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
this.EXPORTED_SYMBOLS = [];
Cu.import("resource:///modules/devtools/gcli.jsm");
Cu.import("resource://gre/modules/devtools/gcli.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/devtools/Require.jsm");
@ -80,7 +80,7 @@ gcli.addCommand({
}
if (profile.isFinished) {
throw gcli.lookup("profilerAlradyFinished");
throw gcli.lookup("profilerAlreadyFinished");
}
panel.switchToProfile(profile, function () profile.start());

Просмотреть файл

@ -3,7 +3,7 @@
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
let gcli = Cu.import("resource:///modules/devtools/gcli.jsm", {}).gcli;
let gcli = Cu.import("resource://gre/modules/devtools/gcli.jsm", {}).gcli;
let gTarget, gPanel, gOptions;
function cmd(typed, expected="") {

Просмотреть файл

@ -11,7 +11,7 @@ const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"].
this.EXPORTED_SYMBOLS = [ ];
Cu.import("resource:///modules/devtools/gcli.jsm");
Cu.import("resource://gre/modules/devtools/gcli.jsm");
/* Responsive Mode commands */
gcli.addCommand({

Просмотреть файл

@ -21,11 +21,10 @@ this.EXPORTED_SYMBOLS = [ 'helpers' ];
var helpers = {};
this.helpers = helpers;
let require = (Cu.import("resource://gre/modules/devtools/Require.jsm", {})).require;
Components.utils.import("resource:///modules/devtools/gcli.jsm", {});
Components.utils.import("resource://gre/modules/devtools/gcli.jsm", {});
let console = (Cu.import("resource://gre/modules/devtools/Console.jsm", {})).console;
let devtools = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools;
let TargetFactory = devtools.TargetFactory;
let TargetFactory = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools.TargetFactory;
let Promise = (Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {})).Promise;
let assert = { ok: ok, is: is, log: info };
@ -158,6 +157,7 @@ helpers.runTests = function(options, tests) {
});
var recover = function(error) {
ok(false, error);
console.error(error);
};
@ -341,7 +341,7 @@ helpers._createDebugCheck = function(options) {
output += ' current: \'' + helpers._actual.current(options) + '\',\n';
output += ' status: \'' + helpers._actual.status(options) + '\',\n';
output += ' options: ' + outputArray(helpers._actual.options(options)) + ',\n';
output += ' error: \'' + helpers._actual.message(options) + '\',\n';
output += ' message: \'' + helpers._actual.message(options) + '\',\n';
output += ' predictions: ' + outputArray(predictions) + ',\n';
output += ' unassigned: ' + outputArray(requisition._unassigned) + ',\n';
output += ' outputState: \'' + helpers._actual.outputState(options) + '\',\n';
@ -378,6 +378,8 @@ helpers._createDebugCheck = function(options) {
output += ' exec: {\n';
output += ' output: \'\',\n';
output += ' completed: true,\n';
output += ' type: \'string\',\n';
output += ' error: false\n';
output += ' }\n';
output += ' }\n';
output += ']);';
@ -702,7 +704,7 @@ helpers._check = function(options, name, checks) {
*/
helpers._exec = function(options, name, expected) {
if (expected == null) {
return Promise.resolve();
return Promise.resolve({});
}
var output = options.display.requisition.exec({ hidden: true });
@ -715,20 +717,32 @@ helpers._exec = function(options, name, expected) {
if (!options.window.document.createElement) {
assert.log('skipping output tests (missing doc.createElement) for ' + name);
return Promise.resolve();
return Promise.resolve({ output: output });
}
if (!('output' in expected)) {
return Promise.resolve();
return Promise.resolve({ output: output });
}
var deferred = Promise.defer();
var checkOutput = function() {
var div = options.window.document.createElement('div');
var nodePromise = converters.convert(output.data, output.type, 'dom',
options.display.requisition.context);
nodePromise.then(function(node) {
var conversionContext = options.display.requisition.conversionContext;
if ('type' in expected) {
assert.is(output.type,
expected.type,
'output.type for: ' + name);
}
if ('error' in expected) {
assert.is(output.error,
expected.error,
'output.error for: ' + name);
}
var convertPromise = converters.convert(output.data, output.type, 'dom',
conversionContext);
return convertPromise.then(function(node) {
div.appendChild(node);
var actualOutput = div.textContent.trim();
@ -757,24 +771,11 @@ helpers._exec = function(options, name, expected) {
doTest(expected.output, actualOutput);
}
deferred.resolve(actualOutput);
return { output: output, text: actualOutput };
});
};
if (output.completed !== false) {
checkOutput();
}
else {
var changed = function() {
if (output.completed !== false) {
checkOutput();
output.onChange.remove(changed);
}
};
output.onChange.add(changed);
}
return deferred.promise;
return output.promise.then(checkOutput, checkOutput);
};
/**
@ -789,15 +790,15 @@ helpers._setup = function(options, name, action) {
return Promise.resolve(action());
}
return Promise.reject('setup must be a string or a function');
return Promise.reject('\'setup\' property must be a string or a function. Is ' + action);
};
/**
* Helper to shutdown the test
*/
helpers._post = function(name, action, output) {
helpers._post = function(name, action, data) {
if (typeof action === 'function') {
return Promise.resolve(action(output));
return Promise.resolve(action(data.output, data.text));
}
return Promise.resolve(action);
};
@ -943,6 +944,8 @@ helpers.audit = function(options, audits) {
if (typeof chunkLen !== 'number') {
chunkLen = 1;
}
if (assert.currentTest) {
var responseTime = (new Date().getTime() - start) / chunkLen;
totalResponseTime += responseTime;
if (responseTime > maxResponseTime) {
@ -950,12 +953,13 @@ helpers.audit = function(options, audits) {
maxResponseCulprit = assert.currentTest + '/' + name;
}
averageOver++;
}
var checkDone = helpers._check(options, name, audit.check);
return checkDone.then(function() {
var execDone = helpers._exec(options, name, audit.exec);
return execDone.then(function(output) {
return helpers._post(name, audit.post, output).then(function() {
return execDone.then(function(data) {
return helpers._post(name, audit.post, data).then(function() {
if (assert.testLogging) {
log('- END \'' + name + '\' in ' + assert.currentTest);
}
@ -963,9 +967,8 @@ helpers.audit = function(options, audits) {
});
});
});
}).then(null, function(ex) {
console.error(ex.stack);
throw(ex);
}).then(function() {
return options.display.inputter.setInput('');
});
};

Просмотреть файл

@ -4,7 +4,7 @@
this.EXPORTED_SYMBOLS = [ ];
Components.utils.import("resource:///modules/devtools/gcli.jsm");
Components.utils.import("resource://gre/modules/devtools/gcli.jsm");
/**
* 'scratchpad' command

Просмотреть файл

@ -21,7 +21,6 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource:///modules/PropertyPanel.jsm");
Cu.import("resource:///modules/source-editor.jsm");
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
Cu.import("resource:///modules/devtools/scratchpad-manager.jsm");
@ -79,7 +78,7 @@ var Scratchpad = {
.replace(/^\/\*/, "")
.replace(/\*\/$/, "");
aLine.split(",").forEach(function (pair) {
aLine.split(",").forEach(pair => {
let [key, val] = pair.split(":");
if (key && val) {
@ -621,18 +620,18 @@ var Scratchpad = {
let encoder = new TextEncoder();
let buffer = encoder.encode(this.getText());
let promise = OS.File.writeAtomic(aFile.path, buffer,{tmpPath: aFile.path + ".tmp"});
promise.then(function success(value) {
promise.then(value => {
if (aCallback) {
aCallback.call(this, Components.results.NS_OK);
}
}.bind(this), function failure(reason) {
}, reason => {
if (!aSilentError) {
window.alert(this.strings.GetStringFromName("saveFile.failed"));
}
if (aCallback) {
aCallback.call(this, Components.results.NS_ERROR_UNEXPECTED);
}
}.bind(this));
});
},
@ -656,8 +655,7 @@ var Scratchpad = {
let channel = NetUtil.newChannel(aFile);
channel.contentType = "application/javascript";
let self = this;
NetUtil.asyncFetch(channel, function(aInputStream, aStatus) {
NetUtil.asyncFetch(channel, (aInputStream, aStatus) => {
let content = null;
if (Components.isSuccessCode(aStatus)) {
@ -670,22 +668,22 @@ var Scratchpad = {
// Check to see if the first line is a mode-line comment.
let line = content.split("\n")[0];
let modeline = self._scanModeLine(line);
let modeline = this._scanModeLine(line);
let chrome = Services.prefs.getBoolPref(DEVTOOLS_CHROME_ENABLED);
if (chrome && modeline["-sp-context"] === "browser") {
self.setBrowserContext();
this.setBrowserContext();
}
self.setText(content);
self.editor.resetUndo();
this.setText(content);
this.editor.resetUndo();
}
else if (!aSilentError) {
window.alert(self.strings.GetStringFromName("openFile.failed"));
window.alert(this.strings.GetStringFromName("openFile.failed"));
}
if (aCallback) {
aCallback.call(self, aStatus, content);
aCallback.call(this, aStatus, content);
}
});
},
@ -698,8 +696,8 @@ var Scratchpad = {
*/
openFile: function SP_openFile(aIndex)
{
let promptCallback = function(aFile) {
this.promptSave(function(aCloseFile, aSaved, aStatus) {
let promptCallback = aFile => {
this.promptSave((aCloseFile, aSaved, aStatus) => {
let shouldOpen = aCloseFile;
if (aSaved && !Components.isSuccessCode(aStatus)) {
shouldOpen = false;
@ -732,23 +730,21 @@ var Scratchpad = {
this.importFromFile(file, false);
this.setRecentFile(file);
}
}.bind(this));
}.bind(this);
});
};
if (aIndex > -1) {
promptCallback();
} else {
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
let fpCallback = function fpCallback_done(aResult) {
if (aResult != Ci.nsIFilePicker.returnCancel) {
promptCallback(fp.file);
}
};
fp.init(window, this.strings.GetStringFromName("openFile.title"),
Ci.nsIFilePicker.modeOpen);
fp.defaultString = "";
fp.open(fpCallback);
fp.open(aResult => {
if (aResult != Ci.nsIFilePicker.returnCancel) {
promptCallback(fp.file);
}
});
}
},
@ -953,7 +949,7 @@ var Scratchpad = {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
file.initWithPath(this.filename);
this.exportToFile(file, true, false, function(aStatus) {
this.exportToFile(file, true, false, aStatus => {
if (Components.isSuccessCode(aStatus)) {
this.editor.dirty = false;
this.setRecentFile(file);
@ -973,10 +969,10 @@ var Scratchpad = {
saveFileAs: function SP_saveFileAs(aCallback)
{
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
let fpCallback = function fpCallback_done(aResult) {
let fpCallback = aResult => {
if (aResult != Ci.nsIFilePicker.returnCancel) {
this.setFilename(fp.file.path);
this.exportToFile(fp.file, true, false, function(aStatus) {
this.exportToFile(fp.file, true, false, aStatus => {
if (Components.isSuccessCode(aStatus)) {
this.editor.dirty = false;
this.setRecentFile(fp.file);
@ -986,7 +982,7 @@ var Scratchpad = {
}
});
}
}.bind(this);
};
fp.init(window, this.strings.GetStringFromName("saveFileAs"),
Ci.nsIFilePicker.modeSave);
@ -1009,7 +1005,7 @@ var Scratchpad = {
return;
}
this.importFromFile(file, false, function(aStatus, aContent) {
this.importFromFile(file, false, (aStatus, aContent) => {
if (aCallback) {
aCallback(aStatus);
}
@ -1045,8 +1041,8 @@ var Scratchpad = {
return;
}
if (button == BUTTON_POSITION_REVERT) {
this.revertFile(function(aStatus) {
if(aCallback){
this.revertFile(aStatus => {
if (aCallback) {
aCallback(true, aStatus);
}
});
@ -1348,7 +1344,7 @@ var Scratchpad = {
}
if (button == BUTTON_POSITION_SAVE) {
this.saveFile(function(aStatus) {
this.saveFile(aStatus => {
if (aCallback) {
aCallback(true, true, aStatus);
}
@ -1387,7 +1383,7 @@ var Scratchpad = {
*/
close: function SP_close(aCallback)
{
this.promptSave(function(aShouldClose, aSaved, aStatus) {
this.promptSave((aShouldClose, aSaved, aStatus) => {
let shouldClose = aShouldClose;
if (aSaved && !Components.isSuccessCode(aStatus)) {
shouldClose = false;
@ -1399,7 +1395,7 @@ var Scratchpad = {
if (aCallback) {
aCallback();
}
}.bind(this));
});
},
_observers: [],

Просмотреть файл

@ -53,6 +53,7 @@
<command id="sp-cmd-errorConsole" oncommand="Scratchpad.openErrorConsole();" disabled="true"/>
<command id="sp-cmd-webConsole" oncommand="Scratchpad.openWebConsole();"/>
<command id="sp-cmd-documentationLink" oncommand="Scratchpad.openDocumentationPage();"/>
<command id="sp-cmd-hideSidebar" oncommand="Scratchpad.sidebar.hide();"/>
</commandset>
<keyset id="sourceEditorKeys"/>
@ -102,6 +103,9 @@
key="&errorConsoleCmd.commandkey;"
command="sp-cmd-errorConsole"
modifiers="accel,shift"/>
<key id="sp-key-hideSidebar"
keycode="VK_ESCAPE"
command="sp-cmd-hideSidebar"/>
<key id="key_openHelp"
keycode="VK_F1"
command="sp-cmd-documentationLink"/>

Просмотреть файл

@ -241,6 +241,10 @@ AppCacheUtils.prototype = {
},
listEntries: function ACU_show(searchTerm) {
if (!Services.prefs.getBoolPref("browser.cache.disk.enable")) {
throw new Error(l10n.GetStringFromName("cacheDisabled"));
}
let entries = [];
Services.cache.visitEntries({
@ -275,6 +279,9 @@ AppCacheUtils.prototype = {
}
});
if (entries.length == 0) {
throw new Error(l10n.GetStringFromName("noResults"));
}
return entries;
},
@ -336,7 +343,7 @@ AppCacheUtils.prototype = {
} else {
this.errors.push({
line: 0,
msg: "The URI passed to AppCacheUtils is invalid."
msg: l10n.GetStringFromName("invalidURI")
});
}
});

Просмотреть файл

@ -1,28 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* Define various constants to match the globals provided by the browser.
* This module helps cases where code is shared between the web and Firefox.
* See also Console.jsm for an implementation of the Firefox console that
* forwards to dump();
*/
this.EXPORTED_SYMBOLS = [ "Node", "HTMLElement", "setTimeout", "clearTimeout" ];
/**
* Expose Node/HTMLElement objects. This allows us to use the Node constants
* without resorting to hardcoded numbers
*/
this.Node = Components.interfaces.nsIDOMNode;
this.HTMLElement = Components.interfaces.nsIDOMHTMLElement;
/*
* Import and re-export the timeout functions from Timer.jsm.
*/
let Timer = Components.utils.import("resource://gre/modules/Timer.jsm", {});
this.setTimeout = Timer.setTimeout;
this.clearTimeout = Timer.clearTimeout;

Просмотреть файл

@ -19,7 +19,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "console",
"resource://gre/modules/devtools/Console.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "gcli",
"resource:///modules/devtools/gcli.jsm");
"resource://gre/modules/devtools/gcli.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CmdCommands",
"resource:///modules/devtools/BuiltinCommands.jsm");
@ -153,7 +153,7 @@ let CommandUtils = {
createEnvironment: function(chromeDocument, contentDocument) {
let environment = {
chromeDocument: chromeDocument,
contentDocument: contentDocument, // Use of contentDocument is deprecated
chromeWindow: chromeDocument.defaultView,
document: contentDocument,
window: contentDocument.defaultView
@ -181,9 +181,13 @@ this.CommandUtils = CommandUtils;
* to using panels.
*/
XPCOMUtils.defineLazyGetter(this, "isLinux", function () {
return OS == "Linux";
});
XPCOMUtils.defineLazyGetter(this, "OS", function () {
let os = Components.classes["@mozilla.org/xre/app-info;1"]
.getService(Components.interfaces.nsIXULRuntime).OS;
return os == "Linux";
return os;
});
/**
@ -761,6 +765,7 @@ function OutputPanel(aDevToolbar, aLoadCallback)
this.displayedOutput = undefined;
this._onload = this._onload.bind(this);
this._update = this._update.bind(this);
this._frame.addEventListener("load", this._onload, true);
this.loaded = false;
@ -791,33 +796,6 @@ OutputPanel.prototype._onload = function OP_onload()
}
};
/**
* Determine the scrollbar width in the current document.
*
* @private
*/
Object.defineProperty(OutputPanel.prototype, 'scrollbarWidth', {
get: function() {
if (this.__scrollbarWidth) {
return this.__scrollbarWidth;
}
let hbox = this.document.createElementNS(XUL_NS, "hbox");
hbox.setAttribute("style", "height: 0%; overflow: hidden");
let scrollbar = this.document.createElementNS(XUL_NS, "scrollbar");
scrollbar.setAttribute("orient", "vertical");
hbox.appendChild(scrollbar);
this.document.documentElement.appendChild(hbox);
this.__scrollbarWidth = scrollbar.clientWidth;
this.document.documentElement.removeChild(hbox);
return this.__scrollbarWidth;
},
enumerable: true
});
/**
* Prevent the popup from hiding if it is not permitted via this.canHide.
*/
@ -863,13 +841,32 @@ OutputPanel.prototype._resize = function CLP_resize()
// Set max panel width to match any content with a max of the width of the
// browser window.
let maxWidth = this._panel.ownerDocument.documentElement.clientWidth;
let width = Math.min(maxWidth, this.document.documentElement.scrollWidth);
// Add scrollbar width to content size in case a scrollbar is needed.
width += this.scrollbarWidth;
// Adjust max width according to OS.
// We'd like to put this in CSS but we can't:
// body { width: calc(min(-5px, max-content)); }
// #_panel { max-width: -5px; }
switch(OS) {
case "Linux":
maxWidth -= 5;
break;
case "Darwin":
maxWidth -= 25;
break;
case "WINNT":
maxWidth -= 5;
break;
}
this.document.body.style.width = "-moz-max-content";
let style = this._frame.contentWindow.getComputedStyle(this.document.body);
let frameWidth = parseInt(style.width, 10);
let width = Math.min(maxWidth, frameWidth);
this.document.body.style.width = width + "px";
// Set the width of the iframe.
this._frame.style.minWidth = width + "px";
this._panel.style.maxWidth = maxWidth + "px";
// browserAdjustment is used to correct the panel height according to the
// browsers borders etc.
@ -906,18 +903,28 @@ OutputPanel.prototype._outputChanged = function OP_outputChanged(aEvent)
this.remove();
this.displayedOutput = aEvent.output;
this.update();
this.displayedOutput.onChange.add(this.update, this);
this.displayedOutput.onClose.add(this.remove, this);
if (this.displayedOutput.completed) {
this._update();
}
else {
this.displayedOutput.promise.then(this._update, this._update)
.then(null, console.error);
}
};
/**
* Called when displayed Output says it's changed or from outputChanged, which
* happens when there is a new displayed Output.
*/
OutputPanel.prototype.update = function OP_update()
OutputPanel.prototype._update = function OP_update()
{
// destroy has been called, bail out
if (this._div == null) {
return;
}
// Empty this._div
while (this._div.hasChildNodes()) {
this._div.removeChild(this._div.firstChild);
@ -927,7 +934,7 @@ OutputPanel.prototype.update = function OP_update()
let requisition = this._devtoolbar.display.requisition;
let nodePromise = converters.convert(this.displayedOutput.data,
this.displayedOutput.type, 'dom',
requisition.context);
requisition.conversionContext);
nodePromise.then(function(node) {
while (this._div.hasChildNodes()) {
this._div.removeChild(this._div.firstChild);
@ -958,7 +965,6 @@ OutputPanel.prototype.remove = function OP_remove()
}
if (this.displayedOutput) {
this.displayedOutput.onChange.remove(this.update, this);
this.displayedOutput.onClose.remove(this.remove, this);
delete this.displayedOutput;
}

Просмотреть файл

@ -14,7 +14,6 @@ include $(DEPTH)/config/autoconf.mk
DISABLED_XPCSHELL_TESTS = unit
MOCHITEST_BROWSER_FILES = \
browser_browser_basic.js \
browser_require_basic.js \
browser_templater_basic.js \
browser_toolbar_basic.js \

Просмотреть файл

@ -1,28 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests exports from Browser.jsm
const TEST_URI = "data:text/html;charset=utf-8,<p id=id>Text</p>";
let imported = {};
Components.utils.import("resource:///modules/devtools/Browser.jsm", imported);
registerCleanupFunction(function tearDown() {
imported = undefined;
});
function test() {
addTab(TEST_URI, function(browser, tab, document) {
runTest(browser, tab, document);
});
}
function runTest(browser, tab, document) {
var p = document.getElementById("id");
ok(p instanceof imported.Node, "Node correctly defined");
ok(p instanceof imported.HTMLElement, "HTMLElement correctly defined");
finish();
}

Просмотреть файл

@ -10,7 +10,7 @@
*/
var Promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise;
var template = Cu.import("resource:///modules/devtools/Templater.jsm", {}).template;
var template = Cu.import("resource://gre/modules/devtools/Templater.jsm", {}).template;
const TEST_URI = "http://example.com/browser/browser/devtools/shared/test/browser_templater_basic.html";

Просмотреть файл

@ -35,6 +35,8 @@ this.EXPORTED_SYMBOLS = ["BreadcrumbsWidget"];
* The element associated with the widget.
*/
this.BreadcrumbsWidget = function BreadcrumbsWidget(aNode) {
this.document = aNode.ownerDocument;
this.window = this.document.defaultView;
this._parent = aNode;
// Create an internal arrowscrollbox container.
@ -59,9 +61,6 @@ this.BreadcrumbsWidget = function BreadcrumbsWidget(aNode) {
};
BreadcrumbsWidget.prototype = {
get document() this._parent.ownerDocument,
get window() this.document.defaultView,
/**
* Inserts an item in this container at the specified index.
*
@ -108,13 +107,12 @@ BreadcrumbsWidget.prototype = {
* Removes all of the child nodes from this container.
*/
removeAllItems: function BCW_removeAllItems() {
let parent = this._parent;
let list = this._list;
let firstChild;
while (firstChild = list.firstChild) {
list.removeChild(firstChild);
while (list.hasChildNodes()) {
list.firstChild.remove();
}
this._selectedItem = null;
},
@ -146,11 +144,11 @@ BreadcrumbsWidget.prototype = {
// Repeated calls to ensureElementIsVisible would interfere with each other
// and may sometimes result in incorrect scroll positions.
this.window.clearTimeout(this._ensureVisibleTimeout);
this._ensureVisibleTimeout = this.window.setTimeout(function() {
this._ensureVisibleTimeout = this.window.setTimeout(() => {
if (this._selectedItem) {
this._list.ensureElementIsVisible(this._selectedItem);
}
}.bind(this), ENSURE_SELECTION_VISIBLE_DELAY);
}, ENSURE_SELECTION_VISIBLE_DELAY);
},
/**
@ -177,6 +175,8 @@ BreadcrumbsWidget.prototype = {
target.setAttribute("overflows", "");
},
window: null,
document: null,
_parent: null,
_list: null,
_selectedItem: null,
@ -192,6 +192,8 @@ BreadcrumbsWidget.prototype = {
* The string or node displayed in the container.
*/
function Breadcrumb(aWidget, aContents) {
this.document = aWidget.document;
this.window = aWidget.window;
this.ownerView = aWidget;
this._target = this.document.createElement("hbox");
@ -201,9 +203,6 @@ function Breadcrumb(aWidget, aContents) {
}
Breadcrumb.prototype = {
get document() this.ownerView.document,
get window() this.document.defaultView,
/**
* Sets the contents displayed in this item's view.
*
@ -228,6 +227,8 @@ Breadcrumb.prototype = {
this._target.appendChild(aContents);
},
window: null,
document: null,
ownerView: null,
_target: null
};

Просмотреть файл

@ -37,6 +37,8 @@ this.EXPORTED_SYMBOLS = ["SideMenuWidget"];
* Specifies if items in this container should display horizontal arrows.
*/
this.SideMenuWidget = function SideMenuWidget(aNode, aShowArrows = true) {
this.document = aNode.ownerDocument;
this.window = this.document.defaultView;
this._parent = aNode;
this._showArrows = aShowArrows;
@ -60,8 +62,10 @@ this.SideMenuWidget = function SideMenuWidget(aNode, aShowArrows = true) {
};
SideMenuWidget.prototype = {
get document() this._parent.ownerDocument,
get window() this.document.defaultView,
/**
* Specifies if groups in this container should be sorted alphabetically.
*/
sortedGroups: true,
/**
* Specifies if this container should try to keep the selected item visible.
@ -70,9 +74,12 @@ SideMenuWidget.prototype = {
maintainSelectionVisible: true,
/**
* Specifies if groups in this container should be sorted alphabetically.
* Specifies that the container viewport should be "stuck" to the
* bottom. That is, the container is automatically scrolled down to
* keep appended items visible, but only when the scroll position is
* already at the bottom.
*/
sortedGroups: true,
autoscrollWithAppendedItems: false,
/**
* Inserts an item in this container at the specified index, optionally
@ -90,12 +97,26 @@ SideMenuWidget.prototype = {
* The element associated with the displayed item.
*/
insertItemAt: function SMW_insertItemAt(aIndex, aContents, aTooltip = "", aGroup = "") {
if (this.maintainSelectionVisible) {
this.ensureSelectionIsVisible(true, true); // Don't worry, it's delayed.
}
// Invalidate any notices set on this widget.
this.removeAttribute("notice");
let maintainScrollAtBottom =
this.autoscrollWithAppendedItems &&
(aIndex < 0 || aIndex >= this._orderedMenuElementsArray.length) &&
(this._list.scrollTop + this._list.clientHeight >= this._list.scrollHeight);
let group = this._getGroupForName(aGroup);
return group.insertItemAt(aIndex, aContents, aTooltip, this._showArrows);
let item = this._getItemForGroup(group, aContents, aTooltip);
let element = item.insertSelfAt(aIndex);
if (this.maintainSelectionVisible) {
this.ensureSelectionIsVisible({ withGroup: true, delayed: true });
}
if (maintainScrollAtBottom) {
this._list.scrollTop = this._list.scrollHeight;
}
return element;
},
/**
@ -117,9 +138,14 @@ SideMenuWidget.prototype = {
* The element associated with the displayed item.
*/
removeChild: function SMW_removeChild(aChild) {
if (aChild.className == "side-menu-widget-item-contents") {
// Remove the item itself, not the contents.
let item = aChild.parentNode;
item.parentNode.removeChild(item);
aChild.parentNode.remove();
} else {
// Groups with no title don't have any special internal structure.
aChild.remove();
}
this._orderedMenuElementsArray.splice(
this._orderedMenuElementsArray.indexOf(aChild), 1);
@ -134,11 +160,11 @@ SideMenuWidget.prototype = {
removeAllItems: function SMW_removeAllItems() {
let parent = this._parent;
let list = this._list;
let firstChild;
while (firstChild = list.firstChild) {
list.removeChild(firstChild);
while (list.hasChildNodes()) {
list.firstChild.remove();
}
this._selectedItem = null;
this._groupsByName.clear();
@ -157,12 +183,12 @@ SideMenuWidget.prototype = {
* @param nsIDOMNode aChild
*/
set selectedItem(aChild) {
let menuElementsArray = this._orderedMenuElementsArray;
let menuArray = this._orderedMenuElementsArray;
if (!aChild) {
this._selectedItem = null;
}
for (let node of menuElementsArray) {
for (let node of menuArray) {
if (node == aChild) {
node.classList.add("selected");
node.parentNode.classList.add("selected");
@ -172,18 +198,18 @@ SideMenuWidget.prototype = {
node.parentNode.classList.remove("selected");
}
}
// Repeated calls to ensureElementIsVisible would interfere with each other
// and may sometimes result in incorrect scroll positions.
this.ensureSelectionIsVisible(false, true);
this.ensureSelectionIsVisible({ delayed: true });
},
/**
* Ensures the selected element is visible.
* @see SideMenuWidget.prototype.ensureElementIsVisible.
*/
ensureSelectionIsVisible:
function SMW_ensureSelectionIsVisible(aGroupFlag, aDelayedFlag) {
this.ensureElementIsVisible(this.selectedItem, aGroupFlag, aDelayedFlag);
ensureSelectionIsVisible: function SMW_ensureSelectionIsVisible(aFlags) {
this.ensureElementIsVisible(this.selectedItem, aFlags);
},
/**
@ -191,28 +217,31 @@ SideMenuWidget.prototype = {
*
* @param nsIDOMNode aElement
* The element to make visible.
* @param boolean aGroupFlag
* True if the group header should also be made visible, if possible.
* @param boolean aDelayedFlag
* True to wait a few cycles before ensuring the selection is visible.
* @param object aFlags [optional]
* An object containing some of the following flags:
* - withGroup: true if the group header should also be made visible, if possible
* - delayed: wait a few cycles before ensuring the selection is visible
*/
ensureElementIsVisible:
function SMW_ensureElementIsVisible(aElement, aGroupFlag, aDelayedFlag) {
ensureElementIsVisible: function SMW_ensureElementIsVisible(aElement, aFlags = {}) {
if (!aElement) {
return;
}
if (aDelayedFlag) {
if (aFlags.delayed) {
delete aFlags.delayed;
this.window.clearTimeout(this._ensureVisibleTimeout);
this._ensureVisibleTimeout = this.window.setTimeout(function() {
this.ensureElementIsVisible(aElement, aGroupFlag, false);
}.bind(this), ENSURE_SELECTION_VISIBLE_DELAY);
this._ensureVisibleTimeout = this.window.setTimeout(() => {
this.ensureElementIsVisible(aElement, aFlags);
}, ENSURE_SELECTION_VISIBLE_DELAY);
return;
}
if (aGroupFlag) {
if (aFlags.withGroup) {
let groupList = aElement.parentNode;
let groupContainer = groupList.parentNode;
groupContainer.scrollIntoView(true); // Align with the top.
}
this._boxObject.ensureElementIsVisible(aElement);
},
@ -349,6 +378,24 @@ SideMenuWidget.prototype = {
return group;
},
/**
* Gets a menu item to be displayed inside a group.
* @see SideMenuWidget.prototype._getGroupForName
*
* @param SideMenuGroup aGroup
* The group to contain the menu item.
* @param string | nsIDOMNode aContents
* The string or node displayed in the container.
* @param string aTooltip [optional]
* A tooltip attribute for the displayed item.
*/
_getItemForGroup: function SMW__getItemForGroup(aGroup, aContents, aTooltip) {
return new SideMenuItem(aGroup, aContents, aTooltip, this._showArrows);
},
window: null,
document: null,
_showArrows: false,
_parent: null,
_list: null,
_boxObject: null,
@ -372,70 +419,44 @@ SideMenuWidget.prototype = {
* The string displayed in the container.
*/
function SideMenuGroup(aWidget, aName) {
this.document = aWidget.document;
this.window = aWidget.window;
this.ownerView = aWidget;
this.identifier = aName;
let document = this.document;
let title = this._title = document.createElement("hbox");
// Create an internal title and list container.
if (aName) {
let target = this._target = this.document.createElement("vbox");
target.className = "side-menu-widget-group";
target.setAttribute("name", aName);
target.setAttribute("tooltiptext", aName);
let list = this._list = this.document.createElement("vbox");
list.className = "side-menu-widget-group-list";
let title = this._title = this.document.createElement("hbox");
title.className = "side-menu-widget-group-title";
let name = this._name = document.createElement("label");
let name = this._name = this.document.createElement("label");
name.className = "plain name";
name.setAttribute("value", aName);
name.setAttribute("crop", "end");
name.setAttribute("flex", "1");
let list = this._list = document.createElement("vbox");
list.className = "side-menu-widget-group-list";
let target = this._target = document.createElement("vbox");
target.className = "side-menu-widget-group side-menu-widget-item-or-group";
target.setAttribute("name", aName);
target.setAttribute("tooltiptext", aName);
title.appendChild(name);
target.appendChild(title);
target.appendChild(list);
}
// Skip a few redundant nodes when no title is shown.
else {
let target = this._target = this._list = this.document.createElement("vbox");
target.className = "side-menu-widget-group side-menu-widget-group-list";
}
}
SideMenuGroup.prototype = {
get document() this.ownerView.document,
get window() this.document.defaultView,
get _groupElementsArray() this.ownerView._orderedGroupElementsArray,
get _menuElementsArray() this.ownerView._orderedMenuElementsArray,
/**
* Inserts an item in this group at the specified index.
*
* @param number aIndex
* The position in the container intended for this item.
* @param string | nsIDOMNode aContents
* The string or node displayed in the container.
* @param string aTooltip [optional]
* A tooltip attribute for the displayed item.
* @param boolean aArrowFlag
* True if a horizontal arrow should be shown.
* @return nsIDOMNode
* The element associated with the displayed item.
*/
insertItemAt: function SMG_insertItemAt(aIndex, aContents, aTooltip, aArrowFlag) {
let list = this._list;
let menuArray = this._menuElementsArray;
let item = new SideMenuItem(this, aContents, aTooltip, aArrowFlag);
// Invalidate any notices set on the owner widget.
this.ownerView.removeAttribute("notice");
if (aIndex >= 0) {
list.insertBefore(item._container, list.childNodes[aIndex]);
menuArray.splice(aIndex, 0, item._target);
} else {
list.appendChild(item._container);
menuArray.push(item._target);
}
return item._target;
},
get _orderedGroupElementsArray() this.ownerView._orderedGroupElementsArray,
get _orderedMenuElementsArray() this.ownerView._orderedMenuElementsArray,
/**
* Inserts this group in the parent container at the specified index.
@ -445,7 +466,7 @@ SideMenuGroup.prototype = {
*/
insertSelfAt: function SMG_insertSelfAt(aIndex) {
let ownerList = this.ownerView._list;
let groupsArray = this._groupElementsArray;
let groupsArray = this._orderedGroupElementsArray;
if (aIndex >= 0) {
ownerList.insertBefore(this._target, groupsArray[aIndex]);
@ -464,7 +485,7 @@ SideMenuGroup.prototype = {
*/
findExpectedIndexForSelf: function SMG_findExpectedIndexForSelf() {
let identifier = this.identifier;
let groupsArray = this._groupElementsArray;
let groupsArray = this._orderedGroupElementsArray;
for (let group of groupsArray) {
let name = group.getAttribute("name");
@ -476,6 +497,8 @@ SideMenuGroup.prototype = {
return -1;
},
window: null,
document: null,
ownerView: null,
identifier: "",
_target: null,
@ -497,31 +520,29 @@ SideMenuGroup.prototype = {
* True if a horizontal arrow should be shown.
*/
function SideMenuItem(aGroup, aContents, aTooltip, aArrowFlag) {
this.document = aGroup.document;
this.window = aGroup.window;
this.ownerView = aGroup;
let document = this.document;
// Show a horizontal arrow towards the content.
if (aArrowFlag) {
let target = this._target = document.createElement("vbox");
let container = this._container = this.document.createElement("hbox");
container.className = "side-menu-widget-item";
container.setAttribute("tooltiptext", aTooltip);
let target = this._target = this.document.createElement("vbox");
target.className = "side-menu-widget-item-contents";
let arrow = this._arrow = document.createElement("hbox");
let arrow = this._arrow = this.document.createElement("hbox");
arrow.className = "side-menu-widget-item-arrow";
let container = this._container = document.createElement("hbox");
container.className = "side-menu-widget-item side-menu-widget-item-or-group";
container.setAttribute("tooltiptext", aTooltip);
container.appendChild(target);
container.appendChild(arrow);
}
// Skip a few redundant nodes when no horizontal arrow is shown.
else {
let target = this._target = this._container = document.createElement("hbox");
target.className =
"side-menu-widget-item " +
"side-menu-widget-item-or-group " +
"side-menu-widget-item-contents";
let target = this._target = this._container = this.document.createElement("hbox");
target.className = "side-menu-widget-item side-menu-widget-item-contents";
}
this._target.setAttribute("flex", "1");
@ -529,8 +550,31 @@ function SideMenuItem(aGroup, aContents, aTooltip, aArrowFlag) {
}
SideMenuItem.prototype = {
get document() this.ownerView.document,
get window() this.document.defaultView,
get _orderedGroupElementsArray() this.ownerView._orderedGroupElementsArray,
get _orderedMenuElementsArray() this.ownerView._orderedMenuElementsArray,
/**
* Inserts this item in the parent group at the specified index.
*
* @param number aIndex
* The position in the container intended for this item.
* @return nsIDOMNode
* The element associated with the displayed item.
*/
insertSelfAt: function SMI_insertSelfAt(aIndex) {
let ownerList = this.ownerView._list;
let menuArray = this._orderedMenuElementsArray;
if (aIndex >= 0) {
ownerList.insertBefore(this._container, ownerList.childNodes[aIndex]);
menuArray.splice(aIndex, 0, this._target);
} else {
ownerList.appendChild(this._container);
menuArray.push(this._target);
}
return this._target;
},
/**
* Sets the contents displayed in this item's view.
@ -559,6 +603,8 @@ SideMenuItem.prototype = {
this._target.appendChild(aContents);
},
window: null,
document: null,
ownerView: null,
_target: null,
_container: null,

Просмотреть файл

@ -128,10 +128,9 @@ VariablesView.prototype = {
}
let list = this._list;
let firstChild;
while (firstChild = list.firstChild) {
list.removeChild(firstChild);
while (list.hasChildNodes()) {
list.firstChild.remove();
}
this._store.length = 0;
@ -163,7 +162,7 @@ VariablesView.prototype = {
this._store.length = 0;
this._itemsByElement.clear();
this._emptyTimeout = this.window.setTimeout(function() {
this._emptyTimeout = this.window.setTimeout(() => {
this._emptyTimeout = null;
prevList.removeEventListener("keypress", this._onViewKeyPress, false);
@ -178,7 +177,7 @@ VariablesView.prototype = {
this._appendEmptyNotice();
this._toggleSearchVisibility(false);
}
}.bind(this), aTimeout);
}, aTimeout);
},
/**
@ -198,6 +197,12 @@ VariablesView.prototype = {
*/
lazyAppend: true,
/**
* Specifies if nodes in this view may be expanded lazily.
* @see Scope.prototype.expand
*/
lazyExpand: true,
/**
* Function called each time a variable or property's value is changed via
* user interaction. If null, then value changes are disabled.
@ -403,7 +408,7 @@ VariablesView.prototype = {
if (!this._searchboxContainer) {
return;
}
this._searchboxContainer.parentNode.removeChild(this._searchboxContainer);
this._searchboxContainer.remove();
this._searchboxNode.removeEventListener("input", this._onSearchboxInput, false);
this._searchboxNode.removeEventListener("keypress", this._onSearchboxKeyPress, false);
@ -642,15 +647,17 @@ VariablesView.prototype = {
* Focuses the next scope, variable or property in this view.
* @see VariablesView.prototype._focusChange
*/
focusNextItem: function VV_focusNextItem(aMaintainViewFocusedFlag)
this._focusChange("advanceFocus", aMaintainViewFocusedFlag),
focusNextItem: function VV_focusNextItem(aMaintainViewFocusedFlag) {
this._focusChange("advanceFocus", aMaintainViewFocusedFlag)
},
/**
* Focuses the previous scope, variable or property in this view.
* @see VariablesView.prototype._focusChange
*/
focusPrevItem: function VV_focusPrevItem(aMaintainViewFocusedFlag)
this._focusChange("rewindFocus", aMaintainViewFocusedFlag),
focusPrevItem: function VV_focusPrevItem(aMaintainViewFocusedFlag) {
this._focusChange("rewindFocus", aMaintainViewFocusedFlag)
},
/**
* Focuses the next or previous scope, variable or property in this view.
@ -761,7 +768,6 @@ VariablesView.prototype = {
if (!item._isArrowVisible) {
return;
}
// Expand scopes, variables and properties before advancing focus.
if (!item._isExpanded) {
item.expand();
@ -1051,7 +1057,8 @@ VariablesView.getterOrSetterEvalMacro = function(aItem, aCurrentString) {
VariablesView.getterOrSetterDeleteCallback = function(aItem) {
aItem._disable();
// Make sure the right getter/setter to value override macro is applied to the target object.
// Make sure the right getter/setter to value override macro is applied
// to the target object.
aItem.ownerView.eval(aItem.evaluationMacro(aItem, ""));
return true; // Don't hide the element.
@ -1237,7 +1244,7 @@ Scope.prototype = {
// even if they were already displayed before. In this case, show a throbber
// to suggest that this scope is expanding.
if (!this._isExpanding &&
this._variablesView.lazyAppend &&
this._variablesView.lazyExpand &&
this._store.size > LAZY_APPEND_BATCH) {
this._isExpanding = true;
@ -1971,6 +1978,13 @@ function Variable(aScope, aName, aDescriptor) {
this._activateNameInput = this._activateNameInput.bind(this);
this._activateValueInput = this._activateValueInput.bind(this);
// Treat safe getter descriptors as descriptors with a value.
if ("getterValue" in aDescriptor) {
aDescriptor.value = aDescriptor.getterValue;
delete aDescriptor.get;
delete aDescriptor.set;
}
Scope.call(this, aScope, aName, this._initialDescriptor = aDescriptor);
this.setGrip(aDescriptor.value);
this._symbolicName = aName;
@ -1995,6 +2009,8 @@ ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, {
* - { value: { type: "object", class: "Object" } }
* - { get: { type: "object", class: "Function" },
* set: { type: "undefined" } }
* - { get: { type "object", class: "Function" },
* getterValue: "foo", getterPrototypeLevel: 2 }
* @param boolean aRelaxed
* True if name duplicates should be allowed.
* @return Property
@ -2374,14 +2390,17 @@ ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, {
let configurableLabel = document.createElement("label");
let enumerableLabel = document.createElement("label");
let writableLabel = document.createElement("label");
let safeGetterLabel = document.createElement("label");
configurableLabel.setAttribute("value", "configurable");
enumerableLabel.setAttribute("value", "enumerable");
writableLabel.setAttribute("value", "writable");
safeGetterLabel.setAttribute("value", "native-getter");
tooltip.setAttribute("orient", "horizontal");
tooltip.appendChild(configurableLabel);
tooltip.appendChild(enumerableLabel);
tooltip.appendChild(writableLabel);
tooltip.appendChild(safeGetterLabel);
this._target.appendChild(tooltip);
this._target.setAttribute("tooltip", tooltip.id);
@ -2420,6 +2439,9 @@ ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, {
if (!descriptor.null && !descriptor.writable && !this.ownerView.getter && !this.ownerView.setter) {
this._target.setAttribute("non-writable", "");
}
if (descriptor && "getterValue" in descriptor) {
this._target.setAttribute("safe-getter", "");
}
if (name == "this") {
this._target.setAttribute("self", "");
}

Просмотреть файл

@ -305,43 +305,39 @@ ViewHelpers.Prefs.prototype = {
*
* @param any aAttachment
* Some attached primitive/object.
* @param string aLabel
* The label displayed in the container.
* @param string aValue
* The actual internal value of the item.
* @param string aDescription [optional]
* An optional description of the item.
* @param nsIDOMNode | nsIDOMDocumentFragment | array aContents [optional]
* A prebuilt node, or an array containing the following properties:
* - aLabel: the label displayed in the container
* - aValue: the actual internal value of the item
* - aDescription: an optional description of the item
*/
this.MenuItem = function MenuItem(aAttachment, aLabel, aValue, aDescription) {
this.MenuItem = function MenuItem(aAttachment, aContents = []) {
this.attachment = aAttachment;
// Allow the insertion of prebuilt nodes.
if (aContents instanceof Ci.nsIDOMNode ||
aContents instanceof Ci.nsIDOMDocumentFragment) {
this._prebuiltTarget = aContents;
}
// Delegate the item view creation to a container widget.
else {
let [aLabel, aValue, aDescription] = aContents;
this._label = aLabel + "";
this._value = aValue + "";
this._description = (aDescription || "") + "";
}
};
MenuItem.prototype = {
/**
* Gets the label set for this item.
* @return string
*/
get label() this._label,
/**
* Gets the value set for this item.
* @return string
*/
get value() this._value,
/**
* Gets the description set for this item.
* @return string
*/
get description() this._description,
get target() this._target,
/**
* Immediately appends a child item to this menu item.
*
* @param nsIDOMNode
* @param nsIDOMNode aElement
* An nsIDOMNode representing the child element to append.
* @param object aOptions [optional]
* Additional options or flags supported by this operation:
@ -363,7 +359,7 @@ MenuItem.prototype = {
}
// Entangle the item with the newly inserted child node.
this._entangleItem(item, this.target.appendChild(aElement));
this._entangleItem(item, this._target.appendChild(aElement));
// Return the item associated with the displayed element.
return item;
@ -379,7 +375,7 @@ MenuItem.prototype = {
if (!aItem) {
return;
}
this.target.removeChild(aItem.target);
this._target.removeChild(aItem._target);
this._untangleItem(aItem);
},
@ -387,20 +383,20 @@ MenuItem.prototype = {
* Visually marks this menu item as selected.
*/
markSelected: function MI_markSelected() {
if (!this.target) {
if (!this._target) {
return;
}
this.target.classList.add("selected");
this._target.classList.add("selected");
},
/**
* Visually marks this menu item as deselected.
*/
markDeselected: function MI_markDeselected() {
if (!this.target) {
if (!this._target) {
return;
}
this.target.classList.remove("selected");
this._target.classList.remove("selected");
},
/**
@ -411,7 +407,7 @@ MenuItem.prototype = {
* @param nsIDOMNode aElement [optional]
* A custom element to set the attributes to.
*/
setAttributes: function MI_setAttributes(aAttributes, aElement = this.target) {
setAttributes: function MI_setAttributes(aAttributes, aElement = this._target) {
for (let [name, value] of aAttributes) {
aElement.setAttribute(name, value);
}
@ -431,7 +427,7 @@ MenuItem.prototype = {
}
this._itemsByElement.set(aElement, aItem);
aItem.target = aElement;
aItem._target = aElement;
},
/**
@ -448,8 +444,19 @@ MenuItem.prototype = {
aItem.remove(childItem);
}
this._itemsByElement.delete(aItem.target);
aItem.target = null;
this._unlinkItem(aItem);
aItem._prebuiltTarget = null;
aItem._target = null;
},
/**
* Deletes an item from the its parent's storage maps.
*
* @param MenuItem aItem
* The item to forget.
*/
_unlinkItem: function MC__unlinkItem(aItem) {
this._itemsByElement.delete(aItem._target);
},
/**
@ -469,7 +476,8 @@ MenuItem.prototype = {
_label: "",
_value: "",
_description: "",
target: null,
_prebuiltTarget: null,
_target: null,
finalize: null,
attachment: null
};
@ -537,8 +545,8 @@ MenuContainer.prototype = {
* (items with "undefined" or "null" labels/values). This can, as well, be
* overridden via the "relaxed" flag.
*
* @param nsIDOMNode | object aContents
* An nsIDOMNode, or an array containing the following properties:
* @param nsIDOMNode | nsIDOMDocumentFragment array aContents
* A prebuilt node, or an array containing the following properties:
* - label: the label displayed in the container
* - value: the actual internal value of the item
* - description: an optional description of the item
@ -555,23 +563,17 @@ MenuContainer.prototype = {
* undefined if the item was staged for a later commit.
*/
push: function MC_push(aContents, aOptions = {}) {
if (aContents instanceof Ci.nsIDOMNode ||
aContents instanceof Ci.nsIDOMElement) {
// Allow the insertion of prebuilt nodes.
aOptions.node = aContents;
aContents = ["", "", ""];
}
let [label, value, description] = aContents;
let item = new MenuItem(aOptions.attachment, label, value, description);
let item = new MenuItem(aOptions.attachment, aContents);
// Batch the item to be added later.
if (aOptions.staged) {
// Commit operations will ignore any specified index.
delete aOptions.index;
return void this._stagedItems.push({ item: item, options: aOptions });
}
// Find the target position in this container and insert the item there.
if (!("index" in aOptions)) {
return this._insertItemAt(this._findExpectedIndex(label), item, aOptions);
return this._insertItemAt(this._findExpectedIndex(item), item, aOptions);
}
// Insert the item at the specified index. If negative or out of bounds,
// the item will be simply appended.
@ -580,6 +582,7 @@ MenuContainer.prototype = {
/**
* Flushes all the prepared items into this container.
* Any specified index on the items will be ignored. Everything is appended.
*
* @param object aOptions [optional]
* Additional options or flags supported by this operation:
@ -590,8 +593,7 @@ MenuContainer.prototype = {
// Sort the items before adding them to this container, if preferred.
if (aOptions.sorted) {
stagedItems.sort(function(a, b) a.item._label.toLowerCase() >
b.item._label.toLowerCase());
stagedItems.sort((a, b) => this._sortPredicate(a.item, b.item));
}
// Append the prepared items to this container.
for (let { item, options } of stagedItems) {
@ -630,7 +632,7 @@ MenuContainer.prototype = {
if (!aItem) {
return;
}
this._container.removeChild(aItem.target);
this._container.removeChild(aItem._target);
this._untangleItem(aItem);
},
@ -684,10 +686,97 @@ MenuContainer.prototype = {
*/
toggleContents: function MC_toggleContents(aVisibleFlag) {
for (let [, item] of this._itemsByElement) {
item.target.hidden = !aVisibleFlag;
item._target.hidden = !aVisibleFlag;
}
},
/**
* Sorts all the items in this container based on a predicate.
*
* @param function aPredicate [optional]
* Items are sorted according to the return value of the function, which
* will become the new default sorting predicate in this container.
* If unspecified, all items will be sorted by their label.
*/
sortContents: function MC_sortContents(aPredicate = this._sortPredicate) {
let sortedItems = this.allItems.sort(this._sortPredicate = aPredicate);
for (let i = 0, len = sortedItems.length; i < len; i++) {
this.swapItems(this.getItemAtIndex(i), sortedItems[i]);
}
},
/**
* Visually swaps two items in this container.
*
* @param MenuItem aFirst
* The first menu item to be swapped.
* @param MenuItem aSecond
* The second menu item to be swapped.
*/
swapItems: function MC_swapItems(aFirst, aSecond) {
if (aFirst == aSecond) { // We're just dandy, thank you.
return;
}
let { _prebuiltTarget: firstPrebuiltTarget, target: firstTarget } = aFirst;
let { _prebuiltTarget: secondPrebuiltTarget, target: secondTarget } = aSecond;
// If the two items were constructed with prebuilt nodes as DocumentFragments,
// then those DocumentFragments are now empty and need to be reassembled.
if (firstPrebuiltTarget instanceof Ci.nsIDOMDocumentFragment) {
for (let node of firstTarget.childNodes) {
firstPrebuiltTarget.appendChild(node.cloneNode(true));
}
}
if (secondPrebuiltTarget instanceof Ci.nsIDOMDocumentFragment) {
for (let node of secondTarget.childNodes) {
secondPrebuiltTarget.appendChild(node.cloneNode(true));
}
}
// 1. Get the indices of the two items to swap.
let i = this._indexOfElement(firstTarget);
let j = this._indexOfElement(secondTarget);
// 2. Remeber the selection index, to reselect an item, if necessary.
let selectedTarget = this._container.selectedItem;
let selectedIndex = -1;
if (selectedTarget == firstTarget) {
selectedIndex = i;
} else if (selectedTarget == secondTarget) {
selectedIndex = j;
}
// 3. Silently nuke both items, nobody needs to know about this.
this._container.removeChild(firstTarget);
this._container.removeChild(secondTarget);
this._unlinkItem(aFirst);
this._unlinkItem(aSecond);
// 4. Add the items again, but reversing their indices.
this._insertItemAt.apply(this, i < j ? [i, aSecond] : [j, aFirst]);
this._insertItemAt.apply(this, i < j ? [j, aFirst] : [i, aSecond]);
// 5. Restore the previous selection, if necessary.
if (selectedIndex == i) {
this._container.selectedItem = aFirst._target;
} else if (selectedIndex == j) {
this._container.selectedItem = aSecond._target;
}
},
/**
* Visually swaps two items in this container at specific indices.
*
* @param number aFirst
* The index of the first menu item to be swapped.
* @param number aSecond
* The index of the second menu item to be swapped.
*/
swapItemsAtIndices: function MC_swapItemsAtIndices(aFirst, aSecond) {
this.swapItems(this.getItemAtIndex(aFirst), this.getItemAtIndex(aSecond));
},
/**
* Checks whether an item with the specified label is among the elements
* shown in this container.
@ -776,14 +865,15 @@ MenuContainer.prototype = {
*/
set selectedItem(aItem) {
// A falsy item is allowed to invalidate the current selection.
let targetNode = aItem ? aItem.target : null;
let targetElement = aItem ? aItem._target : null;
// Prevent selecting the same item again, so return early.
if (this._container.selectedItem == targetNode) {
if (this._container.selectedItem == targetElement) {
return;
}
this._container.selectedItem = targetNode;
ViewHelpers.dispatchEvent(targetNode, "select", aItem);
this._container.selectedItem = targetElement;
ViewHelpers.dispatchEvent(targetElement, "select", aItem);
},
/**
@ -877,7 +967,7 @@ MenuContainer.prototype = {
* The index of the matched item, or -1 if nothing is found.
*/
indexOfItem: function MC_indexOfItem(aItem) {
return this._indexOfElement(aItem.target);
return this._indexOfElement(aItem._target);
},
/**
@ -931,7 +1021,20 @@ MenuContainer.prototype = {
get itemCount() this._itemsByElement.size,
/**
* Returns a list of all the visible (non-hidden) items in this container.
* Returns a list of all items in this container, in the displayed order.
* @return array
*/
get allItems() {
let items = [];
for (let i = 0; i < this.itemCount; i++) {
items.push(this.getItemAtIndex(i));
}
return items;
},
/**
* Returns a list of all the visible (non-hidden) items in this container,
* in no particular order.
* @return array
*/
get visibleItems() {
@ -987,25 +1090,26 @@ MenuContainer.prototype = {
* True if the element is eligible, false otherwise.
*/
isEligible: function MC_isEligible(aItem) {
return this.isUnique(aItem) &&
return aItem._prebuiltTarget || (this.isUnique(aItem) &&
aItem._label != "undefined" && aItem._label != "null" &&
aItem._value != "undefined" && aItem._value != "null";
aItem._value != "undefined" && aItem._value != "null");
},
/**
* Finds the expected item index in this container based on its label.
* Finds the expected item index in this container based on the default
* sort predicate.
*
* @param string aLabel
* The label used to identify the element.
* @param MenuItem aItem
* The item to get the expected index for.
* @return number
* The expected item index.
*/
_findExpectedIndex: function MC__findExpectedIndex(aLabel) {
_findExpectedIndex: function MC__findExpectedIndex(aItem) {
let container = this._container;
let itemCount = this.itemCount;
for (let i = 0; i < itemCount; i++) {
if (this.getItemAtIndex(i)._label > aLabel) {
if (this._sortPredicate(this.getItemAtIndex(i), aItem) > 0) {
return i;
}
}
@ -1028,7 +1132,7 @@ MenuContainer.prototype = {
* @return MenuItem
* The item associated with the displayed element, null if rejected.
*/
_insertItemAt: function MC__insertItemAt(aIndex, aItem, aOptions) {
_insertItemAt: function MC__insertItemAt(aIndex, aItem, aOptions = {}) {
// Relaxed nodes may be appended without verifying their eligibility.
if (!aOptions.relaxed && !this.isEligible(aItem)) {
return null;
@ -1036,14 +1140,14 @@ MenuContainer.prototype = {
// Entangle the item with the newly inserted node.
this._entangleItem(aItem, this._container.insertItemAt(aIndex,
aOptions.node || aItem._label,
aItem._prebuiltTarget || aItem._label, // Allow the insertion of prebuilt nodes.
aItem._value,
aItem._description,
aOptions.attachment));
aItem.attachment));
// Handle any additional options after entangling the item.
if (aOptions.attributes) {
aItem.setAttributes(aOptions.attributes, aItem.target);
aItem.setAttributes(aOptions.attributes, aItem._target);
}
if (aOptions.finalize) {
aItem.finalize = aOptions.finalize;
@ -1065,7 +1169,7 @@ MenuContainer.prototype = {
this._itemsByLabel.set(aItem._label, aItem);
this._itemsByValue.set(aItem._value, aItem);
this._itemsByElement.set(aElement, aItem);
aItem.target = aElement;
aItem._target = aElement;
},
/**
@ -1082,10 +1186,38 @@ MenuContainer.prototype = {
aItem.remove(childItem);
}
this._unlinkItem(aItem);
aItem._prebuiltTarget = null;
aItem._target = null;
},
/**
* Deletes an item from the its parent's storage maps.
*
* @param MenuItem aItem
* The item to forget.
*/
_unlinkItem: function MI__unlinkItem(aItem) {
this._itemsByLabel.delete(aItem._label);
this._itemsByValue.delete(aItem._value);
this._itemsByElement.delete(aItem.target);
aItem.target = null;
this._itemsByElement.delete(aItem._target);
},
/**
* The predicate used when sorting items. By default, items in this view
* are sorted by their label.
*
* @param MenuItem aFirst
* The first menu item used in the comparison.
* @param MenuItem aSecond
* The second menu item used in the comparison.
* @return number
* -1 to sort aFirst to a lower index than aSecond
* 0 to leave aFirst and aSecond unchanged with respect to each other
* 1 to sort aSecond to a lower index than aFirst
*/
_sortPredicate: function MC__sortPredicate(aFirst, aSecond) {
return +(aFirst._label.toLowerCase() > aSecond._label.toLowerCase());
},
_container: null,

Просмотреть файл

@ -16,10 +16,6 @@
overflow-y: auto;
}
.side-menu-widget-group[name=""] > .side-menu-widget-group-title {
display: none;
}
/* VariablesView */
.variables-view-container {

Просмотреть файл

@ -7,7 +7,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
this.EXPORTED_SYMBOLS = [ ];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/devtools/gcli.jsm");
Cu.import("resource://gre/modules/devtools/gcli.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
"resource:///modules/devtools/gDevTools.jsm");

Просмотреть файл

@ -23,7 +23,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "Promise",
* the target's document. It wraps remote debugging protocol comunications.
*
* It emits these events:
* 'stylesheet-added': A stylesheet has been added to the debuggee's document
* 'document-load': debuggee's document is loaded, style sheets are argument
* 'stylesheets-cleared': The debuggee's stylesheets have been reset (e.g. the
* page navigated)
*
@ -37,12 +37,12 @@ let StyleEditorDebuggee = function(target) {
this.clear = this.clear.bind(this);
this._onNewDocument = this._onNewDocument.bind(this);
this._onStyleSheetsAdded = this._onStyleSheetsAdded.bind(this);
this._onDocumentLoad = this._onDocumentLoad.bind(this);
this._target = target;
this._actor = this.target.form.styleEditorActor;
this.client.addListener("styleSheetsAdded", this._onStyleSheetsAdded);
this.client.addListener("documentLoad", this._onDocumentLoad);
this._target.on("navigate", this._onNewDocument);
this._onNewDocument();
@ -128,18 +128,21 @@ StyleEditorDebuggee.prototype = {
},
/**
* Handle stylesheet-added event from the target
* Handler for document load, forward event with
* all the stylesheets available on load.
*
* @param {string} type
* Type of event
* Event type
* @param {object} request
* Event details
* Object with 'styleSheets' array of actor forms
*/
_onStyleSheetsAdded: function(type, request) {
_onDocumentLoad: function(type, request) {
let sheets = [];
for (let form of request.styleSheets) {
let sheet = this._addStyleSheet(form);
this.emit("stylesheet-added", sheet);
sheets.push(sheet);
}
this.emit("document-load", sheets);
},
/**
@ -191,7 +194,6 @@ StyleEditorDebuggee.prototype = {
destroy: function() {
this.clear();
this._target.off("will-navigate", this.clear);
this._target.off("navigate", this._onNewDocument);
}
}

Просмотреть файл

@ -110,7 +110,6 @@ StyleEditorPanel.prototype = {
if (!this._destroyed) {
this._destroyed = true;
this._target.off("will-navigate", this.beforeNavigate);
this._target.off("close", this.destroy);
this._target = null;
this._toolbox = null;

Просмотреть файл

@ -49,12 +49,12 @@ function StyleEditorUI(debuggee, panelDoc) {
this.editors = [];
this.selectedStyleSheetIndex = -1;
this._onStyleSheetAdded = this._onStyleSheetAdded.bind(this);
this._onStyleSheetCreated = this._onStyleSheetCreated.bind(this);
this._onStyleSheetsCleared = this._onStyleSheetsCleared.bind(this);
this._onDocumentLoad = this._onDocumentLoad.bind(this);
this._onError = this._onError.bind(this);
debuggee.on("stylesheet-added", this._onStyleSheetAdded);
debuggee.on("document-load", this._onDocumentLoad);
debuggee.on("stylesheets-cleared", this._onStyleSheetsCleared);
this.createUI();
@ -156,17 +156,21 @@ StyleEditorUI.prototype = {
},
/**
* Handler for debuggee's 'stylesheet-added' event. Add an editor.
* Handler for debuggee's 'document-load' event. Add editors
* for all style sheets in the document
*
* @param {string} event
* Event name
* @param {StyleSheet} styleSheet
* StyleSheet object for new sheet
*/
_onStyleSheetAdded: function(event, styleSheet) {
_onDocumentLoad: function(event, styleSheets) {
for (let sheet of styleSheets) {
this._addStyleSheetEditor(sheet);
}
// this might be the first stylesheet, so remove loading indicator
this._root.classList.remove("loading");
this._addStyleSheetEditor(styleSheet);
this.emit("document-load");
},
/**
@ -421,7 +425,7 @@ StyleEditorUI.prototype = {
destroy: function() {
this._clearStyleSheetEditors();
this._debuggee.off("stylesheet-added", this._onStyleSheetAdded);
this._debuggee.off("document-load", this._onDocumentLoad);
this._debuggee.off("stylesheets-cleared", this._onStyleSheetsCleared);
}
}

Просмотреть файл

@ -27,6 +27,7 @@ _BROWSER_TEST_FILES = \
browser_styleeditor_sv_resize.js \
browser_styleeditor_bug_740541_iframes.js \
browser_styleeditor_bug_851132_middle_click.js \
browser_styleeditor_nostyle.js \
head.js \
helpers.js \
four.html \
@ -39,6 +40,7 @@ _BROWSER_TEST_FILES = \
media.html \
media-small.css \
minified.html \
nostyle.html \
resources_inpage.jsi \
resources_inpage1.css \
resources_inpage2.css \

Просмотреть файл

@ -0,0 +1,41 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const TESTCASE_URI = TEST_BASE + "nostyle.html";
function test()
{
waitForExplicitFinish();
// launch Style Editor right when the tab is created (before load)
// this checks that the Style Editor still launches correctly when it is opened
// *while* the page is still loading. The Style Editor should not signal that
// it is loaded until the accompanying content page is loaded.
addTabAndOpenStyleEditor(function(panel) {
panel.UI.once("document-load", testDocumentLoad);
content.location = TESTCASE_URI;
});
}
function testDocumentLoad(event)
{
let root = gPanelWindow.document.querySelector(".splitview-root");
ok(!root.classList.contains("loading"),
"style editor root element does not have 'loading' class name anymore");
ok(root.querySelector(".empty.placeholder"), "showing 'no style' indicator");
let button = gPanelWindow.document.querySelector(".style-editor-newButton");
ok(!button.hasAttribute("disabled"),
"new style sheet button is enabled");
button = gPanelWindow.document.querySelector(".style-editor-importButton");
ok(!button.hasAttribute("disabled"),
"import button is enabled");
finish();
}

Просмотреть файл

@ -21,11 +21,10 @@ this.EXPORTED_SYMBOLS = [ 'helpers' ];
var helpers = {};
this.helpers = helpers;
let require = (Cu.import("resource://gre/modules/devtools/Require.jsm", {})).require;
Components.utils.import("resource:///modules/devtools/gcli.jsm", {});
Components.utils.import("resource://gre/modules/devtools/gcli.jsm", {});
let console = (Cu.import("resource://gre/modules/devtools/Console.jsm", {})).console;
let devtools = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools;
let TargetFactory = devtools.TargetFactory;
let TargetFactory = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools.TargetFactory;
let Promise = (Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {})).Promise;
let assert = { ok: ok, is: is, log: info };
@ -158,6 +157,7 @@ helpers.runTests = function(options, tests) {
});
var recover = function(error) {
ok(false, error);
console.error(error);
};
@ -341,7 +341,7 @@ helpers._createDebugCheck = function(options) {
output += ' current: \'' + helpers._actual.current(options) + '\',\n';
output += ' status: \'' + helpers._actual.status(options) + '\',\n';
output += ' options: ' + outputArray(helpers._actual.options(options)) + ',\n';
output += ' error: \'' + helpers._actual.message(options) + '\',\n';
output += ' message: \'' + helpers._actual.message(options) + '\',\n';
output += ' predictions: ' + outputArray(predictions) + ',\n';
output += ' unassigned: ' + outputArray(requisition._unassigned) + ',\n';
output += ' outputState: \'' + helpers._actual.outputState(options) + '\',\n';
@ -378,6 +378,8 @@ helpers._createDebugCheck = function(options) {
output += ' exec: {\n';
output += ' output: \'\',\n';
output += ' completed: true,\n';
output += ' type: \'string\',\n';
output += ' error: false\n';
output += ' }\n';
output += ' }\n';
output += ']);';
@ -702,7 +704,7 @@ helpers._check = function(options, name, checks) {
*/
helpers._exec = function(options, name, expected) {
if (expected == null) {
return Promise.resolve();
return Promise.resolve({});
}
var output = options.display.requisition.exec({ hidden: true });
@ -715,20 +717,32 @@ helpers._exec = function(options, name, expected) {
if (!options.window.document.createElement) {
assert.log('skipping output tests (missing doc.createElement) for ' + name);
return Promise.resolve();
return Promise.resolve({ output: output });
}
if (!('output' in expected)) {
return Promise.resolve();
return Promise.resolve({ output: output });
}
var deferred = Promise.defer();
var checkOutput = function() {
var div = options.window.document.createElement('div');
var nodePromise = converters.convert(output.data, output.type, 'dom',
options.display.requisition.context);
nodePromise.then(function(node) {
var conversionContext = options.display.requisition.conversionContext;
if ('type' in expected) {
assert.is(output.type,
expected.type,
'output.type for: ' + name);
}
if ('error' in expected) {
assert.is(output.error,
expected.error,
'output.error for: ' + name);
}
var convertPromise = converters.convert(output.data, output.type, 'dom',
conversionContext);
return convertPromise.then(function(node) {
div.appendChild(node);
var actualOutput = div.textContent.trim();
@ -757,24 +771,11 @@ helpers._exec = function(options, name, expected) {
doTest(expected.output, actualOutput);
}
deferred.resolve(actualOutput);
return { output: output, text: actualOutput };
});
};
if (output.completed !== false) {
checkOutput();
}
else {
var changed = function() {
if (output.completed !== false) {
checkOutput();
output.onChange.remove(changed);
}
};
output.onChange.add(changed);
}
return deferred.promise;
return output.promise.then(checkOutput, checkOutput);
};
/**
@ -789,15 +790,15 @@ helpers._setup = function(options, name, action) {
return Promise.resolve(action());
}
return Promise.reject('setup must be a string or a function');
return Promise.reject('\'setup\' property must be a string or a function. Is ' + action);
};
/**
* Helper to shutdown the test
*/
helpers._post = function(name, action, output) {
helpers._post = function(name, action, data) {
if (typeof action === 'function') {
return Promise.resolve(action(output));
return Promise.resolve(action(data.output, data.text));
}
return Promise.resolve(action);
};
@ -943,6 +944,8 @@ helpers.audit = function(options, audits) {
if (typeof chunkLen !== 'number') {
chunkLen = 1;
}
if (assert.currentTest) {
var responseTime = (new Date().getTime() - start) / chunkLen;
totalResponseTime += responseTime;
if (responseTime > maxResponseTime) {
@ -950,12 +953,13 @@ helpers.audit = function(options, audits) {
maxResponseCulprit = assert.currentTest + '/' + name;
}
averageOver++;
}
var checkDone = helpers._check(options, name, audit.check);
return checkDone.then(function() {
var execDone = helpers._exec(options, name, audit.exec);
return execDone.then(function(output) {
return helpers._post(name, audit.post, output).then(function() {
return execDone.then(function(data) {
return helpers._post(name, audit.post, data).then(function() {
if (assert.testLogging) {
log('- END \'' + name + '\' in ' + assert.currentTest);
}
@ -963,9 +967,8 @@ helpers.audit = function(options, audits) {
});
});
});
}).then(null, function(ex) {
console.error(ex.stack);
throw(ex);
}).then(function() {
return options.display.inputter.setInput('');
});
};

Просмотреть файл

@ -0,0 +1,5 @@
<html>
<div>
Page with no stylesheets
</div>
</html>

Просмотреть файл

@ -12,7 +12,7 @@ let {CssLogic} = require("devtools/styleinspector/css-logic");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PluralForm.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/devtools/Templater.jsm");
Cu.import("resource://gre/modules/devtools/Templater.jsm");
Cu.import("resource:///modules/devtools/gDevTools.jsm");

Просмотреть файл

@ -941,8 +941,8 @@ CssRuleView.prototype = {
*/
nodeChanged: function CssRuleView_nodeChanged()
{
// Ignore refreshes during editing.
if (this.isEditing) {
// Ignore refreshes during editing or when no element is selected.
if (this.isEditing || !this._elementStyle) {
return;
}
@ -1324,6 +1324,7 @@ function TextPropertyEditor(aRuleEditor, aProperty)
this.doc = aRuleEditor.doc;
this.prop = aProperty;
this.prop.editor = this;
this.browserWindow = this.doc.defaultView.top;
let sheet = this.prop.rule.sheet;
let href = sheet ? CssLogic.href(sheet) : null;
@ -1520,9 +1521,14 @@ TextPropertyEditor.prototype = {
href: this.resolveURI(resourceURI)
});
a.addEventListener("click", function(aEvent) {
a.addEventListener("click", (aEvent) => {
// Clicks within the link shouldn't trigger editing.
aEvent.stopPropagation();
aEvent.preventDefault();
this.browserWindow.openUILinkIn(aEvent.target.href, "tab");
}, false);
appendText(this.valueSpan, val.split(resourceURI)[1]);

Просмотреть файл

@ -6,7 +6,7 @@
this.EXPORTED_SYMBOLS = [ ];
Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
Components.utils.import("resource:///modules/devtools/gcli.jsm");
Components.utils.import("resource://gre/modules/devtools/gcli.jsm");
Components.utils.import("resource:///modules/devtools/gDevTools.jsm");
// Fetch TiltManager using the current loader, but don't save a

Просмотреть файл

@ -11,7 +11,6 @@ include $(DEPTH)/config/autoconf.mk
EXTRA_JS_MODULES = \
HUDService.jsm \
PropertyPanel.jsm \
NetworkPanel.jsm \
WebConsolePanel.jsm \
$(NULL)

Просмотреть файл

@ -1,501 +0,0 @@
/* -*- 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.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
this.EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView"];
///////////////////////////////////////////////////////////////////////////
//// PropertyTreeView.
/**
* This is an implementation of the nsITreeView interface. For comments on the
* interface properties, see the documentation:
* https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsITreeView
*/
this.PropertyTreeView = function() {
this._rows = [];
this._objectActors = [];
};
PropertyTreeView.prototype = {
/**
* Stores the visible rows of the tree.
* @private
*/
_rows: null,
/**
* Stores the nsITreeBoxObject for this tree.
* @private
*/
_treeBox: null,
/**
* Track known object actor IDs. We clean these when the panel is
* destroyed/cleaned up.
*
* @private
* @type array
*/
_objectActors: null,
/**
* Map fake object actors to their IDs. This is used when we inspect local
* objects.
* @private
* @type Object
*/
_localObjectActors: null,
_releaseObject: null,
_objectPropertiesProvider: null,
/**
* Use this setter to update the content of the tree.
*
* @param object aData
* A meta object that holds information about the object you want to
* display in the property panel. Object properties:
* - object:
* This is the raw object you want to display. You can only provide
* this object if you want the property panel to work in sync mode.
* - objectProperties:
* An array that holds information on the remote object being
* inspected. Each element in this array describes each property in the
* remote object. See WebConsoleUtils.inspectObject() for details.
* - objectPropertiesProvider:
* A function that is invoked when a new object is needed. This is
* called when the user tries to expand an inspectable property. The
* callback must take four arguments:
* - actorID:
* The object actor ID from which we request the properties.
* - callback:
* The callback function to be invoked when the remote object is
* received. This function takes one argument: the array of
* descriptors for each property in the object represented by the
* actor.
* - releaseObject:
* Function to invoke when an object actor should be released. The
* function must take one argument: the object actor ID.
*/
set data(aData) {
let oldLen = this._rows.length;
this.cleanup();
if (!aData) {
return;
}
if (aData.objectPropertiesProvider) {
this._objectPropertiesProvider = aData.objectPropertiesProvider;
this._releaseObject = aData.releaseObject;
this._propertiesToRows(aData.objectProperties, 0);
this._rows = aData.objectProperties;
}
else if (aData.object) {
this._localObjectActors = Object.create(null);
this._rows = this._inspectObject(aData.object);
}
else {
throw new Error("First argument must have an objectActor or an " +
"object property!");
}
if (this._treeBox) {
this._treeBox.beginUpdateBatch();
if (oldLen) {
this._treeBox.rowCountChanged(0, -oldLen);
}
this._treeBox.rowCountChanged(0, this._rows.length);
this._treeBox.endUpdateBatch();
}
},
/**
* Update a remote object so it can be used with the tree view. This method
* adds properties to each array element.
*
* @private
* @param array aObject
* The remote object you want prepared for use with the tree view.
* @param number aLevel
* The level you want to give to each property in the remote object.
*/
_propertiesToRows: function PTV__propertiesToRows(aObject, aLevel)
{
aObject.forEach(function(aItem) {
aItem._level = aLevel;
aItem._open = false;
aItem._children = null;
if (this._releaseObject) {
["value", "get", "set"].forEach(function(aProp) {
let val = aItem[aProp];
if (val && val.actor) {
this._objectActors.push(val.actor);
if (typeof val.displayString == "object" &&
val.displayString.type == "longString") {
this._objectActors.push(val.displayString.actor);
}
}
}, this);
}
}, this);
},
/**
* Inspect a local object.
*
* @private
* @param object aObject
* The object you want to inspect.
* @return array
* The array of properties, each being described in a way that is
* usable by the tree view.
*/
_inspectObject: function PTV__inspectObject(aObject)
{
this._objectPropertiesProvider = this._localPropertiesProvider.bind(this);
let children =
WebConsoleUtils.inspectObject(aObject, this._localObjectGrip.bind(this));
this._propertiesToRows(children, 0);
return children;
},
/**
* Make a local fake object actor for the given object.
*
* @private
* @param object aObject
* The object to make an actor for.
* @return object
* The fake actor grip that represents the given object.
*/
_localObjectGrip: function PTV__localObjectGrip(aObject)
{
let grip = WebConsoleUtils.getObjectGrip(aObject);
grip.actor = "obj" + gSequenceId();
this._localObjectActors[grip.actor] = aObject;
return grip;
},
/**
* A properties provider for when the user inspects local objects (not remote
* ones).
*
* @private
* @param string aActor
* The ID of the object actor you want.
* @param function aCallback
* The function you want to receive the list of properties.
*/
_localPropertiesProvider:
function PTV__localPropertiesProvider(aActor, aCallback)
{
let object = this._localObjectActors[aActor];
let properties =
WebConsoleUtils.inspectObject(object, this._localObjectGrip.bind(this));
aCallback(properties);
},
/** nsITreeView interface implementation **/
selection: null,
get rowCount() { return this._rows.length; },
setTree: function(treeBox) { this._treeBox = treeBox; },
getCellText: function PTV_getCellText(idx, column)
{
let row = this._rows[idx];
return row.name + ": " + WebConsoleUtils.getPropertyPanelValue(row);
},
getLevel: function(idx) {
return this._rows[idx]._level;
},
isContainer: function(idx) {
return typeof this._rows[idx].value == "object" && this._rows[idx].value &&
this._rows[idx].value.inspectable;
},
isContainerOpen: function(idx) {
return this._rows[idx]._open;
},
isContainerEmpty: function(idx) { return false; },
isSeparator: function(idx) { return false; },
isSorted: function() { return false; },
isEditable: function(idx, column) { return false; },
isSelectable: function(row, col) { return true; },
getParentIndex: function(idx)
{
if (this.getLevel(idx) == 0) {
return -1;
}
for (var t = idx - 1; t >= 0; t--) {
if (this.isContainer(t)) {
return t;
}
}
return -1;
},
hasNextSibling: function(idx, after)
{
let thisLevel = this.getLevel(idx);
return this._rows.slice(after + 1).some(function (r) r._level == thisLevel);
},
toggleOpenState: function(idx)
{
let item = this._rows[idx];
if (!this.isContainer(idx)) {
return;
}
if (item._open) {
this._treeBox.beginUpdateBatch();
item._open = false;
var thisLevel = item._level;
var t = idx + 1, deleteCount = 0;
while (t < this._rows.length && this.getLevel(t++) > thisLevel) {
deleteCount++;
}
if (deleteCount) {
this._rows.splice(idx + 1, deleteCount);
this._treeBox.rowCountChanged(idx + 1, -deleteCount);
}
this._treeBox.invalidateRow(idx);
this._treeBox.endUpdateBatch();
}
else {
let levelUpdate = true;
let callback = function _onRemoteResponse(aProperties) {
this._treeBox.beginUpdateBatch();
if (levelUpdate) {
this._propertiesToRows(aProperties, item._level + 1);
item._children = aProperties;
}
this._rows.splice.apply(this._rows, [idx + 1, 0].concat(item._children));
this._treeBox.rowCountChanged(idx + 1, item._children.length);
this._treeBox.invalidateRow(idx);
this._treeBox.endUpdateBatch();
item._open = true;
}.bind(this);
if (!item._children) {
this._objectPropertiesProvider(item.value.actor, callback);
}
else {
levelUpdate = false;
callback(item._children);
}
}
},
getImageSrc: function(idx, column) { },
getProgressMode : function(idx,column) { },
getCellValue: function(idx, column) { },
cycleHeader: function(col, elem) { },
selectionChanged: function() { },
cycleCell: function(idx, column) { },
performAction: function(action) { },
performActionOnCell: function(action, index, column) { },
performActionOnRow: function(action, row) { },
getRowProperties: function(idx) { return ""; },
getCellProperties: function(idx, column) { return ""; },
getColumnProperties: function(column, element) { return ""; },
setCellValue: function(row, col, value) { },
setCellText: function(row, col, value) { },
drop: function(index, orientation, dataTransfer) { },
canDrop: function(index, orientation, dataTransfer) { return false; },
/**
* Cleanup the property tree view.
*/
cleanup: function PTV_cleanup()
{
if (this._releaseObject) {
this._objectActors.forEach(this._releaseObject);
delete this._objectPropertiesProvider;
delete this._releaseObject;
}
if (this._localObjectActors) {
delete this._localObjectActors;
delete this._objectPropertiesProvider;
}
this._rows = [];
this._objectActors = [];
},
};
///////////////////////////////////////////////////////////////////////////
//// Helper for creating the panel.
/**
* 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);
for (var attr in aAttributes) {
node.setAttribute(attr, aAttributes[attr]);
}
return node;
}
/**
* Creates a new DOMNode and appends it to aParent.
*
* @param nsIDOMDocument aDocument
* Document to create the new DOMNode.
* @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 appendChild(aDocument, aParent, aTag, aAttributes)
{
let node = createElement(aDocument, aTag, aAttributes);
aParent.appendChild(node);
return node;
}
///////////////////////////////////////////////////////////////////////////
//// PropertyPanel
/**
* Creates a new PropertyPanel.
*
* @see PropertyTreeView
* @param nsIDOMNode aParent
* Parent node to append the created panel to.
* @param string aTitle
* Title for the panel.
* @param string aObject
* Object to display in the tree. For details about this object please
* see the PropertyTreeView constructor in this file.
* @param array of objects aButtons
* Array with buttons to display at the bottom of the panel.
*/
this.PropertyPanel = function PropertyPanel(aParent, aTitle, aObject, aButtons)
{
let document = aParent.ownerDocument;
// Create the underlying panel
this.panel = createElement(document, "panel", {
label: aTitle,
titlebar: "normal",
noautofocus: "true",
noautohide: "true",
close: "true",
});
// Create the tree.
let tree = this.tree = createElement(document, "tree", {
flex: 1,
hidecolumnpicker: "true"
});
let treecols = document.createElement("treecols");
appendChild(document, treecols, "treecol", {
primary: "true",
flex: 1,
hideheader: "true",
ignoreincolumnpicker: "true"
});
tree.appendChild(treecols);
tree.appendChild(document.createElement("treechildren"));
this.panel.appendChild(tree);
// Create the footer.
let footer = createElement(document, "hbox", { align: "end" });
appendChild(document, footer, "spacer", { flex: 1 });
// The footer can have butttons.
let self = this;
if (aButtons) {
aButtons.forEach(function(button) {
let buttonNode = appendChild(document, footer, "button", {
label: button.label,
accesskey: button.accesskey || "",
class: button.class || "",
});
buttonNode.addEventListener("command", button.oncommand, false);
});
}
appendChild(document, footer, "resizer", { dir: "bottomend" });
this.panel.appendChild(footer);
aParent.appendChild(this.panel);
// Create the treeView object.
this.treeView = new PropertyTreeView();
this.treeView.data = aObject;
// Set the treeView object on the tree view. This has to be done *after* the
// panel is shown. This is because the tree binding must be attached first.
this.panel.addEventListener("popupshown", function onPopupShow()
{
self.panel.removeEventListener("popupshown", onPopupShow, false);
self.tree.view = self.treeView;
}, false);
this.panel.addEventListener("popuphidden", function onPopupHide()
{
self.panel.removeEventListener("popuphidden", onPopupHide, false);
self.destroy();
}, false);
}
/**
* Destroy the PropertyPanel. This closes the panel and removes it from the
* browser DOM.
*/
PropertyPanel.prototype.destroy = function PP_destroy()
{
this.treeView.data = null;
this.panel.parentNode.removeChild(this.panel);
this.treeView = null;
this.panel = null;
this.tree = null;
}
function gSequenceId()
{
return gSequenceId.n++;
}
gSequenceId.n = 0;

Просмотреть файл

@ -125,6 +125,10 @@ MOCHITEST_BROWSER_FILES = \
browser_webconsole_bug_837351_securityerrors.js \
browser_bug_865871_variables_view_close_on_esc_key.js \
browser_bug_865288_repeat_different_objects.js \
browser_jsterm_inspect.js \
browser_bug_869003_inspect_cross_domain_object.js \
browser_bug_862916_console_dir_and_filter_off.js \
browser_console_native_getters.js \
head.js \
$(NULL)
@ -228,6 +232,8 @@ MOCHITEST_BROWSER_FILES += \
test-eval-in-stackframe.html \
test-bug-859170-longstring-hang.html \
test-bug-837351-security-errors.html \
test-bug-869003-top-window.html \
test-bug-869003-iframe.html \
$(NULL)
include $(topsrcdir)/config/rules.mk

Просмотреть файл

@ -0,0 +1,34 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Check that the output for console.dir() works even if Logging filter is off.
const TEST_URI = "data:text/html;charset=utf8,<p>test for bug 862916";
function test()
{
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, consoleOpened);
}, true);
}
function consoleOpened(hud)
{
ok(hud, "web console opened");
hud.setFilterState("log", false);
registerCleanupFunction(() => hud.setFilterState("log", true));
content.wrappedJSObject.fooBarz = "bug862916";
hud.jsterm.execute("console.dir(window)");
hud.jsterm.once("variablesview-fetched", (aEvent, aVar) => {
ok(aVar, "variables view object");
findVariableViewProperties(aVar, [
{ name: "fooBarz", value: "bug862916" },
], { webconsole: hud }).then(finishTest);
});
}

Просмотреть файл

@ -0,0 +1,89 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Check that users can inspect objects logged from cross-domain iframes -
// bug 869003.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-869003-top-window.html";
let gWebConsole, gJSTerm, gVariablesView;
function test()
{
addTab("data:text/html;charset=utf8,<p>hello");
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, consoleOpened);
}, true);
}
function consoleOpened(hud)
{
gWebConsole = hud;
gJSTerm = hud.jsterm;
content.location = TEST_URI;
waitForMessages({
webconsole: hud,
messages: [{
name: "console.log message",
text: "foobar",
category: CATEGORY_WEBDEV,
severity: SEVERITY_LOG,
objects: true,
}],
}).then(onConsoleMessage);
}
function onConsoleMessage(aResults)
{
let clickable = aResults[0].clickableElements[0];
ok(clickable, "clickable object found");
isnot(clickable.textContent.indexOf("[object Object]"), -1,
"message text check");
gJSTerm.once("variablesview-fetched", onObjFetch);
EventUtils.synthesizeMouse(clickable, 2, 2, {}, gWebConsole.iframeWindow)
}
function onObjFetch(aEvent, aVar)
{
gVariablesView = aVar._variablesView;
ok(gVariablesView, "variables view object");
findVariableViewProperties(aVar, [
{ name: "hello", value: "world!" },
{ name: "bug", value: 869003 },
], { webconsole: gWebConsole }).then(onPropFound);
}
function onPropFound(aResults)
{
let prop = aResults[0].matchedProp;
ok(prop, "matched the |hello| property in the variables view");
// Check that property value updates work.
updateVariablesViewProperty({
property: prop,
field: "value",
string: "'omgtest'",
webconsole: gWebConsole,
callback: onFetchAfterUpdate,
});
}
function onFetchAfterUpdate(aEvent, aVar)
{
info("onFetchAfterUpdate");
findVariableViewProperties(aVar, [
{ name: "hello", value: "omgtest" },
{ name: "bug", value: 869003 },
], { webconsole: gWebConsole }).then(() => {
gWebConsole = gJSTerm = gVariablesView = null;
finishTest();
});
}

Просмотреть файл

@ -0,0 +1,121 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Check that native getters and setters for DOM elements work as expected in
// variables view - bug 870220.
const TEST_URI = "data:text/html;charset=utf8,<title>bug870220</title>\n" +
"<p>hello world\n<p>native getters!";
let gWebConsole, gJSTerm, gVariablesView;
function test()
{
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, consoleOpened);
}, true);
}
function consoleOpened(hud)
{
gWebConsole = hud;
gJSTerm = hud.jsterm;
gJSTerm.execute("document");
waitForMessages({
webconsole: hud,
messages: [{
text: "[object HTMLDocument]",
category: CATEGORY_OUTPUT,
objects: true,
}],
}).then(onEvalResult);
}
function onEvalResult(aResults)
{
let clickable = aResults[0].clickableElements[0];
ok(clickable, "clickable object found");
gJSTerm.once("variablesview-fetched", onDocumentFetch);
EventUtils.synthesizeMouse(clickable, 2, 2, {}, gWebConsole.iframeWindow)
}
function onDocumentFetch(aEvent, aVar)
{
gVariablesView = aVar._variablesView;
ok(gVariablesView, "variables view object");
findVariableViewProperties(aVar, [
{ name: "title", value: "bug870220" },
{ name: "bgColor" },
], { webconsole: gWebConsole }).then(onDocumentPropsFound);
}
function onDocumentPropsFound(aResults)
{
let prop = aResults[1].matchedProp;
ok(prop, "matched the |bgColor| property in the variables view");
// Check that property value updates work.
updateVariablesViewProperty({
property: prop,
field: "value",
string: "'red'",
webconsole: gWebConsole,
callback: onFetchAfterBackgroundUpdate,
});
}
function onFetchAfterBackgroundUpdate(aEvent, aVar)
{
info("onFetchAfterBackgroundUpdate");
is(content.document.bgColor, "red", "document background color changed");
findVariableViewProperties(aVar, [
{ name: "bgColor", value: "red" },
], { webconsole: gWebConsole }).then(testParagraphs);
}
function testParagraphs()
{
gJSTerm.execute("$$('p')");
waitForMessages({
webconsole: gWebConsole,
messages: [{
text: "[object NodeList]",
category: CATEGORY_OUTPUT,
objects: true,
}],
}).then(onEvalNodeList);
}
function onEvalNodeList(aResults)
{
let clickable = aResults[0].clickableElements[0];
ok(clickable, "clickable object found");
gJSTerm.once("variablesview-fetched", onNodeListFetch);
EventUtils.synthesizeMouse(clickable, 2, 2, {}, gWebConsole.iframeWindow)
}
function onNodeListFetch(aEvent, aVar)
{
gVariablesView = aVar._variablesView;
ok(gVariablesView, "variables view object");
findVariableViewProperties(aVar, [
{ name: "0.textContent", value: /hello world/ },
{ name: "1.textContent", value: /native getters/ },
], { webconsole: gWebConsole }).then(() => {
gWebConsole = gJSTerm = gVariablesView = null;
finishTest();
});
}

Просмотреть файл

@ -0,0 +1,35 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Check that the inspect() jsterm helper function works.
function test()
{
const TEST_URI = "data:text/html;charset=utf8,<p>hello bug 869981";
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, consoleOpened);
}, true);
function consoleOpened(hud)
{
content.wrappedJSObject.testProp = "testValue";
hud.jsterm.once("variablesview-fetched", onObjFetch);
hud.jsterm.execute("inspect(window)");
}
function onObjFetch(aEvent, aVar)
{
ok(aVar._variablesView, "variables view object");
findVariableViewProperties(aVar, [
{ name: "testProp", value: "testValue" },
{ name: "document", value: "[object HTMLDocument]" },
], { webconsole: hud }).then(finishTest);
}
}

Просмотреть файл

@ -23,7 +23,7 @@ let initialString = longString.substring(0,
tempScope.DebuggerServer.LONG_STRING_INITIAL_LENGTH);
let inputValues = [
// [showsPropertyPanel?, input value, expected output format,
// [showsVariablesView?, input value, expected output format,
// print() output, console API output, optional console API test]
// 0
@ -132,7 +132,7 @@ function testNext() {
function testGen() {
let cpos = pos;
let showsPropertyPanel = inputValues[cpos][0];
let showsVariablesView = inputValues[cpos][0];
let inputValue = inputValues[cpos][1];
let expectedOutput = inputValues[cpos][2];
@ -224,35 +224,35 @@ function testGen() {
// Test click on output.
let eventHandlerID = eventHandlers.length + 1;
let propertyPanelShown = function(aEvent, aView, aOptions) {
let variablesViewShown = function(aEvent, aView, aOptions) {
if (aOptions.label.indexOf(expectedOutput) == -1) {
return;
}
HUD.jsterm.off("variablesview-open", propertyPanelShown);
HUD.jsterm.off("variablesview-open", variablesViewShown);
eventHandlers[eventHandlerID] = null;
ok(showsPropertyPanel,
"the property panel shown for inputValues[" + cpos + "]");
ok(showsVariablesView,
"the variables view shown for inputValues[" + cpos + "]");
popupShown[cpos] = true;
if (showsPropertyPanel) {
if (showsVariablesView) {
executeSoon(subtestNext);
}
};
HUD.jsterm.on("variablesview-open", propertyPanelShown);
HUD.jsterm.on("variablesview-open", variablesViewShown);
eventHandlers.push(propertyPanelShown);
eventHandlers.push(variablesViewShown);
// Send the mousedown, mouseup and click events to check if the property
// panel opens.
// Send the mousedown, mouseup and click events to check if the variables
// view opens.
EventUtils.sendMouseEvent({ type: "mousedown" }, messageBody, window);
EventUtils.sendMouseEvent({ type: "click" }, messageBody, window);
if (showsPropertyPanel) {
if (showsVariablesView) {
yield; // wait for the panel to open if we need to.
}
@ -276,7 +276,7 @@ function testEnd() {
for (let i = 0; i < inputValues.length; i++) {
if (inputValues[i][0] && !popupShown[i]) {
ok(false, "the property panel failed to show for inputValues[" + i + "]");
ok(false, "the variables view failed to show for inputValues[" + i + "]");
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше