blob: caf8827a7764b8052dde6f9a527f31c924b8b6e5 [file] [log] [blame]
// Copyright 2014 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.
/**
* @unrestricted
*/
Emulation.EmulatedDevice = class {
constructor() {
/** @type {string} */
this.title = '';
/** @type {string} */
this.type = Emulation.EmulatedDevice.Type.Unknown;
/** @type {!Emulation.EmulatedDevice.Orientation} */
this.vertical = {width: 0, height: 0, outlineInsets: null, outlineImage: null};
/** @type {!Emulation.EmulatedDevice.Orientation} */
this.horizontal = {width: 0, height: 0, outlineInsets: null, outlineImage: null};
/** @type {number} */
this.deviceScaleFactor = 1;
/** @type {!Array.<string>} */
this.capabilities = [Emulation.EmulatedDevice.Capability.Touch, Emulation.EmulatedDevice.Capability.Mobile];
/** @type {string} */
this.userAgent = '';
/** @type {!Array.<!Emulation.EmulatedDevice.Mode>} */
this.modes = [];
/** @type {string} */
this._show = Emulation.EmulatedDevice._Show.Default;
/** @type {boolean} */
this._showByDefault = true;
/** @type {?Runtime.Extension} */
this._extension = null;
}
/**
* @param {*} json
* @return {?Emulation.EmulatedDevice}
*/
static fromJSONV1(json) {
try {
/**
* @param {*} object
* @param {string} key
* @param {string} type
* @param {*=} defaultValue
* @return {*}
*/
function parseValue(object, key, type, defaultValue) {
if (typeof object !== 'object' || object === null || !object.hasOwnProperty(key)) {
if (typeof defaultValue !== 'undefined')
return defaultValue;
throw new Error('Emulated device is missing required property \'' + key + '\'');
}
var value = object[key];
if (typeof value !== type || value === null)
throw new Error('Emulated device property \'' + key + '\' has wrong type \'' + typeof value + '\'');
return value;
}
/**
* @param {*} object
* @param {string} key
* @return {number}
*/
function parseIntValue(object, key) {
var value = /** @type {number} */ (parseValue(object, key, 'number'));
if (value !== Math.abs(value))
throw new Error('Emulated device value \'' + key + '\' must be integer');
return value;
}
/**
* @param {*} json
* @return {!UI.Insets}
*/
function parseInsets(json) {
return new UI.Insets(
parseIntValue(json, 'left'), parseIntValue(json, 'top'), parseIntValue(json, 'right'),
parseIntValue(json, 'bottom'));
}
/**
* @param {*} json
* @return {!Emulation.EmulatedDevice.Orientation}
*/
function parseOrientation(json) {
var result = {};
result.width = parseIntValue(json, 'width');
if (result.width < 0 || result.width > Emulation.DeviceModeModel.MaxDeviceSize ||
result.width < Emulation.DeviceModeModel.MinDeviceSize)
throw new Error('Emulated device has wrong width: ' + result.width);
result.height = parseIntValue(json, 'height');
if (result.height < 0 || result.height > Emulation.DeviceModeModel.MaxDeviceSize ||
result.height < Emulation.DeviceModeModel.MinDeviceSize)
throw new Error('Emulated device has wrong height: ' + result.height);
var outlineInsets = parseValue(json['outline'], 'insets', 'object', null);
if (outlineInsets) {
result.outlineInsets = parseInsets(outlineInsets);
if (result.outlineInsets.left < 0 || result.outlineInsets.top < 0)
throw new Error('Emulated device has wrong outline insets');
result.outlineImage = /** @type {string} */ (parseValue(json['outline'], 'image', 'string'));
}
return /** @type {!Emulation.EmulatedDevice.Orientation} */ (result);
}
var result = new Emulation.EmulatedDevice();
result.title = /** @type {string} */ (parseValue(json, 'title', 'string'));
result.type = /** @type {string} */ (parseValue(json, 'type', 'string'));
var rawUserAgent = /** @type {string} */ (parseValue(json, 'user-agent', 'string'));
result.userAgent = SDK.MultitargetNetworkManager.patchUserAgentWithChromeVersion(rawUserAgent);
var capabilities = parseValue(json, 'capabilities', 'object', []);
if (!Array.isArray(capabilities))
throw new Error('Emulated device capabilities must be an array');
result.capabilities = [];
for (var i = 0; i < capabilities.length; ++i) {
if (typeof capabilities[i] !== 'string')
throw new Error('Emulated device capability must be a string');
result.capabilities.push(capabilities[i]);
}
result.deviceScaleFactor = /** @type {number} */ (parseValue(json['screen'], 'device-pixel-ratio', 'number'));
if (result.deviceScaleFactor < 0 || result.deviceScaleFactor > 100)
throw new Error('Emulated device has wrong deviceScaleFactor: ' + result.deviceScaleFactor);
result.vertical = parseOrientation(parseValue(json['screen'], 'vertical', 'object'));
result.horizontal = parseOrientation(parseValue(json['screen'], 'horizontal', 'object'));
var modes = parseValue(json, 'modes', 'object', []);
if (!Array.isArray(modes))
throw new Error('Emulated device modes must be an array');
result.modes = [];
for (var i = 0; i < modes.length; ++i) {
var mode = {};
mode.title = /** @type {string} */ (parseValue(modes[i], 'title', 'string'));
mode.orientation = /** @type {string} */ (parseValue(modes[i], 'orientation', 'string'));
if (mode.orientation !== Emulation.EmulatedDevice.Vertical &&
mode.orientation !== Emulation.EmulatedDevice.Horizontal)
throw new Error('Emulated device mode has wrong orientation \'' + mode.orientation + '\'');
var orientation = result.orientationByName(mode.orientation);
mode.insets = parseInsets(parseValue(modes[i], 'insets', 'object'));
if (mode.insets.top < 0 || mode.insets.left < 0 || mode.insets.right < 0 || mode.insets.bottom < 0 ||
mode.insets.top + mode.insets.bottom > orientation.height ||
mode.insets.left + mode.insets.right > orientation.width)
throw new Error('Emulated device mode \'' + mode.title + '\'has wrong mode insets');
mode.image = /** @type {string} */ (parseValue(modes[i], 'image', 'string', null));
result.modes.push(mode);
}
result._showByDefault = /** @type {boolean} */ (parseValue(json, 'show-by-default', 'boolean', undefined));
result._show =
/** @type {string} */ (parseValue(json, 'show', 'string', Emulation.EmulatedDevice._Show.Default));
return result;
} catch (e) {
return null;
}
}
/**
* @param {!Emulation.EmulatedDevice} device1
* @param {!Emulation.EmulatedDevice} device2
* @return {number}
*/
static deviceComparator(device1, device2) {
var order1 = (device1._extension && device1._extension.descriptor()['order']) || -1;
var order2 = (device2._extension && device2._extension.descriptor()['order']) || -1;
if (order1 > order2)
return 1;
if (order2 > order1)
return -1;
return device1.title < device2.title ? -1 : (device1.title > device2.title ? 1 : 0);
}
/**
* @return {?Runtime.Extension}
*/
extension() {
return this._extension;
}
/**
* @param {?Runtime.Extension} extension
*/
setExtension(extension) {
this._extension = extension;
}
/**
* @param {string} orientation
* @return {!Array.<!Emulation.EmulatedDevice.Mode>}
*/
modesForOrientation(orientation) {
var result = [];
for (var index = 0; index < this.modes.length; index++) {
if (this.modes[index].orientation === orientation)
result.push(this.modes[index]);
}
return result;
}
/**
* @return {*}
*/
_toJSON() {
var json = {};
json['title'] = this.title;
json['type'] = this.type;
json['user-agent'] = this.userAgent;
json['capabilities'] = this.capabilities;
json['screen'] = {};
json['screen']['device-pixel-ratio'] = this.deviceScaleFactor;
json['screen']['vertical'] = this._orientationToJSON(this.vertical);
json['screen']['horizontal'] = this._orientationToJSON(this.horizontal);
json['modes'] = [];
for (var i = 0; i < this.modes.length; ++i) {
var mode = {};
mode['title'] = this.modes[i].title;
mode['orientation'] = this.modes[i].orientation;
mode['insets'] = {};
mode['insets']['left'] = this.modes[i].insets.left;
mode['insets']['top'] = this.modes[i].insets.top;
mode['insets']['right'] = this.modes[i].insets.right;
mode['insets']['bottom'] = this.modes[i].insets.bottom;
if (this.modes[i].image)
mode['image'] = this.modes[i].image;
json['modes'].push(mode);
}
json['show-by-default'] = this._showByDefault;
json['show'] = this._show;
return json;
}
/**
* @param {!Emulation.EmulatedDevice.Orientation} orientation
* @return {*}
*/
_orientationToJSON(orientation) {
var json = {};
json['width'] = orientation.width;
json['height'] = orientation.height;
if (orientation.outlineInsets) {
json['outline'] = {};
json['outline']['insets'] = {};
json['outline']['insets']['left'] = orientation.outlineInsets.left;
json['outline']['insets']['top'] = orientation.outlineInsets.top;
json['outline']['insets']['right'] = orientation.outlineInsets.right;
json['outline']['insets']['bottom'] = orientation.outlineInsets.bottom;
json['outline']['image'] = orientation.outlineImage;
}
return json;
}
/**
* @param {!Emulation.EmulatedDevice.Mode} mode
* @return {string}
*/
modeImage(mode) {
if (!mode.image)
return '';
if (!this._extension)
return mode.image;
return this._extension.module().substituteURL(mode.image);
}
/**
* @param {!Emulation.EmulatedDevice.Mode} mode
* @return {string}
*/
outlineImage(mode) {
var orientation = this.orientationByName(mode.orientation);
if (!orientation.outlineImage)
return '';
if (!this._extension)
return orientation.outlineImage;
return this._extension.module().substituteURL(orientation.outlineImage);
}
/**
* @param {string} name
* @return {!Emulation.EmulatedDevice.Orientation}
*/
orientationByName(name) {
return name === Emulation.EmulatedDevice.Vertical ? this.vertical : this.horizontal;
}
/**
* @return {boolean}
*/
show() {
if (this._show === Emulation.EmulatedDevice._Show.Default)
return this._showByDefault;
return this._show === Emulation.EmulatedDevice._Show.Always;
}
/**
* @param {boolean} show
*/
setShow(show) {
this._show = show ? Emulation.EmulatedDevice._Show.Always : Emulation.EmulatedDevice._Show.Never;
}
/**
* @param {!Emulation.EmulatedDevice} other
*/
copyShowFrom(other) {
this._show = other._show;
}
/**
* @return {boolean}
*/
touch() {
return this.capabilities.indexOf(Emulation.EmulatedDevice.Capability.Touch) !== -1;
}
/**
* @return {boolean}
*/
mobile() {
return this.capabilities.indexOf(Emulation.EmulatedDevice.Capability.Mobile) !== -1;
}
};
/** @typedef {!{title: string, orientation: string, insets: !UI.Insets, image: ?string}} */
Emulation.EmulatedDevice.Mode;
/** @typedef {!{width: number, height: number, outlineInsets: ?UI.Insets, outlineImage: ?string}} */
Emulation.EmulatedDevice.Orientation;
Emulation.EmulatedDevice.Horizontal = 'horizontal';
Emulation.EmulatedDevice.Vertical = 'vertical';
Emulation.EmulatedDevice.Type = {
Phone: 'phone',
Tablet: 'tablet',
Notebook: 'notebook',
Desktop: 'desktop',
Unknown: 'unknown'
};
Emulation.EmulatedDevice.Capability = {
Touch: 'touch',
Mobile: 'mobile'
};
Emulation.EmulatedDevice._Show = {
Always: 'Always',
Default: 'Default',
Never: 'Never'
};
/**
* @unrestricted
*/
Emulation.EmulatedDevicesList = class extends Common.Object {
constructor() {
super();
/** @type {!Common.Setting} */
this._standardSetting = Common.settings.createSetting('standardEmulatedDeviceList', []);
/** @type {!Array.<!Emulation.EmulatedDevice>} */
this._standard = [];
this._listFromJSONV1(this._standardSetting.get(), this._standard);
this._updateStandardDevices();
/** @type {!Common.Setting} */
this._customSetting = Common.settings.createSetting('customEmulatedDeviceList', []);
/** @type {!Array.<!Emulation.EmulatedDevice>} */
this._custom = [];
if (!this._listFromJSONV1(this._customSetting.get(), this._custom))
this.saveCustomDevices();
}
/**
* @return {!Emulation.EmulatedDevicesList}
*/
static instance() {
if (!Emulation.EmulatedDevicesList._instance)
Emulation.EmulatedDevicesList._instance = new Emulation.EmulatedDevicesList();
return /** @type {!Emulation.EmulatedDevicesList} */ (Emulation.EmulatedDevicesList._instance);
}
_updateStandardDevices() {
var devices = [];
var extensions = self.runtime.extensions('emulated-device');
for (var i = 0; i < extensions.length; ++i) {
var device = Emulation.EmulatedDevice.fromJSONV1(extensions[i].descriptor()['device']);
device.setExtension(extensions[i]);
devices.push(device);
}
this._copyShowValues(this._standard, devices);
this._standard = devices;
this.saveStandardDevices();
}
/**
* @param {!Array.<*>} jsonArray
* @param {!Array.<!Emulation.EmulatedDevice>} result
* @return {boolean}
*/
_listFromJSONV1(jsonArray, result) {
if (!Array.isArray(jsonArray))
return false;
var success = true;
for (var i = 0; i < jsonArray.length; ++i) {
var device = Emulation.EmulatedDevice.fromJSONV1(jsonArray[i]);
if (device) {
result.push(device);
if (!device.modes.length) {
device.modes.push({
title: '',
orientation: Emulation.EmulatedDevice.Horizontal,
insets: new UI.Insets(0, 0, 0, 0),
image: null
});
device.modes.push({
title: '',
orientation: Emulation.EmulatedDevice.Vertical,
insets: new UI.Insets(0, 0, 0, 0),
image: null
});
}
} else {
success = false;
}
}
return success;
}
/**
* @return {!Array.<!Emulation.EmulatedDevice>}
*/
standard() {
return this._standard;
}
/**
* @return {!Array.<!Emulation.EmulatedDevice>}
*/
custom() {
return this._custom;
}
revealCustomSetting() {
Common.Revealer.reveal(this._customSetting);
}
/**
* @param {!Emulation.EmulatedDevice} device
*/
addCustomDevice(device) {
this._custom.push(device);
this.saveCustomDevices();
}
/**
* @param {!Emulation.EmulatedDevice} device
*/
removeCustomDevice(device) {
this._custom.remove(device);
this.saveCustomDevices();
}
saveCustomDevices() {
var json = this._custom.map(/** @param {!Emulation.EmulatedDevice} device */ function(device) {
return device._toJSON();
});
this._customSetting.set(json);
this.dispatchEventToListeners(Emulation.EmulatedDevicesList.Events.CustomDevicesUpdated);
}
saveStandardDevices() {
var json = this._standard.map(/** @param {!Emulation.EmulatedDevice} device */ function(device) {
return device._toJSON();
});
this._standardSetting.set(json);
this.dispatchEventToListeners(Emulation.EmulatedDevicesList.Events.StandardDevicesUpdated);
}
/**
* @param {!Array.<!Emulation.EmulatedDevice>} from
* @param {!Array.<!Emulation.EmulatedDevice>} to
*/
_copyShowValues(from, to) {
var deviceById = new Map();
for (var i = 0; i < from.length; ++i)
deviceById.set(from[i].title, from[i]);
for (var i = 0; i < to.length; ++i) {
var title = to[i].title;
if (deviceById.has(title))
to[i].copyShowFrom(/** @type {!Emulation.EmulatedDevice} */ (deviceById.get(title)));
}
}
};
/** @enum {symbol} */
Emulation.EmulatedDevicesList.Events = {
CustomDevicesUpdated: Symbol('CustomDevicesUpdated'),
StandardDevicesUpdated: Symbol('StandardDevicesUpdated')
};
/** @type {?Emulation.EmulatedDevicesList} */
Emulation.EmulatedDevicesList._instance;