RuntimeModel.js 16 KB

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