diff --git a/experimental/puppeteer-firefox/.ci/node10/Dockerfile.linux b/experimental/puppeteer-firefox/.ci/node10/Dockerfile.linux
deleted file mode 100644
index 7c3d3ce0fedcb..0000000000000
--- a/experimental/puppeteer-firefox/.ci/node10/Dockerfile.linux
+++ /dev/null
@@ -1,17 +0,0 @@
-FROM node:10.18.1-stretch
-
-RUN apt-get update && \
- apt-get -y install xvfb gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 \
- libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 \
- libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 \
- libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 \
- libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget && \
- rm -rf /var/lib/apt/lists/*
-
-# Add user so we don't need --no-sandbox.
-RUN groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \
- && mkdir -p /home/pptruser/Downloads \
- && chown -R pptruser:pptruser /home/pptruser
-
-# Run everything after as non-privileged user.
-USER pptruser
diff --git a/experimental/puppeteer-firefox/.ci/node10/Dockerfile.windows b/experimental/puppeteer-firefox/.ci/node10/Dockerfile.windows
deleted file mode 100644
index 8494e332e7da3..0000000000000
--- a/experimental/puppeteer-firefox/.ci/node10/Dockerfile.windows
+++ /dev/null
@@ -1,11 +0,0 @@
-FROM microsoft/windowsservercore:latest
-
-ENV NODE_VERSION 10.18.1
-
-RUN setx /m PATH "%PATH%;C:\nodejs"
-
-RUN powershell -Command \
- netsh interface ipv4 set subinterface 18 mtu=1460 store=persistent ; \
- Invoke-WebRequest $('https://nodejs.org/dist/v{0}/node-v{0}-win-x64.zip' -f $env:NODE_VERSION) -OutFile 'node.zip' -UseBasicParsing ; \
- Expand-Archive node.zip -DestinationPath C:\ ; \
- Rename-Item -Path $('C:\node-v{0}-win-x64' -f $env:NODE_VERSION) -NewName 'C:\nodejs'
diff --git a/experimental/puppeteer-firefox/.cirrus.yml b/experimental/puppeteer-firefox/.cirrus.yml
deleted file mode 100644
index ce31e5c4065a9..0000000000000
--- a/experimental/puppeteer-firefox/.cirrus.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-env:
- DISPLAY: :99.0
-
-task:
- name: node10 (linux)
- container:
- dockerfile: .ci/node10/Dockerfile.linux
- xvfb_start_background_script: Xvfb :99 -ac -screen 0 1024x768x24
- install_script: npm install
- test_script: npm run fjunit
-
-task:
- name: node10 (macOS)
- osx_instance:
- image: high-sierra-base
- env:
- HOMEBREW_NO_AUTO_UPDATE: 1
- node_install_script:
- - brew install node@10
- - brew link --force node@10
- install_script: npm install
- test_script: npm run fjunit
-
-# task:
-# allow_failures: true
-# windows_container:
-# dockerfile: .ci/node10/Dockerfile.windows
-# os_version: 2016
-# name: node10 (windows)
-# install_script: npm install --unsafe-perm
-# test_script: npm run fjunit
diff --git a/experimental/puppeteer-firefox/.gitignore b/experimental/puppeteer-firefox/.gitignore
deleted file mode 100644
index e8dbe44365feb..0000000000000
--- a/experimental/puppeteer-firefox/.gitignore
+++ /dev/null
@@ -1,10 +0,0 @@
-/node_modules/
-.DS_Store
-*.swp
-*.pyc
-.vscode
-package-lock.json
-yarn.lock
-.local-browser
-/test/output-chromium
-/test/output-firefox
diff --git a/experimental/puppeteer-firefox/.npmignore b/experimental/puppeteer-firefox/.npmignore
deleted file mode 100644
index 4578220f3cc3c..0000000000000
--- a/experimental/puppeteer-firefox/.npmignore
+++ /dev/null
@@ -1,36 +0,0 @@
-# exclude all tests
-test
-utils/node6-transform
-
-# exclude internal type definition files
-/lib/*.d.ts
-/node6/lib/*.d.ts
-
-# repeats from .gitignore
-node_modules
-.local-chromium
-.local-browser
-.dev_profile*
-.DS_Store
-*.swp
-*.pyc
-.vscode
-package-lock.json
-/node6/test
-/node6/utils
-/test
-/utils
-/docs
-yarn.lock
-
-# other
-/.ci
-/examples
-.appveyour.yml
-.cirrus.yml
-.editorconfig
-.eslintignore
-.eslintrc.js
-README.md
-tsconfig.json
-
diff --git a/experimental/puppeteer-firefox/DeviceDescriptors.js b/experimental/puppeteer-firefox/DeviceDescriptors.js
deleted file mode 100644
index 05135a115368c..0000000000000
--- a/experimental/puppeteer-firefox/DeviceDescriptors.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * Copyright 2019 Google Inc. All rights reserved.
- *
- * 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.
- */
-
-module.exports = require('./lib/DeviceDescriptors');
diff --git a/experimental/puppeteer-firefox/Errors.js b/experimental/puppeteer-firefox/Errors.js
deleted file mode 100644
index 94d6d41226846..0000000000000
--- a/experimental/puppeteer-firefox/Errors.js
+++ /dev/null
@@ -1 +0,0 @@
-module.exports = require('./lib/Errors');
diff --git a/experimental/puppeteer-firefox/LICENSE b/experimental/puppeteer-firefox/LICENSE
deleted file mode 100644
index afdfe50e72e0e..0000000000000
--- a/experimental/puppeteer-firefox/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright 2017 Google Inc.
-
- 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.
diff --git a/experimental/puppeteer-firefox/README.md b/experimental/puppeteer-firefox/README.md
deleted file mode 100644
index dc4c2a73f83b7..0000000000000
--- a/experimental/puppeteer-firefox/README.md
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-# Prototype: Puppeteer for Firefox
-
-**⚠️ The puppeteer-firefox package has been deprecated**: Firefox support is gradually transitioning to the puppeteer package. As of puppeteer v2.1.0 you can interact with Firefox Nightly. The puppeteer-firefox package will remain available until the transition is complete, but it is no longer actively maintained. For more information visit https://wiki.mozilla.org/Remote
-
-This project is an experimental feasibility prototype to guide the work of implementing Puppeteer endpoints into Firefox's code base. Mozilla's [bug 1545057](https://bugzilla.mozilla.org/show_bug.cgi?id=1545057) tracks the initial milestone, which will be based on a CDP-based [remote protocol](https://wiki.mozilla.org/Remote).
-
-## Getting Started
-
-### Installation
-
-To try out Puppeteer with Firefox in your project, run:
-
-```bash
-npm i puppeteer-firefox
-# or "yarn add puppeteer-firefox"
-```
-
-Note: When you install puppeteer-firefox, it downloads a [custom-built Firefox](https://github.com/puppeteer/juggler) (Firefox/63.0.4) that is guaranteed to work with the API.
-
-### Usage
-
-**Example** - navigating to https://example.com and saving a screenshot as `example.png`:
-
-Save file as **example.js**
-
-```js
-const pptrFirefox = require('puppeteer-firefox');
-
-(async () => {
- const browser = await pptrFirefox.launch();
- const page = await browser.newPage();
- await page.goto('https://example.com');
- await page.screenshot({ path: 'example.png' });
- await browser.close();
-})();
-```
-
-Execute script on the command line
-
-```bash
-node example.js
-```
-
-### Credits
-
-Special thanks to [Amine Bouhlali](https://bitbucket.org/aminerop/) who volunteered the [`puppeteer-firefox`](https://www.npmjs.com/package/puppeteer-firefox) NPM package.
diff --git a/experimental/puppeteer-firefox/examples/screenshot.js b/experimental/puppeteer-firefox/examples/screenshot.js
deleted file mode 100644
index 09d3ccfcee367..0000000000000
--- a/experimental/puppeteer-firefox/examples/screenshot.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * Copyright 2017 Google Inc. All rights reserved.
- *
- * 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';
-
-const puppeteer = require('puppeteer-firefox');
-
-(async() => {
- const browser = await puppeteer.launch();
- const page = await browser.newPage();
- await page.goto('http://example.com');
- await page.screenshot({path: 'example.png'});
- await browser.close();
-})();
diff --git a/experimental/puppeteer-firefox/examples/search.js b/experimental/puppeteer-firefox/examples/search.js
deleted file mode 100644
index 6f55e98173b46..0000000000000
--- a/experimental/puppeteer-firefox/examples/search.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * Copyright 2017 Google Inc. All rights reserved.
- *
- * 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.
- */
-
-/**
- * @fileoverview Search developers.google.com/web for articles tagged
- * "Headless Chrome" and scrape results from the results page.
- */
-
-'use strict';
-
-const puppeteer = require('puppeteer-firefox');
-
-(async() => {
- const browser = await puppeteer.launch();
- const page = await browser.newPage();
-
- await page.goto('https://developers.google.com/web/');
-
- // Type into search box.
- await page.type('.devsite-searchbox input', 'Headless Chrome');
-
- // Wait for suggest overlay to appear and click "show all results".
- const allResultsSelector = '.devsite-suggest-all-results';
- await page.waitForSelector(allResultsSelector);
- await page.click(allResultsSelector);
-
- // Wait for the results page to load and display the results.
- const resultsSelector = '.gsc-results .gsc-thumbnail-inside a.gs-title';
- await page.waitForSelector(resultsSelector);
-
- // Extract the results from the page.
- const links = await page.evaluate(resultsSelector => {
- const anchors = Array.from(document.querySelectorAll(resultsSelector));
- return anchors.map(anchor => {
- const title = anchor.textContent.split('|')[0].trim();
- return `${title} - ${anchor.href}`;
- });
- }, resultsSelector);
- console.log(links.join('\n'));
-
- await browser.close();
-})();
diff --git a/experimental/puppeteer-firefox/index.js b/experimental/puppeteer-firefox/index.js
deleted file mode 100644
index 08b942520ef04..0000000000000
--- a/experimental/puppeteer-firefox/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * Copyright 2018 Google Inc. All rights reserved.
- *
- * 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.
- */
-
-const {helper} = require('./lib/helper');
-const api = require('./lib/api');
-for (const className in api)
- helper.installAsyncStackHooks(api[className]);
-
-const {Puppeteer} = require('./lib/Puppeteer');
-const packageJson = require('./package.json');
-const preferredRevision = packageJson.puppeteer.firefox_revision;
-module.exports = new Puppeteer(__dirname, preferredRevision);
diff --git a/experimental/puppeteer-firefox/install.js b/experimental/puppeteer-firefox/install.js
deleted file mode 100644
index a7ead10b3b480..0000000000000
--- a/experimental/puppeteer-firefox/install.js
+++ /dev/null
@@ -1,97 +0,0 @@
-/**
- * Copyright 2018 Google Inc. All rights reserved.
- *
- * 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.
- */
-
-
-// puppeteer-core should not install anything.
-if (require('./package.json').name === 'puppeteer-core')
- return;
-
-const downloadHost = process.env.PUPPETEER_DOWNLOAD_HOST || process.env.npm_config_puppeteer_download_host || process.env.npm_package_config_puppeteer_download_host;
-const downloadPath = process.env.PUPPETEER_DOWNLOAD_PATH || process.env.npm_config_puppeteer_download_path || process.env.npm_package_config_puppeteer_download_path;
-
-const puppeteer = require('./index');
-const browserFetcher = puppeteer.createBrowserFetcher({ host: downloadHost, product: 'firefox', path: downloadPath });
-
-const revision = require('./package.json').puppeteer.firefox_revision;
-
-const revisionInfo = browserFetcher.revisionInfo(revision);
-
-// Do nothing if the revision is already downloaded.
-if (revisionInfo.local)
- return;
-
-// Override current environment proxy settings with npm configuration, if any.
-const NPM_HTTPS_PROXY = process.env.npm_config_https_proxy || process.env.npm_config_proxy;
-const NPM_HTTP_PROXY = process.env.npm_config_http_proxy || process.env.npm_config_proxy;
-const NPM_NO_PROXY = process.env.npm_config_no_proxy;
-
-if (NPM_HTTPS_PROXY)
- process.env.HTTPS_PROXY = NPM_HTTPS_PROXY;
-if (NPM_HTTP_PROXY)
- process.env.HTTP_PROXY = NPM_HTTP_PROXY;
-if (NPM_NO_PROXY)
- process.env.NO_PROXY = NPM_NO_PROXY;
-
-browserFetcher.download(revisionInfo.revision, onProgress)
- .then(() => browserFetcher.localRevisions())
- .then(onSuccess)
- .catch(onError);
-
-/**
- * @param {!Array}
- * @return {!Promise}
- */
-function onSuccess(localRevisions) {
- console.log('Firefox downloaded to ' + revisionInfo.folderPath);
- localRevisions = localRevisions.filter(revision => revision !== revisionInfo.revision);
- // Remove previous firefox revisions.
- const cleanupOldVersions = localRevisions.map(revision => browserFetcher.remove(revision));
- const installFirefoxPreferences = require('./misc/install-preferences');
- return Promise.all([...cleanupOldVersions, installFirefoxPreferences(revisionInfo.executablePath)]).then(() => {
- console.log('Firefox preferences installed!');
- });
-}
-
-/**
- * @param {!Error} error
- */
-function onError(error) {
- console.error(`ERROR: Failed to download Firefox r${revision}!`);
- console.error(error);
- process.exit(1);
-}
-
-let progressBar = null;
-let lastDownloadedBytes = 0;
-function onProgress(downloadedBytes, totalBytes) {
- if (!progressBar) {
- const ProgressBar = require('progress');
- progressBar = new ProgressBar(`Downloading Firefox+Puppeteer ${revision.substring(0, 8)} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `, {
- complete: '|',
- incomplete: ' ',
- width: 20,
- total: totalBytes,
- });
- }
- const delta = downloadedBytes - lastDownloadedBytes;
- lastDownloadedBytes = downloadedBytes;
- progressBar.tick(delta);
-}
-
-function toMegabytes(bytes) {
- const mb = bytes / 1024 / 1024;
- return `${Math.round(mb * 10) / 10} Mb`;
-}
diff --git a/experimental/puppeteer-firefox/lib/Accessibility.js b/experimental/puppeteer-firefox/lib/Accessibility.js
deleted file mode 100644
index 163576da2d9e0..0000000000000
--- a/experimental/puppeteer-firefox/lib/Accessibility.js
+++ /dev/null
@@ -1,322 +0,0 @@
-/**
- * @typedef {Object} SerializedAXNode
- * @property {string} role
- *
- * @property {string=} name
- * @property {string|number=} value
- * @property {string=} description
- *
- * @property {string=} keyshortcuts
- * @property {string=} roledescription
- * @property {string=} valuetext
- *
- * @property {boolean=} disabled
- * @property {boolean=} expanded
- * @property {boolean=} focused
- * @property {boolean=} modal
- * @property {boolean=} multiline
- * @property {boolean=} multiselectable
- * @property {boolean=} readonly
- * @property {boolean=} required
- * @property {boolean=} selected
- *
- * @property {boolean|"mixed"=} checked
- * @property {boolean|"mixed"=} pressed
- *
- * @property {number=} level
- *
- * @property {string=} autocomplete
- * @property {string=} haspopup
- * @property {string=} invalid
- * @property {string=} orientation
- *
- * @property {Array=} children
- */
-
-class Accessibility {
- constructor(session) {
- this._session = session;
- }
-
- /**
- * @param {{interestingOnly?: boolean}=} options
- * @return {!Promise}
- */
- async snapshot(options = {}) {
- const {interestingOnly = true} = options;
- const {tree} = await this._session.send('Accessibility.getFullAXTree');
- const root = new AXNode(tree);
- if (!interestingOnly)
- return serializeTree(root)[0];
-
- /** @type {!Set} */
- const interestingNodes = new Set();
- collectInterestingNodes(interestingNodes, root, false);
- return serializeTree(root, interestingNodes)[0];
- }
-}
-
-/**
- * @param {!Set} collection
- * @param {!AXNode} node
- * @param {boolean} insideControl
- */
-function collectInterestingNodes(collection, node, insideControl) {
- if (node.isInteresting(insideControl))
- collection.add(node);
- if (node.isLeafNode())
- return;
- insideControl = insideControl || node.isControl();
- for (const child of node._children)
- collectInterestingNodes(collection, child, insideControl);
-}
-
-/**
- * @param {!AXNode} node
- * @param {!Set=} whitelistedNodes
- * @return {!Array}
- */
-function serializeTree(node, whitelistedNodes) {
- /** @type {!Array} */
- const children = [];
- for (const child of node._children)
- children.push(...serializeTree(child, whitelistedNodes));
-
- if (whitelistedNodes && !whitelistedNodes.has(node))
- return children;
-
- const serializedNode = node.serialize();
- if (children.length)
- serializedNode.children = children;
- return [serializedNode];
-}
-
-
-class AXNode {
- constructor(payload) {
- this._payload = payload;
-
- /** @type {!Array} */
- this._children = (payload.children || []).map(x => new AXNode(x));
-
- this._editable = payload.editable;
- this._richlyEditable = this._editable && (payload.tag !== 'textarea' && payload.tag !== 'input');
- this._focusable = payload.focusable;
- this._expanded = payload.expanded;
- this._name = this._payload.name;
- this._role = this._payload.role;
- this._cachedHasFocusableChild;
- }
-
- /**
- * @return {boolean}
- */
- _isPlainTextField() {
- if (this._richlyEditable)
- return false;
- if (this._editable)
- return true;
- return this._role === 'entry';
- }
-
- /**
- * @return {boolean}
- */
- _isTextOnlyObject() {
- const role = this._role;
- return (role === 'text leaf' || role === 'text' || role === 'statictext');
- }
-
- /**
- * @return {boolean}
- */
- _hasFocusableChild() {
- if (this._cachedHasFocusableChild === undefined) {
- this._cachedHasFocusableChild = false;
- for (const child of this._children) {
- if (child._focusable || child._hasFocusableChild()) {
- this._cachedHasFocusableChild = true;
- break;
- }
- }
- }
- return this._cachedHasFocusableChild;
- }
-
- /**
- * @return {boolean}
- */
- isLeafNode() {
- if (!this._children.length)
- return true;
-
- // These types of objects may have children that we use as internal
- // implementation details, but we want to expose them as leaves to platform
- // accessibility APIs because screen readers might be confused if they find
- // any children.
- if (this._isPlainTextField() || this._isTextOnlyObject())
- return true;
-
- // Roles whose children are only presentational according to the ARIA and
- // HTML5 Specs should be hidden from screen readers.
- // (Note that whilst ARIA buttons can have only presentational children, HTML5
- // buttons are allowed to have content.)
- switch (this._role) {
- case 'graphic':
- case 'scrollbar':
- case 'slider':
- case 'separator':
- case 'progressbar':
- return true;
- default:
- break;
- }
-
- // Here and below: Android heuristics
- if (this._hasFocusableChild())
- return false;
- if (this._focusable && this._name)
- return true;
- if (this._role === 'heading' && this._name)
- return true;
- return false;
- }
-
- /**
- * @return {boolean}
- */
- isControl() {
- switch (this._role) {
- case 'checkbutton':
- case 'check menu item':
- case 'check rich option':
- case 'combobox':
- case 'combobox option':
- case 'color chooser':
- case 'listbox':
- case 'listbox option':
- case 'listbox rich option':
- case 'popup menu':
- case 'menupopup':
- case 'menuitem':
- case 'menubar':
- case 'button':
- case 'pushbutton':
- case 'radiobutton':
- case 'radio menuitem':
- case 'scrollbar':
- case 'slider':
- case 'spinbutton':
- case 'switch':
- case 'pagetab':
- case 'entry':
- case 'tree table':
- return true;
- default:
- return false;
- }
- }
-
- /**
- * @param {boolean} insideControl
- * @return {boolean}
- */
- isInteresting(insideControl) {
- if (this._focusable || this._richlyEditable)
- return true;
-
- // If it's not focusable but has a control role, then it's interesting.
- if (this.isControl())
- return true;
-
- // A non focusable child of a control is not interesting
- if (insideControl)
- return false;
-
- return this.isLeafNode() && !!this._name.trim();
- }
-
- /**
- * @return {!SerializedAXNode}
- */
- serialize() {
- /** @type {SerializedAXNode} */
- const node = {
- role: this._role
- };
-
- /** @type {!Array} */
- const userStringProperties = [
- 'name',
- 'value',
- 'description',
- 'roledescription',
- 'valuetext',
- 'keyshortcuts',
- ];
- for (const userStringProperty of userStringProperties) {
- if (!(userStringProperty in this._payload))
- continue;
- node[userStringProperty] = this._payload[userStringProperty];
- }
- /** @type {!Array} */
- const booleanProperties = [
- 'disabled',
- 'expanded',
- 'focused',
- 'modal',
- 'multiline',
- 'multiselectable',
- 'readonly',
- 'required',
- 'selected',
- ];
- for (const booleanProperty of booleanProperties) {
- if (this._role === 'document' && booleanProperty === 'focused')
- continue; // document focusing is strange
- const value = this._payload[booleanProperty];
- if (!value)
- continue;
- node[booleanProperty] = value;
- }
-
- /** @type {!Array} */
- const tristateProperties = [
- 'checked',
- 'pressed',
- ];
- for (const tristateProperty of tristateProperties) {
- if (!(tristateProperty in this._payload))
- continue;
- const value = this._payload[tristateProperty];
- node[tristateProperty] = value;
- }
- /** @type {!Array} */
- const numericalProperties = [
- 'level',
- 'valuemax',
- 'valuemin',
- ];
- for (const numericalProperty of numericalProperties) {
- if (!(numericalProperty in this._payload))
- continue;
- node[numericalProperty] = this._payload[numericalProperty];
- }
- /** @type {!Array} */
- const tokenProperties = [
- 'autocomplete',
- 'haspopup',
- 'invalid',
- 'orientation',
- ];
- for (const tokenProperty of tokenProperties) {
- const value = this._payload[tokenProperty];
- if (!value || value === 'false')
- continue;
- node[tokenProperty] = value;
- }
- return node;
- }
-}
-
-module.exports = {Accessibility};
diff --git a/experimental/puppeteer-firefox/lib/Browser.js b/experimental/puppeteer-firefox/lib/Browser.js
deleted file mode 100644
index fd234500e2c9c..0000000000000
--- a/experimental/puppeteer-firefox/lib/Browser.js
+++ /dev/null
@@ -1,380 +0,0 @@
-const {helper, assert} = require('./helper');
-const {Page} = require('./Page');
-const {Events} = require('./Events');
-const EventEmitter = require('events');
-
-class Browser extends EventEmitter {
- /**
- * @param {!Puppeteer.Connection} connection
- * @param {?Puppeteer.Viewport} defaultViewport
- * @param {?Puppeteer.ChildProcess} process
- * @param {function():void} closeCallback
- */
- static async create(connection, defaultViewport, process, closeCallback) {
- const {browserContextIds} = await connection.send('Target.getBrowserContexts');
- const browser = new Browser(connection, browserContextIds, defaultViewport, process, closeCallback);
- await connection.send('Target.enable');
- return browser;
- }
-
- /**
- * @param {!Puppeteer.Connection} connection
- * @param {!Array} browserContextIds
- * @param {?Puppeteer.Viewport} defaultViewport
- * @param {?Puppeteer.ChildProcess} process
- * @param {function():void} closeCallback
- */
- constructor(connection, browserContextIds, defaultViewport, process, closeCallback) {
- super();
- this._connection = connection;
- this._defaultViewport = defaultViewport;
- this._process = process;
- this._closeCallback = closeCallback;
-
- /** @type {!Map} */
- this._targets = new Map();
-
- this._defaultContext = new BrowserContext(this._connection, this, null);
- /** @type {!Map} */
- this._contexts = new Map();
- for (const browserContextId of browserContextIds)
- this._contexts.set(browserContextId, new BrowserContext(this._connection, this, browserContextId));
-
- this._connection.on(Events.Connection.Disconnected, () => this.emit(Events.Browser.Disconnected));
-
- this._eventListeners = [
- helper.addEventListener(this._connection, 'Target.targetCreated', this._onTargetCreated.bind(this)),
- helper.addEventListener(this._connection, 'Target.targetDestroyed', this._onTargetDestroyed.bind(this)),
- helper.addEventListener(this._connection, 'Target.targetInfoChanged', this._onTargetInfoChanged.bind(this)),
- ];
- }
-
- wsEndpoint() {
- return this._connection.url();
- }
-
- disconnect() {
- this._connection.dispose();
- }
-
- /**
- * @return {boolean}
- */
- isConnected() {
- return !this._connection._closed;
- }
-
- /**
- * @return {!BrowserContext}
- */
- async createIncognitoBrowserContext() {
- const {browserContextId} = await this._connection.send('Target.createBrowserContext');
- const context = new BrowserContext(this._connection, this, browserContextId);
- this._contexts.set(browserContextId, context);
- return context;
- }
-
- /**
- * @return {!Array}
- */
- browserContexts() {
- return [this._defaultContext, ...Array.from(this._contexts.values())];
- }
-
- defaultBrowserContext() {
- return this._defaultContext;
- }
-
- async _disposeContext(browserContextId) {
- await this._connection.send('Target.removeBrowserContext', {browserContextId});
- this._contexts.delete(browserContextId);
- }
-
- /**
- * @return {!Promise}
- */
- async userAgent() {
- const info = await this._connection.send('Browser.getInfo');
- return info.userAgent;
- }
-
- /**
- * @return {!Promise}
- */
- async version() {
- const info = await this._connection.send('Browser.getInfo');
- return info.version;
- }
-
- /**
- * @return {?Puppeteer.ChildProcess}
- */
- process() {
- return this._process;
- }
-
- /**
- * @param {function(!Target):boolean|Promise} predicate
- * @param {{timeout?: number}=} options
- * @return {!Promise}
- */
- async waitForTarget(predicate, options = {}) {
- const {
- timeout = 30000
- } = options;
- let resolve;
- const targetPromise = new Promise(x => resolve = x);
- this.on(Events.Browser.TargetCreated, check);
- this.on('targetchanged', check);
- try {
- if (!timeout)
- return await targetPromise;
- return await helper.waitWithTimeout(
- Promise.race([
- targetPromise,
- (async () => {
- for (const target of this.targets()) {
- if (await predicate(target)) {
- return target;
- }
- }
- await targetPromise;
- })(),
- ]),
- 'target',
- timeout
- );
- } finally {
- this.removeListener(Events.Browser.TargetCreated, check);
- this.removeListener('targetchanged', check);
- }
-
- /**
- * @param {!Target} target
- */
- async function check(target) {
- if (await predicate(target))
- resolve(target);
- }
- }
-
- /**
- * @return {Promise}
- */
- newPage() {
- return this._createPageInContext(this._defaultContext._browserContextId);
- }
-
- /**
- * @param {?string} browserContextId
- * @return {Promise}
- */
- async _createPageInContext(browserContextId) {
- const {targetId} = await this._connection.send('Target.newPage', {
- browserContextId: browserContextId || undefined
- });
- const target = this._targets.get(targetId);
- return await target.page();
- }
-
- async pages() {
- const pageTargets = Array.from(this._targets.values()).filter(target => target.type() === 'page');
- return await Promise.all(pageTargets.map(target => target.page()));
- }
-
- targets() {
- return Array.from(this._targets.values());
- }
-
- target() {
- return this.targets().find(target => target.type() === 'browser');
- }
-
- async _onTargetCreated({targetId, url, browserContextId, openerId, type}) {
- const context = browserContextId ? this._contexts.get(browserContextId) : this._defaultContext;
- const target = new Target(this._connection, this, context, targetId, type, url, openerId);
- this._targets.set(targetId, target);
- if (target.opener() && target.opener()._pagePromise) {
- const openerPage = await target.opener()._pagePromise;
- if (openerPage.listenerCount(Events.Page.Popup)) {
- const popupPage = await target.page();
- openerPage.emit(Events.Page.Popup, popupPage);
- }
- }
- this.emit(Events.Browser.TargetCreated, target);
- context.emit(Events.BrowserContext.TargetCreated, target);
- }
-
- _onTargetDestroyed({targetId}) {
- const target = this._targets.get(targetId);
- this._targets.delete(targetId);
- target._closedCallback();
- this.emit(Events.Browser.TargetDestroyed, target);
- target.browserContext().emit(Events.BrowserContext.TargetDestroyed, target);
- }
-
- _onTargetInfoChanged({targetId, url}) {
- const target = this._targets.get(targetId);
- target._url = url;
- this.emit(Events.Browser.TargetChanged, target);
- target.browserContext().emit(Events.BrowserContext.TargetChanged, target);
- }
-
- async close() {
- helper.removeEventListeners(this._eventListeners);
- await this._closeCallback();
- }
-}
-
-class Target {
- /**
- *
- * @param {*} connection
- * @param {!Browser} browser
- * @param {!BrowserContext} context
- * @param {string} targetId
- * @param {string} type
- * @param {string} url
- * @param {string=} openerId
- */
- constructor(connection, browser, context, targetId, type, url, openerId) {
- this._browser = browser;
- this._context = context;
- this._connection = connection;
- this._targetId = targetId;
- this._type = type;
- /** @type {?Promise} */
- this._pagePromise = null;
- this._url = url;
- this._openerId = openerId;
- this._isClosedPromise = new Promise(fulfill => this._closedCallback = fulfill);
- }
-
- /**
- * @return {?Target}
- */
- opener() {
- return this._openerId ? this._browser._targets.get(this._openerId) : null;
- }
-
- /**
- * @return {"page"|"browser"}
- */
- type() {
- return this._type;
- }
-
- url() {
- return this._url;
- }
-
- /**
- * @return {!BrowserContext}
- */
- browserContext() {
- return this._context;
- }
-
- async page() {
- if (this._type === 'page' && !this._pagePromise) {
- const session = await this._connection.createSession(this._targetId);
- this._pagePromise = Page.create(session, this, this._browser._defaultViewport);
- }
- return this._pagePromise;
- }
-
- browser() {
- return this._browser;
- }
-}
-
-class BrowserContext extends EventEmitter {
- /**
- * @param {!Puppeteer.Connection} connection
- * @param {!Browser} browser
- * @param {?string} browserContextId
- */
- constructor(connection, browser, browserContextId) {
- super();
- this._connection = connection;
- this._browser = browser;
- this._browserContextId = browserContextId;
- }
-
- /**
- * @param {string} origin
- * @param {!Array} permissions
- */
- async overridePermissions(origin, permissions) {
- const webPermissionToProtocol = new Map([
- ['geolocation', 'geo'],
- ['microphone', 'microphone'],
- ['camera', 'camera'],
- ['notifications', 'desktop-notifications'],
- ]);
- permissions = permissions.map(permission => {
- const protocolPermission = webPermissionToProtocol.get(permission);
- if (!protocolPermission)
- throw new Error('Unknown permission: ' + permission);
- return protocolPermission;
- });
- await this._connection.send('Browser.grantPermissions', {origin, browserContextId: this._browserContextId || undefined, permissions});
- }
-
- async clearPermissionOverrides() {
- await this._connection.send('Browser.resetPermissions', {browserContextId: this._browserContextId || undefined});
- }
-
- /**
- * @return {Array}
- */
- targets() {
- return this._browser.targets().filter(target => target.browserContext() === this);
- }
-
- /**
- * @return {Promise>}
- */
- async pages() {
- const pages = await Promise.all(
- this.targets()
- .filter(target => target.type() === 'page')
- .map(target => target.page())
- );
- return pages.filter(page => !!page);
- }
-
- /**
- * @param {function(Target):boolean|Promise} predicate
- * @param {{timeout?: number}=} options
- * @return {!Promise}
- */
- waitForTarget(predicate, options) {
- return this._browser.waitForTarget(target => target.browserContext() === this && predicate(target), options);
- }
-
- /**
- * @return {boolean}
- */
- isIncognito() {
- return !!this._browserContextId;
- }
-
- newPage() {
- return this._browser._createPageInContext(this._browserContextId);
- }
-
- /**
- * @return {!Browser}
- */
- browser() {
- return this._browser;
- }
-
- async close() {
- assert(this._browserContextId, 'Non-incognito contexts cannot be closed!');
- await this._browser._disposeContext(this._browserContextId);
- }
-}
-
-module.exports = {Browser, BrowserContext, Target};
diff --git a/experimental/puppeteer-firefox/lib/BrowserFetcher.js b/experimental/puppeteer-firefox/lib/BrowserFetcher.js
deleted file mode 100644
index cbd4aa42fc9cf..0000000000000
--- a/experimental/puppeteer-firefox/lib/BrowserFetcher.js
+++ /dev/null
@@ -1,342 +0,0 @@
-/**
- * Copyright 2017 Google Inc. All rights reserved.
- *
- * 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.
- */
-
-const os = require('os');
-const fs = require('fs');
-const path = require('path');
-const extract = require('extract-zip');
-const util = require('util');
-const URL = require('url');
-const {helper, assert} = require('./helper');
-const removeRecursive = require('rimraf');
-// @ts-ignore
-const ProxyAgent = require('https-proxy-agent');
-// @ts-ignore
-const getProxyForUrl = require('proxy-from-env').getProxyForUrl;
-
-const downloadURLs = {
- chromium: {
- host: 'https://storage.googleapis.com',
- linux: '%s/chromium-browser-snapshots/Linux_x64/%s/%s.zip',
- mac: '%s/chromium-browser-snapshots/Mac/%s/%s.zip',
- win32: '%s/chromium-browser-snapshots/Win/%s/%s.zip',
- win64: '%s/chromium-browser-snapshots/Win_x64/%s/%s.zip',
- },
- firefox: {
- host: 'https://github.com/puppeteer/juggler/releases',
- linux: '%s/download/%s/%s.zip',
- mac: '%s/download/%s/%s.zip',
- win32: '%s/download/%s/%s.zip',
- win64: '%s/download/%s/%s.zip',
- },
-};
-
-/**
- * @param {string} product
- * @param {string} platform
- * @param {string} revision
- * @return {string}
- */
-function archiveName(product, platform, revision) {
- if (product === 'chromium') {
- if (platform === 'linux')
- return 'chrome-linux';
- if (platform === 'mac')
- return 'chrome-mac';
- if (platform === 'win32' || platform === 'win64') {
- // Windows archive name changed at r591479.
- return parseInt(revision, 10) > 591479 ? 'chrome-win' : 'chrome-win32';
- }
- } else if (product === 'firefox') {
- if (platform === 'linux')
- return 'firefox-linux';
- if (platform === 'mac')
- return 'firefox-mac';
- if (platform === 'win32' || platform === 'win64')
- return 'firefox-' + platform;
- }
- return null;
-}
-
-/**
- * @param {string} product
- * @param {string} platform
- * @param {string} host
- * @param {string} revision
- * @return {string}
- */
-function downloadURL(product, platform, host, revision) {
- const url = util.format(downloadURLs[product][platform], host, revision, archiveName(product, platform, revision));
- return url;
-}
-
-const readdirAsync = helper.promisify(fs.readdir.bind(fs));
-const mkdirAsync = helper.promisify(fs.mkdir.bind(fs));
-const unlinkAsync = helper.promisify(fs.unlink.bind(fs));
-const chmodAsync = helper.promisify(fs.chmod.bind(fs));
-
-function existsAsync(filePath) {
- let fulfill = null;
- const promise = new Promise(x => fulfill = x);
- fs.access(filePath, err => fulfill(!err));
- return promise;
-}
-
-class BrowserFetcher {
- /**
- * @param {string} projectRoot
- * @param {!BrowserFetcher.Options=} options
- */
- constructor(projectRoot, options = {}) {
- this._product = (options.product || 'chromium').toLowerCase();
- assert(this._product === 'chromium' || this._product === 'firefox', `Unknown product: "${options.product}"`);
- this._downloadsFolder = options.path || path.join(projectRoot, '.local-browser');
- this._downloadHost = options.host || downloadURLs[this._product].host;
- this._platform = options.platform || '';
- if (!this._platform) {
- const platform = os.platform();
- if (platform === 'darwin')
- this._platform = 'mac';
- else if (platform === 'linux')
- this._platform = 'linux';
- else if (platform === 'win32')
- this._platform = os.arch() === 'x64' ? 'win64' : 'win32';
- assert(this._platform, 'Unsupported platform: ' + os.platform());
- }
- assert(downloadURLs[this._product][this._platform], 'Unsupported platform: ' + this._platform);
- }
-
- /**
- * @return {string}
- */
- platform() {
- return this._platform;
- }
-
- /**
- * @param {string} revision
- * @return {!Promise}
- */
- canDownload(revision) {
- const url = downloadURL(this._product, this._platform, this._downloadHost, revision);
- let resolve;
- const promise = new Promise(x => resolve = x);
- const request = httpRequest(url, 'HEAD', response => {
- resolve(response.statusCode === 200);
- });
- request.on('error', error => {
- console.error(error);
- resolve(false);
- });
- return promise;
- }
-
- /**
- * @param {string} revision
- * @param {?function(number, number)} progressCallback
- * @return {!Promise}
- */
- async download(revision, progressCallback) {
- const url = downloadURL(this._product, this._platform, this._downloadHost, revision);
- const zipPath = path.join(this._downloadsFolder, `download-${this._product}-${this._platform}-${revision}.zip`);
- const folderPath = this._getFolderPath(revision);
- if (await existsAsync(folderPath))
- return this.revisionInfo(revision);
- if (!(await existsAsync(this._downloadsFolder)))
- await mkdirAsync(this._downloadsFolder);
- try {
- await downloadFile(url, zipPath, progressCallback);
- await extractZip(zipPath, folderPath);
- } finally {
- if (await existsAsync(zipPath))
- await unlinkAsync(zipPath);
- }
- const revisionInfo = this.revisionInfo(revision);
- if (revisionInfo)
- await chmodAsync(revisionInfo.executablePath, 0o755);
- return revisionInfo;
- }
-
- /**
- * @return {!Promise>}
- */
- async localRevisions() {
- if (!await existsAsync(this._downloadsFolder))
- return [];
- const fileNames = await readdirAsync(this._downloadsFolder);
- return fileNames.map(fileName => parseFolderPath(fileName)).filter(entry => entry && entry.platform === this._platform).map(entry => entry.revision);
- }
-
- /**
- * @param {string} revision
- */
- async remove(revision) {
- const folderPath = this._getFolderPath(revision);
- assert(await existsAsync(folderPath), `Failed to remove: revision ${revision} is not downloaded`);
- await new Promise(fulfill => removeRecursive(folderPath, fulfill));
- }
-
- /**
- * @param {string} revision
- * @return {!BrowserFetcher.RevisionInfo}
- */
- revisionInfo(revision) {
- const folderPath = this._getFolderPath(revision);
- let executablePath = '';
- if (this._product === 'chromium') {
- if (this._platform === 'mac')
- executablePath = path.join(folderPath, archiveName(this._product, this._platform, revision), 'Chromium.app', 'Contents', 'MacOS', 'Chromium');
- else if (this._platform === 'linux')
- executablePath = path.join(folderPath, archiveName(this._product, this._platform, revision), 'chrome');
- else if (this._platform === 'win32' || this._platform === 'win64')
- executablePath = path.join(folderPath, archiveName(this._product, this._platform, revision), 'chrome.exe');
- else
- throw new Error('Unsupported platform: ' + this._platform);
- } else if (this._product === 'firefox') {
- if (this._platform === 'mac')
- executablePath = path.join(folderPath, 'firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox');
- else if (this._platform === 'linux')
- executablePath = path.join(folderPath, 'firefox', 'firefox');
- else if (this._platform === 'win32' || this._platform === 'win64')
- executablePath = path.join(folderPath, 'firefox', 'firefox.exe');
- else
- throw new Error('Unsupported platform: ' + this._platform);
- }
- const url = downloadURL(this._product, this._platform, this._downloadHost, revision);
- const local = fs.existsSync(folderPath);
- return {revision, executablePath, folderPath, local, url};
- }
-
- /**
- * @param {string} revision
- * @return {string}
- */
- _getFolderPath(revision) {
- return path.join(this._downloadsFolder, this._product + '-' + this._platform + '-' + revision);
- }
-}
-
-module.exports = {BrowserFetcher};
-
-/**
- * @param {string} folderPath
- * @return {?{platform: string, revision: string}}
- */
-function parseFolderPath(folderPath) {
- const name = path.basename(folderPath);
- const splits = name.split('-');
- if (splits.length !== 3)
- return null;
- const [product, platform, revision] = splits;
- if (!downloadURLs[product][platform])
- return null;
- return {platform, revision};
-}
-
-/**
- * @param {string} url
- * @param {string} destinationPath
- * @param {?function(number, number)} progressCallback
- * @return {!Promise}
- */
-function downloadFile(url, destinationPath, progressCallback) {
- let fulfill, reject;
- let downloadedBytes = 0;
- let totalBytes = 0;
-
- const promise = new Promise((x, y) => { fulfill = x; reject = y; });
-
- const request = httpRequest(url, 'GET', response => {
- if (response.statusCode !== 200) {
- const error = new Error(`Download failed: server returned code ${response.statusCode}. URL: ${url}`);
- // consume response data to free up memory
- response.resume();
- reject(error);
- return;
- }
- const file = fs.createWriteStream(destinationPath);
- file.on('finish', () => fulfill());
- file.on('error', error => reject(error));
- response.pipe(file);
- totalBytes = parseInt(/** @type {string} */ (response.headers['content-length']), 10);
- if (progressCallback)
- response.on('data', onData);
- });
- request.on('error', error => reject(error));
- return promise;
-
- function onData(chunk) {
- downloadedBytes += chunk.length;
- progressCallback(downloadedBytes, totalBytes);
- }
-}
-
-/**
- * @param {string} zipPath
- * @param {string} folderPath
- * @return {!Promise}
- */
-async function extractZip(zipPath, folderPath) {
- try {
- await extract(zipPath, {dir: folderPath});
- } catch (error) {
- return error;
- }
-}
-
-function httpRequest(url, method, response) {
- /** @type {Object} */
- const options = URL.parse(url);
- options.method = method;
-
- const proxyURL = getProxyForUrl(url);
- if (proxyURL) {
- /** @type {Object} */
- const parsedProxyURL = URL.parse(proxyURL);
- parsedProxyURL.secureProxy = parsedProxyURL.protocol === 'https:';
-
- options.agent = new ProxyAgent(parsedProxyURL);
- options.rejectUnauthorized = false;
- }
-
- const requestCallback = res => {
- if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location)
- httpRequest(res.headers.location, method, response);
- else
- response(res);
- };
- const request = options.protocol === 'https:' ?
- require('https').request(options, requestCallback) :
- require('http').request(options, requestCallback);
- request.end();
- return request;
-}
-
-/**
- * @typedef {Object} BrowserFetcher.Options
- * @property {string=} platform
- * @property {string=} path
- * @property {string=} host
- */
-
-/**
- * @typedef {Object} BrowserFetcher.RevisionInfo
- * @property {string} folderPath
- * @property {string} executablePath
- * @property {string} url
- * @property {boolean} local
- * @property {string} revision
- */
diff --git a/experimental/puppeteer-firefox/lib/Connection.js b/experimental/puppeteer-firefox/lib/Connection.js
deleted file mode 100644
index d058575815a0e..0000000000000
--- a/experimental/puppeteer-firefox/lib/Connection.js
+++ /dev/null
@@ -1,242 +0,0 @@
-/**
- * Copyright 2017 Google Inc. All rights reserved.
- *
- * 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.
- */
-const {assert} = require('./helper');
-const {Events} = require('./Events');
-const debugProtocol = require('debug')('puppeteer:protocol');
-const EventEmitter = require('events');
-
-class Connection extends EventEmitter {
- /**
- * @param {string} url
- * @param {!Puppeteer.ConnectionTransport} transport
- * @param {number=} delay
- */
- constructor(url, transport, delay = 0) {
- super();
- this._url = url;
- this._lastId = 0;
- /** @type {!Map}*/
- this._callbacks = new Map();
- this._delay = delay;
-
- this._transport = transport;
- this._transport.onmessage = this._onMessage.bind(this);
- this._transport.onclose = this._onClose.bind(this);
- /** @type {!Map}*/
- this._sessions = new Map();
- this._closed = false;
- }
-
- /**
- * @param {!JugglerSession} session
- * @return {!Connection}
- */
- static fromSession(session) {
- return session._connection;
- }
-
- /**
- * @param {string} sessionId
- * @return {?JugglerSession}
- */
- session(sessionId) {
- return this._sessions.get(sessionId) || null;
- }
-
- /**
- * @return {string}
- */
- url() {
- return this._url;
- }
-
- /**
- * @param {string} method
- * @param {!Object=} params
- * @return {!Promise}
- */
- send(method, params = {}) {
- const id = this._rawSend({method, params});
- return new Promise((resolve, reject) => {
- this._callbacks.set(id, {resolve, reject, error: new Error(), method});
- });
- }
-
- /**
- * @param {*} message
- * @return {number}
- */
- _rawSend(message) {
- const id = ++this._lastId;
- message = JSON.stringify(Object.assign({}, message, {id}));
- debugProtocol('SEND ► ' + message);
- this._transport.send(message);
- return id;
- }
-
- /**
- * @param {string} message
- */
- async _onMessage(message) {
- if (this._delay)
- await new Promise(f => setTimeout(f, this._delay));
- debugProtocol('◀ RECV ' + message);
- const object = JSON.parse(message);
- if (object.method === 'Target.attachedToTarget') {
- const sessionId = object.params.sessionId;
- const session = new JugglerSession(this, object.params.targetInfo.type, sessionId);
- this._sessions.set(sessionId, session);
- } else if (object.method === 'Browser.detachedFromTarget') {
- const session = this._sessions.get(object.params.sessionId);
- if (session) {
- session._onClosed();
- this._sessions.delete(object.params.sessionId);
- }
- }
- if (object.sessionId) {
- const session = this._sessions.get(object.sessionId);
- if (session)
- session._onMessage(object);
- } else if (object.id) {
- const callback = this._callbacks.get(object.id);
- // Callbacks could be all rejected if someone has called `.dispose()`.
- if (callback) {
- this._callbacks.delete(object.id);
- if (object.error)
- callback.reject(createProtocolError(callback.error, callback.method, object));
- else
- callback.resolve(object.result);
- }
- } else {
- this.emit(object.method, object.params);
- }
- }
-
- _onClose() {
- if (this._closed)
- return;
- this._closed = true;
- this._transport.onmessage = null;
- this._transport.onclose = null;
- for (const callback of this._callbacks.values())
- callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));
- this._callbacks.clear();
- for (const session of this._sessions.values())
- session._onClosed();
- this._sessions.clear();
- this.emit(Events.Connection.Disconnected);
- }
-
- dispose() {
- this._onClose();
- this._transport.close();
- }
-
- /**
- * @param {string} targetId
- * @return {!Promise}
- */
- async createSession(targetId) {
- const {sessionId} = await this.send('Target.attachToTarget', {targetId});
- return this._sessions.get(sessionId);
- }
-}
-
-class JugglerSession extends EventEmitter {
- /**
- * @param {!Connection} connection
- * @param {string} targetType
- * @param {string} sessionId
- */
- constructor(connection, targetType, sessionId) {
- super();
- /** @type {!Map}*/
- this._callbacks = new Map();
- this._connection = connection;
- this._targetType = targetType;
- this._sessionId = sessionId;
- }
-
- /**
- * @param {string} method
- * @param {!Object=} params
- * @return {!Promise}
- */
- send(method, params = {}) {
- if (!this._connection)
- return Promise.reject(new Error(`Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.`));
- const id = this._connection._rawSend({sessionId: this._sessionId, method, params});
- return new Promise((resolve, reject) => {
- this._callbacks.set(id, {resolve, reject, error: new Error(), method});
- });
- }
-
- /**
- * @param {{id?: number, method: string, params: Object, error: {message: string, data: any}, result?: *}} object
- */
- _onMessage(object) {
- if (object.id && this._callbacks.has(object.id)) {
- const callback = this._callbacks.get(object.id);
- this._callbacks.delete(object.id);
- if (object.error)
- callback.reject(createProtocolError(callback.error, callback.method, object));
- else
- callback.resolve(object.result);
- } else {
- assert(!object.id);
- this.emit(object.method, object.params);
- }
- }
-
- async detach() {
- if (!this._connection)
- throw new Error(`Session already detached. Most likely the ${this._targetType} has been closed.`);
- await this._connection.send('Target.detachFromTarget', {sessionId: this._sessionId});
- }
-
- _onClosed() {
- for (const callback of this._callbacks.values())
- callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));
- this._callbacks.clear();
- this._connection = null;
- this.emit(Events.JugglerSession.Disconnected);
- }
-}
-
-/**
- * @param {!Error} error
- * @param {string} method
- * @param {{error: {message: string, data: any}}} object
- * @return {!Error}
- */
-function createProtocolError(error, method, object) {
- let message = `Protocol error (${method}): ${object.error.message}`;
- if ('data' in object.error)
- message += ` ${object.error.data}`;
- return rewriteError(error, message);
-}
-
-/**
- * @param {!Error} error
- * @param {string} message
- * @return {!Error}
- */
-function rewriteError(error, message) {
- error.message = message;
- return error;
-}
-
-module.exports = {Connection, JugglerSession};
diff --git a/experimental/puppeteer-firefox/lib/DOMWorld.js b/experimental/puppeteer-firefox/lib/DOMWorld.js
deleted file mode 100644
index 375624df66231..0000000000000
--- a/experimental/puppeteer-firefox/lib/DOMWorld.js
+++ /dev/null
@@ -1,636 +0,0 @@
-const {helper, assert} = require('./helper');
-const {TimeoutError} = require('./Errors');
-const fs = require('fs');
-const util = require('util');
-const readFileAsync = util.promisify(fs.readFile);
-
-class DOMWorld {
- constructor(frame, timeoutSettings) {
- this._frame = frame;
- this._timeoutSettings = timeoutSettings;
-
- this._documentPromise = null;
- this._contextPromise;
- this._contextResolveCallback = null;
- this._setContext(null);
-
- /** @type {!Set} */
- this._waitTasks = new Set();
- this._detached = false;
- }
-
- frame() {
- return this._frame;
- }
-
- _setContext(context) {
- if (context) {
- this._contextResolveCallback.call(null, context);
- this._contextResolveCallback = null;
- for (const waitTask of this._waitTasks)
- waitTask.rerun();
- } else {
- this._documentPromise = null;
- this._contextPromise = new Promise(fulfill => {
- this._contextResolveCallback = fulfill;
- });
- }
- }
-
- _detach() {
- this._detached = true;
- for (const waitTask of this._waitTasks)
- waitTask.terminate(new Error('waitForFunction failed: frame got detached.'));
- }
-
- async executionContext() {
- if (this._detached)
- throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`);
- return this._contextPromise;
- }
-
- async evaluateHandle(pageFunction, ...args) {
- const context = await this.executionContext();
- return context.evaluateHandle(pageFunction, ...args);
- }
-
- async evaluate(pageFunction, ...args) {
- const context = await this.executionContext();
- return context.evaluate(pageFunction, ...args);
- }
-
- /**
- * @param {string} selector
- * @return {!Promise}
- */
- async $(selector) {
- const document = await this._document();
- return document.$(selector);
- }
-
- _document() {
- if (!this._documentPromise)
- this._documentPromise = this.evaluateHandle('document').then(handle => handle.asElement());
- return this._documentPromise;
- }
-
- /**
- * @param {string} expression
- * @return {!Promise>}
- */
- async $x(expression) {
- const document = await this._document();
- return document.$x(expression);
- }
-
- /**
- * @param {string} selector
- * @param {Function|String} pageFunction
- * @param {!Array<*>} args
- * @return {!Promise<(!Object|undefined)>}
- */
- async $eval(selector, pageFunction, ...args) {
- const document = await this._document();
- return document.$eval(selector, pageFunction, ...args);
- }
-
- /**
- * @param {string} selector
- * @param {Function|String} pageFunction
- * @param {!Array<*>} args
- * @return {!Promise<(!Object|undefined)>}
- */
- async $$eval(selector, pageFunction, ...args) {
- const document = await this._document();
- return document.$$eval(selector, pageFunction, ...args);
- }
-
- /**
- * @param {string} selector
- * @return {!Promise>}
- */
- async $$(selector) {
- const document = await this._document();
- return document.$$(selector);
- }
-
- /**
- * @return {!Promise}
- */
- async content() {
- return await this.evaluate(() => {
- let retVal = '';
- if (document.doctype)
- retVal = new XMLSerializer().serializeToString(document.doctype);
- if (document.documentElement)
- retVal += document.documentElement.outerHTML;
- return retVal;
- });
- }
-
- /**
- * @param {string} html
- */
- async setContent(html) {
- await this.evaluate(html => {
- document.open();
- document.write(html);
- document.close();
- }, html);
- }
-
- /**
- * @param {!{content?: string, path?: string, type?: string, url?: string, id?: string}} options
- * @return {!Promise}
- */
- async addScriptTag(options) {
- const {
- type = '',
- id = ''
- } = options;
-
- if (typeof options.url === 'string') {
- const url = options.url;
- try {
- return (await this.evaluateHandle(addScriptUrl, url, id, type)).asElement();
- } catch (error) {
- throw new Error(`Loading script from ${url} failed`);
- }
- }
-
- if (typeof options.path === 'string') {
- let contents = await readFileAsync(options.path, 'utf8');
- contents += '//# sourceURL=' + options.path.replace(/\n/g, '');
- return (await this.evaluateHandle(addScriptContent, contents, id, type)).asElement();
- }
-
- if (typeof options.content === 'string') {
- return (await this.evaluateHandle(addScriptContent, options.content, id, type)).asElement();
- }
-
- throw new Error('Provide an object with a `url`, `path` or `content` property');
-
- /**
- * @param {string} url
- * @param {string} id
- * @param {string} type
- * @return {!Promise}
- */
- async function addScriptUrl(url, id, type) {
- const script = document.createElement('script');
- script.src = url;
- if (id)
- script.id = id;
- if (type)
- script.type = type;
- const promise = new Promise((res, rej) => {
- script.onload = res;
- script.onerror = rej;
- });
- document.head.appendChild(script);
- await promise;
- return script;
- }
-
- /**
- * @param {string} content
- * @param {string} id
- * @param {string} type
- * @return {!HTMLElement}
- */
- function addScriptContent(content, id, type = 'text/javascript') {
- const script = document.createElement('script');
- script.type = type;
- script.text = content;
- if (id)
- script.id = id;
- let error = null;
- script.onerror = e => error = e;
- document.head.appendChild(script);
- if (error)
- throw error;
- return script;
- }
- }
-
- /**
- * @param {!{content?: string, path?: string, url?: string}} options
- * @return {!Promise}
- */
- async addStyleTag(options) {
- if (typeof options.url === 'string') {
- const url = options.url;
- try {
- return (await this.evaluateHandle(addStyleUrl, url)).asElement();
- } catch (error) {
- throw new Error(`Loading style from ${url} failed`);
- }
- }
-
- if (typeof options.path === 'string') {
- let contents = await readFileAsync(options.path, 'utf8');
- contents += '/*# sourceURL=' + options.path.replace(/\n/g, '') + '*/';
- return (await this.evaluateHandle(addStyleContent, contents)).asElement();
- }
-
- if (typeof options.content === 'string') {
- return (await this.evaluateHandle(addStyleContent, options.content)).asElement();
- }
-
- throw new Error('Provide an object with a `url`, `path` or `content` property');
-
- /**
- * @param {string} url
- * @return {!Promise}
- */
- async function addStyleUrl(url) {
- const link = document.createElement('link');
- link.rel = 'stylesheet';
- link.href = url;
- const promise = new Promise((res, rej) => {
- link.onload = res;
- link.onerror = rej;
- });
- document.head.appendChild(link);
- await promise;
- return link;
- }
-
- /**
- * @param {string} content
- * @return {!Promise}
- */
- async function addStyleContent(content) {
- const style = document.createElement('style');
- style.type = 'text/css';
- style.appendChild(document.createTextNode(content));
- const promise = new Promise((res, rej) => {
- style.onload = res;
- style.onerror = rej;
- });
- document.head.appendChild(style);
- await promise;
- return style;
- }
- }
-
- /**
- * @param {string} selector
- * @param {!{delay?: number, button?: string, clickCount?: number}=} options
- */
- async click(selector, options = {}) {
- const handle = await this.$(selector);
- assert(handle, 'No node found for selector: ' + selector);
- await handle.click(options);
- await handle.dispose();
- }
-
- /**
- * @param {string} selector
- */
- async focus(selector) {
- const handle = await this.$(selector);
- assert(handle, 'No node found for selector: ' + selector);
- await handle.focus();
- await handle.dispose();
- }
-
- /**
- * @param {string} selector
- */
- async hover(selector) {
- const handle = await this.$(selector);
- assert(handle, 'No node found for selector: ' + selector);
- await handle.hover();
- await handle.dispose();
- }
-
- /**
- * @param {string} selector
- * @param {!Array} values
- * @return {!Promise>}
- */
- select(selector, ...values) {
- for (const value of values)
- assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"');
- return this.$eval(selector, (element, values) => {
- if (element.nodeName.toLowerCase() !== 'select')
- throw new Error('Element is not a