1/* 2 * Copyright (C) 2012 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31/** 32 * @constructor 33 * @extends {WebInspector.SDKModel} 34 * @param {!WebInspector.Target} target 35 */ 36WebInspector.RuntimeModel = function(target) 37{ 38 WebInspector.SDKModel.call(this, WebInspector.RuntimeModel, target); 39 40 this._debuggerModel = target.debuggerModel; 41 this._agent = target.runtimeAgent(); 42 this.target().registerRuntimeDispatcher(new WebInspector.RuntimeDispatcher(this)); 43 this._agent.enable(); 44 /** 45 * @type {!Object.<number, !WebInspector.ExecutionContext>} 46 */ 47 this._executionContextById = {}; 48} 49 50WebInspector.RuntimeModel.Events = { 51 ExecutionContextCreated: "ExecutionContextCreated", 52 ExecutionContextDestroyed: "ExecutionContextDestroyed", 53} 54 55WebInspector.RuntimeModel.prototype = { 56 57 /** 58 * @return {!Array.<!WebInspector.ExecutionContext>} 59 */ 60 executionContexts: function() 61 { 62 return Object.values(this._executionContextById); 63 }, 64 65 /** 66 * @param {!RuntimeAgent.ExecutionContextDescription} context 67 */ 68 _executionContextCreated: function(context) 69 { 70 var executionContext = new WebInspector.ExecutionContext(this.target(), context.id, context.name, context.origin, context.isPageContext, context.frameId); 71 this._executionContextById[executionContext.id] = executionContext; 72 this.dispatchEventToListeners(WebInspector.RuntimeModel.Events.ExecutionContextCreated, executionContext); 73 }, 74 75 /** 76 * @param {number} executionContextId 77 */ 78 _executionContextDestroyed: function(executionContextId) 79 { 80 var executionContext = this._executionContextById[executionContextId]; 81 delete this._executionContextById[executionContextId]; 82 this.dispatchEventToListeners(WebInspector.RuntimeModel.Events.ExecutionContextDestroyed, executionContext); 83 }, 84 85 _executionContextsCleared: function() 86 { 87 var contexts = this.executionContexts(); 88 this._executionContextById = {}; 89 for (var i = 0; i < contexts.length; ++i) 90 this.dispatchEventToListeners(WebInspector.RuntimeModel.Events.ExecutionContextDestroyed, contexts[i]); 91 }, 92 93 /** 94 * @param {!RuntimeAgent.RemoteObject} payload 95 * @return {!WebInspector.RemoteObject} 96 */ 97 createRemoteObject: function(payload) 98 { 99 console.assert(typeof payload === "object", "Remote object payload should only be an object"); 100 return new WebInspector.RemoteObjectImpl(this.target(), payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.preview); 101 }, 102 103 /** 104 * @param {!RuntimeAgent.RemoteObject} payload 105 * @param {!WebInspector.ScopeRef} scopeRef 106 * @return {!WebInspector.RemoteObject} 107 */ 108 createScopeRemoteObject: function(payload, scopeRef) 109 { 110 return new WebInspector.ScopeRemoteObject(this.target(), payload.objectId, scopeRef, payload.type, payload.subtype, payload.value, payload.description, payload.preview); 111 }, 112 113 /** 114 * @param {number|string|boolean} value 115 * @return {!WebInspector.RemoteObject} 116 */ 117 createRemoteObjectFromPrimitiveValue: function(value) 118 { 119 return new WebInspector.RemoteObjectImpl(this.target(), undefined, typeof value, undefined, value); 120 }, 121 122 /** 123 * @param {string} name 124 * @param {number|string|boolean} value 125 * @return {!WebInspector.RemoteObjectProperty} 126 */ 127 createRemotePropertyFromPrimitiveValue: function(name, value) 128 { 129 return new WebInspector.RemoteObjectProperty(name, this.createRemoteObjectFromPrimitiveValue(value)); 130 }, 131 132 __proto__: WebInspector.SDKModel.prototype 133} 134 135/** 136 * @constructor 137 * @implements {RuntimeAgent.Dispatcher} 138 * @param {!WebInspector.RuntimeModel} runtimeModel 139 */ 140WebInspector.RuntimeDispatcher = function(runtimeModel) 141{ 142 this._runtimeModel = runtimeModel; 143} 144 145WebInspector.RuntimeDispatcher.prototype = { 146 executionContextCreated: function(context) 147 { 148 this._runtimeModel._executionContextCreated(context); 149 }, 150 151 executionContextDestroyed: function(executionContextId) 152 { 153 this._runtimeModel._executionContextDestroyed(executionContextId); 154 }, 155 156 executionContextsCleared: function() 157 { 158 this._runtimeModel._executionContextsCleared(); 159 } 160 161} 162 163/** 164 * @constructor 165 * @extends {WebInspector.SDKObject} 166 * @param {!WebInspector.Target} target 167 * @param {number|undefined} id 168 * @param {string} name 169 * @param {string} origin 170 * @param {boolean} isPageContext 171 * @param {string=} frameId 172 */ 173WebInspector.ExecutionContext = function(target, id, name, origin, isPageContext, frameId) 174{ 175 WebInspector.SDKObject.call(this, target); 176 this.id = id; 177 this.name = name; 178 this.origin = origin; 179 this.isMainWorldContext = isPageContext; 180 this._debuggerModel = target.debuggerModel; 181 this.frameId = frameId; 182} 183 184/** 185 * @param {!WebInspector.ExecutionContext} a 186 * @param {!WebInspector.ExecutionContext} b 187 * @return {number} 188 */ 189WebInspector.ExecutionContext.comparator = function(a, b) 190{ 191 // Main world context should always go first. 192 if (a.isMainWorldContext) 193 return -1; 194 if (b.isMainWorldContext) 195 return +1; 196 return a.name.localeCompare(b.name); 197} 198 199WebInspector.ExecutionContext.prototype = { 200 201 /** 202 * @param {string} expression 203 * @param {string} objectGroup 204 * @param {boolean} includeCommandLineAPI 205 * @param {boolean} doNotPauseOnExceptionsAndMuteConsole 206 * @param {boolean} returnByValue 207 * @param {boolean} generatePreview 208 * @param {function(?WebInspector.RemoteObject, boolean, ?RuntimeAgent.RemoteObject=, ?DebuggerAgent.ExceptionDetails=)} callback 209 */ 210 evaluate: function(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, callback) 211 { 212 //FIXME: It will be moved to separate ExecutionContext 213 if (this._debuggerModel.selectedCallFrame()) { 214 this._debuggerModel.evaluateOnSelectedCallFrame(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, callback); 215 return; 216 } 217 218 if (!expression) { 219 // There is no expression, so the completion should happen against global properties. 220 expression = "this"; 221 } 222 223 /** 224 * @this {WebInspector.ExecutionContext} 225 * @param {?Protocol.Error} error 226 * @param {!RuntimeAgent.RemoteObject} result 227 * @param {boolean=} wasThrown 228 * @param {?DebuggerAgent.ExceptionDetails=} exceptionDetails 229 */ 230 function evalCallback(error, result, wasThrown, exceptionDetails) 231 { 232 if (error) { 233 callback(null, false); 234 return; 235 } 236 237 if (returnByValue) 238 callback(null, !!wasThrown, wasThrown ? null : result, exceptionDetails); 239 else 240 callback(this.target().runtimeModel.createRemoteObject(result), !!wasThrown, undefined, exceptionDetails); 241 } 242 this.target().runtimeAgent().evaluate(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, this.id, returnByValue, generatePreview, evalCallback.bind(this)); 243 }, 244 245 /** 246 * @param {string} expressionString 247 * @param {string} prefix 248 * @param {boolean} force 249 * @param {function(!Array.<string>, number=)} completionsReadyCallback 250 */ 251 completionsForExpression: function(expressionString, prefix, force, completionsReadyCallback) 252 { 253 var lastIndex = expressionString.length - 1; 254 255 var dotNotation = (expressionString[lastIndex] === "."); 256 var bracketNotation = (expressionString[lastIndex] === "["); 257 258 if (dotNotation || bracketNotation) 259 expressionString = expressionString.substr(0, lastIndex); 260 261 if (expressionString && parseInt(expressionString, 10) == expressionString) { 262 // User is entering float value, do not suggest anything. 263 completionsReadyCallback([]); 264 return; 265 } 266 267 if (!prefix && !expressionString && !force) { 268 completionsReadyCallback([]); 269 return; 270 } 271 272 if (!expressionString && this._debuggerModel.selectedCallFrame()) 273 this._debuggerModel.getSelectedCallFrameVariables(receivedPropertyNames.bind(this)); 274 else 275 this.evaluate(expressionString, "completion", true, true, false, false, evaluated.bind(this)); 276 277 /** 278 * @this {WebInspector.ExecutionContext} 279 */ 280 function evaluated(result, wasThrown) 281 { 282 if (!result || wasThrown) { 283 completionsReadyCallback([]); 284 return; 285 } 286 287 /** 288 * @param {string} primitiveType 289 * @suppressReceiverCheck 290 * @this {WebInspector.ExecutionContext} 291 */ 292 function getCompletions(primitiveType) 293 { 294 var object; 295 if (primitiveType === "string") 296 object = new String(""); 297 else if (primitiveType === "number") 298 object = new Number(0); 299 else if (primitiveType === "boolean") 300 object = new Boolean(false); 301 else 302 object = this; 303 304 var resultSet = {}; 305 for (var o = object; o; o = o.__proto__) { 306 try { 307 var names = Object.getOwnPropertyNames(o); 308 for (var i = 0; i < names.length; ++i) 309 resultSet[names[i]] = true; 310 } catch (e) { 311 } 312 } 313 return resultSet; 314 } 315 316 if (result.type === "object" || result.type === "function") 317 result.callFunctionJSON(getCompletions, undefined, receivedPropertyNames.bind(this)); 318 else if (result.type === "string" || result.type === "number" || result.type === "boolean") 319 this.evaluate("(" + getCompletions + ")(\"" + result.type + "\")", "completion", false, true, true, false, receivedPropertyNamesFromEval.bind(this)); 320 } 321 322 /** 323 * @param {?WebInspector.RemoteObject} notRelevant 324 * @param {boolean} wasThrown 325 * @param {?RuntimeAgent.RemoteObject=} result 326 * @this {WebInspector.ExecutionContext} 327 */ 328 function receivedPropertyNamesFromEval(notRelevant, wasThrown, result) 329 { 330 if (result && !wasThrown) 331 receivedPropertyNames.call(this, result.value); 332 else 333 completionsReadyCallback([]); 334 } 335 336 /** 337 * @this {WebInspector.ExecutionContext} 338 */ 339 function receivedPropertyNames(propertyNames) 340 { 341 this.target().runtimeAgent().releaseObjectGroup("completion"); 342 if (!propertyNames) { 343 completionsReadyCallback([]); 344 return; 345 } 346 var includeCommandLineAPI = (!dotNotation && !bracketNotation); 347 if (includeCommandLineAPI) { 348 const commandLineAPI = ["dir", "dirxml", "keys", "values", "profile", "profileEnd", "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", 349 "getEventListeners", "debug", "undebug", "monitor", "unmonitor", "table", "$", "$$", "$x"]; 350 for (var i = 0; i < commandLineAPI.length; ++i) 351 propertyNames[commandLineAPI[i]] = true; 352 } 353 this._reportCompletions(completionsReadyCallback, dotNotation, bracketNotation, expressionString, prefix, Object.keys(propertyNames)); 354 } 355 }, 356 357 /** 358 * @param {function(!Array.<string>, number=)} completionsReadyCallback 359 * @param {boolean} dotNotation 360 * @param {boolean} bracketNotation 361 * @param {string} expressionString 362 * @param {string} prefix 363 * @param {!Array.<string>} properties 364 */ 365 _reportCompletions: function(completionsReadyCallback, dotNotation, bracketNotation, expressionString, prefix, properties) { 366 if (bracketNotation) { 367 if (prefix.length && prefix[0] === "'") 368 var quoteUsed = "'"; 369 else 370 var quoteUsed = "\""; 371 } 372 373 var results = []; 374 375 if (!expressionString) { 376 const keywords = ["break", "case", "catch", "continue", "default", "delete", "do", "else", "finally", "for", "function", "if", "in", 377 "instanceof", "new", "return", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with"]; 378 properties = properties.concat(keywords); 379 } 380 381 properties.sort(); 382 383 for (var i = 0; i < properties.length; ++i) { 384 var property = properties[i]; 385 386 // Assume that all non-ASCII characters are letters and thus can be used as part of identifier. 387 if (dotNotation && !/^[a-zA-Z_$\u008F-\uFFFF][a-zA-Z0-9_$\u008F-\uFFFF]*$/.test(property)) 388 continue; 389 390 if (bracketNotation) { 391 if (!/^[0-9]+$/.test(property)) 392 property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed; 393 property += "]"; 394 } 395 396 if (property.length < prefix.length) 397 continue; 398 if (prefix.length && !property.startsWith(prefix)) 399 continue; 400 401 results.push(property); 402 } 403 completionsReadyCallback(results); 404 }, 405 406 __proto__: WebInspector.SDKObject.prototype 407} 408 409/** 410 * @type {!WebInspector.RuntimeModel} 411 */ 412WebInspector.runtimeModel; 413