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.Object} 34 * @param {WebInspector.ResourceTreeModel} resourceTreeModel 35 */ 36 WebInspector.RuntimeModel = function(resourceTreeModel) 37 { 38 resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, this._frameAdded, this); 39 resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this); 40 resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameDetached, this); 41 resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded, this._didLoadCachedResources, this); 42 this._frameIdToContextList = {}; 43 } 44 45 WebInspector.RuntimeModel.Events = { 46 FrameExecutionContextListAdded: "FrameExecutionContextListAdded", 47 FrameExecutionContextListRemoved: "FrameExecutionContextListRemoved", 48 } 49 50 WebInspector.RuntimeModel.prototype = { 51 /** 52 * @param {WebInspector.ExecutionContext} executionContext 53 */ 54 setCurrentExecutionContext: function(executionContext) 55 { 56 this._currentExecutionContext = executionContext; 57 }, 58 59 /** 60 * @return {WebInspector.ExecutionContext} 61 */ 62 currentExecutionContext: function() 63 { 64 return this._currentExecutionContext; 65 }, 66 67 /** 68 * @return {Array.<WebInspector.FrameExecutionContextList>} 69 */ 70 contextLists: function() 71 { 72 return Object.values(this._frameIdToContextList); 73 }, 74 75 /** 76 * @param {WebInspector.ResourceTreeFrame} frame 77 * @return {WebInspector.FrameExecutionContextList} 78 */ 79 contextListByFrame: function(frame) 80 { 81 return this._frameIdToContextList[frame.id]; 82 }, 83 84 _frameAdded: function(event) 85 { 86 var frame = event.data; 87 var context = new WebInspector.FrameExecutionContextList(frame); 88 this._frameIdToContextList[frame.id] = context; 89 this.dispatchEventToListeners(WebInspector.RuntimeModel.Events.FrameExecutionContextListAdded, context); 90 }, 91 92 _frameNavigated: function(event) 93 { 94 var frame = event.data; 95 var context = this._frameIdToContextList[frame.id]; 96 if (context) 97 context._frameNavigated(frame); 98 }, 99 100 _frameDetached: function(event) 101 { 102 var frame = event.data; 103 var context = this._frameIdToContextList[frame.id]; 104 if (!context) 105 return; 106 this.dispatchEventToListeners(WebInspector.RuntimeModel.Events.FrameExecutionContextListRemoved, context); 107 delete this._frameIdToContextList[frame.id]; 108 }, 109 110 _didLoadCachedResources: function() 111 { 112 InspectorBackend.registerRuntimeDispatcher(new WebInspector.RuntimeDispatcher(this)); 113 RuntimeAgent.enable(); 114 }, 115 116 _executionContextCreated: function(context) 117 { 118 var contextList = this._frameIdToContextList[context.frameId]; 119 // FIXME(85708): this should never happen 120 if (!contextList) 121 return; 122 contextList._addExecutionContext(new WebInspector.ExecutionContext(context.id, context.name, context.isPageContext)); 123 }, 124 125 /** 126 * @param {string} expression 127 * @param {string} objectGroup 128 * @param {boolean} includeCommandLineAPI 129 * @param {boolean} doNotPauseOnExceptionsAndMuteConsole 130 * @param {boolean} returnByValue 131 * @param {boolean} generatePreview 132 * @param {function(?WebInspector.RemoteObject, boolean, RuntimeAgent.RemoteObject=)} callback 133 */ 134 evaluate: function(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, callback) 135 { 136 if (WebInspector.debuggerModel.selectedCallFrame()) { 137 WebInspector.debuggerModel.evaluateOnSelectedCallFrame(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, callback); 138 return; 139 } 140 141 if (!expression) { 142 // There is no expression, so the completion should happen against global properties. 143 expression = "this"; 144 } 145 146 /** 147 * @param {?Protocol.Error} error 148 * @param {RuntimeAgent.RemoteObject} result 149 * @param {boolean=} wasThrown 150 */ 151 function evalCallback(error, result, wasThrown) 152 { 153 if (error) { 154 console.error(error); 155 callback(null, false); 156 return; 157 } 158 159 if (returnByValue) 160 callback(null, !!wasThrown, wasThrown ? null : result); 161 else 162 callback(WebInspector.RemoteObject.fromPayload(result), !!wasThrown); 163 } 164 RuntimeAgent.evaluate(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, this._currentExecutionContext ? this._currentExecutionContext.id : undefined, returnByValue, generatePreview, evalCallback); 165 }, 166 167 /** 168 * @param {Element} proxyElement 169 * @param {Range} wordRange 170 * @param {boolean} force 171 * @param {function(!Array.<string>, number=)} completionsReadyCallback 172 */ 173 completionsForTextPrompt: function(proxyElement, wordRange, force, completionsReadyCallback) 174 { 175 // Pass less stop characters to rangeOfWord so the range will be a more complete expression. 176 var expressionRange = wordRange.startContainer.rangeOfWord(wordRange.startOffset, " =:[({;,!+-*/&|^<>", proxyElement, "backward"); 177 var expressionString = expressionRange.toString(); 178 var prefix = wordRange.toString(); 179 this._completionsForExpression(expressionString, prefix, force, completionsReadyCallback); 180 }, 181 182 /** 183 * @param {string} expressionString 184 * @param {string} prefix 185 * @param {boolean} force 186 * @param {function(!Array.<string>, number=)} completionsReadyCallback 187 */ 188 _completionsForExpression: function(expressionString, prefix, force, completionsReadyCallback) 189 { 190 var lastIndex = expressionString.length - 1; 191 192 var dotNotation = (expressionString[lastIndex] === "."); 193 var bracketNotation = (expressionString[lastIndex] === "["); 194 195 if (dotNotation || bracketNotation) 196 expressionString = expressionString.substr(0, lastIndex); 197 198 if (expressionString && parseInt(expressionString, 10) == expressionString) { 199 // User is entering float value, do not suggest anything. 200 completionsReadyCallback([]); 201 return; 202 } 203 204 if (!prefix && !expressionString && !force) { 205 completionsReadyCallback([]); 206 return; 207 } 208 209 if (!expressionString && WebInspector.debuggerModel.selectedCallFrame()) 210 WebInspector.debuggerModel.getSelectedCallFrameVariables(receivedPropertyNames.bind(this)); 211 else 212 this.evaluate(expressionString, "completion", true, true, false, false, evaluated.bind(this)); 213 214 function evaluated(result, wasThrown) 215 { 216 if (!result || wasThrown) { 217 completionsReadyCallback([]); 218 return; 219 } 220 221 function getCompletions(primitiveType) 222 { 223 var object; 224 if (primitiveType === "string") 225 object = new String(""); 226 else if (primitiveType === "number") 227 object = new Number(0); 228 else if (primitiveType === "boolean") 229 object = new Boolean(false); 230 else 231 object = this; 232 233 var resultSet = {}; 234 for (var o = object; o; o = o.__proto__) { 235 try { 236 var names = Object.getOwnPropertyNames(o); 237 for (var i = 0; i < names.length; ++i) 238 resultSet[names[i]] = true; 239 } catch (e) { 240 } 241 } 242 return resultSet; 243 } 244 245 if (result.type === "object" || result.type === "function") 246 result.callFunctionJSON(getCompletions, undefined, receivedPropertyNames.bind(this)); 247 else if (result.type === "string" || result.type === "number" || result.type === "boolean") 248 this.evaluate("(" + getCompletions + ")(\"" + result.type + "\")", "completion", false, true, true, false, receivedPropertyNamesFromEval.bind(this)); 249 } 250 251 function receivedPropertyNamesFromEval(notRelevant, wasThrown, result) 252 { 253 if (result && !wasThrown) 254 receivedPropertyNames.call(this, result.value); 255 else 256 completionsReadyCallback([]); 257 } 258 259 function receivedPropertyNames(propertyNames) 260 { 261 RuntimeAgent.releaseObjectGroup("completion"); 262 if (!propertyNames) { 263 completionsReadyCallback([]); 264 return; 265 } 266 var includeCommandLineAPI = (!dotNotation && !bracketNotation); 267 if (includeCommandLineAPI) { 268 const commandLineAPI = ["dir", "dirxml", "keys", "values", "profile", "profileEnd", "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", 269 "getEventListeners", "debug", "undebug", "monitor", "unmonitor", "table", "$", "$$", "$x"]; 270 for (var i = 0; i < commandLineAPI.length; ++i) 271 propertyNames[commandLineAPI[i]] = true; 272 } 273 this._reportCompletions(completionsReadyCallback, dotNotation, bracketNotation, expressionString, prefix, Object.keys(propertyNames)); 274 } 275 }, 276 277 /** 278 * @param {function(!Array.<string>, number=)} completionsReadyCallback 279 * @param {boolean} dotNotation 280 * @param {boolean} bracketNotation 281 * @param {string} expressionString 282 * @param {string} prefix 283 * @param {Array.<string>} properties 284 */ 285 _reportCompletions: function(completionsReadyCallback, dotNotation, bracketNotation, expressionString, prefix, properties) { 286 if (bracketNotation) { 287 if (prefix.length && prefix[0] === "'") 288 var quoteUsed = "'"; 289 else 290 var quoteUsed = "\""; 291 } 292 293 var results = []; 294 295 if (!expressionString) { 296 const keywords = ["break", "case", "catch", "continue", "default", "delete", "do", "else", "finally", "for", "function", "if", "in", 297 "instanceof", "new", "return", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with"]; 298 properties = properties.concat(keywords); 299 } 300 301 properties.sort(); 302 303 for (var i = 0; i < properties.length; ++i) { 304 var property = properties[i]; 305 306 if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property)) 307 continue; 308 309 if (bracketNotation) { 310 if (!/^[0-9]+$/.test(property)) 311 property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed; 312 property += "]"; 313 } 314 315 if (property.length < prefix.length) 316 continue; 317 if (prefix.length && !property.startsWith(prefix)) 318 continue; 319 320 results.push(property); 321 } 322 completionsReadyCallback(results); 323 }, 324 325 __proto__: WebInspector.Object.prototype 326 } 327 328 /** 329 * @type {WebInspector.RuntimeModel} 330 */ 331 WebInspector.runtimeModel = null; 332 333 /** 334 * @constructor 335 * @implements {RuntimeAgent.Dispatcher} 336 * @param {WebInspector.RuntimeModel} runtimeModel 337 */ 338 WebInspector.RuntimeDispatcher = function(runtimeModel) 339 { 340 this._runtimeModel = runtimeModel; 341 } 342 343 WebInspector.RuntimeDispatcher.prototype = { 344 executionContextCreated: function(context) 345 { 346 this._runtimeModel._executionContextCreated(context); 347 } 348 } 349 350 /** 351 * @constructor 352 * @extends {WebInspector.Object} 353 */ 354 WebInspector.ExecutionContext = function(id, name, isPageContext) 355 { 356 this.id = id; 357 this.name = (isPageContext && !name) ? "<page context>" : name; 358 this.isMainWorldContext = isPageContext; 359 } 360 361 /** 362 * @param {!WebInspector.ExecutionContext} a 363 * @param {!WebInspector.ExecutionContext} b 364 * @return {number} 365 */ 366 WebInspector.ExecutionContext.comparator = function(a, b) 367 { 368 // Main world context should always go first. 369 if (a.isMainWorldContext) 370 return -1; 371 if (b.isMainWorldContext) 372 return +1; 373 return a.name.localeCompare(b.name); 374 } 375 376 /** 377 * @constructor 378 * @extends {WebInspector.Object} 379 */ 380 WebInspector.FrameExecutionContextList = function(frame) 381 { 382 this._frame = frame; 383 this._executionContexts = []; 384 } 385 386 WebInspector.FrameExecutionContextList.EventTypes = { 387 ContextsUpdated: "ContextsUpdated", 388 ContextAdded: "ContextAdded" 389 } 390 391 WebInspector.FrameExecutionContextList.prototype = 392 { 393 _frameNavigated: function(frame) 394 { 395 this._frame = frame; 396 this._executionContexts = []; 397 this.dispatchEventToListeners(WebInspector.FrameExecutionContextList.EventTypes.ContextsUpdated, this); 398 }, 399 400 /** 401 * @param {!WebInspector.ExecutionContext} context 402 */ 403 _addExecutionContext: function(context) 404 { 405 var insertAt = insertionIndexForObjectInListSortedByFunction(context, this._executionContexts, WebInspector.ExecutionContext.comparator); 406 this._executionContexts.splice(insertAt, 0, context); 407 this.dispatchEventToListeners(WebInspector.FrameExecutionContextList.EventTypes.ContextAdded, this); 408 }, 409 410 executionContexts: function() 411 { 412 return this._executionContexts; 413 }, 414 415 mainWorldContext: function() 416 { 417 return this._executionContexts[0]; 418 }, 419 420 /** 421 * @param {string} securityOrigin 422 */ 423 contextBySecurityOrigin: function(securityOrigin) 424 { 425 for (var i = 0; i < this._executionContexts.length; ++i) { 426 var context = this._executionContexts[i]; 427 if (!context.isMainWorldContext && context.name === securityOrigin) 428 return context; 429 } 430 }, 431 432 get frameId() 433 { 434 return this._frame.id; 435 }, 436 437 get url() 438 { 439 return this._frame.url; 440 }, 441 442 get displayName() 443 { 444 if (!this._frame.parentFrame) 445 return "<top frame>"; 446 var name = this._frame.name || ""; 447 var subtitle = new WebInspector.ParsedURL(this._frame.url).displayName; 448 if (subtitle) { 449 if (!name) 450 return subtitle; 451 return name + "( " + subtitle + " )"; 452 } 453 return "<iframe>"; 454 }, 455 456 __proto__: WebInspector.Object.prototype 457 } 458