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