profile.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  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. * @fileoverview Profile management module. This module is considered internal;
  19. * users should use {@link selenium-webdriver/firefox}.
  20. */
  21. 'use strict';
  22. const fs = require('fs'),
  23. path = require('path'),
  24. vm = require('vm');
  25. const isDevMode = require('../lib/devmode'),
  26. Symbols = require('../lib/symbols'),
  27. io = require('../io'),
  28. {Zip, unzip} = require('../io/zip'),
  29. extension = require('./extension');
  30. /**
  31. * Parses a user.js file in a Firefox profile directory.
  32. * @param {string} f Path to the file to parse.
  33. * @return {!Promise<!Object>} A promise for the parsed preferences as
  34. * a JSON object. If the file does not exist, an empty object will be
  35. * returned.
  36. */
  37. function loadUserPrefs(f) {
  38. return io.read(f).then(
  39. function onSuccess(contents) {
  40. var prefs = {};
  41. var context = vm.createContext({
  42. 'user_pref': function(key, value) {
  43. prefs[key] = value;
  44. }
  45. });
  46. vm.runInContext(contents.toString(), context, f);
  47. return prefs;
  48. },
  49. function onError(err) {
  50. if (err && err.code === 'ENOENT') {
  51. return {};
  52. }
  53. throw err;
  54. });
  55. }
  56. /**
  57. * @param {!Object} prefs The default preferences to write. Will be
  58. * overridden by user.js preferences in the template directory and the
  59. * frozen preferences required by WebDriver.
  60. * @param {string} dir Path to the directory write the file to.
  61. * @return {!Promise<string>} A promise for the profile directory,
  62. * to be fulfilled when user preferences have been written.
  63. */
  64. function writeUserPrefs(prefs, dir) {
  65. var userPrefs = path.join(dir, 'user.js');
  66. return loadUserPrefs(userPrefs).then(function(overrides) {
  67. Object.assign(prefs, overrides);
  68. let keys = Object.keys(prefs);
  69. if (!keys.length) {
  70. return dir;
  71. }
  72. let contents = Object.keys(prefs).map(function(key) {
  73. return 'user_pref(' + JSON.stringify(key) + ', ' +
  74. JSON.stringify(prefs[key]) + ');';
  75. }).join('\n');
  76. return new Promise((resolve, reject) => {
  77. fs.writeFile(userPrefs, contents, function(err) {
  78. err && reject(err) || resolve(dir);
  79. });
  80. });
  81. });
  82. };
  83. /**
  84. * Installs a group of extensions in the given profile directory. If the
  85. * WebDriver extension is not included in this set, the default version
  86. * bundled with this package will be installed.
  87. * @param {!Array.<string>} extensions The extensions to install, as a
  88. * path to an unpacked extension directory or a path to a xpi file.
  89. * @param {string} dir The profile directory to install to.
  90. * @return {!Promise<string>} A promise for the main profile directory
  91. * once all extensions have been installed.
  92. */
  93. function installExtensions(extensions, dir) {
  94. var next = 0;
  95. var extensionDir = path.join(dir, 'extensions');
  96. return new Promise(function(fulfill, reject) {
  97. io.mkdir(extensionDir).then(installNext, reject);
  98. function installNext() {
  99. if (next >= extensions.length) {
  100. fulfill(dir);
  101. } else {
  102. install(extensions[next++]);
  103. }
  104. }
  105. function install(ext) {
  106. extension.install(ext, extensionDir).then(function(id) {
  107. installNext();
  108. }, reject);
  109. }
  110. });
  111. }
  112. /**
  113. * Models a Firefox profile directory for use with the FirefoxDriver. The
  114. * {@code Profile} directory uses an in-memory model until
  115. * {@link #writeToDisk} or {@link #encode} is called.
  116. */
  117. class Profile {
  118. /**
  119. * @param {string=} opt_dir Path to an existing Firefox profile directory to
  120. * use a template for this profile. If not specified, a blank profile will
  121. * be used.
  122. */
  123. constructor(opt_dir) {
  124. /** @private {!Object} */
  125. this.preferences_ = {};
  126. /** @private {(string|undefined)} */
  127. this.template_ = opt_dir;
  128. /** @private {!Array<string>} */
  129. this.extensions_ = [];
  130. }
  131. /**
  132. * @return {(string|undefined)} Path to an existing Firefox profile directory
  133. * to use as a template when writing this Profile to disk.
  134. */
  135. getTemplateDir() {
  136. return this.template_;
  137. }
  138. /**
  139. * Registers an extension to be included with this profile.
  140. * @param {string} extension Path to the extension to include, as either an
  141. * unpacked extension directory or the path to a xpi file.
  142. */
  143. addExtension(extension) {
  144. this.extensions_.push(extension);
  145. }
  146. /**
  147. * @return {!Array<string>} A list of extensions to install in this profile.
  148. */
  149. getExtensions() {
  150. return this.extensions_;
  151. }
  152. /**
  153. * Sets a desired preference for this profile.
  154. * @param {string} key The preference key.
  155. * @param {(string|number|boolean)} value The preference value.
  156. * @throws {Error} If attempting to set a frozen preference.
  157. */
  158. setPreference(key, value) {
  159. this.preferences_[key] = value;
  160. }
  161. /**
  162. * Returns the currently configured value of a profile preference. This does
  163. * not include any defaults defined in the profile's template directory user.js
  164. * file (if a template were specified on construction).
  165. * @param {string} key The desired preference.
  166. * @return {(string|number|boolean|undefined)} The current value of the
  167. * requested preference.
  168. */
  169. getPreference(key) {
  170. return this.preferences_[key];
  171. }
  172. /**
  173. * @return {!Object} A copy of all currently configured preferences.
  174. */
  175. getPreferences() {
  176. return Object.assign({}, this.preferences_);
  177. }
  178. /**
  179. * Specifies which host the driver should listen for commands on. If not
  180. * specified, the driver will default to "localhost". This option should be
  181. * specified when "localhost" is not mapped to the loopback address
  182. * (127.0.0.1) in `/etc/hosts`.
  183. *
  184. * @param {string} host the host the driver should listen for commands on
  185. */
  186. setHost(host) {
  187. this.preferences_['webdriver_firefox_allowed_hosts'] = host;
  188. }
  189. /**
  190. * @return {boolean} Whether the FirefoxDriver is configured to automatically
  191. * accept untrusted SSL certificates.
  192. */
  193. acceptUntrustedCerts() {
  194. return !!this.preferences_['webdriver_accept_untrusted_certs'];
  195. }
  196. /**
  197. * Sets whether the FirefoxDriver should automatically accept untrusted SSL
  198. * certificates.
  199. * @param {boolean} value .
  200. */
  201. setAcceptUntrustedCerts(value) {
  202. this.preferences_['webdriver_accept_untrusted_certs'] = !!value;
  203. }
  204. /**
  205. * Sets whether to assume untrusted certificates come from untrusted issuers.
  206. * @param {boolean} value .
  207. */
  208. setAssumeUntrustedCertIssuer(value) {
  209. this.preferences_['webdriver_assume_untrusted_issuer'] = !!value;
  210. }
  211. /**
  212. * @return {boolean} Whether to assume untrusted certs come from untrusted
  213. * issuers.
  214. */
  215. assumeUntrustedCertIssuer() {
  216. return !!this.preferences_['webdriver_assume_untrusted_issuer'];
  217. }
  218. /**
  219. * Writes this profile to disk.
  220. * @return {!Promise<string>} A promise for the path to the new profile
  221. * directory.
  222. */
  223. writeToDisk() {
  224. var profileDir = io.tmpDir();
  225. if (this.template_) {
  226. profileDir = profileDir.then(function(dir) {
  227. return io.copyDir(
  228. /** @type {string} */(this.template_),
  229. dir, /(parent\.lock|lock|\.parentlock)/);
  230. }.bind(this));
  231. }
  232. // Freeze preferences for async operations.
  233. let prefs = Object.assign({}, this.preferences_);
  234. // Freeze extensions for async operations.
  235. var extensions = this.extensions_.concat();
  236. return profileDir.then(function(dir) {
  237. return writeUserPrefs(prefs, dir);
  238. }).then(function(dir) {
  239. return installExtensions(extensions, dir);
  240. });
  241. }
  242. /**
  243. * Write profile to disk, compress its containing directory, and return
  244. * it as a Base64 encoded string.
  245. *
  246. * @return {!Promise<string>} A promise for the encoded profile as
  247. * Base64 string.
  248. *
  249. */
  250. encode() {
  251. return this.writeToDisk().then(function(dir) {
  252. let zip = new Zip;
  253. return zip.addDir(dir)
  254. .then(() => zip.toBuffer())
  255. .then(buf => buf.toString('base64'));
  256. });
  257. }
  258. /**
  259. * Encodes this profile as a zipped, base64 encoded directory.
  260. * @return {!Promise<string>} A promise for the encoded profile.
  261. */
  262. [Symbols.serialize]() {
  263. return this.encode();
  264. }
  265. }
  266. // PUBLIC API
  267. exports.Profile = Profile;
  268. exports.loadUserPrefs = loadUserPrefs;