DevToolsUtils.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. /* General utilities used throughout devtools. */
  6. var { Ci, Cu, Cc, components } = require("chrome");
  7. var Services = require("Services");
  8. var promise = require("promise");
  9. var defer = require("devtools/shared/defer");
  10. var flags = require("./flags");
  11. var {getStack, callFunctionWithAsyncStack} = require("devtools/shared/platform/stack");
  12. loader.lazyRequireGetter(this, "FileUtils",
  13. "resource://gre/modules/FileUtils.jsm", true);
  14. // Re-export the thread-safe utils.
  15. const ThreadSafeDevToolsUtils = require("./ThreadSafeDevToolsUtils.js");
  16. for (let key of Object.keys(ThreadSafeDevToolsUtils)) {
  17. exports[key] = ThreadSafeDevToolsUtils[key];
  18. }
  19. /**
  20. * Waits for the next tick in the event loop to execute a callback.
  21. */
  22. exports.executeSoon = function executeSoon(aFn) {
  23. if (isWorker) {
  24. setImmediate(aFn);
  25. } else {
  26. let executor;
  27. // Only enable async stack reporting when DEBUG_JS_MODULES is set
  28. // (customized local builds) to avoid a performance penalty.
  29. if (AppConstants.DEBUG_JS_MODULES || flags.testing) {
  30. let stack = getStack();
  31. executor = () => {
  32. callFunctionWithAsyncStack(aFn, stack, "DevToolsUtils.executeSoon");
  33. };
  34. } else {
  35. executor = aFn;
  36. }
  37. Services.tm.mainThread.dispatch({
  38. run: exports.makeInfallible(executor)
  39. }, Ci.nsIThread.DISPATCH_NORMAL);
  40. }
  41. };
  42. /**
  43. * Waits for the next tick in the event loop.
  44. *
  45. * @return Promise
  46. * A promise that is resolved after the next tick in the event loop.
  47. */
  48. exports.waitForTick = function waitForTick() {
  49. let deferred = defer();
  50. exports.executeSoon(deferred.resolve);
  51. return deferred.promise;
  52. };
  53. /**
  54. * Waits for the specified amount of time to pass.
  55. *
  56. * @param number aDelay
  57. * The amount of time to wait, in milliseconds.
  58. * @return Promise
  59. * A promise that is resolved after the specified amount of time passes.
  60. */
  61. exports.waitForTime = function waitForTime(aDelay) {
  62. let deferred = defer();
  63. setTimeout(deferred.resolve, aDelay);
  64. return deferred.promise;
  65. };
  66. /**
  67. * Like Array.prototype.forEach, but doesn't cause jankiness when iterating over
  68. * very large arrays by yielding to the browser and continuing execution on the
  69. * next tick.
  70. *
  71. * @param Array aArray
  72. * The array being iterated over.
  73. * @param Function aFn
  74. * The function called on each item in the array. If a promise is
  75. * returned by this function, iterating over the array will be paused
  76. * until the respective promise is resolved.
  77. * @returns Promise
  78. * A promise that is resolved once the whole array has been iterated
  79. * over, and all promises returned by the aFn callback are resolved.
  80. */
  81. exports.yieldingEach = function yieldingEach(aArray, aFn) {
  82. const deferred = defer();
  83. let i = 0;
  84. let len = aArray.length;
  85. let outstanding = [deferred.promise];
  86. (function loop() {
  87. const start = Date.now();
  88. while (i < len) {
  89. // Don't block the main thread for longer than 16 ms at a time. To
  90. // maintain 60fps, you have to render every frame in at least 16ms; we
  91. // aren't including time spent in non-JS here, but this is Good
  92. // Enough(tm).
  93. if (Date.now() - start > 16) {
  94. exports.executeSoon(loop);
  95. return;
  96. }
  97. try {
  98. outstanding.push(aFn(aArray[i], i++));
  99. } catch (e) {
  100. deferred.reject(e);
  101. return;
  102. }
  103. }
  104. deferred.resolve();
  105. }());
  106. return promise.all(outstanding);
  107. };
  108. /**
  109. * Like XPCOMUtils.defineLazyGetter, but with a |this| sensitive getter that
  110. * allows the lazy getter to be defined on a prototype and work correctly with
  111. * instances.
  112. *
  113. * @param Object aObject
  114. * The prototype object to define the lazy getter on.
  115. * @param String aKey
  116. * The key to define the lazy getter on.
  117. * @param Function aCallback
  118. * The callback that will be called to determine the value. Will be
  119. * called with the |this| value of the current instance.
  120. */
  121. exports.defineLazyPrototypeGetter =
  122. function defineLazyPrototypeGetter(aObject, aKey, aCallback) {
  123. Object.defineProperty(aObject, aKey, {
  124. configurable: true,
  125. get: function () {
  126. const value = aCallback.call(this);
  127. Object.defineProperty(this, aKey, {
  128. configurable: true,
  129. writable: true,
  130. value: value
  131. });
  132. return value;
  133. }
  134. });
  135. };
  136. /**
  137. * Safely get the property value from a Debugger.Object for a given key. Walks
  138. * the prototype chain until the property is found.
  139. *
  140. * @param Debugger.Object aObject
  141. * The Debugger.Object to get the value from.
  142. * @param String aKey
  143. * The key to look for.
  144. * @return Any
  145. */
  146. exports.getProperty = function getProperty(aObj, aKey) {
  147. let root = aObj;
  148. try {
  149. do {
  150. const desc = aObj.getOwnPropertyDescriptor(aKey);
  151. if (desc) {
  152. if ("value" in desc) {
  153. return desc.value;
  154. }
  155. // Call the getter if it's safe.
  156. return exports.hasSafeGetter(desc) ? desc.get.call(root).return : undefined;
  157. }
  158. aObj = aObj.proto;
  159. } while (aObj);
  160. } catch (e) {
  161. // If anything goes wrong report the error and return undefined.
  162. exports.reportException("getProperty", e);
  163. }
  164. return undefined;
  165. };
  166. /**
  167. * Determines if a descriptor has a getter which doesn't call into JavaScript.
  168. *
  169. * @param Object aDesc
  170. * The descriptor to check for a safe getter.
  171. * @return Boolean
  172. * Whether a safe getter was found.
  173. */
  174. exports.hasSafeGetter = function hasSafeGetter(aDesc) {
  175. // Scripted functions that are CCWs will not appear scripted until after
  176. // unwrapping.
  177. try {
  178. let fn = aDesc.get.unwrap();
  179. return fn && fn.callable && fn.class == "Function" && fn.script === undefined;
  180. } catch (e) {
  181. // Avoid exception 'Object in compartment marked as invisible to Debugger'
  182. return false;
  183. }
  184. };
  185. /**
  186. * Check if it is safe to read properties and execute methods from the given JS
  187. * object. Safety is defined as being protected from unintended code execution
  188. * from content scripts (or cross-compartment code).
  189. *
  190. * See bugs 945920 and 946752 for discussion.
  191. *
  192. * @type Object aObj
  193. * The object to check.
  194. * @return Boolean
  195. * True if it is safe to read properties from aObj, or false otherwise.
  196. */
  197. exports.isSafeJSObject = function isSafeJSObject(aObj) {
  198. // If we are running on a worker thread, Cu is not available. In this case,
  199. // we always return false, just to be on the safe side.
  200. if (isWorker) {
  201. return false;
  202. }
  203. if (Cu.getGlobalForObject(aObj) ==
  204. Cu.getGlobalForObject(exports.isSafeJSObject)) {
  205. return true; // aObj is not a cross-compartment wrapper.
  206. }
  207. let principal = Cu.getObjectPrincipal(aObj);
  208. if (Services.scriptSecurityManager.isSystemPrincipal(principal)) {
  209. return true; // allow chrome objects
  210. }
  211. return Cu.isXrayWrapper(aObj);
  212. };
  213. exports.dumpn = function dumpn(str) {
  214. if (flags.wantLogging) {
  215. dump("DBG-SERVER: " + str + "\n");
  216. }
  217. };
  218. /**
  219. * A verbose logger for low-level tracing.
  220. */
  221. exports.dumpv = function (msg) {
  222. if (flags.wantVerbose) {
  223. exports.dumpn(msg);
  224. }
  225. };
  226. /**
  227. * Defines a getter on a specified object that will be created upon first use.
  228. *
  229. * @param aObject
  230. * The object to define the lazy getter on.
  231. * @param aName
  232. * The name of the getter to define on aObject.
  233. * @param aLambda
  234. * A function that returns what the getter should return. This will
  235. * only ever be called once.
  236. */
  237. exports.defineLazyGetter = function defineLazyGetter(aObject, aName, aLambda) {
  238. Object.defineProperty(aObject, aName, {
  239. get: function () {
  240. delete aObject[aName];
  241. return aObject[aName] = aLambda.apply(aObject);
  242. },
  243. configurable: true,
  244. enumerable: true
  245. });
  246. };
  247. exports.defineLazyGetter(this, "AppConstants", () => {
  248. if (isWorker) {
  249. return {};
  250. }
  251. const scope = {};
  252. Cu.import("resource://gre/modules/AppConstants.jsm", scope);
  253. return scope.AppConstants;
  254. });
  255. /**
  256. * No operation. The empty function.
  257. */
  258. exports.noop = function () { };
  259. let assertionFailureCount = 0;
  260. Object.defineProperty(exports, "assertionFailureCount", {
  261. get() {
  262. return assertionFailureCount;
  263. }
  264. });
  265. function reallyAssert(condition, message) {
  266. if (!condition) {
  267. assertionFailureCount++;
  268. const err = new Error("Assertion failure: " + message);
  269. exports.reportException("DevToolsUtils.assert", err);
  270. throw err;
  271. }
  272. }
  273. /**
  274. * DevToolsUtils.assert(condition, message)
  275. *
  276. * @param Boolean condition
  277. * @param String message
  278. *
  279. * Assertions are enabled when any of the following are true:
  280. * - This is a DEBUG_JS_MODULES build
  281. * - This is a DEBUG build
  282. * - flags.testing is set to true
  283. *
  284. * If assertions are enabled, then `condition` is checked and if false-y, the
  285. * assertion failure is logged and then an error is thrown.
  286. *
  287. * If assertions are not enabled, then this function is a no-op.
  288. */
  289. Object.defineProperty(exports, "assert", {
  290. get: () => (AppConstants.DEBUG || AppConstants.DEBUG_JS_MODULES || flags.testing)
  291. ? reallyAssert
  292. : exports.noop,
  293. });
  294. /**
  295. * Defines a getter on a specified object for a module. The module will not
  296. * be imported until first use.
  297. *
  298. * @param aObject
  299. * The object to define the lazy getter on.
  300. * @param aName
  301. * The name of the getter to define on aObject for the module.
  302. * @param aResource
  303. * The URL used to obtain the module.
  304. * @param aSymbol
  305. * The name of the symbol exported by the module.
  306. * This parameter is optional and defaults to aName.
  307. */
  308. exports.defineLazyModuleGetter = function defineLazyModuleGetter(aObject, aName,
  309. aResource,
  310. aSymbol)
  311. {
  312. this.defineLazyGetter(aObject, aName, function XPCU_moduleLambda() {
  313. var temp = {};
  314. Cu.import(aResource, temp);
  315. return temp[aSymbol || aName];
  316. });
  317. };
  318. exports.defineLazyGetter(this, "NetUtil", () => {
  319. return Cu.import("resource://gre/modules/NetUtil.jsm", {}).NetUtil;
  320. });
  321. exports.defineLazyGetter(this, "OS", () => {
  322. return Cu.import("resource://gre/modules/osfile.jsm", {}).OS;
  323. });
  324. exports.defineLazyGetter(this, "TextDecoder", () => {
  325. return Cu.import("resource://gre/modules/osfile.jsm", {}).TextDecoder;
  326. });
  327. exports.defineLazyGetter(this, "NetworkHelper", () => {
  328. return require("devtools/shared/webconsole/network-helper");
  329. });
  330. /**
  331. * Performs a request to load the desired URL and returns a promise.
  332. *
  333. * @param aURL String
  334. * The URL we will request.
  335. * @param aOptions Object
  336. * An object with the following optional properties:
  337. * - loadFromCache: if false, will bypass the cache and
  338. * always load fresh from the network (default: true)
  339. * - policy: the nsIContentPolicy type to apply when fetching the URL
  340. * (only works when loading from system principal)
  341. * - window: the window to get the loadGroup from
  342. * - charset: the charset to use if the channel doesn't provide one
  343. * - principal: the principal to use, if omitted, the request is loaded
  344. * with a codebase principal corresponding to the url being
  345. * loaded, using the origin attributes of the window, if any.
  346. * - cacheKey: when loading from cache, use this key to retrieve a cache
  347. * specific to a given SHEntry. (Allows loading POST
  348. * requests from cache)
  349. * @returns Promise that resolves with an object with the following members on
  350. * success:
  351. * - content: the document at that URL, as a string,
  352. * - contentType: the content type of the document
  353. *
  354. * If an error occurs, the promise is rejected with that error.
  355. *
  356. * XXX: It may be better to use nsITraceableChannel to get to the sources
  357. * without relying on caching when we can (not for eval, etc.):
  358. * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
  359. */
  360. function mainThreadFetch(aURL, aOptions = { loadFromCache: true,
  361. policy: Ci.nsIContentPolicy.TYPE_OTHER,
  362. window: null,
  363. charset: null,
  364. principal: null,
  365. cacheKey: null }) {
  366. // Create a channel.
  367. let url = aURL.split(" -> ").pop();
  368. let channel;
  369. try {
  370. channel = newChannelForURL(url, aOptions);
  371. } catch (ex) {
  372. return promise.reject(ex);
  373. }
  374. // Set the channel options.
  375. channel.loadFlags = aOptions.loadFromCache
  376. ? channel.LOAD_FROM_CACHE
  377. : channel.LOAD_BYPASS_CACHE;
  378. // When loading from cache, the cacheKey allows us to target a specific
  379. // SHEntry and offer ways to restore POST requests from cache.
  380. if (aOptions.loadFromCache &&
  381. aOptions.cacheKey && channel instanceof Ci.nsICacheInfoChannel) {
  382. channel.cacheKey = aOptions.cacheKey;
  383. }
  384. if (aOptions.window) {
  385. // Respect private browsing.
  386. channel.loadGroup = aOptions.window.QueryInterface(Ci.nsIInterfaceRequestor)
  387. .getInterface(Ci.nsIWebNavigation)
  388. .QueryInterface(Ci.nsIDocumentLoader)
  389. .loadGroup;
  390. }
  391. let deferred = defer();
  392. let onResponse = (stream, status, request) => {
  393. if (!components.isSuccessCode(status)) {
  394. deferred.reject(new Error(`Failed to fetch ${url}. Code ${status}.`));
  395. return;
  396. }
  397. try {
  398. // We cannot use NetUtil to do the charset conversion as if charset
  399. // information is not available and our default guess is wrong the method
  400. // might fail and we lose the stream data. This means we can't fall back
  401. // to using the locale default encoding (bug 1181345).
  402. // Read and decode the data according to the locale default encoding.
  403. let available = stream.available();
  404. let source = NetUtil.readInputStreamToString(stream, available);
  405. stream.close();
  406. // We do our own BOM sniffing here because there's no convenient
  407. // implementation of the "decode" algorithm
  408. // (https://encoding.spec.whatwg.org/#decode) exposed to JS.
  409. let bomCharset = null;
  410. if (available >= 3 && source.codePointAt(0) == 0xef &&
  411. source.codePointAt(1) == 0xbb && source.codePointAt(2) == 0xbf) {
  412. bomCharset = "UTF-8";
  413. source = source.slice(3);
  414. } else if (available >= 2 && source.codePointAt(0) == 0xfe &&
  415. source.codePointAt(1) == 0xff) {
  416. bomCharset = "UTF-16BE";
  417. source = source.slice(2);
  418. } else if (available >= 2 && source.codePointAt(0) == 0xff &&
  419. source.codePointAt(1) == 0xfe) {
  420. bomCharset = "UTF-16LE";
  421. source = source.slice(2);
  422. }
  423. // If the channel or the caller has correct charset information, the
  424. // content will be decoded correctly. If we have to fall back to UTF-8 and
  425. // the guess is wrong, the conversion fails and convertToUnicode returns
  426. // the input unmodified. Essentially we try to decode the data as UTF-8
  427. // and if that fails, we use the locale specific default encoding. This is
  428. // the best we can do if the source does not provide charset info.
  429. let charset = bomCharset || channel.contentCharset || aOptions.charset || "UTF-8";
  430. let unicodeSource = NetworkHelper.convertToUnicode(source, charset);
  431. deferred.resolve({
  432. content: unicodeSource,
  433. contentType: request.contentType
  434. });
  435. } catch (ex) {
  436. let uri = request.originalURI;
  437. if (ex.name === "NS_BASE_STREAM_CLOSED" && uri instanceof Ci.nsIFileURL) {
  438. // Empty files cause NS_BASE_STREAM_CLOSED exception. Use OS.File to
  439. // differentiate between empty files and other errors (bug 1170864).
  440. // This can be removed when bug 982654 is fixed.
  441. uri.QueryInterface(Ci.nsIFileURL);
  442. let result = OS.File.read(uri.file.path).then(bytes => {
  443. // Convert the bytearray to a String.
  444. let decoder = new TextDecoder();
  445. let content = decoder.decode(bytes);
  446. // We can't detect the contentType without opening a channel
  447. // and that failed already. This is the best we can do here.
  448. return {
  449. content,
  450. contentType: "text/plain"
  451. };
  452. });
  453. deferred.resolve(result);
  454. } else {
  455. deferred.reject(ex);
  456. }
  457. }
  458. };
  459. // Open the channel
  460. try {
  461. NetUtil.asyncFetch(channel, onResponse);
  462. } catch (ex) {
  463. return promise.reject(ex);
  464. }
  465. return deferred.promise;
  466. }
  467. /**
  468. * Opens a channel for given URL. Tries a bit harder than NetUtil.newChannel.
  469. *
  470. * @param {String} url - The URL to open a channel for.
  471. * @param {Object} options - The options object passed to @method fetch.
  472. * @return {nsIChannel} - The newly created channel. Throws on failure.
  473. */
  474. function newChannelForURL(url, { policy, window, principal }) {
  475. var securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL;
  476. let uri;
  477. try {
  478. uri = Services.io.newURI(url, null, null);
  479. } catch (e) {
  480. // In the xpcshell tests, the script url is the absolute path of the test
  481. // file, which will make a malformed URI error be thrown. Add the file
  482. // scheme to see if it helps.
  483. uri = Services.io.newURI("file://" + url, null, null);
  484. }
  485. let channelOptions = {
  486. contentPolicyType: policy,
  487. securityFlags: securityFlags,
  488. uri: uri
  489. };
  490. let prin = principal;
  491. if (!prin) {
  492. let oa = {};
  493. if (window) {
  494. oa = window.document.nodePrincipal.originAttributes;
  495. }
  496. prin = Services.scriptSecurityManager
  497. .createCodebasePrincipal(uri, oa);
  498. }
  499. // contentPolicyType is required when specifying a principal
  500. if (!channelOptions.contentPolicyType) {
  501. channelOptions.contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER;
  502. }
  503. channelOptions.loadingPrincipal = prin;
  504. try {
  505. return NetUtil.newChannel(channelOptions);
  506. } catch (e) {
  507. // In xpcshell tests on Windows, nsExternalProtocolHandler::NewChannel()
  508. // can throw NS_ERROR_UNKNOWN_PROTOCOL if the external protocol isn't
  509. // supported by Windows, so we also need to handle the exception here if
  510. // parsing the URL above doesn't throw.
  511. return newChannelForURL("file://" + url, { policy, window, principal });
  512. }
  513. }
  514. // Fetch is defined differently depending on whether we are on the main thread
  515. // or a worker thread.
  516. if (!this.isWorker) {
  517. exports.fetch = mainThreadFetch;
  518. } else {
  519. // Services is not available in worker threads, nor is there any other way
  520. // to fetch a URL. We need to enlist the help from the main thread here, by
  521. // issuing an rpc request, to fetch the URL on our behalf.
  522. exports.fetch = function (url, options) {
  523. return rpc("fetch", url, options);
  524. };
  525. }
  526. /**
  527. * Open the file at the given path for reading.
  528. *
  529. * @param {String} filePath
  530. *
  531. * @returns Promise<nsIInputStream>
  532. */
  533. exports.openFileStream = function (filePath) {
  534. return new Promise((resolve, reject) => {
  535. const uri = NetUtil.newURI(new FileUtils.File(filePath));
  536. NetUtil.asyncFetch(
  537. { uri, loadUsingSystemPrincipal: true },
  538. (stream, result) => {
  539. if (!components.isSuccessCode(result)) {
  540. reject(new Error(`Could not open "${filePath}": result = ${result}`));
  541. return;
  542. }
  543. resolve(stream);
  544. }
  545. );
  546. });
  547. };
  548. /*
  549. * All of the flags have been moved to a different module. Make sure
  550. * nobody is accessing them anymore, and don't write new code using
  551. * them. We can remove this code after a while.
  552. */
  553. function errorOnFlag(exports, name) {
  554. Object.defineProperty(exports, name, {
  555. get: () => {
  556. const msg = `Cannot get the flag ${name}. ` +
  557. `Use the "devtools/shared/flags" module instead`;
  558. console.error(msg);
  559. throw new Error(msg);
  560. },
  561. set: () => {
  562. const msg = `Cannot set the flag ${name}. ` +
  563. `Use the "devtools/shared/flags" module instead`;
  564. console.error(msg);
  565. throw new Error(msg);
  566. }
  567. });
  568. }
  569. errorOnFlag(exports, "testing");
  570. errorOnFlag(exports, "wantLogging");
  571. errorOnFlag(exports, "wantVerbose");
  572. // Calls the property with the given `name` on the given `object`, where
  573. // `name` is a string, and `object` a Debugger.Object instance.
  574. ///
  575. // This function uses only the Debugger.Object API to call the property. It
  576. // avoids the use of unsafeDeference. This is useful for example in workers,
  577. // where unsafeDereference will return an opaque security wrapper to the
  578. // referent.
  579. function callPropertyOnObject(object, name) {
  580. // Find the property.
  581. let descriptor;
  582. let proto = object;
  583. do {
  584. descriptor = proto.getOwnPropertyDescriptor(name);
  585. if (descriptor !== undefined) {
  586. break;
  587. }
  588. proto = proto.proto;
  589. } while (proto !== null);
  590. if (descriptor === undefined) {
  591. throw new Error("No such property");
  592. }
  593. let value = descriptor.value;
  594. if (typeof value !== "object" || value === null || !("callable" in value)) {
  595. throw new Error("Not a callable object.");
  596. }
  597. // Call the property.
  598. let result = value.call(object);
  599. if (result === null) {
  600. throw new Error("Code was terminated.");
  601. }
  602. if ("throw" in result) {
  603. throw result.throw;
  604. }
  605. return result.return;
  606. }
  607. exports.callPropertyOnObject = callPropertyOnObject;