selenium-webdriver 3.4.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.md +29 -1
- package/NOTICE +1 -1
- package/README.md +1 -4
- package/chrome.js +78 -36
- package/edge.js +2 -2
- package/example/chrome_headless.js +47 -0
- package/example/chrome_mobile_emulation.js +1 -1
- package/firefox/extension.js +118 -81
- package/firefox/index.js +10 -106
- package/firefox/profile.js +13 -49
- package/ie.js +2 -2
- package/index.js +4 -1
- package/io/index.js +59 -0
- package/io/zip.js +214 -0
- package/lib/atoms/getAttribute.js +8 -11
- package/lib/atoms/is-displayed.js +73 -78
- package/lib/capabilities.js +1 -1
- package/lib/error.js +13 -1
- package/lib/http.js +1 -1
- package/lib/test/data/firefox/webextension.xpi +0 -0
- package/lib/test/data/key_logger.html +34 -0
- package/lib/test/data/single_text_input.html +12 -0
- package/lib/test/index.js +1 -34
- package/lib/webdriver.js +3 -12
- package/opera.js +1 -1
- package/package.json +2 -2
- package/phantomjs.js +2 -2
- package/remote/index.js +11 -10
- package/safari.js +2 -2
- package/test/firefox/extension_test.js +41 -17
- package/test/firefox/firefox_test.js +44 -9
- package/test/firefox/profile_test.js +14 -59
- package/test/http/http_test.js +18 -0
- package/test/{io_test.js → io/io_test.js} +40 -1
- package/test/io/zip_test.js +128 -0
- package/test/lib/error_test.js +20 -1
- package/test/lib/webdriver_test.js +5 -3
- package/test/page_loading_test.js +0 -6
- package/test/remote_test.js +3 -3
- package/test/session_test.js +68 -23
- package/lib/firefox/webdriver.xpi +0 -0
package/CHANGES.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
## v3.5.0
|
|
2
|
+
|
|
3
|
+
### Notice
|
|
4
|
+
|
|
5
|
+
Native support for Firefox 45 (ESR) has been removed. Users will have to connect
|
|
6
|
+
to a remote Selenium server that supports Firefox 45.
|
|
7
|
+
|
|
8
|
+
### Changes
|
|
9
|
+
|
|
10
|
+
* Removed native support for Firefox 46 and older.
|
|
11
|
+
- The `SELENIUM_MARIONETTE` enviornment variable no longer has an effect.
|
|
12
|
+
- `selenium-webdriver/firefox.Capability.MARIONETTE` is deprecated.
|
|
13
|
+
- `selenium-webdriver/firefox.Options#useGeckoDriver()` is deprecated and now a no-op.
|
|
14
|
+
* `firefox.Options` will no longer discard the `"moz:firefoxOptions"` set in
|
|
15
|
+
user provided capabilities (via `Builder.withCapabilities({})`). When both
|
|
16
|
+
are used, the settings in `firefox.Options` will be applied _last_.
|
|
17
|
+
* Added `chrome.Options#headless()` and `chrome.Options#windowSize()`, which
|
|
18
|
+
may be used to start Chrome in headless mode (requires Chrome 59+) and to set
|
|
19
|
+
the initial window size, respectively.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Changes for W3C WebDriver Spec Compliance
|
|
23
|
+
|
|
24
|
+
* Added `error.WebDriverError#remoteStacktrace` to capture the stacktrace
|
|
25
|
+
reported by a remote WebDriver endpoint (if any).
|
|
26
|
+
* Fixed `WebElement#sendKeys` to send text as a string instead of an array of
|
|
27
|
+
strings.
|
|
28
|
+
|
|
1
29
|
## v3.4.0
|
|
2
30
|
|
|
3
31
|
### Notice
|
|
@@ -17,7 +45,7 @@ This release requires [geckodriver 0.15.0](https://github.com/mozilla/geckodrive
|
|
|
17
45
|
|
|
18
46
|
* Fixed W3C response parsing, which expects response data to always be a JSON
|
|
19
47
|
object with a `value` key.
|
|
20
|
-
* Added W3C endpoints for interacting with various types of
|
|
48
|
+
* Added W3C endpoints for interacting with various types of
|
|
21
49
|
[user prompts](https://w3c.github.io/webdriver/webdriver-spec.html#user-prompts).
|
|
22
50
|
* Added W3C endpoints for remotely executing scripts.
|
|
23
51
|
* Added W3C endpoints to get current window handle and all windows handles.
|
package/NOTICE
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Copyright 2011-
|
|
1
|
+
Copyright 2011-2017 Software Freedom Conservancy
|
|
2
2
|
Copyright 2004-2011 Selenium committers
|
package/README.md
CHANGED
|
@@ -17,16 +17,13 @@ be placed on your system [PATH]. Apple's safaridriver is shipped with
|
|
|
17
17
|
Safari 10 for OS X El Capitan and macOS Sierra. You will need to enable Remote
|
|
18
18
|
Automation in the Develop menu of Safari 10 before testing.
|
|
19
19
|
|
|
20
|
-
> **NOTE:** Mozilla's [geckodriver] is only required for Firefox 47+.
|
|
21
|
-
> Everything you need for Firefox 38-46 is included with this package.
|
|
22
|
-
|
|
23
20
|
|
|
24
21
|
| Browser | Component |
|
|
25
22
|
| ----------------- | ---------------------------------- |
|
|
26
23
|
| Chrome | [chromedriver(.exe)][chrome] |
|
|
27
24
|
| Internet Explorer | [IEDriverServer.exe][release] |
|
|
28
25
|
| Edge | [MicrosoftWebDriver.msi][edge] |
|
|
29
|
-
| Firefox
|
|
26
|
+
| Firefox | [geckodriver(.exe)][geckodriver] |
|
|
30
27
|
| PhantomJS | [phantomjs(.exe)][phantomjs] |
|
|
31
28
|
| Opera | [operadriver(.exe)][opera] |
|
|
32
29
|
| Safari | [safaridriver] |
|
package/chrome.js
CHANGED
|
@@ -36,6 +36,20 @@
|
|
|
36
36
|
* a unique browser session with a clean user profile (unless otherwise
|
|
37
37
|
* configured through the {@link Options} class).
|
|
38
38
|
*
|
|
39
|
+
* __Headless Chrome__ <a id="headless"></a>
|
|
40
|
+
*
|
|
41
|
+
* To start Chrome in headless mode, simply call
|
|
42
|
+
* {@linkplain Options#headless Options.headless()}. Note, starting in headless
|
|
43
|
+
* mode currently also disables GPU acceleration.
|
|
44
|
+
*
|
|
45
|
+
* let chrome = require('selenium-webdriver/chrome');
|
|
46
|
+
* let {Builder} = require('selenium-webdriver');
|
|
47
|
+
*
|
|
48
|
+
* let driver = new Builder()
|
|
49
|
+
* .forBrowser('chrome')
|
|
50
|
+
* .setChromeOptions(new chrome.Options().headless())
|
|
51
|
+
* .build();
|
|
52
|
+
*
|
|
39
53
|
* __Customizing the ChromeDriver Server__ <a id="custom-server"></a>
|
|
40
54
|
*
|
|
41
55
|
* By default, every Chrome session will use a single driver service, which is
|
|
@@ -114,19 +128,19 @@
|
|
|
114
128
|
|
|
115
129
|
'use strict';
|
|
116
130
|
|
|
117
|
-
const fs = require('fs')
|
|
118
|
-
|
|
131
|
+
const fs = require('fs');
|
|
132
|
+
const util = require('util');
|
|
119
133
|
|
|
120
|
-
const http = require('./http')
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
134
|
+
const http = require('./http');
|
|
135
|
+
const io = require('./io');
|
|
136
|
+
const {Capabilities, Capability} = require('./lib/capabilities');
|
|
137
|
+
const command = require('./lib/command');
|
|
138
|
+
const logging = require('./lib/logging');
|
|
139
|
+
const promise = require('./lib/promise');
|
|
140
|
+
const Symbols = require('./lib/symbols');
|
|
141
|
+
const webdriver = require('./lib/webdriver');
|
|
142
|
+
const portprober = require('./net/portprober');
|
|
143
|
+
const remote = require('./remote');
|
|
130
144
|
|
|
131
145
|
|
|
132
146
|
/**
|
|
@@ -293,11 +307,7 @@ function getDefaultService() {
|
|
|
293
307
|
}
|
|
294
308
|
|
|
295
309
|
|
|
296
|
-
|
|
297
|
-
* @type {string}
|
|
298
|
-
* @const
|
|
299
|
-
*/
|
|
300
|
-
let OPTIONS_CAPABILITY_KEY = 'chromeOptions';
|
|
310
|
+
const OPTIONS_CAPABILITY_KEY = 'chromeOptions';
|
|
301
311
|
|
|
302
312
|
|
|
303
313
|
/**
|
|
@@ -362,28 +372,61 @@ class Options {
|
|
|
362
372
|
* browser. Each argument may be specified with or without the "--" prefix
|
|
363
373
|
* (e.g. "--foo" and "foo"). Arguments with an associated value should be
|
|
364
374
|
* delimited by an "=": "foo=bar".
|
|
365
|
-
*
|
|
375
|
+
*
|
|
376
|
+
* @param {...(string|!Array<string>)} args The arguments to add.
|
|
366
377
|
* @return {!Options} A self reference.
|
|
367
378
|
*/
|
|
368
|
-
addArguments(
|
|
369
|
-
let
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
this.options_.args = args;
|
|
379
|
+
addArguments(...args) {
|
|
380
|
+
let newArgs = (this.options_.args || []).concat(...args);
|
|
381
|
+
if (newArgs.length) {
|
|
382
|
+
this.options_.args = newArgs;
|
|
373
383
|
}
|
|
374
384
|
return this;
|
|
375
385
|
}
|
|
376
386
|
|
|
387
|
+
/**
|
|
388
|
+
* Configures the chromedriver to start Chrome in headless mode.
|
|
389
|
+
*
|
|
390
|
+
* > __NOTE:__ Resizing the browser window in headless mode is only supported
|
|
391
|
+
* > in Chrome 60. Users are encouraged to set an initial window size with
|
|
392
|
+
* > the {@link #windowSize windowSize({width, height})} option.
|
|
393
|
+
*
|
|
394
|
+
* @return {!Options} A self reference.
|
|
395
|
+
*/
|
|
396
|
+
headless() {
|
|
397
|
+
// TODO(jleyba): Remove `disable-gpu` once head Chrome no longer requires
|
|
398
|
+
// that to be set.
|
|
399
|
+
return this.addArguments('headless', 'disable-gpu');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Sets the initial window size.
|
|
404
|
+
*
|
|
405
|
+
* @param {{width: number, height: number}} size The desired window size.
|
|
406
|
+
* @return {!Options} A self reference.
|
|
407
|
+
* @throws {TypeError} if width or height is unspecified, not a number, or
|
|
408
|
+
* less than or equal to 0.
|
|
409
|
+
*/
|
|
410
|
+
windowSize({width, height}) {
|
|
411
|
+
function checkArg(arg) {
|
|
412
|
+
if (typeof arg !== 'number' || arg <= 0) {
|
|
413
|
+
throw TypeError('Arguments must be {width, height} with numbers > 0');
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
checkArg(width);
|
|
417
|
+
checkArg(height);
|
|
418
|
+
return this.addArguments(`window-size=${width},${height}`);
|
|
419
|
+
}
|
|
420
|
+
|
|
377
421
|
/**
|
|
378
422
|
* List of Chrome command line switches to exclude that ChromeDriver by default
|
|
379
423
|
* passes when starting Chrome. Do not prefix switches with "--".
|
|
380
424
|
*
|
|
381
|
-
* @param {...(string|!Array<string>)}
|
|
425
|
+
* @param {...(string|!Array<string>)} args The switches to exclude.
|
|
382
426
|
* @return {!Options} A self reference.
|
|
383
427
|
*/
|
|
384
|
-
excludeSwitches(
|
|
385
|
-
let switches = this.options_.excludeSwitches || [];
|
|
386
|
-
switches = switches.concat.apply(switches, arguments);
|
|
428
|
+
excludeSwitches(...args) {
|
|
429
|
+
let switches = (this.options_.excludeSwitches || []).concat(...args);
|
|
387
430
|
if (switches.length) {
|
|
388
431
|
this.options_.excludeSwitches = switches;
|
|
389
432
|
}
|
|
@@ -394,13 +437,12 @@ class Options {
|
|
|
394
437
|
* Add additional extensions to install when launching Chrome. Each extension
|
|
395
438
|
* should be specified as the path to the packed CRX file, or a Buffer for an
|
|
396
439
|
* extension.
|
|
397
|
-
* @param {...(string|!Buffer|!Array<(string|!Buffer)>)}
|
|
440
|
+
* @param {...(string|!Buffer|!Array<(string|!Buffer)>)} args The
|
|
398
441
|
* extensions to add.
|
|
399
442
|
* @return {!Options} A self reference.
|
|
400
443
|
*/
|
|
401
|
-
addExtensions(
|
|
402
|
-
this.extensions_ =
|
|
403
|
-
this.extensions_.concat.apply(this.extensions_, arguments);
|
|
444
|
+
addExtensions(...args) {
|
|
445
|
+
this.extensions_ = this.extensions_.concat(...args);
|
|
404
446
|
return this;
|
|
405
447
|
}
|
|
406
448
|
|
|
@@ -716,7 +758,7 @@ class Driver extends webdriver.WebDriver {
|
|
|
716
758
|
(opt_config || Capabilities.chrome());
|
|
717
759
|
|
|
718
760
|
return /** @type {!Driver} */(
|
|
719
|
-
|
|
761
|
+
super.createSession(executor, caps, opt_flow));
|
|
720
762
|
}
|
|
721
763
|
|
|
722
764
|
/**
|
|
@@ -737,7 +779,7 @@ class Driver extends webdriver.WebDriver {
|
|
|
737
779
|
new command.Command(Command.LAUNCH_APP).setParameter('id', id),
|
|
738
780
|
'Driver.launchApp()');
|
|
739
781
|
}
|
|
740
|
-
|
|
782
|
+
|
|
741
783
|
/**
|
|
742
784
|
* Schedules a command to get Chrome network emulation settings.
|
|
743
785
|
* @return {!promise.Thenable<T>} A promise that will be resolved
|
|
@@ -751,16 +793,16 @@ class Driver extends webdriver.WebDriver {
|
|
|
751
793
|
|
|
752
794
|
/**
|
|
753
795
|
* Schedules a command to set Chrome network emulation settings.
|
|
754
|
-
*
|
|
796
|
+
*
|
|
755
797
|
* __Sample Usage:__
|
|
756
|
-
*
|
|
798
|
+
*
|
|
757
799
|
* driver.setNetworkConditions({
|
|
758
800
|
* offline: false,
|
|
759
801
|
* latency: 5, // Additional latency (ms).
|
|
760
802
|
* download_throughput: 500 * 1024, // Maximal aggregated download throughput.
|
|
761
803
|
* upload_throughput: 500 * 1024 // Maximal aggregated upload throughput.
|
|
762
804
|
* });
|
|
763
|
-
*
|
|
805
|
+
*
|
|
764
806
|
* @param {Object} spec Defines the network conditions to set
|
|
765
807
|
* @return {!promise.Thenable<void>} A promise that will be resolved
|
|
766
808
|
* when network emulation settings are set.
|
package/edge.js
CHANGED
|
@@ -278,8 +278,8 @@ class Driver extends webdriver.WebDriver {
|
|
|
278
278
|
opt_config instanceof Options ? opt_config.toCapabilities() :
|
|
279
279
|
(opt_config || capabilities.Capabilities.edge());
|
|
280
280
|
|
|
281
|
-
return /** @type {!Driver} */(
|
|
282
|
-
executor, caps, opt_flow,
|
|
281
|
+
return /** @type {!Driver} */(super.createSession(
|
|
282
|
+
executor, caps, opt_flow, () => service.kill()));
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
/**
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Licensed to the Software Freedom Conservancy (SFC) under one
|
|
2
|
+
// or more contributor license agreements. See the NOTICE file
|
|
3
|
+
// distributed with this work for additional information
|
|
4
|
+
// regarding copyright ownership. The SFC licenses this file
|
|
5
|
+
// to you under the Apache License, Version 2.0 (the
|
|
6
|
+
// "License"); you may not use this file except in compliance
|
|
7
|
+
// with the License. You may obtain a copy of the License at
|
|
8
|
+
//
|
|
9
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
//
|
|
11
|
+
// Unless required by applicable law or agreed to in writing,
|
|
12
|
+
// software distributed under the License is distributed on an
|
|
13
|
+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
// KIND, either express or implied. See the License for the
|
|
15
|
+
// specific language governing permissions and limitations
|
|
16
|
+
// under the License.
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @fileoverview An example of running Chrome in headless mode.
|
|
20
|
+
*
|
|
21
|
+
* Before running this script, ensure you have Chrome 59+ installed and that
|
|
22
|
+
* chromedriver 2.30+ is present on your system PATH:
|
|
23
|
+
* <https://sites.google.com/a/chromium.org/chromedriver/downloads>
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* node selenium-webdriver/example/chrome_headless.js
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const chrome = require('../chrome');
|
|
30
|
+
const {Builder, By, until} = require('..');
|
|
31
|
+
|
|
32
|
+
const width = 640;
|
|
33
|
+
const height = 480;
|
|
34
|
+
|
|
35
|
+
let driver = new Builder()
|
|
36
|
+
.forBrowser('chrome')
|
|
37
|
+
.setChromeOptions(
|
|
38
|
+
new chrome.Options().headless().windowSize({width, height}))
|
|
39
|
+
.build();
|
|
40
|
+
|
|
41
|
+
driver.get('http://www.google.com/ncr')
|
|
42
|
+
.then(_ => driver.findElement(By.name('q')).sendKeys('webdriver'))
|
|
43
|
+
.then(_ => driver.findElement(By.name('btnG')).click())
|
|
44
|
+
.then(_ => driver.wait(until.titleIs('webdriver - Google Search'), 1000))
|
|
45
|
+
.then(
|
|
46
|
+
_ => driver.quit(),
|
|
47
|
+
e => driver.quit().then(() => { throw e; }));
|
|
@@ -31,7 +31,7 @@ promise.consume(function* () {
|
|
|
31
31
|
driver = yield new Builder()
|
|
32
32
|
.forBrowser('chrome')
|
|
33
33
|
.setChromeOptions(
|
|
34
|
-
new Options().setMobileEmulation({deviceName: '
|
|
34
|
+
new Options().setMobileEmulation({deviceName: 'Nexus 5X'}))
|
|
35
35
|
.build();
|
|
36
36
|
yield driver.get('http://www.google.com/ncr');
|
|
37
37
|
yield driver.findElement(By.name('q')).sendKeys('webdriver');
|
package/firefox/extension.js
CHANGED
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
|
|
20
20
|
'use strict';
|
|
21
21
|
|
|
22
|
-
const
|
|
23
|
-
fs = require('fs'),
|
|
22
|
+
const fs = require('fs'),
|
|
24
23
|
path = require('path'),
|
|
25
24
|
xml = require('xml2js');
|
|
26
25
|
|
|
27
26
|
const io = require('../io');
|
|
27
|
+
const zip = require('../io/zip');
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
@@ -56,11 +56,7 @@ function install(extension, dir) {
|
|
|
56
56
|
if (!details.unpack) {
|
|
57
57
|
return io.copy(extension, dst + '.xpi').then(() => details.id);
|
|
58
58
|
} else {
|
|
59
|
-
return
|
|
60
|
-
// TODO: find an async library for inflating a zip archive.
|
|
61
|
-
new AdmZip(extension).extractAllTo(dst, true);
|
|
62
|
-
return details.id;
|
|
63
|
-
});
|
|
59
|
+
return zip.unzip(extension, dst).then(() => details.id);
|
|
64
60
|
}
|
|
65
61
|
} else {
|
|
66
62
|
return io.copyDir(extension, dst).then(() => details.id);
|
|
@@ -79,105 +75,146 @@ var AddonDetails;
|
|
|
79
75
|
var RdfRoot;
|
|
80
76
|
|
|
81
77
|
|
|
82
|
-
|
|
83
78
|
/**
|
|
84
79
|
* Extracts the details needed to install an add-on.
|
|
85
80
|
* @param {string} addonPath Path to the extension directory.
|
|
86
81
|
* @return {!Promise<!AddonDetails>} A promise for the add-on details.
|
|
87
82
|
*/
|
|
88
83
|
function getDetails(addonPath) {
|
|
89
|
-
return
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
id: getNodeText(description, em + 'id'),
|
|
97
|
-
name: getNodeText(description, em + 'name'),
|
|
98
|
-
version: getNodeText(description, em + 'version'),
|
|
99
|
-
unpack: getNodeText(description, em + 'unpack') || false
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
if (typeof details.unpack === 'string') {
|
|
103
|
-
details.unpack = details.unpack.toLowerCase() === 'true';
|
|
84
|
+
return io.stat(addonPath).then((stats) => {
|
|
85
|
+
if (stats.isDirectory()) {
|
|
86
|
+
return parseDirectory(addonPath);
|
|
87
|
+
} else if (addonPath.slice(-4) === '.xpi') {
|
|
88
|
+
return parseXpiFile(addonPath);
|
|
89
|
+
} else {
|
|
90
|
+
throw Error('Add-on path is not an xpi or a directory: ' + addonPath);
|
|
104
91
|
}
|
|
92
|
+
});
|
|
105
93
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Parse an install.rdf for a Firefox add-on.
|
|
96
|
+
* @param {string} rdf The contents of install.rdf for the add-on.
|
|
97
|
+
* @return {!Promise<!AddonDetails>} A promise for the add-on details.
|
|
98
|
+
*/
|
|
99
|
+
function parseInstallRdf(rdf) {
|
|
100
|
+
return parseXml(rdf).then(function(doc) {
|
|
101
|
+
var em = getNamespaceId(doc, 'http://www.mozilla.org/2004/em-rdf#');
|
|
102
|
+
var rdf = getNamespaceId(
|
|
103
|
+
doc, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
|
|
104
|
+
|
|
105
|
+
var description = doc[rdf + 'RDF'][rdf + 'Description'][0];
|
|
106
|
+
var details = {
|
|
107
|
+
id: getNodeText(description, em + 'id'),
|
|
108
|
+
name: getNodeText(description, em + 'name'),
|
|
109
|
+
version: getNodeText(description, em + 'version'),
|
|
110
|
+
unpack: getNodeText(description, em + 'unpack') || false
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (typeof details.unpack === 'string') {
|
|
114
|
+
details.unpack = details.unpack.toLowerCase() === 'true';
|
|
115
|
+
}
|
|
109
116
|
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
if (!details.id) {
|
|
118
|
+
throw new AddonFormatError('Could not find add-on ID for ' + addonPath);
|
|
119
|
+
}
|
|
112
120
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
121
|
+
return details;
|
|
122
|
+
});
|
|
116
123
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
124
|
+
function parseXml(text) {
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
xml.parseString(text, (err, data) => {
|
|
127
|
+
if (err) {
|
|
128
|
+
reject(err);
|
|
129
|
+
} else {
|
|
130
|
+
resolve(data);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
121
134
|
}
|
|
122
135
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (namespaces[ns] !== url) {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
136
|
+
function getNodeText(node, name) {
|
|
137
|
+
return node[name] && node[name][0] || '';
|
|
138
|
+
}
|
|
129
139
|
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
function getNamespaceId(doc, url) {
|
|
141
|
+
var keys = Object.keys(doc);
|
|
142
|
+
if (keys.length !== 1) {
|
|
143
|
+
throw new AddonFormatError('Malformed manifest for add-on ' + addonPath);
|
|
132
144
|
}
|
|
133
|
-
return true;
|
|
134
|
-
});
|
|
135
|
-
return id;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
145
|
|
|
146
|
+
var namespaces = /** @type {!RdfRoot} */(doc[keys[0]]).$;
|
|
147
|
+
var id = '';
|
|
148
|
+
Object.keys(namespaces).some(function(ns) {
|
|
149
|
+
if (namespaces[ns] !== url) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
139
152
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
153
|
+
if (ns.indexOf(':') != -1) {
|
|
154
|
+
id = ns.split(':')[1] + ':';
|
|
155
|
+
}
|
|
156
|
+
return true;
|
|
157
|
+
});
|
|
158
|
+
return id;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Parse a manifest for a Firefox WebExtension.
|
|
164
|
+
* @param {{
|
|
165
|
+
* name: string,
|
|
166
|
+
* version: string,
|
|
167
|
+
* applications: {gecko:{id:string}}
|
|
168
|
+
* }} json JSON representation of the manifest.
|
|
169
|
+
* @return {!AddonDetails} The add-on details.
|
|
170
|
+
*/
|
|
171
|
+
function parseManifestJson({name, version, applications}) {
|
|
172
|
+
if (!(applications && applications.gecko && applications.gecko.id)) {
|
|
173
|
+
throw new AddonFormatError('Could not find add-on ID for ' + addonPath);
|
|
174
|
+
}
|
|
147
175
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
let zip = new AdmZip(addonPath);
|
|
176
|
+
return {id: applications.gecko.id, name, version, unpack: false};
|
|
177
|
+
}
|
|
151
178
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return
|
|
179
|
+
function parseXpiFile(filePath) {
|
|
180
|
+
return zip.load(filePath).then(archive => {
|
|
181
|
+
if (archive.has('install.rdf')) {
|
|
182
|
+
return archive.getFile('install.rdf')
|
|
183
|
+
.then(buf => parseInstallRdf(buf.toString('utf8')));
|
|
156
184
|
}
|
|
157
185
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (!stats.isDirectory()) {
|
|
163
|
-
throw Error(
|
|
164
|
-
'Add-on path is neither a xpi nor a directory: ' + addonPath);
|
|
186
|
+
if (archive.has('manifest.json')) {
|
|
187
|
+
return archive.getFile('manifest.json')
|
|
188
|
+
.then(buf => JSON.parse(buf.toString('utf8')))
|
|
189
|
+
.then(parseManifestJson);
|
|
165
190
|
}
|
|
166
|
-
|
|
191
|
+
|
|
192
|
+
throw new AddonFormatError(
|
|
193
|
+
`Couldn't find install.rdf or manifest.json in ${filePath}`);
|
|
167
194
|
});
|
|
168
195
|
}
|
|
169
196
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
197
|
+
function parseDirectory(dirPath) {
|
|
198
|
+
const rdfPath = path.join(dirPath, 'install.rdf');
|
|
199
|
+
const jsonPath = path.join(dirPath, 'manifest.json');
|
|
200
|
+
return io.exists(rdfPath)
|
|
201
|
+
.then(rdfExists => {
|
|
202
|
+
if (rdfExists) {
|
|
203
|
+
return io.read(rdfPath)
|
|
204
|
+
.then(buf => parseInstallRdf(buf.toString('utf8')));
|
|
205
|
+
}
|
|
206
|
+
return io.exists(jsonPath)
|
|
207
|
+
.then(jsonExists => {
|
|
208
|
+
if (jsonExists) {
|
|
209
|
+
return io.read(jsonPath)
|
|
210
|
+
.then(buf => JSON.parse(buf.toString('utf8')))
|
|
211
|
+
.then(parseManifestJson);
|
|
212
|
+
}
|
|
213
|
+
throw new AddonFormatError(
|
|
214
|
+
`Couldn't find install.rdf or manifest.json in ${dirPath}`);
|
|
215
|
+
});
|
|
216
|
+
})
|
|
217
|
+
}
|
|
181
218
|
}
|
|
182
219
|
|
|
183
220
|
|