UNPKG

chrome-devtools-frontend

Version:
186 lines (164 loc) 7.04 kB
// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import type * as Common from '../../core/common/common.js'; import * as Root from '../../core/root/root.js'; import * as SDK from '../../core/sdk/sdk.js'; import * as UI from '../../ui/legacy/legacy.js'; import type * as Protocol from '../../generated/protocol.js'; import * as EmulationModel from '../../models/emulation/emulation.js'; import {DeviceModeView} from './DeviceModeView.js'; import {type InspectedPagePlaceholder} from './InspectedPagePlaceholder.js'; let deviceModeWrapperInstance: DeviceModeWrapper; export class DeviceModeWrapper extends UI.Widget.VBox { private readonly inspectedPagePlaceholder: InspectedPagePlaceholder; private deviceModeView: DeviceModeView|null; private readonly toggleDeviceModeAction: UI.ActionRegistration.Action|null; private showDeviceModeSetting: Common.Settings.Setting<boolean>; private constructor(inspectedPagePlaceholder: InspectedPagePlaceholder) { super(); this.inspectedPagePlaceholder = inspectedPagePlaceholder; this.deviceModeView = null; this.toggleDeviceModeAction = UI.ActionRegistry.ActionRegistry.instance().action('emulation.toggle-device-mode'); const model = EmulationModel.DeviceModeModel.DeviceModeModel.instance(); this.showDeviceModeSetting = model.enabledSetting(); this.showDeviceModeSetting.setRequiresUserAction(Boolean(Root.Runtime.Runtime.queryParam('hasOtherClients'))); this.showDeviceModeSetting.addChangeListener(this.update.bind(this, false)); SDK.TargetManager.TargetManager.instance().addModelListener( SDK.OverlayModel.OverlayModel, SDK.OverlayModel.Events.ScreenshotRequested, this.screenshotRequestedFromOverlay, this); this.update(true); } static instance(opts: { forceNew: boolean|null, inspectedPagePlaceholder: InspectedPagePlaceholder|null, } = {forceNew: null, inspectedPagePlaceholder: null}): DeviceModeWrapper { const {forceNew, inspectedPagePlaceholder} = opts; if (!deviceModeWrapperInstance || forceNew) { if (!inspectedPagePlaceholder) { throw new Error( `Unable to create DeviceModeWrapper: inspectedPagePlaceholder must be provided: ${new Error().stack}`); } deviceModeWrapperInstance = new DeviceModeWrapper(inspectedPagePlaceholder); } return deviceModeWrapperInstance; } toggleDeviceMode(): void { this.showDeviceModeSetting.set(!this.showDeviceModeSetting.get()); } isDeviceModeOn(): boolean { return this.showDeviceModeSetting.get(); } captureScreenshot(fullSize?: boolean, clip?: Protocol.Page.Viewport): boolean { if (!this.deviceModeView) { this.deviceModeView = new DeviceModeView(); } this.deviceModeView.setNonEmulatedAvailableSize(this.inspectedPagePlaceholder.element); if (fullSize) { void this.deviceModeView.captureFullSizeScreenshot(); } else if (clip) { void this.deviceModeView.captureAreaScreenshot(clip); } else { void this.deviceModeView.captureScreenshot(); } return true; } private screenshotRequestedFromOverlay(event: Common.EventTarget.EventTargetEvent<Protocol.Page.Viewport>): void { const clip = event.data; this.captureScreenshot(false, clip); } private update(force: boolean): void { if (this.toggleDeviceModeAction) { this.toggleDeviceModeAction.setToggled(this.showDeviceModeSetting.get()); } if (!force) { const showing = this.deviceModeView && this.deviceModeView.isShowing(); if (this.showDeviceModeSetting.get() === showing) { return; } } if (this.showDeviceModeSetting.get()) { if (!this.deviceModeView) { this.deviceModeView = new DeviceModeView(); } this.deviceModeView.show(this.element); this.inspectedPagePlaceholder.clearMinimumSize(); this.inspectedPagePlaceholder.show(this.deviceModeView.element); } else { if (this.deviceModeView) { this.deviceModeView.exitHingeMode(); this.deviceModeView.detach(); } this.inspectedPagePlaceholder.restoreMinimumSize(); this.inspectedPagePlaceholder.show(this.element); } } } let actionDelegateInstance: ActionDelegate; export class ActionDelegate implements UI.ActionRegistration.ActionDelegate { handleAction(context: UI.Context.Context, actionId: string): boolean { if (DeviceModeWrapper.instance()) { switch (actionId) { case 'emulation.capture-screenshot': return DeviceModeWrapper.instance().captureScreenshot(); case 'emulation.capture-node-screenshot': { const node = UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode); if (!node) { return true; } async function captureClip(): Promise<void> { if (!node) { return; } const object = await node.resolveToObject(); if (!object) { return; } const result = await object.callFunction(function() { const rect = (this as Element).getBoundingClientRect(); const docRect = (this as Element).ownerDocument.documentElement.getBoundingClientRect(); return JSON.stringify({ x: rect.left - docRect.left, y: rect.top - docRect.top, width: rect.width, height: rect.height, scale: 1, }); }); if (!result.object) { throw new Error('Clipping error: could not get object data.'); } const clip = (JSON.parse((result.object.value as string))); const response = await node.domModel().target().pageAgent().invoke_getLayoutMetrics(); const error = response.getError(); // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/naming-convention const page_zoom = !error && response.visualViewport.zoom || 1; clip.x *= page_zoom; clip.y *= page_zoom; clip.width *= page_zoom; clip.height *= page_zoom; DeviceModeWrapper.instance().captureScreenshot(false, clip); } void captureClip(); return true; } case 'emulation.capture-full-height-screenshot': return DeviceModeWrapper.instance().captureScreenshot(true); case 'emulation.toggle-device-mode': DeviceModeWrapper.instance().toggleDeviceMode(); return true; } } return false; } static instance(opts: { forceNew: boolean|null, } = {forceNew: null}): ActionDelegate { const {forceNew} = opts; if (!actionDelegateInstance || forceNew) { actionDelegateInstance = new ActionDelegate(); } return actionDelegateInstance; } }