diff --git a/browser/devtools/commandline/test/Makefile.in b/browser/devtools/commandline/test/Makefile.in index b3289046d3fc..b4c944914796 100644 --- a/browser/devtools/commandline/test/Makefile.in +++ b/browser/devtools/commandline/test/Makefile.in @@ -57,6 +57,7 @@ MOCHITEST_BROWSER_FILES = \ browser_gcli_keyboard3.js \ browser_gcli_menu.js \ browser_gcli_node.js \ + browser_gcli_remote.js \ browser_gcli_resource.js \ browser_gcli_scratchpad.js \ browser_gcli_spell.js \ diff --git a/browser/devtools/commandline/test/browser_gcli_remote.js b/browser/devtools/commandline/test/browser_gcli_remote.js new file mode 100644 index 000000000000..4e5765d28fc0 --- /dev/null +++ b/browser/devtools/commandline/test/browser_gcli_remote.js @@ -0,0 +1,462 @@ +/* + * 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) { + +// + +// 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,

gcli-testRemote.js

"; + +function test() { + helpers.addTabWithToolbar(TEST_URI, function(options) { + return helpers.runTests(options, exports); + }).then(finish); +} + +// + +'use strict'; + +// var assert = require('test/assert'); +// var helpers = require('gclitest/helpers'); +// var mockCommands = require('gclitest/mockCommands'); + +exports.setup = function(options) { + mockCommands.setup(); +}; + +exports.shutdown = function(options) { + mockCommands.shutdown(); +}; + +exports.testRemote = function(options) { + return helpers.audit(options, [ + { + skipRemainingIf: !options.isHttp, + setup: 'remote ', + check: { + input: 'remote ', + hints: '', + markup: 'EEEEEEV', + cursor: 7, + current: '__command', + status: 'ERROR', + options: [ ], + message: 'Can\'t use \'remote\'.', + predictions: [ ], + unassigned: [ ], + } + }, + { + setup: 'connect remote', + check: { + input: 'connect remote', + hints: ' [options]', + markup: 'VVVVVVVVVVVVVV', + cursor: 14, + current: 'prefix', + status: 'VALID', + options: [ ], + message: '', + predictions: [ ], + unassigned: [ ], + args: { + command: { name: 'connect' }, + prefix: { value: 'remote', arg: ' remote', status: 'VALID', message: '' }, + host: { value: undefined, arg: '', status: 'VALID', message: '' }, + port: { value: undefined, arg: '', status: 'VALID', message: '' }, + } + }, + exec: { + output: /^Added [0-9]* commands.$/, + completed: false, + type: 'string', + error: false + } + }, + { + setup: 'remote ', + check: { + input: 'remote ', + hints: '', + markup: 'IIIIIIV', + cursor: 7, + current: '__command', + status: 'ERROR', + optionsIncludes: [ + 'remote', 'remote cd', 'remote context', 'remote echo', + 'remote exec', 'remote exit', 'remote firefox', 'remote help', + 'remote intro', 'remote make' + ], + message: '', + predictions: [ 'remote' ], + unassigned: [ ], + } + }, + { + setup: 'remote echo hello world', + check: { + input: 'remote echo hello world', + hints: '', + markup: 'VVVVVVVVVVVVVVVVVVVVVVV', + cursor: 23, + current: 'message', + status: 'VALID', + options: [ ], + message: '', + predictions: [ ], + unassigned: [ ], + args: { + command: { name: 'remote echo' }, + message: { + value: 'hello world', + arg: ' hello world', + status: 'VALID', + message: '' + } + } + }, + exec: { + output: 'hello world', + completed: false, + type: 'string', + error: false + } + }, + { + setup: 'remote exec ls', + check: { + input: 'remote exec ls', + hints: '', + markup: 'VVVVVVVVVVVVVV', + cursor: 14, + current: 'command', + status: 'VALID', + options: [ ], + message: '', + predictions: [ ], + unassigned: [ ], + args: { + command: { + value: 'ls', + arg: ' ls', + status: 'VALID', + message: '' + } + } + }, + exec: { + // output: '', We can't rely on the contents of the FS + completed: false, + type: 'string', + error: false + } + }, + { + setup: 'remote sleep mistake', + check: { + input: 'remote sleep mistake', + hints: '', + markup: 'VVVVVVVVVVVVVEEEEEEE', + cursor: 20, + current: 'length', + status: 'ERROR', + options: [ ], + message: 'Can\'t convert "mistake" to a number.', + predictions: [ ], + unassigned: [ ], + args: { + command: { name: 'remote sleep' }, + length: { + value: undefined, + arg: ' mistake', + status: 'ERROR', + message: 'Can\'t convert "mistake" to a number.' + } + } + } + }, + { + setup: 'remote sleep 1', + check: { + input: 'remote sleep 1', + hints: '', + markup: 'VVVVVVVVVVVVVV', + cursor: 14, + current: 'length', + status: 'VALID', + options: [ ], + message: '', + predictions: [ ], + unassigned: [ ], + args: { + command: { name: 'remote sleep' }, + length: { value: 1, arg: ' 1', status: 'VALID', message: '' } + } + }, + exec: { + output: 'Done', + completed: false, + type: 'string', + error: false + } + }, + { + setup: 'remote help ', + skipIf: true, // The help command is not remotable + check: { + input: 'remote help ', + hints: '[search]', + markup: 'VVVVVVVVVVVV', + cursor: 12, + current: 'search', + status: 'VALID', + options: [ ], + message: '', + predictions: [ ], + unassigned: [ ], + args: { + command: { name: 'remote help' }, + search: { + value: undefined, + arg: '', + status: 'VALID', + message: '' + } + } + }, + exec: { + output: '', + completed: false, + type: 'string', + error: false + } + }, + { + setup: 'remote intro', + check: { + input: 'remote intro', + hints: '', + markup: 'VVVVVVVVVVVV', + cursor: 12, + current: '__command', + status: 'VALID', + options: [ ], + message: '', + predictions: [ ], + unassigned: [ ], + args: { + command: { name: 'remote intro' } + } + }, + exec: { + output: [ + /^This command line/, + /F1\/Escape/ + ], + completed: false, + type: 'intro', + error: false + } + }, + { + setup: 'context remote', + check: { + input: 'context remote', + hints: '', + markup: 'VVVVVVVVVVVVVV', + cursor: 14, + current: 'prefix', + status: 'VALID', + optionsContains: [ 'remote', 'remote cd', 'remote echo', 'remote exec', 'remote exit', 'remote firefox', 'remote help', 'remote intro', 'remote make' ], + message: '', + predictionsContains: [ 'remote', 'remote cd', 'remote echo', 'remote exec', 'remote exit', 'remote firefox', 'remote help', 'remote intro', 'remote make', 'remote pref' ], + unassigned: [ ], + args: { + command: { name: 'context' }, + prefix: { + /*value:[object Object],*/ + arg: ' remote', + status: 'VALID', + message: '' + } + } + }, + exec: { + output: 'Using remote as a command prefix', + completed: true, + type: 'string', + error: false + } + }, + { + setup: 'exec ls', + check: { + input: 'exec ls', + hints: '', + markup: 'VVVVVVV', + cursor: 7, + current: 'command', + status: 'VALID', + options: [ ], + message: '', + predictions: [ ], + unassigned: [ ], + args: { + command: { value: 'ls', arg: ' ls', status: 'VALID', message: '' }, + } + }, + exec: { + // output: '', We can't rely on the contents of the filesystem + completed: false, + type: 'string', + error: false + } + }, + { + setup: 'echo hello world', + check: { + input: 'echo hello world', + hints: '', + markup: 'VVVVVVVVVVVVVVVV', + cursor: 16, + current: 'message', + status: 'VALID', + options: [ ], + message: '', + predictions: [ ], + unassigned: [ ], + args: { + command: { name: 'remote echo' }, + message: { + value: 'hello world', + arg: ' hello world', + status: 'VALID', + message: '' + } + } + }, + exec: { + output: /^hello world$/, + type: 'string', + error: false + } + }, + { + setup: 'context', + check: { + input: 'context', + hints: ' [prefix]', + markup: 'VVVVVVV', + cursor: 7, + current: '__command', + status: 'VALID', + optionsContains: [ 'remote', 'remote cd', 'remote echo', 'remote exec', 'remote exit', 'remote firefox', 'remote help', 'remote intro', 'remote make' ], + message: '', + predictions: [ ], + unassigned: [ ], + args: { + command: { name: 'context' }, + prefix: { value: undefined, arg: '', status: 'VALID', message: '' } + } + }, + exec: { + output: 'Command prefix is unset', + completed: true, + type: 'string', + error: false + } + }, + { + setup: 'disconnect ', + check: { + input: 'disconnect ', + hints: 'remote', + markup: 'VVVVVVVVVVV', + cursor: 11, + current: 'prefix', + status: 'ERROR', + options: [ 'remote' ], + message: '', + predictions: [ 'remote' ], + unassigned: [ ], + args: { + command: { name: 'disconnect' }, + prefix: { + value: undefined, + arg: '', + status: 'INCOMPLETE', + message: '' + } + } + } + }, + { + setup: 'disconnect remote --force', + check: { + input: 'disconnect remote --force', + hints: '', + markup: 'VVVVVVVVVVVVVVVVVVVVVVVVV', + cursor: 25, + current: 'force', + status: 'VALID', + message: '', + unassigned: [ ], + args: { + command: { name: 'disconnect' }, + prefix: { + value: function(connection) { + assert.is(connection.prefix, 'remote', 'disconnecting remote'); + }, + arg: ' remote', + status: 'VALID', + message: '' + } + } + }, + exec: { + output: /^Removed [0-9]* commands.$/, + completed: true, + type: 'string', + error: false + } + }, + { + setup: 'remote ', + check: { + input: 'remote ', + hints: '', + markup: 'EEEEEEV', + cursor: 7, + current: '__command', + status: 'ERROR', + options: [ ], + message: 'Can\'t use \'remote\'.', + predictions: [ ], + unassigned: [ ], + } + } + ]); +}; + + +// }); + diff --git a/browser/devtools/commandline/test/helpers.js b/browser/devtools/commandline/test/helpers.js index a44218c55b98..b7b9bd490e0e 100644 --- a/browser/devtools/commandline/test/helpers.js +++ b/browser/devtools/commandline/test/helpers.js @@ -226,7 +226,7 @@ helpers._actual = { .replace(/ $/, ''); }; - var promisedJoin = util.promised(join); + var promisedJoin = Promise.promised(join); return promisedJoin(templateData.directTabText, templateData.emptyParameters, templateData.arrowTabText); @@ -314,12 +314,12 @@ helpers._createDebugCheck = function(options) { var hintsPromise = helpers._actual.hints(options); var predictionsPromise = helpers._actual.predictions(options); - return util.all(hintsPromise, predictionsPromise).then(function(values) { + return Promise.all(hintsPromise, predictionsPromise).then(function(values) { var hints = values[0]; var predictions = values[1]; var output = ''; - output += 'helpers.audit(options, [\n'; + output += 'return helpers.audit(options, [\n'; output += ' {\n'; if (cursor === input.length) { @@ -627,13 +627,14 @@ helpers._check = function(options, name, checks) { Object.keys(checks.args).forEach(function(paramName) { var check = checks.args[paramName]; - var assignment; - if (paramName === 'command') { + // We allow an 'argument' called 'command' to be the command itself, but + // what if the command has a parameter called 'command' (for example, an + // 'exec' command)? We default to using the parameter because checking + // the command value is less useful + var assignment = requisition.getAssignment(paramName); + if (assignment == null && paramName === 'command') { assignment = requisition.commandAssignment; } - else { - assignment = requisition.getAssignment(paramName); - } if (assignment == null) { assert.ok(false, 'Unknown arg: ' + paramName + suffix); @@ -641,9 +642,19 @@ helpers._check = function(options, name, checks) { } if ('value' in check) { - assert.is(assignment.value, - check.value, - 'arg.' + paramName + '.value' + suffix); + if (typeof check.value === 'function') { + try { + check.value(assignment.value); + } + catch (ex) { + assert.ok(false, '' + ex); + } + } + else { + assert.is(assignment.value, + check.value, + 'arg.' + paramName + '.value' + suffix); + } } if ('name' in check) { @@ -689,7 +700,7 @@ helpers._check = function(options, name, checks) { }); } - return util.all(outstanding).then(function() { + return Promise.all(outstanding).then(function() { // Ensure the promise resolves to nothing return undefined; }); @@ -707,7 +718,16 @@ helpers._exec = function(options, name, expected) { return Promise.resolve({}); } - var output = options.display.requisition.exec({ hidden: true }); + var output; + try { + output = options.display.requisition.exec({ hidden: true }); + } + catch (ex) { + assert.ok(false, 'Failure executing \'' + name + '\': ' + ex); + util.errorHandler(ex); + + return Promise.resolve({}); + } if ('completed' in expected) { assert.is(output.completed, @@ -725,9 +745,6 @@ helpers._exec = function(options, name, expected) { } var checkOutput = function() { - var div = options.window.document.createElement('div'); - var conversionContext = options.display.requisition.conversionContext; - if ('type' in expected) { assert.is(output.type, expected.type, @@ -740,20 +757,20 @@ helpers._exec = function(options, name, expected) { 'output.error for: ' + name); } + var conversionContext = options.display.requisition.conversionContext; var convertPromise = converters.convert(output.data, output.type, 'dom', conversionContext); return convertPromise.then(function(node) { - div.appendChild(node); - var actualOutput = div.textContent.trim(); + var actualOutput = node.textContent.trim(); var doTest = function(match, against) { if (match.test(against)) { - assert.ok(true, 'html output for ' + name + ' should match ' + - match.source); + assert.ok(true, 'html output for ' + name + ' should match /' + + match.source + '/'); } else { - assert.ok(false, 'html output for ' + name + ' should match ' + + assert.ok(false, 'html output for ' + name + ' should match /' + match.source + - '. Actual textContent: "' + against + '"'); + '/. Actual textContent: "' + against + '"'); } }; @@ -810,11 +827,13 @@ var totalResponseTime = 0; var averageOver = 0; var maxResponseTime = 0; var maxResponseCulprit = undefined; +var start = undefined; /** * Restart the stats collection process */ helpers.resetResponseTimes = function() { + start = new Date().getTime(); totalResponseTime = 0; averageOver = 0; maxResponseTime = 0; @@ -849,6 +868,20 @@ Object.defineProperty(helpers, 'maxResponseCulprit', { enumerable: true }); +/** + * Quick summary of the times + */ +Object.defineProperty(helpers, 'timingSummary', { + get: function() { + var elapsed = (new Date().getTime() - start) / 1000; + return 'Total ' + elapsed + 's, ' + + 'ave response ' + helpers.averageResponseTime + 'ms, ' + + 'max response ' + helpers.maxResponseTime + 'ms ' + + 'from \'' + helpers.maxResponseCulprit + '\''; + }, + enumerable: true +}); + /** * A way of turning a set of tests into something more declarative, this helps * to allow tests to be asynchronous. diff --git a/browser/devtools/debugger/test/Makefile.in b/browser/devtools/debugger/test/Makefile.in index a2cf80ae7ac4..61f0079e7da3 100644 --- a/browser/devtools/debugger/test/Makefile.in +++ b/browser/devtools/debugger/test/Makefile.in @@ -99,7 +99,6 @@ MOCHITEST_BROWSER_TESTS = \ browser_dbg_source_maps-01.js \ browser_dbg_source_maps-02.js \ head.js \ - helpers.js \ $(NULL) MOCHITEST_BROWSER_PAGES = \ diff --git a/browser/devtools/debugger/test/head.js b/browser/devtools/debugger/test/head.js index f5a3c8f94b4b..6cf366c53de6 100644 --- a/browser/devtools/debugger/test/head.js +++ b/browser/devtools/debugger/test/head.js @@ -24,7 +24,7 @@ let TargetFactory = devtools.TargetFactory; // Import the GCLI test helper let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); -Services.scriptloader.loadSubScript(testDir + "/helpers.js", this); +Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this); const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/"; const TAB1_URL = EXAMPLE_URL + "browser_dbg_tab1.html"; diff --git a/browser/devtools/debugger/test/helpers.js b/browser/devtools/debugger/test/helpers.js deleted file mode 100644 index a44218c55b98..000000000000 --- a/browser/devtools/debugger/test/helpers.js +++ /dev/null @@ -1,1005 +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 as a JSM -// ------------ -// helpers._createDebugCheck() can be useful at runtime. To use as a JSM, copy -// commandline/test/helpers.js to shared/helpers.jsm, and then import to -// DeveloperToolbar.jsm with: -// XPCOMUtils.defineLazyModuleGetter(this, "helpers", -// "resource:///modules/devtools/helpers.jsm"); -// At the bottom of DeveloperToolbar.prototype._onload add this: -// var options = { display: this.display }; -// this._input.onkeypress = function(ev) { -// helpers.setup(options); -// dump(helpers._createDebugCheck() + '\n\n'); -// }; -// Now GCLI will emit output on every keypress that both explains the state -// of GCLI and can be run as a test case. - -this.EXPORTED_SYMBOLS = [ 'helpers' ]; -var helpers = {}; -this.helpers = helpers; -let require = (Cu.import("resource://gre/modules/devtools/Require.jsm", {})).require; -Components.utils.import("resource://gre/modules/devtools/gcli.jsm", {}); - -let console = (Cu.import("resource://gre/modules/devtools/Console.jsm", {})).console; -let TargetFactory = (Cu.import("resource://gre/modules/devtools/Loader.jsm", {})).devtools.TargetFactory; - -let Promise = (Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {})).Promise; -let assert = { ok: ok, is: is, log: info }; - -var util = require('util/util'); - -var converters = require('gcli/converters'); - -/** - * Warning: For use with Firefox Mochitests only. - * - * Open a new tab at a URL and call a callback on load, and then tidy up when - * the callback finishes. - * The function will be passed a set of test options, and will usually return a - * promise to indicate that the tab can be cleared up. (To be formal, we call - * Promise.resolve() on the return value of the callback function) - * - * The options used by addTab include: - * - chromeWindow: XUL window parent of created tab. a.k.a 'window' in mochitest - * - tab: The new XUL tab element, as returned by gBrowser.addTab() - * - target: The debug target as defined by the devtools framework - * - browser: The XUL browser element for the given tab - * - window: Content window for the created tab. a.k.a 'content' in mochitest - * - isFirefox: Always true. Allows test sharing with GCLI - * - * Normally addTab will create an options object containing the values as - * described above. However these options can be customized by the third - * 'options' parameter. This has the ability to customize the value of - * chromeWindow or isFirefox, and to add new properties. - * - * @param url The URL for the new tab - * @param callback The function to call on page load - * @param options An optional set of options to customize the way the tests run - */ -helpers.addTab = function(url, callback, options) { - var deferred = Promise.defer(); - - waitForExplicitFinish(); - - options = options || {}; - options.chromeWindow = options.chromeWindow || window; - options.isFirefox = true; - - var tabbrowser = options.chromeWindow.gBrowser; - options.tab = tabbrowser.addTab(); - tabbrowser.selectedTab = options.tab; - options.browser = tabbrowser.getBrowserForTab(options.tab); - options.target = TargetFactory.forTab(options.tab); - - var onPageLoad = function() { - options.browser.removeEventListener("load", onPageLoad, true); - options.document = options.browser.contentDocument; - options.window = options.document.defaultView; - - var cleanUp = function() { - tabbrowser.removeTab(options.tab); - - delete options.window; - delete options.document; - - delete options.target; - delete options.browser; - delete options.tab; - - delete options.chromeWindow; - delete options.isFirefox; - - deferred.resolve(); - }; - - var reply = callback(options); - Promise.resolve(reply).then(cleanUp, function(error) { - ok(false, error); - cleanUp(); - }); - }; - - options.browser.contentWindow.location = url; - options.browser.addEventListener("load", onPageLoad, true); - - return deferred.promise; -}; - -/** - * Warning: For use with Firefox Mochitests only. - * - * As addTab, but that also opens the developer toolbar. In addition a new - * 'display' property is added to the options object with the display from GCLI - * in the developer toolbar - */ -helpers.addTabWithToolbar = function(url, callback, options) { - return helpers.addTab(url, function(innerOptions) { - var win = innerOptions.chromeWindow; - var deferred = Promise.defer(); - - win.DeveloperToolbar.show(true, function() { - innerOptions.display = win.DeveloperToolbar.display; - - var cleanUp = function() { - win.DeveloperToolbar.hide(); - delete innerOptions.display; - deferred.resolve(); - }; - - var reply = callback(innerOptions); - Promise.resolve(reply).then(cleanUp, function(error) { - ok(false, error); - cleanUp(); - }); - }); - return deferred.promise; - }, options); -}; - -/** - * Warning: For use with Firefox Mochitests only. - * - * Run a set of test functions stored in the values of the 'exports' object - * functions stored under setup/shutdown will be run at the start/end of the - * sequence of tests. - * A test will be considered finished when its return value is resolved. - * @param options An object to be passed to the test functions - * @param tests An object containing named test functions - * @return a promise which will be resolved when all tests have been run and - * their return values resolved - */ -helpers.runTests = function(options, tests) { - var testNames = Object.keys(tests).filter(function(test) { - return test != "setup" && test != "shutdown"; - }); - - var recover = function(error) { - ok(false, error); - console.error(error); - }; - - info("SETUP"); - var setupDone = (tests.setup != null) ? - Promise.resolve(tests.setup(options)) : - Promise.resolve(); - - var testDone = setupDone.then(function() { - return util.promiseEach(testNames, function(testName) { - info(testName); - var action = tests[testName]; - - if (typeof action === "function") { - var reply = action.call(tests, options); - return Promise.resolve(reply); - } - else if (Array.isArray(action)) { - return helpers.audit(options, action); - } - - return Promise.reject("test action '" + testName + - "' is not a function or helpers.audit() object"); - }); - }, recover); - - return testDone.then(function() { - info("SHUTDOWN"); - return (tests.shutdown != null) ? - Promise.resolve(tests.shutdown(options)) : - Promise.resolve(); - }, recover); -}; - -/////////////////////////////////////////////////////////////////////////////// - -function checkOptions(options) { - if (options == null) { - console.trace(); - throw new Error('Missing options object'); - } - if (options.display == null) { - console.trace(); - throw new Error('options object does not contain a display property'); - } - if (options.display.requisition == null) { - console.trace(); - throw new Error('display object does not contain a requisition'); - } -} - -/** - * Various functions to return the actual state of the command line - */ -helpers._actual = { - input: function(options) { - return options.display.inputter.element.value; - }, - - hints: function(options) { - var templateData = options.display.completer._getCompleterTemplateData(); - var join = function(directTabText, emptyParameters, arrowTabText) { - return (directTabText + emptyParameters.join('') + arrowTabText) - .replace(/\u00a0/g, ' ') - .replace(/\u21E5/, '->') - .replace(/ $/, ''); - }; - - var promisedJoin = util.promised(join); - return promisedJoin(templateData.directTabText, - templateData.emptyParameters, - templateData.arrowTabText); - }, - - markup: function(options) { - var cursor = options.display.inputter.element.selectionStart; - var statusMarkup = options.display.requisition.getInputStatusMarkup(cursor); - return statusMarkup.map(function(s) { - return Array(s.string.length + 1).join(s.status.toString()[0]); - }).join(''); - }, - - cursor: function(options) { - return options.display.inputter.element.selectionStart; - }, - - current: function(options) { - return options.display.requisition.getAssignmentAt(helpers._actual.cursor(options)).param.name; - }, - - status: function(options) { - return options.display.requisition.getStatus().toString(); - }, - - predictions: function(options) { - var cursor = options.display.inputter.element.selectionStart; - var assignment = options.display.requisition.getAssignmentAt(cursor); - return assignment.getPredictions().then(function(predictions) { - return predictions.map(function(prediction) { - return prediction.name; - }); - }); - }, - - unassigned: function(options) { - return options.display.requisition._unassigned.map(function(assignment) { - return assignment.arg.toString(); - }.bind(this)); - }, - - outputState: function(options) { - var outputData = options.display.focusManager._shouldShowOutput(); - return outputData.visible + ':' + outputData.reason; - }, - - tooltipState: function(options) { - var tooltipData = options.display.focusManager._shouldShowTooltip(); - return tooltipData.visible + ':' + tooltipData.reason; - }, - - options: function(options) { - if (options.display.tooltip.field.menu == null) { - return []; - } - return options.display.tooltip.field.menu.items.map(function(item) { - return item.name.textContent ? item.name.textContent : item.name; - }); - }, - - message: function(options) { - return options.display.tooltip.errorEle.textContent; - } -}; - -function shouldOutputUnquoted(value) { - var type = typeof value; - return value == null || type === 'boolean' || type === 'number'; -} - -function outputArray(array) { - return (array.length === 0) ? - '[ ]' : - '[ \'' + array.join('\', \'') + '\' ]'; -} - -helpers._createDebugCheck = function(options) { - checkOptions(options); - var requisition = options.display.requisition; - var command = requisition.commandAssignment.value; - var cursor = helpers._actual.cursor(options); - var input = helpers._actual.input(options); - var padding = Array(input.length + 1).join(' '); - - var hintsPromise = helpers._actual.hints(options); - var predictionsPromise = helpers._actual.predictions(options); - - return util.all(hintsPromise, predictionsPromise).then(function(values) { - var hints = values[0]; - var predictions = values[1]; - var output = ''; - - output += 'helpers.audit(options, [\n'; - output += ' {\n'; - - if (cursor === input.length) { - output += ' setup: \'' + input + '\',\n'; - } - else { - output += ' name: \'' + input + ' (cursor=' + cursor + ')\',\n'; - output += ' setup: function() {\n'; - output += ' return helpers.setInput(options, \'' + input + '\', ' + cursor + ');\n'; - output += ' },\n'; - } - - output += ' check: {\n'; - - output += ' input: \'' + input + '\',\n'; - output += ' hints: ' + padding + '\'' + hints + '\',\n'; - output += ' markup: \'' + helpers._actual.markup(options) + '\',\n'; - output += ' cursor: ' + cursor + ',\n'; - output += ' current: \'' + helpers._actual.current(options) + '\',\n'; - output += ' status: \'' + helpers._actual.status(options) + '\',\n'; - output += ' options: ' + outputArray(helpers._actual.options(options)) + ',\n'; - output += ' message: \'' + helpers._actual.message(options) + '\',\n'; - output += ' predictions: ' + outputArray(predictions) + ',\n'; - output += ' unassigned: ' + outputArray(requisition._unassigned) + ',\n'; - output += ' outputState: \'' + helpers._actual.outputState(options) + '\',\n'; - output += ' tooltipState: \'' + helpers._actual.tooltipState(options) + '\'' + - (command ? ',' : '') +'\n'; - - if (command) { - output += ' args: {\n'; - output += ' command: { name: \'' + command.name + '\' },\n'; - - requisition.getAssignments().forEach(function(assignment) { - output += ' ' + assignment.param.name + ': { '; - - if (typeof assignment.value === 'string') { - output += 'value: \'' + assignment.value + '\', '; - } - else if (shouldOutputUnquoted(assignment.value)) { - output += 'value: ' + assignment.value + ', '; - } - else { - output += '/*value:' + assignment.value + ',*/ '; - } - - output += 'arg: \'' + assignment.arg + '\', '; - output += 'status: \'' + assignment.getStatus().toString() + '\', '; - output += 'message: \'' + assignment.getMessage() + '\''; - output += ' },\n'; - }); - - output += ' }\n'; - } - - output += ' },\n'; - output += ' exec: {\n'; - output += ' output: \'\',\n'; - output += ' completed: true,\n'; - output += ' type: \'string\',\n'; - output += ' error: false\n'; - output += ' }\n'; - output += ' }\n'; - output += ']);'; - - return output; - }.bind(this), util.errorHandler); -}; - -/** - * Simulate focusing the input field - */ -helpers.focusInput = function(options) { - checkOptions(options); - options.display.inputter.focus(); -}; - -/** - * Simulate pressing TAB in the input field - */ -helpers.pressTab = function(options) { - checkOptions(options); - return helpers.pressKey(options, 9 /*KeyEvent.DOM_VK_TAB*/); -}; - -/** - * Simulate pressing RETURN in the input field - */ -helpers.pressReturn = function(options) { - checkOptions(options); - return helpers.pressKey(options, 13 /*KeyEvent.DOM_VK_RETURN*/); -}; - -/** - * Simulate pressing a key by keyCode in the input field - */ -helpers.pressKey = function(options, keyCode) { - checkOptions(options); - var fakeEvent = { - keyCode: keyCode, - preventDefault: function() { }, - timeStamp: new Date().getTime() - }; - options.display.inputter.onKeyDown(fakeEvent); - return options.display.inputter.handleKeyUp(fakeEvent); -}; - -/** - * A list of special key presses and how to to them, for the benefit of - * helpers.setInput - */ -var ACTIONS = { - '': function(options) { - return helpers.pressTab(options); - }, - '': function(options) { - return helpers.pressReturn(options); - }, - '': function(options) { - return helpers.pressKey(options, 38 /*KeyEvent.DOM_VK_UP*/); - }, - '': function(options) { - return helpers.pressKey(options, 40 /*KeyEvent.DOM_VK_DOWN*/); - } -}; - -/** - * Used in helpers.setInput to cut an input string like "blahfoo" into - * an array like [ "blah", "", "foo", "" ]. - * When using this RegExp, you also need to filter out the blank strings. - */ -var CHUNKER = /([^<]*)(<[A-Z]+>)/; - -/** - * Alter the input to typed optionally leaving the cursor at - * cursor. - * @return A promise of the number of key-presses to respond - */ -helpers.setInput = function(options, typed, cursor) { - checkOptions(options); - var promise = undefined; - var inputter = options.display.inputter; - // We try to measure average keypress time, but setInput can simulate - // several, so we try to keep track of how many - var chunkLen = 1; - - // The easy case is a simple string without things like - if (typed.indexOf('<') === -1) { - promise = inputter.setInput(typed); - } - else { - // Cut the input up into input strings separated by '' tokens. The - // CHUNKS RegExp leaves blanks so we filter them out. - var chunks = typed.split(CHUNKER).filter(function(s) { - return s != ''; - }); - chunkLen = chunks.length + 1; - - // We're working on this in chunks so first clear the input - promise = inputter.setInput('').then(function() { - return util.promiseEach(chunks, function(chunk) { - if (chunk.charAt(0) === '<') { - var action = ACTIONS[chunk]; - if (typeof action !== 'function') { - console.error('Known actions: ' + Object.keys(ACTIONS).join()); - throw new Error('Key action not found "' + chunk + '"'); - } - return action(options); - } - else { - return inputter.setInput(inputter.element.value + chunk); - } - }); - }); - } - - return promise.then(function() { - if (cursor != null) { - options.display.inputter.setCursor({ start: cursor, end: cursor }); - } - else { - // This is a hack because jsdom appears to not handle cursor updates - // in the same way as most browsers. - if (options.isJsdom) { - options.display.inputter.setCursor({ - start: typed.length, - end: typed.length - }); - } - } - - options.display.focusManager.onInputChange(); - - // Firefox testing is noisy and distant, so logging helps - if (options.isFirefox) { - var cursorStr = (cursor == null ? '' : ', ' + cursor); - log('setInput("' + typed + '"' + cursorStr + ')'); - } - - return chunkLen; - }); -}; - -/** - * Helper for helpers.audit() to ensure that all the 'check' properties match. - * See helpers.audit for more information. - * @param name The name to use in error messages - * @param checks See helpers.audit for a list of available checks - * @return A promise which resolves to undefined when the checks are complete - */ -helpers._check = function(options, name, checks) { - if (checks == null) { - return Promise.resolve(); - } - - var outstanding = []; - var suffix = name ? ' (for \'' + name + '\')' : ''; - - if ('input' in checks) { - assert.is(helpers._actual.input(options), checks.input, 'input' + suffix); - } - - if ('cursor' in checks) { - assert.is(helpers._actual.cursor(options), checks.cursor, 'cursor' + suffix); - } - - if ('current' in checks) { - assert.is(helpers._actual.current(options), checks.current, 'current' + suffix); - } - - if ('status' in checks) { - assert.is(helpers._actual.status(options), checks.status, 'status' + suffix); - } - - if ('markup' in checks) { - assert.is(helpers._actual.markup(options), checks.markup, 'markup' + suffix); - } - - if ('hints' in checks) { - var hintCheck = function(actualHints) { - assert.is(actualHints, checks.hints, 'hints' + suffix); - }; - outstanding.push(helpers._actual.hints(options).then(hintCheck)); - } - - if ('predictions' in checks) { - var predictionsCheck = function(actualPredictions) { - helpers._arrayIs(actualPredictions, - checks.predictions, - 'predictions' + suffix); - }; - outstanding.push(helpers._actual.predictions(options).then(predictionsCheck)); - } - - if ('predictionsContains' in checks) { - var containsCheck = function(actualPredictions) { - checks.predictionsContains.forEach(function(prediction) { - var index = actualPredictions.indexOf(prediction); - assert.ok(index !== -1, - 'predictionsContains:' + prediction + suffix); - }); - }; - outstanding.push(helpers._actual.predictions(options).then(containsCheck)); - } - - if ('unassigned' in checks) { - helpers._arrayIs(helpers._actual.unassigned(options), - checks.unassigned, - 'unassigned' + suffix); - } - - if ('tooltipState' in checks) { - if (options.isJsdom) { - assert.log('Skipped ' + name + '/tooltipState due to jsdom'); - } - else { - assert.is(helpers._actual.tooltipState(options), - checks.tooltipState, - 'tooltipState' + suffix); - } - } - - if ('outputState' in checks) { - if (options.isJsdom) { - assert.log('Skipped ' + name + '/outputState due to jsdom'); - } - else { - assert.is(helpers._actual.outputState(options), - checks.outputState, - 'outputState' + suffix); - } - } - - if ('options' in checks) { - helpers._arrayIs(helpers._actual.options(options), - checks.options, - 'options' + suffix); - } - - if ('error' in checks) { - assert.is(helpers._actual.message(options), checks.error, 'error' + suffix); - } - - if (checks.args != null) { - var requisition = options.display.requisition; - Object.keys(checks.args).forEach(function(paramName) { - var check = checks.args[paramName]; - - var assignment; - if (paramName === 'command') { - assignment = requisition.commandAssignment; - } - else { - assignment = requisition.getAssignment(paramName); - } - - if (assignment == null) { - assert.ok(false, 'Unknown arg: ' + paramName + suffix); - return; - } - - if ('value' in check) { - assert.is(assignment.value, - check.value, - 'arg.' + paramName + '.value' + suffix); - } - - if ('name' in check) { - if (options.isJsdom) { - assert.log('Skipped arg.' + paramName + '.name due to jsdom'); - } - else { - assert.is(assignment.value.name, - check.name, - 'arg.' + paramName + '.name' + suffix); - } - } - - if ('type' in check) { - assert.is(assignment.arg.type, - check.type, - 'arg.' + paramName + '.type' + suffix); - } - - if ('arg' in check) { - assert.is(assignment.arg.toString(), - check.arg, - 'arg.' + paramName + '.arg' + suffix); - } - - if ('status' in check) { - assert.is(assignment.getStatus().toString(), - check.status, - 'arg.' + paramName + '.status' + suffix); - } - - if ('message' in check) { - if (typeof check.message.test === 'function') { - assert.ok(check.message.test(assignment.getMessage()), - 'arg.' + paramName + '.message' + suffix); - } - else { - assert.is(assignment.getMessage(), - check.message, - 'arg.' + paramName + '.message' + suffix); - } - } - }); - } - - return util.all(outstanding).then(function() { - // Ensure the promise resolves to nothing - return undefined; - }); -}; - -/** - * Helper for helpers.audit() to ensure that all the 'exec' properties work. - * See helpers.audit for more information. - * @param name The name to use in error messages - * @param expected See helpers.audit for a list of available exec checks - * @return A promise which resolves to undefined when the checks are complete - */ -helpers._exec = function(options, name, expected) { - if (expected == null) { - return Promise.resolve({}); - } - - var output = options.display.requisition.exec({ hidden: true }); - - if ('completed' in expected) { - assert.is(output.completed, - expected.completed, - 'output.completed false for: ' + name); - } - - if (!options.window.document.createElement) { - assert.log('skipping output tests (missing doc.createElement) for ' + name); - return Promise.resolve({ output: output }); - } - - if (!('output' in expected)) { - return Promise.resolve({ output: output }); - } - - var checkOutput = function() { - var div = options.window.document.createElement('div'); - var conversionContext = options.display.requisition.conversionContext; - - if ('type' in expected) { - assert.is(output.type, - expected.type, - 'output.type for: ' + name); - } - - if ('error' in expected) { - assert.is(output.error, - expected.error, - 'output.error for: ' + name); - } - - var convertPromise = converters.convert(output.data, output.type, 'dom', - conversionContext); - return convertPromise.then(function(node) { - div.appendChild(node); - var actualOutput = div.textContent.trim(); - - var doTest = function(match, against) { - if (match.test(against)) { - assert.ok(true, 'html output for ' + name + ' should match ' + - match.source); - } else { - assert.ok(false, 'html output for ' + name + ' should match ' + - match.source + - '. Actual textContent: "' + against + '"'); - } - }; - - if (typeof expected.output === 'string') { - assert.is(actualOutput, - expected.output, - 'html output for ' + name); - } - else if (Array.isArray(expected.output)) { - expected.output.forEach(function(match) { - doTest(match, actualOutput); - }); - } - else { - doTest(expected.output, actualOutput); - } - - return { output: output, text: actualOutput }; - }); - }; - - return output.promise.then(checkOutput, checkOutput); -}; - -/** - * Helper to setup the test - */ -helpers._setup = function(options, name, action) { - if (typeof action === 'string') { - return helpers.setInput(options, action); - } - - if (typeof action === 'function') { - return Promise.resolve(action()); - } - - return Promise.reject('\'setup\' property must be a string or a function. Is ' + action); -}; - -/** - * Helper to shutdown the test - */ -helpers._post = function(name, action, data) { - if (typeof action === 'function') { - return Promise.resolve(action(data.output, data.text)); - } - return Promise.resolve(action); -}; - -/* - * We do some basic response time stats so we can see if we're getting slow - */ -var totalResponseTime = 0; -var averageOver = 0; -var maxResponseTime = 0; -var maxResponseCulprit = undefined; - -/** - * Restart the stats collection process - */ -helpers.resetResponseTimes = function() { - totalResponseTime = 0; - averageOver = 0; - maxResponseTime = 0; - maxResponseCulprit = undefined; -}; - -/** - * Expose an average response time in milliseconds - */ -Object.defineProperty(helpers, 'averageResponseTime', { - get: function() { - return averageOver === 0 ? - undefined : - Math.round(100 * totalResponseTime / averageOver) / 100; - }, - enumerable: true -}); - -/** - * Expose a maximum response time in milliseconds - */ -Object.defineProperty(helpers, 'maxResponseTime', { - get: function() { return Math.round(maxResponseTime * 100) / 100; }, - enumerable: true -}); - -/** - * Expose the name of the test that provided the maximum response time - */ -Object.defineProperty(helpers, 'maxResponseCulprit', { - get: function() { return maxResponseCulprit; }, - enumerable: true -}); - -/** - * A way of turning a set of tests into something more declarative, this helps - * to allow tests to be asynchronous. - * @param audits An array of objects each of which contains: - * - setup: string/function to be called to set the test up. - * If audit is a string then it is passed to helpers.setInput(). - * If audit is a function then it is executed. The tests will wait while - * tests that return promises complete. - * - name: For debugging purposes. If name is undefined, and 'setup' - * is a string then the setup value will be used automatically - * - skipIf: A function to define if the test should be skipped. Useful for - * excluding tests from certain environments (e.g. jsdom, firefox, etc). - * The name of the test will be used in log messages noting the skip - * See helpers.reason for pre-defined skip functions. The skip function must - * be synchronous, and will be passed the test options object. - * - skipRemainingIf: A function to skip all the remaining audits in this set. - * See skipIf for details of how skip functions work. - * - check: Check data. Available checks: - * - input: The text displayed in the input field - * - cursor: The position of the start of the cursor - * - status: One of "VALID", "ERROR", "INCOMPLETE" - * - hints: The hint text, i.e. a concatenation of the directTabText, the - * emptyParameters and the arrowTabText. The text as inserted into the UI - * will include NBSP and Unicode RARR characters, these should be - * represented using normal space and '->' for the arrow - * - markup: What state should the error markup be in. e.g. "VVVIIIEEE" - * - args: Maps of checks to make against the arguments: - * - value: i.e. assignment.value (which ignores defaultValue) - * - type: Argument/BlankArgument/MergedArgument/etc i.e. what's assigned - * Care should be taken with this since it's something of an - * implementation detail - * - arg: The toString value of the argument - * - status: i.e. assignment.getStatus - * - message: i.e. assignment.getMessage - * - name: For commands - checks assignment.value.name - * - exec: Object to indicate we should execute the command and check the - * results. Available checks: - * - output: A string, RegExp or array of RegExps to compare with the output - * If typeof output is a string then the output should be exactly equal - * to the given string. If the type of output is a RegExp or array of - * RegExps then the output should match all RegExps - * - completed: A boolean which declares that we should check to see if the - * command completed synchronously - * - post: Function to be called after the checks have been run - */ -helpers.audit = function(options, audits) { - checkOptions(options); - var skipReason = null; - return util.promiseEach(audits, function(audit) { - var name = audit.name; - if (name == null && typeof audit.setup === 'string') { - name = audit.setup; - } - - if (assert.testLogging) { - log('- START \'' + name + '\' in ' + assert.currentTest); - } - - if (audit.skipIf) { - var skip = (typeof audit.skipIf === 'function') ? - audit.skipIf(options) : - !!audit.skipIf; - if (skip) { - var reason = audit.skipIf.name ? 'due to ' + audit.skipIf.name : ''; - assert.log('Skipped ' + name + ' ' + reason); - return Promise.resolve(undefined); - } - } - - if (audit.skipRemainingIf) { - var skipRemainingIf = (typeof audit.skipRemainingIf === 'function') ? - audit.skipRemainingIf(options) : - !!audit.skipRemainingIf; - if (skipRemainingIf) { - skipReason = audit.skipRemainingIf.name ? - 'due to ' + audit.skipRemainingIf.name : - ''; - assert.log('Skipped ' + name + ' ' + skipReason); - return Promise.resolve(undefined); - } - } - - if (skipReason != null) { - assert.log('Skipped ' + name + ' ' + skipReason); - return Promise.resolve(undefined); - } - - var start = new Date().getTime(); - - var setupDone = helpers._setup(options, name, audit.setup); - return setupDone.then(function(chunkLen) { - - if (typeof chunkLen !== 'number') { - chunkLen = 1; - } - - if (assert.currentTest) { - var responseTime = (new Date().getTime() - start) / chunkLen; - totalResponseTime += responseTime; - if (responseTime > maxResponseTime) { - maxResponseTime = responseTime; - 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(data) { - return helpers._post(name, audit.post, data).then(function() { - if (assert.testLogging) { - log('- END \'' + name + '\' in ' + assert.currentTest); - } - }); - }); - }); - }); - }).then(function() { - return options.display.inputter.setInput(''); - }); -}; - -/** - * Compare 2 arrays. - */ -helpers._arrayIs = function(actual, expected, message) { - assert.ok(Array.isArray(actual), 'actual is not an array: ' + message); - assert.ok(Array.isArray(expected), 'expected is not an array: ' + message); - - if (!Array.isArray(actual) || !Array.isArray(expected)) { - return; - } - - assert.is(actual.length, expected.length, 'array length: ' + message); - - for (var i = 0; i < actual.length && i < expected.length; i++) { - assert.is(actual[i], expected[i], 'member[' + i + ']: ' + message); - } -}; - -/** - * A quick helper to log to the correct place - */ -function log(message) { - if (typeof info === 'function') { - info(message); - } - else { - console.log(message); - } -} - -//}); diff --git a/browser/devtools/fontinspector/font-inspector.js b/browser/devtools/fontinspector/font-inspector.js index 6ee5d114307f..7995e586806f 100644 --- a/browser/devtools/fontinspector/font-inspector.js +++ b/browser/devtools/fontinspector/font-inspector.js @@ -183,7 +183,6 @@ FontInspector.prototype = { */ let extraCSS = "* {padding:0;margin:0}"; extraCSS += ".theme-dark {color: white}"; - extraCSS += "p {font-family: '" + name + "';}"; extraCSS += "p {font-size: 40px;line-height:60px;padding:0 10px;margin:0;}"; cssCode += extraCSS; let src = "data:text/html;charset=utf-8,

Abc

"; @@ -194,6 +193,7 @@ FontInspector.prototype = { // nightmare. doc.querySelector("base").href = base; doc.querySelector("style").textContent = cssCode; + doc.querySelector("p").style.fontFamily = name; // Forward theme doc.documentElement.className = document.documentElement.className; }, true); diff --git a/browser/devtools/fontinspector/font-inspector.xhtml b/browser/devtools/fontinspector/font-inspector.xhtml index cb50b1d8a107..19dd2061c978 100644 --- a/browser/devtools/fontinspector/font-inspector.xhtml +++ b/browser/devtools/fontinspector/font-inspector.xhtml @@ -24,7 +24,7 @@