From 19227363e4dd19cb46289092b13157014f6dc2fa Mon Sep 17 00:00:00 2001 From: Joe Walker Date: Wed, 25 Jun 2014 11:21:02 +0100 Subject: [PATCH] Bug 1023233 - Add getSpec to union type; f=gl, r=mratcliffe --- browser/devtools/commandline/test/browser.ini | 1 + .../commandline/test/browser_cmd_inject.js | 54 +++-- .../commandline/test/browser_gcli_types.js | 21 +- .../commandline/test/browser_gcli_union.js | 188 ++++++++++++++++++ .../commandline/test/browser_gcli_url.js | 122 ++++++++++++ .../devtools/commandline/test/mockCommands.js | 95 +++++++++ toolkit/devtools/gcli/commands/inject.js | 14 +- .../gcli/source/lib/gcli/commands/test.js | 22 +- .../gcli/source/lib/gcli/connectors/index.js | 1 + .../gcli/source/lib/gcli/fields/selection.js | 2 +- .../devtools/gcli/source/lib/gcli/index.js | 1 + .../gcli/source/lib/gcli/types/delegate.js | 6 +- .../gcli/source/lib/gcli/types/file.js | 2 +- .../gcli/source/lib/gcli/types/selection.js | 6 +- .../gcli/source/lib/gcli/types/types.js | 9 +- .../gcli/source/lib/gcli/types/union.js | 124 +++++++----- .../gcli/source/lib/gcli/types/url.js | 87 ++++++++ .../gcli/source/lib/gcli/util/host.js | 8 + .../gcli/source/lib/gcli/util/legacy.js | 34 ++++ 19 files changed, 686 insertions(+), 111 deletions(-) create mode 100644 browser/devtools/commandline/test/browser_gcli_union.js create mode 100644 browser/devtools/commandline/test/browser_gcli_url.js create mode 100644 toolkit/devtools/gcli/source/lib/gcli/types/url.js diff --git a/browser/devtools/commandline/test/browser.ini b/browser/devtools/commandline/test/browser.ini index ea5b691ef672..7831692cae74 100644 --- a/browser/devtools/commandline/test/browser.ini +++ b/browser/devtools/commandline/test/browser.ini @@ -103,3 +103,4 @@ skip-if = true || e10s # Disabled until TZ bug is fixed [browser_gcli_tokenize.js] [browser_gcli_tooltip.js] [browser_gcli_types.js] +[browser_gcli_union.js] diff --git a/browser/devtools/commandline/test/browser_cmd_inject.js b/browser/devtools/commandline/test/browser_cmd_inject.js index ffe829a144dc..588487b2e029 100644 --- a/browser/devtools/commandline/test/browser_cmd_inject.js +++ b/browser/devtools/commandline/test/browser_cmd_inject.js @@ -10,36 +10,51 @@ function test() { helpers.addTabWithToolbar(TEST_URI, function(options) { return helpers.audit(options, [ { - setup: 'inject', + setup: 'inject', check: { input: 'inject', - hints: ' ', markup: 'VVVVVV', + hints: ' ', status: 'ERROR' }, }, { - setup: 'inject j', + setup: 'inject j', check: { input: 'inject j', - hints: 'Query', markup: 'VVVVVVVI', + hints: 'Query', status: 'ERROR' }, }, { - setup: 'inject http://example.com/browser/browser/devtools/commandline/test/browser_cmd_inject.js', + setup: 'inject notauri', + check: { + input: 'inject notauri', + hints: ' -> http://notauri/', + markup: 'VVVVVVVIIIIIII', + status: 'ERROR', + args: { + library: { + value: undefined, + status: 'INCOMPLETE' + } + } + } + }, + { + setup: 'inject http://example.com/browser/browser/devtools/commandline/test/browser_cmd_inject.js', check: { input: 'inject http://example.com/browser/browser/devtools/commandline/test/browser_cmd_inject.js', - hints: '', markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV', + hints: '', status: 'VALID', args: { library: { value: function(library) { - is(library.type, 'string', 'inject type name'); - is(library.string, 'http://example.com/browser/browser/devtools/commandline/test/browser_cmd_inject.js', - 'inject uri data'); + is(library.type, 'url', 'inject type name'); + is(library.url.origin, 'http://example.com', 'inject url hostname'); + ok(library.url.path.indexOf('_inject.js') != -1, 'inject url path'); }, status: 'VALID' } @@ -48,27 +63,6 @@ function test() { exec: { output: [ /http:\/\/example.com\/browser\/browser\/devtools\/commandline\/test\/browser_cmd_inject.js loaded/ ] } - }, - { - setup: 'inject notauri', - check: { - input: 'inject notauri', - hints: '', - markup: 'VVVVVVVVVVVVVV', - status: 'VALID', - args: { - library: { - value: function(library) { - is(library.type, 'string', 'inject type name'); - is(library.string, 'notauri', 'inject notauri data'); - }, - status: 'VALID' - } - } - }, - exec: { - output: [ /Failed to load notauri - Invalid URI/ ] - } } ]); }).then(finish, helpers.handleError); diff --git a/browser/devtools/commandline/test/browser_gcli_types.js b/browser/devtools/commandline/test/browser_gcli_types.js index 67d8c897e8b4..c12c5cd38e16 100644 --- a/browser/devtools/commandline/test/browser_gcli_types.js +++ b/browser/devtools/commandline/test/browser_gcli_types.js @@ -77,7 +77,7 @@ function forEachType(options, typeSpec, callback) { return; } else if (name === 'union') { - typeSpec.types = [{ name: "string" }]; + typeSpec.alternatives = [{ name: 'string' }]; } var type = types.createType(typeSpec); @@ -89,7 +89,7 @@ function forEachType(options, typeSpec, callback) { delete typeSpec.data; delete typeSpec.delegateType; delete typeSpec.subtype; - delete typeSpec.types; + delete typeSpec.alternatives; return value; }); @@ -134,3 +134,20 @@ exports.testNullDefault = function(options) { }); }); }; + +exports.testGetSpec = function(options) { + return forEachType(options, {}, function(type) { + if (type.name === 'param') { + return; + } + + var spec = type.getSpec('cmd', 'param'); + assert.ok(spec != null, 'non null spec for ' + type.name); + + var str = JSON.stringify(spec); + assert.ok(str != null, 'serializable spec for ' + type.name); + + var example = options.requisition.types.createType(spec); + assert.ok(example != null, 'creatable spec for ' + type.name); + }); +}; diff --git a/browser/devtools/commandline/test/browser_gcli_union.js b/browser/devtools/commandline/test/browser_gcli_union.js new file mode 100644 index 000000000000..9f3dd08c134d --- /dev/null +++ b/browser/devtools/commandline/test/browser_gcli_union.js @@ -0,0 +1,188 @@ +/* + * 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. + */ + +'use strict'; +// + +// THIS FILE IS GENERATED FROM SOURCE IN THE GCLI PROJECT +// DO NOT EDIT IT DIRECTLY + +var exports = {}; + +var TEST_URI = "data:text/html;charset=utf-8,

gcli-testUnion.js

"; + +function test() { + return Task.spawn(function() { + let options = yield helpers.openTab(TEST_URI); + yield helpers.openToolbar(options); + gcli.addItems(mockCommands.items); + + yield helpers.runTests(options, exports); + + gcli.removeItems(mockCommands.items); + yield helpers.closeToolbar(options); + yield helpers.closeTab(options); + }).then(finish, helpers.handleError); +} + +// + +// var assert = require('../testharness/assert'); +// var helpers = require('./helpers'); + +exports.testDefault = function(options) { + return helpers.audit(options, [ + { + setup: 'unionc1', + check: { + input: 'unionc1', + markup: 'VVVVVVV', + hints: ' ', + status: 'ERROR', + args: { + first: { + value: undefined, + arg: '', + status: 'INCOMPLETE' + } + } + } + }, + { + setup: 'unionc1 three', + check: { + input: 'unionc1 three', + markup: 'VVVVVVVVVVVVV', + hints: '', + status: 'VALID', + args: { + first: { + value: function(data) { + assert.is(Object.keys(data).length, 2, 'union3 Object.keys'); + assert.is(data.type, 'string', 'union3 val type'); + assert.is(data.string, 'three', 'union3 val string'); + }, + arg: ' three', + status: 'VALID' + } + } + }, + exec: { + output: [ + /"type": ?"string"/, + /"string": ?"three"/ + ] + }, + post: function(output, text) { + var data = output.data.first; + assert.is(Object.keys(data).length, 2, 'union3 Object.keys'); + assert.is(data.type, 'string', 'union3 val type'); + assert.is(data.string, 'three', 'union3 val string'); + } + }, + { + setup: 'unionc1 one', + check: { + input: 'unionc1 one', + markup: 'VVVVVVVVVVV', + hints: '', + status: 'VALID', + args: { + first: { + value: function(data) { + assert.is(Object.keys(data).length, 2, 'union1 Object.keys'); + assert.is(data.type, 'selection', 'union1 val type'); + assert.is(data.selection, 1, 'union1 val selection'); + }, + arg: ' one', + status: 'VALID' + } + } + }, + exec: { + output: [ + /"type": ?"selection"/, + /"selection": ?1/ + ] + }, + post: function(output, text) { + var data = output.data.first; + assert.is(Object.keys(data).length, 2, 'union1 Object.keys'); + assert.is(data.type, 'selection', 'union1 val type'); + assert.is(data.selection, 1, 'union1 val selection'); + } + }, + { + skipIf: options.isPhantomjs, // Phantom goes weird with predictions + setup: 'unionc1 5', + check: { + input: 'unionc1 5', + markup: 'VVVVVVVVV', + hints: ' -> two', + predictions: [ 'two' ], + status: 'VALID', + args: { + first: { + value: function(data) { + assert.is(Object.keys(data).length, 2, 'union5 Object.keys'); + assert.is(data.type, 'number', 'union5 val type'); + assert.is(data.number, 5, 'union5 val number'); + }, + arg: ' 5', + status: 'VALID' + } + } + }, + exec: { + output: [ + /"type": ?"number"/, + /"number": ?5/ + ] + }, + post: function(output, text) { + var data = output.data.first; + assert.is(Object.keys(data).length, 2, 'union5 Object.keys'); + assert.is(data.type, 'number', 'union5 val type'); + assert.is(data.number, 5, 'union5 val number'); + } + }, + { + skipRemainingIf: options.isPhantomjs, + setup: 'unionc2 on', + check: { + input: 'unionc2 on', + hints: 'e', + markup: 'VVVVVVVVII', + current: 'first', + status: 'ERROR', + predictionsContains: [ + 'one', + 'http://on/', + 'https://on/' + ], + args: { + command: { name: 'unionc2' }, + first: { + value: undefined, + arg: ' on', + status: 'INCOMPLETE', + message: 'Can\'t use \'on\'.' + }, + } + } + } + ]); +}; diff --git a/browser/devtools/commandline/test/browser_gcli_url.js b/browser/devtools/commandline/test/browser_gcli_url.js new file mode 100644 index 000000000000..1fec41eb738f --- /dev/null +++ b/browser/devtools/commandline/test/browser_gcli_url.js @@ -0,0 +1,122 @@ +/* + * 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. + */ + +'use strict'; +// + +// THIS FILE IS GENERATED FROM SOURCE IN THE GCLI PROJECT +// DO NOT EDIT IT DIRECTLY + +var exports = {}; + +var TEST_URI = "data:text/html;charset=utf-8,

gcli-testUrl.js

"; + +function test() { + return Task.spawn(function() { + let options = yield helpers.openTab(TEST_URI); + yield helpers.openToolbar(options); + gcli.addItems(mockCommands.items); + + yield helpers.runTests(options, exports); + + gcli.removeItems(mockCommands.items); + yield helpers.closeToolbar(options); + yield helpers.closeTab(options); + }).then(finish, helpers.handleError); +} + +// + +// var assert = require('../testharness/assert'); +// var helpers = require('./helpers'); + +exports.testDefault = function(options) { + return helpers.audit(options, [ + { + skipRemainingIf: options.isPhantomjs, + setup: 'urlc', + check: { + input: 'urlc', + markup: 'VVVV', + hints: ' ', + status: 'ERROR', + args: { + url: { + value: undefined, + arg: '', + status: 'INCOMPLETE' + } + } + } + }, + { + setup: 'urlc example', + check: { + input: 'urlc example', + markup: 'VVVVVIIIIIII', + hints: ' -> http://example/', + predictions: [ + 'http://example/', + 'https://example/', + 'http://localhost:9999/example' + ], + status: 'ERROR', + args: { + url: { + value: undefined, + arg: ' example', + status: 'INCOMPLETE' + } + } + }, + }, + { + setup: 'urlc example.com/', + check: { + input: 'urlc example.com/', + markup: 'VVVVVIIIIIIIIIIII', + hints: ' -> http://example.com/', + status: 'ERROR', + args: { + url: { + value: undefined, + arg: ' example.com/', + status: 'INCOMPLETE' + } + } + }, + }, + { + setup: 'urlc http://example.com/index?q=a#hash', + check: { + input: 'urlc http://example.com/index?q=a#hash', + markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV', + hints: '', + status: 'VALID', + args: { + url: { + value: function(data) { + assert.is(data.hash, '#hash', 'url hash'); + }, + arg: ' http://example.com/index?q=a#hash', + status: 'VALID' + } + } + }, + exec: { output: /"url": ?/ } + } + ]); +}; diff --git a/browser/devtools/commandline/test/mockCommands.js b/browser/devtools/commandline/test/mockCommands.js index 74dfa74e060d..1be8bb4dec44 100644 --- a/browser/devtools/commandline/test/mockCommands.js +++ b/browser/devtools/commandline/test/mockCommands.js @@ -24,6 +24,7 @@ var Promise = require('gcli/util/promise').Promise; +var converters = require('gcli/converters/converters'); var mockCommands = {}; // We use an alias for exports here because this module is used in Firefox @@ -40,6 +41,9 @@ mockCommands.setup = function(requisition) { else if (item.item === 'type') { requisition.types.addType(item); } + else if (item.item === 'converter') { + converters.addConverter(item); + } else { console.error('Ignoring item ', item); } @@ -54,6 +58,9 @@ mockCommands.shutdown = function(requisition) { else if (item.item === 'type') { requisition.types.removeType(item); } + else if (item.item === 'converter') { + converters.removeConverter(item); + } else { console.error('Ignoring item ', item); } @@ -70,6 +77,25 @@ function createExec(name) { } mockCommands.items = [ + { + item: 'converter', + from: 'json', + to: 'string', + exec: function(json, context) { + return JSON.stringify(json, null, ' '); + } + }, + { + item: 'converter', + from: 'json', + to: 'view', + exec: function(json, context) { + var html = JSON.stringify(json, null, ' ').replace(/\n/g, '
'); + return { + html: '
' + html + '
' + }; + } + }, { item: 'type', name: 'optionType', @@ -670,5 +696,74 @@ mockCommands.items = [ exec: function(args, context) { return 'Test completed'; } + }, + { + item: 'command', + name: 'urlc', + params: [ + { + name: 'url', + type: 'url' + } + ], + returnType: 'json', + exec: function(args, context) { + return args; + } + }, + { + item: 'command', + name: 'unionc1', + params: [ + { + name: 'first', + type: { + name: 'union', + alternatives: [ + { + name: 'selection', + lookup: [ + { name: 'one', value: 1 }, + { name: 'two', value: 2 }, + ] + }, + 'number', + { name: 'string' } + ] + } + } + ], + returnType: 'json', + exec: function(args, context) { + return args; + } + }, + { + item: 'command', + name: 'unionc2', + params: [ + { + name: 'first', + type: { + name: 'union', + alternatives: [ + { + name: 'selection', + lookup: [ + { name: 'one', value: 1 }, + { name: 'two', value: 2 }, + ] + }, + { + name: 'url' + } + ] + } + } + ], + returnType: 'json', + exec: function(args, context) { + return args; + } } ]; diff --git a/toolkit/devtools/gcli/commands/inject.js b/toolkit/devtools/gcli/commands/inject.js index 20a49ad49400..5f79b7b46b09 100644 --- a/toolkit/devtools/gcli/commands/inject.js +++ b/toolkit/devtools/gcli/commands/inject.js @@ -14,10 +14,10 @@ exports.items = [ description: gcli.lookup("injectDesc"), manual: gcli.lookup("injectManual2"), params: [{ - name: 'library', + name: "library", type: { name: "union", - types: [ + alternatives: [ { name: "selection", lookup: [ @@ -45,7 +45,7 @@ exports.items = [ ] }, { - name: "string" + name: "url" } ] }, @@ -55,9 +55,13 @@ exports.items = [ let document = context.environment.document; let library = args.library; let name = (library.type === "selection") ? - library.selection.name : library.string; + library.selection.name : library.url; let src = (library.type === "selection") ? - library.selection.src : library.string; + library.selection.src : library.url; + + if (context.environment.window.location.protocol == "https:") { + src = src.replace(/^http:/, "https:"); + } try { // Check if URI is valid diff --git a/toolkit/devtools/gcli/source/lib/gcli/commands/test.js b/toolkit/devtools/gcli/source/lib/gcli/commands/test.js index a2442ea5aa53..446514a154eb 100644 --- a/toolkit/devtools/gcli/source/lib/gcli/commands/test.js +++ b/toolkit/devtools/gcli/source/lib/gcli/commands/test.js @@ -21,6 +21,7 @@ require('../test/suite'); var examiner = require('../testharness/examiner'); var stati = require('../testharness/status').stati; var helpers = require('../test/helpers'); +var cli = require('../cli'); var Requisition = require('../cli').Requisition; var createRequisitionAutomator = require('../test/automators/requisition').createRequisitionAutomator; @@ -85,7 +86,15 @@ exports.items = [ return args.suite.run(options).then(function() { requisition.canon.getCommand('mocks').off(requisition); - return examiner.toRemote(); + var output = context.typedData('examiner-output', examiner.toRemote()); + + if (output.data.summary.status === stati.pass) { + return output; + } + else { + cli.logErrors = false; + throw output; + } }); } }, @@ -94,15 +103,8 @@ exports.items = [ from: 'examiner-output', to: 'string', exec: function(output, conversionContext) { - var reply = '\n' + examiner.detailedResultLog('NodeJS/NoDom') + - '\n' + helpers.timingSummary; - - if (output.summary.status === stati.pass) { - return reply; - } - else { - throw reply; - } + return '\n' + examiner.detailedResultLog('NodeJS/NoDom') + + '\n' + helpers.timingSummary; } }, { diff --git a/toolkit/devtools/gcli/source/lib/gcli/connectors/index.js b/toolkit/devtools/gcli/source/lib/gcli/connectors/index.js index bbe48bd9b4f7..a9788bf8d289 100644 --- a/toolkit/devtools/gcli/source/lib/gcli/connectors/index.js +++ b/toolkit/devtools/gcli/source/lib/gcli/connectors/index.js @@ -54,6 +54,7 @@ var items = [ require('../types/setting').items, require('../types/string').items, require('../types/union').items, + require('../types/url').items, require('../fields/delegate').items, require('../fields/selection').items, diff --git a/toolkit/devtools/gcli/source/lib/gcli/fields/selection.js b/toolkit/devtools/gcli/source/lib/gcli/fields/selection.js index a314203881a4..1aa45f6e53d2 100644 --- a/toolkit/devtools/gcli/source/lib/gcli/fields/selection.js +++ b/toolkit/devtools/gcli/source/lib/gcli/fields/selection.js @@ -49,7 +49,7 @@ SelectionField.claim = function(type, context) { if (context == null) { return Field.NO_MATCH; } - return type.getType(context).isSelection ? Field.MATCH : Field.NO_MATCH; + return type.getType(context).hasPredictions ? Field.DEFAULT : Field.NO_MATCH; }; SelectionField.prototype.destroy = function() { diff --git a/toolkit/devtools/gcli/source/lib/gcli/index.js b/toolkit/devtools/gcli/source/lib/gcli/index.js index e6483f37a9b0..6e19548bc6ec 100644 --- a/toolkit/devtools/gcli/source/lib/gcli/index.js +++ b/toolkit/devtools/gcli/source/lib/gcli/index.js @@ -49,6 +49,7 @@ var items = [ require('./types/setting').items, require('./types/string').items, require('./types/union').items, + require('./types/url').items, require('./fields/delegate').items, require('./fields/selection').items, diff --git a/toolkit/devtools/gcli/source/lib/gcli/types/delegate.js b/toolkit/devtools/gcli/source/lib/gcli/types/delegate.js index 2ea7b880ba7e..50597f6b6cae 100644 --- a/toolkit/devtools/gcli/source/lib/gcli/types/delegate.js +++ b/toolkit/devtools/gcli/source/lib/gcli/types/delegate.js @@ -39,7 +39,7 @@ exports.items = [ getSpec: function(commandName, paramName) { return { - name: 'remote', + name: 'delegate', param: paramName }; }, @@ -142,6 +142,10 @@ exports.items = [ item: 'type', name: 'blank', + getSpec: function(commandName, paramName) { + return 'blank'; + }, + stringify: function(value, context) { return ''; }, diff --git a/toolkit/devtools/gcli/source/lib/gcli/types/file.js b/toolkit/devtools/gcli/source/lib/gcli/types/file.js index c608fe91c3ec..e150da643b36 100644 --- a/toolkit/devtools/gcli/source/lib/gcli/types/file.js +++ b/toolkit/devtools/gcli/source/lib/gcli/types/file.js @@ -49,7 +49,7 @@ exports.items = [ existing: 'maybe', // Should be one of 'yes', 'no', 'maybe' matches: undefined, // RegExp to match the file part of the path - isSelection: true, // It's not really a selection, but acts like one + hasPredictions: true, constructor: function() { if (this.filetype !== 'any' && this.filetype !== 'file' && diff --git a/toolkit/devtools/gcli/source/lib/gcli/types/selection.js b/toolkit/devtools/gcli/source/lib/gcli/types/selection.js index cd5873828296..5ec3d38fd887 100644 --- a/toolkit/devtools/gcli/source/lib/gcli/types/selection.js +++ b/toolkit/devtools/gcli/source/lib/gcli/types/selection.js @@ -381,10 +381,10 @@ SelectionType.prototype._findValue = function(lookup, value) { }; /** - * SelectionType is designed to be inherited from, so SelectionField needs a way - * to check if something works like a selection without using 'name' + * This is how we indicate to SelectionField that we have predictions that + * might work in a menu. */ -SelectionType.prototype.isSelection = true; +SelectionType.prototype.hasPredictions = true; SelectionType.prototype.name = 'selection'; diff --git a/toolkit/devtools/gcli/source/lib/gcli/types/types.js b/toolkit/devtools/gcli/source/lib/gcli/types/types.js index 4f833bebfe78..ee8a093c1b2f 100644 --- a/toolkit/devtools/gcli/source/lib/gcli/types/types.js +++ b/toolkit/devtools/gcli/source/lib/gcli/types/types.js @@ -1140,13 +1140,8 @@ Types.prototype.createType = function(typeSpec) { // Copy the properties of typeSpec onto the new type util.copyProperties(typeSpec, newType); - // Delegate and Array types need special powers to create types. Injecting - // ourselves at this point seems nasty, but better than the alternative of - // forcing all children of delegate types to require the full types API, and - // not know where to get it from - if (newType.name === 'delegate' || newType.name === 'array') { - newType.types = this; - } + // Several types need special powers to create child types + newType.types = this; if (typeof NewTypeCtor !== 'function') { if (typeof newType.constructor === 'function') { diff --git a/toolkit/devtools/gcli/source/lib/gcli/types/union.js b/toolkit/devtools/gcli/source/lib/gcli/types/union.js index ee5d766f6478..34922d1ae86c 100644 --- a/toolkit/devtools/gcli/source/lib/gcli/types/union.js +++ b/toolkit/devtools/gcli/source/lib/gcli/types/union.js @@ -18,79 +18,101 @@ var Promise = require('../util/promise').Promise; var l10n = require('../util/l10n'); -var centralTypes = require("./types").centralTypes; -var Conversion = require("./types").Conversion; -var Status = require("./types").Status; +var Conversion = require('./types').Conversion; +var Status = require('./types').Status; exports.items = [ { // The union type allows for a combination of different parameter types. - item: "type", - name: "union", + item: 'type', + name: 'union', + hasPredictions: true, constructor: function() { - // Get the properties of the type. The last object in the list of types - // should always be a string type. - this.types = this.types.map(function(typeData) { - typeData.type = centralTypes.createType(typeData); - typeData.lookup = typeData.lookup; - return typeData; - }); + // Get the properties of the type. Later types in the list should always + // be more general, so 'catch all' types like string must be last + this.alternatives = this.alternatives.map(function(typeData) { + return this.types.createType(typeData); + }.bind(this)); + }, + + getSpec: function(command, param) { + var spec = { name: 'union', alternatives: [] }; + this.alternatives.forEach(function(type) { + spec.alternatives.push(type.getSpec(command, param)); + }.bind(this)); + return spec; }, stringify: function(value, context) { if (value == null) { - return ""; + return ''; } - var type = this.types.find(function(typeData) { - return typeData.name == value.type; - }).type; + var type = this.alternatives.find(function(typeData) { + return typeData.name === value.type; + }); return type.stringify(value[value.type], context); }, parse: function(arg, context) { - // Try to parse the given argument in the provided order of the parameter - // types. Returns a promise containing the Conversion of the value that - // was parsed. - var self = this; + var conversionPromises = this.alternatives.map(function(type) { + return type.parse(arg, context); + }.bind(this)); - var onError = function(i) { - if (i >= self.types.length) { - return Promise.reject(new Conversion(undefined, arg, Status.ERROR, - l10n.lookup("commandParseError"))); - } else { - return tryNext(i + 1); - } - }; + return Promise.all(conversionPromises).then(function(conversions) { + // Find a list of the predictions made by any conversion + var predictionPromises = conversions.map(function(conversion) { + return conversion.getPredictions(context); + }.bind(this)); - var tryNext = function(i) { - var type = self.types[i].type; + return Promise.all(predictionPromises).then(function(allPredictions) { + // Take one prediction from each set of predictions, ignoring + // duplicates, until we've got up to Conversion.maxPredictions + var maxIndex = allPredictions.reduce(function(prev, prediction) { + return Math.max(prev, prediction.length); + }.bind(this), 0); + var predictions = []; - try { - return type.parse(arg, context).then(function(conversion) { - if (conversion.getStatus() === Status.VALID || - conversion.getStatus() === Status.INCOMPLETE) { - // Converts the conversion value of the union type to an - // object that identifies the current working type and the - // data associated with it - if (conversion.value) { - var oldConversionValue = conversion.value; - conversion.value = { type: type.name }; - conversion.value[type.name] = oldConversionValue; + indexLoop: + for (var index = 0; index < maxIndex; index++) { + for (var p = 0; p <= allPredictions.length; p++) { + if (predictions.length >= Conversion.maxPredictions) { + break indexLoop; } - return conversion; - } else { - return onError(i); - } - }); - } catch(e) { - return onError(i); - } - }; - return Promise.resolve(tryNext(0)); + if (allPredictions[p] != null) { + var prediction = allPredictions[p][index]; + if (prediction != null && predictions.indexOf(prediction) === -1) { + predictions.push(prediction); + } + } + } + } + + var bestStatus = Status.ERROR; + var value; + for (var i = 0; i < conversions.length; i++) { + var conversion = conversions[i]; + var thisStatus = conversion.getStatus(arg); + if (thisStatus < bestStatus) { + bestStatus = thisStatus; + } + if (bestStatus === Status.VALID) { + var type = this.alternatives[i].name; + value = { type: type }; + value[type] = conversion.value; + break; + } + } + + var msg = (bestStatus === Status.VALID) ? + '' : + l10n.lookupFormat('typesSelectionNomatch', [ arg.text ]); + return new Conversion(value, arg, bestStatus, msg, predictions); + }.bind(this)); + }.bind(this)); }, } ]; diff --git a/toolkit/devtools/gcli/source/lib/gcli/types/url.js b/toolkit/devtools/gcli/source/lib/gcli/types/url.js new file mode 100644 index 000000000000..8baf743138c7 --- /dev/null +++ b/toolkit/devtools/gcli/source/lib/gcli/types/url.js @@ -0,0 +1,87 @@ +/* + * 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. + */ + +'use strict'; + +var host = require('../util/host'); +var Promise = require('../util/promise').Promise; +var Status = require('./types').Status; +var Conversion = require('./types').Conversion; + +exports.items = [ + { + item: 'type', + name: 'url', + + getSpec: function() { + return 'url'; + }, + + stringify: function(value, context) { + if (value == null) { + return ''; + } + return value.href; + }, + + parse: function(arg, context) { + var conversion; + + try { + var url = host.createUrl(arg.text); + conversion = new Conversion(url, arg); + } + catch (ex) { + var predictions = []; + var status = Status.ERROR; + + // Maybe the URL was missing a scheme? + if (arg.text.indexOf('://') === -1) { + [ 'http', 'https' ].forEach(function(scheme) { + try { + var http = host.createUrl(scheme + '://' + arg.text); + predictions.push({ name: http.href, value: http }); + } + catch (ex) { + // Ignore + } + }.bind(this)); + + // Try to create a URL with the current page as a base ref + if (context.environment.window) { + try { + var base = context.environment.window.location.href; + var localized = host.createUrl(arg.text, base); + predictions.push({ name: localized.href, value: localized }); + } + catch (ex) { + // Ignore + } + } + } + + if (predictions.length > 0) { + status = Status.INCOMPLETE; + } + + conversion = new Conversion(undefined, arg, status, + ex.message, predictions); + } + + return Promise.resolve(conversion); + } + } +]; diff --git a/toolkit/devtools/gcli/source/lib/gcli/util/host.js b/toolkit/devtools/gcli/source/lib/gcli/util/host.js index 349f67a9b5dc..ed092e567998 100644 --- a/toolkit/devtools/gcli/source/lib/gcli/util/host.js +++ b/toolkit/devtools/gcli/source/lib/gcli/util/host.js @@ -18,6 +18,7 @@ var Cc = require('chrome').Cc; var Ci = require('chrome').Ci; +var URL = require("sdk/url").URL; var Task = require('resource://gre/modules/Task.jsm').Task; @@ -71,6 +72,13 @@ exports.exec = function(task) { return Task.spawn(task); }; +/** + * The URL API is new enough that we need specific platform help + */ +exports.createUrl = function(uristr, base) { + return URL(uristr, base); +}; + /** * Load some HTML into the given document and return a DOM element. * This utility assumes that the html has a single root (other than whitespace) diff --git a/toolkit/devtools/gcli/source/lib/gcli/util/legacy.js b/toolkit/devtools/gcli/source/lib/gcli/util/legacy.js index a55395455a3a..07b0fd71aa28 100644 --- a/toolkit/devtools/gcli/source/lib/gcli/util/legacy.js +++ b/toolkit/devtools/gcli/source/lib/gcli/util/legacy.js @@ -75,6 +75,40 @@ if (typeof document !== 'undefined' && typeof HTMLElement !== 'undefined' && }); } +/** + * Array.find + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find + */ +if (!Array.prototype.find) { + Object.defineProperty(Array.prototype, 'find', { + enumerable: false, + configurable: true, + writable: true, + value: function(predicate) { + if (this == null) { + throw new TypeError('Array.prototype.find called on null or undefined'); + } + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } + var list = Object(this); + var length = list.length >>> 0; + var thisArg = arguments[1]; + var value; + + for (var i = 0; i < length; i++) { + if (i in list) { + value = list[i]; + if (predicate.call(thisArg, value, i, list)) { + return value; + } + } + } + return undefined; + } + }); +} + /** * String.prototype.trimLeft is non-standard, but it works in Firefox, * Chrome and Opera. It's easiest to create a shim here.