257 lines
10 KiB
JavaScript
257 lines
10 KiB
JavaScript
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.Screenshotter = void 0;
|
|
|
|
var _helper = require("./helper");
|
|
|
|
var _utils = require("../utils/utils");
|
|
|
|
/**
|
|
* Copyright 2019 Google Inc. All rights reserved.
|
|
* Modifications copyright (c) Microsoft Corporation.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
class Screenshotter {
|
|
constructor(page) {
|
|
this._queue = new TaskQueue();
|
|
this._page = void 0;
|
|
this._page = page;
|
|
this._queue = new TaskQueue();
|
|
}
|
|
|
|
async _originalViewportSize(progress) {
|
|
const originalViewportSize = this._page.viewportSize();
|
|
|
|
let viewportSize = originalViewportSize;
|
|
if (!viewportSize) viewportSize = await this._page.mainFrame().waitForFunctionValueInUtility(progress, () => ({
|
|
width: window.innerWidth,
|
|
height: window.innerHeight
|
|
}));
|
|
return {
|
|
viewportSize,
|
|
originalViewportSize
|
|
};
|
|
}
|
|
|
|
async _fullPageSize(progress) {
|
|
const fullPageSize = await this._page.mainFrame().waitForFunctionValueInUtility(progress, () => {
|
|
if (!document.body || !document.documentElement) return null;
|
|
return {
|
|
width: Math.max(document.body.scrollWidth, document.documentElement.scrollWidth, document.body.offsetWidth, document.documentElement.offsetWidth, document.body.clientWidth, document.documentElement.clientWidth),
|
|
height: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight, document.body.offsetHeight, document.documentElement.offsetHeight, document.body.clientHeight, document.documentElement.clientHeight)
|
|
};
|
|
});
|
|
return fullPageSize;
|
|
}
|
|
|
|
async screenshotPage(progress, options) {
|
|
const format = validateScreenshotOptions(options);
|
|
return this._queue.postTask(async () => {
|
|
const {
|
|
viewportSize,
|
|
originalViewportSize
|
|
} = await this._originalViewportSize(progress);
|
|
|
|
if (options.fullPage) {
|
|
const fullPageSize = await this._fullPageSize(progress);
|
|
let documentRect = {
|
|
x: 0,
|
|
y: 0,
|
|
width: fullPageSize.width,
|
|
height: fullPageSize.height
|
|
};
|
|
let overriddenViewportSize = null;
|
|
const fitsViewport = fullPageSize.width <= viewportSize.width && fullPageSize.height <= viewportSize.height;
|
|
|
|
if (!this._page._delegate.canScreenshotOutsideViewport() && !fitsViewport) {
|
|
overriddenViewportSize = fullPageSize;
|
|
progress.throwIfAborted(); // Avoid side effects.
|
|
|
|
await this._page.setViewportSize(overriddenViewportSize);
|
|
progress.cleanupWhenAborted(() => this._restoreViewport(originalViewportSize));
|
|
}
|
|
|
|
if (options.clip) documentRect = trimClipToSize(options.clip, documentRect);
|
|
const buffer = await this._screenshot(progress, format, documentRect, undefined, options);
|
|
progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup.
|
|
|
|
if (overriddenViewportSize) await this._restoreViewport(originalViewportSize);
|
|
return buffer;
|
|
}
|
|
|
|
const viewportRect = options.clip ? trimClipToSize(options.clip, viewportSize) : {
|
|
x: 0,
|
|
y: 0,
|
|
...viewportSize
|
|
};
|
|
return await this._screenshot(progress, format, undefined, viewportRect, options);
|
|
});
|
|
}
|
|
|
|
async screenshotElement(progress, handle, options = {}) {
|
|
const format = validateScreenshotOptions(options);
|
|
return this._queue.postTask(async () => {
|
|
const {
|
|
viewportSize,
|
|
originalViewportSize
|
|
} = await this._originalViewportSize(progress);
|
|
await handle._waitAndScrollIntoViewIfNeeded(progress);
|
|
progress.throwIfAborted(); // Do not do extra work.
|
|
|
|
let boundingBox = await handle.boundingBox();
|
|
(0, _utils.assert)(boundingBox, 'Node is either not visible or not an HTMLElement');
|
|
(0, _utils.assert)(boundingBox.width !== 0, 'Node has 0 width.');
|
|
(0, _utils.assert)(boundingBox.height !== 0, 'Node has 0 height.');
|
|
let overriddenViewportSize = null;
|
|
const fitsViewport = boundingBox.width <= viewportSize.width && boundingBox.height <= viewportSize.height;
|
|
|
|
if (!this._page._delegate.canScreenshotOutsideViewport() && !fitsViewport) {
|
|
overriddenViewportSize = _helper.helper.enclosingIntSize({
|
|
width: Math.max(viewportSize.width, boundingBox.width),
|
|
height: Math.max(viewportSize.height, boundingBox.height)
|
|
});
|
|
progress.throwIfAborted(); // Avoid side effects.
|
|
|
|
await this._page.setViewportSize(overriddenViewportSize);
|
|
progress.cleanupWhenAborted(() => this._restoreViewport(originalViewportSize));
|
|
progress.throwIfAborted(); // Avoid extra work.
|
|
|
|
await handle._waitAndScrollIntoViewIfNeeded(progress);
|
|
boundingBox = await handle.boundingBox();
|
|
(0, _utils.assert)(boundingBox, 'Node is either not visible or not an HTMLElement');
|
|
(0, _utils.assert)(boundingBox.width !== 0, 'Node has 0 width.');
|
|
(0, _utils.assert)(boundingBox.height !== 0, 'Node has 0 height.');
|
|
}
|
|
|
|
progress.throwIfAborted(); // Avoid extra work.
|
|
|
|
const scrollOffset = await this._page.mainFrame().waitForFunctionValueInUtility(progress, () => ({
|
|
x: window.scrollX,
|
|
y: window.scrollY
|
|
}));
|
|
const documentRect = { ...boundingBox
|
|
};
|
|
documentRect.x += scrollOffset.x;
|
|
documentRect.y += scrollOffset.y;
|
|
const buffer = await this._screenshot(progress, format, _helper.helper.enclosingIntRect(documentRect), undefined, options);
|
|
progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup.
|
|
|
|
if (overriddenViewportSize) await this._restoreViewport(originalViewportSize);
|
|
return buffer;
|
|
});
|
|
}
|
|
|
|
async _screenshot(progress, format, documentRect, viewportRect, options) {
|
|
if (options.__testHookBeforeScreenshot) await options.__testHookBeforeScreenshot();
|
|
progress.throwIfAborted(); // Screenshotting is expensive - avoid extra work.
|
|
|
|
const shouldSetDefaultBackground = options.omitBackground && format === 'png';
|
|
|
|
if (shouldSetDefaultBackground) {
|
|
await this._page._delegate.setBackgroundColor({
|
|
r: 0,
|
|
g: 0,
|
|
b: 0,
|
|
a: 0
|
|
});
|
|
progress.cleanupWhenAborted(() => this._page._delegate.setBackgroundColor());
|
|
}
|
|
|
|
progress.throwIfAborted(); // Avoid extra work.
|
|
|
|
const buffer = await this._page._delegate.takeScreenshot(progress, format, documentRect, viewportRect, options.quality);
|
|
progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup.
|
|
|
|
if (shouldSetDefaultBackground) await this._page._delegate.setBackgroundColor();
|
|
progress.throwIfAborted(); // Avoid side effects.
|
|
|
|
if (options.__testHookAfterScreenshot) await options.__testHookAfterScreenshot();
|
|
return buffer;
|
|
}
|
|
|
|
async _restoreViewport(originalViewportSize) {
|
|
(0, _utils.assert)(!this._page._delegate.canScreenshotOutsideViewport());
|
|
if (originalViewportSize) await this._page.setViewportSize(originalViewportSize);else await this._page._delegate.resetViewport();
|
|
}
|
|
|
|
}
|
|
|
|
exports.Screenshotter = Screenshotter;
|
|
|
|
class TaskQueue {
|
|
constructor() {
|
|
this._chain = void 0;
|
|
this._chain = Promise.resolve();
|
|
}
|
|
|
|
postTask(task) {
|
|
const result = this._chain.then(task);
|
|
|
|
this._chain = result.catch(() => {});
|
|
return result;
|
|
}
|
|
|
|
}
|
|
|
|
function trimClipToSize(clip, size) {
|
|
const p1 = {
|
|
x: Math.max(0, Math.min(clip.x, size.width)),
|
|
y: Math.max(0, Math.min(clip.y, size.height))
|
|
};
|
|
const p2 = {
|
|
x: Math.max(0, Math.min(clip.x + clip.width, size.width)),
|
|
y: Math.max(0, Math.min(clip.y + clip.height, size.height))
|
|
};
|
|
const result = {
|
|
x: p1.x,
|
|
y: p1.y,
|
|
width: p2.x - p1.x,
|
|
height: p2.y - p1.y
|
|
};
|
|
(0, _utils.assert)(result.width && result.height, 'Clipped area is either empty or outside the resulting image');
|
|
return result;
|
|
}
|
|
|
|
function validateScreenshotOptions(options) {
|
|
let format = null; // options.type takes precedence over inferring the type from options.path
|
|
// because it may be a 0-length file with no extension created beforehand (i.e. as a temp file).
|
|
|
|
if (options.type) {
|
|
(0, _utils.assert)(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type);
|
|
format = options.type;
|
|
}
|
|
|
|
if (!format) format = 'png';
|
|
|
|
if (options.quality !== undefined) {
|
|
(0, _utils.assert)(format === 'jpeg', 'options.quality is unsupported for the ' + format + ' screenshots');
|
|
(0, _utils.assert)(typeof options.quality === 'number', 'Expected options.quality to be a number but found ' + typeof options.quality);
|
|
(0, _utils.assert)(Number.isInteger(options.quality), 'Expected options.quality to be an integer');
|
|
(0, _utils.assert)(options.quality >= 0 && options.quality <= 100, 'Expected options.quality to be between 0 and 100 (inclusive), got ' + options.quality);
|
|
}
|
|
|
|
if (options.clip) {
|
|
(0, _utils.assert)(typeof options.clip.x === 'number', 'Expected options.clip.x to be a number but found ' + typeof options.clip.x);
|
|
(0, _utils.assert)(typeof options.clip.y === 'number', 'Expected options.clip.y to be a number but found ' + typeof options.clip.y);
|
|
(0, _utils.assert)(typeof options.clip.width === 'number', 'Expected options.clip.width to be a number but found ' + typeof options.clip.width);
|
|
(0, _utils.assert)(typeof options.clip.height === 'number', 'Expected options.clip.height to be a number but found ' + typeof options.clip.height);
|
|
(0, _utils.assert)(options.clip.width !== 0, 'Expected options.clip.width not to be 0.');
|
|
(0, _utils.assert)(options.clip.height !== 0, 'Expected options.clip.height not to be 0.');
|
|
}
|
|
|
|
return format;
|
|
} |