UNPKG

monaca-lib

Version:

Monaca cloud API bindings for JavaScript

670 lines (590 loc) 22.1 kB
// 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. /** * @constructor */ WebInspector.EmulatedDevice = function() { /** @type {string} */ this.title = ""; /** @type {string} */ this.type = WebInspector.EmulatedDevice.Type.Unknown; /** @type {!WebInspector.EmulatedDevice.Orientation} */ this.vertical = {width: 0, height: 0, outlineInsets: null, outlineImages: null}; /** @type {!WebInspector.EmulatedDevice.Orientation} */ this.horizontal = {width: 0, height: 0, outlineInsets: null, outlineImages: null}; /** @type {number} */ this.deviceScaleFactor = 1; /** @type {!Array.<string>} */ this.capabilities = [WebInspector.EmulatedDevice.Capability.Touch, WebInspector.EmulatedDevice.Capability.Mobile]; /** @type {string} */ this.userAgent = ""; /** @type {!Array.<!WebInspector.EmulatedDevice.Mode>} */ this.modes = []; /** @type {string} */ this._show = WebInspector.EmulatedDevice._Show.Default; /** @type {boolean} */ this._showByDefault = true; } /** @typedef {!{title: string, orientation: string, pageRect: !WebInspector.Geometry.Rect, images: !WebInspector.EmulatedDevice.Images}} */ WebInspector.EmulatedDevice.Mode; /** @typedef {!{width: number, height: number, outlineInsets: ?WebInspector.Geometry.Insets, outlineImages: ?WebInspector.EmulatedDevice.Images}} */ WebInspector.EmulatedDevice.Orientation; WebInspector.EmulatedDevice.Horizontal = "horizontal"; WebInspector.EmulatedDevice.Vertical = "vertical"; WebInspector.EmulatedDevice.Type = { Phone: "phone", Tablet: "tablet", Notebook: "notebook", Desktop: "desktop", Unknown: "unknown" } WebInspector.EmulatedDevice.Capability = { Touch: "touch", Mobile: "mobile" } WebInspector.EmulatedDevice._Show = { Always: "Always", Default: "Default", Never: "Never" } /** * @param {*} json * @return {?WebInspector.EmulatedDevice} */ WebInspector.EmulatedDevice.fromJSONV1 = function(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 {!WebInspector.Geometry.Rect} */ function parseIntRect(json) { var result = {}; result.top = parseIntValue(json, "top"); result.left = parseIntValue(json, "left"); result.width = parseIntValue(json, "width"); result.height = parseIntValue(json, "height"); return /** @type {!WebInspector.Geometry.Rect} */ (result); } /** * @param {*} json * @return {?WebInspector.Geometry.Insets} */ function parseIntInsets(json) { if (json === null) return null; var result = {}; result.top = parseIntValue(json, "top"); result.left = parseIntValue(json, "left"); return /** @type {?WebInspector.Geometry.Insets} */ (result); } /** * @param {*} json * @return {!WebInspector.EmulatedDevice.Images} */ function parseImages(json) { if (!Array.isArray(json)) throw new Error("Emulated device images is not an array"); var result = new WebInspector.EmulatedDevice.Images(); for (var i = 0; i < json.length; ++i) { var src = /** @type {string} */ (parseValue(json[i], "src", "string")); var scale = /** @type {number} */ (parseValue(json[i], "scale", "number")); if (scale <= 0) throw new Error("Emulated device property image scale must be positive"); result.addSource(src, scale); } return result; } /** * @param {*} json * @return {!WebInspector.EmulatedDevice.Orientation} */ function parseOrientation(json) { var result = {}; result.width = parseIntValue(json, "width"); if (result.width < 0 || result.width > WebInspector.OverridesSupport.MaxDeviceSize) throw new Error("Emulated device has wrong width: " + result.width); result.height = parseIntValue(json, "height"); if (result.height < 0 || result.height > WebInspector.OverridesSupport.MaxDeviceSize) throw new Error("Emulated device has wrong height: " + result.height); result.outlineInsets = parseIntInsets(parseValue(json["outline"], "insets", "object", null)); if (result.outlineInsets) { if (result.outlineInsets.left < 0 || result.outlineInsets.top < 0) throw new Error("Emulated device has wrong outline insets"); result.outlineImages = parseImages(parseValue(json["outline"], "images", "object")); } return /** @type {!WebInspector.EmulatedDevice.Orientation} */ (result); } var result = new WebInspector.EmulatedDevice(); result.title = /** @type {string} */ (parseValue(json, "title", "string")); result.type = /** @type {string} */ (parseValue(json, "type", "string")); result.userAgent = /** @type {string} */ (parseValue(json, "user-agent", "string")); 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 !== WebInspector.EmulatedDevice.Vertical && mode.orientation !== WebInspector.EmulatedDevice.Horizontal) throw new Error("Emulated device mode has wrong orientation '" + mode.orientation + "'"); var orientation = result.orientationByName(mode.orientation); mode.pageRect = parseIntRect(parseValue(modes[i], "page-rect", "object")); if (mode.pageRect.top < 0 || mode.pageRect.left < 0 || mode.pageRect.width < 0 || mode.pageRect.height < 0 || mode.pageRect.top + mode.pageRect.height > orientation.height || mode.pageRect.left + mode.pageRect.width > orientation.width) { throw new Error("Emulated device mode '" + mode.title + "'has wrong page rect"); } mode.images = parseImages(parseValue(modes[i], "images", "object")); result.modes.push(mode); } result._showByDefault = /** @type {boolean} */ (parseValue(json, "show-by-default", "boolean", true)); result._show = /** @type {string} */ (parseValue(json, "show", "string", WebInspector.EmulatedDevice._Show.Default)); return result; } catch (e) { WebInspector.console.error("Failed to update emulated device list. " + String(e)); return null; } } /** * @param {!WebInspector.OverridesSupport.Device} device * @param {string} title * @param {string=} type * @return {!WebInspector.EmulatedDevice} */ WebInspector.EmulatedDevice.fromOverridesDevice = function(device, title, type) { var result = new WebInspector.EmulatedDevice(); result.title = title; result.type = type || WebInspector.EmulatedDevice.Type.Unknown; result.vertical.width = device.width; result.vertical.height = device.height; result.horizontal.width = device.height; result.horizontal.height = device.width; result.deviceScaleFactor = device.deviceScaleFactor; result.userAgent = device.userAgent; if (device.touch) result.capabilities.push(WebInspector.EmulatedDevice.Capability.Touch); if (device.mobile) result.capabilities.push(WebInspector.EmulatedDevice.Capability.Mobile); return result; } /** * @param {!WebInspector.EmulatedDevice} device1 * @param {!WebInspector.EmulatedDevice} device2 * @return {number} */ WebInspector.EmulatedDevice.compareByTitle = function(device1, device2) { return device1.title < device2.title ? -1 : (device1.title > device2.title ? 1 : 0); } WebInspector.EmulatedDevice.prototype = { /** * @return {*} */ _toJSON: function() { 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["page-rect"] = this.modes[i].pageRect; mode["images"] = this.modes[i].images._toJSON(); json["modes"].push(mode); } json["show-by-default"] = this._showByDefault; json["show"] = this._show; return json; }, /** * @param {!WebInspector.EmulatedDevice.Orientation} orientation * @return {*} */ _orientationToJSON: function(orientation) { var json = {}; json["width"] = orientation.width; json["height"] = orientation.height; if (orientation.outlineInsets) { json["outline"] = {}; json["outline"]["insets"] = orientation.outlineInsets; json["outline"]["images"] = orientation.outlineImages._toJSON(); } return json; }, /** * @return {!WebInspector.OverridesSupport.Device} */ toOverridesDevice: function() { var result = {}; result.width = this.vertical.width; result.height = this.vertical.height; result.deviceScaleFactor = this.deviceScaleFactor; result.userAgent = this.userAgent; result.touch = this.touch(); result.mobile = this.mobile(); return result; }, /** * @param {string} name * @return {!WebInspector.EmulatedDevice.Orientation} */ orientationByName: function(name) { return name === WebInspector.EmulatedDevice.Vertical ? this.vertical : this.horizontal; }, /** * @return {boolean} */ show: function() { if (this._show === WebInspector.EmulatedDevice._Show.Default) return this._showByDefault; return this._show === WebInspector.EmulatedDevice._Show.Always; }, /** * @param {boolean} show */ setShow: function(show) { this._show = show ? WebInspector.EmulatedDevice._Show.Always : WebInspector.EmulatedDevice._Show.Never; }, /** * @param {!WebInspector.EmulatedDevice} other */ copyShowFrom: function(other) { this._show = other._show; }, /** * @return {boolean} */ touch: function() { return this.capabilities.indexOf(WebInspector.EmulatedDevice.Capability.Touch) !== -1; }, /** * @return {boolean} */ mobile: function() { return this.capabilities.indexOf(WebInspector.EmulatedDevice.Capability.Mobile) !== -1; } } /** * @constructor * @extends {WebInspector.Object} */ WebInspector.EmulatedDevice.Images = function() { WebInspector.Object.call(this); this._sources = []; this._scales = []; } WebInspector.EmulatedDevice.Images.Events = { Update: "Update" } WebInspector.EmulatedDevice.Images.prototype = { /** * @return {*} */ _toJSON: function() { var result = []; for (var i = 0; i < this._sources.length; ++i) result.push({src: this._sources[i], scale: this._scales[i]}); return result; }, /** * @param {string} src * @param {number} scale */ addSource: function(src, scale) { this._sources.push(src); this._scales.push(scale); }, __proto__: WebInspector.Object.prototype } /** * @constructor * @extends {WebInspector.Object} */ WebInspector.EmulatedDevicesList = function() { WebInspector.Object.call(this); /** * @param {!Array.<*>} list * @param {string} type * @return {!Array.<*>} */ function convert(list, type) { var result = []; for (var i = 0; i < list.length; ++i) { var device = WebInspector.EmulatedDevice.fromOverridesDevice(/** @type {!WebInspector.OverridesSupport.Device} */ (list[i]), list[i].title, type); result.push(device._toJSON()); } return result; } // FIXME: shrink default list once external list is good enough. var defaultValue = convert(WebInspector.OverridesUI._phones, "phone") .concat(convert(WebInspector.OverridesUI._tablets, "tablet")) .concat(convert(WebInspector.OverridesUI._notebooks, "notebook")); /** @type {!WebInspector.Setting} */ this._standardSetting = WebInspector.settings.createSetting("standardEmulatedDeviceList", defaultValue); /** @type {!Array.<!WebInspector.EmulatedDevice>} */ this._standard = this._listFromJSONV1(this._standardSetting.get()); /** @type {!WebInspector.Setting} */ this._customSetting = WebInspector.settings.createSetting("customEmulatedDeviceList", []); /** @type {!Array.<!WebInspector.EmulatedDevice>} */ this._custom = this._listFromJSONV1(this._customSetting.get()); /** @type {!WebInspector.Setting} */ this._lastUpdatedSetting = WebInspector.settings.createSetting("lastUpdatedDeviceList", null); /** @type {boolean} */ this._updating = false; } WebInspector.EmulatedDevicesList.Events = { CustomDevicesUpdated: "CustomDevicesUpdated", IsUpdatingChanged: "IsUpdatingChanged", StandardDevicesUpdated: "StandardDevicesUpdated" } WebInspector.EmulatedDevicesList._DevicesJsonUrl = "https://api.github.com/repos/GoogleChrome/devtools-device-data/contents/devices.json?ref=release"; WebInspector.EmulatedDevicesList._UpdateIntervalMs = 24 * 60 * 60 * 1000; WebInspector.EmulatedDevicesList.prototype = { /** * @param {!Array.<*>} jsonArray * @return {!Array.<!WebInspector.EmulatedDevice>} */ _listFromJSONV1: function(jsonArray) { var result = []; if (!Array.isArray(jsonArray)) return result; for (var i = 0; i < jsonArray.length; ++i) { var device = WebInspector.EmulatedDevice.fromJSONV1(jsonArray[i]); if (device) result.push(device); } return result; }, /** * @return {!Array.<!WebInspector.EmulatedDevice>} */ standard: function() { return this._standard; }, /** * @return {!Array.<!WebInspector.EmulatedDevice>} */ custom: function() { return this._custom; }, /** * @param {!WebInspector.EmulatedDevice} device */ addCustomDevice: function(device) { this._custom.push(device); this.saveCustomDevices(); }, /** * @param {!WebInspector.EmulatedDevice} device */ removeCustomDevice: function(device) { this._custom.remove(device); this.saveCustomDevices(); }, saveCustomDevices: function() { var json = this._custom.map(/** @param {!WebInspector.EmulatedDevice} device */ function(device) { return device._toJSON(); }); this._customSetting.set(json); this.dispatchEventToListeners(WebInspector.EmulatedDevicesList.Events.CustomDevicesUpdated); }, saveStandardDevices: function() { var json = this._standard.map(/** @param {!WebInspector.EmulatedDevice} device */ function(device) { return device._toJSON(); }); this._standardSetting.set(json); this.dispatchEventToListeners(WebInspector.EmulatedDevicesList.Events.StandardDevicesUpdated); }, update: function() { if (this._updating) return; this._updating = true; this.dispatchEventToListeners(WebInspector.EmulatedDevicesList.Events.IsUpdatingChanged); /** * @param {*} json * @return {!Promise.<string>} */ function decodeBase64Content(json) { return loadXHR("data:application/json;charset=utf-8;base64," + json.content); } /** * FIXME: promise chain below does not compile with JSON.parse. * @return {*} */ function myJsonParse(json) { return JSON.parse(json); } loadXHR(WebInspector.EmulatedDevicesList._DevicesJsonUrl) .then(JSON.parse) .then(decodeBase64Content) .then(myJsonParse) .then(this._parseUpdatedDevices.bind(this)) .then(this._updateFinished.bind(this)) .catch(this._updateFailed.bind(this)); }, maybeAutoUpdate: function() { if (!Runtime.experiments.isEnabled("externalDeviceList")) return; var lastUpdated = this._lastUpdatedSetting.get(); if (lastUpdated && (Date.now() - lastUpdated < WebInspector.EmulatedDevicesList._UpdateIntervalMs)) return; this.update(); }, /** * @param {*} json */ _parseUpdatedDevices: function(json) { if (!json || typeof json !== "object") { WebInspector.console.error("Malfromed device list"); return; } if (!("version" in json) || typeof json["version"] !== "number") { WebInspector.console.error("Device list does not specify version"); return; } var version = json["version"]; if (version === 1) { this._parseDevicesV1(json); return; } WebInspector.console.error("Unsupported device list version '" + version + "'"); }, /** * @param {*} json */ _parseDevicesV1: function(json) { if (!("devices" in json)) { WebInspector.console.error("Malfromed device list"); return; } var devices = json["devices"]; if (!Array.isArray(devices)) { WebInspector.console.error("Malfromed device list"); return; } devices = this._listFromJSONV1(devices); this._copyShowValues(this._standard, devices); this._standard = devices; this.saveStandardDevices(); WebInspector.console.log("Device list updated successfully"); }, _updateFailed: function() { WebInspector.console.error("Cannot update device list"); this._updateFinished(); }, _updateFinished: function() { this._updating = false; this._lastUpdatedSetting.set(Date.now()); this.dispatchEventToListeners(WebInspector.EmulatedDevicesList.Events.IsUpdatingChanged); }, /** * @param {!Array.<!WebInspector.EmulatedDevice>} from * @param {!Array.<!WebInspector.EmulatedDevice>} to */ _copyShowValues: function(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(deviceById.get(title)); } }, /** * @return {boolean} */ isUpdating: function() { return this._updating; }, __proto__: WebInspector.Object.prototype } /** @type {!WebInspector.EmulatedDevicesList} */ WebInspector.emulatedDevicesList;