blob: 365bd28b408da7779d9b671d75921aafe62e80bc [file] [log] [blame]
// 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.
/**
* @implements {SDK.SDKModelObserver<!SDK.EmulationModel>}
* @extends {Common.Object}
* @unrestricted
*/
export default class DeviceModeModel extends Common.Object {
constructor() {
super();
this._screenRect = new UI.Rect(0, 0, 1, 1);
this._visiblePageRect = new UI.Rect(0, 0, 1, 1);
this._availableSize = new UI.Size(1, 1);
this._preferredSize = new UI.Size(1, 1);
this._initialized = false;
this._appliedDeviceSize = new UI.Size(1, 1);
this._appliedDeviceScaleFactor = window.devicePixelRatio;
this._appliedUserAgentType = UA.Desktop;
this._scaleSetting = Common.settings.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._widthSetting = Common.settings.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.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.createSetting('emulation.deviceUA', UA.Mobile);
this._uaSetting.addChangeListener(this._uaSettingChanged, this);
this._deviceScaleFactorSetting = Common.settings.createSetting('emulation.deviceScaleFactor', 0);
this._deviceScaleFactorSetting.addChangeListener(this._deviceScaleFactorSettingChanged, this);
this._deviceOutlineSetting = Common.settings.moduleSetting('emulation.showDeviceOutline');
this._deviceOutlineSetting.addChangeListener(this._deviceOutlineSettingChanged, this);
this._toolbarControlsEnabledSetting =
Common.settings.createSetting('emulation.toolbarControlsEnabled', true, Common.SettingStorageType.Session);
/** @type {!Type} */
this._type = Type.None;
/** @type {?Emulation.EmulatedDevice} */
this._device = null;
/** @type {?Emulation.EmulatedDevice.Mode} */
this._mode = null;
/** @type {number} */
this._fitScale = 1;
this._touchEnabled = false;
this._touchMobile = false;
/** @type {?SDK.EmulationModel} */
this._emulationModel = null;
/** @type {?function()} */
this._onModelAvailable = null;
SDK.targetManager.observeModels(SDK.EmulationModel, this);
}
/**
* @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 (value > MaxDeviceSize) {
errorMessage = ls`Width must be less than or equal to ${MaxDeviceSize}.`;
} else if (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 (value > MaxDeviceSize) {
errorMessage = ls`Height must be less than or equal to ${MaxDeviceSize}.`;
} else if (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 (value > MaxDeviceScaleFactor) {
errorMessage = ls`Device pixel ratio must be less than or equal to ${MaxDeviceScaleFactor}.`;
} else if (value < MinDeviceScaleFactor) {
errorMessage = ls`Device pixel ratio must be greater than or equal to ${MinDeviceScaleFactor}.`;
} else {
valid = true;
}
return {valid, errorMessage};
}
/**
* @param {!UI.Size} availableSize
* @param {!UI.Size} preferredSize
*/
setAvailableSize(availableSize, preferredSize) {
this._availableSize = availableSize;
this._preferredSize = preferredSize;
this._initialized = true;
this._calculateAndEmulate(false);
}
/**
* @param {!Type} type
* @param {?Emulation.EmulatedDevice} device
* @param {?Emulation.EmulatedDevice.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) {
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 {?Emulation.EmulatedDevice}
*/
device() {
return this._device;
}
/**
* @return {?Emulation.EmulatedDevice.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.Rect}
*/
outlineRect() {
return this._outlineRect;
}
/**
* @return {!UI.Rect}
*/
screenRect() {
return this._screenRect;
}
/**
* @return {!UI.Rect}
*/
visiblePageRect() {
return this._visiblePageRect;
}
/**
* @return {number}
*/
scale() {
return this._scale;
}
/**
* @return {number}
*/
fitScale() {
return this._fitScale;
}
/**
* @return {!UI.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.mobile();
case Type.None:
return false;
case Type.Responsive:
return this._uaSetting.get() === UA.Mobile || this._uaSetting.get() === UA.MobileNoTouch;
}
return false;
}
/**
* @return {!Common.Setting}
*/
enabledSetting() {
return Common.settings.createSetting('emulation.showDeviceMode', false);
}
/**
* @return {!Common.Setting}
*/
scaleSetting() {
return this._scaleSetting;
}
/**
* @return {!Common.Setting}
*/
uaSetting() {
return this._uaSetting;
}
/**
* @return {!Common.Setting}
*/
deviceScaleFactorSetting() {
return this._deviceScaleFactorSetting;
}
/**
* @return {!Common.Setting}
*/
deviceOutlineSetting() {
return this._deviceOutlineSetting;
}
/**
* @return {!Common.Setting}
*/
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
*/
modelAdded(emulationModel) {
if (!this._emulationModel && emulationModel.supportsDeviceEmulation()) {
this._emulationModel = emulationModel;
if (this._onModelAvailable) {
const callback = this._onModelAvailable;
this._onModelAvailable = null;
callback();
}
} else {
emulationModel.emulateTouch(this._touchEnabled, this._touchMobile);
}
}
/**
* @override
* @param {!SDK.EmulationModel} emulationModel
*/
modelRemoved(emulationModel) {
if (this._emulationModel === emulationModel) {
this._emulationModel = null;
}
}
/**
* @return {?string}
*/
inspectedURL() {
return this._emulationModel ? this._emulationModel.target().inspectedURL() : null;
}
_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.Insets}
*/
_currentOutline() {
let outline = new UI.Insets(0, 0, 0, 0);
if (this._type !== Type.Device) {
return outline;
}
const orientation = this._device.orientationByName(this._mode.orientation);
if (this._deviceOutlineSetting.get()) {
outline = orientation.outlineInsets || outline;
}
return outline;
}
/**
* @return {!UI.Insets}
*/
_currentInsets() {
if (this._type !== Type.Device) {
return new UI.Insets(0, 0, 0, 0);
}
return this._mode.insets;
}
/**
* @param {boolean} resetPageScaleFactor
*/
_calculateAndEmulate(resetPageScaleFactor) {
if (!this._emulationModel) {
this._onModelAvailable = this._calculateAndEmulate.bind(this, resetPageScaleFactor);
}
const mobile = this._isMobile();
if (this._type === Type.Device) {
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.Size(orientation.width, orientation.height), insets, outline, this._scaleSetting.get(),
this._device.deviceScaleFactor, mobile, this._mode.orientation === Emulation.EmulatedDevice.Horizontal ?
Protocol.Emulation.ScreenOrientationType.LandscapePrimary :
Protocol.Emulation.ScreenOrientationType.PortraitPrimary,
resetPageScaleFactor);
this._applyUserAgent(this._device.userAgent);
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.Insets(0, 0, 0, 0), new UI.Insets(0, 0, 0, 0), 1, 0, mobile, null,
resetPageScaleFactor);
this._applyUserAgent('');
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.Size(screenWidth, screenHeight), new UI.Insets(0, 0, 0, 0), new UI.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 : '');
this._applyTouch(
this._uaSetting.get() === UA.DesktopTouch || this._uaSetting.get() === UA.Mobile,
this._uaSetting.get() === UA.Mobile);
}
const overlayModel = this._emulationModel ? this._emulationModel.overlayModel() : null;
if (overlayModel) {
overlayModel.setShowViewportSizeOnResize(this._type === Type.None);
}
this.dispatchEventToListeners(Events.Updated);
}
/**
* @param {number} screenWidth
* @param {number} screenHeight
* @param {!UI.Insets=} outline
* @param {!UI.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
*/
_applyUserAgent(userAgent) {
SDK.multitargetNetworkManager.setUserAgentOverride(userAgent);
}
/**
* @param {!UI.Size} screenSize
* @param {!UI.Insets} insets
* @param {!UI.Insets} outline
* @param {number} scale
* @param {number} deviceScaleFactor
* @param {boolean} mobile
* @param {?Protocol.Emulation.ScreenOrientationType} screenOrientation
* @param {boolean} resetPageScaleFactor
*/
_applyDeviceMetrics(
screenSize,
insets,
outline,
scale,
deviceScaleFactor,
mobile,
screenOrientation,
resetPageScaleFactor) {
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.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.Rect(
Math.max(0, (this._availableSize.width - screenSize.width * scale) / 2), outline.top * scale,
screenSize.width * scale, screenSize.height * scale);
this._outlineRect = new UI.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.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 (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) {
const metrics = {
width: pageWidth,
height: pageHeight,
deviceScaleFactor: deviceScaleFactor,
mobile: mobile,
scale: scale,
screenWidth: screenSize.width,
screenHeight: screenSize.height,
positionX: positionX,
positionY: positionY,
dontSetVisibleSize: true
};
if (screenOrientation) {
metrics.screenOrientation = {type: screenOrientation, angle: screenOrientationAngle};
}
this._emulationModel.emulateDevice(metrics);
} else {
this._emulationModel.emulateDevice(null);
}
}
/**
* @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) : null;
if (!screenCaptureModel) {
return null;
}
const overlayModel = this._emulationModel ? this._emulationModel.overlayModel() : null;
if (overlayModel) {
overlayModel.setShowViewportSizeOnResize(false);
}
// Emulate full size device if necessary.
let deviceMetrics;
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);
deviceMetrics = {
width: Math.floor(metrics.contentWidth),
height: Math.floor(contentHeight),
deviceScaleFactor: this._appliedDeviceScaleFactor,
mobile: this._isMobile(),
};
clip = {x: 0, y: 0, width: deviceMetrics.width, height: deviceMetrics.height, scale: 1};
if (this._device) {
const screenOrientation = this._mode.orientation === Emulation.EmulatedDevice.Horizontal ?
Protocol.Emulation.ScreenOrientationType.LandscapePrimary :
Protocol.Emulation.ScreenOrientationType.PortraitPrimary;
const screenOrientationAngle =
screenOrientation === Protocol.Emulation.ScreenOrientationType.LandscapePrimary ? 90 : 0;
deviceMetrics.screenOrientation = {type: screenOrientation, angle: screenOrientationAngle};
}
await this._emulationModel.resetPageScaleFactor();
await this._emulationModel.emulateDevice(deviceMetrics);
}
const screenshot = await screenCaptureModel.captureScreenshot('png', 100, clip);
if (fullSize) {
if (this._device) {
const orientation = this._device.orientationByName(this._mode.orientation);
deviceMetrics.width = orientation.width;
deviceMetrics.height = orientation.height;
} 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.targetManager.models(SDK.EmulationModel)) {
emulationModel.emulateTouch(touchEnabled, mobile);
}
}
}
/** @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('Mobile'),
MobileNoTouch: Common.UIString('Mobile (no touch)'),
Desktop: Common.UIString('Desktop'),
DesktopTouch: Common.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.MultitargetNetworkManager.patchUserAgentWithChromeVersion(_mobileUserAgent);
export const defaultMobileScaleFactor = 2;
/* Legacy exported object */
self.Emulation = self.Emulation || {};
/* Legacy exported object */
Emulation = Emulation || {};
/**
* @constructor
*/
Emulation.DeviceModeModel = DeviceModeModel;
/** @enum {string} */
Emulation.DeviceModeModel.Events = Events;
/** @enum {string} */
Emulation.DeviceModeModel.Type = Type;
/** @enum {string} */
Emulation.DeviceModeModel.UA = UA;
Emulation.DeviceModeModel.MinDeviceSize = MinDeviceSize;
Emulation.DeviceModeModel.MaxDeviceSize = MaxDeviceSize;
Emulation.DeviceModeModel.MinDeviceScaleFactor = MinDeviceScaleFactor;
Emulation.DeviceModeModel.MaxDeviceScaleFactor = MaxDeviceScaleFactor;
Emulation.DeviceModeModel.MaxDeviceNameLength = MaxDeviceNameLength;
Emulation.DeviceModeModel._defaultMobileUserAgent = _defaultMobileUserAgent;
Emulation.DeviceModeModel.defaultMobileScaleFactor = defaultMobileScaleFactor;