Bug 1632710 - [puppeteer] Remove experimental/ dir r=remote-protocol-reviewers,jgraham,whimboo
authorMaja Frydrychowicz <mjzffr@gmail.com>
Tue, 02 Jun 2020 20:49:00 +0000
changeset 534187 b57a835e980a2ff920e9b805e60df9f2221d0d56
parent 534186 14ff816a22bd19a0206f90dc1cdbe08a8166f793
child 534188 dadc7312128e70919054924ab980f4344c504da2
push id37483
push userapavel@mozilla.com
push dateFri, 05 Jun 2020 21:40:11 +0000
treeherdermozilla-central@dadc7312128e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersremote-protocol-reviewers, jgraham, whimboo
bugs1632710
milestone79.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1632710 - [puppeteer] Remove experimental/ dir r=remote-protocol-reviewers,jgraham,whimboo Differential Revision: https://phabricator.services.mozilla.com/D77624
.hgignore
remote/mach_commands.py
remote/test/puppeteer/experimental/puppeteer-firefox/.ci/node6/Dockerfile.linux
remote/test/puppeteer/experimental/puppeteer-firefox/.ci/node8/Dockerfile.linux
remote/test/puppeteer/experimental/puppeteer-firefox/.ci/node8/Dockerfile.windows
remote/test/puppeteer/experimental/puppeteer-firefox/.cirrus.yml
remote/test/puppeteer/experimental/puppeteer-firefox/.gitignore
remote/test/puppeteer/experimental/puppeteer-firefox/.npmignore
remote/test/puppeteer/experimental/puppeteer-firefox/DeviceDescriptors.js
remote/test/puppeteer/experimental/puppeteer-firefox/Errors.js
remote/test/puppeteer/experimental/puppeteer-firefox/LICENSE
remote/test/puppeteer/experimental/puppeteer-firefox/README.md
remote/test/puppeteer/experimental/puppeteer-firefox/examples/screenshot.js
remote/test/puppeteer/experimental/puppeteer-firefox/examples/search.js
remote/test/puppeteer/experimental/puppeteer-firefox/index.js
remote/test/puppeteer/experimental/puppeteer-firefox/install.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/Accessibility.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/Browser.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/BrowserFetcher.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/Connection.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/DOMWorld.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/DeviceDescriptors.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/Dialog.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/Errors.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/Events.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/ExecutionContext.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/FrameManager.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/Input.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/JSHandle.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/Launcher.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/NavigationWatchdog.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/NetworkManager.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/Page.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/Puppeteer.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/TimeoutSettings.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/USKeyboardLayout.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/WebSocketTransport.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/api.js
remote/test/puppeteer/experimental/puppeteer-firefox/lib/externs.d.ts
remote/test/puppeteer/experimental/puppeteer-firefox/lib/helper.js
remote/test/puppeteer/experimental/puppeteer-firefox/misc/00-puppeteer-prefs.js
remote/test/puppeteer/experimental/puppeteer-firefox/misc/install-preferences.js
remote/test/puppeteer/experimental/puppeteer-firefox/misc/puppeteer.cfg
remote/test/puppeteer/experimental/puppeteer-firefox/package.json
remote/test/puppeteer/experimental/puppeteer-firefox/tsconfig.json
--- a/.hgignore
+++ b/.hgignore
@@ -100,16 +100,18 @@ compile_commands\.json
 
 # Ignore node_modules directories in devtools
 ^devtools/.*/node_modules/
 
 # Ignore node_module directories and npm artifacts
 ^remote/test/puppeteer/package-lock.json
 ^remote/test/puppeteer/node_modules/
 ^remote/test/puppeteer/.local-chromium/
+^remote/test/puppeteer/.local-firefox/
+^remote/test/puppeteer/experimental
 
 # git checkout of libstagefright
 ^media/libstagefright/android$
 
 # Tag files generated by GNU Global
 (^|/)GTAGS$
 (^|/)GRTAGS$
 (^|/)GSYMS$
--- a/remote/mach_commands.py
+++ b/remote/mach_commands.py
@@ -36,19 +36,16 @@ from mozbuild import nodeutil
 import mozlog
 import mozprofile
 
 
 EX_CONFIG = 78
 EX_SOFTWARE = 70
 EX_USAGE = 64
 
-DEFAULT_REPO = "https://github.com/andreastt/puppeteer.git"
-DEFAULT_COMMITISH = "firefox"
-
 
 def setup():
     # add node and npm from mozbuild to front of system path
     npm, _ = nodeutil.find_npm_executable()
     if not npm:
         exit(EX_CONFIG, "could not find npm executable")
     path = os.path.abspath(os.path.join(npm, os.pardir))
     os.environ["PATH"] = "{}:{}".format(path, os.environ["PATH"])
@@ -66,24 +63,22 @@ class RemoteCommands(MachCommandBase):
         """The remote subcommands all relate to the remote protocol."""
         self._sub_mach(['help', 'remote'])
         return 1
 
     @SubCommand("remote", "vendor-puppeteer",
                 "Pull in latest changes of the Puppeteer client.")
     @CommandArgument("--repository",
                      metavar="REPO",
-                     default=DEFAULT_REPO,
-                     help="The (possibly remote) repository to clone from. "
-                          "Defaults to {}.".format(DEFAULT_REPO))
+                     required=True,
+                     help="The (possibly remote) repository to clone from.")
     @CommandArgument("--commitish",
                      metavar="COMMITISH",
-                     default=DEFAULT_COMMITISH,
-                     help="The commit or tag object name to check out. "
-                          "Defaults to \"{}\".".format(DEFAULT_COMMITISH))
+                     required=True,
+                     help="The commit or tag object name to check out.")
     def vendor_puppeteer(self, repository, commitish):
         puppeteerdir = os.path.join(self.remotedir, "test", "puppeteer")
 
         shutil.rmtree(puppeteerdir, ignore_errors=True)
         os.makedirs(puppeteerdir)
         with TemporaryDirectory() as tmpdir:
             git("clone", "-q", repository, tmpdir)
             git("checkout", commitish, worktree=tmpdir)
@@ -93,16 +88,20 @@ class RemoteCommands(MachCommandBase):
 
         # remove files which may interfere with git checkout of central
         try:
             os.remove(os.path.join(puppeteerdir, ".gitattributes"))
             os.remove(os.path.join(puppeteerdir, ".gitignore"))
         except OSError:
             pass
 
+        experimental_dir = os.path.join(puppeteerdir, "experimental")
+        if os.path.isdir(experimental_dir):
+            shutil.rmtree(experimental_dir)
+
         import yaml
         annotation = {
             "schema": 1,
             "bugzilla": {
                 "product": "Remote Protocol",
                 "component": "Agent",
             },
             "origin": {
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/.ci/node6/Dockerfile.linux
+++ /dev/null
@@ -1,17 +0,0 @@
-FROM node:6.12.3
-
-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
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/.ci/node8/Dockerfile.linux
+++ /dev/null
@@ -1,17 +0,0 @@
-FROM node:8.11.3-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
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/.ci/node8/Dockerfile.windows
+++ /dev/null
@@ -1,11 +0,0 @@
-FROM microsoft/windowsservercore:latest
-
-ENV NODE_VERSION 8.11.3
-
-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'
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/.cirrus.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-env:
-  DISPLAY: :99.0
-
-task:
-  name: node8 (linux)
-  container:
-    dockerfile: .ci/node8/Dockerfile.linux
-  xvfb_start_background_script: Xvfb :99 -ac -screen 0 1024x768x24
-  install_script: npm install
-  test_script: npm run fjunit
-
-task:
-  name: node8 (macOS)
-  osx_instance:
-    image: high-sierra-base
-  env:
-    HOMEBREW_NO_AUTO_UPDATE: 1
-  node_install_script:
-    - brew install node@8
-    - brew link --force node@8
-  install_script: npm install
-  test_script: npm run fjunit
-
-# task:
-#   allow_failures: true
-#  windows_container:
-#    dockerfile: .ci/node8/Dockerfile.windows
-#    os_version: 2016
-#  name: node8 (windows)
-#  install_script: npm install --unsafe-perm
-#  test_script: npm run fjunit
deleted file mode 100644
--- a/remote/test/puppeteer/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
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/.npmignore
+++ /dev/null
@@ -1,37 +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
-.travis.yml
-README.md
-tsconfig.json
-
deleted file mode 100644
--- a/remote/test/puppeteer/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');
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/Errors.js
+++ /dev/null
@@ -1,1 +0,0 @@
-module.exports = require('./lib/Errors');
deleted file mode 100644
--- a/remote/test/puppeteer/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.
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/README.md
+++ /dev/null
@@ -1,56 +0,0 @@
-<img src="https://user-images.githubusercontent.com/39191/49555713-a07b3c00-f8b5-11e8-8aba-f2d03cd83da5.png" height="200" align="right">
-
-# Prototype: Puppeteer for Firefox
-
-> Use Puppeteer's API with Firefox
-
-**⚠️ BEWARE**: Experimental. Just for preview. Installation and usage will change.
-
-This project is a 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
-```
-
-
-### API Status
-
-Current tip-of-tree status of Puppeteer-Firefox is available at [isPuppeteerFirefoxReady?](https://aslushnikov.github.io/ispuppeteerfirefoxready/)
-
-
-### Credits
-
-Special thanks to [Amine Bouhlali](https://bitbucket.org/aminerop/) who volunteered the [`puppeteer-firefox`](https://www.npmjs.com/package/puppeteer-firefox) NPM package.
deleted file mode 100644
--- a/remote/test/puppeteer/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();
-})();
deleted file mode 100644
--- a/remote/test/puppeteer/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('#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();
-})();
deleted file mode 100644
--- a/remote/test/puppeteer/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);
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/install.js
+++ /dev/null
@@ -1,96 +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 puppeteer = require('./index');
-const browserFetcher = puppeteer.createBrowserFetcher({ host: downloadHost, product: 'firefox' });
-
-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<string>}
- * @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`;
-}
deleted file mode 100644
--- a/remote/test/puppeteer/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<SerializedAXNode>=} children
- */
-
-class Accessibility {
-  constructor(session) {
-    this._session = session;
-  }
-
-  /**
-   * @param {{interestingOnly?: boolean}=} options
-   * @return {!Promise<!SerializedAXNode>}
-   */
-  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<!AXNode>} */
-    const interestingNodes = new Set();
-    collectInterestingNodes(interestingNodes, root, false);
-    return serializeTree(root, interestingNodes)[0];
-  }
-}
-
-/**
- * @param {!Set<!AXNode>} 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<!AXNode>=} whitelistedNodes
- * @return {!Array<!SerializedAXNode>}
- */
-function serializeTree(node, whitelistedNodes) {
-  /** @type {!Array<!SerializedAXNode>} */
-  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<!AXNode>} */
-    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<keyof SerializedAXNode>} */
-    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<keyof SerializedAXNode>} */
-    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<keyof SerializedAXNode>} */
-    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<keyof SerializedAXNode>} */
-    const numericalProperties = [
-      'level',
-      'valuemax',
-      'valuemin',
-    ];
-    for (const numericalProperty of numericalProperties) {
-      if (!(numericalProperty in this._payload))
-        continue;
-      node[numericalProperty] = this._payload[numericalProperty];
-    }
-    /** @type {!Array<keyof SerializedAXNode>} */
-    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};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/Browser.js
+++ /dev/null
@@ -1,369 +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<string>} 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<string, !Target>} */
-    this._targets = new Map();
-
-    this._defaultContext = new BrowserContext(this._connection, this, null);
-    /** @type {!Map<string, !BrowserContext>} */
-    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<!BrowserContext>}
-   */
-  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<string>}
-   */
-  async userAgent() {
-    const info = await this._connection.send('Browser.getInfo');
-    return info.userAgent;
-  }
-
-  /**
-   * @return {!Promise<string>}
-   */
-  async version() {
-    const info = await this._connection.send('Browser.getInfo');
-    return info.version;
-  }
-
-  /**
-   * @return {?Puppeteer.ChildProcess}
-   */
-  process() {
-    return this._process;
-  }
-
-  /**
-   * @param {function(!Target):boolean} predicate
-   * @param {{timeout?: number}=} options
-   * @return {!Promise<!Target>}
-   */
-  async waitForTarget(predicate, options = {}) {
-    const {
-      timeout = 30000
-    } = options;
-    const existingTarget = this.targets().find(predicate);
-    if (existingTarget)
-      return existingTarget;
-    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(targetPromise, 'target', timeout);
-    } finally {
-      this.removeListener(Events.Browser.TargetCreated, check);
-      this.removeListener('targetchanged', check);
-    }
-
-    /**
-     * @param {!Target} target
-     */
-    function check(target) {
-      if (predicate(target))
-        resolve(target);
-    }
-  }
-
-  /**
-   * @return {Promise<Page>}
-   */
-  newPage() {
-    return this._createPageInContext(this._defaultContext._browserContextId);
-  }
-
-  /**
-   * @param {?string} browserContextId
-   * @return {Promise<Page>}
-   */
-  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<!Page>} */
-    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<string>} 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<Target>}
-   */
-  targets() {
-    return this._browser.targets().filter(target => target.browserContext() === this);
-  }
-
-  /**
-   * @return {Promise<Array<Puppeteer.Page>>}
-   */
-  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} predicate
-   * @param {{timeout?: number}=} options
-   * @return {!Promise<Target>}
-   */
-  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};
deleted file mode 100644
--- a/remote/test/puppeteer/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 DEFAULT_DOWNLOAD_HOST = 'https://storage.googleapis.com';
-
-const downloadURLs = {
-  chromium: {
-    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: {
-    linux: '%s/juggler-builds/%s/%s.zip',
-    mac: '%s/juggler-builds/%s/%s.zip',
-    win32: '%s/juggler-builds/%s/%s.zip',
-    win64: '%s/juggler-builds/%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) {
-  return util.format(downloadURLs[product][platform], host, revision, archiveName(product, platform, revision));
-}
-
-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', `Unkown product: "${options.product}"`);
-    this._downloadsFolder = options.path || path.join(projectRoot, '.local-browser');
-    this._downloadHost = options.host || DEFAULT_DOWNLOAD_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<boolean>}
-   */
-  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<!BrowserFetcher.RevisionInfo>}
-   */
-  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<!Array<string>>}
-   */
-  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<?Error>}
- */
-function extractZip(zipPath, folderPath) {
-  return new Promise((fulfill, reject) => extract(zipPath, {dir: folderPath}, err => {
-    if (err)
-      reject(err);
-    else
-      fulfill();
-  }));
-}
-
-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
- */
deleted file mode 100644
--- a/remote/test/puppeteer/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<number, {resolve: function, reject: function, error: !Error, method: string}>}*/
-    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<string, !JugglerSession>}*/
-    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<?Object>}
-   */
-  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<!JugglerSession>}
-   */
-  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<number, {resolve: function, reject: function, error: !Error, method: string}>}*/
-    this._callbacks = new Map();
-    this._connection = connection;
-    this._targetType = targetType;
-    this._sessionId = sessionId;
-  }
-
-  /**
-   * @param {string} method
-   * @param {!Object=} params
-   * @return {!Promise<?Object>}
-   */
-  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};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/DOMWorld.js
+++ /dev/null
@@ -1,625 +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<!WaitTask>} */
-    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<?ElementHandle>}
-   */
-  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<!Array<!ElementHandle>>}
-   */
-  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<!Array<!ElementHandle>>}
-   */
-  async $$(selector) {
-    const document = await this._document();
-    return document.$$(selector);
-  }
-
-  /**
-   * @return {!Promise<String>}
-   */
-  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}} options
-   * @return {!Promise<!ElementHandle>}
-   */
-  async addScriptTag(options) {
-    if (typeof options.url === 'string') {
-      const url = options.url;
-      try {
-        return (await this.evaluateHandle(addScriptUrl, url, options.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, options.type)).asElement();
-    }
-
-    if (typeof options.content === 'string') {
-      return (await this.evaluateHandle(addScriptContent, options.content, options.type)).asElement();
-    }
-
-    throw new Error('Provide an object with a `url`, `path` or `content` property');
-
-    /**
-     * @param {string} url
-     * @param {string} type
-     * @return {!Promise<!HTMLElement>}
-     */
-    async function addScriptUrl(url, type) {
-      const script = document.createElement('script');
-      script.src = url;
-      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} type
-     * @return {!HTMLElement}
-     */
-    function addScriptContent(content, type = 'text/javascript') {
-      const script = document.createElement('script');
-      script.type = type;
-      script.text = content;
-      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<!ElementHandle>}
-   */
-  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<!HTMLElement>}
-     */
-    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<!HTMLElement>}
-     */
-    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<string>} values
-  * @return {!Promise<!Array<string>>}
-  */
-  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 <select> element.');
-
-      const options = Array.from(element.options);
-      element.value = undefined;
-      for (const option of options) {
-        option.selected = values.includes(option.value);
-        if (option.selected && !element.multiple)
-          break;
-      }
-      element.dispatchEvent(new Event('input', { 'bubbles': true }));
-      element.dispatchEvent(new Event('change', { 'bubbles': true }));
-      return options.filter(option => option.selected).map(option => option.value);
-    }, values);
-  }
-
-  /**
-   * @param {string} selector
-   */
-  async tap(selector) {
-    const handle = await this.$(selector);
-    assert(handle, 'No node found for selector: ' + selector);
-    await handle.tap();
-    await handle.dispose();
-  }
-
-  /**
-   * @param {string} selector
-   * @param {string} text
-   * @param {{delay: (number|undefined)}=} options
-   */
-  async type(selector, text, options) {
-    const handle = await this.$(selector);
-    assert(handle, 'No node found for selector: ' + selector);
-    await handle.type(text, options);
-    await handle.dispose();
-  }
-
-  /**
-   * @param {string} selector
-   * @param {!{timeout?: number, visible?: boolean, hidden?: boolean}=} options
-   * @return {!Promise<!ElementHandle>}
-   */
-  waitForSelector(selector, options) {
-    return this._waitForSelectorOrXPath(selector, false, options);
-  }
-
-  /**
-   * @param {string} xpath
-   * @param {!{timeout?: number, visible?: boolean, hidden?: boolean}=} options
-   * @return {!Promise<!ElementHandle>}
-   */
-  waitForXPath(xpath, options) {
-    return this._waitForSelectorOrXPath(xpath, true, options);
-  }
-
-  /**
-   * @param {Function|string} pageFunction
-   * @param {!{polling?: string|number, timeout?: number}=} options
-   * @return {!Promise<!JSHandle>}
-   */
-  waitForFunction(pageFunction, options = {}, ...args) {
-    const {
-      polling = 'raf',
-      timeout = this._timeoutSettings.timeout(),
-    } = options;
-    return new WaitTask(this, pageFunction, 'function', polling, timeout, ...args).promise;
-  }
-
-  /**
-   * @return {!Promise<string>}
-   */
-  async title() {
-    return this.evaluate(() => document.title);
-  }
-
-  /**
-   * @param {string} selectorOrXPath
-   * @param {boolean} isXPath
-   * @param {!{timeout?: number, visible?: boolean, hidden?: boolean}=} options
-   * @return {!Promise<!ElementHandle>}
-   */
-  async _waitForSelectorOrXPath(selectorOrXPath, isXPath, options = {}) {
-    const {
-      visible: waitForVisible = false,
-      hidden: waitForHidden = false,
-      timeout = this._timeoutSettings.timeout(),
-    } = options;
-    const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation';
-    const title = `${isXPath ? 'XPath' : 'selector'} "${selectorOrXPath}"${waitForHidden ? ' to be hidden' : ''}`;
-    const waitTask = new WaitTask(this, predicate, title, polling, timeout, selectorOrXPath, isXPath, waitForVisible, waitForHidden);
-    const handle = await waitTask.promise;
-    if (!handle.asElement()) {
-      await handle.dispose();
-      return null;
-    }
-    return handle.asElement();
-
-    /**
-     * @param {string} selectorOrXPath
-     * @param {boolean} isXPath
-     * @param {boolean} waitForVisible
-     * @param {boolean} waitForHidden
-     * @return {?Node|boolean}
-     */
-    function predicate(selectorOrXPath, isXPath, waitForVisible, waitForHidden) {
-      const node = isXPath
-        ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
-        : document.querySelector(selectorOrXPath);
-      if (!node)
-        return waitForHidden;
-      if (!waitForVisible && !waitForHidden)
-        return node;
-      const element = /** @type {Element} */ (node.nodeType === Node.TEXT_NODE ? node.parentElement : node);
-
-      const style = window.getComputedStyle(element);
-      const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
-      const success = (waitForVisible === isVisible || waitForHidden === !isVisible);
-      return success ? node : null;
-
-      /**
-       * @return {boolean}
-       */
-      function hasVisibleBoundingBox() {
-        const rect = element.getBoundingClientRect();
-        return !!(rect.top || rect.bottom || rect.width || rect.height);
-      }
-    }
-  }
-}
-
-/**
- * @internal
- */
-class WaitTask {
-  /**
-   * @param {!DOMWorld} domWorld
-   * @param {Function|string} predicateBody
-   * @param {string|number} polling
-   * @param {number} timeout
-   * @param {!Array<*>} args
-   */
-  constructor(domWorld, predicateBody, title, polling, timeout, ...args) {
-    if (helper.isString(polling))
-      assert(polling === 'raf' || polling === 'mutation', 'Unknown polling option: ' + polling);
-    else if (helper.isNumber(polling))
-      assert(polling > 0, 'Cannot poll with non-positive interval: ' + polling);
-    else
-      throw new Error('Unknown polling options: ' + polling);
-
-    this._domWorld = domWorld;
-    this._polling = polling;
-    this._timeout = timeout;
-    this._predicateBody = helper.isString(predicateBody) ? 'return (' + predicateBody + ')' : 'return (' + predicateBody + ')(...args)';
-    this._args = args;
-    this._runCount = 0;
-    domWorld._waitTasks.add(this);
-    this.promise = new Promise((resolve, reject) => {
-      this._resolve = resolve;
-      this._reject = reject;
-    });
-    // Since page navigation requires us to re-install the pageScript, we should track
-    // timeout on our end.
-    if (timeout) {
-      const timeoutError = new TimeoutError(`waiting for ${title} failed: timeout ${timeout}ms exceeded`);
-      this._timeoutTimer = setTimeout(() => this.terminate(timeoutError), timeout);
-    }
-    this.rerun();
-  }
-
-  /**
-   * @param {!Error} error
-   */
-  terminate(error) {
-    this._terminated = true;
-    this._reject(error);
-    this._cleanup();
-  }
-
-  async rerun() {
-    const runCount = ++this._runCount;
-    /** @type {?JSHandle} */
-    let success = null;
-    let error = null;
-    try {
-      success = await this._domWorld.evaluateHandle(waitForPredicatePageFunction, this._predicateBody, this._polling, this._timeout, ...this._args);
-    } catch (e) {
-      error = e;
-    }
-
-    if (this._terminated || runCount !== this._runCount) {
-      if (success)
-        await success.dispose();
-      return;
-    }
-
-    // Ignore timeouts in pageScript - we track timeouts ourselves.
-    // If the frame's execution context has already changed, `frame.evaluate` will
-    // throw an error - ignore this predicate run altogether.
-    if (!error && await this._domWorld.evaluate(s => !s, success).catch(e => true)) {
-      await success.dispose();
-      return;
-    }
-
-    // When the page is navigated, the promise is rejected.
-    // Try again right away.
-    if (error && error.message.includes('Execution context was destroyed')) {
-      this.rerun();
-      return;
-    }
-
-    if (error)
-      this._reject(error);
-    else
-      this._resolve(success);
-
-    this._cleanup();
-  }
-
-  _cleanup() {
-    clearTimeout(this._timeoutTimer);
-    this._domWorld._waitTasks.delete(this);
-    this._runningTask = null;
-  }
-}
-
-/**
- * @param {string} predicateBody
- * @param {string} polling
- * @param {number} timeout
- * @return {!Promise<*>}
- */
-async function waitForPredicatePageFunction(predicateBody, polling, timeout, ...args) {
-  const predicate = new Function('...args', predicateBody);
-  let timedOut = false;
-  if (timeout)
-    setTimeout(() => timedOut = true, timeout);
-  if (polling === 'raf')
-    return await pollRaf();
-  if (polling === 'mutation')
-    return await pollMutation();
-  if (typeof polling === 'number')
-    return await pollInterval(polling);
-
-  /**
-   * @return {!Promise<*>}
-   */
-  function pollMutation() {
-    const success = predicate.apply(null, args);
-    if (success)
-      return Promise.resolve(success);
-
-    let fulfill;
-    const result = new Promise(x => fulfill = x);
-    const observer = new MutationObserver(mutations => {
-      if (timedOut) {
-        observer.disconnect();
-        fulfill();
-      }
-      const success = predicate.apply(null, args);
-      if (success) {
-        observer.disconnect();
-        fulfill(success);
-      }
-    });
-    observer.observe(document, {
-      childList: true,
-      subtree: true,
-      attributes: true
-    });
-    return result;
-  }
-
-  /**
-   * @return {!Promise<*>}
-   */
-  function pollRaf() {
-    let fulfill;
-    const result = new Promise(x => fulfill = x);
-    onRaf();
-    return result;
-
-    function onRaf() {
-      if (timedOut) {
-        fulfill();
-        return;
-      }
-      const success = predicate.apply(null, args);
-      if (success)
-        fulfill(success);
-      else
-        requestAnimationFrame(onRaf);
-    }
-  }
-
-  /**
-   * @param {number} pollInterval
-   * @return {!Promise<*>}
-   */
-  function pollInterval(pollInterval) {
-    let fulfill;
-    const result = new Promise(x => fulfill = x);
-    onTimeout();
-    return result;
-
-    function onTimeout() {
-      if (timedOut) {
-        fulfill();
-        return;
-      }
-      const success = predicate.apply(null, args);
-      if (success)
-        fulfill(success);
-      else
-        setTimeout(onTimeout, pollInterval);
-    }
-  }
-}
-
-module.exports = {DOMWorld};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/DeviceDescriptors.js
+++ /dev/null
@@ -1,824 +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.
- */
-
-module.exports = [
-  {
-    'name': 'Blackberry PlayBook',
-    'userAgent': 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+',
-    'viewport': {
-      'width': 600,
-      'height': 1024,
-      'deviceScaleFactor': 1,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Blackberry PlayBook landscape',
-    'userAgent': 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+',
-    'viewport': {
-      'width': 1024,
-      'height': 600,
-      'deviceScaleFactor': 1,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'BlackBerry Z30',
-    'userAgent': 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+',
-    'viewport': {
-      'width': 360,
-      'height': 640,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'BlackBerry Z30 landscape',
-    'userAgent': 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+',
-    'viewport': {
-      'width': 640,
-      'height': 360,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Galaxy Note 3',
-    'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
-    'viewport': {
-      'width': 360,
-      'height': 640,
-      'deviceScaleFactor': 3,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Galaxy Note 3 landscape',
-    'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
-    'viewport': {
-      'width': 640,
-      'height': 360,
-      'deviceScaleFactor': 3,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Galaxy Note II',
-    'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
-    'viewport': {
-      'width': 360,
-      'height': 640,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Galaxy Note II landscape',
-    'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
-    'viewport': {
-      'width': 640,
-      'height': 360,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Galaxy S III',
-    'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
-    'viewport': {
-      'width': 360,
-      'height': 640,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Galaxy S III landscape',
-    'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
-    'viewport': {
-      'width': 640,
-      'height': 360,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Galaxy S5',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 360,
-      'height': 640,
-      'deviceScaleFactor': 3,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Galaxy S5 landscape',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 640,
-      'height': 360,
-      'deviceScaleFactor': 3,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'iPad',
-    'userAgent': 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
-    'viewport': {
-      'width': 768,
-      'height': 1024,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'iPad landscape',
-    'userAgent': 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
-    'viewport': {
-      'width': 1024,
-      'height': 768,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'iPad Mini',
-    'userAgent': 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
-    'viewport': {
-      'width': 768,
-      'height': 1024,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'iPad Mini landscape',
-    'userAgent': 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
-    'viewport': {
-      'width': 1024,
-      'height': 768,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'iPad Pro',
-    'userAgent': 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
-    'viewport': {
-      'width': 1024,
-      'height': 1366,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'iPad Pro landscape',
-    'userAgent': 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
-    'viewport': {
-      'width': 1366,
-      'height': 1024,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'iPhone 4',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53',
-    'viewport': {
-      'width': 320,
-      'height': 480,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'iPhone 4 landscape',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53',
-    'viewport': {
-      'width': 480,
-      'height': 320,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'iPhone 5',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1',
-    'viewport': {
-      'width': 320,
-      'height': 568,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'iPhone 5 landscape',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1',
-    'viewport': {
-      'width': 568,
-      'height': 320,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'iPhone 6',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
-    'viewport': {
-      'width': 375,
-      'height': 667,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'iPhone 6 landscape',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
-    'viewport': {
-      'width': 667,
-      'height': 375,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'iPhone 6 Plus',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
-    'viewport': {
-      'width': 414,
-      'height': 736,
-      'deviceScaleFactor': 3,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'iPhone 6 Plus landscape',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
-    'viewport': {
-      'width': 736,
-      'height': 414,
-      'deviceScaleFactor': 3,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'iPhone 7',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
-    'viewport': {
-      'width': 375,
-      'height': 667,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'iPhone 7 landscape',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
-    'viewport': {
-      'width': 667,
-      'height': 375,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'iPhone 7 Plus',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
-    'viewport': {
-      'width': 414,
-      'height': 736,
-      'deviceScaleFactor': 3,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'iPhone 7 Plus landscape',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
-    'viewport': {
-      'width': 736,
-      'height': 414,
-      'deviceScaleFactor': 3,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'iPhone 8',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
-    'viewport': {
-      'width': 375,
-      'height': 667,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'iPhone 8 landscape',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
-    'viewport': {
-      'width': 667,
-      'height': 375,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'iPhone 8 Plus',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
-    'viewport': {
-      'width': 414,
-      'height': 736,
-      'deviceScaleFactor': 3,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'iPhone 8 Plus landscape',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
-    'viewport': {
-      'width': 736,
-      'height': 414,
-      'deviceScaleFactor': 3,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'iPhone SE',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1',
-    'viewport': {
-      'width': 320,
-      'height': 568,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'iPhone SE landscape',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1',
-    'viewport': {
-      'width': 568,
-      'height': 320,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'iPhone X',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
-    'viewport': {
-      'width': 375,
-      'height': 812,
-      'deviceScaleFactor': 3,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'iPhone X landscape',
-    'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
-    'viewport': {
-      'width': 812,
-      'height': 375,
-      'deviceScaleFactor': 3,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Kindle Fire HDX',
-    'userAgent': 'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true',
-    'viewport': {
-      'width': 800,
-      'height': 1280,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Kindle Fire HDX landscape',
-    'userAgent': 'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true',
-    'viewport': {
-      'width': 1280,
-      'height': 800,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'LG Optimus L70',
-    'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 384,
-      'height': 640,
-      'deviceScaleFactor': 1.25,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'LG Optimus L70 landscape',
-    'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 640,
-      'height': 384,
-      'deviceScaleFactor': 1.25,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Microsoft Lumia 550',
-    'userAgent': 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263',
-    'viewport': {
-      'width': 640,
-      'height': 360,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Microsoft Lumia 950',
-    'userAgent': 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263',
-    'viewport': {
-      'width': 360,
-      'height': 640,
-      'deviceScaleFactor': 4,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Microsoft Lumia 950 landscape',
-    'userAgent': 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263',
-    'viewport': {
-      'width': 640,
-      'height': 360,
-      'deviceScaleFactor': 4,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Nexus 10',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Safari/537.36',
-    'viewport': {
-      'width': 800,
-      'height': 1280,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Nexus 10 landscape',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Safari/537.36',
-    'viewport': {
-      'width': 1280,
-      'height': 800,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Nexus 4',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 384,
-      'height': 640,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Nexus 4 landscape',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 640,
-      'height': 384,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Nexus 5',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 360,
-      'height': 640,
-      'deviceScaleFactor': 3,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Nexus 5 landscape',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 640,
-      'height': 360,
-      'deviceScaleFactor': 3,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Nexus 5X',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 412,
-      'height': 732,
-      'deviceScaleFactor': 2.625,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Nexus 5X landscape',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 732,
-      'height': 412,
-      'deviceScaleFactor': 2.625,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Nexus 6',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 412,
-      'height': 732,
-      'deviceScaleFactor': 3.5,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Nexus 6 landscape',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 732,
-      'height': 412,
-      'deviceScaleFactor': 3.5,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Nexus 6P',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 412,
-      'height': 732,
-      'deviceScaleFactor': 3.5,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Nexus 6P landscape',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 732,
-      'height': 412,
-      'deviceScaleFactor': 3.5,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Nexus 7',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Safari/537.36',
-    'viewport': {
-      'width': 600,
-      'height': 960,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Nexus 7 landscape',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Safari/537.36',
-    'viewport': {
-      'width': 960,
-      'height': 600,
-      'deviceScaleFactor': 2,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Nokia Lumia 520',
-    'userAgent': 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)',
-    'viewport': {
-      'width': 320,
-      'height': 533,
-      'deviceScaleFactor': 1.5,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Nokia Lumia 520 landscape',
-    'userAgent': 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)',
-    'viewport': {
-      'width': 533,
-      'height': 320,
-      'deviceScaleFactor': 1.5,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Nokia N9',
-    'userAgent': 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13',
-    'viewport': {
-      'width': 480,
-      'height': 854,
-      'deviceScaleFactor': 1,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Nokia N9 landscape',
-    'userAgent': 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13',
-    'viewport': {
-      'width': 854,
-      'height': 480,
-      'deviceScaleFactor': 1,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Pixel 2',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 411,
-      'height': 731,
-      'deviceScaleFactor': 2.625,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Pixel 2 landscape',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 731,
-      'height': 411,
-      'deviceScaleFactor': 2.625,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  },
-  {
-    'name': 'Pixel 2 XL',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 411,
-      'height': 823,
-      'deviceScaleFactor': 3.5,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': false
-    }
-  },
-  {
-    'name': 'Pixel 2 XL landscape',
-    'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Mobile Safari/537.36',
-    'viewport': {
-      'width': 823,
-      'height': 411,
-      'deviceScaleFactor': 3.5,
-      'isMobile': true,
-      'hasTouch': true,
-      'isLandscape': true
-    }
-  }
-];
-for (const device of module.exports)
-  module.exports[device.name] = device;
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/Dialog.js
+++ /dev/null
@@ -1,57 +0,0 @@
-const {helper, assert, debugError} = require('./helper');
-
-class Dialog {
-  constructor(client, payload) {
-    this._client = client;
-    this._dialogId = payload.dialogId;
-    this._type = payload.type;
-    this._message = payload.message;
-    this._handled = false;
-    this._defaultValue = payload.defaultValue || '';
-  }
-
-  /**
-   * @return {string}
-   */
-  type() {
-    return this._type;
-  }
-
-  /**
-   * @return {string}
-   */
-  message() {
-    return this._message;
-  }
-
-  /**
-   * @return {string}
-   */
-  defaultValue() {
-    return this._defaultValue;
-  }
-
-  /**
-   * @param {string=} promptText
-   */
-  async accept(promptText) {
-    assert(!this._handled, 'Cannot accept dialog which is already handled!');
-    this._handled = true;
-    await this._client.send('Page.handleDialog', {
-      dialogId: this._dialogId,
-      accept: true,
-      promptText: promptText
-    }).catch(debugError);
-  }
-
-  async dismiss() {
-    assert(!this._handled, 'Cannot dismiss dialog which is already handled!');
-    this._handled = true;
-    await this._client.send('Page.handleDialog', {
-      dialogId: this._dialogId,
-      accept: false
-    }).catch(debugError);
-  }
-}
-
-module.exports = {Dialog};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/Errors.js
+++ /dev/null
@@ -1,29 +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.
- */
-
-class CustomError extends Error {
-  constructor(message) {
-    super(message);
-    this.name = this.constructor.name;
-    Error.captureStackTrace(this, this.constructor);
-  }
-}
-
-class TimeoutError extends CustomError {}
-
-module.exports = {
-  TimeoutError,
-};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/Events.js
+++ /dev/null
@@ -1,53 +0,0 @@
-const Events = {
-  Page: {
-    Close: 'close',
-    Console: 'console',
-    Dialog: 'dialog',
-    DOMContentLoaded: 'domcontentloaded',
-    FrameAttached: 'frameattached',
-    FrameDetached: 'framedetached',
-    FrameNavigated: 'framenavigated',
-    Load: 'load',
-    PageError: 'pageerror',
-    Popup: 'popup',
-    Request: 'request',
-    Response: 'response',
-    RequestFinished: 'requestfinished',
-    RequestFailed: 'requestfailed',
-  },
-  Browser: {
-    Disconnected: 'disconnected',
-    TargetCreated: 'targetcreated',
-    TargetChanged: 'targetchanged',
-    TargetDestroyed: 'targetdestroyed',
-  },
-  BrowserContext: {
-    TargetCreated: 'targetcreated',
-    TargetChanged: 'targetchanged',
-    TargetDestroyed: 'targetdestroyed',
-  },
-
-  Connection: {
-    Disconnected: Symbol('Events.Connection.Disconnected'),
-  },
-
-  JugglerSession: {
-    Disconnected: Symbol('Events.JugglerSession.Disconnected'),
-  },
-
-  FrameManager: {
-    Load: Symbol('Events.FrameManager.Load'),
-    DOMContentLoaded: Symbol('Events.FrameManager.DOMContentLoaded'),
-    FrameAttached: Symbol('Events.FrameManager.FrameAttached'),
-    FrameNavigated: Symbol('Events.FrameManager.FrameNavigated'),
-    FrameDetached: Symbol('Events.FrameManager.FrameDetached'),
-  },
-
-  NetworkManager: {
-    Request: Symbol('Events.NetworkManager.Request'),
-    Response: Symbol('Events.NetworkManager.Response'),
-    RequestFinished: Symbol('Events.NetworkManager.RequestFinished'),
-  },
-};
-
-module.exports = {Events};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/ExecutionContext.js
+++ /dev/null
@@ -1,103 +0,0 @@
-const {helper, assert, debugError} = require('./helper');
-const {JSHandle, createHandle} = require('./JSHandle');
-
-class ExecutionContext {
-  /**
-   * @param {!PageSession} session
-   * @param {?Frame} frame
-   * @param {string} executionContextId
-   */
-  constructor(session, frame, executionContextId) {
-    this._session = session;
-    this._frame = frame;
-    this._executionContextId = executionContextId;
-  }
-
-  async evaluateHandle(pageFunction, ...args) {
-    if (helper.isString(pageFunction)) {
-      const payload = await this._session.send('Runtime.evaluate', {
-        expression: pageFunction,
-        executionContextId: this._executionContextId,
-      }).catch(rewriteError);
-      return createHandle(this, payload.result, payload.exceptionDetails);
-    }
-    if (typeof pageFunction !== 'function')
-      throw new Error(`Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.`);
-
-    let functionText = pageFunction.toString();
-    try {
-      new Function('(' + functionText + ')');
-    } catch (e1) {
-      // This means we might have a function shorthand. Try another
-      // time prefixing 'function '.
-      if (functionText.startsWith('async '))
-        functionText = 'async function ' + functionText.substring('async '.length);
-      else
-        functionText = 'function ' + functionText;
-      try {
-        new Function('(' + functionText  + ')');
-      } catch (e2) {
-        // We tried hard to serialize, but there's a weird beast here.
-        throw new Error('Passed function is not well-serializable!');
-      }
-    }
-    args = args.map(arg => {
-      if (arg instanceof JSHandle) {
-        if (arg._context !== this)
-          throw new Error('JSHandles can be evaluated only in the context they were created!');
-        if (arg._disposed)
-          throw new Error('JSHandle is disposed!');
-        return arg._protocolValue;
-      }
-      if (Object.is(arg, Infinity))
-        return {unserializableValue: 'Infinity'};
-      if (Object.is(arg, -Infinity))
-        return {unserializableValue: '-Infinity'};
-      if (Object.is(arg, -0))
-        return {unserializableValue: '-0'};
-      if (Object.is(arg, NaN))
-        return {unserializableValue: 'NaN'};
-      return {value: arg};
-    });
-    let callFunctionPromise;
-    try {
-      callFunctionPromise = this._session.send('Runtime.callFunction', {
-        functionDeclaration: functionText,
-        args,
-        executionContextId: this._executionContextId
-      });
-    } catch (err) {
-      if (err instanceof TypeError && err.message.startsWith('Converting circular structure to JSON'))
-        err.message += ' Are you passing a nested JSHandle?';
-      throw err;
-    }
-    const payload = await callFunctionPromise.catch(rewriteError);
-    return createHandle(this, payload.result, payload.exceptionDetails);
-
-    function rewriteError(error) {
-      if (error.message.includes('Failed to find execution context with id'))
-        throw new Error('Execution context was destroyed, most likely because of a navigation.');
-      throw error;
-    }
-  }
-
-  frame() {
-    return this._frame;
-  }
-
-  async evaluate(pageFunction, ...args) {
-    try {
-      const handle = await this.evaluateHandle(pageFunction, ...args);
-      const result = await handle.jsonValue();
-      await handle.dispose();
-      return result;
-    } catch (e) {
-      if (e.message.includes('cyclic object value') || e.message.includes('Object is not serializable'))
-        return undefined;
-      throw e;
-    }
-  }
-
-}
-
-module.exports = {ExecutionContext};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/FrameManager.js
+++ /dev/null
@@ -1,478 +0,0 @@
-const {helper, assert} = require('./helper');
-const {TimeoutError} = require('./Errors');
-const fs = require('fs');
-const util = require('util');
-const EventEmitter = require('events');
-const {Events} = require('./Events');
-const {ExecutionContext} = require('./ExecutionContext');
-const {NavigationWatchdog, NextNavigationWatchdog} = require('./NavigationWatchdog');
-const {DOMWorld} = require('./DOMWorld');
-
-const readFileAsync = util.promisify(fs.readFile);
-
-class FrameManager extends EventEmitter {
-  /**
-   * @param {PageSession} session
-   * @param {Page} page
-   */
-  constructor(session, page, networkManager, timeoutSettings) {
-    super();
-    this._session = session;
-    this._page = page;
-    this._networkManager = networkManager;
-    this._timeoutSettings = timeoutSettings;
-    this._mainFrame = null;
-    this._frames = new Map();
-    /** @type {!Map<string, !ExecutionContext>} */
-    this._contextIdToContext = new Map();
-    this._eventListeners = [
-      helper.addEventListener(this._session, 'Page.eventFired', this._onEventFired.bind(this)),
-      helper.addEventListener(this._session, 'Page.frameAttached', this._onFrameAttached.bind(this)),
-      helper.addEventListener(this._session, 'Page.frameDetached', this._onFrameDetached.bind(this)),
-      helper.addEventListener(this._session, 'Page.navigationCommitted', this._onNavigationCommitted.bind(this)),
-      helper.addEventListener(this._session, 'Page.sameDocumentNavigation', this._onSameDocumentNavigation.bind(this)),
-      helper.addEventListener(this._session, 'Runtime.executionContextCreated', this._onExecutionContextCreated.bind(this)),
-      helper.addEventListener(this._session, 'Runtime.executionContextDestroyed', this._onExecutionContextDestroyed.bind(this)),
-    ];
-  }
-
-  executionContextById(executionContextId) {
-    return this._contextIdToContext.get(executionContextId) || null;
-  }
-
-  _onExecutionContextCreated({executionContextId, auxData}) {
-    const frameId = auxData ? auxData.frameId : null;
-    const frame = this._frames.get(frameId) || null;
-    const context = new ExecutionContext(this._session, frame, executionContextId);
-    if (frame)
-      frame._mainWorld._setContext(context);
-    this._contextIdToContext.set(executionContextId, context);
-  }
-
-  _onExecutionContextDestroyed({executionContextId}) {
-    const context = this._contextIdToContext.get(executionContextId);
-    if (!context)
-      return;
-    this._contextIdToContext.delete(executionContextId);
-    if (context._frame)
-      context._frame._mainWorld._setContext(null);
-  }
-
-  frame(frameId) {
-    return this._frames.get(frameId);
-  }
-
-  mainFrame() {
-    return this._mainFrame;
-  }
-
-  frames() {
-    /** @type {!Array<!Frame>} */
-    let frames = [];
-    collect(this._mainFrame);
-    return frames;
-
-    function collect(frame) {
-      frames.push(frame);
-      for (const subframe of frame._children)
-        collect(subframe);
-    }
-  }
-
-  _onNavigationCommitted(params) {
-    const frame = this._frames.get(params.frameId);
-    frame._navigated(params.url, params.name, params.navigationId);
-    frame._DOMContentLoadedFired = false;
-    frame._loadFired = false;
-    this.emit(Events.FrameManager.FrameNavigated, frame);
-  }
-
-  _onSameDocumentNavigation(params) {
-    const frame = this._frames.get(params.frameId);
-    frame._url = params.url;
-    this.emit(Events.FrameManager.FrameNavigated, frame);
-  }
-
-  _onFrameAttached(params) {
-    const frame = new Frame(this._session, this, this._networkManager, this._page, params.frameId, this._timeoutSettings);
-    const parentFrame = this._frames.get(params.parentFrameId) || null;
-    if (parentFrame) {
-      frame._parentFrame = parentFrame;
-      parentFrame._children.add(frame);
-    } else {
-      assert(!this._mainFrame, 'INTERNAL ERROR: re-attaching main frame!');
-      this._mainFrame = frame;
-    }
-    this._frames.set(params.frameId, frame);
-    this.emit(Events.FrameManager.FrameAttached, frame);
-  }
-
-  _onFrameDetached(params) {
-    const frame = this._frames.get(params.frameId);
-    this._frames.delete(params.frameId);
-    frame._detach();
-    this.emit(Events.FrameManager.FrameDetached, frame);
-  }
-
-  _onEventFired({frameId, name}) {
-    const frame = this._frames.get(frameId);
-    frame._firedEvents.add(name.toLowerCase());
-    if (frame === this._mainFrame) {
-      if (name === 'load')
-        this.emit(Events.FrameManager.Load);
-      else if (name === 'DOMContentLoaded')
-        this.emit(Events.FrameManager.DOMContentLoaded);
-    }
-  }
-
-  dispose() {
-    helper.removeEventListeners(this._eventListeners);
-  }
-}
-
-class Frame {
-  /**
-   * @param {*} session
-   * @param {!Page} page
-   * @param {string} frameId
-   */
-  constructor(session, frameManager, networkManager, page, frameId, timeoutSettings) {
-    this._session = session;
-    this._page = page;
-    this._frameManager = frameManager;
-    this._networkManager = networkManager;
-    this._timeoutSettings = timeoutSettings;
-    this._frameId = frameId;
-    /** @type {?Frame} */
-    this._parentFrame = null;
-    this._url = '';
-    this._name = '';
-    /** @type {!Set<!Frame>} */
-    this._children = new Set();
-    this._detached = false;
-
-
-    this._firedEvents = new Set();
-
-    this._mainWorld = new DOMWorld(this, timeoutSettings);
-  }
-
-  async executionContext() {
-    return this._mainWorld.executionContext();
-  }
-
-  /**
-   * @param {!{timeout?: number, waitUntil?: string|!Array<string>}} options
-   */
-  async waitForNavigation(options = {}) {
-    const {
-      timeout = this._timeoutSettings.navigationTimeout(),
-      waitUntil = ['load'],
-    } = options;
-    const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
-
-    const timeoutError = new TimeoutError('Navigation timeout of ' + timeout + ' ms exceeded');
-    let timeoutCallback;
-    const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
-    const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
-
-    const nextNavigationDog = new NextNavigationWatchdog(this._session, this);
-    const error1 = await Promise.race([
-      nextNavigationDog.promise(),
-      timeoutPromise,
-    ]);
-    nextNavigationDog.dispose();
-
-    // If timeout happened first - throw.
-    if (error1) {
-      clearTimeout(timeoutId);
-      throw error1;
-    }
-
-    const {navigationId, url} = nextNavigationDog.navigation();
-
-    if (!navigationId) {
-      // Same document navigation happened.
-      clearTimeout(timeoutId);
-      return null;
-    }
-
-    const watchDog = new NavigationWatchdog(this._session, this, this._networkManager, navigationId, url, normalizedWaitUntil);
-    const error = await Promise.race([
-      timeoutPromise,
-      watchDog.promise(),
-    ]);
-    watchDog.dispose();
-    clearTimeout(timeoutId);
-    if (error)
-      throw error;
-    return watchDog.navigationResponse();
-  }
-
-  /**
-   * @param {string} url
-   * @param {!{timeout?: number, waitUntil?: string|!Array<string>}} options
-   */
-  async goto(url, options = {}) {
-    const {
-      timeout = this._timeoutSettings.navigationTimeout(),
-      waitUntil = ['load'],
-      referer,
-    } = options;
-    const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
-    const {navigationId} = await this._session.send('Page.navigate', {
-      frameId: this._frameId,
-      referer,
-      url,
-    });
-    if (!navigationId)
-      return;
-
-    const timeoutError = new TimeoutError('Navigation timeout of ' + timeout + ' ms exceeded');
-    let timeoutCallback;
-    const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
-    const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
-
-    const watchDog = new NavigationWatchdog(this._session, this, this._networkManager, navigationId, url, normalizedWaitUntil);
-    const error = await Promise.race([
-      timeoutPromise,
-      watchDog.promise(),
-    ]);
-    watchDog.dispose();
-    clearTimeout(timeoutId);
-    if (error)
-      throw error;
-    return watchDog.navigationResponse();
-  }
-
-  /**
-   * @param {string} selector
-   * @param {!{delay?: number, button?: string, clickCount?: number}=} options
-   */
-  async click(selector, options = {}) {
-    return this._mainWorld.click(selector, options);
-  }
-
-  /**
-   * @param {string} selector
-   */
-  async tap(selector) {
-    return this._mainWorld.tap(selector);
-  }
-
-  /**
-   * @param {string} selector
-   * @param {string} text
-   * @param {{delay: (number|undefined)}=} options
-   */
-  async type(selector, text, options) {
-    return this._mainWorld.type(selector, text, options);
-  }
-
-  /**
-   * @param {string} selector
-   */
-  async focus(selector) {
-    return this._mainWorld.focus(selector);
-  }
-
-  /**
-   * @param {string} selector
-   */
-  async hover(selector) {
-    return this._mainWorld.hover(selector);
-  }
-
-  _detach() {
-    this._parentFrame._children.delete(this);
-    this._parentFrame = null;
-    this._detached = true;
-    this._mainWorld._detach();
-  }
-
-  _navigated(url, name, navigationId) {
-    this._url = url;
-    this._name = name;
-    this._lastCommittedNavigationId = navigationId;
-    this._firedEvents.clear();
-  }
-
-  /**
-  * @param {string} selector
-  * @param {!Array<string>} values
-  * @return {!Promise<!Array<string>>}
-  */
-  select(selector, ...values) {
-    return this._mainWorld.select(selector, ...values);
-  }
-
-  /**
-   * @param {(string|number|Function)} selectorOrFunctionOrTimeout
-   * @param {!{polling?: string|number, timeout?: number, visible?: boolean, hidden?: boolean}=} options
-   * @param {!Array<*>} args
-   * @return {!Promise<!JSHandle>}
-   */
-  waitFor(selectorOrFunctionOrTimeout, options, ...args) {
-    const xPathPattern = '//';
-
-    if (helper.isString(selectorOrFunctionOrTimeout)) {
-      const string = /** @type {string} */ (selectorOrFunctionOrTimeout);
-      if (string.startsWith(xPathPattern))
-        return this.waitForXPath(string, options);
-      return this.waitForSelector(string, options);
-    }
-    if (helper.isNumber(selectorOrFunctionOrTimeout))
-      return new Promise(fulfill => setTimeout(fulfill, /** @type {number} */ (selectorOrFunctionOrTimeout)));
-    if (typeof selectorOrFunctionOrTimeout === 'function')
-      return this.waitForFunction(selectorOrFunctionOrTimeout, options, ...args);
-    return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout)));
-  }
-
-  /**
-   * @param {Function|string} pageFunction
-   * @param {!{polling?: string|number, timeout?: number}=} options
-   * @return {!Promise<!JSHandle>}
-   */
-  waitForFunction(pageFunction, options = {}, ...args) {
-    return this._mainWorld.waitForFunction(pageFunction, options, ...args);
-  }
-
-  /**
-   * @param {string} selector
-   * @param {!{timeout?: number, visible?: boolean, hidden?: boolean}=} options
-   * @return {!Promise<!ElementHandle>}
-   */
-  waitForSelector(selector, options) {
-    return this._mainWorld.waitForSelector(selector, options);
-  }
-
-  /**
-   * @param {string} xpath
-   * @param {!{timeout?: number, visible?: boolean, hidden?: boolean}=} options
-   * @return {!Promise<!ElementHandle>}
-   */
-  waitForXPath(xpath, options) {
-    return this._mainWorld.waitForXPath(xpath, options);
-  }
-
-  /**
-   * @return {!Promise<String>}
-   */
-  async content() {
-    return this._mainWorld.content();
-  }
-
-  /**
-   * @param {string} html
-   */
-  async setContent(html) {
-    return this._mainWorld.setContent(html);
-  }
-
-  async evaluate(pageFunction, ...args) {
-    return this._mainWorld.evaluate(pageFunction, ...args);
-  }
-
-  /**
-   * @param {string} selector
-   * @return {!Promise<?ElementHandle>}
-   */
-  async $(selector) {
-    return this._mainWorld.$(selector);
-  }
-
-  /**
-   * @param {string} selector
-   * @return {!Promise<!Array<!ElementHandle>>}
-   */
-  async $$(selector) {
-    return this._mainWorld.$$(selector);
-  }
-
-  /**
-   * @param {string} selector
-   * @param {Function|String} pageFunction
-   * @param {!Array<*>} args
-   * @return {!Promise<(!Object|undefined)>}
-   */
-  async $eval(selector, pageFunction, ...args) {
-    return this._mainWorld.$eval(selector, pageFunction, ...args);
-  }
-
-  /**
-   * @param {string} selector
-   * @param {Function|String} pageFunction
-   * @param {!Array<*>} args
-   * @return {!Promise<(!Object|undefined)>}
-   */
-  async $$eval(selector, pageFunction, ...args) {
-    return this._mainWorld.$$eval(selector, pageFunction, ...args);
-  }
-
-  /**
-   * @param {string} expression
-   * @return {!Promise<!Array<!ElementHandle>>}
-   */
-  async $x(expression) {
-    return this._mainWorld.$x(expression);
-  }
-
-  async evaluateHandle(pageFunction, ...args) {
-    return this._mainWorld.evaluateHandle(pageFunction, ...args);
-  }
-
-  /**
-   * @param {!{content?: string, path?: string, type?: string, url?: string}} options
-   * @return {!Promise<!ElementHandle>}
-   */
-  async addScriptTag(options) {
-    return this._mainWorld.addScriptTag(options);
-  }
-
-  /**
-   * @param {!{content?: string, path?: string, url?: string}} options
-   * @return {!Promise<!ElementHandle>}
-   */
-  async addStyleTag(options) {
-    return this._mainWorld.addStyleTag(options);
-  }
-
-  /**
-   * @return {!Promise<string>}
-   */
-  async title() {
-    return this._mainWorld.title();
-  }
-
-  name() {
-    return this._name;
-  }
-
-  isDetached() {
-    return this._detached;
-  }
-
-  childFrames() {
-    return Array.from(this._children);
-  }
-
-  url() {
-    return this._url;
-  }
-
-  parentFrame() {
-    return this._parentFrame;
-  }
-}
-
-function normalizeWaitUntil(waitUntil) {
-  if (!Array.isArray(waitUntil))
-    waitUntil = [waitUntil];
-  for (const condition of waitUntil) {
-    if (condition !== 'load' && condition !== 'domcontentloaded')
-      throw new Error('Unknown waitUntil condition: ' + condition);
-  }
-  return waitUntil;
-}
-
-module.exports = {FrameManager, Frame, normalizeWaitUntil};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/Input.js
+++ /dev/null
@@ -1,340 +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 keyDefinitions = require('./USKeyboardLayout');
-const os = require('os');
-
-/**
- * @typedef {Object} KeyDescription
- * @property {number} keyCode
- * @property {string} key
- * @property {string} text
- * @property {string} code
- * @property {number} location
- */
-
-class Keyboard {
-  constructor(client) {
-    this._client = client;
-    this._modifiers = 0;
-    this._pressedKeys = new Set();
-  }
-
-  /**
-   * @param {string} key
-   */
-  async down(key) {
-    const description = this._keyDescriptionForString(key);
-
-    const repeat = this._pressedKeys.has(description.code);
-    this._pressedKeys.add(description.code);
-    this._modifiers |= this._modifierBit(description.key);
-
-    await this._client.send('Page.dispatchKeyEvent', {
-      type: 'keydown',
-      keyCode: description.keyCode,
-      code: description.code,
-      key: description.key,
-      repeat,
-      location: description.location
-    });
-  }
-
-  /**
-   * @param {string} key
-   * @return {number}
-   */
-  _modifierBit(key) {
-    if (key === 'Alt')
-      return 1;
-    if (key === 'Control')
-      return 2;
-    if (key === 'Shift')
-      return 4;
-    if (key === 'Meta')
-      return 8;
-    return 0;
-  }
-
-  /**
-   * @param {string} keyString
-   * @return {KeyDescription}
-   */
-  _keyDescriptionForString(keyString) {
-    const shift = this._modifiers & 8;
-    const description = {
-      key: '',
-      keyCode: 0,
-      code: '',
-      text: '',
-      location: 0
-    };
-    const definition = keyDefinitions[keyString];
-    if (!definition)
-      throw new Error(`Unknown key: "${keyString}"`);
-
-    if (definition.key)
-      description.key = definition.key;
-    if (shift && definition.shiftKey)
-      description.key = definition.shiftKey;
-
-    if (definition.keyCode)
-      description.keyCode = definition.keyCode;
-    if (shift && definition.shiftKeyCode)
-      description.keyCode = definition.shiftKeyCode;
-
-    if (definition.code)
-      description.code = definition.code;
-
-    if (definition.location)
-      description.location = definition.location;
-
-    if (description.key.length === 1)
-      description.text = description.key;
-
-    if (definition.text)
-      description.text = definition.text;
-    if (shift && definition.shiftText)
-      description.text = definition.shiftText;
-
-    // if any modifiers besides shift are pressed, no text should be sent
-    if (this._modifiers & ~8)
-      description.text = '';
-
-    if (description.code === 'MetaLeft')
-      description.code = 'OSLeft';
-    if (description.code === 'MetaRight')
-      description.code = 'OSRight';
-    return description;
-  }
-
-  /**
-   * @param {string} key
-   */
-  async up(key) {
-    const description = this._keyDescriptionForString(key);
-
-    this._modifiers &= ~this._modifierBit(description.key);
-    this._pressedKeys.delete(description.code);
-    await this._client.send('Page.dispatchKeyEvent', {
-      type: 'keyup',
-      key: description.key,
-      keyCode: description.keyCode,
-      code: description.code,
-      location: description.location,
-      repeat: false
-    });
-  }
-
-  /**
-   * @param {string} char
-   */
-  async sendCharacter(char) {
-    await this._client.send('Page.insertText', {
-      text: char
-    });
-  }
-
-  /**
-   * @param {string} text
-   * @param {!{delay?: number}=} options
-   */
-  async type(text, options = {}) {
-    const {delay = null} = options;
-    for (const char of text) {
-      if (keyDefinitions[char])
-        await this.press(char, {delay});
-      else
-        await this.sendCharacter(char);
-      if (delay !== null)
-        await new Promise(f => setTimeout(f, delay));
-    }
-  }
-
-  /**
-   * @param {string} key
-   * @param {!{delay?: number}=} options
-   */
-  async press(key, options = {}) {
-    const {delay = null} = options;
-    await this.down(key);
-    if (delay !== null)
-      await new Promise(f => setTimeout(f, options.delay));
-    await this.up(key);
-  }
-}
-
-class Mouse {
-  /**
-   * @param {!Keyboard} keyboard
-   */
-  constructor(client, keyboard) {
-    this._client = client;
-    this._keyboard = keyboard;
-    this._x = 0;
-    this._y = 0;
-    this._buttons = 0;
-  }
-
-  /**
-   * @param {number} x
-   * @param {number} y
-   * @param {{steps?: number}=} options
-   */
-  async move(x, y, options = {}) {
-    const {steps = 1} = options;
-    const fromX = this._x, fromY = this._y;
-    this._x = x;
-    this._y = y;
-    for (let i = 1; i <= steps; i++) {
-      await this._client.send('Page.dispatchMouseEvent', {
-        type: 'mousemove',
-        button: 0,
-        x: fromX + (this._x - fromX) * (i / steps),
-        y: fromY + (this._y - fromY) * (i / steps),
-        modifiers: this._keyboard._modifiers,
-        buttons: this._buttons,
-      });
-    }
-  }
-
-  /**
-   * @param {number} x
-   * @param {number} y
-   * @param {!{delay?: number, button?: string, clickCount?: number}=} options
-   */
-  async click(x, y, options = {}) {
-    const {delay = null} = options;
-    if (delay !== null) {
-      await Promise.all([
-        this.move(x, y),
-        this.down(options),
-      ]);
-      await new Promise(f => setTimeout(f, delay));
-      await this.up(options);
-    } else {
-      await Promise.all([
-        this.move(x, y),
-        this.down(options),
-        this.up(options),
-      ]);
-    }
-  }
-
-  /**
-   * @param {!{button?: string, clickCount?: number}=} options
-   */
-  async down(options = {}) {
-    const {
-      button = "left",
-      clickCount = 1
-    } = options;
-    if (button === 'left')
-      this._buttons |= 1;
-    if (button === 'right')
-      this._buttons |= 2;
-    if (button === 'middle')
-      this._buttons |= 4;
-    await this._client.send('Page.dispatchMouseEvent', {
-      type: 'mousedown',
-      button: this._buttonNameToButton(button),
-      x: this._x,
-      y: this._y,
-      modifiers: this._keyboard._modifiers,
-      clickCount,
-      buttons: this._buttons,
-    });
-  }
-
-  /**
-   * @param {string} buttonName
-   * @return {number}
-   */
-  _buttonNameToButton(buttonName) {
-    if (buttonName === 'left')
-      return 0;
-    if (buttonName === 'middle')
-      return 1;
-    if (buttonName === 'right')
-      return 2;
-  }
-
-  /**
-   * @param {!{button?: string, clickCount?: number}=} options
-   */
-  async up(options = {}) {
-    const {
-      button = "left",
-      clickCount = 1
-    } = options;
-    if (button === 'left')
-      this._buttons &= ~1;
-    if (button === 'right')
-      this._buttons &= ~2;
-    if (button === 'middle')
-      this._buttons &= ~4;
-    await this._client.send('Page.dispatchMouseEvent', {
-      type: 'mouseup',
-      button: this._buttonNameToButton(button),
-      x: this._x,
-      y: this._y,
-      modifiers: this._keyboard._modifiers,
-      clickCount: clickCount,
-      buttons: this._buttons,
-    });
-  }
-}
-
-class Touchscreen {
-  /**
-   * @param {Puppeteer.JugglerSession} client
-   * @param {Keyboard} keyboard
-   * @param {Mouse} mouse
-   */
-  constructor(client, keyboard, mouse) {
-    this._client = client;
-    this._keyboard = keyboard;
-    this._mouse = mouse;
-  }
-
-  /**
-   * @param {number} x
-   * @param {number} y
-   */
-  async tap(x, y) {
-    const touchPoints = [{x: Math.round(x), y: Math.round(y)}];
-    let {defaultPrevented} = (await this._client.send('Page.dispatchTouchEvent', {
-      type: 'touchStart',
-      touchPoints,
-      modifiers: this._keyboard._modifiers
-    }));
-    defaultPrevented = (await this._client.send('Page.dispatchTouchEvent', {
-      type: 'touchEnd',
-      touchPoints,
-      modifiers: this._keyboard._modifiers
-    })).defaultPrevented || defaultPrevented;
-    // Do not dispatch related mouse events if either of touch events
-    // were prevented.
-    // See https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent#Event_order
-    if (defaultPrevented)
-      return;
-    await this._mouse.move(x, y);
-    await this._mouse.down();
-    await this._mouse.up();
-  }
-}
-
-module.exports = { Keyboard, Mouse, Touchscreen };
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/JSHandle.js
+++ /dev/null
@@ -1,435 +0,0 @@
-const {assert, debugError} = require('./helper');
-const path = require('path');
-
-class JSHandle {
-
-  /**
-   * @param {!ExecutionContext} context
-   * @param {*} payload
-   */
-  constructor(context, payload) {
-    this._context = context;
-    this._session = this._context._session;
-    this._executionContextId = this._context._executionContextId;
-    this._objectId = payload.objectId;
-    this._type = payload.type;
-    this._subtype = payload.subtype;
-    this._disposed = false;
-    this._protocolValue = {
-      unserializableValue: payload.unserializableValue,
-      value: payload.value,
-      objectId: payload.objectId,
-    };
-  }
-
-  /**
-   * @return {ExecutionContext}
-   */
-  executionContext() {
-    return this._context;
-  }
-
-  /**
-   * @override
-   * @return {string}
-   */
-  toString() {
-    if (this._objectId)
-      return 'JSHandle@' + (this._subtype || this._type);
-    return 'JSHandle:' + this._deserializeValue(this._protocolValue);
-  }
-
-  /**
-   * @param {string} propertyName
-   * @return {!Promise<?JSHandle>}
-   */
-  async getProperty(propertyName) {
-    const objectHandle = await this._context.evaluateHandle((object, propertyName) => {
-      const result = {__proto__: null};
-      result[propertyName] = object[propertyName];
-      return result;
-    }, this, propertyName);
-    const properties = await objectHandle.getProperties();
-    const result = properties.get(propertyName) || null;
-    await objectHandle.dispose();
-    return result;
-  }
-
-  /**
-   * @return {!Promise<Map<string, !JSHandle>>}
-   */
-  async getProperties() {
-    const response = await this._session.send('Runtime.getObjectProperties', {
-      executionContextId: this._executionContextId,
-      objectId: this._objectId,
-    });
-    const result = new Map();
-    for (const property of response.properties) {
-      result.set(property.name, createHandle(this._context, property.value, null));
-    }
-    return result;
-  }
-
-  _deserializeValue({unserializableValue, value}) {
-    if (unserializableValue === 'Infinity')
-      return Infinity;
-    if (unserializableValue === '-Infinity')
-      return -Infinity;
-    if (unserializableValue === '-0')
-      return -0;
-    if (unserializableValue === 'NaN')
-      return NaN;
-    return value;
-  }
-
-  async jsonValue() {
-    if (!this._objectId)
-      return this._deserializeValue(this._protocolValue);
-    const simpleValue = await this._session.send('Runtime.callFunction', {
-      executionContextId: this._executionContextId,
-      returnByValue: true,
-      functionDeclaration: (e => e).toString(),
-      args: [this._protocolValue],
-    });
-    return this._deserializeValue(simpleValue.result);
-  }
-
-  /**
-   * @return {?ElementHandle}
-   */
-  asElement() {
-    return null;
-  }
-
-  async dispose() {
-    if (!this._objectId)
-      return;
-    this._disposed = true;
-    await this._session.send('Runtime.disposeObject', {
-      executionContextId: this._executionContextId,
-      objectId: this._objectId,
-    }).catch(error => {
-      // Exceptions might happen in case of a page been navigated or closed.
-      // Swallow these since they are harmless and we don't leak anything in this case.
-      debugError(error);
-    });
-  }
-}
-
-class ElementHandle extends JSHandle {
-  /**
-   * @param {Frame} frame
-   * @param {ExecutionContext} context
-   * @param {*} payload
-   */
-  constructor(frame, context, payload) {
-    super(context, payload);
-    this._frame = frame;
-    this._frameId = frame._frameId;
-  }
-
-  /**
-   * @return {?Frame}
-   */
-  async contentFrame() {
-    const {frameId} = await this._session.send('Page.contentFrame', {
-      frameId: this._frameId,
-      objectId: this._objectId,
-    });
-    if (!frameId)
-      return null;
-    const frame = this._frame._frameManager.frame(frameId);
-    return frame;
-  }
-
-  /**
-   * @override
-   * @return {!ElementHandle}
-   */
-  asElement() {
-    return this;
-  }
-
-  /**
-   * @return {!Promise<{width: number, height: number, x: number, y: number}>}
-   */
-  async boundingBox() {
-    return await this._session.send('Page.getBoundingBox', {
-      frameId: this._frameId,
-      objectId: this._objectId,
-    });
-  }
-
-  /**
-   * @param {{encoding?: string, path?: string}} options
-   */
-  async screenshot(options = {}) {
-    const clip = await this._session.send('Page.getBoundingBox', {
-      frameId: this._frameId,
-      objectId: this._objectId,
-    });
-    if (!clip)
-      throw new Error('Node is either not visible or not an HTMLElement');
-    assert(clip.width, 'Node has 0 width.');
-    assert(clip.height, 'Node has 0 height.');
-    await this._scrollIntoViewIfNeeded();
-
-    return await this._frame._page.screenshot(Object.assign({}, options, {
-      clip: {
-        x: clip.x,
-        y: clip.y,
-        width: clip.width,
-        height: clip.height,
-      },
-    }));
-  }
-
-  /**
-   * @returns {!Promise<boolean>}
-   */
-  isIntersectingViewport() {
-    return this._frame.evaluate(async element => {
-      const visibleRatio = await new Promise(resolve => {
-        const observer = new IntersectionObserver(entries => {
-          resolve(entries[0].intersectionRatio);
-          observer.disconnect();
-        });
-        observer.observe(element);
-        // Firefox doesn't call IntersectionObserver callback unless
-        // there are rafs.
-        requestAnimationFrame(() => {});
-      });
-      return visibleRatio > 0;
-    }, this);
-  }
-
-  /**
-   * @param {string} selector
-   * @return {!Promise<?ElementHandle>}
-   */
-  async $(selector) {
-    const handle = await this._frame.evaluateHandle(
-        (element, selector) => element.querySelector(selector),
-        this, selector
-    );
-    const element = handle.asElement();
-    if (element)
-      return element;
-    await handle.dispose();
-    return null;
-  }
-
-  /**
-   * @param {string} selector
-   * @return {!Promise<!Array<!ElementHandle>>}
-   */
-  async $$(selector) {
-    const arrayHandle = await this._frame.evaluateHandle(
-        (element, selector) => element.querySelectorAll(selector),
-        this, selector
-    );
-    const properties = await arrayHandle.getProperties();
-    await arrayHandle.dispose();
-    const result = [];
-    for (const property of properties.values()) {
-      const elementHandle = property.asElement();
-      if (elementHandle)
-        result.push(elementHandle);
-    }
-    return result;
-  }
-
-  /**
-   * @param {string} selector
-   * @param {Function|String} pageFunction
-   * @param {!Array<*>} args
-   * @return {!Promise<(!Object|undefined)>}
-   */
-  async $eval(selector, pageFunction, ...args) {
-    const elementHandle = await this.$(selector);
-    if (!elementHandle)
-      throw new Error(`Error: failed to find element matching selector "${selector}"`);
-    const result = await this._frame.evaluate(pageFunction, elementHandle, ...args);
-    await elementHandle.dispose();
-    return result;
-  }
-
-  /**
-   * @param {string} selector
-   * @param {Function|String} pageFunction
-   * @param {!Array<*>} args
-   * @return {!Promise<(!Object|undefined)>}
-   */
-  async $$eval(selector, pageFunction, ...args) {
-    const arrayHandle = await this._frame.evaluateHandle(
-        (element, selector) => Array.from(element.querySelectorAll(selector)),
-        this, selector
-    );
-
-    const result = await this._frame.evaluate(pageFunction, arrayHandle, ...args);
-    await arrayHandle.dispose();
-    return result;
-  }
-
-  /**
-   * @param {string} expression
-   * @return {!Promise<!Array<!ElementHandle>>}
-   */
-  async $x(expression) {
-    const arrayHandle = await this._frame.evaluateHandle(
-        (element, expression) => {
-          const document = element.ownerDocument || element;
-          const iterator = document.evaluate(expression, element, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
-          const array = [];
-          let item;
-          while ((item = iterator.iterateNext()))
-            array.push(item);
-          return array;
-        },
-        this, expression
-    );
-    const properties = await arrayHandle.getProperties();
-    await arrayHandle.dispose();
-    const result = [];
-    for (const property of properties.values()) {
-      const elementHandle = property.asElement();
-      if (elementHandle)
-        result.push(elementHandle);
-    }
-    return result;
-  }
-
-  async _scrollIntoViewIfNeeded() {
-    const error = await this._frame.evaluate(async(element) => {
-      if (!element.isConnected)
-        return 'Node is detached from document';
-      if (element.nodeType !== Node.ELEMENT_NODE)
-        return 'Node is not of type HTMLElement';
-      const visibleRatio = await new Promise(resolve => {
-        const observer = new IntersectionObserver(entries => {
-          resolve(entries[0].intersectionRatio);
-          observer.disconnect();
-        });
-        observer.observe(element);
-        // Firefox doesn't call IntersectionObserver callback unless
-        // there are rafs.
-        requestAnimationFrame(() => {});
-      });
-      if (visibleRatio !== 1.0)
-        element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
-      return false;
-    }, this);
-    if (error)
-      throw new Error(error);
-  }
-
-  /**
-   * @param {!{delay?: number, button?: string, clickCount?: number}=} options
-   */
-  async click(options) {
-    await this._scrollIntoViewIfNeeded();
-    const {x, y} = await this._clickablePoint();
-    await this._frame._page.mouse.click(x, y, options);
-  }
-
-  async tap() {
-    await this._scrollIntoViewIfNeeded();
-    const {x, y} = await this._clickablePoint();
-    await this._frame._page.touchscreen.tap(x, y);
-  }
-
-  /**
-   * @param {!Array<string>} filePaths
-   */
-  async uploadFile(...filePaths) {
-    const files = filePaths.map(filePath => path.resolve(filePath));
-    await this._session.send('Page.setFileInputFiles', {
-      frameId: this._frameId,
-      objectId: this._objectId,
-      files,
-    });
-  }
-
-  async hover() {
-    await this._scrollIntoViewIfNeeded();
-    const {x, y} = await this._clickablePoint();
-    await this._frame._page.mouse.move(x, y);
-  }
-
-  async focus() {
-    await this._frame.evaluate(element => element.focus(), this);
-  }
-
-  /**
-   * @param {string} text
-   * @param {{delay: (number|undefined)}=} options
-   */
-  async type(text, options) {
-    await this.focus();
-    await this._frame._page.keyboard.type(text, options);
-  }
-
-  /**
-   * @param {string} key
-   * @param {!{delay?: number}=} options
-   */
-  async press(key, options) {
-    await this.focus();
-    await this._frame._page.keyboard.press(key, options);
-  }
-
-
-  /**
-   * @return {!Promise<!{x: number, y: number}>}
-   */
-  async _clickablePoint() {
-    const result = await this._session.send('Page.getContentQuads', {
-      frameId: this._frameId,
-      objectId: this._objectId,
-    }).catch(debugError);
-    if (!result || !result.quads.length)
-      throw new Error('Node is either not visible or not an HTMLElement');
-    // Filter out quads that have too small area to click into.
-    const quads = result.quads.filter(quad => computeQuadArea(quad) > 1);
-    if (!quads.length)
-      throw new Error('Node is either not visible or not an HTMLElement');
-    // Return the middle point of the first quad.
-    return computeQuadCenter(quads[0]);
-  }
-}
-
-function createHandle(context, result, exceptionDetails) {
-  const frame = context.frame();
-  if (exceptionDetails) {
-    if (exceptionDetails.value)
-      throw new Error('Evaluation failed: ' + JSON.stringify(exceptionDetails.value));
-    else
-      throw new Error('Evaluation failed: ' + exceptionDetails.text + '\n' + exceptionDetails.stack);
-  }
-  return result.subtype === 'node' ? new ElementHandle(frame, context, result) : new JSHandle(context, result);
-}
-
-function computeQuadArea(quad) {
-  // Compute sum of all directed areas of adjacent triangles
-  // https://en.wikipedia.org/wiki/Polygon#Simple_polygons
-  let area = 0;
-  const points = [quad.p1, quad.p2, quad.p3, quad.p4];
-  for (let i = 0; i < points.length; ++i) {
-    const p1 = points[i];
-    const p2 = points[(i + 1) % points.length];
-    area += (p1.x * p2.y - p2.x * p1.y) / 2;
-  }
-  return Math.abs(area);
-}
-
-function computeQuadCenter(quad) {
-  let x = 0, y = 0;
-  for (const point of [quad.p1, quad.p2, quad.p3, quad.p4]) {
-    x += point.x;
-    y += point.y;
-  }
-  return {x: x / 4, y: y / 4};
-}
-
-
-module.exports = {JSHandle, ElementHandle, createHandle};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/Launcher.js
+++ /dev/null
@@ -1,295 +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 path = require('path');
-const removeFolder = require('rimraf');
-const childProcess = require('child_process');
-const {Connection} = require('./Connection');
-const {Browser} = require('./Browser');
-const {BrowserFetcher} = require('./BrowserFetcher');
-const readline = require('readline');
-const fs = require('fs');
-const util = require('util');
-const {helper, debugError} = require('./helper');
-const {TimeoutError} = require('./Errors')
-const WebSocketTransport = require('./WebSocketTransport');
-
-const mkdtempAsync = util.promisify(fs.mkdtemp);
-const removeFolderAsync = util.promisify(removeFolder);
-
-const FIREFOX_PROFILE_PATH = path.join(os.tmpdir(), 'puppeteer_firefox_profile-');
-
-const DEFAULT_ARGS = [
-  '-no-remote',
-  '-foreground',
-];
-
-/**
- * @internal
- */
-class Launcher {
-  constructor(projectRoot, preferredRevision) {
-    this._projectRoot = projectRoot;
-    this._preferredRevision = preferredRevision;
-  }
-
-  defaultArgs(options = {}) {
-    const {
-      headless = true,
-      args = [],
-      userDataDir = null,
-    } = options;
-    const firefoxArguments = [...DEFAULT_ARGS];
-    if (userDataDir)
-      firefoxArguments.push('-profile', userDataDir);
-    if (headless)
-      firefoxArguments.push('-headless');
-    firefoxArguments.push(...args);
-    if (args.every(arg => arg.startsWith('-')))
-      firefoxArguments.push('about:blank');
-    return firefoxArguments;
-  }
-
-  /**
-   * @param {Object} options
-   * @return {!Promise<!Browser>}
-   */
-  async launch(options = {}) {
-    const {
-      ignoreDefaultArgs = false,
-      args = [],
-      dumpio = false,
-      executablePath = null,
-      env = process.env,
-      handleSIGHUP = true,
-      handleSIGINT = true,
-      handleSIGTERM = true,
-      ignoreHTTPSErrors = false,
-      headless = true,
-      defaultViewport = {width: 800, height: 600},
-      slowMo = 0,
-      timeout = 30000,
-    } = options;
-
-    const firefoxArguments = [];
-    if (!ignoreDefaultArgs)
-      firefoxArguments.push(...this.defaultArgs(options));
-    else if (Array.isArray(ignoreDefaultArgs))
-      firefoxArguments.push(...this.defaultArgs(options).filter(arg => !ignoreDefaultArgs.includes(arg)));
-    else
-      firefoxArguments.push(...args);
-
-    if (!firefoxArguments.includes('-juggler'))
-      firefoxArguments.push('-juggler', '0');
-
-    let temporaryProfileDir = null;
-    if (!firefoxArguments.includes('-profile') && !firefoxArguments.includes('--profile')) {
-      temporaryProfileDir = await mkdtempAsync(FIREFOX_PROFILE_PATH);
-      firefoxArguments.push(`-profile`, temporaryProfileDir);
-    }
-
-    let firefoxExecutable = executablePath;
-    if (!firefoxExecutable) {
-      const {missingText, executablePath} = this._resolveExecutablePath();
-      if (missingText)
-        throw new Error(missingText);
-      firefoxExecutable = executablePath;
-    }
-    const stdio = ['pipe', 'pipe', 'pipe'];
-    const firefoxProcess = childProcess.spawn(
-        firefoxExecutable,
-        firefoxArguments,
-        {
-          // On non-windows platforms, `detached: false` makes child process a leader of a new
-          // process group, making it possible to kill child process tree with `.kill(-pid)` command.
-          // @see https://nodejs.org/api/child_process.html#child_process_options_detached
-          detached: process.platform !== 'win32',
-          stdio,
-          // On linux Juggler ships the libstdc++ it was linked against.
-          env: os.platform() === 'linux' ? {
-            ...env,
-            LD_LIBRARY_PATH: `${path.dirname(firefoxExecutable)}:${process.env.LD_LIBRARY_PATH}`,
-          } : env,
-        }
-    );
-
-    if (dumpio) {
-      firefoxProcess.stderr.pipe(process.stderr);
-      firefoxProcess.stdout.pipe(process.stdout);
-    }
-
-    let firefoxClosed = false;
-    const waitForFirefoxToClose = new Promise((fulfill, reject) => {
-      firefoxProcess.once('exit', () => {
-        firefoxClosed = true;
-        // Cleanup as processes exit.
-        if (temporaryProfileDir) {
-          removeFolderAsync(temporaryProfileDir)
-              .then(() => fulfill())
-              .catch(err => console.error(err));
-        } else {
-          fulfill();
-        }
-      });
-    });
-
-    const listeners = [ helper.addEventListener(process, 'exit', killFirefox) ];
-    if (handleSIGINT)
-      listeners.push(helper.addEventListener(process, 'SIGINT', () => { killFirefox(); process.exit(130); }));
-    if (handleSIGTERM)
-      listeners.push(helper.addEventListener(process, 'SIGTERM', gracefullyCloseFirefox));
-    if (handleSIGHUP)
-      listeners.push(helper.addEventListener(process, 'SIGHUP', gracefullyCloseFirefox));
-    /** @type {?Connection} */
-    let connection = null;
-    try {
-      const url = await waitForWSEndpoint(firefoxProcess, timeout);
-      const transport = await WebSocketTransport.create(url);
-      connection = new Connection(url, transport, slowMo);
-      const browser = await Browser.create(connection, defaultViewport, firefoxProcess, gracefullyCloseFirefox);
-      if (ignoreHTTPSErrors)
-        await connection.send('Browser.setIgnoreHTTPSErrors', {enabled: true});
-      await browser.waitForTarget(t => t.type() === 'page');
-      return browser;
-    } catch (e) {
-      killFirefox();
-      throw e;
-    }
-
-    function gracefullyCloseFirefox() {
-      helper.removeEventListeners(listeners);
-      if (temporaryProfileDir) {
-        killFirefox();
-      } else if (connection) {
-        connection.send('Browser.close').catch(error => {
-          debugError(error);
-          killFirefox();
-        });
-      }
-      return waitForFirefoxToClose;
-    }
-
-    // This method has to be sync to be used as 'exit' event handler.
-    function killFirefox() {
-      helper.removeEventListeners(listeners);
-      if (firefoxProcess.pid && !firefoxProcess.killed && !firefoxClosed) {
-        // Force kill chrome.
-        try {
-          if (process.platform === 'win32')
-            childProcess.execSync(`taskkill /pid ${firefoxProcess.pid} /T /F`);
-          else
-            process.kill(-firefoxProcess.pid, 'SIGKILL');
-        } catch (e) {
-          // the process might have already stopped
-        }
-      }
-      // Attempt to remove temporary profile directory to avoid littering.
-      try {
-        removeFolder.sync(temporaryProfileDir);
-      } catch (e) { }
-    }
-  }
-
-  /**
-   * @param {Object} options
-   * @return {!Promise<!Browser>}
-   */
-  async connect(options = {}) {
-    const {
-      browserWSEndpoint,
-      slowMo = 0,
-      defaultViewport = {width: 800, height: 600},
-      ignoreHTTPSErrors = false,
-    } = options;
-    let connection = null;
-    const transport = await WebSocketTransport.create(browserWSEndpoint);
-    connection = new Connection(browserWSEndpoint, transport, slowMo);
-    const browser = await Browser.create(connection, defaultViewport, null, () => connection.send('Browser.close').catch(debugError));
-    if (ignoreHTTPSErrors)
-      await connection.send('Browser.setIgnoreHTTPSErrors', {enabled: true});
-    return browser;
-  }
-
-  /**
-   * @return {string}
-   */
-  executablePath() {
-    return this._resolveExecutablePath().executablePath;
-  }
-
-  _resolveExecutablePath() {
-    const browserFetcher = new BrowserFetcher(this._projectRoot, { product: 'firefox' });
-    const revisionInfo = browserFetcher.revisionInfo(this._preferredRevision);
-    const missingText = !revisionInfo.local ? `Firefox revision is not downloaded. Run "npm install" or "yarn install"` : null;
-    return {executablePath: revisionInfo.executablePath, missingText};
-  }
-}
-
-/**
- * @param {!Puppeteer.ChildProcess} firefoxProcess
- * @param {number} timeout
- * @return {!Promise<string>}
- */
-function waitForWSEndpoint(firefoxProcess, timeout) {
-  return new Promise((resolve, reject) => {
-    const rl = readline.createInterface({ input: firefoxProcess.stdout });
-    let stderr = '';
-    const listeners = [
-      helper.addEventListener(rl, 'line', onLine),
-      helper.addEventListener(rl, 'close', () => onClose()),
-      helper.addEventListener(firefoxProcess, 'exit', () => onClose()),
-      helper.addEventListener(firefoxProcess, 'error', error => onClose(error))
-    ];
-    const timeoutId = timeout ? setTimeout(onTimeout, timeout) : 0;
-
-    /**
-     * @param {!Error=} error
-     */
-    function onClose(error) {
-      cleanup();
-      reject(new Error([
-        'Failed to launch Firefox!' + (error ? ' ' + error.message : ''),
-        stderr,
-        '',
-      ].join('\n')));
-    }
-
-    function onTimeout() {
-      cleanup();
-      reject(new TimeoutError(`Timed out after ${timeout} ms while trying to connect to Firefox!`));
-    }
-
-    /**
-     * @param {string} line
-     */
-    function onLine(line) {
-      stderr += line + '\n';
-      const match = line.match(/^Juggler listening on (ws:\/\/.*)$/);
-      if (!match)
-        return;
-      cleanup();
-      resolve(match[1]);
-    }
-
-    function cleanup() {
-      if (timeoutId)
-        clearTimeout(timeoutId);
-      helper.removeEventListeners(listeners);
-    }
-  });
-}
-
-module.exports = {Launcher};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/NavigationWatchdog.js
+++ /dev/null
@@ -1,119 +0,0 @@
-const {helper} = require('./helper');
-const {Events} = require('./Events');
-
-/**
- * @internal
- */
-class NextNavigationWatchdog {
-  constructor(session, navigatedFrame) {
-    this._navigatedFrame = navigatedFrame;
-    this._promise = new Promise(x => this._resolveCallback = x);
-    this._navigation = null;
-    this._eventListeners = [
-      helper.addEventListener(session, 'Page.navigationStarted', this._onNavigationStarted.bind(this)),
-      helper.addEventListener(session, 'Page.sameDocumentNavigation', this._onSameDocumentNavigation.bind(this)),
-    ];
-  }
-
-  promise() {
-    return this._promise;
-  }
-
-  navigation() {
-    return this._navigation;
-  }
-
-  _onNavigationStarted(params) {
-    if (params.frameId === this._navigatedFrame._frameId) {
-      this._navigation = {
-        navigationId: params.navigationId,
-        url: params.url,
-      };
-      this._resolveCallback();
-    }
-  }
-
-  _onSameDocumentNavigation(params) {
-    if (params.frameId === this._navigatedFrame._frameId) {
-      this._navigation = {
-        navigationId: null,
-      };
-      this._resolveCallback();
-    }
-  }
-
-  dispose() {
-    helper.removeEventListeners(this._eventListeners);
-  }
-}
-
-/**
- * @internal
- */
-class NavigationWatchdog {
-  constructor(session, navigatedFrame, networkManager, targetNavigationId, targetURL, firedEvents) {
-    this._navigatedFrame = navigatedFrame;
-    this._targetNavigationId = targetNavigationId;
-    this._firedEvents = firedEvents;
-    this._targetURL = targetURL;
-
-    this._promise = new Promise(x => this._resolveCallback = x);
-    this._navigationRequest = null;
-
-    const check = this._checkNavigationComplete.bind(this);
-    this._eventListeners = [
-      helper.addEventListener(session, Events.JugglerSession.Disconnected, () => this._resolveCallback(new Error('Navigation failed because browser has disconnected!'))),
-      helper.addEventListener(session, 'Page.eventFired', check),
-      helper.addEventListener(session, 'Page.frameAttached', check),
-      helper.addEventListener(session, 'Page.frameDetached', check),
-      helper.addEventListener(session, 'Page.navigationStarted', check),
-      helper.addEventListener(session, 'Page.navigationCommitted', check),
-      helper.addEventListener(session, 'Page.navigationAborted', this._onNavigationAborted.bind(this)),
-      helper.addEventListener(networkManager, Events.NetworkManager.Request, this._onRequest.bind(this)),
-      helper.addEventListener(navigatedFrame._frameManager, Events.FrameManager.FrameDetached, check),
-    ];
-    check();
-  }
-
-  _onRequest(request) {
-    if (request.frame() !== this._navigatedFrame || !request.isNavigationRequest())
-      return;
-    this._navigationRequest = request;
-  }
-
-  navigationResponse() {
-    return this._navigationRequest ? this._navigationRequest.response() : null;
-  }
-
-  _checkNavigationComplete() {
-    if (this._navigatedFrame.isDetached()) {
-      this._resolveCallback(new Error('Navigating frame was detached'));
-    } else if (this._navigatedFrame._lastCommittedNavigationId === this._targetNavigationId
-        && checkFiredEvents(this._navigatedFrame, this._firedEvents)) {
-      this._resolveCallback(null);
-    }
-
-    function checkFiredEvents(frame, firedEvents) {
-      for (const subframe of frame._children) {
-        if (!checkFiredEvents(subframe, firedEvents))
-          return false;
-      }
-      return firedEvents.every(event => frame._firedEvents.has(event));
-    }
-  }
-
-  _onNavigationAborted(params) {
-    if (params.frameId === this._navigatedFrame._frameId && params.navigationId === this._targetNavigationId)
-      this._resolveCallback(new Error('Navigation to ' + this._targetURL + ' failed: ' + params.errorText));
-  }
-
-  promise() {
-    return this._promise;
-  }
-
-  dispose() {
-    helper.removeEventListeners(this._eventListeners);
-  }
-}
-
-module.exports = {NavigationWatchdog, NextNavigationWatchdog};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/NetworkManager.js
+++ /dev/null
@@ -1,355 +0,0 @@
-const {helper, assert, debugError} = require('./helper');
-const util = require('util');
-const EventEmitter = require('events');
-const {Events} = require('./Events');
-
-class NetworkManager extends EventEmitter {
-  constructor(session) {
-    super();
-    this._session = session;
-
-    this._requests = new Map();
-    this._frameManager = null;
-
-    this._eventListeners = [
-      helper.addEventListener(session, 'Network.requestWillBeSent', this._onRequestWillBeSent.bind(this)),
-      helper.addEventListener(session, 'Network.responseReceived', this._onResponseReceived.bind(this)),
-      helper.addEventListener(session, 'Network.requestFinished', this._onRequestFinished.bind(this)),
-      helper.addEventListener(session, 'Network.requestFailed', this._onRequestFailed.bind(this)),
-    ];
-  }
-
-  dispose() {
-    helper.removeEventListeners(this._eventListeners);
-  }
-
-  setFrameManager(frameManager) {
-    this._frameManager = frameManager;
-  }
-
-  async setExtraHTTPHeaders(headers) {
-    const array = [];
-    for (const [name, value] of Object.entries(headers)) {
-      assert(helper.isString(value), `Expected value of header "${name}" to be String, but "${typeof value}" is found.`);
-      array.push({name, value});
-    }
-    await this._session.send('Network.setExtraHTTPHeaders', {headers: array});
-  }
-
-  async setRequestInterception(enabled) {
-    await this._session.send('Network.setRequestInterception', {enabled});
-  }
-
-  _onRequestWillBeSent(event) {
-    const redirected = event.redirectedFrom ? this._requests.get(event.redirectedFrom) : null;
-    const frame = redirected ? redirected.frame() : (this._frameManager && event.frameId ? this._frameManager.frame(event.frameId) : null);
-    if (!frame)
-      return;
-    let redirectChain = [];
-    if (redirected) {
-      redirectChain = redirected._redirectChain;
-      redirectChain.push(redirected);
-      this._requests.delete(redirected._id);
-    }
-    const request = new Request(this._session, frame, redirectChain, event);
-    this._requests.set(request._id, request);
-    this.emit(Events.NetworkManager.Request, request);
-  }
-
-  _onResponseReceived(event) {
-    const request = this._requests.get(event.requestId);
-    if (!request)
-      return;
-    const response = new Response(this._session, request, event);
-    request._response = response;
-    this.emit(Events.NetworkManager.Response, response);
-  }
-
-  _onRequestFinished(event) {
-    const request = this._requests.get(event.requestId);
-    if (!request)
-      return;
-    // Keep redirected requests in the map for future reference in redirectChain.
-    const isRedirected = request.response().status() >= 300 && request.response().status() <= 399;
-    if (isRedirected) {
-      request.response()._bodyLoadedPromiseFulfill.call(null, new Error('Response body is unavailable for redirect responses'));
-    } else {
-      this._requests.delete(request._id);
-      request.response()._bodyLoadedPromiseFulfill.call(null);
-    }
-    this.emit(Events.NetworkManager.RequestFinished, request);
-  }
-
-  _onRequestFailed(event) {
-    const request = this._requests.get(event.requestId);
-    if (!request)
-      return;
-    this._requests.delete(request._id);
-    if (request.response())
-      request.response()._bodyLoadedPromiseFulfill.call(null);
-    request._errorText = event.errorCode;
-    this.emit(Events.NetworkManager.RequestFailed, request);
-  }
-}
-
-/**
- *
- * document, stylesheet, image, media, font, script, texttrack, xhr, fetch, eventsource, websocket, manifest, other.
- */
-const causeToResourceType = {
-  TYPE_INVALID: 'other',
-  TYPE_OTHER: 'other',
-  TYPE_SCRIPT: 'script',
-  TYPE_IMAGE: 'image',
-  TYPE_STYLESHEET: 'stylesheet',
-  TYPE_OBJECT: 'other',
-  TYPE_DOCUMENT: 'document',
-  TYPE_SUBDOCUMENT: 'document',
-  TYPE_REFRESH: 'document',
-  TYPE_PING: 'other',
-  TYPE_XMLHTTPREQUEST: 'xhr',
-  TYPE_OBJECT_SUBREQUEST: 'other',
-  TYPE_DTD: 'other',
-  TYPE_FONT: 'font',
-  TYPE_MEDIA: 'media',
-  TYPE_WEBSOCKET: 'websocket',
-  TYPE_CSP_REPORT: 'other',
-  TYPE_XSLT: 'other',
-  TYPE_BEACON: 'other',
-  TYPE_FETCH: 'fetch',
-  TYPE_IMAGESET: 'images',
-  TYPE_WEB_MANIFEST: 'manifest',
-};
-
-class Request {
-  constructor(session, frame, redirectChain, payload) {
-    this._session = session;
-    this._frame = frame;
-    this._id = payload.requestId;
-    this._redirectChain = redirectChain;
-    this._url = payload.url;
-    this._postData = payload.postData;
-    this._suspended = payload.suspended;
-    this._response = null;
-    this._errorText = null;
-    this._isNavigationRequest = payload.isNavigationRequest;
-    this._method = payload.method;
-    this._resourceType = causeToResourceType[payload.cause] || 'other';
-    this._headers = {};
-    this._interceptionHandled = false;
-    for (const {name, value} of payload.headers)
-      this._headers[name.toLowerCase()] = value;
-  }
-
-  failure() {
-    return this._errorText ? {errorText: this._errorText} : null;
-  }
-
-  async continue(overrides = {}) {
-    assert(!overrides.url, 'Puppeteer-Firefox does not support overriding URL');
-    assert(!overrides.method, 'Puppeteer-Firefox does not support overriding method');
-    assert(!overrides.postData, 'Puppeteer-Firefox does not support overriding postData');
-    assert(this._suspended, 'Request Interception is not enabled!');
-    assert(!this._interceptionHandled, 'Request is already handled!');
-    this._interceptionHandled = true;
-    const {
-      headers,
-    } = overrides;
-    await this._session.send('Network.resumeSuspendedRequest', {
-      requestId: this._id,
-      headers: headers ? Object.entries(headers).filter(([, value]) => !Object.is(value, undefined)).map(([name, value]) => ({name, value})) : undefined,
-    }).catch(error => {
-      debugError(error);
-    });
-  }
-
-  async abort() {
-    assert(this._suspended, 'Request Interception is not enabled!');
-    assert(!this._interceptionHandled, 'Request is already handled!');
-    this._interceptionHandled = true;
-    await this._session.send('Network.abortSuspendedRequest', {
-      requestId: this._id,
-    }).catch(error => {
-      debugError(error);
-    });
-  }
-
-  postData() {
-    return this._postData;
-  }
-
-  headers() {
-    return {...this._headers};
-  }
-
-  redirectChain() {
-    return this._redirectChain.slice();
-  }
-
-  resourceType() {
-    return this._resourceType;
-  }
-
-  url() {
-    return this._url;
-  }
-
-  method() {
-    return this._method;
-  }
-
-  isNavigationRequest() {
-    return this._isNavigationRequest;
-  }
-
-  frame() {
-    return this._frame;
-  }
-
-  response() {
-    return this._response;
-  }
-}
-
-class Response {
-  constructor(session, request, payload) {
-    this._session = session;
-    this._request = request;
-    this._remoteIPAddress = payload.remoteIPAddress;
-    this._remotePort = payload.remotePort;
-    this._status = payload.status;
-    this._statusText = payload.statusText;
-    this._headers = {};
-    this._securityDetails = payload.securityDetails ? new SecurityDetails(payload.securityDetails) : null;
-    for (const {name, value} of payload.headers)
-      this._headers[name.toLowerCase()] = value;
-    this._bodyLoadedPromise = new Promise(fulfill => {
-      this._bodyLoadedPromiseFulfill = fulfill;
-    });
-  }
-
-  /**
-   * @return {!Promise<!Buffer>}
-   */
-  buffer() {
-    if (!this._contentPromise) {
-      this._contentPromise = this._bodyLoadedPromise.then(async error => {
-        if (error)
-          throw error;
-        const response = await this._session.send('Network.getResponseBody', {
-          requestId: this._request._id
-        });
-        if (response.evicted)
-          throw new Error(`Response body for ${this._request.method()} ${this._request.url()} was evicted!`);
-        return Buffer.from(response.base64body, 'base64');
-      });
-    }
-    return this._contentPromise;
-  }
-
-  /**
-   * @return {!Promise<string>}
-   */
-  async text() {
-    const content = await this.buffer();
-    return content.toString('utf8');
-  }
-
-  /**
-   * @return {!Promise<!Object>}
-   */
-  async json() {
-    const content = await this.text();
-    return JSON.parse(content);
-  }
-
-  securityDetails() {
-    return this._securityDetails;
-  }
-
-  headers() {
-    return {...this._headers};
-  }
-
-  status() {
-    return this._status;
-  }
-
-  statusText() {
-    return this._statusText;
-  }
-
-  ok() {
-    return this._status >= 200 && this._status <= 299;
-  }
-
-  remoteAddress() {
-    return {
-      ip: this._remoteIPAddress,
-      port: this._remotePort,
-    };
-  }
-
-  frame() {
-    return this._request.frame();
-  }
-
-  url() {
-    return this._request.url();
-  }
-
-  request() {
-    return this._request;
-  }
-}
-
-class SecurityDetails {
-  /**
-   * @param {!Protocol.Network.SecurityDetails} securityPayload
-   */
-  constructor(securityPayload) {
-    this._subjectName = securityPayload['subjectName'];
-    this._issuer = securityPayload['issuer'];
-    this._validFrom = securityPayload['validFrom'];
-    this._validTo = securityPayload['validTo'];
-    this._protocol = securityPayload['protocol'];
-  }
-
-  /**
-   * @return {string}
-   */
-  subjectName() {
-    return this._subjectName;
-  }
-
-  /**
-   * @return {string}
-   */
-  issuer() {
-    return this._issuer;
-  }
-
-  /**
-   * @return {number}
-   */
-  validFrom() {
-    return this._validFrom;
-  }
-
-  /**
-   * @return {number}
-   */
-  validTo() {
-    return this._validTo;
-  }
-
-  /**
-   * @return {string}
-   */
-  protocol() {
-    return this._protocol;
-  }
-}
-
-
-module.exports = {NetworkManager, Request, Response, SecurityDetails};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/Page.js
+++ /dev/null
@@ -1,813 +0,0 @@
-const {helper, debugError, assert} = require('./helper');
-const {Keyboard, Mouse, Touchscreen} = require('./Input');
-const {Dialog} = require('./Dialog');
-const {TimeoutError} = require('./Errors');
-const fs = require('fs');
-const mime = require('mime');
-const EventEmitter = require('events');
-const {createHandle} = require('./JSHandle');
-const {Events} = require('./Events');
-const {Connection} = require('./Connection');
-const {FrameManager, normalizeWaitUntil} = require('./FrameManager');
-const {NetworkManager} = require('./NetworkManager');
-const {TimeoutSettings} = require('./TimeoutSettings');
-const {NavigationWatchdog} = require('./NavigationWatchdog');
-const {Accessibility} = require('./Accessibility');
-
-const writeFileAsync = helper.promisify(fs.writeFile);
-
-class Page extends EventEmitter {
-  /**
-   *
-   * @param {!Puppeteer.JugglerSession} connection
-   * @param {!Puppeteer.Target} target
-   * @param {?Puppeteer.Viewport} defaultViewport
-   */
-  static async create(session, target, defaultViewport) {
-    const page = new Page(session, target);
-    await Promise.all([
-      session.send('Runtime.enable'),
-      session.send('Network.enable'),
-      session.send('Page.enable'),
-    ]);
-
-    if (defaultViewport)
-      await page.setViewport(defaultViewport);
-    return page;
-  }
-
-  /**
-   * @param {!PageSession} session
-   * @param {!Puppeteer.Target} target
-   */
-  constructor(session, target) {
-    super();
-    this._timeoutSettings = new TimeoutSettings();
-    this._session = session;
-    this._target = target;
-    this._keyboard = new Keyboard(session);
-    this._mouse = new Mouse(session, this._keyboard);
-    this._touchscreen = new Touchscreen(session, this._keyboard, this._mouse);
-    this._accessibility = new Accessibility(session);
-    this._closed = false;
-    /** @type {!Map<string, Function>} */
-    this._pageBindings = new Map();
-    this._networkManager = new NetworkManager(session);
-    this._frameManager = new FrameManager(session, this, this._networkManager, this._timeoutSettings);
-    this._networkManager.setFrameManager(this._frameManager);
-    this._eventListeners = [
-      helper.addEventListener(this._session, 'Page.uncaughtError', this._onUncaughtError.bind(this)),
-      helper.addEventListener(this._session, 'Runtime.console', this._onConsole.bind(this)),
-      helper.addEventListener(this._session, 'Page.dialogOpened', this._onDialogOpened.bind(this)),
-      helper.addEventListener(this._session, 'Page.bindingCalled', this._onBindingCalled.bind(this)),
-      helper.addEventListener(this._frameManager, Events.FrameManager.Load, () => this.emit(Events.Page.Load)),
-      helper.addEventListener(this._frameManager, Events.FrameManager.DOMContentLoaded, () => this.emit(Events.Page.DOMContentLoaded)),
-      helper.addEventListener(this._frameManager, Events.FrameManager.FrameAttached, frame => this.emit(Events.Page.FrameAttached, frame)),
-      helper.addEventListener(this._frameManager, Events.FrameManager.FrameDetached, frame => this.emit(Events.Page.FrameDetached, frame)),
-      helper.addEventListener(this._frameManager, Events.FrameManager.FrameNavigated, frame => this.emit(Events.Page.FrameNavigated, frame)),
-      helper.addEventListener(this._networkManager, Events.NetworkManager.Request, request => this.emit(Events.Page.Request, request)),
-      helper.addEventListener(this._networkManager, Events.NetworkManager.Response, response => this.emit(Events.Page.Response, response)),
-      helper.addEventListener(this._networkManager, Events.NetworkManager.RequestFinished, request => this.emit(Events.Page.RequestFinished, request)),
-      helper.addEventListener(this._networkManager, Events.NetworkManager.RequestFailed, request => this.emit(Events.Page.RequestFailed, request)),
-    ];
-    this._viewport = null;
-    this._target._isClosedPromise.then(() => {
-      this._closed = true;
-      this._frameManager.dispose();
-      this._networkManager.dispose();
-      helper.removeEventListeners(this._eventListeners);
-      this.emit(Events.Page.Close);
-    });
-  }
-
-  /**
-   * @param {!Array<string>} urls
-   * @return {!Promise<!Array<Network.Cookie>>}
-   */
-  async cookies(...urls) {
-    const connection = Connection.fromSession(this._session);
-    return (await connection.send('Browser.getCookies', {
-      browserContextId: this._target._context._browserContextId || undefined,
-      urls: urls.length ? urls : [this.url()]
-    })).cookies;
-  }
-
-  /**
-   * @param {Array<Protocol.Network.deleteCookiesParameters>} cookies
-   */
-  async deleteCookie(...cookies) {
-    const pageURL = this.url();
-    const items = [];
-    for (const cookie of cookies) {
-      const item = {
-        url: cookie.url,
-        domain: cookie.domain,
-        path: cookie.path,
-        name: cookie.name,
-      };
-      if (!item.url && pageURL.startsWith('http'))
-        item.url = pageURL;
-      items.push(item);
-    }
-
-    const connection = Connection.fromSession(this._session);
-    await connection.send('Browser.deleteCookies', {
-      browserContextId: this._target._context._browserContextId || undefined,
-      cookies: items,
-    });
-  }
-
-  /**
-   * @param {Array<Network.CookieParam>} cookies
-   */
-  async setCookie(...cookies) {
-    const pageURL = this.url();
-    const startsWithHTTP = pageURL.startsWith('http');
-    const items = cookies.map(cookie => {
-      const item = Object.assign({}, cookie);
-      if (!item.url && startsWithHTTP)
-        item.url = pageURL;
-      assert(item.url !== 'about:blank', `Blank page can not have cookie "${item.name}"`);
-      assert(!String.prototype.startsWith.call(item.url || '', 'data:'), `Data URL page can not have cookie "${item.name}"`);
-      return item;
-    });
-    await this.deleteCookie(...items);
-    if (items.length) {
-      const connection = Connection.fromSession(this._session);
-      await connection.send('Browser.setCookies', {
-        browserContextId: this._target._context._browserContextId || undefined,
-        cookies: items
-      });
-    }
-  }
-
-  async setRequestInterception(enabled) {
-    await this._networkManager.setRequestInterception(enabled);
-  }
-
-  async setExtraHTTPHeaders(headers) {
-    await this._networkManager.setExtraHTTPHeaders(headers);
-  }
-
-  /**
-   * @param {?string} type
-   */
-  async emulateMediaType(type) {
-    assert(type === 'screen' || type === 'print' || type === null, 'Unsupported media type: ' + type);
-    await this._session.send('Page.setEmulatedMedia', {media: type || ''});
-  }
-
-  /**
-   * @param {string} name
-   * @param {Function} puppeteerFunction
-   */
-  async exposeFunction(name, puppeteerFunction) {
-    if (this._pageBindings.has(name))
-      throw new Error(`Failed to add page binding with name ${name}: window['${name}'] already exists!`);
-    this._pageBindings.set(name, puppeteerFunction);
-
-    const expression = helper.evaluationString(addPageBinding, name);
-    await this._session.send('Page.addBinding', {name: name});
-    await this._session.send('Page.addScriptToEvaluateOnNewDocument', {script: expression});
-    await Promise.all(this.frames().map(frame => frame.evaluate(expression).catch(debugError)));
-
-    function addPageBinding(bindingName) {
-      const binding = window[bindingName];
-      window[bindingName] = (...args) => {
-        const me = window[bindingName];
-        let callbacks = me['callbacks'];
-        if (!callbacks) {
-          callbacks = new Map();
-          me['callbacks'] = callbacks;
-        }
-        const seq = (me['lastSeq'] || 0) + 1;
-        me['lastSeq'] = seq;
-        const promise = new Promise((resolve, reject) => callbacks.set(seq, {resolve, reject}));
-        binding(JSON.stringify({name: bindingName, seq, args}));
-        return promise;
-      };
-    }
-  }
-
-  /**
-   * @param {!Protocol.Runtime.bindingCalledPayload} event
-   */
-  async _onBindingCalled(event) {
-    const {name, seq, args} = JSON.parse(event.payload);
-    let expression = null;
-    try {
-      const result = await this._pageBindings.get(name)(...args);
-      expression = helper.evaluationString(deliverResult, name, seq, result);
-    } catch (error) {
-      if (error instanceof Error)
-        expression = helper.evaluationString(deliverError, name, seq, error.message, error.stack);
-      else
-        expression = helper.evaluationString(deliverErrorValue, name, seq, error);
-    }
-    this._session.send('Runtime.evaluate', { expression, executionContextId: event.executionContextId }).catch(debugError);
-
-    /**
-     * @param {string} name
-     * @param {number} seq
-     * @param {*} result
-     */
-    function deliverResult(name, seq, result) {
-      window[name]['callbacks'].get(seq).resolve(result);
-      window[name]['callbacks'].delete(seq);
-    }
-
-    /**
-     * @param {string} name
-     * @param {number} seq
-     * @param {string} message
-     * @param {string} stack
-     */
-    function deliverError(name, seq, message, stack) {
-      const error = new Error(message);
-      error.stack = stack;
-      window[name]['callbacks'].get(seq).reject(error);
-      window[name]['callbacks'].delete(seq);
-    }
-
-    /**
-     * @param {string} name
-     * @param {number} seq
-     * @param {*} value
-     */
-    function deliverErrorValue(name, seq, value) {
-      window[name]['callbacks'].get(seq).reject(value);
-      window[name]['callbacks'].delete(seq);
-    }
-  }
-
-  _sessionClosePromise() {
-    if (!this._disconnectPromise)
-      this._disconnectPromise = new Promise(fulfill => this._session.once(Events.JugglerSession.Disconnected, () => fulfill(new Error('Target closed'))));
-    return this._disconnectPromise;
-  }
-
-  /**
-   * @param {(string|Function)} urlOrPredicate
-   * @param {!{timeout?: number}=} options
-   * @return {!Promise<!Puppeteer.Request>}
-   */
-  async waitForRequest(urlOrPredicate, options = {}) {
-    const {
-      timeout = this._timeoutSettings.timeout(),
-    } = options;
-    return helper.waitForEvent(this._networkManager, Events.NetworkManager.Request, request => {
-      if (helper.isString(urlOrPredicate))
-        return (urlOrPredicate === request.url());
-      if (typeof urlOrPredicate === 'function')
-        return !!(urlOrPredicate(request));
-      return false;
-    }, timeout, this._sessionClosePromise());
-  }
-
-  /**
-   * @param {(string|Function)} urlOrPredicate
-   * @param {!{timeout?: number}=} options
-   * @return {!Promise<!Puppeteer.Response>}
-   */
-  async waitForResponse(urlOrPredicate, options = {}) {
-    const {
-      timeout = this._timeoutSettings.timeout(),
-    } = options;
-    return helper.waitForEvent(this._networkManager, Events.NetworkManager.Response, response => {
-      if (helper.isString(urlOrPredicate))
-        return (urlOrPredicate === response.url());
-      if (typeof urlOrPredicate === 'function')
-        return !!(urlOrPredicate(response));
-      return false;
-    }, timeout, this._sessionClosePromise());
-  }
-
-  /**
-   * @param {number} timeout
-   */
-  setDefaultNavigationTimeout(timeout) {
-    this._timeoutSettings.setDefaultNavigationTimeout(timeout);
-  }
-
-  /**
-   * @param {number} timeout
-   */
-  setDefaultTimeout(timeout) {
-    this._timeoutSettings.setDefaultTimeout(timeout);
-  }
-
-  /**
-   * @param {string} userAgent
-   */
-  async setUserAgent(userAgent) {
-    await this._session.send('Page.setUserAgent', {userAgent});
-  }
-
-  /**
-   * @param {string} userAgent
-   */
-  async setJavaScriptEnabled(enabled) {
-    await this._session.send('Page.setJavascriptEnabled', {enabled});
-  }
-
-  /**
-   * @param {string} userAgent
-   */
-  async setCacheEnabled(enabled) {
-    await this._session.send('Page.setCacheDisabled', {cacheDisabled: !enabled});
-  }
-
-  /**
-   * @param {{viewport: !Puppeteer.Viewport, userAgent: string}} options
-   */
-  async emulate(options) {
-    await Promise.all([
-      this.setViewport(options.viewport),
-      this.setUserAgent(options.userAgent),
-    ]);
-  }
-
-  /**
-   * @return {BrowserContext}
-   */
-  browserContext() {
-    return this._target.browserContext();
-  }
-
-  _onUncaughtError(params) {
-    const error = new Error(params.message);
-    error.stack = params.stack;
-    this.emit(Events.Page.PageError, error);
-  }
-
-  viewport() {
-    return this._viewport;
-  }
-
-  /**
-   * @param {!Puppeteer.Viewport} viewport
-   */
-  async setViewport(viewport) {
-    const {
-      width,
-      height,
-      isMobile = false,
-      deviceScaleFactor = 1,
-      hasTouch = false,
-      isLandscape = false,
-    } = viewport;
-    await this._session.send('Page.setViewport', {
-      viewport: { width, height, isMobile, deviceScaleFactor, hasTouch, isLandscape },
-    });
-    const oldIsMobile = this._viewport ? this._viewport.isMobile : false;
-    const oldHasTouch = this._viewport ? this._viewport.hasTouch : false;
-    this._viewport = viewport;
-    if (oldIsMobile !== isMobile || oldHasTouch !== hasTouch)
-      await this.reload();
-  }
-
-  /**
-   * @param {function()|string} pageFunction
-   * @param {!Array<*>} args
-   */
-  async evaluateOnNewDocument(pageFunction, ...args) {
-    const script = helper.evaluationString(pageFunction, ...args);
-    await this._session.send('Page.addScriptToEvaluateOnNewDocument', { script });
-  }
-
-  browser() {
-    return this._target.browser();
-  }
-
-  target() {
-    return this._target;
-  }
-
-  url() {
-    return this._frameManager.mainFrame().url();
-  }
-
-  frames() {
-    return this._frameManager.frames();
-  }
-
-  _onDialogOpened(params) {
-    this.emit(Events.Page.Dialog, new Dialog(this._session, params));
-  }
-
-  mainFrame() {
-    return this._frameManager.mainFrame();
-  }
-
-  get accessibility() {
-    return this._accessibility;
-  }
-
-  get keyboard(){
-    return this._keyboard;
-  }
-
-  get mouse(){
-    return this._mouse;
-  }
-
-  get touchscreen(){
-    return this._touchscreen;
-  }
-
-  /**
-   * @param {!{timeout?: number, waitUntil?: string|!Array<string>}} options
-   */
-  async waitForNavigation(options = {}) {
-    return this._frameManager.mainFrame().waitForNavigation(options);
-  }
-
-  /**
-   * @param {string} url
-   * @param {!{timeout?: number, waitUntil?: string|!Array<string>}} options
-   */
-  async goto(url, options = {}) {
-    return this._frameManager.mainFrame().goto(url, options);
-  }
-
-  /**
-   * @param {!{timeout?: number, waitUntil?: string|!Array<string>}} options
-   */
-  async goBack(options = {}) {
-    const {
-      timeout = this._timeoutSettings.navigationTimeout(),
-      waitUntil = ['load'],
-    } = options;
-    const frame = this._frameManager.mainFrame();
-    const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
-    const {navigationId, navigationURL} = await this._session.send('Page.goBack', {
-      frameId: frame._frameId,
-    });
-    if (!navigationId)
-      return null;
-
-    const timeoutError = new TimeoutError('Navigation timeout of ' + timeout + ' ms exceeded');
-    let timeoutCallback;
-    const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
-    const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
-
-    const watchDog = new NavigationWatchdog(this._session, frame, this._networkManager, navigationId, navigationURL, normalizedWaitUntil);
-    const error = await Promise.race([
-      timeoutPromise,
-      watchDog.promise(),
-    ]);
-    watchDog.dispose();
-    clearTimeout(timeoutId);
-    if (error)
-      throw error;
-    return watchDog.navigationResponse();
-  }
-
-  /**
-   * @param {!{timeout?: number, waitUntil?: string|!Array<string>}} options
-   */
-  async goForward(options = {}) {
-    const {
-      timeout = this._timeoutSettings.navigationTimeout(),
-      waitUntil = ['load'],
-    } = options;
-    const frame = this._frameManager.mainFrame();
-    const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
-    const {navigationId, navigationURL} = await this._session.send('Page.goForward', {
-      frameId: frame._frameId,
-    });
-    if (!navigationId)
-      return null;
-
-    const timeoutError = new TimeoutError('Navigation timeout of ' + timeout + ' ms exceeded');
-    let timeoutCallback;
-    const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
-    const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
-
-    const watchDog = new NavigationWatchdog(this._session, frame, this._networkManager, navigationId, navigationURL, normalizedWaitUntil);
-    const error = await Promise.race([
-      timeoutPromise,
-      watchDog.promise(),
-    ]);
-    watchDog.dispose();
-    clearTimeout(timeoutId);
-    if (error)
-      throw error;
-    return watchDog.navigationResponse();
-  }
-
-  /**
-   * @param {!{timeout?: number, waitUntil?: string|!Array<string>}} options
-   */
-  async reload(options = {}) {
-    const {
-      timeout = this._timeoutSettings.navigationTimeout(),
-      waitUntil = ['load'],
-    } = options;
-    const frame = this._frameManager.mainFrame();
-    const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
-    const {navigationId, navigationURL} = await this._session.send('Page.reload', {
-      frameId: frame._frameId,
-    });
-    if (!navigationId)
-      return null;
-
-    const timeoutError = new TimeoutError('Navigation timeout of ' + timeout + ' ms exceeded');
-    let timeoutCallback;
-    const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
-    const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
-
-    const watchDog = new NavigationWatchdog(this._session, frame, this._networkManager, navigationId, navigationURL, normalizedWaitUntil);
-    const error = await Promise.race([
-      timeoutPromise,
-      watchDog.promise(),
-    ]);
-    watchDog.dispose();
-    clearTimeout(timeoutId);
-    if (error)
-      throw error;
-    return watchDog.navigationResponse();
-  }
-
-  /**
-   * @param {{fullPage?: boolean, clip?: {width: number, height: number, x: number, y: number}, encoding?: string, path?: string}} options
-   * @return {Promise<string|Buffer>}
-   */
-  async screenshot(options = {}) {
-    const {data} = await this._session.send('Page.screenshot', {
-      mimeType: getScreenshotMimeType(options),
-      fullPage: options.fullPage,
-      clip: processClip(options.clip),
-    });
-    const buffer = options.encoding === 'base64' ? data : Buffer.from(data, 'base64');
-    if (options.path)
-      await writeFileAsync(options.path, buffer);
-    return buffer;
-
-    function processClip(clip) {
-      if (!clip)
-        return undefined;
-      const x = Math.round(clip.x);
-      const y = Math.round(clip.y);
-      const width = Math.round(clip.width + clip.x - x);
-      const height = Math.round(clip.height + clip.y - y);
-      return {x, y, width, height};
-    }
-  }
-
-  async evaluate(pageFunction, ...args) {
-    return await this._frameManager.mainFrame().evaluate(pageFunction, ...args);
-  }
-
-  /**
-   * @param {!{content?: string, path?: string, type?: string, url?: string}} options
-   * @return {!Promise<!ElementHandle>}
-   */
-  async addScriptTag(options) {
-    return await this._frameManager.mainFrame().addScriptTag(options);
-  }
-
-  /**
-   * @param {!{content?: string, path?: string, url?: string}} options
-   * @return {!Promise<!ElementHandle>}
-   */
-  async addStyleTag(options) {
-    return await this._frameManager.mainFrame().addStyleTag(options);
-  }
-
-  /**
-   * @param {string} selector
-   * @param {!{delay?: number, button?: string, clickCount?: number}=} options
-   */
-  async click(selector, options = {}) {
-    return await this._frameManager.mainFrame().click(selector, options);
-  }
-
-  /**
-   * @param {string} selector
-   */
-  tap(selector) {
-    return this.mainFrame().tap(selector);
-  }
-
-  /**
-   * @param {string} selector
-   * @param {string} text
-   * @param {{delay: (number|undefined)}=} options
-   */
-  async type(selector, text, options) {
-    return await this._frameManager.mainFrame().type(selector, text, options);
-  }
-
-  /**
-   * @param {string} selector
-   */
-  async focus(selector) {
-    return await this._frameManager.mainFrame().focus(selector);
-  }
-
-  /**
-   * @param {string} selector
-   */
-  async hover(selector) {
-    return await this._frameManager.mainFrame().hover(selector);
-  }
-
-  /**
-   * @param {(string|number|Function)} selectorOrFunctionOrTimeout
-   * @param {!{polling?: string|number, timeout?: number, visible?: boolean, hidden?: boolean}=} options
-   * @param {!Array<*>} args
-   * @return {!Promise<!JSHandle>}
-   */
-  async waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
-    return await this._frameManager.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
-  }
-
-  /**
-   * @param {Function|string} pageFunction
-   * @param {!{polling?: string|number, timeout?: number}=} options
-   * @return {!Promise<!JSHandle>}
-   */
-  async waitForFunction(pageFunction, options = {}, ...args) {
-    return await this._frameManager.mainFrame().waitForFunction(pageFunction, options, ...args);
-  }
-
-  /**
-   * @param {string} selector
-   * @param {!{timeout?: number, visible?: boolean, hidden?: boolean}=} options
-   * @return {!Promise<!ElementHandle>}
-   */
-  async waitForSelector(selector, options = {}) {
-    return await this._frameManager.mainFrame().waitForSelector(selector, options);
-  }
-
-  /**
-   * @param {string} xpath
-   * @param {!{timeout?: number, visible?: boolean, hidden?: boolean}=} options
-   * @return {!Promise<!ElementHandle>}
-   */
-  async waitForXPath(xpath, options = {}) {
-    return await this._frameManager.mainFrame().waitForXPath(xpath, options);
-  }
-
-  /**
-   * @return {!Promise<string>}
-   */
-  async title() {
-    return await this._frameManager.mainFrame().title();
-  }
-
-  /**
-   * @param {string} selector
-   * @return {!Promise<?ElementHandle>}
-   */
-  async $(selector) {
-    return await this._frameManager.mainFrame().$(selector);
-  }
-
-  /**
-   * @param {string} selector
-   * @return {!Promise<!Array<!ElementHandle>>}
-   */
-  async $$(selector) {
-    return await this._frameManager.mainFrame().$$(selector);
-  }
-
-  /**
-   * @param {string} selector
-   * @param {Function|String} pageFunction
-   * @param {!Array<*>} args
-   * @return {!Promise<(!Object|undefined)>}
-   */
-  async $eval(selector, pageFunction, ...args) {
-    return await this._frameManager.mainFrame().$eval(selector, pageFunction, ...args);
-  }
-
-  /**
-   * @param {string} selector
-   * @param {Function|String} pageFunction
-   * @param {!Array<*>} args
-   * @return {!Promise<(!Object|undefined)>}
-   */
-  async $$eval(selector, pageFunction, ...args) {
-    return await this._frameManager.mainFrame().$$eval(selector, pageFunction, ...args);
-  }
-
-  /**
-   * @param {string} expression
-   * @return {!Promise<!Array<!ElementHandle>>}
-   */
-  async $x(expression) {
-    return await this._frameManager.mainFrame().$x(expression);
-  }
-
-  async evaluateHandle(pageFunction, ...args) {
-    return await this._frameManager.mainFrame().evaluateHandle(pageFunction, ...args);
-  }
-
-  /**
-  * @param {string} selector
-  * @param {!Array<string>} values
-  * @return {!Promise<!Array<string>>}
-  */
-  async select(selector, ...values) {
-    return await this._frameManager.mainFrame().select(selector, ...values);
-  }
-
-  async close(options = {}) {
-    const {
-      runBeforeUnload = false,
-    } = options;
-    await this._session.send('Page.close', { runBeforeUnload });
-    if (!runBeforeUnload)
-      await this._target._isClosedPromise;
-  }
-
-  async content() {
-    return await this._frameManager.mainFrame().content();
-  }
-
-  /**
-   * @param {string} html
-   */
-  async setContent(html) {
-    return await this._frameManager.mainFrame().setContent(html);
-  }
-
-  _onConsole({type, args, executionContextId, location}) {
-    const context = this._frameManager.executionContextById(executionContextId);
-    this.emit(Events.Page.Console, new ConsoleMessage(type, args.map(arg => createHandle(context, arg)), location));
-  }
-
-  /**
-   * @return {boolean}
-   */
-  isClosed() {
-    return this._closed;
-  }
-}
-
-// Expose alias for deprecated method.
-Page.prototype.emulateMedia = Page.prototype.emulateMediaType;
-
-class ConsoleMessage {
-  /**
-   * @param {string} type
-   * @param {!Array<!JSHandle>} args
-   */
-  constructor(type, args, location) {
-    this._type = type;
-    this._args = args;
-    this._location = location;
-  }
-
-  location() {
-    return this._location;
-  }
-
-  /**
-   * @return {string}
-   */
-  type() {
-    return this._type;
-  }
-
-  /**
-   * @return {!Array<!JSHandle>}
-   */
-  args() {
-    return this._args;
-  }
-
-  /**
-   * @return {string}
-   */
-  text() {
-    return this._args.map(arg => {
-      if (arg._objectId)
-        return arg.toString();
-      return arg._deserializeValue(arg._protocolValue);
-    }).join(' ');
-  }
-}
-
-function getScreenshotMimeType(options) {
-  // options.type takes precedence over inferring the type from options.path
-  // because it may be a 0-length file with no extension created beforehand (i.e. as a temp file).
-  if (options.type) {
-    if (options.type === 'png')
-      return 'image/png';
-    if (options.type === 'jpeg')
-      return 'image/jpeg';
-    throw new Error('Unknown options.type value: ' + options.type);
-  }
-  if (options.path) {
-    const fileType = mime.getType(options.path);
-    if (fileType === 'image/png' || fileType === 'image/jpeg')
-      return fileType;
-    throw new Error('Unsupported screenshot mime type: ' + fileType);
-  }
-  return 'image/png';
-}
-
-module.exports = {Page, ConsoleMessage};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/Puppeteer.js
+++ /dev/null
@@ -1,67 +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.
- */
-
-const {Launcher} = require('./Launcher.js');
-const {BrowserFetcher} = require('./BrowserFetcher.js');
-const Errors = require('./Errors');
-const DeviceDescriptors = require('./DeviceDescriptors');
-
-class Puppeteer {
-  /**
-   * @param {string} projectRoot
-   * @param {string} preferredRevision
-   */
-  constructor(projectRoot, preferredRevision) {
-    this._projectRoot = projectRoot;
-    this._launcher = new Launcher(projectRoot, preferredRevision);
-  }
-
-  async launch(options = {}) {
-    return this._launcher.launch(options);
-  }
-
-  async connect(options) {
-    return this._launcher.connect(options);
-  }
-
-  createBrowserFetcher(options) {
-    return new BrowserFetcher(this._projectRoot, options);
-  }
-
-  executablePath() {
-    return this._launcher.executablePath();
-  }
-
-  defaultArgs(options) {
-    return this._launcher.defaultArgs(options);
-  }
-
-  /**
-   * @return {Object}
-   */
-  get devices() {
-    return DeviceDescriptors;
-  }
-
-  /**
-   * @return {Object}
-   */
-  get errors() {
-    return Errors;
-  }
-}
-
-module.exports = {Puppeteer};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/TimeoutSettings.js
+++ /dev/null
@@ -1,60 +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.
- */
-
-const DEFAULT_TIMEOUT = 30000;
-
-class TimeoutSettings {
-  constructor() {
-    this._defaultTimeout = null;
-    this._defaultNavigationTimeout = null;
-  }
-
-  /**
-   * @param {number} timeout
-   */
-  setDefaultTimeout(timeout) {
-    this._defaultTimeout = timeout;
-  }
-
-  /**
-   * @param {number} timeout
-   */
-  setDefaultNavigationTimeout(timeout) {
-    this._defaultNavigationTimeout = timeout;
-  }
-
-  /**
-   * @return {number}
-   */
-  navigationTimeout() {
-    if (this._defaultNavigationTimeout !== null)
-      return this._defaultNavigationTimeout;
-    if (this._defaultTimeout !== null)
-      return this._defaultTimeout;
-    return DEFAULT_TIMEOUT;
-  }
-
-  /**
-   * @return {number}
-   */
-  timeout() {
-    if (this._defaultTimeout !== null)
-      return this._defaultTimeout;
-    return DEFAULT_TIMEOUT;
-  }
-}
-
-module.exports = {TimeoutSettings};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/USKeyboardLayout.js
+++ /dev/null
@@ -1,281 +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.
- */
-
-/**
- * @typedef {Object} KeyDefinition
- * @property {number=} keyCode
- * @property {number=} shiftKeyCode
- * @property {string=} key
- * @property {string=} shiftKey
- * @property {string=} code
- * @property {string=} text
- * @property {string=} shiftText
- * @property {number=} location
- */
-
-/**
- * @type {Object<string, KeyDefinition>}
- */
-module.exports = {
-  '0': {'keyCode': 48, 'key': '0', 'code': 'Digit0'},
-  '1': {'keyCode': 49, 'key': '1', 'code': 'Digit1'},
-  '2': {'keyCode': 50, 'key': '2', 'code': 'Digit2'},
-  '3': {'keyCode': 51, 'key': '3', 'code': 'Digit3'},
-  '4': {'keyCode': 52, 'key': '4', 'code': 'Digit4'},
-  '5': {'keyCode': 53, 'key': '5', 'code': 'Digit5'},
-  '6': {'keyCode': 54, 'key': '6', 'code': 'Digit6'},
-  '7': {'keyCode': 55, 'key': '7', 'code': 'Digit7'},
-  '8': {'keyCode': 56, 'key': '8', 'code': 'Digit8'},
-  '9': {'keyCode': 57, 'key': '9', 'code': 'Digit9'},
-  'Power': {'key': 'Power', 'code': 'Power'},
-  'Eject': {'key': 'Eject', 'code': 'Eject'},
-  'Abort': {'keyCode': 3, 'code': 'Abort', 'key': 'Cancel'},
-  'Help': {'keyCode': 6, 'code': 'Help', 'key': 'Help'},
-  'Backspace': {'keyCode': 8, 'code': 'Backspace', 'key': 'Backspace'},
-  'Tab': {'keyCode': 9, 'code': 'Tab', 'key': 'Tab'},
-  'Numpad5': {'keyCode': 12, 'shiftKeyCode': 101, 'key': 'Clear', 'code': 'Numpad5', 'shiftKey': '5', 'location': 3},
-  'NumpadEnter': {'keyCode': 13, 'code': 'NumpadEnter', 'key': 'Enter', 'text': '\r', 'location': 3},
-  'Enter': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r'},
-  '\r': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r'},
-  '\n': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r'},
-  'ShiftLeft': {'keyCode': 16, 'code': 'ShiftLeft', 'key': 'Shift', 'location': 1},
-  'ShiftRight': {'keyCode': 16, 'code': 'ShiftRight', 'key': 'Shift', 'location': 2},
-  'ControlLeft': {'keyCode': 17, 'code': 'ControlLeft', 'key': 'Control', 'location': 1},
-  'ControlRight': {'keyCode': 17, 'code': 'ControlRight', 'key': 'Control', 'location': 2},
-  'AltLeft': {'keyCode': 18, 'code': 'AltLeft', 'key': 'Alt', 'location': 1},
-  'AltRight': {'keyCode': 18, 'code': 'AltRight', 'key': 'Alt', 'location': 2},
-  'Pause': {'keyCode': 19, 'code': 'Pause', 'key': 'Pause'},
-  'CapsLock': {'keyCode': 20, 'code': 'CapsLock', 'key': 'CapsLock'},
-  'Escape': {'keyCode': 27, 'code': 'Escape', 'key': 'Escape'},
-  'Convert': {'keyCode': 28, 'code': 'Convert', 'key': 'Convert'},
-  'NonConvert': {'keyCode': 29, 'code': 'NonConvert', 'key': 'NonConvert'},
-  'Space': {'keyCode': 32, 'code': 'Space', 'key': ' '},
-  'Numpad9': {'keyCode': 33, 'shiftKeyCode': 105, 'key': 'PageUp', 'code': 'Numpad9', 'shiftKey': '9', 'location': 3},
-  'PageUp': {'keyCode': 33, 'code': 'PageUp', 'key': 'PageUp'},
-  'Numpad3': {'keyCode': 34, 'shiftKeyCode': 99, 'key': 'PageDown', 'code': 'Numpad3', 'shiftKey': '3', 'location': 3},
-  'PageDown': {'keyCode': 34, 'code': 'PageDown', 'key': 'PageDown'},
-  'End': {'keyCode': 35, 'code': 'End', 'key': 'End'},
-  'Numpad1': {'keyCode': 35, 'shiftKeyCode': 97, 'key': 'End', 'code': 'Numpad1', 'shiftKey': '1', 'location': 3},
-  'Home': {'keyCode': 36, 'code': 'Home', 'key': 'Home'},
-  'Numpad7': {'keyCode': 36, 'shiftKeyCode': 103, 'key': 'Home', 'code': 'Numpad7', 'shiftKey': '7', 'location': 3},
-  'ArrowLeft': {'keyCode': 37, 'code': 'ArrowLeft', 'key': 'ArrowLeft'},
-  'Numpad4': {'keyCode': 37, 'shiftKeyCode': 100, 'key': 'ArrowLeft', 'code': 'Numpad4', 'shiftKey': '4', 'location': 3},
-  'Numpad8': {'keyCode': 38, 'shiftKeyCode': 104, 'key': 'ArrowUp', 'code': 'Numpad8', 'shiftKey': '8', 'location': 3},
-  'ArrowUp': {'keyCode': 38, 'code': 'ArrowUp', 'key': 'ArrowUp'},
-  'ArrowRight': {'keyCode': 39, 'code': 'ArrowRight', 'key': 'ArrowRight'},
-  'Numpad6': {'keyCode': 39, 'shiftKeyCode': 102, 'key': 'ArrowRight', 'code': 'Numpad6', 'shiftKey': '6', 'location': 3},
-  'Numpad2': {'keyCode': 40, 'shiftKeyCode': 98, 'key': 'ArrowDown', 'code': 'Numpad2', 'shiftKey': '2', 'location': 3},
-  'ArrowDown': {'keyCode': 40, 'code': 'ArrowDown', 'key': 'ArrowDown'},
-  'Select': {'keyCode': 41, 'code': 'Select', 'key': 'Select'},
-  'Open': {'keyCode': 43, 'code': 'Open', 'key': 'Execute'},
-  'PrintScreen': {'keyCode': 44, 'code': 'PrintScreen', 'key': 'PrintScreen'},
-  'Insert': {'keyCode': 45, 'code': 'Insert', 'key': 'Insert'},
-  'Numpad0': {'keyCode': 45, 'shiftKeyCode': 96, 'key': 'Insert', 'code': 'Numpad0', 'shiftKey': '0', 'location': 3},
-  'Delete': {'keyCode': 46, 'code': 'Delete', 'key': 'Delete'},
-  'NumpadDecimal': {'keyCode': 46, 'shiftKeyCode': 110, 'code': 'NumpadDecimal', 'key': '\u0000', 'shiftKey': '.', 'location': 3},
-  'Digit0': {'keyCode': 48, 'code': 'Digit0', 'shiftKey': ')', 'key': '0'},
-  'Digit1': {'keyCode': 49, 'code': 'Digit1', 'shiftKey': '!', 'key': '1'},
-  'Digit2': {'keyCode': 50, 'code': 'Digit2', 'shiftKey': '@', 'key': '2'},
-  'Digit3': {'keyCode': 51, 'code': 'Digit3', 'shiftKey': '#', 'key': '3'},
-  'Digit4': {'keyCode': 52, 'code': 'Digit4', 'shiftKey': '$', 'key': '4'},
-  'Digit5': {'keyCode': 53, 'code': 'Digit5', 'shiftKey': '%', 'key': '5'},
-  'Digit6': {'keyCode': 54, 'code': 'Digit6', 'shiftKey': '^', 'key': '6'},
-  'Digit7': {'keyCode': 55, 'code': 'Digit7', 'shiftKey': '&', 'key': '7'},
-  'Digit8': {'keyCode': 56, 'code': 'Digit8', 'shiftKey': '*', 'key': '8'},
-  'Digit9': {'keyCode': 57, 'code': 'Digit9', 'shiftKey': '\(', 'key': '9'},
-  'KeyA': {'keyCode': 65, 'code': 'KeyA', 'shiftKey': 'A', 'key': 'a'},
-  'KeyB': {'keyCode': 66, 'code': 'KeyB', 'shiftKey': 'B', 'key': 'b'},
-  'KeyC': {'keyCode': 67, 'code': 'KeyC', 'shiftKey': 'C', 'key': 'c'},
-  'KeyD': {'keyCode': 68, 'code': 'KeyD', 'shiftKey': 'D', 'key': 'd'},
-  'KeyE': {'keyCode': 69, 'code': 'KeyE', 'shiftKey': 'E', 'key': 'e'},
-  'KeyF': {'keyCode': 70, 'code': 'KeyF', 'shiftKey': 'F', 'key': 'f'},
-  'KeyG': {'keyCode': 71, 'code': 'KeyG', 'shiftKey': 'G', 'key': 'g'},
-  'KeyH': {'keyCode': 72, 'code': 'KeyH', 'shiftKey': 'H', 'key': 'h'},
-  'KeyI': {'keyCode': 73, 'code': 'KeyI', 'shiftKey': 'I', 'key': 'i'},
-  'KeyJ': {'keyCode': 74, 'code': 'KeyJ', 'shiftKey': 'J', 'key': 'j'},
-  'KeyK': {'keyCode': 75, 'code': 'KeyK', 'shiftKey': 'K', 'key': 'k'},
-  'KeyL': {'keyCode': 76, 'code': 'KeyL', 'shiftKey': 'L', 'key': 'l'},
-  'KeyM': {'keyCode': 77, 'code': 'KeyM', 'shiftKey': 'M', 'key': 'm'},
-  'KeyN': {'keyCode': 78, 'code': 'KeyN', 'shiftKey': 'N', 'key': 'n'},
-  'KeyO': {'keyCode': 79, 'code': 'KeyO', 'shiftKey': 'O', 'key': 'o'},
-  'KeyP': {'keyCode': 80, 'code': 'KeyP', 'shiftKey': 'P', 'key': 'p'},
-  'KeyQ': {'keyCode': 81, 'code': 'KeyQ', 'shiftKey': 'Q', 'key': 'q'},
-  'KeyR': {'keyCode': 82, 'code': 'KeyR', 'shiftKey': 'R', 'key': 'r'},
-  'KeyS': {'keyCode': 83, 'code': 'KeyS', 'shiftKey': 'S', 'key': 's'},
-  'KeyT': {'keyCode': 84, 'code': 'KeyT', 'shiftKey': 'T', 'key': 't'},
-  'KeyU': {'keyCode': 85, 'code': 'KeyU', 'shiftKey': 'U', 'key': 'u'},
-  'KeyV': {'keyCode': 86, 'code': 'KeyV', 'shiftKey': 'V', 'key': 'v'},
-  'KeyW': {'keyCode': 87, 'code': 'KeyW', 'shiftKey': 'W', 'key': 'w'},
-  'KeyX': {'keyCode': 88, 'code': 'KeyX', 'shiftKey': 'X', 'key': 'x'},
-  'KeyY': {'keyCode': 89, 'code': 'KeyY', 'shiftKey': 'Y', 'key': 'y'},
-  'KeyZ': {'keyCode': 90, 'code': 'KeyZ', 'shiftKey': 'Z', 'key': 'z'},
-  'MetaLeft': {'keyCode': 91, 'code': 'MetaLeft', 'key': 'Meta', 'location': 1},
-  'MetaRight': {'keyCode': 92, 'code': 'MetaRight', 'key': 'Meta', 'location': 2},
-  'ContextMenu': {'keyCode': 93, 'code': 'ContextMenu', 'key': 'ContextMenu'},
-  'NumpadMultiply': {'keyCode': 106, 'code': 'NumpadMultiply', 'key': '*', 'location': 3},
-  'NumpadAdd': {'keyCode': 107, 'code': 'NumpadAdd', 'key': '+', 'location': 3},
-  'NumpadSubtract': {'keyCode': 109, 'code': 'NumpadSubtract', 'key': '-', 'location': 3},
-  'NumpadDivide': {'keyCode': 111, 'code': 'NumpadDivide', 'key': '/', 'location': 3},
-  'F1': {'keyCode': 112, 'code': 'F1', 'key': 'F1'},
-  'F2': {'keyCode': 113, 'code': 'F2', 'key': 'F2'},
-  'F3': {'keyCode': 114, 'code': 'F3', 'key': 'F3'},
-  'F4': {'keyCode': 115, 'code': 'F4', 'key': 'F4'},
-  'F5': {'keyCode': 116, 'code': 'F5', 'key': 'F5'},
-  'F6': {'keyCode': 117, 'code': 'F6', 'key': 'F6'},
-  'F7': {'keyCode': 118, 'code': 'F7', 'key': 'F7'},
-  'F8': {'keyCode': 119, 'code': 'F8', 'key': 'F8'},
-  'F9': {'keyCode': 120, 'code': 'F9', 'key': 'F9'},
-  'F10': {'keyCode': 121, 'code': 'F10', 'key': 'F10'},
-  'F11': {'keyCode': 122, 'code': 'F11', 'key': 'F11'},
-  'F12': {'keyCode': 123, 'code': 'F12', 'key': 'F12'},
-  'F13': {'keyCode': 124, 'code': 'F13', 'key': 'F13'},
-  'F14': {'keyCode': 125, 'code': 'F14', 'key': 'F14'},
-  'F15': {'keyCode': 126, 'code': 'F15', 'key': 'F15'},
-  'F16': {'keyCode': 127, 'code': 'F16', 'key': 'F16'},
-  'F17': {'keyCode': 128, 'code': 'F17', 'key': 'F17'},
-  'F18': {'keyCode': 129, 'code': 'F18', 'key': 'F18'},
-  'F19': {'keyCode': 130, 'code': 'F19', 'key': 'F19'},
-  'F20': {'keyCode': 131, 'code': 'F20', 'key': 'F20'},
-  'F21': {'keyCode': 132, 'code': 'F21', 'key': 'F21'},
-  'F22': {'keyCode': 133, 'code': 'F22', 'key': 'F22'},
-  'F23': {'keyCode': 134, 'code': 'F23', 'key': 'F23'},
-  'F24': {'keyCode': 135, 'code': 'F24', 'key': 'F24'},
-  'NumLock': {'keyCode': 144, 'code': 'NumLock', 'key': 'NumLock'},
-  'ScrollLock': {'keyCode': 145, 'code': 'ScrollLock', 'key': 'ScrollLock'},
-  'AudioVolumeMute': {'keyCode': 173, 'code': 'AudioVolumeMute', 'key': 'AudioVolumeMute'},
-  'AudioVolumeDown': {'keyCode': 174, 'code': 'AudioVolumeDown', 'key': 'AudioVolumeDown'},
-  'AudioVolumeUp': {'keyCode': 175, 'code': 'AudioVolumeUp', 'key': 'AudioVolumeUp'},
-  'MediaTrackNext': {'keyCode': 176, 'code': 'MediaTrackNext', 'key': 'MediaTrackNext'},
-  'MediaTrackPrevious': {'keyCode': 177, 'code': 'MediaTrackPrevious', 'key': 'MediaTrackPrevious'},
-  'MediaStop': {'keyCode': 178, 'code': 'MediaStop', 'key': 'MediaStop'},
-  'MediaPlayPause': {'keyCode': 179, 'code': 'MediaPlayPause', 'key': 'MediaPlayPause'},
-  'Semicolon': {'keyCode': 186, 'code': 'Semicolon', 'shiftKey': ':', 'key': ';'},
-  'Equal': {'keyCode': 187, 'code': 'Equal', 'shiftKey': '+', 'key': '='},
-  'NumpadEqual': {'keyCode': 187, 'code': 'NumpadEqual', 'key': '=', 'location': 3},
-  'Comma': {'keyCode': 188, 'code': 'Comma', 'shiftKey': '\<', 'key': ','},
-  'Minus': {'keyCode': 189, 'code': 'Minus', 'shiftKey': '_', 'key': '-'},
-  'Period': {'keyCode': 190, 'code': 'Period', 'shiftKey': '>', 'key': '.'},
-  'Slash': {'keyCode': 191, 'code': 'Slash', 'shiftKey': '?', 'key': '/'},
-  'Backquote': {'keyCode': 192, 'code': 'Backquote', 'shiftKey': '~', 'key': '`'},
-  'BracketLeft': {'keyCode': 219, 'code': 'BracketLeft', 'shiftKey': '{', 'key': '['},
-  'Backslash': {'keyCode': 220, 'code': 'Backslash', 'shiftKey': '|', 'key': '\\'},
-  'BracketRight': {'keyCode': 221, 'code': 'BracketRight', 'shiftKey': '}', 'key': ']'},
-  'Quote': {'keyCode': 222, 'code': 'Quote', 'shiftKey': '"', 'key': '\''},
-  'AltGraph': {'keyCode': 225, 'code': 'AltGraph', 'key': 'AltGraph'},
-  'Props': {'keyCode': 247, 'code': 'Props', 'key': 'CrSel'},
-  'Cancel': {'keyCode': 3, 'key': 'Cancel', 'code': 'Abort'},
-  'Clear': {'keyCode': 12, 'key': 'Clear', 'code': 'Numpad5', 'location': 3},
-  'Shift': {'keyCode': 16, 'key': 'Shift', 'code': 'ShiftLeft', 'location': 1},
-  'Control': {'keyCode': 17, 'key': 'Control', 'code': 'ControlLeft', 'location': 1},
-  'Alt': {'keyCode': 18, 'key': 'Alt', 'code': 'AltLeft', 'location': 1},
-  'Accept': {'keyCode': 30, 'key': 'Accept'},
-  'ModeChange': {'keyCode': 31, 'key': 'ModeChange'},
-  ' ': {'keyCode': 32, 'key': ' ', 'code': 'Space'},
-  'Print': {'keyCode': 42, 'key': 'Print'},
-  'Execute': {'keyCode': 43, 'key': 'Execute', 'code': 'Open'},
-  '\u0000': {'keyCode': 46, 'key': '\u0000', 'code': 'NumpadDecimal', 'location': 3},
-  'a': {'keyCode': 65, 'key': 'a', 'code': 'KeyA'},
-  'b': {'keyCode': 66, 'key': 'b', 'code': 'KeyB'},
-  'c': {'keyCode': 67, 'key': 'c', 'code': 'KeyC'},
-  'd': {'keyCode': 68, 'key': 'd', 'code': 'KeyD'},
-  'e': {'keyCode': 69, 'key': 'e', 'code': 'KeyE'},
-  'f': {'keyCode': 70, 'key': 'f', 'code': 'KeyF'},
-  'g': {'keyCode': 71, 'key': 'g', 'code': 'KeyG'},
-  'h': {'keyCode': 72, 'key': 'h', 'code': 'KeyH'},
-  'i': {'keyCode': 73, 'key': 'i', 'code': 'KeyI'},
-  'j': {'keyCode': 74, 'key': 'j', 'code': 'KeyJ'},
-  'k': {'keyCode': 75, 'key': 'k', 'code': 'KeyK'},
-  'l': {'keyCode': 76, 'key': 'l', 'code': 'KeyL'},
-  'm': {'keyCode': 77, 'key': 'm', 'code': 'KeyM'},
-  'n': {'keyCode': 78, 'key': 'n', 'code': 'KeyN'},
-  'o': {'keyCode': 79, 'key': 'o', 'code': 'KeyO'},
-  'p': {'keyCode': 80, 'key': 'p', 'code': 'KeyP'},
-  'q': {'keyCode': 81, 'key': 'q', 'code': 'KeyQ'},
-  'r': {'keyCode': 82, 'key': 'r', 'code': 'KeyR'},
-  's': {'keyCode': 83, 'key': 's', 'code': 'KeyS'},
-  't': {'keyCode': 84, 'key': 't', 'code': 'KeyT'},
-  'u': {'keyCode': 85, 'key': 'u', 'code': 'KeyU'},
-  'v': {'keyCode': 86, 'key': 'v', 'code': 'KeyV'},
-  'w': {'keyCode': 87, 'key': 'w', 'code': 'KeyW'},
-  'x': {'keyCode': 88, 'key': 'x', 'code': 'KeyX'},
-  'y': {'keyCode': 89, 'key': 'y', 'code': 'KeyY'},
-  'z': {'keyCode': 90, 'key': 'z', 'code': 'KeyZ'},
-  'Meta': {'keyCode': 91, 'key': 'Meta', 'code': 'MetaLeft', 'location': 1},
-  '*': {'keyCode': 106, 'key': '*', 'code': 'NumpadMultiply', 'location': 3},
-  '+': {'keyCode': 107, 'key': '+', 'code': 'NumpadAdd', 'location': 3},
-  '-': {'keyCode': 109, 'key': '-', 'code': 'NumpadSubtract', 'location': 3},
-  '/': {'keyCode': 111, 'key': '/', 'code': 'NumpadDivide', 'location': 3},
-  ';': {'keyCode': 186, 'key': ';', 'code': 'Semicolon'},
-  '=': {'keyCode': 187, 'key': '=', 'code': 'Equal'},
-  ',': {'keyCode': 188, 'key': ',', 'code': 'Comma'},
-  '.': {'keyCode': 190, 'key': '.', 'code': 'Period'},
-  '`': {'keyCode': 192, 'key': '`', 'code': 'Backquote'},
-  '[': {'keyCode': 219, 'key': '[', 'code': 'BracketLeft'},
-  '\\': {'keyCode': 220, 'key': '\\', 'code': 'Backslash'},
-  ']': {'keyCode': 221, 'key': ']', 'code': 'BracketRight'},
-  '\'': {'keyCode': 222, 'key': '\'', 'code': 'Quote'},
-  'Attn': {'keyCode': 246, 'key': 'Attn'},
-  'CrSel': {'keyCode': 247, 'key': 'CrSel', 'code': 'Props'},
-  'ExSel': {'keyCode': 248, 'key': 'ExSel'},
-  'EraseEof': {'keyCode': 249, 'key': 'EraseEof'},
-  'Play': {'keyCode': 250, 'key': 'Play'},
-  'ZoomOut': {'keyCode': 251, 'key': 'ZoomOut'},
-  ')': {'keyCode': 48, 'key': ')', 'code': 'Digit0'},
-  '!': {'keyCode': 49, 'key': '!', 'code': 'Digit1'},
-  '@': {'keyCode': 50, 'key': '@', 'code': 'Digit2'},
-  '#': {'keyCode': 51, 'key': '#', 'code': 'Digit3'},
-  '$': {'keyCode': 52, 'key': '$', 'code': 'Digit4'},
-  '%': {'keyCode': 53, 'key': '%', 'code': 'Digit5'},
-  '^': {'keyCode': 54, 'key': '^', 'code': 'Digit6'},
-  '&': {'keyCode': 55, 'key': '&', 'code': 'Digit7'},
-  '(': {'keyCode': 57, 'key': '\(', 'code': 'Digit9'},
-  'A': {'keyCode': 65, 'key': 'A', 'code': 'KeyA'},
-  'B': {'keyCode': 66, 'key': 'B', 'code': 'KeyB'},
-  'C': {'keyCode': 67, 'key': 'C', 'code': 'KeyC'},
-  'D': {'keyCode': 68, 'key': 'D', 'code': 'KeyD'},
-  'E': {'keyCode': 69, 'key': 'E', 'code': 'KeyE'},
-  'F': {'keyCode': 70, 'key': 'F', 'code': 'KeyF'},
-  'G': {'keyCode': 71, 'key': 'G', 'code': 'KeyG'},
-  'H': {'keyCode': 72, 'key': 'H', 'code': 'KeyH'},
-  'I': {'keyCode': 73, 'key': 'I', 'code': 'KeyI'},
-  'J': {'keyCode': 74, 'key': 'J', 'code': 'KeyJ'},
-  'K': {'keyCode': 75, 'key': 'K', 'code': 'KeyK'},
-  'L': {'keyCode': 76, 'key': 'L', 'code': 'KeyL'},
-  'M': {'keyCode': 77, 'key': 'M', 'code': 'KeyM'},
-  'N': {'keyCode': 78, 'key': 'N', 'code': 'KeyN'},
-  'O': {'keyCode': 79, 'key': 'O', 'code': 'KeyO'},
-  'P': {'keyCode': 80, 'key': 'P', 'code': 'KeyP'},
-  'Q': {'keyCode': 81, 'key': 'Q', 'code': 'KeyQ'},
-  'R': {'keyCode': 82, 'key': 'R', 'code': 'KeyR'},
-  'S': {'keyCode': 83, 'key': 'S', 'code': 'KeyS'},
-  'T': {'keyCode': 84, 'key': 'T', 'code': 'KeyT'},
-  'U': {'keyCode': 85, 'key': 'U', 'code': 'KeyU'},
-  'V': {'keyCode': 86, 'key': 'V', 'code': 'KeyV'},
-  'W': {'keyCode': 87, 'key': 'W', 'code': 'KeyW'},
-  'X': {'keyCode': 88, 'key': 'X', 'code': 'KeyX'},
-  'Y': {'keyCode': 89, 'key': 'Y', 'code': 'KeyY'},
-  'Z': {'keyCode': 90, 'key': 'Z', 'code': 'KeyZ'},
-  ':': {'keyCode': 186, 'key': ':', 'code': 'Semicolon'},
-  '<': {'keyCode': 188, 'key': '\<', 'code': 'Comma'},
-  '_': {'keyCode': 189, 'key': '_', 'code': 'Minus'},
-  '>': {'keyCode': 190, 'key': '>', 'code': 'Period'},
-  '?': {'keyCode': 191, 'key': '?', 'code': 'Slash'},
-  '~': {'keyCode': 192, 'key': '~', 'code': 'Backquote'},
-  '{': {'keyCode': 219, 'key': '{', 'code': 'BracketLeft'},
-  '|': {'keyCode': 220, 'key': '|', 'code': 'Backslash'},
-  '}': {'keyCode': 221, 'key': '}', 'code': 'BracketRight'},
-  '"': {'keyCode': 222, 'key': '"', 'code': 'Quote'}
-};
\ No newline at end of file
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/WebSocketTransport.js
+++ /dev/null
@@ -1,102 +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 WebSocket = require('ws');
-
-/**
- * @implements {!Puppeteer.ConnectionTransport}
- */
-class WebSocketTransport {
-  /**
-   * @param {string} url
-   * @return {!Promise<!WebSocketTransport>}
-   */
-  static create(url) {
-    return new Promise((resolve, reject) => {
-      const ws = new WebSocket(url, [], { perMessageDeflate: false });
-      ws.addEventListener('open', () => resolve(new WebSocketTransport(ws)));
-      ws.addEventListener('error', reject);
-    });
-  }
-
-  /**
-   * @param {!WebSocket} ws
-   */
-  constructor(ws) {
-    this._ws = ws;
-    this._dispatchQueue = new DispatchQueue(this);
-    this._ws.addEventListener('message', event => {
-      this._dispatchQueue.enqueue(event.data);
-    });
-    this._ws.addEventListener('close', event => {
-      if (this.onclose)
-        this.onclose.call(null);
-    });
-    // Silently ignore all errors - we don't know what to do with them.
-    this._ws.addEventListener('error', () => {});
-    this.onmessage = null;
-    this.onclose = null;
-  }
-
-  /**
-   * @param {string} message
-   */
-  send(message) {
-    this._ws.send(message);
-  }
-
-  close() {
-    this._ws.close();
-  }
-}
-
-// We want to dispatch all "message" events in separate tasks
-// to make sure all message-related promises are resolved first
-// before dispatching next message.
-//
-// We cannot just use setTimeout() in Node.js here like we would
-// do in Browser - see https://github.com/nodejs/node/issues/23773
-// Thus implement a dispatch queue that enforces new tasks manually.
-/**
- * @internal
- */
-class DispatchQueue {
-  constructor(transport) {
-    this._transport = transport;
-
-    this._timeoutId = null;
-    this._queue = [];
-    this._dispatch = this._dispatch.bind(this);
-  }
-
-  enqueue(message) {
-    this._queue.push(message);
-    if (!this._timeoutId)
-      this._timeoutId = setTimeout(this._dispatch, 0);
-  }
-
-  _dispatch() {
-    const message = this._queue.shift();
-    if (this._queue.length)
-      this._timeoutId = setTimeout(this._dispatch, 0)
-    else
-      this._timeoutId = null;
-
-    if (this._transport.onmessage)
-      this._transport.onmessage.call(null, message);
-  }
-}
-
-module.exports = WebSocketTransport;
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/api.js
+++ /dev/null
@@ -1,22 +0,0 @@
-module.exports = {
-  Accessibility: require('./Accessibility').Accessibility,
-  Browser: require('./Browser').Browser,
-  BrowserContext: require('./Browser').BrowserContext,
-  BrowserFetcher: require('./BrowserFetcher').BrowserFetcher,
-  ConsoleMessage: require('./Page').ConsoleMessage,
-  Dialog: require('./Dialog').Dialog,
-  ElementHandle: require('./JSHandle').ElementHandle,
-  ExecutionContext: require('./ExecutionContext').ExecutionContext,
-  Frame: require('./FrameManager').Frame,
-  JSHandle: require('./JSHandle').JSHandle,
-  Keyboard: require('./Input').Keyboard,
-  Mouse: require('./Input').Mouse,
-  Page: require('./Page').Page,
-  Puppeteer: require('./Puppeteer').Puppeteer,
-  Request: require('./NetworkManager').Request,
-  Response: require('./NetworkManager').Response,
-  SecurityDetails: require('./NetworkManager').SecurityDetails,
-  Target: require('./Browser').Target,
-  Touchscreen: require('./Input').Touchscreen,
-  TimeoutError: require('./Errors').TimeoutError,
-};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/externs.d.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { Connection as RealConnection } from './Connection';
-import { Target as RealTarget } from './Browser';
-import * as child_process from 'child_process';
-declare global {
-  module Puppeteer {
-
-    export interface ConnectionTransport {
-      send(string);
-      close();
-      onmessage?: (message: string) => void,
-      onclose?: () => void,
-    }
-
-    export interface ChildProcess extends child_process.ChildProcess { }
-
-    export type Viewport = {
-      width: number;
-      height: number;
-      deviceScaleFactor?: number;
-      isMobile?: boolean;
-      isLandscape?: boolean;
-      hasTouch?: boolean;
-    }
-
-    export class Connection extends RealConnection { }
-    export class Target extends RealTarget { }
-  }
-}
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/lib/helper.js
+++ /dev/null
@@ -1,194 +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 {TimeoutError} = require('./Errors');
-
-/**
- * @internal
- */
-class Helper {
-  /**
-   * @param {!Object} classType
-   */
-  static installAsyncStackHooks(classType) {
-    for (const methodName of Reflect.ownKeys(classType.prototype)) {
-      const method = Reflect.get(classType.prototype, methodName);
-      if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function' || method.constructor.name !== 'AsyncFunction')
-        continue;
-      Reflect.set(classType.prototype, methodName, function(...args) {
-        const syncStack = {};
-        Error.captureStackTrace(syncStack);
-        return method.call(this, ...args).catch(e => {
-          const stack = syncStack.stack.substring(syncStack.stack.indexOf('\n') + 1);
-          const clientStack = stack.substring(stack.indexOf('\n'));
-          if (e instanceof Error && e.stack && !e.stack.includes(clientStack))
-            e.stack += '\n  -- ASYNC --\n' + stack;
-          throw e;
-        });
-      });
-    }
-  }
-
-  /**
-   * @param {Function|string} fun
-   * @param {!Array<*>} args
-   * @return {string}
-   */
-  static evaluationString(fun, ...args) {
-    if (Helper.isString(fun)) {
-      if (args.length !== 0)
-        throw new Error('Cannot evaluate a string with arguments');
-      return /** @type {string} */ (fun);
-    }
-    return `(${fun})(${args.map(serializeArgument).join(',')})`;
-
-    /**
-     * @param {*} arg
-     * @return {string}
-     */
-    function serializeArgument(arg) {
-      if (Object.is(arg, undefined))
-        return 'undefined';
-      return JSON.stringify(arg);
-    }
-  }
-
-  /**
-   * @param {function} nodeFunction
-   * @return {function}
-   */
-  static promisify(nodeFunction) {
-    function promisified(...args) {
-      return new Promise((resolve, reject) => {
-        function callback(err, ...result) {
-          if (err)
-            return reject(err);
-          if (result.length === 1)
-            return resolve(result[0]);
-          return resolve(result);
-        }
-        nodeFunction.call(null, ...args, callback);
-      });
-    }
-    return promisified;
-  }
-
-  /**
-   * @param {!Object} obj
-   * @return {boolean}
-   */
-  static isNumber(obj) {
-    return typeof obj === 'number' || obj instanceof Number;
-  }
-
-  /**
-   * @param {!Object} obj
-   * @return {boolean}
-   */
-  static isString(obj) {
-    return typeof obj === 'string' || obj instanceof String;
-  }
-
-  /**
-   * @param {!NodeJS.EventEmitter} emitter
-   * @param {(string|symbol)} eventName
-   * @param {function(?)} handler
-   * @return {{emitter: !NodeJS.EventEmitter, eventName: (string|symbol), handler: function(?)}}
-   */
-  static addEventListener(emitter, eventName, handler) {
-    emitter.on(eventName, handler);
-    return { emitter, eventName, handler };
-  }
-
-  /**
-   * @param {!Array<{emitter: !NodeJS.EventEmitter, eventName: (string|symbol), handler: function(?)}>} listeners
-   */
-  static removeEventListeners(listeners) {
-    for (const listener of listeners)
-      listener.emitter.removeListener(listener.eventName, listener.handler);
-    listeners.splice(0, listeners.length);
-  }
-
-  /**
-   * @param {!NodeJS.EventEmitter} emitter
-   * @param {(string|symbol)} eventName
-   * @param {function} predicate
-   * @param {number} timeout
-   * @param {!Promise<!Error>} abortPromise
-   * @return {!Promise}
-   */
-  static async waitForEvent(emitter, eventName, predicate, timeout, abortPromise) {
-    let eventTimeout, resolveCallback, rejectCallback;
-    const promise = new Promise((resolve, reject) => {
-      resolveCallback = resolve;
-      rejectCallback = reject;
-    });
-    const listener = Helper.addEventListener(emitter, eventName, event => {
-      if (!predicate(event))
-        return;
-      resolveCallback(event);
-    });
-    if (timeout) {
-      eventTimeout = setTimeout(() => {
-        rejectCallback(new TimeoutError('Timeout exceeded while waiting for event'));
-      }, timeout);
-    }
-    function cleanup() {
-      Helper.removeEventListeners([listener]);
-      clearTimeout(eventTimeout);
-    }
-    const result = await Promise.race([promise, abortPromise]).then(r => {
-      cleanup();
-      return r;
-    }, e => {
-      cleanup();
-      throw e;
-    });
-    if (result instanceof Error)
-      throw result;
-    return result;
-  }
-
-  /**
-   * @template T
-   * @param {!Promise<T>} promise
-   * @param {string} taskName
-   * @param {number} timeout
-   * @return {!Promise<T>}
-   */
-  static async waitWithTimeout(promise, taskName, timeout) {
-    let reject;
-    const timeoutError = new TimeoutError(`waiting for ${taskName} failed: timeout ${timeout}ms exceeded`);
-    const timeoutPromise = new Promise((resolve, x) => reject = x);
-    const timeoutTimer = setTimeout(() => reject(timeoutError), timeout);
-    try {
-      return await Promise.race([promise, timeoutPromise]);
-    } finally {
-      clearTimeout(timeoutTimer);
-    }
-  }
-}
-
-function assert(condition, errorText) {
-  if (!condition)
-    throw new Error(errorText);
-}
-
-
-module.exports = {
-  helper: Helper,
-  debugError: require('debug')(`puppeteer:error`),
-  assert,
-};
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/misc/00-puppeteer-prefs.js
+++ /dev/null
@@ -1,3 +0,0 @@
-// Any comment. You must start the file with a single-line comment!
-pref("general.config.filename", "puppeteer.cfg");
-pref("general.config.obscure_value", 0);
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/misc/install-preferences.js
+++ /dev/null
@@ -1,59 +0,0 @@
-const os = require('os');
-const fs = require('fs');
-const path = require('path');
-const util = require('util');
-
-// Install browser preferences after downloading and unpacking
-// firefox instances.
-// Based on:   https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Enterprise_deployment_before_60#Configuration
-async function installFirefoxPreferences(executablePath) {
-  const firefoxFolder = path.dirname(executablePath);
-  const mkdirAsync = util.promisify(fs.mkdir.bind(fs));
-
-  let prefPath = '';
-  let configPath = '';
-  if (os.platform() === 'darwin') {
-    prefPath = path.join(firefoxFolder, '..', 'Resources', 'defaults', 'pref');
-    configPath = path.join(firefoxFolder, '..', 'Resources');
-  } else if (os.platform() === 'linux') {
-    if (!fs.existsSync(path.join(firefoxFolder, 'browser', 'defaults')))
-      await mkdirAsync(path.join(firefoxFolder, 'browser', 'defaults'));
-    if (!fs.existsSync(path.join(firefoxFolder, 'browser', 'defaults', 'preferences')))
-      await mkdirAsync(path.join(firefoxFolder, 'browser', 'defaults', 'preferences'));
-    prefPath = path.join(firefoxFolder, 'browser', 'defaults', 'preferences');
-    configPath = firefoxFolder;
-  } else if (os.platform() === 'win32') {
-    prefPath = path.join(firefoxFolder, 'defaults', 'pref');
-    configPath = firefoxFolder;
-  } else {
-    throw new Error('Unsupported platform: ' + os.platform());
-  }
-
-  await Promise.all([
-    copyFile({
-      from: path.join(__dirname, '00-puppeteer-prefs.js'),
-      to: path.join(prefPath, '00-puppeteer-prefs.js'),
-    }),
-    copyFile({
-      from: path.join(__dirname, 'puppeteer.cfg'),
-      to: path.join(configPath, 'puppeteer.cfg'),
-    }),
-  ]);
-}
-
-function copyFile({from, to}) {
-  var rd = fs.createReadStream(from);
-  var wr = fs.createWriteStream(to);
-  return new Promise(function(resolve, reject) {
-    rd.on('error', reject);
-    wr.on('error', reject);
-    wr.on('finish', resolve);
-    rd.pipe(wr);
-  }).catch(function(error) {
-    rd.destroy();
-    wr.end();
-    throw error;
-  });
-}
-
-module.exports = installFirefoxPreferences;
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/misc/puppeteer.cfg
+++ /dev/null
@@ -1,209 +0,0 @@
-// Any comment. You must start the file with a comment!
-
-// Make sure Shield doesn't hit the network.
-// pref("app.normandy.api_url", "");
-pref("app.normandy.enabled", false);
-
-// Disable updater
-pref("app.update.enabled", false);
-// make absolutely sure it is really off
-pref("app.update.auto", false);
-pref("app.update.mode", 0);
-pref("app.update.service.enabled", false);
-
-// Dislabe newtabpage
-pref("browser.startup.homepage", 'about:blank');
-pref("browser.newtabpage.enabled", false);
-// Disable topstories
-pref("browser.newtabpage.activity-stream.feeds.section.topstories", false);
-
-// DevTools JSONViewer sometimes fails to load dependencies with its require.js.
-// This doesn't affect Puppeteer operations, but spams console with a lot of
-// unpleasant errors.
-// (bug 1424372)
-pref("devtools.jsonview.enabled", false);
-
-// Increase the APZ content response timeout in tests to 1 minute.
-// This is to accommodate the fact that test environments tends to be
-// slower than production environments (with the b2g emulator being
-// the slowest of them all), resulting in the production timeout value
-// sometimes being exceeded and causing false-positive test failures.
-//
-// (bug 1176798, bug 1177018, bug 1210465)
-pref("apz.content_response_timeout", 60000);
-
-// Allow creating files in content process - required for
-// |Page.setFileInputFiles| protocol method.
-pref("dom.file.createInChild", true);
-
-// Indicate that the download panel has been shown once so that
-// whichever download test runs first doesn't show the popup
-// inconsistently.
-pref("browser.download.panel.shown", true);
-
-// Background thumbnails in particular cause grief, and disabling
-// thumbnails in general cannot hurt
-pref("browser.pagethumbnails.capturing_disabled", true);
-
-// Disable safebrowsing components.
-pref("browser.safebrowsing.blockedURIs.enabled", false);
-pref("browser.safebrowsing.downloads.enabled", false);
-pref("browser.safebrowsing.passwords.enabled", false);
-pref("browser.safebrowsing.malware.enabled", false);
-pref("browser.safebrowsing.phishing.enabled", false);
-
-// Disable updates to search engines.
-pref("browser.search.update", false);
-
-// Do not restore the last open set of tabs if the browser has crashed
-pref("browser.sessionstore.resume_from_crash", false);
-
-// Don't check for the default web browser during startup.
-pref("browser.shell.checkDefaultBrowser", false);
-
-// Do not redirect user when a milstone upgrade of Firefox is detected
-pref("browser.startup.homepage_override.mstone", "ignore");
-
-// Disable browser animations (tabs, fullscreen, sliding alerts)
-pref("toolkit.cosmeticAnimations.enabled", false);
-
-// Close the window when the last tab gets closed
-pref("browser.tabs.closeWindowWithLastTab", true);
-
-// Do not allow background tabs to be zombified on Android, otherwise for
-// tests that open additional tabs, the test harness tab itself might get
-// unloaded
-pref("browser.tabs.disableBackgroundZombification", false);
-
-// Do not warn when closing all open tabs
-pref("browser.tabs.warnOnClose", false);
-
-// Do not warn when closing all other open tabs
-pref("browser.tabs.warnOnCloseOtherTabs", false);
-
-// Do not warn when multiple tabs will be opened
-pref("browser.tabs.warnOnOpen", false);
-
-// Disable first run splash page on Windows 10
-pref("browser.usedOnWindows10.introURL", "");
-
-// Disable the UI tour.
-//
-// Should be set in profile.
-pref("browser.uitour.enabled", false);
-
-// Turn off search suggestions in the location bar so as not to trigger
-// network connections.
-pref("browser.urlbar.suggest.searches", false);
-
-// Do not warn on quitting Firefox
-pref("browser.warnOnQuit", false);
-
-// Do not show datareporting policy notifications which can
-// interfere with tests
-pref(
-  "datareporting.healthreport.documentServerURI",
-  "http://%(server)s/dummy/healthreport/",
-);
-pref("datareporting.healthreport.logging.consoleEnabled", false);
-pref("datareporting.healthreport.service.enabled", false);
-pref("datareporting.healthreport.service.firstRun", false);
-pref("datareporting.healthreport.uploadEnabled", false);
-pref("datareporting.policy.dataSubmissionEnabled", false);
-pref("datareporting.policy.dataSubmissionPolicyAccepted", false);
-pref("datareporting.policy.dataSubmissionPolicyBypassNotification", true);
-
-// Automatically unload beforeunload alerts
-pref("dom.disable_beforeunload", false);
-
-// Disable popup-blocker
-pref("dom.disable_open_during_load", false);
-
-// Disable the ProcessHangMonitor
-pref("dom.ipc.reportProcessHangs", false);
-
-// Disable slow script dialogues
-pref("dom.max_chrome_script_run_time", 0);
-pref("dom.max_script_run_time", 0);
-
-// Only load extensions from the application and user profile
-// AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
-pref("extensions.autoDisableScopes", 0);
-pref("extensions.enabledScopes", 5);
-
-// Disable metadata caching for installed add-ons by default
-pref("extensions.getAddons.cache.enabled", false);
-
-// Disable installing any distribution extensions or add-ons.
-pref("extensions.installDistroAddons", false);
-
-// Turn off extension updates so they do not bother tests
-pref("extensions.update.enabled", false);
-pref("extensions.update.notifyUser", false);
-
-// Make sure opening about:addons will not hit the network
-pref("extensions.getAddons.discovery.api_url","data:, ");
-
-pref("extensions.screenshots.disabled", true);
-pref("extensions.screenshots.upload-disabled", true);
-
-// Allow the application to have focus even it runs in the background
-pref("focusmanager.testmode", true);
-
-// Disable useragent updates
-pref("general.useragent.updates.enabled", false);
-
-// Always use network provider for geolocation tests so we bypass the
-// macOS dialog raised by the corelocation provider
-pref("geo.provider.testing", true);
-
-// Do not scan Wifi
-pref("geo.wifi.scan", false);
-
-// Show chrome errors and warnings in the error console
-pref("javascript.options.showInConsole", true);
-
-// Do not prompt with long usernames or passwords in URLs
-pref("network.http.phishy-userpass-length", 255);
-
-// Do not prompt for temporary redirects
-pref("network.http.prompt-temp-redirect", false);
-
-// Disable speculative connections so they are not reported as leaking
-// when they are hanging around
-pref("network.http.speculative-parallel-limit", 0);
-
-// Do not automatically switch between offline and online
-pref("network.manage-offline-status", false);
-
-// Make sure SNTP requests do not hit the network
-pref("network.sntp.pools", "%(server)s");
-
-// Local documents have access to all other local documents,
-// including directory listings
-pref("security.fileuri.strict_origin_policy", false);
-
-// Tests do not wait for the notification button security delay
-pref("security.notification_enable_delay", 0);
-
-// Ensure blocklist updates do not hit the network
-pref("services.settings.server", "http://%(server)s/dummy/blocklist/");
-
-// Do not automatically fill sign-in forms with known usernames and
-// passwords
-pref("signon.autofillForms", false);
-
-// Disable password capture, so that tests that include forms are not
-// influenced by the presence of the persistent doorhanger notification
-pref("signon.rememberSignons", false);
-
-// Disable first-run welcome page
-pref("startup.homepage_welcome_url", "about:blank");
-pref("startup.homepage_welcome_url.additional", "");
-
-// Prevent starting into safe mode after application crashes
-pref("toolkit.startup.max_resumed_crashes", -1);
-lockPref("toolkit.crashreporter.enabled", false);
-
-// Disable crash reporter.
-Components.classes["@mozilla.org/toolkit/crash-reporter;1"].getService(Components.interfaces.nsICrashReporter).submitReports = false;
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/package.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
-  "name": "puppeteer-firefox",
-  "version": "0.5.0",
-  "description": "Puppeteer API for Firefox",
-  "main": "index.js",
-  "repository": "github:puppeteer/puppeteer",
-  "homepage": "https://github.com/puppeteer/puppeteer/tree/master/experimental/puppeteer-firefox",
-  "engines": {
-    "node": ">=8.9.4"
-  },
-  "puppeteer": {
-    "firefox_revision": "765beffcf39dc68cb2005b2b5343e283e26df7a3"
-  },
-  "scripts": {
-    "install": "node install.js",
-    "tsc": "tsc -p ."
-  },
-  "author": "The Chromium Authors",
-  "license": "Apache-2.0",
-  "dependencies": {
-    "debug": "^4.1.0",
-    "extract-zip": "^1.6.6",
-    "https-proxy-agent": "^2.2.1",
-    "mime": "^2.0.3",
-    "progress": "^2.0.1",
-    "proxy-from-env": "^1.0.0",
-    "rimraf": "^2.6.1",
-    "ws": "^6.1.0"
-  }
-}
deleted file mode 100644
--- a/remote/test/puppeteer/experimental/puppeteer-firefox/tsconfig.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-  "compilerOptions": {
-    "checkJs": true,
-    "allowJs": true,
-    "target": "es2017",
-    "noEmit": true
-  },
-  "include": [
-    "lib"
-  ]
-}
\ No newline at end of file