diff options
Diffstat (limited to 'node_modules/selenium-webdriver/firefox')
-rw-r--r-- | node_modules/selenium-webdriver/firefox/binary.js | 344 | ||||
-rw-r--r-- | node_modules/selenium-webdriver/firefox/extension.js | 187 | ||||
-rw-r--r-- | node_modules/selenium-webdriver/firefox/index.js | 722 | ||||
-rw-r--r-- | node_modules/selenium-webdriver/firefox/profile.js | 430 |
4 files changed, 0 insertions, 1683 deletions
diff --git a/node_modules/selenium-webdriver/firefox/binary.js b/node_modules/selenium-webdriver/firefox/binary.js deleted file mode 100644 index a1360bab7..000000000 --- a/node_modules/selenium-webdriver/firefox/binary.js +++ /dev/null @@ -1,344 +0,0 @@ -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you 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. - -/** - * @fileoverview Manages Firefox binaries. This module is considered internal; - * users should use {@link ./firefox selenium-webdriver/firefox}. - */ - -'use strict'; - -const child = require('child_process'), - fs = require('fs'), - path = require('path'), - util = require('util'); - -const isDevMode = require('../lib/devmode'), - Symbols = require('../lib/symbols'), - io = require('../io'), - exec = require('../io/exec'); - - - -/** @const */ -const NO_FOCUS_LIB_X86 = isDevMode ? - path.join(__dirname, '../../../../cpp/prebuilt/i386/libnoblur.so') : - path.join(__dirname, '../lib/firefox/i386/libnoblur.so') ; - -/** @const */ -const NO_FOCUS_LIB_AMD64 = isDevMode ? - path.join(__dirname, '../../../../cpp/prebuilt/amd64/libnoblur64.so') : - path.join(__dirname, '../lib/firefox/amd64/libnoblur64.so') ; - -const X_IGNORE_NO_FOCUS_LIB = 'x_ignore_nofocus.so'; - - -/** - * @param {string} file Path to the file to find, relative to the program files - * root. - * @return {!Promise<?string>} A promise for the located executable. - * The promise will resolve to {@code null} if Firefox was not found. - */ -function findInProgramFiles(file) { - let files = [ - process.env['PROGRAMFILES'] || 'C:\\Program Files', - process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)' - ].map(prefix => path.join(prefix, file)); - return io.exists(files[0]).then(function(exists) { - return exists ? files[0] : io.exists(files[1]).then(function(exists) { - return exists ? files[1] : null; - }); - }); -} - - -/** - * Provides methods for locating the executable for a Firefox release channel - * on Windows and MacOS. For other systems (i.e. Linux), Firefox will always - * be located on the system PATH. - * - * @final - */ -class Channel { - /** - * @param {string} darwin The path to check when running on MacOS. - * @param {string} win32 The path to check when running on Windows. - */ - constructor(darwin, win32) { - /** @private @const */ this.darwin_ = darwin; - /** @private @const */ this.win32_ = win32; - /** @private {Promise<string>} */ - this.found_ = null; - } - - /** - * Attempts to locate the Firefox executable for this release channel. This - * will first check the default installation location for the channel before - * checking the user's PATH. The returned promise will be rejected if Firefox - * can not be found. - * - * @return {!Promise<string>} A promise for the location of the located - * Firefox executable. - */ - locate() { - if (this.found_) { - return this.found_; - } - - let found; - switch (process.platform) { - case 'darwin': - found = io.exists(this.darwin_) - .then(exists => exists ? this.darwin_ : io.findInPath('firefox')); - break; - - case 'win32': - found = findInProgramFiles(this.win32_) - .then(found => found || io.findInPath('firefox.exe')); - break; - - default: - found = Promise.resolve(io.findInPath('firefox')); - break; - } - - this.found_ = found.then(found => { - if (found) { - // TODO: verify version info. - return found; - } - throw Error('Could not locate Firefox on the current system'); - }); - return this.found_; - } -} - - -/** - * Firefox's developer channel. - * @const - * @see <https://www.mozilla.org/en-US/firefox/channel/desktop/#aurora> - */ -Channel.AURORA = new Channel( - '/Applications/FirefoxDeveloperEdition.app/Contents/MacOS/firefox-bin', - 'Firefox Developer Edition\\firefox.exe'); - -/** - * Firefox's beta channel. Note this is provided mainly for convenience as - * the beta channel has the same installation location as the main release - * channel. - * @const - * @see <https://www.mozilla.org/en-US/firefox/channel/desktop/#beta> - */ -Channel.BETA = new Channel( - '/Applications/Firefox.app/Contents/MacOS/firefox-bin', - 'Mozilla Firefox\\firefox.exe'); - -/** - * Firefox's release channel. - * @const - * @see <https://www.mozilla.org/en-US/firefox/desktop/> - */ -Channel.RELEASE = new Channel( - '/Applications/Firefox.app/Contents/MacOS/firefox-bin', - 'Mozilla Firefox\\firefox.exe'); - -/** - * Firefox's nightly release channel. - * @const - * @see <https://www.mozilla.org/en-US/firefox/channel/desktop/#nightly> - */ -Channel.NIGHTLY = new Channel( - '/Applications/FirefoxNightly.app/Contents/MacOS/firefox-bin', - 'Nightly\\firefox.exe'); - - -/** - * Copies the no focus libs into the given profile directory. - * @param {string} profileDir Path to the profile directory to install into. - * @return {!Promise<string>} The LD_LIBRARY_PATH prefix string to use - * for the installed libs. - */ -function installNoFocusLibs(profileDir) { - var x86 = path.join(profileDir, 'x86'); - var amd64 = path.join(profileDir, 'amd64'); - - return io.mkdir(x86) - .then(() => copyLib(NO_FOCUS_LIB_X86, x86)) - .then(() => io.mkdir(amd64)) - .then(() => copyLib(NO_FOCUS_LIB_AMD64, amd64)) - .then(function() { - return x86 + ':' + amd64; - }); - - function copyLib(src, dir) { - return io.copy(src, path.join(dir, X_IGNORE_NO_FOCUS_LIB)); - } -} - - -/** - * Provides a mechanism to configure and launch Firefox in a subprocess for - * use with WebDriver. - * - * If created _without_ a path for the Firefox binary to use, this class will - * attempt to find Firefox when {@link #launch()} is called. For MacOS and - * Windows, this class will look for Firefox in the current platform's default - * installation location (e.g. /Applications/Firefox.app on MacOS). For all - * other platforms, the Firefox executable must be available on your system - * `PATH`. - * - * @final - */ -class Binary { - /** - * @param {?(string|Channel)=} opt_exeOrChannel Either the path to a specific - * Firefox binary to use, or a {@link Channel} instance that describes - * how to locate the desired Firefox version. - */ - constructor(opt_exeOrChannel) { - /** @private {?(string|Channel)} */ - this.exe_ = opt_exeOrChannel || null; - - /** @private {!Array.<string>} */ - this.args_ = []; - - /** @private {!Object<string, string>} */ - this.env_ = {}; - Object.assign(this.env_, process.env, { - MOZ_CRASHREPORTER_DISABLE: '1', - MOZ_NO_REMOTE: '1', - NO_EM_RESTART: '1' - }); - - /** @private {boolean} */ - this.devEdition_ = false; - } - - /** - * @return {(string|undefined)} The path to the Firefox executable to use, or - * `undefined` if WebDriver should attempt to locate Firefox automatically - * on the current system. - */ - getExe() { - return typeof this.exe_ === 'string' ? this.exe_ : undefined; - } - - /** - * Add arguments to the command line used to start Firefox. - * @param {...(string|!Array.<string>)} var_args Either the arguments to add - * as varargs, or the arguments as an array. - */ - addArguments(var_args) { - for (var i = 0; i < arguments.length; i++) { - if (Array.isArray(arguments[i])) { - this.args_ = this.args_.concat(arguments[i]); - } else { - this.args_.push(arguments[i]); - } - } - } - - /** - * @return {!Array<string>} The command line arguments to use when starting - * the browser. - */ - getArguments() { - return this.args_; - } - - /** - * Specifies whether to use Firefox Developer Edition instead of the normal - * stable channel. Setting this option has no effect if this instance was - * created with a path to a specific Firefox binary. - * - * This method has no effect on Unix systems where the Firefox application - * has the same (default) name regardless of version. - * - * @param {boolean=} opt_use Whether to use the developer edition. Defaults to - * true. - * @deprecated Use the {@link Channel} class to indicate the desired Firefox - * version when creating a new binary: `new Binary(Channel.AURORA)`. - */ - useDevEdition(opt_use) { - this.devEdition_ = opt_use === undefined || !!opt_use; - } - - /** - * Returns a promise for the Firefox executable used by this instance. The - * returned promise will be immediately resolved if the user supplied an - * executable path when this instance was created. Otherwise, an attempt will - * be made to find Firefox on the current system. - * - * @return {!Promise<string>} a promise for the path to the Firefox executable - * used by this instance. - */ - locate() { - if (typeof this.exe_ === 'string') { - return Promise.resolve(this.exe_); - } else if (this.exe_ instanceof Channel) { - return this.exe_.locate(); - } - let channel = this.devEdition_ ? Channel.AURORA : Channel.RELEASE; - return channel.locate(); - } - - /** - * Launches Firefox and returns a promise that will be fulfilled when the - * process terminates. - * @param {string} profile Path to the profile directory to use. - * @return {!Promise<!exec.Command>} A promise for the handle to the started - * subprocess. - */ - launch(profile) { - let env = {}; - Object.assign(env, this.env_, {XRE_PROFILE_PATH: profile}); - - let args = ['-foreground'].concat(this.args_); - - return this.locate().then(function(firefox) { - if (process.platform === 'win32' || process.platform === 'darwin') { - return exec(firefox, {args: args, env: env}); - } - return installNoFocusLibs(profile).then(function(ldLibraryPath) { - env['LD_LIBRARY_PATH'] = ldLibraryPath + ':' + env['LD_LIBRARY_PATH']; - env['LD_PRELOAD'] = X_IGNORE_NO_FOCUS_LIB; - return exec(firefox, {args: args, env: env}); - }); - }); - } - - /** - * Returns a promise for the wire representation of this binary. Note: the - * FirefoxDriver only supports passing the path to the binary executable over - * the wire; all command line arguments and environment variables will be - * discarded. - * - * @return {!Promise<string>} A promise for this binary's wire representation. - */ - [Symbols.serialize]() { - return this.locate(); - } -} - - -// PUBLIC API - - -exports.Binary = Binary; -exports.Channel = Channel; - diff --git a/node_modules/selenium-webdriver/firefox/extension.js b/node_modules/selenium-webdriver/firefox/extension.js deleted file mode 100644 index 3bda759a7..000000000 --- a/node_modules/selenium-webdriver/firefox/extension.js +++ /dev/null @@ -1,187 +0,0 @@ -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you 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. - -/** @fileoverview Utilities for working with Firefox extensions. */ - -'use strict'; - -const AdmZip = require('adm-zip'), - fs = require('fs'), - path = require('path'), - xml = require('xml2js'); - -const io = require('../io'); - - -/** - * Thrown when there an add-on is malformed. - */ -class AddonFormatError extends Error { - /** @param {string} msg The error message. */ - constructor(msg) { - super(msg); - /** @override */ - this.name = this.constructor.name; - } -} - - - -/** - * Installs an extension to the given directory. - * @param {string} extension Path to the extension to install, as either a xpi - * file or a directory. - * @param {string} dir Path to the directory to install the extension in. - * @return {!Promise<string>} A promise for the add-on ID once - * installed. - */ -function install(extension, dir) { - return getDetails(extension).then(function(details) { - var dst = path.join(dir, details.id); - if (extension.slice(-4) === '.xpi') { - if (!details.unpack) { - return io.copy(extension, dst + '.xpi').then(() => details.id); - } else { - return Promise.resolve().then(function() { - // TODO: find an async library for inflating a zip archive. - new AdmZip(extension).extractAllTo(dst, true); - return details.id; - }); - } - } else { - return io.copyDir(extension, dst).then(() => details.id); - } - }); -} - - -/** - * Describes a Firefox add-on. - * @typedef {{id: string, name: string, version: string, unpack: boolean}} - */ -var AddonDetails; - -/** @typedef {{$: !Object<string, string>}} */ -var RdfRoot; - - - -/** - * Extracts the details needed to install an add-on. - * @param {string} addonPath Path to the extension directory. - * @return {!Promise<!AddonDetails>} A promise for the add-on details. - */ -function getDetails(addonPath) { - return readManifest(addonPath).then(function(doc) { - var em = getNamespaceId(doc, 'http://www.mozilla.org/2004/em-rdf#'); - var rdf = getNamespaceId( - doc, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'); - - var description = doc[rdf + 'RDF'][rdf + 'Description'][0]; - var details = { - id: getNodeText(description, em + 'id'), - name: getNodeText(description, em + 'name'), - version: getNodeText(description, em + 'version'), - unpack: getNodeText(description, em + 'unpack') || false - }; - - if (typeof details.unpack === 'string') { - details.unpack = details.unpack.toLowerCase() === 'true'; - } - - if (!details.id) { - throw new AddonFormatError('Could not find add-on ID for ' + addonPath); - } - - return details; - }); - - function getNodeText(node, name) { - return node[name] && node[name][0] || ''; - } - - function getNamespaceId(doc, url) { - var keys = Object.keys(doc); - if (keys.length !== 1) { - throw new AddonFormatError('Malformed manifest for add-on ' + addonPath); - } - - var namespaces = /** @type {!RdfRoot} */(doc[keys[0]]).$; - var id = ''; - Object.keys(namespaces).some(function(ns) { - if (namespaces[ns] !== url) { - return false; - } - - if (ns.indexOf(':') != -1) { - id = ns.split(':')[1] + ':'; - } - return true; - }); - return id; - } -} - - -/** - * Reads the manifest for a Firefox add-on. - * @param {string} addonPath Path to a Firefox add-on as a xpi or an extension. - * @return {!Promise<!Object>} A promise for the parsed manifest. - */ -function readManifest(addonPath) { - var manifest; - - if (addonPath.slice(-4) === '.xpi') { - manifest = new Promise((resolve, reject) => { - let zip = new AdmZip(addonPath); - - if (!zip.getEntry('install.rdf')) { - reject(new AddonFormatError( - 'Could not find install.rdf in ' + addonPath)); - return; - } - - zip.readAsTextAsync('install.rdf', resolve); - }); - } else { - manifest = io.stat(addonPath).then(function(stats) { - if (!stats.isDirectory()) { - throw Error( - 'Add-on path is neither a xpi nor a directory: ' + addonPath); - } - return io.read(path.join(addonPath, 'install.rdf')); - }); - } - - return manifest.then(function(content) { - return new Promise((resolve, reject) => { - xml.parseString(content, (err, data) => { - if (err) { - reject(err); - } else { - resolve(data); - } - }); - }); - }); -} - - -// PUBLIC API - - -exports.install = install; diff --git a/node_modules/selenium-webdriver/firefox/index.js b/node_modules/selenium-webdriver/firefox/index.js deleted file mode 100644 index d5c88274a..000000000 --- a/node_modules/selenium-webdriver/firefox/index.js +++ /dev/null @@ -1,722 +0,0 @@ -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you 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. - -/** - * @fileoverview Defines the {@linkplain Driver WebDriver} client for Firefox. - * Before using this module, you must download the latest - * [geckodriver release] and ensure it can be found on your system [PATH]. - * - * Each FirefoxDriver instance will be created with an anonymous profile, - * ensuring browser historys do not share session data (cookies, history, cache, - * offline storage, etc.) - * - * __Customizing the Firefox Profile__ - * - * The {@linkplain Profile} class may be used to configure the browser profile - * used with WebDriver, with functions to install additional - * {@linkplain Profile#addExtension extensions}, configure browser - * {@linkplain Profile#setPreference preferences}, and more. For example, you - * may wish to include Firebug: - * - * const {Builder} = require('selenium-webdriver'); - * const firefox = require('selenium-webdriver/firefox'); - * - * let profile = new firefox.Profile(); - * profile.addExtension('/path/to/firebug.xpi'); - * profile.setPreference('extensions.firebug.showChromeErrors', true); - * - * let options = new firefox.Options().setProfile(profile); - * let driver = new Builder() - * .forBrowser('firefox') - * .setFirefoxOptions(options) - * .build(); - * - * The {@linkplain Profile} class may also be used to configure WebDriver based - * on a pre-existing browser profile: - * - * let profile = new firefox.Profile( - * '/usr/local/home/bob/.mozilla/firefox/3fgog75h.testing'); - * let options = new firefox.Options().setProfile(profile); - * - * The FirefoxDriver will _never_ modify a pre-existing profile; instead it will - * create a copy for it to modify. By extension, there are certain browser - * preferences that are required for WebDriver to function properly and they - * will always be overwritten. - * - * __Using a Custom Firefox Binary__ - * - * On Windows and MacOS, the FirefoxDriver will search for Firefox in its - * default installation location: - * - * - Windows: C:\Program Files and C:\Program Files (x86). - * - MacOS: /Applications/Firefox.app - * - * For Linux, Firefox will always be located on the PATH: `$(where firefox)`. - * - * Several methods are provided for starting Firefox with a custom executable. - * First, on Windows and MacOS, you may configure WebDriver to check the default - * install location for a non-release channel. If the requested channel cannot - * be found in its default location, WebDriver will fallback to searching your - * PATH. _Note:_ on Linux, Firefox is _always_ located on your path, regardless - * of the requested channel. - * - * const {Builder} = require('selenium-webdriver'); - * const firefox = require('selenium-webdriver/firefox'); - * - * let options = new firefox.Options().setBinary(firefox.Channel.NIGHTLY); - * let driver = new Builder() - * .forBrowser('firefox') - * .setFirefoxOptions(options) - * .build(); - * - * On all platforms, you may configrue WebDriver to use a Firefox specific - * executable: - * - * let options = new firefox.Options() - * .setBinary('/my/firefox/install/dir/firefox-bin'); - * - * __Remote Testing__ - * - * You may customize the Firefox binary and profile when running against a - * remote Selenium server. Your custom profile will be packaged as a zip and - * transfered to the remote host for use. The profile will be transferred - * _once for each new session_. The performance impact should be minimal if - * you've only configured a few extra browser preferences. If you have a large - * profile with several extensions, you should consider installing it on the - * remote host and defining its path via the {@link Options} class. Custom - * binaries are never copied to remote machines and must be referenced by - * installation path. - * - * const {Builder} = require('selenium-webdriver'); - * const firefox = require('selenium-webdriver/firefox'); - * - * let options = new firefox.Options() - * .setProfile('/profile/path/on/remote/host') - * .setBinary('/install/dir/on/remote/host/firefox-bin'); - * - * let driver = new Builder() - * .forBrowser('firefox') - * .usingServer('http://127.0.0.1:4444/wd/hub') - * .setFirefoxOptions(options) - * .build(); - * - * __Testing Older Versions of Firefox__ - * - * To test versions of Firefox prior to Firefox 47, you must disable the use of - * the geckodriver using the {@link Options} class. - * - * let options = new firefox.Options().useGeckoDriver(false); - * - * Alternatively, you may disable the geckodriver at runtime by setting the - * environment variable `SELENIUM_MARIONETTE=false`. - * - * [geckodriver release]: https://github.com/mozilla/geckodriver/releases/ - * [PATH]: http://en.wikipedia.org/wiki/PATH_%28variable%29 - */ - -'use strict'; - -const url = require('url'); - -const {Binary, Channel} = require('./binary'), - Profile = require('./profile').Profile, - decodeProfile = require('./profile').decode, - http = require('../http'), - httpUtil = require('../http/util'), - io = require('../io'), - capabilities = require('../lib/capabilities'), - command = require('../lib/command'), - logging = require('../lib/logging'), - promise = require('../lib/promise'), - webdriver = require('../lib/webdriver'), - net = require('../net'), - portprober = require('../net/portprober'), - remote = require('../remote'); - - -/** - * Firefox-specific capability keys. Users should use the {@linkplain Options} - * class instead of referencing these keys directly. _These keys are considered - * implementation details and may be removed or changed at any time._ - * - * @enum {string} - */ -const Capability = { - /** - * Defines the Firefox binary to use. May be set to either a - * {@linkplain Binary} instance, or a string path to the Firefox executable. - */ - BINARY: 'firefox_binary', - - /** - * Specifies whether to use Mozilla's Marionette, or the legacy FirefoxDriver - * from the Selenium project. Defaults to false. - */ - MARIONETTE: 'marionette', - - /** - * Defines the Firefox profile to use. May be set to either a - * {@linkplain Profile} instance, or to a base-64 encoded zip of a profile - * directory. - */ - PROFILE: 'firefox_profile' -}; - - -/** - * Configuration options for the FirefoxDriver. - */ -class Options { - constructor() { - /** @private {Profile} */ - this.profile_ = null; - - /** @private {Binary} */ - this.binary_ = null; - - /** @private {logging.Preferences} */ - this.logPrefs_ = null; - - /** @private {?capabilities.ProxyConfig} */ - this.proxy_ = null; - - /** @private {boolean} */ - this.marionette_ = true; - } - - /** - * Sets the profile to use. The profile may be specified as a - * {@link Profile} object or as the path to an existing Firefox profile to use - * as a template. - * - * @param {(string|!Profile)} profile The profile to use. - * @return {!Options} A self reference. - */ - setProfile(profile) { - if (typeof profile === 'string') { - profile = new Profile(profile); - } - this.profile_ = profile; - return this; - } - - /** - * Sets the binary to use. The binary may be specified as the path to a - * Firefox executable, a specific {@link Channel}, or as a {@link Binary} - * object. - * - * @param {(string|!Binary|!Channel)} binary The binary to use. - * @return {!Options} A self reference. - * @throws {TypeError} If `binary` is an invalid type. - */ - setBinary(binary) { - if (typeof binary === 'string' || binary instanceof Channel) { - binary = new Binary(binary); - } - - if (!(binary instanceof Binary)) { - throw TypeError( - 'binary must be a string path, Channel, or Binary object'); - } - - this.binary_ = binary; - return this; - } - - /** - * Sets the logging preferences for the new session. - * @param {logging.Preferences} prefs The logging preferences. - * @return {!Options} A self reference. - */ - setLoggingPreferences(prefs) { - this.logPrefs_ = prefs; - return this; - } - - /** - * Sets the proxy to use. - * - * @param {capabilities.ProxyConfig} proxy The proxy configuration to use. - * @return {!Options} A self reference. - */ - setProxy(proxy) { - this.proxy_ = proxy; - return this; - } - - /** - * Sets whether to use Mozilla's geckodriver to drive the browser. This option - * is enabled by default and required for Firefox 47+. - * - * @param {boolean} enable Whether to enable the geckodriver. - * @see https://github.com/mozilla/geckodriver - */ - useGeckoDriver(enable) { - this.marionette_ = enable; - return this; - } - - /** - * Converts these options to a {@link capabilities.Capabilities} instance. - * - * @return {!capabilities.Capabilities} A new capabilities object. - */ - toCapabilities() { - var caps = capabilities.Capabilities.firefox(); - if (this.logPrefs_) { - caps.set(capabilities.Capability.LOGGING_PREFS, this.logPrefs_); - } - if (this.proxy_) { - caps.set(capabilities.Capability.PROXY, this.proxy_); - } - if (this.binary_) { - caps.set(Capability.BINARY, this.binary_); - } - if (this.profile_) { - caps.set(Capability.PROFILE, this.profile_); - } - caps.set(Capability.MARIONETTE, this.marionette_); - return caps; - } -} - - -/** - * Enum of available command contexts. - * - * Command contexts are specific to Marionette, and may be used with the - * {@link #context=} method. Contexts allow you to direct all subsequent - * commands to either "content" (default) or "chrome". The latter gives - * you elevated security permissions. - * - * @enum {string} - */ -const Context = { - CONTENT: "content", - CHROME: "chrome", -}; - - -const GECKO_DRIVER_EXE = - process.platform === 'win32' ? 'geckodriver.exe' : 'geckodriver'; - - -/** - * @return {string} . - * @throws {Error} - */ -function findGeckoDriver() { - let exe = io.findInPath(GECKO_DRIVER_EXE, true); - if (!exe) { - throw Error( - 'The ' + GECKO_DRIVER_EXE + ' executable could not be found on the current ' + - 'PATH. Please download the latest version from ' + - 'https://github.com/mozilla/geckodriver/releases/' + - 'WebDriver and ensure it can be found on your PATH.'); - } - return exe; -} - - -/** - * @param {(Profile|string)} profile The profile to prepare. - * @param {number} port The port the FirefoxDriver should listen on. - * @return {!Promise<string>} a promise for the path to the profile directory. - */ -function prepareProfile(profile, port) { - if (typeof profile === 'string') { - return decodeProfile(/** @type {string} */(profile)).then(dir => { - profile = new Profile(dir); - profile.setPreference('webdriver_firefox_port', port); - return profile.writeToDisk(); - }); - } - - profile = profile || new Profile; - profile.setPreference('webdriver_firefox_port', port); - return profile.writeToDisk(); -} - - -function normalizeProxyConfiguration(config) { - if ('manual' === config.proxyType) { - if (config.ftpProxy && !config.ftpProxyPort) { - let hostAndPort = net.splitHostAndPort(config.ftpProxy); - config.ftpProxy = hostAndPort.host; - config.ftpProxyPort = hostAndPort.port; - } - - if (config.httpProxy && !config.httpProxyPort) { - let hostAndPort = net.splitHostAndPort(config.httpProxy); - config.httpProxy = hostAndPort.host; - config.httpProxyPort = hostAndPort.port; - } - - if (config.sslProxy && !config.sslProxyPort) { - let hostAndPort = net.splitHostAndPort(config.sslProxy); - config.sslProxy = hostAndPort.host; - config.sslProxyPort = hostAndPort.port; - } - - if (config.socksProxy && !config.socksProxyPort) { - let hostAndPort = net.splitHostAndPort(config.socksProxy); - config.socksProxy = hostAndPort.host; - config.socksProxyPort = hostAndPort.port; - } - } else if ('pac' === config.proxyType) { - if (config.proxyAutoconfigUrl && !config.pacUrl) { - config.pacUrl = config.proxyAutoconfigUrl; - } - } - return config; -} - - -/** @enum {string} */ -const ExtensionCommand = { - GET_CONTEXT: 'getContext', - SET_CONTEXT: 'setContext', -}; - - -/** - * Creates a command executor with support for Marionette's custom commands. - * @param {!Promise<string>} serverUrl The server's URL. - * @return {!command.Executor} The new command executor. - */ -function createExecutor(serverUrl) { - let client = serverUrl.then(url => new http.HttpClient(url)); - let executor = new http.Executor(client); - configureExecutor(executor); - return executor; -} - - -/** - * Configures the given executor with Firefox-specific commands. - * @param {!http.Executor} executor the executor to configure. - */ -function configureExecutor(executor) { - executor.defineCommand( - ExtensionCommand.GET_CONTEXT, - 'GET', - '/session/:sessionId/moz/context'); - - executor.defineCommand( - ExtensionCommand.SET_CONTEXT, - 'POST', - '/session/:sessionId/moz/context'); -} - - -/** - * Creates {@link selenium-webdriver/remote.DriverService} instances that manage - * a [geckodriver](https://github.com/mozilla/geckodriver) server in a child - * process. - */ -class ServiceBuilder extends remote.DriverService.Builder { - /** - * @param {string=} opt_exe Path to the server executable to use. If omitted, - * the builder will attempt to locate the geckodriver on the system PATH. - */ - constructor(opt_exe) { - super(opt_exe || findGeckoDriver()); - this.setLoopback(true); // Required. - } - - /** - * Enables verbose logging. - * - * @param {boolean=} opt_trace Whether to enable trace-level logging. By - * default, only debug logging is enabled. - * @return {!ServiceBuilder} A self reference. - */ - enableVerboseLogging(opt_trace) { - return this.addArguments(opt_trace ? '-vv' : '-v'); - } - - /** - * Sets the path to the executable Firefox binary that the geckodriver should - * use. If this method is not called, this builder will attempt to locate - * Firefox in the default installation location for the current platform. - * - * @param {(string|!Binary)} binary Path to the executable Firefox binary to use. - * @return {!ServiceBuilder} A self reference. - * @see Binary#locate() - */ - setFirefoxBinary(binary) { - let exe = typeof binary === 'string' - ? Promise.resolve(binary) : binary.locate(); - return this.addArguments('-b', exe); - } -} - - -/** - * @typedef {{executor: !command.Executor, - * capabilities: (!capabilities.Capabilities| - * {desired: (capabilities.Capabilities|undefined), - * required: (capabilities.Capabilities|undefined)}), - * onQuit: function(this: void): ?}} - */ -var DriverSpec; - - -/** - * @param {(http.Executor|remote.DriverService|undefined)} executor - * @param {!capabilities.Capabilities} caps - * @param {Profile} profile - * @param {Binary} binary - * @return {DriverSpec} - */ -function createGeckoDriver(executor, caps, profile, binary) { - let firefoxOptions = {}; - caps.set('moz:firefoxOptions', firefoxOptions); - - if (binary) { - if (binary.getExe()) { - firefoxOptions['binary'] = binary.getExe(); - } - - let args = binary.getArguments(); - if (args.length) { - firefoxOptions['args'] = args; - } - } - - if (profile) { - // If the user specified a template directory or any extensions to install, - // we need to encode the profile as a base64 string (which requires writing - // it to disk first). Otherwise, if the user just specified some custom - // preferences, we can send those directly. - if (profile.getTemplateDir() || profile.getExtensions().length) { - firefoxOptions['profile'] = profile.encode(); - - } else { - let prefs = profile.getPreferences(); - if (Object.keys(prefs).length) { - firefoxOptions['prefs'] = prefs; - } - } - } - - let sessionCaps = caps; - if (caps.has(capabilities.Capability.PROXY)) { - let proxy = normalizeProxyConfiguration( - caps.get(capabilities.Capability.PROXY)); - - // Marionette requires proxy settings to be specified as required - // capabilities. See mozilla/geckodriver#97 - let required = new capabilities.Capabilities() - .set(capabilities.Capability.PROXY, proxy); - - caps.delete(capabilities.Capability.PROXY); - sessionCaps = {required, desired: caps}; - } - - /** @type {!command.Executor} */ - let cmdExecutor; - let onQuit = function() {}; - - if (executor instanceof http.Executor) { - configureExecutor(executor); - cmdExecutor = executor; - } else if (executor instanceof remote.DriverService) { - cmdExecutor = createExecutor(executor.start()); - onQuit = () => executor.kill(); - } else { - let builder = new ServiceBuilder(); - if (binary) { - builder.setFirefoxBinary(binary); - } - let service = builder.build(); - cmdExecutor = createExecutor(service.start()); - onQuit = () => service.kill(); - } - - return { - executor: cmdExecutor, - capabilities: sessionCaps, - onQuit - }; -} - - -/** - * @param {!capabilities.Capabilities} caps - * @param {Profile} profile - * @param {!Binary} binary - * @return {DriverSpec} - */ -function createLegacyDriver(caps, profile, binary, flow) { - profile = profile || new Profile; - - let freePort = portprober.findFreePort(); - let preparedProfile = - freePort.then(port => prepareProfile(profile, port)); - let command = preparedProfile.then(dir => binary.launch(dir)); - - let serverUrl = command.then(() => freePort) - .then(function(/** number */port) { - let serverUrl = url.format({ - protocol: 'http', - hostname: net.getLoopbackAddress(), - port: port + '', - pathname: '/hub' - }); - let ready = httpUtil.waitForServer(serverUrl, 45 * 1000); - return ready.then(() => serverUrl); - }); - - return { - executor: createExecutor(serverUrl), - capabilities: caps, - onQuit: function() { - return command.then(command => { - command.kill(); - return preparedProfile.then(io.rmDir) - .then(() => command.result(), - () => command.result()); - }); - } - }; -} - - -/** - * A WebDriver client for Firefox. - */ -class Driver extends webdriver.WebDriver { - /** - * Creates a new Firefox session. - * - * @param {(Options|capabilities.Capabilities|Object)=} opt_config The - * configuration options for this driver, specified as either an - * {@link Options} or {@link capabilities.Capabilities}, or as a raw hash - * object. - * @param {(http.Executor|remote.DriverService)=} opt_executor Either a - * pre-configured command executor to use for communicating with an - * externally managed remote end (which is assumed to already be running), - * or the `DriverService` to use to start the geckodriver in a child - * process. - * - * If an executor is provided, care should e taken not to use reuse it with - * other clients as its internal command mappings will be updated to support - * Firefox-specific commands. - * - * _This parameter may only be used with Mozilla's GeckoDriver._ - * - * @param {promise.ControlFlow=} opt_flow The flow to - * schedule commands through. Defaults to the active flow object. - * @throws {Error} If a custom command executor is provided and the driver is - * configured to use the legacy FirefoxDriver from the Selenium project. - * @return {!Driver} A new driver instance. - */ - static createSession(opt_config, opt_executor, opt_flow) { - let caps; - if (opt_config instanceof Options) { - caps = opt_config.toCapabilities(); - } else { - caps = new capabilities.Capabilities(opt_config); - } - - let binary = caps.get(Capability.BINARY) || new Binary(); - caps.delete(Capability.BINARY); - if (typeof binary === 'string') { - binary = new Binary(binary); - } - - let profile; - if (caps.has(Capability.PROFILE)) { - profile = caps.get(Capability.PROFILE); - caps.delete(Capability.PROFILE); - } - - // Users must now explicitly disable marionette to use the legacy - // FirefoxDriver. - let noMarionette = - caps.get(Capability.MARIONETTE) === false - || /^0|false$/i.test(process.env['SELENIUM_MARIONETTE']); - let useMarionette = !noMarionette; - - let spec; - if (useMarionette) { - spec = createGeckoDriver(opt_executor, caps, profile, binary); - } else { - if (opt_executor) { - throw Error('You may not use a custom command executor with the legacy' - + ' FirefoxDriver'); - } - spec = createLegacyDriver(caps, profile, binary, opt_flow); - } - - return /** @type {!Driver} */(webdriver.WebDriver.createSession( - spec.executor, spec.capabilities, opt_flow, this, spec.onQuit)); - } - - /** - * This function is a no-op as file detectors are not supported by this - * implementation. - * @override - */ - setFileDetector() { - } - - /** - * Get the context that is currently in effect. - * - * @return {!promise.Thenable<Context>} Current context. - */ - getContext() { - return this.schedule( - new command.Command(ExtensionCommand.GET_CONTEXT), - 'get WebDriver.context'); - } - - /** - * Changes target context for commands between chrome- and content. - * - * Changing the current context has a stateful impact on all subsequent - * commands. The {@link Context.CONTENT} context has normal web - * platform document permissions, as if you would evaluate arbitrary - * JavaScript. The {@link Context.CHROME} context gets elevated - * permissions that lets you manipulate the browser chrome itself, - * with full access to the XUL toolkit. - * - * Use your powers wisely. - * - * @param {!promise.Thenable<void>} ctx The context to switch to. - */ - setContext(ctx) { - return this.schedule( - new command.Command(ExtensionCommand.SET_CONTEXT) - .setParameter("context", ctx), - 'set WebDriver.context'); - } -} - - -// PUBLIC API - - -exports.Binary = Binary; -exports.Channel = Channel; -exports.Context = Context; -exports.Driver = Driver; -exports.Options = Options; -exports.Profile = Profile; -exports.ServiceBuilder = ServiceBuilder; diff --git a/node_modules/selenium-webdriver/firefox/profile.js b/node_modules/selenium-webdriver/firefox/profile.js deleted file mode 100644 index b7f6363f9..000000000 --- a/node_modules/selenium-webdriver/firefox/profile.js +++ /dev/null @@ -1,430 +0,0 @@ -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you 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. - -/** - * @fileoverview Profile management module. This module is considered internal; - * users should use {@link selenium-webdriver/firefox}. - */ - -'use strict'; - -const AdmZip = require('adm-zip'), - fs = require('fs'), - path = require('path'), - vm = require('vm'); - -const isDevMode = require('../lib/devmode'), - Symbols = require('../lib/symbols'), - io = require('../io'), - extension = require('./extension'); - - -/** @const */ -const WEBDRIVER_PREFERENCES_PATH = isDevMode - ? path.join(__dirname, '../../../firefox-driver/webdriver.json') - : path.join(__dirname, '../lib/firefox/webdriver.json'); - -/** @const */ -const WEBDRIVER_EXTENSION_PATH = isDevMode - ? path.join(__dirname, - '../../../../build/javascript/firefox-driver/webdriver.xpi') - : path.join(__dirname, '../lib/firefox/webdriver.xpi'); - -/** @const */ -const WEBDRIVER_EXTENSION_NAME = 'fxdriver@googlecode.com'; - - - -/** @type {Object} */ -var defaultPreferences = null; - -/** - * Synchronously loads the default preferences used for the FirefoxDriver. - * @return {!Object} The default preferences JSON object. - */ -function getDefaultPreferences() { - if (!defaultPreferences) { - var contents = /** @type {string} */( - fs.readFileSync(WEBDRIVER_PREFERENCES_PATH, 'utf8')); - defaultPreferences = /** @type {!Object} */(JSON.parse(contents)); - } - return defaultPreferences; -} - - -/** - * Parses a user.js file in a Firefox profile directory. - * @param {string} f Path to the file to parse. - * @return {!Promise<!Object>} A promise for the parsed preferences as - * a JSON object. If the file does not exist, an empty object will be - * returned. - */ -function loadUserPrefs(f) { - return io.read(f).then( - function onSuccess(contents) { - var prefs = {}; - var context = vm.createContext({ - 'user_pref': function(key, value) { - prefs[key] = value; - } - }); - vm.runInContext(contents.toString(), context, f); - return prefs; - }, - function onError(err) { - if (err && err.code === 'ENOENT') { - return {}; - } - throw err; - }); -} - - - -/** - * @param {!Object} prefs The default preferences to write. Will be - * overridden by user.js preferences in the template directory and the - * frozen preferences required by WebDriver. - * @param {string} dir Path to the directory write the file to. - * @return {!Promise<string>} A promise for the profile directory, - * to be fulfilled when user preferences have been written. - */ -function writeUserPrefs(prefs, dir) { - var userPrefs = path.join(dir, 'user.js'); - return loadUserPrefs(userPrefs).then(function(overrides) { - Object.assign(prefs, overrides); - Object.assign(prefs, getDefaultPreferences()['frozen']); - - var contents = Object.keys(prefs).map(function(key) { - return 'user_pref(' + JSON.stringify(key) + ', ' + - JSON.stringify(prefs[key]) + ');'; - }).join('\n'); - - return new Promise((resolve, reject) => { - fs.writeFile(userPrefs, contents, function(err) { - err && reject(err) || resolve(dir); - }); - }); - }); -}; - - -/** - * Installs a group of extensions in the given profile directory. If the - * WebDriver extension is not included in this set, the default version - * bundled with this package will be installed. - * @param {!Array.<string>} extensions The extensions to install, as a - * path to an unpacked extension directory or a path to a xpi file. - * @param {string} dir The profile directory to install to. - * @param {boolean=} opt_excludeWebDriverExt Whether to skip installation of - * the default WebDriver extension. - * @return {!Promise<string>} A promise for the main profile directory - * once all extensions have been installed. - */ -function installExtensions(extensions, dir, opt_excludeWebDriverExt) { - var hasWebDriver = !!opt_excludeWebDriverExt; - var next = 0; - var extensionDir = path.join(dir, 'extensions'); - - return new Promise(function(fulfill, reject) { - io.mkdir(extensionDir).then(installNext, reject); - - function installNext() { - if (next >= extensions.length) { - if (hasWebDriver) { - fulfill(dir); - } else { - install(WEBDRIVER_EXTENSION_PATH); - } - } else { - install(extensions[next++]); - } - } - - function install(ext) { - extension.install(ext, extensionDir).then(function(id) { - hasWebDriver = hasWebDriver || (id === WEBDRIVER_EXTENSION_NAME); - installNext(); - }, reject); - } - }); -} - - -/** - * Decodes a base64 encoded profile. - * @param {string} data The base64 encoded string. - * @return {!Promise<string>} A promise for the path to the decoded profile - * directory. - */ -function decode(data) { - return io.tmpFile().then(function(file) { - var buf = new Buffer(data, 'base64'); - return io.write(file, buf) - .then(io.tmpDir) - .then(function(dir) { - var zip = new AdmZip(file); - zip.extractAllTo(dir); // Sync only? Why?? :-( - return dir; - }); - }); -} - - - -/** - * Models a Firefox profile directory for use with the FirefoxDriver. The - * {@code Profile} directory uses an in-memory model until - * {@link #writeToDisk} or {@link #encode} is called. - */ -class Profile { - /** - * @param {string=} opt_dir Path to an existing Firefox profile directory to - * use a template for this profile. If not specified, a blank profile will - * be used. - */ - constructor(opt_dir) { - /** @private {!Object} */ - this.preferences_ = {}; - - /** @private {boolean} */ - this.nativeEventsEnabled_ = true; - - /** @private {(string|undefined)} */ - this.template_ = opt_dir; - - /** @private {number} */ - this.port_ = 0; - - /** @private {!Array<string>} */ - this.extensions_ = []; - } - - /** - * @return {(string|undefined)} Path to an existing Firefox profile directory - * to use as a template when writing this Profile to disk. - */ - getTemplateDir() { - return this.template_; - } - - /** - * Registers an extension to be included with this profile. - * @param {string} extension Path to the extension to include, as either an - * unpacked extension directory or the path to a xpi file. - */ - addExtension(extension) { - this.extensions_.push(extension); - } - - /** - * @return {!Array<string>} A list of extensions to install in this profile. - */ - getExtensions() { - return this.extensions_; - } - - /** - * Sets a desired preference for this profile. - * @param {string} key The preference key. - * @param {(string|number|boolean)} value The preference value. - * @throws {Error} If attempting to set a frozen preference. - */ - setPreference(key, value) { - var frozen = getDefaultPreferences()['frozen']; - if (frozen.hasOwnProperty(key) && frozen[key] !== value) { - throw Error('You may not set ' + key + '=' + JSON.stringify(value) - + '; value is frozen for proper WebDriver functionality (' - + key + '=' + JSON.stringify(frozen[key]) + ')'); - } - this.preferences_[key] = value; - } - - /** - * Returns the currently configured value of a profile preference. This does - * not include any defaults defined in the profile's template directory user.js - * file (if a template were specified on construction). - * @param {string} key The desired preference. - * @return {(string|number|boolean|undefined)} The current value of the - * requested preference. - */ - getPreference(key) { - return this.preferences_[key]; - } - - /** - * @return {!Object} A copy of all currently configured preferences. - */ - getPreferences() { - return Object.assign({}, this.preferences_); - } - - /** - * Specifies which host the driver should listen for commands on. If not - * specified, the driver will default to "localhost". This option should be - * specified when "localhost" is not mapped to the loopback address - * (127.0.0.1) in `/etc/hosts`. - * - * @param {string} host the host the driver should listen for commands on - */ - setHost(host) { - this.preferences_['webdriver_firefox_allowed_hosts'] = host; - } - - /** - * @return {number} The port this profile is currently configured to use, or - * 0 if the port will be selected at random when the profile is written - * to disk. - */ - getPort() { - return this.port_; - } - - /** - * Sets the port to use for the WebDriver extension loaded by this profile. - * @param {number} port The desired port, or 0 to use any free port. - */ - setPort(port) { - this.port_ = port; - } - - /** - * @return {boolean} Whether the FirefoxDriver is configured to automatically - * accept untrusted SSL certificates. - */ - acceptUntrustedCerts() { - return !!this.preferences_['webdriver_accept_untrusted_certs']; - } - - /** - * Sets whether the FirefoxDriver should automatically accept untrusted SSL - * certificates. - * @param {boolean} value . - */ - setAcceptUntrustedCerts(value) { - this.preferences_['webdriver_accept_untrusted_certs'] = !!value; - } - - /** - * Sets whether to assume untrusted certificates come from untrusted issuers. - * @param {boolean} value . - */ - setAssumeUntrustedCertIssuer(value) { - this.preferences_['webdriver_assume_untrusted_issuer'] = !!value; - } - - /** - * @return {boolean} Whether to assume untrusted certs come from untrusted - * issuers. - */ - assumeUntrustedCertIssuer() { - return !!this.preferences_['webdriver_assume_untrusted_issuer']; - } - - /** - * Sets whether to use native events with this profile. - * @param {boolean} enabled . - */ - setNativeEventsEnabled(enabled) { - this.nativeEventsEnabled_ = enabled; - } - - /** - * Returns whether native events are enabled in this profile. - * @return {boolean} . - */ - nativeEventsEnabled() { - return this.nativeEventsEnabled_; - } - - /** - * Writes this profile to disk. - * @param {boolean=} opt_excludeWebDriverExt Whether to exclude the WebDriver - * extension from the generated profile. Used to reduce the size of an - * {@link #encode() encoded profile} since the server will always install - * the extension itself. - * @return {!Promise<string>} A promise for the path to the new profile - * directory. - */ - writeToDisk(opt_excludeWebDriverExt) { - var profileDir = io.tmpDir(); - if (this.template_) { - profileDir = profileDir.then(function(dir) { - return io.copyDir( - /** @type {string} */(this.template_), - dir, /(parent\.lock|lock|\.parentlock)/); - }.bind(this)); - } - - // Freeze preferences for async operations. - var prefs = {}; - Object.assign(prefs, getDefaultPreferences()['mutable']); - Object.assign(prefs, getDefaultPreferences()['frozen']); - Object.assign(prefs, this.preferences_); - - // Freeze extensions for async operations. - var extensions = this.extensions_.concat(); - - return profileDir.then(function(dir) { - return writeUserPrefs(prefs, dir); - }).then(function(dir) { - return installExtensions(extensions, dir, !!opt_excludeWebDriverExt); - }); - } - - /** - * Write profile to disk, compress its containing directory, and return - * it as a Base64 encoded string. - * - * @return {!Promise<string>} A promise for the encoded profile as - * Base64 string. - * - */ - encode() { - return this.writeToDisk(true).then(function(dir) { - var zip = new AdmZip(); - zip.addLocalFolder(dir, ''); - // Stored compression, see https://en.wikipedia.org/wiki/Zip_(file_format) - zip.getEntries().forEach(function(entry) { - entry.header.method = 0; - }); - - return io.tmpFile().then(function(file) { - zip.writeZip(file); // Sync! Why oh why :-( - return io.read(file); - }); - }).then(function(data) { - return data.toString('base64'); - }); - } - - /** - * Encodes this profile as a zipped, base64 encoded directory. - * @return {!Promise<string>} A promise for the encoded profile. - */ - [Symbols.serialize]() { - return this.encode(); - } -} - - -// PUBLIC API - - -exports.Profile = Profile; -exports.decode = decode; -exports.loadUserPrefs = loadUserPrefs; |