diff --git a/package.json b/package.json index 0e372f5a655136..b7a6a0035f461f 100644 --- a/package.json +++ b/package.json @@ -233,6 +233,7 @@ "@types/minimatch": "^2.0.29", "@types/node": "^8.10.20", "@types/prop-types": "^15.5.3", + "@types/puppeteer": "^1.6.2", "@types/react": "^16.3.14", "@types/react-dom": "^16.0.5", "@types/react-redux": "^6.0.6", diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/layout.ts b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/layout.ts index eb7caf10307f32..2516dfe18e5e11 100644 --- a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/layout.ts +++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/layout.ts @@ -3,8 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Size } from '../../../../../types'; -import { ViewZoomWidthHeight } from './types'; +import { Size, ViewZoomWidthHeight } from '../../../../../types'; export interface PageSizeParams { pageMarginTop: number; diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.ts b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.ts index ea58a09c5550e5..9eabf701be9383 100644 --- a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.ts +++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/preserve_layout.ts @@ -7,6 +7,7 @@ import path from 'path'; import { Size } from '../../../../../types'; import { Layout, PageSizeParams } from './layout'; +// We use a zoom of two to bump up the resolution of the screenshot a bit. const ZOOM: number = 2; export class PreserveLayout extends Layout { diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print_layout.ts b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print_layout.ts index 44f65d35e6d9cc..0997081a441371 100644 --- a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print_layout.ts +++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/print_layout.ts @@ -4,19 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ import path from 'path'; -import { KbnServer, Size } from '../../../../../types'; +import { EvaluateOptions, KbnServer, Size } from '../../../../../types'; import { Layout } from './layout'; import { CaptureConfig } from './types'; -type EvalArgs = any[]; - -interface EvaluateOptions { - // 'fn' is a function in string form to avoid tslint from auto formatting it into a version not - // underfood by transform_fn safeWrap. - fn: ((...evalArgs: EvalArgs) => any); - args: EvalArgs; // Arguments to be passed into the function defined by fn. -} - interface BrowserClient { evaluate: (evaluateOptions: EvaluateOptions) => void; } diff --git a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/types.d.ts b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/types.d.ts index 0ade1093312ea2..8a2157078be330 100644 --- a/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/types.d.ts +++ b/x-pack/plugins/reporting/export_types/printable_pdf/server/lib/layouts/types.d.ts @@ -9,9 +9,3 @@ export interface CaptureConfig { zoom: number; viewport: Size; } - -export interface ViewZoomWidthHeight { - zoom: number; - width: number; - height: number; -} diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts new file mode 100644 index 00000000000000..ea90e8bf8316d8 --- /dev/null +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as Chrome from 'puppeteer'; +import { + ElementPosition, + EvalArgs, + EvalFn, + EvaluateOptions, + Logger, + ViewZoomWidthHeight, +} from '../../../../types'; + +export interface ChromiumDriverOptions { + logger: Logger; +} + +const WAIT_FOR_DELAY_MS: number = 100; + +export class HeadlessChromiumDriver { + private readonly page: Chrome.Page; + private readonly logger: Logger; + + constructor(page: Chrome.Page, { logger }: ChromiumDriverOptions) { + this.page = page; + this.logger = logger; + } + + public async open( + url: string, + { headers, waitForSelector }: { headers: Record; waitForSelector: string } + ) { + this.logger.debug(`HeadlessChromiumDriver:opening url ${url}`); + + await this.page.setExtraHTTPHeaders(headers); + await this.page.goto(url, { waitUntil: 'networkidle0' }); + await this.page.waitFor(waitForSelector); + } + + public async screenshot(elementPosition: ElementPosition) { + let clip; + if (elementPosition) { + const { boundingClientRect, scroll = { x: 0, y: 0 } } = elementPosition; + clip = { + x: boundingClientRect.left + scroll.x, + y: boundingClientRect.top + scroll.y, + height: boundingClientRect.height, + width: boundingClientRect.width, + }; + } + + const screenshot = await this.page.screenshot({ + clip, + }); + + return screenshot.toString('base64'); + } + + public async evaluate({ fn, args = [] }: EvaluateOptions) { + const result = await this.page.evaluate(fn, ...args); + return result; + } + + public waitForSelector(selector: string) { + return this.page.waitFor(selector); + } + + public async waitFor({ fn, args, toEqual }: { fn: EvalFn; args: EvalArgs; toEqual: T }) { + while (true) { + const result = await this.evaluate({ fn, args }); + if (result === toEqual) { + return; + } + + await new Promise(r => setTimeout(r, WAIT_FOR_DELAY_MS)); + } + } + + public async setViewport({ width, height, zoom }: ViewZoomWidthHeight) { + this.logger.debug(`Setting viewport to width: ${width}, height: ${height}, zoom: ${zoom}`); + + await this.page.setViewport({ + width: Math.floor(width / zoom), + height: Math.floor(height / zoom), + deviceScaleFactor: zoom, + isMobile: false, + }); + } +} diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver/index.js b/x-pack/plugins/reporting/server/browsers/chromium/driver/index.js deleted file mode 100644 index 1f14532e804482..00000000000000 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver/index.js +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export class HeadlessChromiumDriver { - constructor(page, { maxScreenshotDimension, logger }) { - this._page = page; - this._maxScreenshotDimension = maxScreenshotDimension; - this._waitForDelayMs = 100; - this._zoom = 1; - this._logger = logger; - } - - async open(url, { headers, waitForSelector }) { - this._logger.debug(`HeadlessChromiumDriver:opening url ${url}`); - - await this._page.setExtraHTTPHeaders(headers); - await this._page.goto(url, { waitUntil: 'networkidle0' }); - - this.documentNode = await this._page.evaluateHandle(() => document); - await this._page.waitFor(waitForSelector); - } - - async screenshot(elementPosition = null) { - let clip; - if (elementPosition) { - const { boundingClientRect, scroll = { x: 0, y: 0 } } = elementPosition; - clip = { - x: boundingClientRect.left + scroll.x, - y: boundingClientRect.top + scroll.y, - height: boundingClientRect.height, - width: boundingClientRect.width, - }; - } - - const screenshot = await this._page.screenshot({ - encoding: 'base64', - clip, - }); - - return screenshot; - } - - async evaluate({ fn, args = [] }) { - const result = await this._page.evaluate(fn, ...args); - return result; - } - - waitForSelector(selector) { - return this._page.waitFor(selector); - } - - async waitFor({ fn, args, toEqual }) { - while (true) { - const result = await this.evaluate({ fn, args }); - if (result === toEqual) { - return; - } - - await new Promise(r => setTimeout(r, this._waitForDelayMs)); - } - } - - async setViewport({ width, height, zoom }) { - this._logger.debug(`Setting viewport to width: ${width}, height: ${height}, zoom: ${zoom}`); - - await this._page.setViewport({ - width: Math.floor(width / zoom), - height: Math.floor(height / zoom), - deviceScaleFactor: zoom, - isMobile: false, - }); - - this._zoom = zoom; - } -} diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver/index.ts new file mode 100644 index 00000000000000..428dc020f9b7e1 --- /dev/null +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { HeadlessChromiumDriver } from './chromium_driver'; diff --git a/x-pack/plugins/reporting/types.d.ts b/x-pack/plugins/reporting/types.d.ts index 3e5f243fc82525..11c171779fa775 100644 --- a/x-pack/plugins/reporting/types.d.ts +++ b/x-pack/plugins/reporting/types.d.ts @@ -21,3 +21,35 @@ export interface Logger { error: (message: string) => void; warning: (message: string) => void; } + +export interface ViewZoomWidthHeight { + zoom: number; + width: number; + height: number; +} + +export type EvalArgs = any[]; +export type EvalFn = ((...evalArgs: EvalArgs) => T); + +export interface EvaluateOptions { + fn: EvalFn; + args: EvalArgs; // Arguments to be passed into the function defined by fn. +} + +export interface ElementPosition { + boundingClientRect: { + // modern browsers support x/y, but older ones don't + top: number; + left: number; + width: number; + height: number; + }; + scroll: { + x: number; + y: number; + }; +} + +export interface HeadlessElementInfo { + position: ElementPosition; +} diff --git a/yarn.lock b/yarn.lock index 8c22fee2635e78..800284455806d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -488,6 +488,13 @@ version "15.5.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.3.tgz#bef071852dca2a2dbb65fecdb7bfb30cedae2de2" +"@types/puppeteer@^1.6.2": + version "1.6.2" + resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.6.2.tgz#9b4ba40a67abad3c729a021ffd259d929d00c5e0" + dependencies: + "@types/events" "*" + "@types/node" "*" + "@types/react-dom@^16.0.5": version "16.0.5" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.0.5.tgz#a757457662e3819409229e8f86795ff37b371f96"