| /* |
| * Copyright (C) 2013 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| WI.RuntimeManager = class RuntimeManager extends WI.Object |
| { |
| constructor() |
| { |
| super(); |
| |
| this._activeExecutionContext = null; |
| |
| WI.settings.consoleSavedResultAlias.addEventListener(WI.Setting.Event.Changed, (event) => { |
| for (let target of WI.targets) { |
| // COMPATIBILITY (iOS 12.2): Runtime.setSavedResultAlias did not exist. |
| if (target.hasCommand("Runtime.setSavedResultAlias")) |
| target.RuntimeAgent.setSavedResultAlias(WI.settings.consoleSavedResultAlias.value); |
| } |
| }); |
| |
| WI.Frame.addEventListener(WI.Frame.Event.ExecutionContextsCleared, this._frameExecutionContextsCleared, this); |
| } |
| |
| // Static |
| |
| static supportsAwaitPromise() |
| { |
| // COMPATIBILITY (iOS 12): Runtime.awaitPromise did not exist. |
| return InspectorBackend.hasCommand("Runtime.awaitPromise"); |
| } |
| |
| static preferredSavedResultPrefix() |
| { |
| // COMPATIBILITY (iOS 12.2): Runtime.setSavedResultAlias did not exist. |
| if (!InspectorBackend.hasCommand("Runtime.setSavedResultAlias")) |
| return "$"; |
| return WI.settings.consoleSavedResultAlias.value || "$"; |
| } |
| |
| // Target |
| |
| initializeTarget(target) |
| { |
| target.RuntimeAgent.enable(); |
| |
| // COMPATIBILITY (iOS 8): Runtime.enableTypeProfiler did not exist. |
| if (target.hasCommand("Runtime.enableTypeProfiler") && WI.settings.showJavaScriptTypeInformation.value) |
| target.RuntimeAgent.enableTypeProfiler(); |
| |
| // COMPATIBILITY (iOS 10): Runtime.enableControlFlowProfiler did not exist. |
| if (target.hasCommand("Runtime.enableControlFlowProfiler") && WI.settings.enableControlFlowProfiler.value) |
| target.RuntimeAgent.enableControlFlowProfiler(); |
| |
| // COMPATIBILITY (iOS 12.2): Runtime.setSavedResultAlias did not exist. |
| if (target.hasCommand("Runtime.setSavedResultAlias") && WI.settings.consoleSavedResultAlias.value) |
| target.RuntimeAgent.setSavedResultAlias(WI.settings.consoleSavedResultAlias.value); |
| } |
| |
| // Public |
| |
| get activeExecutionContext() |
| { |
| return this._activeExecutionContext; |
| } |
| |
| set activeExecutionContext(executionContext) |
| { |
| if (this._activeExecutionContext === executionContext) |
| return; |
| |
| this._activeExecutionContext = executionContext; |
| |
| this.dispatchEventToListeners(WI.RuntimeManager.Event.ActiveExecutionContextChanged); |
| } |
| |
| evaluateInInspectedWindow(expression, options, callback) |
| { |
| if (!this._activeExecutionContext) { |
| callback(null, false); |
| return; |
| } |
| |
| let {objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, saveResult, emulateUserGesture, sourceURLAppender} = options; |
| |
| includeCommandLineAPI = includeCommandLineAPI || false; |
| doNotPauseOnExceptionsAndMuteConsole = doNotPauseOnExceptionsAndMuteConsole || false; |
| returnByValue = returnByValue || false; |
| generatePreview = generatePreview || false; |
| saveResult = saveResult || false; |
| emulateUserGesture = emulateUserGesture || false; |
| sourceURLAppender = sourceURLAppender || appendWebInspectorSourceURL; |
| |
| console.assert(objectGroup, "RuntimeManager.evaluateInInspectedWindow should always be called with an objectGroup"); |
| console.assert(typeof sourceURLAppender === "function"); |
| |
| if (!expression) { |
| // There is no expression, so the completion should happen against global properties. |
| expression = "this"; |
| } else if (/^\s*\{/.test(expression) && /\}\s*$/.test(expression)) { |
| // Transform {a:1} to ({a:1}) so it is treated like an object literal instead of a block with a label. |
| expression = "(" + expression + ")"; |
| } else if (/\bawait\b/.test(expression)) { |
| // Transform `await <expr>` into an async function assignment. |
| expression = this._tryApplyAwaitConvenience(expression); |
| } |
| |
| expression = sourceURLAppender(expression); |
| |
| let target = this._activeExecutionContext.target; |
| let executionContextId = this._activeExecutionContext.id; |
| |
| if (WI.debuggerManager.activeCallFrame) { |
| target = WI.debuggerManager.activeCallFrame.target; |
| executionContextId = target.executionContext.id; |
| } |
| |
| function evalCallback(error, result, wasThrown, savedResultIndex) |
| { |
| this.dispatchEventToListeners(WI.RuntimeManager.Event.DidEvaluate, {objectGroup}); |
| |
| if (error) { |
| console.error(error); |
| callback(null, false); |
| return; |
| } |
| |
| if (returnByValue) |
| callback(null, wasThrown, wasThrown ? null : result, savedResultIndex); |
| else |
| callback(WI.RemoteObject.fromPayload(result, target), wasThrown, savedResultIndex); |
| } |
| |
| if (WI.debuggerManager.activeCallFrame) { |
| // COMPATIBILITY (iOS 8): "saveResult" did not exist. |
| // COMPATIBILITY (iOS 13): "emulateUserGesture" did not exist. |
| target.DebuggerAgent.evaluateOnCallFrame.invoke({callFrameId: WI.debuggerManager.activeCallFrame.id, expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, saveResult, emulateUserGesture}, evalCallback.bind(this)); |
| return; |
| } |
| |
| // COMPATIBILITY (iOS 8): "saveResult" did not exist. |
| // COMPATIBILITY (iOS 12.2): "emulateUserGesture" did not exist. |
| target.RuntimeAgent.evaluate.invoke({expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, contextId: executionContextId, returnByValue, generatePreview, saveResult, emulateUserGesture}, evalCallback.bind(this)); |
| } |
| |
| saveResult(remoteObject, callback) |
| { |
| console.assert(remoteObject instanceof WI.RemoteObject); |
| |
| let target = this._activeExecutionContext.target; |
| let executionContextId = this._activeExecutionContext.id; |
| |
| // COMPATIBILITY (iOS 8): Runtime.saveResult did not exist. |
| if (!target.hasCommand("Runtime.saveResult")) { |
| callback(undefined); |
| return; |
| } |
| |
| function mycallback(error, savedResultIndex) |
| { |
| callback(savedResultIndex); |
| } |
| |
| if (remoteObject.objectId) |
| target.RuntimeAgent.saveResult(remoteObject.asCallArgument(), mycallback); |
| else |
| target.RuntimeAgent.saveResult(remoteObject.asCallArgument(), executionContextId, mycallback); |
| } |
| |
| // Private |
| |
| _frameExecutionContextsCleared(event) |
| { |
| let contexts = event.data.contexts || []; |
| |
| let currentContextWasDestroyed = contexts.some((context) => context.id === this._activeExecutionContext.id); |
| if (currentContextWasDestroyed) |
| this.activeExecutionContext = WI.mainTarget.executionContext; |
| } |
| |
| _tryApplyAwaitConvenience(originalExpression) |
| { |
| let esprimaSyntaxTree; |
| |
| // Do not transform if the original code parses just fine. |
| try { |
| esprima.parse(originalExpression); |
| return originalExpression; |
| } catch { } |
| |
| // Do not transform if the async function version does not parse. |
| try { |
| esprimaSyntaxTree = esprima.parse("(async function(){" + originalExpression + "})"); |
| } catch { |
| return originalExpression; |
| } |
| |
| // Assert expected AST produced by our wrapping code. |
| console.assert(esprimaSyntaxTree.type === "Program"); |
| console.assert(esprimaSyntaxTree.body.length === 1); |
| console.assert(esprimaSyntaxTree.body[0].type === "ExpressionStatement"); |
| console.assert(esprimaSyntaxTree.body[0].expression.type === "FunctionExpression"); |
| console.assert(esprimaSyntaxTree.body[0].expression.async); |
| console.assert(esprimaSyntaxTree.body[0].expression.body.type === "BlockStatement"); |
| |
| // Do not transform if there is more than one statement. |
| let asyncFunctionBlock = esprimaSyntaxTree.body[0].expression.body; |
| if (asyncFunctionBlock.body.length !== 1) |
| return originalExpression; |
| |
| // Extract the variable name for transformation. |
| let variableName; |
| let anonymous = false; |
| let declarationKind = "var"; |
| let awaitPortion; |
| let statement = asyncFunctionBlock.body[0]; |
| if (statement.type === "ExpressionStatement" |
| && statement.expression.type === "AwaitExpression") { |
| // await <expr> |
| anonymous = true; |
| } else if (statement.type === "ExpressionStatement" |
| && statement.expression.type === "AssignmentExpression" |
| && statement.expression.right.type === "AwaitExpression" |
| && statement.expression.left.type === "Identifier") { |
| // x = await <expr> |
| variableName = statement.expression.left.name; |
| awaitPortion = originalExpression.substring(originalExpression.indexOf("await")); |
| } else if (statement.type === "VariableDeclaration" |
| && statement.declarations.length === 1 |
| && statement.declarations[0].init.type === "AwaitExpression" |
| && statement.declarations[0].id.type === "Identifier") { |
| // var x = await <expr> |
| variableName = statement.declarations[0].id.name; |
| declarationKind = statement.kind; |
| awaitPortion = originalExpression.substring(originalExpression.indexOf("await")); |
| } else { |
| // Do not transform if this was not one of the simple supported syntaxes. |
| return originalExpression; |
| } |
| |
| if (anonymous) { |
| return ` |
| (async function() { |
| try { |
| let result = ${originalExpression}; |
| console.info("%o", result); |
| } catch (e) { |
| console.error(e); |
| } |
| })(); |
| undefined`; |
| } |
| |
| return `${declarationKind} ${variableName}; |
| (async function() { |
| try { |
| ${variableName} = ${awaitPortion}; |
| console.info("%o", ${variableName}); |
| } catch (e) { |
| console.error(e); |
| } |
| })(); |
| undefined;`; |
| } |
| }; |
| |
| WI.RuntimeManager.ConsoleObjectGroup = "console"; |
| WI.RuntimeManager.TopLevelExecutionContextIdentifier = undefined; |
| |
| WI.RuntimeManager.Event = { |
| DidEvaluate: Symbol("runtime-manager-did-evaluate"), |
| DefaultExecutionContextChanged: Symbol("runtime-manager-default-execution-context-changed"), |
| ActiveExecutionContextChanged: Symbol("runtime-manager-active-execution-context-changed"), |
| }; |