1 of 36

Headless Chrome

BlinkOn 8, Tokyo

skyostil@, dvallet@, lushnikov@

2 of 36

Outline

  • New stuff in Headless
  • What’s next?
  • Puppeteer

3 of 36

Resource interception

Browser

HTTP request

HTTP response

4 of 36

Resource interception

Browser

HTTP request

HTTP response

DevTools client

Mocked response

Modified request

5 of 36

Resource interception

  • Network.setRequestInterceptionEnabled
    • Also accepts URL patterns
  • Network.requestIntercepted event
    • Request details (incl. request type)
    • Includes navigations, redirects
  • Network.continueInterceptedRequest
    • Can override outgoing URL, headers, method or post data
    • Alternatively can mock response (or replace with an error)
    • Handle 401/407 authentication challenge

Browser

HTTP request

HTTP response

DevTools client

Mocked response

Modified request

6 of 36

Rendering control

  • Headless renders to a virtual display
  • Problem: Always runs at 60 fps
  • Problem: Missing content in screenshots
  • Problem: Nondeterministic animations

Headless Browser

Virtual

display

Render

Take screenshot

7 of 36

Rendering control

Browser

Renderer B

Renderer A

Renderer C

Display

8 of 36

Rendering control

Browser

Renderer B

Renderer A

Renderer C

Display

Requesting to render

9 of 36

Rendering control

Browser

Renderer B

Renderer A

Renderer C

Display

Requesting to render

Requesting VSYNC

10 of 36

Rendering control

Browser

Display

VSYNC

Renderer A

Renderer B

Renderer C

11 of 36

Rendering control

Browser

Display

VSYNC

BeginFrame

- animation time

- deadline

Renderer A

Renderer B

Renderer C

12 of 36

Rendering control

Browser

Display

VSYNC

BeginFrameAck + new frame

Renderer A

Renderer B

Renderer C

13 of 36

Rendering control

Browser

Display

VSYNC

Composite

Renderer A

Renderer B

Renderer C

BeginFrameAck + new frame

14 of 36

Rendering control

Browser

Renderer A

Renderer B

Renderer C

DevTools client

15 of 36

Rendering control

Browser

Headless.needsBeginFramesChanged

Renderer A

Renderer B

Renderer C

DevTools client

16 of 36

Rendering control

Browser

Headless.needsBeginFramesChanged

Headless.beginFrame(time)

Renderer A

Renderer B

Renderer C

DevTools client

17 of 36

Rendering control

Browser

Headless.needsBeginFramesChanged

Headless.beginFrame(time)

Headless.beginFrame result

Renderer A

Renderer B

Renderer C

DevTools client

Screenshot

18 of 36

Rendering control

  • Headless.enable
  • Headless.mainFrameReadyForScreenshots event
  • Headless.needsBeginFramesChanged event
  • Headless.beginFrame
    • Time, deadline, interval
    • Optionally capture a screenshot
    • Returns: did we need to draw anything? (+ screenshot)
  • Emulation.setDeviceMetricsOverride can set viewport too
  • Only supported in headless mode for now

19 of 36

C++ embedder utils: Custom fetch behavior

net::URLRequestJob

ExpeditedDispatcher

DeterministicDispatcher

ThrottledDispatcher

20 of 36

C++ embedder utils: TabSocket

JavaScript context

C++ embedder

Renderer

21 of 36

C++ embedder utils: TabSocket

V8 isolated world

C++ embedder

Renderer

V8 main world

DOM

22 of 36

C++ embedder utils: TabSocket

V8 isolated world

C++ embedder

Renderer

JavaScript

TabSocket.send('hello');

TabSocket.onmessage = (message) => {

console.log(message);

};

C++

SendMessageToContext(const std::string& message,

int context_id);

OnMessageFromContext(const std::string& message,

int context_id);

V8 main world

DOM

23 of 36

macOS/Windows support

  • Mac available since M60
    • Not true headless: hidden window implementation
    • Other than that, same functionality
    • This may change with MacViews
  • Windows available since M61
    • Adapted Headless to chrome.dll (+165 KB), chrome_child.dll (+70 KB)
  • All platforms have custom headless_shell build target with minimal library

./path_to_chrome[.exe] --headless --remote-debugging-port=9222 --disable-gpu

./headless_shell[.exe] --remote-debugging-port=9222 --disable-gpu

24 of 36

Selenium/ChromeDriver support

  • Most features supported!
  • Added (virtual) window management
    • Set/Get Window state (maximize, minimized, fullscreen normal)
    • Set/Get Window bounds
  • User agent support
  • Key/Mouse input supported
  • Work in progress
    • Emulate network conditions
    • Download support
    • Certificate handling
  • Not supported
    • Extensions: looking into launch app feature

ChromeOptions options = new ChromeOptions();

options.addArguments("headless");

options.addArguments("window-size=1200x600");

25 of 36

Certificate error handling

Coming soon: ChromeDriver support

const CDP = require('chrome-remote-interface');

CDP(async (client) => {

const {Page, Security} = client;

// ignore all the certificate errors

Security.certificateError(({eventId}) => {

Security.handleCertificateError({

eventId,

action: 'continue'

});

});

await Page.enable(); await Security.enable();

await Security.setOverrideCertificateErrors({override: true});

await Page.navigate({url: 'https://cockroach-adriatic-0001.crdb.io:8080'});

)};

26 of 36

Print-to-PDF

TODO: Further PDF customizations (custom headers, CSS styling)

const CDP = require('chrome-remote-interface');

const fs = require('fs');

CDP(async (client) => {

const {Page} = client;

await Page.enable();

await Page.navigate({url: 'https://github.com'});

await Page.loadEventFired();

const {data} = await Page.printToPDF({

landscape: true,

printBackground: true,

marginTop: 0,

marginBottom: 0,

});

fs.writeFileSync('page.pdf', Buffer.from(data, 'base64'));

});

27 of 36

Downloads

  • TODO: Advanced download handling
    • Events
    • ByteStream handling

const CDP = require('chrome-remote-interface');

CDP(async (client) => {

const {Page} = client;

await Page.enable();

await Page.setDownloadBehavior({behavior : "allow",

downloadPath: "/tmp/download1"});

Page.navigate({url:"http://ipv4.download.thinkbroadband.com/5MB.zip"});

});

28 of 36

Next for Headless

  • Rendering control in non-headless mode
  • More page load determinism
    • E.g., date/time override
  • SwiftShader (WebGL) for all platforms
    • Hardware GPU support
  • Performance improvements
    • Saving and restoring browser sessions
    • Protocol wire format
  • New platform: Fuchsia
    • Also tweaks for AWS Lambda style environments

29 of 36

github.com/GoogleChrome/puppeteer

lushnikov@

30 of 36

What is Puppeteer?

A Node.js library for automating headless Chrome

const puppeteer = require('puppeteer');

(async() => {

const browser = await puppeteer.launch();

const page = await browser.newPage();

await page.goto('https://github.com',

{waitUntil: 'networkidle'});

await page.pdf({path: 'page.pdf'});

await browser.close();

})();

Chrome DevTools Protocol

Puppeteer

Headless

Chromium

31 of 36

Large developer demand for automation

  • Server-side rendering
  • Web scraping
  • Testing
  • Mobile emulation
  • Screenshots (visual regression, mobile compatibility, etc)
  • ...
  • Scripting the acquisition of a DMV appointment ;)

32 of 36

Why should we invest here?

Already several existing tools

  • PhantomJS - 2014-era WebKit
  • NightmareJS - Electron-based

Our interests:

  • Many existing and emerging DevTools protocol clients
    • E.g. Critical CSS extraction, PWA validation, perf measurement
  • Dogfood the protocol from an automation standpoint
  • Puppeteer serves as reference implementation for other protocol clients

33 of 36

A new API?

  • WebDriver/Selenium is great.
    • But...
    • Doesn’t satisfy many power-user needs (Service worker, JS coverage, etc..)
  • Chrome DevTools Protocol is THE official headless API
    • Large, verbose and fast-evolving API surface
    • Too powerful for the typical developer
    • Taking a screenshot is nowhere near a 1-liner
  • Puppeteer API
    • High-level, async/await based API
    • Exposes the protocol’s power safely
    • Predictable compatibility via Semver versioning

34 of 36

How we develop Puppeteer

  • Not a part of the Chromium repository
  • All development on GitHub
  • Published to npm
    • Bundles a specific revision of chromium
  • Team
    • Core eng: lushnikov@, einbinder@
    • DevRel: ericbidelman@
    • In the month since launch, now > 40 contributors in total.
  • Adoption
    • 14000+ stars on GitHub
    • 80+ projects depend on Puppeteer on npm

35 of 36

Demo

36 of 36

Thanks!