mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-01 09:47:56 +08:00
For all comments that contained both the unrestricted annotation and any other potential annotation, remove only the unrestricted annotation. R=szuend@chromium.org No-Presubmit: True Bug: 1011811 Change-Id: I27dd67f657cdb1a9233d768eea81cabe2591f5f7 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2569759 Commit-Queue: Tim van der Lippe <tvanderlippe@chromium.org> Reviewed-by: Simon Zünd <szuend@chromium.org>
980 lines
31 KiB
JavaScript
980 lines
31 KiB
JavaScript
// Copyright 2015 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 * as Common from '../common/common.js'; // eslint-disable-line no-unused-vars
|
|
import * as Host from '../host/host.js';
|
|
import * as Root from '../root/root.js';
|
|
import * as SDK from '../sdk/sdk.js';
|
|
import * as UI from '../ui/ui.js';
|
|
|
|
import {EmulatedDevice, Horizontal, HorizontalSpanned, Mode, Vertical, VerticalSpanned} from './EmulatedDevices.js'; // eslint-disable-line no-unused-vars
|
|
|
|
/** @type {!DeviceModeModel} */
|
|
let deviceModeModelInstance;
|
|
|
|
/**
|
|
* @implements {SDK.SDKModel.SDKModelObserver<!SDK.EmulationModel.EmulationModel>}
|
|
* @extends {Common.ObjectWrapper.ObjectWrapper}
|
|
*/
|
|
export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper {
|
|
/**
|
|
* @private
|
|
*/
|
|
constructor() {
|
|
super();
|
|
this._screenRect = new UI.Geometry.Rect(0, 0, 1, 1);
|
|
this._visiblePageRect = new UI.Geometry.Rect(0, 0, 1, 1);
|
|
this._availableSize = new UI.Geometry.Size(1, 1);
|
|
this._preferredSize = new UI.Geometry.Size(1, 1);
|
|
this._initialized = false;
|
|
this._appliedDeviceSize = new UI.Geometry.Size(1, 1);
|
|
this._appliedDeviceScaleFactor = window.devicePixelRatio;
|
|
this._appliedUserAgentType = UA.Desktop;
|
|
this._experimentDualScreenSupport = Root.Runtime.experiments.isEnabled('dualScreenSupport');
|
|
this._webPlatformExperimentalFeaturesEnabled = !!eval('window.getWindowSegments');
|
|
|
|
this._scaleSetting = Common.Settings.Settings.instance().createSetting('emulation.deviceScale', 1);
|
|
// We've used to allow zero before.
|
|
if (!this._scaleSetting.get()) {
|
|
this._scaleSetting.set(1);
|
|
}
|
|
this._scaleSetting.addChangeListener(this._scaleSettingChanged, this);
|
|
this._scale = 1;
|
|
|
|
this._widthSetting = Common.Settings.Settings.instance().createSetting('emulation.deviceWidth', 400);
|
|
if (this._widthSetting.get() < MinDeviceSize) {
|
|
this._widthSetting.set(MinDeviceSize);
|
|
}
|
|
if (this._widthSetting.get() > MaxDeviceSize) {
|
|
this._widthSetting.set(MaxDeviceSize);
|
|
}
|
|
this._widthSetting.addChangeListener(this._widthSettingChanged, this);
|
|
|
|
this._heightSetting = Common.Settings.Settings.instance().createSetting('emulation.deviceHeight', 0);
|
|
if (this._heightSetting.get() && this._heightSetting.get() < MinDeviceSize) {
|
|
this._heightSetting.set(MinDeviceSize);
|
|
}
|
|
if (this._heightSetting.get() > MaxDeviceSize) {
|
|
this._heightSetting.set(MaxDeviceSize);
|
|
}
|
|
this._heightSetting.addChangeListener(this._heightSettingChanged, this);
|
|
|
|
this._uaSetting = Common.Settings.Settings.instance().createSetting('emulation.deviceUA', UA.Mobile);
|
|
this._uaSetting.addChangeListener(this._uaSettingChanged, this);
|
|
this._deviceScaleFactorSetting =
|
|
Common.Settings.Settings.instance().createSetting('emulation.deviceScaleFactor', 0);
|
|
this._deviceScaleFactorSetting.addChangeListener(this._deviceScaleFactorSettingChanged, this);
|
|
|
|
this._deviceOutlineSetting = Common.Settings.Settings.instance().moduleSetting('emulation.showDeviceOutline');
|
|
this._deviceOutlineSetting.addChangeListener(this._deviceOutlineSettingChanged, this);
|
|
|
|
this._toolbarControlsEnabledSetting = Common.Settings.Settings.instance().createSetting(
|
|
'emulation.toolbarControlsEnabled', true, Common.Settings.SettingStorageType.Session);
|
|
|
|
/** @type {!Type} */
|
|
this._type = Type.None;
|
|
/** @type {?EmulatedDevice} */
|
|
this._device = null;
|
|
/** @type {?Mode} */
|
|
this._mode = null;
|
|
/** @type {number} */
|
|
this._fitScale = 1;
|
|
this._touchEnabled = false;
|
|
this._touchMobile = false;
|
|
|
|
/** @type {?SDK.EmulationModel.EmulationModel} */
|
|
this._emulationModel = null;
|
|
/** @type {?function():void} */
|
|
this._onModelAvailable = null;
|
|
SDK.SDKModel.TargetManager.instance().observeModels(SDK.EmulationModel.EmulationModel, this);
|
|
}
|
|
|
|
static instance(opts = {forceNew: null}) {
|
|
if (!deviceModeModelInstance || opts.forceNew) {
|
|
deviceModeModelInstance = new DeviceModeModel();
|
|
}
|
|
|
|
return deviceModeModelInstance;
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @return {{valid: boolean, errorMessage: (string|undefined)}}
|
|
*/
|
|
static widthValidator(value) {
|
|
let valid = false;
|
|
let errorMessage;
|
|
|
|
if (!/^[\d]+$/.test(value)) {
|
|
errorMessage = ls`Width must be a number.`;
|
|
} else if (Number(value) > MaxDeviceSize) {
|
|
errorMessage = ls`Width must be less than or equal to ${MaxDeviceSize}.`;
|
|
} else if (Number(value) < MinDeviceSize) {
|
|
errorMessage = ls`Width must be greater than or equal to ${MinDeviceSize}.`;
|
|
} else {
|
|
valid = true;
|
|
}
|
|
|
|
return {valid, errorMessage};
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @return {{valid: boolean, errorMessage: (string|undefined)}}
|
|
*/
|
|
static heightValidator(value) {
|
|
let valid = false;
|
|
let errorMessage;
|
|
|
|
if (!/^[\d]+$/.test(value)) {
|
|
errorMessage = ls`Height must be a number.`;
|
|
} else if (Number(value) > MaxDeviceSize) {
|
|
errorMessage = ls`Height must be less than or equal to ${MaxDeviceSize}.`;
|
|
} else if (Number(value) < MinDeviceSize) {
|
|
errorMessage = ls`Height must be greater than or equal to ${MinDeviceSize}.`;
|
|
} else {
|
|
valid = true;
|
|
}
|
|
|
|
return {valid, errorMessage};
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @return {{valid: boolean, errorMessage: (string|undefined)}}
|
|
*/
|
|
static scaleValidator(value) {
|
|
let valid = false;
|
|
let errorMessage;
|
|
const parsedValue = Number(value.trim());
|
|
|
|
if (!value) {
|
|
valid = true;
|
|
} else if (Number.isNaN(parsedValue)) {
|
|
errorMessage = ls`Device pixel ratio must be a number or blank.`;
|
|
} else if (Number(value) > MaxDeviceScaleFactor) {
|
|
errorMessage = ls`Device pixel ratio must be less than or equal to ${MaxDeviceScaleFactor}.`;
|
|
} else if (Number(value) < MinDeviceScaleFactor) {
|
|
errorMessage = ls`Device pixel ratio must be greater than or equal to ${MinDeviceScaleFactor}.`;
|
|
} else {
|
|
valid = true;
|
|
}
|
|
|
|
return {valid, errorMessage};
|
|
}
|
|
|
|
/**
|
|
* @param {!UI.Geometry.Size} availableSize
|
|
* @param {!UI.Geometry.Size} preferredSize
|
|
*/
|
|
setAvailableSize(availableSize, preferredSize) {
|
|
this._availableSize = availableSize;
|
|
this._preferredSize = preferredSize;
|
|
this._initialized = true;
|
|
this._calculateAndEmulate(false);
|
|
}
|
|
|
|
/**
|
|
* @param {!Type} type
|
|
* @param {?EmulatedDevice} device
|
|
* @param {?Mode} mode
|
|
* @param {number=} scale
|
|
*/
|
|
emulate(type, device, mode, scale) {
|
|
const resetPageScaleFactor = this._type !== type || this._device !== device || this._mode !== mode;
|
|
this._type = type;
|
|
|
|
if (type === Type.Device && device && mode) {
|
|
console.assert(!!device && !!mode, 'Must pass device and mode for device emulation');
|
|
this._mode = mode;
|
|
this._device = device;
|
|
if (this._initialized) {
|
|
const orientation = device.orientationByName(mode.orientation);
|
|
this._scaleSetting.set(
|
|
scale ||
|
|
this._calculateFitScale(
|
|
orientation.width, orientation.height, this._currentOutline(), this._currentInsets()));
|
|
}
|
|
} else {
|
|
this._device = null;
|
|
this._mode = null;
|
|
}
|
|
|
|
if (type !== Type.None) {
|
|
Host.userMetrics.actionTaken(Host.UserMetrics.Action.DeviceModeEnabled);
|
|
}
|
|
this._calculateAndEmulate(resetPageScaleFactor);
|
|
}
|
|
|
|
/**
|
|
* @param {number} width
|
|
*/
|
|
setWidth(width) {
|
|
const max = Math.min(MaxDeviceSize, this._preferredScaledWidth());
|
|
width = Math.max(Math.min(width, max), 1);
|
|
this._widthSetting.set(width);
|
|
}
|
|
|
|
/**
|
|
* @param {number} width
|
|
*/
|
|
setWidthAndScaleToFit(width) {
|
|
width = Math.max(Math.min(width, MaxDeviceSize), 1);
|
|
this._scaleSetting.set(this._calculateFitScale(width, this._heightSetting.get()));
|
|
this._widthSetting.set(width);
|
|
}
|
|
|
|
/**
|
|
* @param {number} height
|
|
*/
|
|
setHeight(height) {
|
|
const max = Math.min(MaxDeviceSize, this._preferredScaledHeight());
|
|
height = Math.max(Math.min(height, max), 0);
|
|
if (height === this._preferredScaledHeight()) {
|
|
height = 0;
|
|
}
|
|
this._heightSetting.set(height);
|
|
}
|
|
|
|
/**
|
|
* @param {number} height
|
|
*/
|
|
setHeightAndScaleToFit(height) {
|
|
height = Math.max(Math.min(height, MaxDeviceSize), 0);
|
|
this._scaleSetting.set(this._calculateFitScale(this._widthSetting.get(), height));
|
|
this._heightSetting.set(height);
|
|
}
|
|
|
|
/**
|
|
* @param {number} scale
|
|
*/
|
|
setScale(scale) {
|
|
this._scaleSetting.set(scale);
|
|
}
|
|
|
|
/**
|
|
* @return {?EmulatedDevice}
|
|
*/
|
|
device() {
|
|
return this._device;
|
|
}
|
|
|
|
/**
|
|
* @return {?Mode}
|
|
*/
|
|
mode() {
|
|
return this._mode;
|
|
}
|
|
|
|
/**
|
|
* @return {!Type}
|
|
*/
|
|
type() {
|
|
return this._type;
|
|
}
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
screenImage() {
|
|
return (this._device && this._mode) ? this._device.modeImage(this._mode) : '';
|
|
}
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
outlineImage() {
|
|
return (this._device && this._mode && this._deviceOutlineSetting.get()) ? this._device.outlineImage(this._mode) :
|
|
'';
|
|
}
|
|
|
|
/**
|
|
* @return {?UI.Geometry.Rect}
|
|
*/
|
|
outlineRect() {
|
|
return this._outlineRect || null;
|
|
}
|
|
|
|
/**
|
|
* @return {!UI.Geometry.Rect}
|
|
*/
|
|
screenRect() {
|
|
return this._screenRect;
|
|
}
|
|
|
|
/**
|
|
* @return {!UI.Geometry.Rect}
|
|
*/
|
|
visiblePageRect() {
|
|
return this._visiblePageRect;
|
|
}
|
|
|
|
/**
|
|
* @return {number}
|
|
*/
|
|
scale() {
|
|
return this._scale;
|
|
}
|
|
|
|
/**
|
|
* @return {number}
|
|
*/
|
|
fitScale() {
|
|
return this._fitScale;
|
|
}
|
|
|
|
/**
|
|
* @return {!UI.Geometry.Size}
|
|
*/
|
|
appliedDeviceSize() {
|
|
return this._appliedDeviceSize;
|
|
}
|
|
|
|
/**
|
|
* @return {number}
|
|
*/
|
|
appliedDeviceScaleFactor() {
|
|
return this._appliedDeviceScaleFactor;
|
|
}
|
|
|
|
/**
|
|
* @return {!UA}
|
|
*/
|
|
appliedUserAgentType() {
|
|
return this._appliedUserAgentType;
|
|
}
|
|
|
|
/**
|
|
* @return {boolean}
|
|
*/
|
|
isFullHeight() {
|
|
return !this._heightSetting.get();
|
|
}
|
|
|
|
/**
|
|
* @return {boolean}
|
|
*/
|
|
_isMobile() {
|
|
switch (this._type) {
|
|
case Type.Device:
|
|
return this._device ? this._device.mobile() : false;
|
|
case Type.None:
|
|
return false;
|
|
case Type.Responsive:
|
|
return this._uaSetting.get() === UA.Mobile || this._uaSetting.get() === UA.MobileNoTouch;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @return {!Common.Settings.Setting<boolean>}
|
|
*/
|
|
enabledSetting() {
|
|
return Common.Settings.Settings.instance().createSetting('emulation.showDeviceMode', false);
|
|
}
|
|
|
|
/**
|
|
* @return {!Common.Settings.Setting<number>}
|
|
*/
|
|
scaleSetting() {
|
|
return this._scaleSetting;
|
|
}
|
|
|
|
/**
|
|
* @return {!Common.Settings.Setting<UA>}
|
|
*/
|
|
uaSetting() {
|
|
return this._uaSetting;
|
|
}
|
|
|
|
/**
|
|
* @return {!Common.Settings.Setting<number>}
|
|
*/
|
|
deviceScaleFactorSetting() {
|
|
return this._deviceScaleFactorSetting;
|
|
}
|
|
|
|
/**
|
|
* @return {!Common.Settings.Setting<boolean>}
|
|
*/
|
|
deviceOutlineSetting() {
|
|
return this._deviceOutlineSetting;
|
|
}
|
|
|
|
/**
|
|
* @return {!Common.Settings.Setting<boolean>}
|
|
*/
|
|
toolbarControlsEnabledSetting() {
|
|
return this._toolbarControlsEnabledSetting;
|
|
}
|
|
|
|
reset() {
|
|
this._deviceScaleFactorSetting.set(0);
|
|
this._scaleSetting.set(1);
|
|
this.setWidth(400);
|
|
this.setHeight(0);
|
|
this._uaSetting.set(UA.Mobile);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {!SDK.EmulationModel.EmulationModel} emulationModel
|
|
*/
|
|
modelAdded(emulationModel) {
|
|
if (!this._emulationModel && emulationModel.supportsDeviceEmulation()) {
|
|
this._emulationModel = emulationModel;
|
|
if (this._onModelAvailable) {
|
|
const callback = this._onModelAvailable;
|
|
this._onModelAvailable = null;
|
|
callback();
|
|
}
|
|
const resourceTreeModel = emulationModel.target().model(SDK.ResourceTreeModel.ResourceTreeModel);
|
|
if (resourceTreeModel) {
|
|
resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.FrameResized, this._onFrameChange, this);
|
|
resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.FrameNavigated, this._onFrameChange, this);
|
|
}
|
|
} else {
|
|
emulationModel.emulateTouch(this._touchEnabled, this._touchMobile);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {!SDK.EmulationModel.EmulationModel} emulationModel
|
|
*/
|
|
modelRemoved(emulationModel) {
|
|
if (this._emulationModel === emulationModel) {
|
|
this._emulationModel = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return {?string}
|
|
*/
|
|
inspectedURL() {
|
|
return this._emulationModel ? this._emulationModel.target().inspectedURL() : null;
|
|
}
|
|
|
|
_onFrameChange() {
|
|
const overlayModel = this._emulationModel ? this._emulationModel.overlayModel() : null;
|
|
if (!overlayModel) {
|
|
return;
|
|
}
|
|
|
|
this._showHingeIfApplicable(overlayModel);
|
|
}
|
|
|
|
_scaleSettingChanged() {
|
|
this._calculateAndEmulate(false);
|
|
}
|
|
|
|
_widthSettingChanged() {
|
|
this._calculateAndEmulate(false);
|
|
}
|
|
|
|
_heightSettingChanged() {
|
|
this._calculateAndEmulate(false);
|
|
}
|
|
|
|
_uaSettingChanged() {
|
|
this._calculateAndEmulate(true);
|
|
}
|
|
|
|
_deviceScaleFactorSettingChanged() {
|
|
this._calculateAndEmulate(false);
|
|
}
|
|
|
|
_deviceOutlineSettingChanged() {
|
|
this._calculateAndEmulate(false);
|
|
}
|
|
|
|
/**
|
|
* @return {number}
|
|
*/
|
|
_preferredScaledWidth() {
|
|
return Math.floor(this._preferredSize.width / (this._scaleSetting.get() || 1));
|
|
}
|
|
|
|
/**
|
|
* @return {number}
|
|
*/
|
|
_preferredScaledHeight() {
|
|
return Math.floor(this._preferredSize.height / (this._scaleSetting.get() || 1));
|
|
}
|
|
|
|
/**
|
|
* @return {!UI.Geometry.Insets}
|
|
*/
|
|
_currentOutline() {
|
|
let outline = new UI.Geometry.Insets(0, 0, 0, 0);
|
|
if (this._type !== Type.Device || !this._device || !this._mode) {
|
|
return outline;
|
|
}
|
|
const orientation = this._device.orientationByName(this._mode.orientation);
|
|
if (this._deviceOutlineSetting.get()) {
|
|
outline = orientation.outlineInsets || outline;
|
|
}
|
|
return outline;
|
|
}
|
|
|
|
/**
|
|
* @return {!UI.Geometry.Insets}
|
|
*/
|
|
_currentInsets() {
|
|
if (this._type !== Type.Device || !this._mode) {
|
|
return new UI.Geometry.Insets(0, 0, 0, 0);
|
|
}
|
|
return this._mode.insets;
|
|
}
|
|
|
|
/**
|
|
* @return {!Protocol.Emulation.ScreenOrientationType}
|
|
*/
|
|
_getScreenOrientationType() {
|
|
if (!this._mode) {
|
|
throw new Error('Mode required to get orientation type.');
|
|
}
|
|
switch (this._mode.orientation) {
|
|
case VerticalSpanned:
|
|
case Vertical:
|
|
return Protocol.Emulation.ScreenOrientationType.PortraitPrimary;
|
|
case HorizontalSpanned:
|
|
case Horizontal:
|
|
default:
|
|
return Protocol.Emulation.ScreenOrientationType.LandscapePrimary;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {boolean} resetPageScaleFactor
|
|
*/
|
|
_calculateAndEmulate(resetPageScaleFactor) {
|
|
if (!this._emulationModel) {
|
|
this._onModelAvailable = this._calculateAndEmulate.bind(this, resetPageScaleFactor);
|
|
}
|
|
const mobile = this._isMobile();
|
|
const overlayModel = this._emulationModel ? this._emulationModel.overlayModel() : null;
|
|
if (overlayModel) {
|
|
this._showHingeIfApplicable(overlayModel);
|
|
}
|
|
if (this._type === Type.Device && this._device && this._mode) {
|
|
const orientation = this._device.orientationByName(this._mode.orientation);
|
|
const outline = this._currentOutline();
|
|
const insets = this._currentInsets();
|
|
this._fitScale = this._calculateFitScale(orientation.width, orientation.height, outline, insets);
|
|
if (mobile) {
|
|
this._appliedUserAgentType = this._device.touch() ? UA.Mobile : UA.MobileNoTouch;
|
|
} else {
|
|
this._appliedUserAgentType = this._device.touch() ? UA.DesktopTouch : UA.Desktop;
|
|
}
|
|
this._applyDeviceMetrics(
|
|
new UI.Geometry.Size(orientation.width, orientation.height), insets, outline, this._scaleSetting.get(),
|
|
this._device.deviceScaleFactor, mobile, this._getScreenOrientationType(), resetPageScaleFactor,
|
|
this._webPlatformExperimentalFeaturesEnabled);
|
|
this._applyUserAgent(this._device.userAgent, this._device.userAgentMetadata);
|
|
this._applyTouch(this._device.touch(), mobile);
|
|
} else if (this._type === Type.None) {
|
|
this._fitScale = this._calculateFitScale(this._availableSize.width, this._availableSize.height);
|
|
this._appliedUserAgentType = UA.Desktop;
|
|
this._applyDeviceMetrics(
|
|
this._availableSize, new UI.Geometry.Insets(0, 0, 0, 0), new UI.Geometry.Insets(0, 0, 0, 0), 1, 0, mobile,
|
|
null, resetPageScaleFactor);
|
|
this._applyUserAgent('', null);
|
|
this._applyTouch(false, false);
|
|
} else if (this._type === Type.Responsive) {
|
|
let screenWidth = this._widthSetting.get();
|
|
if (!screenWidth || screenWidth > this._preferredScaledWidth()) {
|
|
screenWidth = this._preferredScaledWidth();
|
|
}
|
|
let screenHeight = this._heightSetting.get();
|
|
if (!screenHeight || screenHeight > this._preferredScaledHeight()) {
|
|
screenHeight = this._preferredScaledHeight();
|
|
}
|
|
const defaultDeviceScaleFactor = mobile ? defaultMobileScaleFactor : 0;
|
|
this._fitScale = this._calculateFitScale(this._widthSetting.get(), this._heightSetting.get());
|
|
this._appliedUserAgentType = this._uaSetting.get();
|
|
this._applyDeviceMetrics(
|
|
new UI.Geometry.Size(screenWidth, screenHeight), new UI.Geometry.Insets(0, 0, 0, 0),
|
|
new UI.Geometry.Insets(0, 0, 0, 0), this._scaleSetting.get(),
|
|
this._deviceScaleFactorSetting.get() || defaultDeviceScaleFactor, mobile,
|
|
screenHeight >= screenWidth ? Protocol.Emulation.ScreenOrientationType.PortraitPrimary :
|
|
Protocol.Emulation.ScreenOrientationType.LandscapePrimary,
|
|
resetPageScaleFactor);
|
|
this._applyUserAgent(mobile ? _defaultMobileUserAgent : '', mobile ? _defaultMobileUserAgentMetadata : null);
|
|
this._applyTouch(
|
|
this._uaSetting.get() === UA.DesktopTouch || this._uaSetting.get() === UA.Mobile,
|
|
this._uaSetting.get() === UA.Mobile);
|
|
}
|
|
|
|
if (overlayModel) {
|
|
overlayModel.setShowViewportSizeOnResize(this._type === Type.None);
|
|
}
|
|
this.dispatchEventToListeners(Events.Updated);
|
|
}
|
|
|
|
/**
|
|
* @param {number} screenWidth
|
|
* @param {number} screenHeight
|
|
* @param {!UI.Geometry.Insets=} outline
|
|
* @param {!UI.Geometry.Insets=} insets
|
|
* @return {number}
|
|
*/
|
|
_calculateFitScale(screenWidth, screenHeight, outline, insets) {
|
|
const outlineWidth = outline ? outline.left + outline.right : 0;
|
|
const outlineHeight = outline ? outline.top + outline.bottom : 0;
|
|
const insetsWidth = insets ? insets.left + insets.right : 0;
|
|
const insetsHeight = insets ? insets.top + insets.bottom : 0;
|
|
let scale = Math.min(
|
|
screenWidth ? this._preferredSize.width / (screenWidth + outlineWidth) : 1,
|
|
screenHeight ? this._preferredSize.height / (screenHeight + outlineHeight) : 1);
|
|
scale = Math.min(Math.floor(scale * 100), 100);
|
|
|
|
let sharpScale = scale;
|
|
while (sharpScale > scale * 0.7) {
|
|
let sharp = true;
|
|
if (screenWidth) {
|
|
sharp = sharp && Number.isInteger((screenWidth - insetsWidth) * sharpScale / 100);
|
|
}
|
|
if (screenHeight) {
|
|
sharp = sharp && Number.isInteger((screenHeight - insetsHeight) * sharpScale / 100);
|
|
}
|
|
if (sharp) {
|
|
return sharpScale / 100;
|
|
}
|
|
sharpScale -= 1;
|
|
}
|
|
return scale / 100;
|
|
}
|
|
|
|
/**
|
|
* @param {number} width
|
|
* @param {number} height
|
|
*/
|
|
setSizeAndScaleToFit(width, height) {
|
|
this._scaleSetting.set(this._calculateFitScale(width, height));
|
|
this.setWidth(width);
|
|
this.setHeight(height);
|
|
}
|
|
|
|
/**
|
|
* @param {string} userAgent
|
|
* @param {?Protocol.Emulation.UserAgentMetadata} userAgentMetadata
|
|
*/
|
|
_applyUserAgent(userAgent, userAgentMetadata) {
|
|
SDK.NetworkManager.MultitargetNetworkManager.instance().setUserAgentOverride(userAgent, userAgentMetadata);
|
|
}
|
|
|
|
/**
|
|
* @param {!UI.Geometry.Size} screenSize
|
|
* @param {!UI.Geometry.Insets} insets
|
|
* @param {!UI.Geometry.Insets} outline
|
|
* @param {number} scale
|
|
* @param {number} deviceScaleFactor
|
|
* @param {boolean} mobile
|
|
* @param {?Protocol.Emulation.ScreenOrientationType} screenOrientation
|
|
* @param {boolean} resetPageScaleFactor
|
|
* @param {boolean=} forceMetricsOverride
|
|
*/
|
|
_applyDeviceMetrics(
|
|
screenSize, insets, outline, scale, deviceScaleFactor, mobile, screenOrientation, resetPageScaleFactor,
|
|
forceMetricsOverride = false) {
|
|
screenSize.width = Math.max(1, Math.floor(screenSize.width));
|
|
screenSize.height = Math.max(1, Math.floor(screenSize.height));
|
|
|
|
let pageWidth = screenSize.width - insets.left - insets.right;
|
|
let pageHeight = screenSize.height - insets.top - insets.bottom;
|
|
this._emulatedPageSize = new UI.Geometry.Size(pageWidth, pageHeight);
|
|
|
|
const positionX = insets.left;
|
|
const positionY = insets.top;
|
|
const screenOrientationAngle =
|
|
screenOrientation === Protocol.Emulation.ScreenOrientationType.LandscapePrimary ? 90 : 0;
|
|
|
|
this._appliedDeviceSize = screenSize;
|
|
this._appliedDeviceScaleFactor = deviceScaleFactor || window.devicePixelRatio;
|
|
this._screenRect = new UI.Geometry.Rect(
|
|
Math.max(0, (this._availableSize.width - screenSize.width * scale) / 2), outline.top * scale,
|
|
screenSize.width * scale, screenSize.height * scale);
|
|
this._outlineRect = new UI.Geometry.Rect(
|
|
this._screenRect.left - outline.left * scale, 0, (outline.left + screenSize.width + outline.right) * scale,
|
|
(outline.top + screenSize.height + outline.bottom) * scale);
|
|
this._visiblePageRect = new UI.Geometry.Rect(
|
|
positionX * scale, positionY * scale,
|
|
Math.min(pageWidth * scale, this._availableSize.width - this._screenRect.left - positionX * scale),
|
|
Math.min(pageHeight * scale, this._availableSize.height - this._screenRect.top - positionY * scale));
|
|
this._scale = scale;
|
|
if (!forceMetricsOverride) {
|
|
// When sending displayFeature, we cannot use the optimization below due to backend restrictions.
|
|
if (scale === 1 && this._availableSize.width >= screenSize.width &&
|
|
this._availableSize.height >= screenSize.height) {
|
|
// When we have enough space, no page size override is required. This will speed things up and remove lag.
|
|
pageWidth = 0;
|
|
pageHeight = 0;
|
|
}
|
|
if (this._visiblePageRect.width === pageWidth * scale && this._visiblePageRect.height === pageHeight * scale &&
|
|
Number.isInteger(pageWidth * scale) && Number.isInteger(pageHeight * scale)) {
|
|
// When we only have to apply scale, do not resize the page. This will speed things up and remove lag.
|
|
pageWidth = 0;
|
|
pageHeight = 0;
|
|
}
|
|
}
|
|
|
|
if (!this._emulationModel) {
|
|
return;
|
|
}
|
|
|
|
if (resetPageScaleFactor) {
|
|
this._emulationModel.resetPageScaleFactor();
|
|
}
|
|
if (pageWidth || pageHeight || mobile || deviceScaleFactor || scale !== 1 || screenOrientation ||
|
|
forceMetricsOverride) {
|
|
/** @type {!Protocol.Emulation.SetDeviceMetricsOverrideRequest} */
|
|
const metrics = {
|
|
width: pageWidth,
|
|
height: pageHeight,
|
|
deviceScaleFactor: deviceScaleFactor,
|
|
mobile: mobile,
|
|
scale: scale,
|
|
screenWidth: screenSize.width,
|
|
screenHeight: screenSize.height,
|
|
positionX: positionX,
|
|
positionY: positionY,
|
|
dontSetVisibleSize: true,
|
|
displayFeature: undefined,
|
|
screenOrientation: undefined,
|
|
};
|
|
const displayFeature = this._getDisplayFeature();
|
|
if (displayFeature) {
|
|
metrics.displayFeature = displayFeature;
|
|
}
|
|
if (screenOrientation) {
|
|
metrics.screenOrientation = {type: screenOrientation, angle: screenOrientationAngle};
|
|
}
|
|
this._emulationModel.emulateDevice(metrics);
|
|
} else {
|
|
this._emulationModel.emulateDevice(null);
|
|
}
|
|
}
|
|
|
|
exitHingeMode() {
|
|
const overlayModel = this._emulationModel ? this._emulationModel.overlayModel() : null;
|
|
if (overlayModel) {
|
|
overlayModel.showHingeForDualScreen(null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return {boolean}
|
|
*/
|
|
webPlatformExperimentalFeaturesEnabled() {
|
|
return this._webPlatformExperimentalFeaturesEnabled;
|
|
}
|
|
|
|
/**
|
|
* @return {boolean}
|
|
*/
|
|
shouldReportDisplayFeature() {
|
|
return this._webPlatformExperimentalFeaturesEnabled && this._experimentDualScreenSupport;
|
|
}
|
|
|
|
/**
|
|
* @param {boolean} fullSize
|
|
* @param {!Protocol.Page.Viewport=} clip
|
|
* @return {!Promise<?string>}
|
|
*/
|
|
async captureScreenshot(fullSize, clip) {
|
|
const screenCaptureModel =
|
|
this._emulationModel ? this._emulationModel.target().model(SDK.ScreenCaptureModel.ScreenCaptureModel) : null;
|
|
if (!screenCaptureModel) {
|
|
return null;
|
|
}
|
|
|
|
const overlayModel = this._emulationModel ? this._emulationModel.overlayModel() : null;
|
|
if (overlayModel) {
|
|
overlayModel.setShowViewportSizeOnResize(false);
|
|
}
|
|
|
|
// Emulate full size device if necessary.
|
|
if (fullSize) {
|
|
const metrics = await screenCaptureModel.fetchLayoutMetrics();
|
|
if (!metrics) {
|
|
return null;
|
|
}
|
|
|
|
// Cap the height to not hit the GPU limit.
|
|
const contentHeight = Math.min((1 << 14) / this._appliedDeviceScaleFactor, metrics.contentHeight);
|
|
/** @type {!Protocol.Emulation.SetDeviceMetricsOverrideRequest} */
|
|
const deviceMetrics = {
|
|
width: Math.floor(metrics.contentWidth),
|
|
height: Math.floor(contentHeight),
|
|
deviceScaleFactor: this._appliedDeviceScaleFactor,
|
|
mobile: this._isMobile(),
|
|
displayFeature: undefined,
|
|
screenOrientation: undefined,
|
|
};
|
|
|
|
const displayFeature = this._getDisplayFeature();
|
|
if (displayFeature) {
|
|
deviceMetrics.displayFeature = displayFeature;
|
|
}
|
|
|
|
clip = {x: 0, y: 0, width: deviceMetrics.width, height: deviceMetrics.height, scale: 1};
|
|
|
|
if (this._device && this._mode) {
|
|
const screenOrientation = this._mode.orientation === Horizontal ?
|
|
Protocol.Emulation.ScreenOrientationType.LandscapePrimary :
|
|
Protocol.Emulation.ScreenOrientationType.PortraitPrimary;
|
|
const screenOrientationAngle =
|
|
screenOrientation === Protocol.Emulation.ScreenOrientationType.LandscapePrimary ? 90 : 0;
|
|
deviceMetrics.screenOrientation = {type: screenOrientation, angle: screenOrientationAngle};
|
|
}
|
|
if (this._emulationModel) {
|
|
await this._emulationModel.resetPageScaleFactor();
|
|
await this._emulationModel.emulateDevice(deviceMetrics);
|
|
}
|
|
}
|
|
const screenshot =
|
|
await screenCaptureModel.captureScreenshot(Protocol.Page.CaptureScreenshotRequestFormat.Png, 100, clip);
|
|
|
|
/** @type {!Protocol.Page.SetDeviceMetricsOverrideRequest} */
|
|
const deviceMetrics = {
|
|
width: 0,
|
|
height: 0,
|
|
deviceScaleFactor: 0,
|
|
mobile: false,
|
|
};
|
|
if (fullSize && this._emulationModel) {
|
|
if (this._device && this._mode) {
|
|
const orientation = this._device.orientationByName(this._mode.orientation);
|
|
deviceMetrics.width = orientation.width;
|
|
deviceMetrics.height = orientation.height;
|
|
const dispFeature = this._getDisplayFeature();
|
|
if (dispFeature) {
|
|
// @ts-ignore: displayFeature isn't in protocol.d.ts but is an
|
|
// experimental flag:
|
|
// https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setDeviceMetricsOverride
|
|
deviceMetrics.displayFeature = dispFeature;
|
|
}
|
|
} else {
|
|
deviceMetrics.width = 0;
|
|
deviceMetrics.height = 0;
|
|
}
|
|
await this._emulationModel.emulateDevice(deviceMetrics);
|
|
}
|
|
this._calculateAndEmulate(false);
|
|
return screenshot;
|
|
}
|
|
|
|
/**
|
|
* @param {boolean} touchEnabled
|
|
* @param {boolean} mobile
|
|
*/
|
|
_applyTouch(touchEnabled, mobile) {
|
|
this._touchEnabled = touchEnabled;
|
|
this._touchMobile = mobile;
|
|
for (const emulationModel of SDK.SDKModel.TargetManager.instance().models(SDK.EmulationModel.EmulationModel)) {
|
|
emulationModel.emulateTouch(touchEnabled, mobile);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.OverlayModel.OverlayModel} overlayModel
|
|
*/
|
|
_showHingeIfApplicable(overlayModel) {
|
|
const orientation = (this._device && this._mode) ? this._device.orientationByName(this._mode.orientation) : null;
|
|
if (this._experimentDualScreenSupport && orientation && orientation.hinge) {
|
|
overlayModel.showHingeForDualScreen(orientation.hinge);
|
|
return;
|
|
}
|
|
|
|
overlayModel.showHingeForDualScreen(null);
|
|
}
|
|
|
|
/**
|
|
* @return {!Protocol.Emulation.DisplayFeatureOrientation}
|
|
*/
|
|
_getDisplayFeatureOrientation() {
|
|
if (!this._mode) {
|
|
throw new Error('Mode required to get display feature orientation.');
|
|
}
|
|
switch (this._mode.orientation) {
|
|
case VerticalSpanned:
|
|
case Vertical:
|
|
return Protocol.Emulation.DisplayFeatureOrientation.Vertical;
|
|
case HorizontalSpanned:
|
|
case Horizontal:
|
|
default:
|
|
return Protocol.Emulation.DisplayFeatureOrientation.Horizontal;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return {?Protocol.Emulation.DisplayFeature}
|
|
*/
|
|
_getDisplayFeature() {
|
|
if (!this.shouldReportDisplayFeature()) {
|
|
return null;
|
|
}
|
|
|
|
if (!this._device || !this._mode ||
|
|
(this._mode.orientation !== VerticalSpanned && this._mode.orientation !== HorizontalSpanned)) {
|
|
return null;
|
|
}
|
|
|
|
const orientation = this._device.orientationByName(this._mode.orientation);
|
|
if (!orientation || !orientation.hinge) {
|
|
return null;
|
|
}
|
|
|
|
const hinge = orientation.hinge;
|
|
return {
|
|
orientation: this._getDisplayFeatureOrientation(),
|
|
offset: (this._mode.orientation === VerticalSpanned) ? hinge.x : hinge.y,
|
|
maskLength: (this._mode.orientation === VerticalSpanned) ? hinge.width : hinge.height
|
|
};
|
|
}
|
|
}
|
|
|
|
/** @enum {string} */
|
|
export const Events = {
|
|
Updated: 'Updated'
|
|
};
|
|
|
|
/** @enum {string} */
|
|
export const Type = {
|
|
None: 'None',
|
|
Responsive: 'Responsive',
|
|
Device: 'Device'
|
|
};
|
|
|
|
/** @enum {string} */
|
|
export const UA = {
|
|
Mobile: Common.UIString.UIString('Mobile'),
|
|
MobileNoTouch: Common.UIString.UIString('Mobile (no touch)'),
|
|
Desktop: Common.UIString.UIString('Desktop'),
|
|
DesktopTouch: Common.UIString.UIString('Desktop (touch)')
|
|
};
|
|
|
|
export const MinDeviceSize = 50;
|
|
export const MaxDeviceSize = 9999;
|
|
export const MinDeviceScaleFactor = 0;
|
|
export const MaxDeviceScaleFactor = 10;
|
|
export const MaxDeviceNameLength = 50;
|
|
|
|
const _mobileUserAgent =
|
|
'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36';
|
|
export const _defaultMobileUserAgent =
|
|
SDK.NetworkManager.MultitargetNetworkManager.patchUserAgentWithChromeVersion(_mobileUserAgent);
|
|
|
|
export const _defaultMobileUserAgentMetadata = {
|
|
brands: SDK.NetworkManager.MultitargetNetworkManager.getChromeBrands(),
|
|
fullVersion: SDK.NetworkManager.MultitargetNetworkManager.getChromeVersion(),
|
|
platform: 'Android',
|
|
platformVersion: '6.0',
|
|
architecture: '',
|
|
model: 'Nexus 5',
|
|
mobile: true
|
|
};
|
|
export const defaultMobileScaleFactor = 2;
|