| // Copyright 2016 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // Top level callback function that can be called by any component to create |
| // a Runtime.RemoteObject from a JS object passed in from C++. |
| devtoolsBackend.createRemoteObjectCallback = function(object, params) { |
| var remoteObject = null; |
| try { |
| remoteObject = new devtoolsBackend.runtime.RemoteObject( |
| object, params.objectGroup, params.returnByValue); |
| } catch(e) { |
| console.log('Exception creating remote object: ' + e); |
| } |
| |
| var json = ""; |
| try { |
| json = JSON.stringify(remoteObject); |
| } catch(e) { |
| console.log('Exception stringifying remote object: ' + e) |
| } |
| return json; |
| } |
| |
| // JavaScript functions used by the Chrome debugging protocol Runtime domain: |
| // https://developer.chrome.com/devtools/docs/protocol/1.1/runtime |
| |
| devtoolsBackend.runtime = {}; |
| |
| // Calls a function on a previously accessed RemoteObject with an argument list |
| // and returns a new RemoteObject. Used extensively by devtools for |
| // auto-completion. The new RemoteObject uses the same |objectGroup| as the |
| // original object. |
| // https://developer.chrome.com/devtools/docs/protocol/1.1/runtime#command-callFunctionOn |
| devtoolsBackend.runtime.callFunctionOn = function(params) { |
| var result = {}; |
| var value = null; |
| try { |
| eval('var f = ' + params.functionDeclaration); |
| var objectEntry = this._objectStore[params.objectId]; |
| var thisArg = objectEntry.object; |
| var objectGroup = objectEntry.objectGroup; |
| value = f.apply(thisArg, params.arguments); |
| result.wasThrown = false; |
| } catch(e) { |
| value = e; |
| result.exceptionDetails = e; |
| result.wasThrown = true; |
| } |
| |
| result.result = |
| new this.RemoteObject(value, objectGroup, params.returnByValue); |
| return JSON.stringify(result); |
| } |
| |
| // Evaluates a string and returns a RemoteObject. |
| // https://developer.chrome.com/devtools/docs/protocol/1.1/runtime#command-evaluate |
| devtoolsBackend.runtime.evaluate = function(params) { |
| var result = {}; |
| var value = null; |
| try { |
| if (params.includeCommandLineAPI) { |
| this._addCommandLineAPI(); |
| } |
| |
| // Use |eval| indirectly, to cause evaluation at global scope. |
| // This is so subsequent calls can access variables declared here, etc. |
| var geval = eval; |
| value = geval(params.expression); |
| |
| // Store the last result if we're doing a "real" evaluation, not an |
| // auto-complete. Seems a little special case-y, but this is taken from |
| // Chrome's implementation, and looks like the only way to differentiate. |
| if (params.objectGroup == 'console') { |
| this._lastResult = value; |
| } |
| |
| result.wasThrown = false; |
| } catch(e) { |
| value = e; |
| result.exceptionDetails = e; |
| result.wasThrown = true; |
| } |
| |
| // Create the RemoteObject corresponding to the result. |
| result.result = |
| new this.RemoteObject(value, params.objectGroup, params.returnByValue); |
| |
| // Add the preview, if requested. |
| if (params.generatePreview) { |
| var preview = this.generatePreview(value); |
| if (preview) { |
| result.result.preview = preview; |
| } |
| } |
| |
| if (params.includeCommandLineAPI) { |
| this._removeCommandLineAPI(); |
| } |
| |
| return JSON.stringify(result); |
| } |
| |
| // Returns the properties of a previously accessed object as an array of |
| // PropertyDescriptor objects. |
| // The parameters specifify several options: |
| // * ownProperties - only include immediate properties, not the prototype chain. |
| // * accessorPropertiesOnly - only include accessor properties. |
| // https://developer.chrome.com/devtools/docs/protocol/1.1/runtime#runtime.getproperties |
| devtoolsBackend.runtime.getProperties = function(params) { |
| var result = {}; |
| var properties = []; |
| try { |
| var objectEntry = this._objectStore[params.objectId]; |
| var object = objectEntry.object; |
| var objectGroup = objectEntry.objectGroup; |
| this.addProperties(object, objectGroup, !params.ownProperties, properties); |
| |
| if (params.accessorPropertiesOnly) { |
| properties = properties.filter(function(element, index, array) { |
| return (element.get || element.set); |
| }); |
| } |
| result.wasThrown = false; |
| } catch(e) { |
| value = e; |
| result.exceptionDetails = e; |
| result.wasThrown = true; |
| } |
| |
| result.result = properties; |
| return JSON.stringify(result); |
| } |
| |
| // Releases our reference to a previously accessed object. |
| // https://developer.chrome.com/devtools/docs/protocol/1.1/runtime#command-releaseObject |
| devtoolsBackend.runtime.releaseObject = function(params) { |
| delete this._objectStore[params.objectId]; |
| } |
| |
| // Releases our references to a group of previously accessed objects. |
| // https://developer.chrome.com/devtools/docs/protocol/1.1/runtime#command-releaseObjectGroup |
| devtoolsBackend.runtime.releaseObjectGroup = function(params) { |
| for (var objectId in this._objectStore) { |
| var objectEntry = this._objectStore[objectId]; |
| if (objectEntry && objectEntry.objectGroup == params.objectGroup) { |
| delete this._objectStore[objectId]; |
| } |
| } |
| } |
| |
| // Adds the properties of |object| to the |result| array as new |
| // PropertyDescriptor objects. Some properties may be objects themselves, |
| // in which case new RemoteObjects are created using the specified |
| // |objectGroup|, which should be that of |object|. |
| // If |includePrototype| is set, the function will be called recursively on |
| // the prototype chain of the object. |
| devtoolsBackend.runtime.addProperties = |
| function(object, objectGroup, includePrototype, result) { |
| var properties = Object.getOwnPropertyNames(object); |
| var foundProto = false; |
| for (var i = 0; i < properties.length; i++) { |
| var key = properties[i]; |
| foundProto = foundProto || (key == '__proto__'); |
| |
| // If we can't find the property, and its name corresponds to a number, |
| // try it as an array index (integer instead of string). |
| if (object[key] == null) { |
| if (!isNaN(key)) { |
| key = parseInt(key); |
| } else { |
| continue; |
| } |
| } |
| |
| var propertyDescriptor = |
| new this.PropertyDescriptor(object, objectGroup, key); |
| result.push(propertyDescriptor); |
| } |
| |
| var proto = null; |
| try { |
| proto = Object.getPrototypeOf(object); |
| } catch (e) {} |
| |
| if (includePrototype) { |
| // Recursively add the properties from the prototype chain. |
| if (proto) { |
| this.addProperties(proto, objectGroup, includePrototype, result); |
| } |
| } else if (proto && !foundProto) { |
| // |getOwnPropertyNames| may not include the object prototype, |
| // so if that's the case, add it now. It's a deprecated name, but devtools |
| // still uses it. |
| var propertyDescriptor = |
| new this.PropertyDescriptor(object, objectGroup, '__proto__'); |
| result.push(propertyDescriptor); |
| } |
| } |
| |
| // Gets an object from the internal object store. |
| devtoolsBackend.runtime.getObject = function(objectId) { |
| return this._objectStore[objectId].object; |
| } |
| |
| // Adds an object to the internal object store and returns a unique id that can |
| // be used to access it again. |
| devtoolsBackend.runtime.addObject = function(object, objectGroup) { |
| // If we've already added this object, then use the same objectId. |
| for (var objectId in this._objectStore) { |
| var objectEntry = this._objectStore[objectId]; |
| if (objectEntry.object === object && |
| objectEntry.objectGroup == objectGroup) { |
| return objectId; |
| } |
| } |
| |
| var objectId = this._nextObjectId.toString(); |
| this._nextObjectId += 1; |
| this._objectStore[objectId] = {}; |
| this._objectStore[objectId].object = object; |
| this._objectStore[objectId].objectGroup = objectGroup; |
| return objectId; |
| } |
| |
| // Generates an object preview, which may be requested for the evaluate |
| // command. |
| devtoolsBackend.runtime.generatePreview = function(object) { |
| if (!object || (typeof object != 'object')) { |
| return null; |
| } else { |
| return new this.ObjectPreview(object); |
| } |
| } |
| |
| // Returns the subtype of an object, or null if the specified value is not an |
| // object. |
| devtoolsBackend.runtime.getSubtype = function(object) { |
| if (typeof object == 'object') { |
| if (object instanceof Array) { |
| return 'array'; |
| } else if (object instanceof Date) { |
| return 'date'; |
| } else if (object instanceof Error) { |
| return 'error'; |
| } else if (object instanceof Node) { |
| return 'node'; |
| } else if (object instanceof RegExp) { |
| return 'regexp'; |
| } |
| } |
| return null; |
| } |
| |
| // Tries to get the classname of an object by following the prototype chain |
| // and looking for a constructor. |
| devtoolsBackend.runtime.getClassName = function(object) { |
| try { |
| for (var obj = object; obj && !this.className; |
| obj = Object.getPrototypeOf(obj)) { |
| if (obj.constructor) { |
| return obj.constructor.name; |
| } |
| } |
| } catch(e) {} |
| |
| return null; |
| } |
| |
| // Creates a RemoteObject, which is the type used to return many values to |
| // devtools. If |value| is an object, then is it inserted into |
| // |devtoolsBackend.runtime._objectStore| and the |objectId| key used to |
| // access it is included in the RemoteObject. If |value| is not an object, |
| // or |returnByValue| is true, then |value| is directly included in the |
| // RemoteObject. |
| devtoolsBackend.runtime.RemoteObject = function(value, objectGroup, |
| returnByValue) { |
| this.type = typeof value; |
| |
| if (value == null) { |
| this.subtype == 'null'; |
| this.value = null; |
| return; |
| } |
| |
| if (this.type == 'object') { |
| this.objectId = devtoolsBackend.runtime.addObject(value, objectGroup); |
| this.subtype = devtoolsBackend.runtime.getSubtype(value); |
| this.className = devtoolsBackend.runtime.getClassName(value); |
| } |
| |
| // Fill in the description field. Devtools will only display arrays |
| // correctly if their description follows a particular format. For other |
| // values, try to use the generic string conversion, and fall back to the |
| // className if that fails. |
| if (this.subtype == 'array') { |
| this.description = 'Array[' + value.length + ']'; |
| } else { |
| try { |
| this.description = value.toString(); |
| } catch(e) { |
| this.description = this.className; |
| } |
| } |
| |
| if (returnByValue || this.type != 'object') { |
| this.value = value; |
| } |
| } |
| |
| // Creates a PropertyDescriptor for |property| of |object|, |
| // which is the type used to return object properties to devtools. |
| // Some properties may be objects, in which case new RemoteObjects are created |
| // and inserted into |devtoolsBackend.runtime._objectStore| using the specified |
| // |objectGroup|, which should be that of |object|. |
| devtoolsBackend.runtime.PropertyDescriptor = function(object, objectGroup, |
| property) { |
| this.name = property.toString(); |
| var descriptor = Object.getOwnPropertyDescriptor(object, property); |
| // Some Cobalt objects don't seem to support |getOwnPropertyDescriptor|, |
| // so we handle that case in the else clause below. |
| if (descriptor) { |
| this.configurable = descriptor.configurable; |
| this.enumerable = descriptor.enumerable; |
| if (descriptor.get) { |
| this.get = new devtoolsBackend.runtime.RemoteObject(descriptor.get, |
| objectGroup, |
| false); |
| } |
| if (descriptor.set) { |
| this.set = new devtoolsBackend.runtime.RemoteObject(descriptor.set, |
| objectGroup, |
| false); |
| } |
| if (descriptor.value != null) { |
| this.value = new devtoolsBackend.runtime.RemoteObject(descriptor.value, |
| objectGroup, |
| false); |
| } |
| this.writable = descriptor.writable; |
| } else if (object[property] != null) { |
| this.configurable = false; |
| this.enumerable = object.propertyIsEnumerable(property); |
| if (object.__lookupGetter__(property)) { |
| this.get = object.__lookupGetter__(property); |
| } |
| if (object.__lookupSetter__(property)) { |
| this.set = object.__lookupSetter__(property); |
| } |
| this.value = new devtoolsBackend.runtime.RemoteObject(object[property], |
| objectGroup, false); |
| } |
| } |
| |
| // Creates an ObjectPreview, the type to represent a preview of an object, |
| // which may be requested by devtools in the evaluate command. |
| devtoolsBackend.runtime.ObjectPreview = function(value) { |
| this.type = typeof value; |
| this.subtype = devtoolsBackend.runtime.getSubtype(value); |
| this.lossless = true; |
| this.overflow = false; |
| this.properties = []; |
| |
| // Use the className as the preview description. This matches Chrome. |
| this.description = devtoolsBackend.runtime.getClassName(value); |
| |
| // If we have an array-like object, add the array items, or append the |
| // length to the description if there's too many. |
| if (value.length != null) { |
| var MAX_ARRAY_ITEMS = 99; |
| if (value.length <= MAX_ARRAY_ITEMS) { |
| for (var i = 0; i < value.length; i++) { |
| var property = new devtoolsBackend.runtime.PropertyPreview(i, value[i]); |
| this.properties.push(property); |
| if (typeof value[i] == 'object') { |
| this.lossless = false; |
| } |
| } |
| } else { |
| this.description += '[' + value.length + ']'; |
| this.lossless = false; |
| this.overflow = true; |
| } |
| return; |
| } |
| |
| // Add object properties, up to a maximum. |
| var MAX_PROPERTIES = 5; |
| var numProperties = 0; |
| for (var name in value) { |
| if (value[name] != null) { |
| if (++numProperties > MAX_PROPERTIES) { |
| this.lossless = false; |
| this.overflow = true; |
| break; |
| } |
| if (typeof property == 'object') { |
| this.lossless = false; |
| } |
| var property = new devtoolsBackend.runtime.PropertyPreview(name, |
| value[name]); |
| this.properties.push(property); |
| } |
| } |
| } |
| |
| // Creates a PropertyPreview, the type to represent a preview of a single |
| // object property. |
| devtoolsBackend.runtime.PropertyPreview = function(name, value) { |
| this.name = name.toString(); |
| this.type = typeof value; |
| |
| try { |
| this.value = value.toString(); |
| } catch(e) {} |
| |
| if (this.type == 'object') { |
| this.subtype = devtoolsBackend.runtime.getSubtype(value); |
| } |
| } |
| |
| // The object store used to reference objects by internally generated id. |
| devtoolsBackend.runtime._objectStore = {}; |
| devtoolsBackend.runtime._nextObjectId = 0; |
| |
| // The last evaluated result. |
| devtoolsBackend.runtime._lastResult = null; |
| |
| // Values in the global scope that have been overridden by corresponding |
| // members of the Command Line API for the duration of an evaluation. We use |
| // this to restore the original values after the evaluation. |
| devtoolsBackend.runtime._globalOverrides = {}; |
| |
| // Command Line API implementation. |
| // This is a set of convenience variables/functions that are not present in |
| // the global scope by default, but can be specified as available to the |
| // Runtime.evaluate function by the includeCommandLineAPI parameter. |
| // https://developers.google.com/web/tools/chrome-devtools/debug/command-line/command-line-reference |
| devtoolsBackend.runtime._commandLineAPI = {}; |
| |
| devtoolsBackend.runtime._commandLineAPI.$_ = |
| devtoolsBackend.runtime._lastResult; |
| |
| devtoolsBackend.runtime._commandLineAPI.$ = |
| document.querySelector.bind(document); |
| |
| devtoolsBackend.runtime._commandLineAPI.$$ = |
| document.querySelectorAll.bind(document); |
| |
| devtoolsBackend.runtime._commandLineAPI.keys = Object.keys; |
| |
| devtoolsBackend.runtime._commandLineAPI.values = function(object) { |
| var keys = Object.keys(object); |
| var result = []; |
| for (var i = 0; i < keys.length; i++) { |
| result.push(object[keys[i]]); |
| } |
| return result; |
| } |
| |
| devtoolsBackend.runtime._addCommandLineAPI = function() { |
| this._commandLineAPI.$_ = this._lastResult; |
| for (var property in this._commandLineAPI) { |
| if (this._commandLineAPI.hasOwnProperty(property)) { |
| this._globalOverrides[property] = window[property]; |
| window[property] = this._commandLineAPI[property]; |
| } |
| } |
| } |
| |
| devtoolsBackend.runtime._removeCommandLineAPI = function() { |
| for (var property in this._globalOverrides) { |
| if (this._globalOverrides.hasOwnProperty(property)) { |
| window[property] = this._globalOverrides[property]; |
| delete this._globalOverrides[property]; |
| } |
| } |
| } |