puppeteer-core
Advanced tools
Comparing version 1.14.0 to 1.15.0
@@ -0,1 +1,19 @@ | ||
<!-- gen:toc --> | ||
- [How to Contribute](#how-to-contribute) | ||
* [Contributor License Agreement](#contributor-license-agreement) | ||
* [Getting setup](#getting-setup) | ||
* [Code reviews](#code-reviews) | ||
* [Code Style](#code-style) | ||
* [API guidelines](#api-guidelines) | ||
* [Commit Messages](#commit-messages) | ||
* [Writing Documentation](#writing-documentation) | ||
* [Adding New Dependencies](#adding-new-dependencies) | ||
* [Writing Tests](#writing-tests) | ||
* [Public API Coverage](#public-api-coverage) | ||
* [Debugging Puppeteer](#debugging-puppeteer) | ||
- [For Project Maintainers](#for-project-maintainers) | ||
* [Releasing to NPM](#releasing-to-npm) | ||
* [Building Chromium Revisions](#building-chromium-revisions) | ||
<!-- gen:stop --> | ||
# How to Contribute | ||
@@ -202,4 +220,6 @@ | ||
## [For Project Maintainers] Releasing to NPM | ||
# For Project Maintainers | ||
## Releasing to NPM | ||
Releasing to NPM consists of 3 phases: | ||
@@ -228,1 +248,10 @@ 1. Source Code: mark a release. | ||
## Building Chromium Revisions | ||
Project maintainers with explicit access can request Chromium builds. | ||
1. Goto [rpc explorer](https://cr-buildbucket.appspot.com/rpcexplorer/services/buildbucket.v2.Builds/ScheduleBuild?request={%20%20%20%20%22builder%22:%20{%20%20%20%20%20%20%20%20%22project%22:%20%22chromium%22,%20%20%20%20%20%20%20%20%22bucket%22:%20%22ci%22,%20%20%20%20%20%20%20%20%22builder%22:%20%22mac-rel%22%20%20%20%20},%20%20%20%20%22gitilesCommit%22:%20{%20%20%20%20%20%20%20%20%22host%22:%20%22chromium.googlesource.com%22,%20%20%20%20%20%20%20%20%22project%22:%20%22chromium/src%22,%20%20%20%20%20%20%20%20%22id%22:%20%22f6d8f73b94d1715b64f621d2112fbcecd0fc860a%22,%20%20%20%20%20%20%20%20%22ref%22:%20%22refs/heads/master%22%20%20%20%20},%20%20%20%20%22requestId%22:%20%22random%20string%20i%20guess%22}) | ||
2. Login with Google account | ||
3. Set "builder" to either "mac-rel", "linux-rel", "win32-rel" or "win-rel" | ||
4. Set "id" to the full SHA of the commit to be built | ||
5. Hit "Send" |
/** | ||
* Copyright 2017 Google Inc. All rights reserved. | ||
* Copyright 2019 Google Inc. All rights reserved. | ||
* | ||
@@ -17,809 +17,13 @@ * Licensed under the Apache License, Version 2.0 (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; | ||
let asyncawait = true; | ||
try { | ||
new Function('async function test(){await 1}'); | ||
} catch (error) { | ||
asyncawait = false; | ||
} | ||
// If node does not support async await, use the compiled version. | ||
if (asyncawait) | ||
module.exports = require('./lib/DeviceDescriptors'); | ||
else | ||
module.exports = require('./node6/lib/DeviceDescriptors'); |
@@ -73,2 +73,9 @@ /** | ||
/** | ||
* @return {boolean} | ||
*/ | ||
_hasContext() { | ||
return !this._contextResolveCallback; | ||
} | ||
_detach() { | ||
@@ -75,0 +82,0 @@ this._detached = true; |
@@ -23,2 +23,3 @@ /** | ||
const {DOMWorld} = require('./DOMWorld'); | ||
const {NetworkManager} = require('./NetworkManager'); | ||
@@ -30,12 +31,12 @@ const UTILITY_WORLD_NAME = '__puppeteer_utility_world__'; | ||
* @param {!Puppeteer.CDPSession} client | ||
* @param {!Protocol.Page.FrameTree} frameTree | ||
* @param {!Puppeteer.Page} page | ||
* @param {!Puppeteer.NetworkManager} networkManager | ||
* @param {boolean} ignoreHTTPSErrors | ||
* @param {!Puppeteer.TimeoutSettings} timeoutSettings | ||
*/ | ||
constructor(client, frameTree, page, networkManager, timeoutSettings) { | ||
constructor(client, page, ignoreHTTPSErrors, timeoutSettings) { | ||
super(); | ||
this._client = client; | ||
this._page = page; | ||
this._networkManager = networkManager; | ||
this._networkManager = new NetworkManager(client, ignoreHTTPSErrors); | ||
this._networkManager.setFrameManager(this); | ||
this._timeoutSettings = timeoutSettings; | ||
@@ -58,6 +59,25 @@ /** @type {!Map<string, !Frame>} */ | ||
this._client.on('Page.lifecycleEvent', event => this._onLifecycleEvent(event)); | ||
} | ||
async initialize() { | ||
const [,{frameTree}] = await Promise.all([ | ||
this._client.send('Page.enable'), | ||
this._client.send('Page.getFrameTree'), | ||
]); | ||
this._handleFrameTree(frameTree); | ||
await Promise.all([ | ||
this._client.send('Page.setLifecycleEventsEnabled', { enabled: true }), | ||
this._client.send('Runtime.enable', {}).then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)), | ||
this._networkManager.initialize(), | ||
]); | ||
} | ||
/** | ||
* @return {!NetworkManager} | ||
*/ | ||
networkManager() { | ||
return this._networkManager; | ||
} | ||
/** | ||
* @param {!Puppeteer.Frame} frame | ||
@@ -247,6 +267,2 @@ * @param {string} url | ||
async ensureSecondaryDOMWorld() { | ||
await this._ensureIsolatedWorld(UTILITY_WORLD_NAME); | ||
} | ||
/** | ||
@@ -297,6 +313,10 @@ * @param {string} name | ||
if (frame) { | ||
if (contextPayload.auxData && !!contextPayload.auxData['isDefault']) | ||
if (contextPayload.auxData && !!contextPayload.auxData['isDefault']) { | ||
world = frame._mainWorld; | ||
else if (contextPayload.name === UTILITY_WORLD_NAME) | ||
} else if (contextPayload.name === UTILITY_WORLD_NAME && !frame._secondaryWorld._hasContext()) { | ||
// In case of multiple sessions to the same target, there's a race between | ||
// connections so we might end up creating multiple isolated worlds. | ||
// We can use either. | ||
world = frame._secondaryWorld; | ||
} | ||
} | ||
@@ -303,0 +323,0 @@ if (contextPayload.auxData && contextPayload.auxData['type'] === 'isolated') |
@@ -192,9 +192,13 @@ /** | ||
async _clickablePoint() { | ||
const result = await this._client.send('DOM.getContentQuads', { | ||
objectId: this._remoteObject.objectId | ||
}).catch(debugError); | ||
const [result, layoutMetrics] = await Promise.all([ | ||
this._client.send('DOM.getContentQuads', { | ||
objectId: this._remoteObject.objectId | ||
}).catch(debugError), | ||
this._client.send('Page.getLayoutMetrics'), | ||
]); | ||
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.map(quad => this._fromProtocolQuad(quad)).filter(quad => computeQuadArea(quad) > 1); | ||
const {clientWidth, clientHeight} = layoutMetrics.layoutViewport; | ||
const quads = result.quads.map(quad => this._fromProtocolQuad(quad)).map(quad => this._intersectQuadWithViewport(quad, clientWidth, clientHeight)).filter(quad => computeQuadArea(quad) > 1); | ||
if (!quads.length) | ||
@@ -238,2 +242,15 @@ throw new Error('Node is either not visible or not an HTMLElement'); | ||
/** | ||
* @param {!Array<{x: number, y: number}>} quad | ||
* @param {number} width | ||
* @param {number} height | ||
* @return {!Array<{x: number, y: number}>} | ||
*/ | ||
_intersectQuadWithViewport(quad, width, height) { | ||
return quad.map(point => ({ | ||
x: Math.min(Math.max(point.x, 0), width), | ||
y: Math.min(Math.max(point.y, 0), height), | ||
})); | ||
} | ||
async hover() { | ||
@@ -240,0 +257,0 @@ await this._scrollIntoViewIfNeeded(); |
@@ -19,2 +19,3 @@ /** | ||
const http = require('http'); | ||
const https = require('https'); | ||
const URL = require('url'); | ||
@@ -60,3 +61,2 @@ const removeFolder = require('rimraf'); | ||
'--no-first-run', | ||
'--safebrowsing-disable-auto-update', | ||
'--enable-automation', | ||
@@ -302,3 +302,3 @@ '--password-store=basic', | ||
if (!this._isPuppeteerCore) { | ||
const executablePath = process.env['PUPPETEER_EXECUTABLE_PATH']; | ||
const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH || process.env.npm_config_puppeteer_executable_path || process.env.npm_package_config_puppeteer_executable_path; | ||
if (executablePath) { | ||
@@ -388,4 +388,5 @@ const missingText = !fs.existsSync(executablePath) ? 'Tried to use PUPPETEER_EXECUTABLE_PATH env variable to launch browser but did not find any executable at: ' + executablePath : null; | ||
const endpointURL = URL.resolve(browserURL, '/json/version'); | ||
const protocol = endpointURL.startsWith('https') ? https : http; | ||
const requestOptions = Object.assign(URL.parse(endpointURL), { method: 'GET' }); | ||
const request = http.request(requestOptions, res => { | ||
const request = protocol.request(requestOptions, res => { | ||
let data = ''; | ||
@@ -392,0 +393,0 @@ if (res.statusCode !== 200) { |
@@ -40,3 +40,2 @@ /** | ||
this._frameManager = frameManager; | ||
this._networkManager = frameManager._networkManager; | ||
this._frame = frame; | ||
@@ -52,3 +51,3 @@ this._initialLoaderId = frame._loaderId; | ||
helper.addEventListener(this._frameManager, Events.FrameManager.FrameDetached, this._onFrameDetached.bind(this)), | ||
helper.addEventListener(this._networkManager, Events.NetworkManager.Request, this._onRequest.bind(this)), | ||
helper.addEventListener(this._frameManager.networkManager(), Events.NetworkManager.Request, this._onRequest.bind(this)), | ||
]; | ||
@@ -55,0 +54,0 @@ |
@@ -19,3 +19,2 @@ /** | ||
const {Events} = require('./Events'); | ||
const Multimap = require('./Multimap'); | ||
@@ -26,5 +25,6 @@ class NetworkManager extends EventEmitter { | ||
*/ | ||
constructor(client) { | ||
constructor(client, ignoreHTTPSErrors) { | ||
super(); | ||
this._client = client; | ||
this._ignoreHTTPSErrors = ignoreHTTPSErrors; | ||
this._frameManager = null; | ||
@@ -46,9 +46,9 @@ /** @type {!Map<string, !Request>} */ | ||
this._protocolRequestInterceptionEnabled = false; | ||
/** @type {!Multimap<string, string>} */ | ||
this._requestHashToRequestIds = new Multimap(); | ||
/** @type {!Multimap<string, string>} */ | ||
this._requestHashToInterceptionIds = new Multimap(); | ||
this._userCacheDisabled = false; | ||
/** @type {!Map<string, string>} */ | ||
this._requestIdToInterceptionId = new Map(); | ||
this._client.on('Fetch.requestPaused', this._onRequestPaused.bind(this)); | ||
this._client.on('Fetch.authRequired', this._onAuthRequired.bind(this)); | ||
this._client.on('Network.requestWillBeSent', this._onRequestWillBeSent.bind(this)); | ||
this._client.on('Network.requestIntercepted', this._onRequestIntercepted.bind(this)); | ||
this._client.on('Network.requestServedFromCache', this._onRequestServedFromCache.bind(this)); | ||
@@ -60,2 +60,8 @@ this._client.on('Network.responseReceived', this._onResponseReceived.bind(this)); | ||
async initialize() { | ||
await this._client.send('Network.enable'); | ||
if (this._ignoreHTTPSErrors) | ||
await this._client.send('Security.setIgnoreCertificateErrors', {ignore: true}); | ||
} | ||
/** | ||
@@ -120,2 +126,10 @@ * @param {!Puppeteer.FrameManager} frameManager | ||
/** | ||
* @param {boolean} enabled | ||
*/ | ||
async setCacheEnabled(enabled) { | ||
this._userCacheDisabled = !enabled; | ||
await this._updateProtocolCacheDisabled(); | ||
} | ||
/** | ||
* @param {boolean} value | ||
@@ -133,9 +147,24 @@ */ | ||
this._protocolRequestInterceptionEnabled = enabled; | ||
const patterns = enabled ? [{urlPattern: '*'}] : []; | ||
await Promise.all([ | ||
this._client.send('Network.setCacheDisabled', {cacheDisabled: enabled}), | ||
this._client.send('Network.setRequestInterception', {patterns}) | ||
]); | ||
if (enabled) { | ||
await Promise.all([ | ||
this._updateProtocolCacheDisabled(), | ||
this._client.send('Fetch.enable', { | ||
handleAuthRequests: true, | ||
patterns: [{urlPattern: '*'}], | ||
}), | ||
]); | ||
} else { | ||
await Promise.all([ | ||
this._updateProtocolCacheDisabled(), | ||
this._client.send('Fetch.disable') | ||
]); | ||
} | ||
} | ||
async _updateProtocolCacheDisabled() { | ||
await this._client.send('Network.setCacheDisabled', { | ||
cacheDisabled: this._userCacheDisabled || this._protocolRequestInterceptionEnabled | ||
}); | ||
} | ||
/** | ||
@@ -147,9 +176,8 @@ * @param {!Protocol.Network.requestWillBeSentPayload} event | ||
if (this._protocolRequestInterceptionEnabled && !event.request.url.startsWith('data:')) { | ||
const requestHash = generateRequestHash(event.request); | ||
const interceptionId = this._requestHashToInterceptionIds.firstValue(requestHash); | ||
const requestId = event.requestId; | ||
const interceptionId = this._requestIdToInterceptionId.get(requestId); | ||
if (interceptionId) { | ||
this._onRequest(event, interceptionId); | ||
this._requestHashToInterceptionIds.delete(requestHash, interceptionId); | ||
this._requestIdToInterceptionId.delete(requestId); | ||
} else { | ||
this._requestHashToRequestIds.set(requestHash, event.requestId); | ||
this._requestIdToRequestWillBeSentEvent.set(event.requestId, event); | ||
@@ -163,36 +191,38 @@ } | ||
/** | ||
* @param {!Protocol.Network.requestInterceptedPayload} event | ||
* @param {!Protocol.Fetch.authRequiredPayload} event | ||
*/ | ||
_onRequestIntercepted(event) { | ||
if (event.authChallenge) { | ||
/** @type {"Default"|"CancelAuth"|"ProvideCredentials"} */ | ||
let response = 'Default'; | ||
if (this._attemptedAuthentications.has(event.interceptionId)) { | ||
response = 'CancelAuth'; | ||
} else if (this._credentials) { | ||
response = 'ProvideCredentials'; | ||
this._attemptedAuthentications.add(event.interceptionId); | ||
} | ||
const {username, password} = this._credentials || {username: undefined, password: undefined}; | ||
this._client.send('Network.continueInterceptedRequest', { | ||
interceptionId: event.interceptionId, | ||
authChallengeResponse: { response, username, password } | ||
}).catch(debugError); | ||
return; | ||
_onAuthRequired(event) { | ||
/** @type {"Default"|"CancelAuth"|"ProvideCredentials"} */ | ||
let response = 'Default'; | ||
if (this._attemptedAuthentications.has(event.requestId)) { | ||
response = 'CancelAuth'; | ||
} else if (this._credentials) { | ||
response = 'ProvideCredentials'; | ||
this._attemptedAuthentications.add(event.requestId); | ||
} | ||
const {username, password} = this._credentials || {username: undefined, password: undefined}; | ||
this._client.send('Fetch.continueWithAuth', { | ||
requestId: event.requestId, | ||
authChallengeResponse: { response, username, password }, | ||
}).catch(debugError); | ||
} | ||
/** | ||
* @param {!Protocol.Fetch.requestPausedPayload} event | ||
*/ | ||
_onRequestPaused(event) { | ||
if (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled) { | ||
this._client.send('Network.continueInterceptedRequest', { | ||
interceptionId: event.interceptionId | ||
this._client.send('Fetch.continueRequest', { | ||
requestId: event.requestId | ||
}).catch(debugError); | ||
} | ||
const requestHash = generateRequestHash(event.request); | ||
const requestId = this._requestHashToRequestIds.firstValue(requestHash); | ||
if (requestId) { | ||
const requestId = event.networkId; | ||
const interceptionId = event.requestId; | ||
if (requestId && this._requestIdToRequestWillBeSentEvent.has(requestId)) { | ||
const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(requestId); | ||
this._onRequest(requestWillBeSentEvent, event.interceptionId); | ||
this._requestHashToRequestIds.delete(requestHash, requestId); | ||
this._onRequest(requestWillBeSentEvent, interceptionId); | ||
this._requestIdToRequestWillBeSentEvent.delete(requestId); | ||
} else { | ||
this._requestHashToInterceptionIds.set(requestHash, event.interceptionId); | ||
this._requestIdToInterceptionId.set(requestId, interceptionId); | ||
} | ||
@@ -419,8 +449,8 @@ } | ||
this._interceptionHandled = true; | ||
await this._client.send('Network.continueInterceptedRequest', { | ||
interceptionId: this._interceptionId, | ||
await this._client.send('Fetch.continueRequest', { | ||
requestId: this._interceptionId, | ||
url, | ||
method, | ||
postData, | ||
headers, | ||
headers: headers ? headersArray(headers) : undefined, | ||
}).catch(error => { | ||
@@ -446,2 +476,3 @@ // In certain cases, protocol will return error if the request was already canceled | ||
/** @type {!Object<string, string>} */ | ||
const responseHeaders = {}; | ||
@@ -455,20 +486,9 @@ if (response.headers) { | ||
if (responseBody && !('content-length' in responseHeaders)) | ||
responseHeaders['content-length'] = Buffer.byteLength(responseBody); | ||
responseHeaders['content-length'] = String(Buffer.byteLength(responseBody)); | ||
const statusCode = response.status || 200; | ||
const statusText = statusTexts[statusCode] || ''; | ||
const statusLine = `HTTP/1.1 ${statusCode} ${statusText}`; | ||
const CRLF = '\r\n'; | ||
let text = statusLine + CRLF; | ||
for (const header of Object.keys(responseHeaders)) | ||
text += header + ': ' + responseHeaders[header] + CRLF; | ||
text += CRLF; | ||
let responseBuffer = Buffer.from(text, 'utf8'); | ||
if (responseBody) | ||
responseBuffer = Buffer.concat([responseBuffer, responseBody]); | ||
await this._client.send('Network.continueInterceptedRequest', { | ||
interceptionId: this._interceptionId, | ||
rawResponse: responseBuffer.toString('base64') | ||
await this._client.send('Fetch.fulfillRequest', { | ||
requestId: this._interceptionId, | ||
responseCode: response.status || 200, | ||
responseHeaders: headersArray(responseHeaders), | ||
body: responseBody ? responseBody.toString('base64') : undefined, | ||
}).catch(error => { | ||
@@ -493,4 +513,4 @@ // In certain cases, protocol will return error if the request was already canceled | ||
this._interceptionHandled = true; | ||
await this._client.send('Network.continueInterceptedRequest', { | ||
interceptionId: this._interceptionId, | ||
await this._client.send('Fetch.failRequest', { | ||
requestId: this._interceptionId, | ||
errorReason | ||
@@ -663,38 +683,2 @@ }).catch(error => { | ||
const IGNORED_HEADERS = new Set(['accept', 'referer', 'x-devtools-emulate-network-conditions-client-id', 'cookie', 'origin', 'content-type', 'intervention']); | ||
/** | ||
* @param {!Protocol.Network.Request} request | ||
* @return {string} | ||
*/ | ||
function generateRequestHash(request) { | ||
let normalizedURL = request.url; | ||
try { | ||
// Decoding is necessary to normalize URLs. @see crbug.com/759388 | ||
// The method will throw if the URL is malformed. In this case, | ||
// consider URL to be normalized as-is. | ||
normalizedURL = decodeURI(request.url); | ||
} catch (e) { | ||
} | ||
const hash = { | ||
url: normalizedURL, | ||
method: request.method, | ||
postData: request.postData, | ||
headers: {}, | ||
}; | ||
if (!normalizedURL.startsWith('data:')) { | ||
const headers = Object.keys(request.headers); | ||
headers.sort(); | ||
for (let header of headers) { | ||
const headerValue = request.headers[header]; | ||
header = header.toLowerCase(); | ||
if (IGNORED_HEADERS.has(header)) | ||
continue; | ||
hash.headers[header] = headerValue; | ||
} | ||
} | ||
return JSON.stringify(hash); | ||
} | ||
class SecurityDetails { | ||
@@ -748,65 +732,13 @@ /** | ||
const statusTexts = { | ||
'100': 'Continue', | ||
'101': 'Switching Protocols', | ||
'102': 'Processing', | ||
'200': 'OK', | ||
'201': 'Created', | ||
'202': 'Accepted', | ||
'203': 'Non-Authoritative Information', | ||
'204': 'No Content', | ||
'206': 'Partial Content', | ||
'207': 'Multi-Status', | ||
'208': 'Already Reported', | ||
'209': 'IM Used', | ||
'300': 'Multiple Choices', | ||
'301': 'Moved Permanently', | ||
'302': 'Found', | ||
'303': 'See Other', | ||
'304': 'Not Modified', | ||
'305': 'Use Proxy', | ||
'306': 'Switch Proxy', | ||
'307': 'Temporary Redirect', | ||
'308': 'Permanent Redirect', | ||
'400': 'Bad Request', | ||
'401': 'Unauthorized', | ||
'402': 'Payment Required', | ||
'403': 'Forbidden', | ||
'404': 'Not Found', | ||
'405': 'Method Not Allowed', | ||
'406': 'Not Acceptable', | ||
'407': 'Proxy Authentication Required', | ||
'408': 'Request Timeout', | ||
'409': 'Conflict', | ||
'410': 'Gone', | ||
'411': 'Length Required', | ||
'412': 'Precondition Failed', | ||
'413': 'Payload Too Large', | ||
'414': 'URI Too Long', | ||
'415': 'Unsupported Media Type', | ||
'416': 'Range Not Satisfiable', | ||
'417': 'Expectation Failed', | ||
'418': 'I\'m a teapot', | ||
'421': 'Misdirected Request', | ||
'422': 'Unprocessable Entity', | ||
'423': 'Locked', | ||
'424': 'Failed Dependency', | ||
'426': 'Upgrade Required', | ||
'428': 'Precondition Required', | ||
'429': 'Too Many Requests', | ||
'431': 'Request Header Fields Too Large', | ||
'451': 'Unavailable For Legal Reasons', | ||
'500': 'Internal Server Error', | ||
'501': 'Not Implemented', | ||
'502': 'Bad Gateway', | ||
'503': 'Service Unavailable', | ||
'504': 'Gateway Timeout', | ||
'505': 'HTTP Version Not Supported', | ||
'506': 'Variant Also Negotiates', | ||
'507': 'Insufficient Storage', | ||
'508': 'Loop Detected', | ||
'510': 'Not Extended', | ||
'511': 'Network Authentication Required', | ||
}; | ||
/** | ||
* @param {Object<string, string>} headers | ||
* @return {!Array<{name: string, value: string}>} | ||
*/ | ||
function headersArray(headers) { | ||
const result = []; | ||
for (const name in headers) | ||
result.push({name, value: headers[name]}); | ||
return result; | ||
} | ||
module.exports = {Request, Response, NetworkManager, SecurityDetails}; |
@@ -22,3 +22,2 @@ /** | ||
const {Connection} = require('./Connection'); | ||
const {NetworkManager} = require('./NetworkManager'); | ||
const {Dialog} = require('./Dialog'); | ||
@@ -47,21 +46,11 @@ const {EmulationManager} = require('./EmulationManager'); | ||
static async create(client, target, ignoreHTTPSErrors, defaultViewport, screenshotTaskQueue) { | ||
await client.send('Page.enable'); | ||
const {frameTree} = await client.send('Page.getFrameTree'); | ||
const page = new Page(client, target, frameTree, ignoreHTTPSErrors, screenshotTaskQueue); | ||
const page = new Page(client, target, ignoreHTTPSErrors, screenshotTaskQueue); | ||
await Promise.all([ | ||
page._frameManager.initialize(), | ||
client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true}), | ||
client.send('Page.setLifecycleEventsEnabled', { enabled: true }), | ||
client.send('Network.enable', {}), | ||
client.send('Runtime.enable', {}).then(() => page._frameManager.ensureSecondaryDOMWorld()), | ||
client.send('Security.enable', {}), | ||
client.send('Performance.enable', {}), | ||
client.send('Log.enable', {}), | ||
]); | ||
if (ignoreHTTPSErrors) | ||
await client.send('Security.setOverrideCertificateErrors', {override: true}); | ||
// Initialize default page size. | ||
if (defaultViewport) | ||
await page.setViewport(defaultViewport); | ||
return page; | ||
@@ -73,7 +62,6 @@ } | ||
* @param {!Puppeteer.Target} target | ||
* @param {!Protocol.Page.FrameTree} frameTree | ||
* @param {boolean} ignoreHTTPSErrors | ||
* @param {!Puppeteer.TaskQueue} screenshotTaskQueue | ||
*/ | ||
constructor(client, target, frameTree, ignoreHTTPSErrors, screenshotTaskQueue) { | ||
constructor(client, target, ignoreHTTPSErrors, screenshotTaskQueue) { | ||
super(); | ||
@@ -88,6 +76,4 @@ this._closed = false; | ||
this._accessibility = new Accessibility(client); | ||
this._networkManager = new NetworkManager(client); | ||
/** @type {!FrameManager} */ | ||
this._frameManager = new FrameManager(client, frameTree, this, this._networkManager, this._timeoutSettings); | ||
this._networkManager.setFrameManager(this._frameManager); | ||
this._frameManager = new FrameManager(client, this, ignoreHTTPSErrors, this._timeoutSettings); | ||
this._emulationManager = new EmulationManager(client); | ||
@@ -97,3 +83,2 @@ this._tracing = new Tracing(client); | ||
this._pageBindings = new Map(); | ||
this._ignoreHTTPSErrors = ignoreHTTPSErrors; | ||
this._coverage = new Coverage(client); | ||
@@ -133,6 +118,7 @@ this._javascriptEnabled = true; | ||
this._networkManager.on(Events.NetworkManager.Request, event => this.emit(Events.Page.Request, event)); | ||
this._networkManager.on(Events.NetworkManager.Response, event => this.emit(Events.Page.Response, event)); | ||
this._networkManager.on(Events.NetworkManager.RequestFailed, event => this.emit(Events.Page.RequestFailed, event)); | ||
this._networkManager.on(Events.NetworkManager.RequestFinished, event => this.emit(Events.Page.RequestFinished, event)); | ||
const networkManager = this._frameManager.networkManager(); | ||
networkManager.on(Events.NetworkManager.Request, event => this.emit(Events.Page.Request, event)); | ||
networkManager.on(Events.NetworkManager.Response, event => this.emit(Events.Page.Response, event)); | ||
networkManager.on(Events.NetworkManager.RequestFailed, event => this.emit(Events.Page.RequestFailed, event)); | ||
networkManager.on(Events.NetworkManager.RequestFinished, event => this.emit(Events.Page.RequestFinished, event)); | ||
@@ -145,3 +131,2 @@ client.on('Page.domContentEventFired', event => this.emit(Events.Page.DOMContentLoaded)); | ||
client.on('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails)); | ||
client.on('Security.certificateError', event => this._onCertificateError(event)); | ||
client.on('Inspector.targetCrashed', event => this._onTargetCrashed()); | ||
@@ -266,3 +251,3 @@ client.on('Performance.metrics', event => this._emitMetrics(event)); | ||
async setRequestInterception(value) { | ||
return this._networkManager.setRequestInterception(value); | ||
return this._frameManager.networkManager().setRequestInterception(value); | ||
} | ||
@@ -274,3 +259,3 @@ | ||
setOfflineMode(enabled) { | ||
return this._networkManager.setOfflineMode(enabled); | ||
return this._frameManager.networkManager().setOfflineMode(enabled); | ||
} | ||
@@ -293,14 +278,2 @@ | ||
/** | ||
* @param {!Protocol.Security.certificateErrorPayload} event | ||
*/ | ||
_onCertificateError(event) { | ||
if (!this._ignoreHTTPSErrors) | ||
return; | ||
this._client.send('Security.handleCertificateError', { | ||
eventId: event.eventId, | ||
action: 'continue' | ||
}).catch(debugError); | ||
} | ||
/** | ||
* @param {string} selector | ||
@@ -462,3 +435,3 @@ * @return {!Promise<?Puppeteer.ElementHandle>} | ||
async authenticate(credentials) { | ||
return this._networkManager.authenticate(credentials); | ||
return this._frameManager.networkManager().authenticate(credentials); | ||
} | ||
@@ -470,3 +443,3 @@ | ||
async setExtraHTTPHeaders(headers) { | ||
return this._networkManager.setExtraHTTPHeaders(headers); | ||
return this._frameManager.networkManager().setExtraHTTPHeaders(headers); | ||
} | ||
@@ -478,3 +451,3 @@ | ||
async setUserAgent(userAgent) { | ||
return this._networkManager.setUserAgent(userAgent); | ||
return this._frameManager.networkManager().setUserAgent(userAgent); | ||
} | ||
@@ -701,3 +674,3 @@ | ||
} = options; | ||
return helper.waitForEvent(this._networkManager, Events.NetworkManager.Request, request => { | ||
return helper.waitForEvent(this._frameManager.networkManager(), Events.NetworkManager.Request, request => { | ||
if (helper.isString(urlOrPredicate)) | ||
@@ -720,3 +693,3 @@ return (urlOrPredicate === request.url()); | ||
} = options; | ||
return helper.waitForEvent(this._networkManager, Events.NetworkManager.Response, response => { | ||
return helper.waitForEvent(this._frameManager.networkManager(), Events.NetworkManager.Response, response => { | ||
if (helper.isString(urlOrPredicate)) | ||
@@ -840,3 +813,3 @@ return (urlOrPredicate === response.url()); | ||
async setCacheEnabled(enabled = true) { | ||
await this._client.send('Network.setCacheDisabled', {cacheDisabled: !enabled}); | ||
await this._frameManager.networkManager().setCacheEnabled(enabled); | ||
} | ||
@@ -843,0 +816,0 @@ |
@@ -18,2 +18,4 @@ /** | ||
const BrowserFetcher = require('./BrowserFetcher'); | ||
const Errors = require('./Errors'); | ||
const DeviceDescriptors = require('./DeviceDescriptors'); | ||
@@ -55,2 +57,16 @@ module.exports = class { | ||
/** | ||
* @return {Object} | ||
*/ | ||
get devices() { | ||
return DeviceDescriptors; | ||
} | ||
/** | ||
* @return {Object} | ||
*/ | ||
get errors() { | ||
return Errors; | ||
} | ||
/** | ||
* @param {!Launcher.ChromeArgOptions=} options | ||
@@ -57,0 +73,0 @@ * @return {!Array<string>} |
@@ -73,2 +73,9 @@ /** | ||
/** | ||
* @return {boolean} | ||
*/ | ||
_hasContext() { | ||
return !this._contextResolveCallback; | ||
} | ||
_detach() { | ||
@@ -75,0 +82,0 @@ this._detached = true; |
@@ -23,2 +23,3 @@ /** | ||
const {DOMWorld} = require('./DOMWorld'); | ||
const {NetworkManager} = require('./NetworkManager'); | ||
@@ -30,12 +31,12 @@ const UTILITY_WORLD_NAME = '__puppeteer_utility_world__'; | ||
* @param {!Puppeteer.CDPSession} client | ||
* @param {!Protocol.Page.FrameTree} frameTree | ||
* @param {!Puppeteer.Page} page | ||
* @param {!Puppeteer.NetworkManager} networkManager | ||
* @param {boolean} ignoreHTTPSErrors | ||
* @param {!Puppeteer.TimeoutSettings} timeoutSettings | ||
*/ | ||
constructor(client, frameTree, page, networkManager, timeoutSettings) { | ||
constructor(client, page, ignoreHTTPSErrors, timeoutSettings) { | ||
super(); | ||
this._client = client; | ||
this._page = page; | ||
this._networkManager = networkManager; | ||
this._networkManager = new NetworkManager(client, ignoreHTTPSErrors); | ||
this._networkManager.setFrameManager(this); | ||
this._timeoutSettings = timeoutSettings; | ||
@@ -58,3 +59,48 @@ /** @type {!Map<string, !Frame>} */ | ||
this._client.on('Page.lifecycleEvent', event => this._onLifecycleEvent(event)); | ||
} | ||
/* async */ initialize() {return (fn => { | ||
const gen = fn.call(this); | ||
return new Promise((resolve, reject) => { | ||
function step(key, arg) { | ||
let info, value; | ||
try { | ||
info = gen[key](arg); | ||
value = info.value; | ||
} catch (error) { | ||
reject(error); | ||
return; | ||
} | ||
if (info.done) { | ||
resolve(value); | ||
} else { | ||
return Promise.resolve(value).then( | ||
value => { | ||
step('next', value); | ||
}, | ||
err => { | ||
step('throw', err); | ||
}); | ||
} | ||
} | ||
return step('next'); | ||
}); | ||
})(function*(){ | ||
const [,{frameTree}] = (yield Promise.all([ | ||
this._client.send('Page.enable'), | ||
this._client.send('Page.getFrameTree'), | ||
])); | ||
this._handleFrameTree(frameTree); | ||
(yield Promise.all([ | ||
this._client.send('Page.setLifecycleEventsEnabled', { enabled: true }), | ||
this._client.send('Runtime.enable', {}).then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)), | ||
this._networkManager.initialize(), | ||
])); | ||
});} | ||
/** | ||
* @return {!NetworkManager} | ||
*/ | ||
networkManager() { | ||
return this._networkManager; | ||
} | ||
@@ -325,32 +371,2 @@ | ||
/* async */ ensureSecondaryDOMWorld() {return (fn => { | ||
const gen = fn.call(this); | ||
return new Promise((resolve, reject) => { | ||
function step(key, arg) { | ||
let info, value; | ||
try { | ||
info = gen[key](arg); | ||
value = info.value; | ||
} catch (error) { | ||
reject(error); | ||
return; | ||
} | ||
if (info.done) { | ||
resolve(value); | ||
} else { | ||
return Promise.resolve(value).then( | ||
value => { | ||
step('next', value); | ||
}, | ||
err => { | ||
step('throw', err); | ||
}); | ||
} | ||
} | ||
return step('next'); | ||
}); | ||
})(function*(){ | ||
(yield this._ensureIsolatedWorld(UTILITY_WORLD_NAME)); | ||
});} | ||
/** | ||
@@ -427,6 +443,10 @@ * @param {string} name | ||
if (frame) { | ||
if (contextPayload.auxData && !!contextPayload.auxData['isDefault']) | ||
if (contextPayload.auxData && !!contextPayload.auxData['isDefault']) { | ||
world = frame._mainWorld; | ||
else if (contextPayload.name === UTILITY_WORLD_NAME) | ||
} else if (contextPayload.name === UTILITY_WORLD_NAME && !frame._secondaryWorld._hasContext()) { | ||
// In case of multiple sessions to the same target, there's a race between | ||
// connections so we might end up creating multiple isolated worlds. | ||
// We can use either. | ||
world = frame._secondaryWorld; | ||
} | ||
} | ||
@@ -433,0 +453,0 @@ if (contextPayload.auxData && contextPayload.auxData['type'] === 'isolated') |
@@ -400,9 +400,13 @@ /** | ||
})(function*(){ | ||
const result = (yield this._client.send('DOM.getContentQuads', { | ||
objectId: this._remoteObject.objectId | ||
}).catch(debugError)); | ||
const [result, layoutMetrics] = (yield Promise.all([ | ||
this._client.send('DOM.getContentQuads', { | ||
objectId: this._remoteObject.objectId | ||
}).catch(debugError), | ||
this._client.send('Page.getLayoutMetrics'), | ||
])); | ||
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.map(quad => this._fromProtocolQuad(quad)).filter(quad => computeQuadArea(quad) > 1); | ||
const {clientWidth, clientHeight} = layoutMetrics.layoutViewport; | ||
const quads = result.quads.map(quad => this._fromProtocolQuad(quad)).map(quad => this._intersectQuadWithViewport(quad, clientWidth, clientHeight)).filter(quad => computeQuadArea(quad) > 1); | ||
if (!quads.length) | ||
@@ -446,2 +450,15 @@ throw new Error('Node is either not visible or not an HTMLElement'); | ||
/** | ||
* @param {!Array<{x: number, y: number}>} quad | ||
* @param {number} width | ||
* @param {number} height | ||
* @return {!Array<{x: number, y: number}>} | ||
*/ | ||
_intersectQuadWithViewport(quad, width, height) { | ||
return quad.map(point => ({ | ||
x: Math.min(Math.max(point.x, 0), width), | ||
y: Math.min(Math.max(point.y, 0), height), | ||
})); | ||
} | ||
/* async */ hover() {return (fn => { | ||
@@ -448,0 +465,0 @@ const gen = fn.call(this); |
@@ -19,2 +19,3 @@ /** | ||
const http = require('http'); | ||
const https = require('https'); | ||
const URL = require('url'); | ||
@@ -60,3 +61,2 @@ const removeFolder = require('rimraf'); | ||
'--no-first-run', | ||
'--safebrowsing-disable-auto-update', | ||
'--enable-automation', | ||
@@ -354,3 +354,3 @@ '--password-store=basic', | ||
if (!this._isPuppeteerCore) { | ||
const executablePath = process.env['PUPPETEER_EXECUTABLE_PATH']; | ||
const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH || process.env.npm_config_puppeteer_executable_path || process.env.npm_package_config_puppeteer_executable_path; | ||
if (executablePath) { | ||
@@ -440,4 +440,5 @@ const missingText = !fs.existsSync(executablePath) ? 'Tried to use PUPPETEER_EXECUTABLE_PATH env variable to launch browser but did not find any executable at: ' + executablePath : null; | ||
const endpointURL = URL.resolve(browserURL, '/json/version'); | ||
const protocol = endpointURL.startsWith('https') ? https : http; | ||
const requestOptions = Object.assign(URL.parse(endpointURL), { method: 'GET' }); | ||
const request = http.request(requestOptions, res => { | ||
const request = protocol.request(requestOptions, res => { | ||
let data = ''; | ||
@@ -444,0 +445,0 @@ if (res.statusCode !== 200) { |
@@ -40,3 +40,2 @@ /** | ||
this._frameManager = frameManager; | ||
this._networkManager = frameManager._networkManager; | ||
this._frame = frame; | ||
@@ -52,3 +51,3 @@ this._initialLoaderId = frame._loaderId; | ||
helper.addEventListener(this._frameManager, Events.FrameManager.FrameDetached, this._onFrameDetached.bind(this)), | ||
helper.addEventListener(this._networkManager, Events.NetworkManager.Request, this._onRequest.bind(this)), | ||
helper.addEventListener(this._frameManager.networkManager(), Events.NetworkManager.Request, this._onRequest.bind(this)), | ||
]; | ||
@@ -55,0 +54,0 @@ |
@@ -19,3 +19,2 @@ /** | ||
const {Events} = require('./Events'); | ||
const Multimap = require('./Multimap'); | ||
@@ -26,5 +25,6 @@ class NetworkManager extends EventEmitter { | ||
*/ | ||
constructor(client) { | ||
constructor(client, ignoreHTTPSErrors) { | ||
super(); | ||
this._client = client; | ||
this._ignoreHTTPSErrors = ignoreHTTPSErrors; | ||
this._frameManager = null; | ||
@@ -46,9 +46,9 @@ /** @type {!Map<string, !Request>} */ | ||
this._protocolRequestInterceptionEnabled = false; | ||
/** @type {!Multimap<string, string>} */ | ||
this._requestHashToRequestIds = new Multimap(); | ||
/** @type {!Multimap<string, string>} */ | ||
this._requestHashToInterceptionIds = new Multimap(); | ||
this._userCacheDisabled = false; | ||
/** @type {!Map<string, string>} */ | ||
this._requestIdToInterceptionId = new Map(); | ||
this._client.on('Fetch.requestPaused', this._onRequestPaused.bind(this)); | ||
this._client.on('Fetch.authRequired', this._onAuthRequired.bind(this)); | ||
this._client.on('Network.requestWillBeSent', this._onRequestWillBeSent.bind(this)); | ||
this._client.on('Network.requestIntercepted', this._onRequestIntercepted.bind(this)); | ||
this._client.on('Network.requestServedFromCache', this._onRequestServedFromCache.bind(this)); | ||
@@ -60,2 +60,34 @@ this._client.on('Network.responseReceived', this._onResponseReceived.bind(this)); | ||
/* async */ initialize() {return (fn => { | ||
const gen = fn.call(this); | ||
return new Promise((resolve, reject) => { | ||
function step(key, arg) { | ||
let info, value; | ||
try { | ||
info = gen[key](arg); | ||
value = info.value; | ||
} catch (error) { | ||
reject(error); | ||
return; | ||
} | ||
if (info.done) { | ||
resolve(value); | ||
} else { | ||
return Promise.resolve(value).then( | ||
value => { | ||
step('next', value); | ||
}, | ||
err => { | ||
step('throw', err); | ||
}); | ||
} | ||
} | ||
return step('next'); | ||
}); | ||
})(function*(){ | ||
(yield this._client.send('Network.enable')); | ||
if (this._ignoreHTTPSErrors) | ||
(yield this._client.send('Security.setIgnoreCertificateErrors', {ignore: true})); | ||
});} | ||
/** | ||
@@ -224,2 +256,36 @@ * @param {!Puppeteer.FrameManager} frameManager | ||
/** | ||
* @param {boolean} enabled | ||
*/ | ||
/* async */ setCacheEnabled(enabled) {return (fn => { | ||
const gen = fn.call(this); | ||
return new Promise((resolve, reject) => { | ||
function step(key, arg) { | ||
let info, value; | ||
try { | ||
info = gen[key](arg); | ||
value = info.value; | ||
} catch (error) { | ||
reject(error); | ||
return; | ||
} | ||
if (info.done) { | ||
resolve(value); | ||
} else { | ||
return Promise.resolve(value).then( | ||
value => { | ||
step('next', value); | ||
}, | ||
err => { | ||
step('throw', err); | ||
}); | ||
} | ||
} | ||
return step('next'); | ||
}); | ||
})(function*(){ | ||
this._userCacheDisabled = !enabled; | ||
(yield this._updateProtocolCacheDisabled()); | ||
});} | ||
/** | ||
* @param {boolean} value | ||
@@ -289,9 +355,50 @@ */ | ||
this._protocolRequestInterceptionEnabled = enabled; | ||
const patterns = enabled ? [{urlPattern: '*'}] : []; | ||
(yield Promise.all([ | ||
this._client.send('Network.setCacheDisabled', {cacheDisabled: enabled}), | ||
this._client.send('Network.setRequestInterception', {patterns}) | ||
])); | ||
if (enabled) { | ||
(yield Promise.all([ | ||
this._updateProtocolCacheDisabled(), | ||
this._client.send('Fetch.enable', { | ||
handleAuthRequests: true, | ||
patterns: [{urlPattern: '*'}], | ||
}), | ||
])); | ||
} else { | ||
(yield Promise.all([ | ||
this._updateProtocolCacheDisabled(), | ||
this._client.send('Fetch.disable') | ||
])); | ||
} | ||
});} | ||
/* async */ _updateProtocolCacheDisabled() {return (fn => { | ||
const gen = fn.call(this); | ||
return new Promise((resolve, reject) => { | ||
function step(key, arg) { | ||
let info, value; | ||
try { | ||
info = gen[key](arg); | ||
value = info.value; | ||
} catch (error) { | ||
reject(error); | ||
return; | ||
} | ||
if (info.done) { | ||
resolve(value); | ||
} else { | ||
return Promise.resolve(value).then( | ||
value => { | ||
step('next', value); | ||
}, | ||
err => { | ||
step('throw', err); | ||
}); | ||
} | ||
} | ||
return step('next'); | ||
}); | ||
})(function*(){ | ||
(yield this._client.send('Network.setCacheDisabled', { | ||
cacheDisabled: this._userCacheDisabled || this._protocolRequestInterceptionEnabled | ||
})); | ||
});} | ||
/** | ||
@@ -303,9 +410,8 @@ * @param {!Protocol.Network.requestWillBeSentPayload} event | ||
if (this._protocolRequestInterceptionEnabled && !event.request.url.startsWith('data:')) { | ||
const requestHash = generateRequestHash(event.request); | ||
const interceptionId = this._requestHashToInterceptionIds.firstValue(requestHash); | ||
const requestId = event.requestId; | ||
const interceptionId = this._requestIdToInterceptionId.get(requestId); | ||
if (interceptionId) { | ||
this._onRequest(event, interceptionId); | ||
this._requestHashToInterceptionIds.delete(requestHash, interceptionId); | ||
this._requestIdToInterceptionId.delete(requestId); | ||
} else { | ||
this._requestHashToRequestIds.set(requestHash, event.requestId); | ||
this._requestIdToRequestWillBeSentEvent.set(event.requestId, event); | ||
@@ -319,36 +425,38 @@ } | ||
/** | ||
* @param {!Protocol.Network.requestInterceptedPayload} event | ||
* @param {!Protocol.Fetch.authRequiredPayload} event | ||
*/ | ||
_onRequestIntercepted(event) { | ||
if (event.authChallenge) { | ||
/** @type {"Default"|"CancelAuth"|"ProvideCredentials"} */ | ||
let response = 'Default'; | ||
if (this._attemptedAuthentications.has(event.interceptionId)) { | ||
response = 'CancelAuth'; | ||
} else if (this._credentials) { | ||
response = 'ProvideCredentials'; | ||
this._attemptedAuthentications.add(event.interceptionId); | ||
} | ||
const {username, password} = this._credentials || {username: undefined, password: undefined}; | ||
this._client.send('Network.continueInterceptedRequest', { | ||
interceptionId: event.interceptionId, | ||
authChallengeResponse: { response, username, password } | ||
}).catch(debugError); | ||
return; | ||
_onAuthRequired(event) { | ||
/** @type {"Default"|"CancelAuth"|"ProvideCredentials"} */ | ||
let response = 'Default'; | ||
if (this._attemptedAuthentications.has(event.requestId)) { | ||
response = 'CancelAuth'; | ||
} else if (this._credentials) { | ||
response = 'ProvideCredentials'; | ||
this._attemptedAuthentications.add(event.requestId); | ||
} | ||
const {username, password} = this._credentials || {username: undefined, password: undefined}; | ||
this._client.send('Fetch.continueWithAuth', { | ||
requestId: event.requestId, | ||
authChallengeResponse: { response, username, password }, | ||
}).catch(debugError); | ||
} | ||
/** | ||
* @param {!Protocol.Fetch.requestPausedPayload} event | ||
*/ | ||
_onRequestPaused(event) { | ||
if (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled) { | ||
this._client.send('Network.continueInterceptedRequest', { | ||
interceptionId: event.interceptionId | ||
this._client.send('Fetch.continueRequest', { | ||
requestId: event.requestId | ||
}).catch(debugError); | ||
} | ||
const requestHash = generateRequestHash(event.request); | ||
const requestId = this._requestHashToRequestIds.firstValue(requestHash); | ||
if (requestId) { | ||
const requestId = event.networkId; | ||
const interceptionId = event.requestId; | ||
if (requestId && this._requestIdToRequestWillBeSentEvent.has(requestId)) { | ||
const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(requestId); | ||
this._onRequest(requestWillBeSentEvent, event.interceptionId); | ||
this._requestHashToRequestIds.delete(requestHash, requestId); | ||
this._onRequest(requestWillBeSentEvent, interceptionId); | ||
this._requestIdToRequestWillBeSentEvent.delete(requestId); | ||
} else { | ||
this._requestHashToInterceptionIds.set(requestHash, event.interceptionId); | ||
this._requestIdToInterceptionId.set(requestId, interceptionId); | ||
} | ||
@@ -601,8 +709,8 @@ } | ||
this._interceptionHandled = true; | ||
(yield this._client.send('Network.continueInterceptedRequest', { | ||
interceptionId: this._interceptionId, | ||
(yield this._client.send('Fetch.continueRequest', { | ||
requestId: this._interceptionId, | ||
url, | ||
method, | ||
postData, | ||
headers, | ||
headers: headers ? headersArray(headers) : undefined, | ||
}).catch(error => { | ||
@@ -654,2 +762,3 @@ // In certain cases, protocol will return error if the request was already canceled | ||
/** @type {!Object<string, string>} */ | ||
const responseHeaders = {}; | ||
@@ -663,20 +772,9 @@ if (response.headers) { | ||
if (responseBody && !('content-length' in responseHeaders)) | ||
responseHeaders['content-length'] = Buffer.byteLength(responseBody); | ||
responseHeaders['content-length'] = String(Buffer.byteLength(responseBody)); | ||
const statusCode = response.status || 200; | ||
const statusText = statusTexts[statusCode] || ''; | ||
const statusLine = `HTTP/1.1 ${statusCode} ${statusText}`; | ||
const CRLF = '\r\n'; | ||
let text = statusLine + CRLF; | ||
for (const header of Object.keys(responseHeaders)) | ||
text += header + ': ' + responseHeaders[header] + CRLF; | ||
text += CRLF; | ||
let responseBuffer = Buffer.from(text, 'utf8'); | ||
if (responseBody) | ||
responseBuffer = Buffer.concat([responseBuffer, responseBody]); | ||
(yield this._client.send('Network.continueInterceptedRequest', { | ||
interceptionId: this._interceptionId, | ||
rawResponse: responseBuffer.toString('base64') | ||
(yield this._client.send('Fetch.fulfillRequest', { | ||
requestId: this._interceptionId, | ||
responseCode: response.status || 200, | ||
responseHeaders: headersArray(responseHeaders), | ||
body: responseBody ? responseBody.toString('base64') : undefined, | ||
}).catch(error => { | ||
@@ -727,4 +825,4 @@ // In certain cases, protocol will return error if the request was already canceled | ||
this._interceptionHandled = true; | ||
(yield this._client.send('Network.continueInterceptedRequest', { | ||
interceptionId: this._interceptionId, | ||
(yield this._client.send('Fetch.failRequest', { | ||
requestId: this._interceptionId, | ||
errorReason | ||
@@ -975,38 +1073,2 @@ }).catch(error => { | ||
const IGNORED_HEADERS = new Set(['accept', 'referer', 'x-devtools-emulate-network-conditions-client-id', 'cookie', 'origin', 'content-type', 'intervention']); | ||
/** | ||
* @param {!Protocol.Network.Request} request | ||
* @return {string} | ||
*/ | ||
function generateRequestHash(request) { | ||
let normalizedURL = request.url; | ||
try { | ||
// Decoding is necessary to normalize URLs. @see crbug.com/759388 | ||
// The method will throw if the URL is malformed. In this case, | ||
// consider URL to be normalized as-is. | ||
normalizedURL = decodeURI(request.url); | ||
} catch (e) { | ||
} | ||
const hash = { | ||
url: normalizedURL, | ||
method: request.method, | ||
postData: request.postData, | ||
headers: {}, | ||
}; | ||
if (!normalizedURL.startsWith('data:')) { | ||
const headers = Object.keys(request.headers); | ||
headers.sort(); | ||
for (let header of headers) { | ||
const headerValue = request.headers[header]; | ||
header = header.toLowerCase(); | ||
if (IGNORED_HEADERS.has(header)) | ||
continue; | ||
hash.headers[header] = headerValue; | ||
} | ||
} | ||
return JSON.stringify(hash); | ||
} | ||
class SecurityDetails { | ||
@@ -1060,65 +1122,13 @@ /** | ||
const statusTexts = { | ||
'100': 'Continue', | ||
'101': 'Switching Protocols', | ||
'102': 'Processing', | ||
'200': 'OK', | ||
'201': 'Created', | ||
'202': 'Accepted', | ||
'203': 'Non-Authoritative Information', | ||
'204': 'No Content', | ||
'206': 'Partial Content', | ||
'207': 'Multi-Status', | ||
'208': 'Already Reported', | ||
'209': 'IM Used', | ||
'300': 'Multiple Choices', | ||
'301': 'Moved Permanently', | ||
'302': 'Found', | ||
'303': 'See Other', | ||
'304': 'Not Modified', | ||
'305': 'Use Proxy', | ||
'306': 'Switch Proxy', | ||
'307': 'Temporary Redirect', | ||
'308': 'Permanent Redirect', | ||
'400': 'Bad Request', | ||
'401': 'Unauthorized', | ||
'402': 'Payment Required', | ||
'403': 'Forbidden', | ||
'404': 'Not Found', | ||
'405': 'Method Not Allowed', | ||
'406': 'Not Acceptable', | ||
'407': 'Proxy Authentication Required', | ||
'408': 'Request Timeout', | ||
'409': 'Conflict', | ||
'410': 'Gone', | ||
'411': 'Length Required', | ||
'412': 'Precondition Failed', | ||
'413': 'Payload Too Large', | ||
'414': 'URI Too Long', | ||
'415': 'Unsupported Media Type', | ||
'416': 'Range Not Satisfiable', | ||
'417': 'Expectation Failed', | ||
'418': 'I\'m a teapot', | ||
'421': 'Misdirected Request', | ||
'422': 'Unprocessable Entity', | ||
'423': 'Locked', | ||
'424': 'Failed Dependency', | ||
'426': 'Upgrade Required', | ||
'428': 'Precondition Required', | ||
'429': 'Too Many Requests', | ||
'431': 'Request Header Fields Too Large', | ||
'451': 'Unavailable For Legal Reasons', | ||
'500': 'Internal Server Error', | ||
'501': 'Not Implemented', | ||
'502': 'Bad Gateway', | ||
'503': 'Service Unavailable', | ||
'504': 'Gateway Timeout', | ||
'505': 'HTTP Version Not Supported', | ||
'506': 'Variant Also Negotiates', | ||
'507': 'Insufficient Storage', | ||
'508': 'Loop Detected', | ||
'510': 'Not Extended', | ||
'511': 'Network Authentication Required', | ||
}; | ||
/** | ||
* @param {Object<string, string>} headers | ||
* @return {!Array<{name: string, value: string}>} | ||
*/ | ||
function headersArray(headers) { | ||
const result = []; | ||
for (const name in headers) | ||
result.push({name, value: headers[name]}); | ||
return result; | ||
} | ||
module.exports = {Request, Response, NetworkManager, SecurityDetails}; |
@@ -18,2 +18,4 @@ /** | ||
const BrowserFetcher = require('./BrowserFetcher'); | ||
const Errors = require('./Errors'); | ||
const DeviceDescriptors = require('./DeviceDescriptors'); | ||
@@ -55,2 +57,16 @@ module.exports = class { | ||
/** | ||
* @return {Object} | ||
*/ | ||
get devices() { | ||
return DeviceDescriptors; | ||
} | ||
/** | ||
* @return {Object} | ||
*/ | ||
get errors() { | ||
return Errors; | ||
} | ||
/** | ||
* @param {!Launcher.ChromeArgOptions=} options | ||
@@ -57,0 +73,0 @@ * @return {!Array<string>} |
{ | ||
"name": "puppeteer-core", | ||
"version": "1.14.0", | ||
"version": "1.15.0", | ||
"description": "A high-level API to control headless Chrome over the DevTools Protocol", | ||
@@ -11,3 +11,3 @@ "main": "index.js", | ||
"puppeteer": { | ||
"chromium_revision": "641577" | ||
"chromium_revision": "650583" | ||
}, | ||
@@ -14,0 +14,0 @@ "scripts": { |
@@ -9,3 +9,3 @@ # Puppeteer | ||
###### [API](https://github.com/GoogleChrome/puppeteer/blob/v1.14.0/docs/api.md) | [FAQ](#faq) | [Contributing](https://github.com/GoogleChrome/puppeteer/blob/master/CONTRIBUTING.md) | [Troubleshooting](https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md) | ||
###### [API](https://github.com/GoogleChrome/puppeteer/blob/v1.15.0/docs/api.md) | [FAQ](#faq) | [Contributing](https://github.com/GoogleChrome/puppeteer/blob/master/CONTRIBUTING.md) | [Troubleshooting](https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md) | ||
@@ -41,3 +41,3 @@ > Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). Puppeteer runs [headless](https://developers.google.com/web/updates/2017/04/headless-chrome) by default, but can be configured to run full (non-headless) Chrome or Chromium. | ||
Note: When you install Puppeteer, it downloads a recent version of Chromium (~170MB Mac, ~282MB Linux, ~280MB Win) that is guaranteed to work with the API. To skip the download, see [Environment variables](https://github.com/GoogleChrome/puppeteer/blob/v1.14.0/docs/api.md#environment-variables). | ||
Note: When you install Puppeteer, it downloads a recent version of Chromium (~170MB Mac, ~282MB Linux, ~280MB Win) that is guaranteed to work with the API. To skip the download, see [Environment variables](https://github.com/GoogleChrome/puppeteer/blob/v1.15.0/docs/api.md#environment-variables). | ||
@@ -65,3 +65,3 @@ | ||
Puppeteer will be familiar to people using other browser testing frameworks. You create an instance | ||
of `Browser`, open pages, and then manipulate them with [Puppeteer's API](https://github.com/GoogleChrome/puppeteer/blob/v1.14.0/docs/api.md#). | ||
of `Browser`, open pages, and then manipulate them with [Puppeteer's API](https://github.com/GoogleChrome/puppeteer/blob/v1.15.0/docs/api.md#). | ||
@@ -91,3 +91,3 @@ **Example** - navigating to https://example.com and saving a screenshot as *example.png*: | ||
Puppeteer sets an initial page size to 800px x 600px, which defines the screenshot size. The page size can be customized with [`Page.setViewport()`](https://github.com/GoogleChrome/puppeteer/blob/v1.14.0/docs/api.md#pagesetviewportviewport). | ||
Puppeteer sets an initial page size to 800px x 600px, which defines the screenshot size. The page size can be customized with [`Page.setViewport()`](https://github.com/GoogleChrome/puppeteer/blob/v1.15.0/docs/api.md#pagesetviewportviewport). | ||
@@ -117,3 +117,3 @@ **Example** - create a PDF. | ||
See [`Page.pdf()`](https://github.com/GoogleChrome/puppeteer/blob/v1.14.0/docs/api.md#pagepdfoptions) for more information about creating pdfs. | ||
See [`Page.pdf()`](https://github.com/GoogleChrome/puppeteer/blob/v1.15.0/docs/api.md#pagepdfoptions) for more information about creating pdfs. | ||
@@ -153,3 +153,3 @@ **Example** - evaluate script in the context of the page | ||
See [`Page.evaluate()`](https://github.com/GoogleChrome/puppeteer/blob/v1.14.0/docs/api.md#pageevaluatepagefunction-args) for more information on `evaluate` and related methods like `evaluateOnNewDocument` and `exposeFunction`. | ||
See [`Page.evaluate()`](https://github.com/GoogleChrome/puppeteer/blob/v1.15.0/docs/api.md#pageevaluatepagefunction-args) for more information on `evaluate` and related methods like `evaluateOnNewDocument` and `exposeFunction`. | ||
@@ -163,3 +163,3 @@ <!-- [END getstarted] --> | ||
Puppeteer launches Chromium in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). To launch a full version of Chromium, set the ['headless' option](https://github.com/GoogleChrome/puppeteer/blob/v1.14.0/docs/api.md#puppeteerlaunchoptions) when launching a browser: | ||
Puppeteer launches Chromium in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). To launch a full version of Chromium, set the ['headless' option](https://github.com/GoogleChrome/puppeteer/blob/v1.15.0/docs/api.md#puppeteerlaunchoptions) when launching a browser: | ||
@@ -180,3 +180,3 @@ ```js | ||
See [`Puppeteer.launch()`](https://github.com/GoogleChrome/puppeteer/blob/v1.14.0/docs/api.md#puppeteerlaunchoptions) for more information. | ||
See [`Puppeteer.launch()`](https://github.com/GoogleChrome/puppeteer/blob/v1.15.0/docs/api.md#puppeteerlaunchoptions) for more information. | ||
@@ -193,3 +193,3 @@ See [`this article`](https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/) for a description of the differences between Chromium and Chrome. [`This article`](https://chromium.googlesource.com/chromium/src/+/master/docs/chromium_browser_vs_google_chrome.md) describes some differences for Linux users. | ||
- [API Documentation](https://github.com/GoogleChrome/puppeteer/blob/v1.14.0/docs/api.md) | ||
- [API Documentation](https://github.com/GoogleChrome/puppeteer/blob/v1.15.0/docs/api.md) | ||
- [Examples](https://github.com/GoogleChrome/puppeteer/tree/master/examples/) | ||
@@ -250,8 +250,4 @@ - [Community list of Puppeteer resources](https://github.com/transitive-bullshit/awesome-puppeteer) | ||
# Debug output can be enabled/disabled by namespace | ||
env DEBUG="puppeteer:protocol" node script.js # protocol connection messages | ||
env DEBUG="puppeteer:session" node script.js # protocol session messages (protocol messages to targets) | ||
# Protocol traffic can be rather noisy. This example filters out all Network domain messages | ||
env DEBUG="puppeteer:session" env DEBUG_COLORS=true node script.js 2>&1 | grep -v '"Network' | ||
env DEBUG="puppeteer:*" env DEBUG_COLORS=true node script.js 2>&1 | grep -v '"Network' | ||
@@ -365,3 +361,3 @@ 6. Debug your Puppeteer (node) code easily, using [ndb](https://github.com/GoogleChromeLabs/ndb) | ||
* Puppeteer is bundled with Chromium--not Chrome--and so by default, it inherits all of [Chromium's media-related limitations](https://www.chromium.org/audio-video). This means that Puppeteer does not support licensed formats such as AAC or H.264. (However, it is possible to force Puppeteer to use a separately-installed version Chrome instead of Chromium via the [`executablePath` option to `puppeteer.launch`](https://github.com/GoogleChrome/puppeteer/blob/v1.14.0/docs/api.md#puppeteerlaunchoptions). You should only use this configuration if you need an official release of Chrome that supports these media formats.) | ||
* Puppeteer is bundled with Chromium--not Chrome--and so by default, it inherits all of [Chromium's media-related limitations](https://www.chromium.org/audio-video). This means that Puppeteer does not support licensed formats such as AAC or H.264. (However, it is possible to force Puppeteer to use a separately-installed version Chrome instead of Chromium via the [`executablePath` option to `puppeteer.launch`](https://github.com/GoogleChrome/puppeteer/blob/v1.15.0/docs/api.md#puppeteerlaunchoptions). You should only use this configuration if you need an official release of Chrome that supports these media formats.) | ||
* Since Puppeteer (in all configurations) controls a desktop version of Chromium/Chrome, features that are only supported by the mobile version of Chrome are not supported. This means that Puppeteer [does not support HTTP Live Streaming (HLS)](https://caniuse.com/#feat=http-live-streaming). | ||
@@ -368,0 +364,0 @@ |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
738715
72
22164
378
44
24