| // 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; |