"use strict";
/**
 * 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.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.Screenshotter = void 0;
const helper_1 = require("./helper");
const utils_1 = require("../utils/utils");
class Screenshotter {
    constructor(page) {
        this._queue = new TaskQueue();
        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().waitForFunctionValue(progress, () => ({ width: window.innerWidth, height: window.innerHeight }));
        return { viewportSize, originalViewportSize };
    }
    async _fullPageSize(progress) {
        const fullPageSize = await this._page.mainFrame().waitForFunctionValue(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();
            utils_1.assert(boundingBox, 'Node is either not visible or not an HTMLElement');
            utils_1.assert(boundingBox.width !== 0, 'Node has 0 width.');
            utils_1.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_1.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();
                utils_1.assert(boundingBox, 'Node is either not visible or not an HTMLElement');
                utils_1.assert(boundingBox.width !== 0, 'Node has 0 width.');
                utils_1.assert(boundingBox.height !== 0, 'Node has 0 height.');
            }
            progress.throwIfAborted(); // Avoid extra work.
            const scrollOffset = await this._page.mainFrame().waitForFunctionValue(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_1.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) {
        utils_1.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 = 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 };
    utils_1.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) {
        utils_1.assert(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type);
        format = options.type;
    }
    if (!format)
        format = 'png';
    if (options.quality !== undefined) {
        utils_1.assert(format === 'jpeg', 'options.quality is unsupported for the ' + format + ' screenshots');
        utils_1.assert(typeof options.quality === 'number', 'Expected options.quality to be a number but found ' + (typeof options.quality));
        utils_1.assert(Number.isInteger(options.quality), 'Expected options.quality to be an integer');
        utils_1.assert(options.quality >= 0 && options.quality <= 100, 'Expected options.quality to be between 0 and 100 (inclusive), got ' + options.quality);
    }
    if (options.clip) {
        utils_1.assert(typeof options.clip.x === 'number', 'Expected options.clip.x to be a number but found ' + (typeof options.clip.x));
        utils_1.assert(typeof options.clip.y === 'number', 'Expected options.clip.y to be a number but found ' + (typeof options.clip.y));
        utils_1.assert(typeof options.clip.width === 'number', 'Expected options.clip.width to be a number but found ' + (typeof options.clip.width));
        utils_1.assert(typeof options.clip.height === 'number', 'Expected options.clip.height to be a number but found ' + (typeof options.clip.height));
        utils_1.assert(options.clip.width !== 0, 'Expected options.clip.width not to be 0.');
        utils_1.assert(options.clip.height !== 0, 'Expected options.clip.height not to be 0.');
    }
    return format;
}
//# sourceMappingURL=screenshotter.js.map