Bug 1023233 - Add getSpec to union type; f=gl, r=mratcliffe

This commit is contained in:
Joe Walker 2014-06-25 11:21:02 +01:00
parent e4acacd2dc
commit 809f332b79
19 changed files with 686 additions and 111 deletions

View File

@ -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]

View File

@ -10,36 +10,51 @@ function test() {
helpers.addTabWithToolbar(TEST_URI, function(options) {
return helpers.audit(options, [
{
setup: 'inject',
setup: 'inject',
check: {
input: 'inject',
hints: ' <library>',
markup: 'VVVVVV',
hints: ' <library>',
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);

View File

@ -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);
});
};

View File

@ -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';
// <INJECTED SOURCE:START>
// 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,<p id='gcli-input'>gcli-testUnion.js</p>";
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);
}
// <INJECTED SOURCE:END>
// 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: ' <first>',
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\'.'
},
}
}
}
]);
};

View File

@ -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';
// <INJECTED SOURCE:START>
// 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,<p id='gcli-input'>gcli-testUrl.js</p>";
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);
}
// <INJECTED SOURCE:END>
// 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: ' <url>',
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": ?/ }
}
]);
};

View File

@ -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, '&#160;').replace(/\n/g, '<br/>');
return {
html: '<pre>' + html + '</pre>'
};
}
},
{
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;
}
}
];

View File

@ -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

View File

@ -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;
}
},
{

View File

@ -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,

View File

@ -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() {

View File

@ -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,

View File

@ -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 '';
},

View File

@ -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' &&

View File

@ -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';

View File

@ -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') {

View File

@ -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));
},
}
];

View File

@ -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);
}
}
];

View File

@ -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)

View File

@ -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.