|  | // Copyright 2016 the V8 project authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | InspectorTest = {}; | 
|  | InspectorTest._dumpInspectorProtocolMessages = false; | 
|  | InspectorTest._commandsForLogging = new Set(); | 
|  | InspectorTest._sessions = new Set(); | 
|  |  | 
|  | InspectorTest.log = utils.print.bind(utils); | 
|  | InspectorTest.quitImmediately = utils.quit.bind(utils); | 
|  |  | 
|  | InspectorTest.logProtocolCommandCalls = function(command) { | 
|  | InspectorTest._commandsForLogging.add(command); | 
|  | } | 
|  |  | 
|  | InspectorTest.completeTest = function() { | 
|  | var promises = []; | 
|  | for (var session of InspectorTest._sessions) | 
|  | promises.push(session.Protocol.Debugger.disable()); | 
|  | Promise.all(promises).then(() => utils.quit()); | 
|  | } | 
|  |  | 
|  | InspectorTest.waitForPendingTasks = function() { | 
|  | var promises = []; | 
|  | for (var session of InspectorTest._sessions) | 
|  | promises.push(session.Protocol.Runtime.evaluate({ expression: "new Promise(r => setTimeout(r, 0))//# sourceURL=wait-for-pending-tasks.js", awaitPromise: true })); | 
|  | return Promise.all(promises); | 
|  | } | 
|  |  | 
|  | InspectorTest.startDumpingProtocolMessages = function() { | 
|  | InspectorTest._dumpInspectorProtocolMessages = true; | 
|  | } | 
|  |  | 
|  | InspectorTest.logMessage = function(originalMessage) { | 
|  | var message = JSON.parse(JSON.stringify(originalMessage)); | 
|  | if (message.id) | 
|  | message.id = "<messageId>"; | 
|  |  | 
|  | const nonStableFields = new Set(["objectId", "scriptId", "exceptionId", "timestamp", | 
|  | "executionContextId", "callFrameId", "breakpointId", "bindRemoteObjectFunctionId", "formatterObjectId" ]); | 
|  | var objects = [ message ]; | 
|  | while (objects.length) { | 
|  | var object = objects.shift(); | 
|  | for (var key in object) { | 
|  | if (nonStableFields.has(key)) | 
|  | object[key] = `<${key}>`; | 
|  | else if (typeof object[key] === "string" && object[key].match(/\d+:\d+:\d+:debug/)) | 
|  | object[key] = object[key].replace(/\d+/, '<scriptId>'); | 
|  | else if (typeof object[key] === "object") | 
|  | objects.push(object[key]); | 
|  | } | 
|  | } | 
|  |  | 
|  | InspectorTest.logObject(message); | 
|  | return originalMessage; | 
|  | } | 
|  |  | 
|  | InspectorTest.logObject = function(object, title) { | 
|  | var lines = []; | 
|  |  | 
|  | function dumpValue(value, prefix, prefixWithName) { | 
|  | if (typeof value === "object" && value !== null) { | 
|  | if (value instanceof Array) | 
|  | dumpItems(value, prefix, prefixWithName); | 
|  | else | 
|  | dumpProperties(value, prefix, prefixWithName); | 
|  | } else { | 
|  | lines.push(prefixWithName + String(value).replace(/\n/g, " ")); | 
|  | } | 
|  | } | 
|  |  | 
|  | function dumpProperties(object, prefix, firstLinePrefix) { | 
|  | prefix = prefix || ""; | 
|  | firstLinePrefix = firstLinePrefix || prefix; | 
|  | lines.push(firstLinePrefix + "{"); | 
|  |  | 
|  | var propertyNames = Object.keys(object); | 
|  | propertyNames.sort(); | 
|  | for (var i = 0; i < propertyNames.length; ++i) { | 
|  | var name = propertyNames[i]; | 
|  | if (!object.hasOwnProperty(name)) | 
|  | continue; | 
|  | var prefixWithName = "    " + prefix + name + " : "; | 
|  | dumpValue(object[name], "    " + prefix, prefixWithName); | 
|  | } | 
|  | lines.push(prefix + "}"); | 
|  | } | 
|  |  | 
|  | function dumpItems(object, prefix, firstLinePrefix) { | 
|  | prefix = prefix || ""; | 
|  | firstLinePrefix = firstLinePrefix || prefix; | 
|  | lines.push(firstLinePrefix + "["); | 
|  | for (var i = 0; i < object.length; ++i) | 
|  | dumpValue(object[i], "    " + prefix, "    " + prefix + "[" + i + "] : "); | 
|  | lines.push(prefix + "]"); | 
|  | } | 
|  |  | 
|  | dumpValue(object, "", title || ""); | 
|  | InspectorTest.log(lines.join("\n")); | 
|  | } | 
|  |  | 
|  | InspectorTest.ContextGroup = class { | 
|  | constructor() { | 
|  | this.id = utils.createContextGroup(); | 
|  | } | 
|  |  | 
|  | schedulePauseOnNextStatement(reason, details) { | 
|  | utils.schedulePauseOnNextStatement(this.id, reason, details); | 
|  | } | 
|  |  | 
|  | cancelPauseOnNextStatement() { | 
|  | utils.cancelPauseOnNextStatement(this.id); | 
|  | } | 
|  |  | 
|  | addScript(string, lineOffset, columnOffset, url) { | 
|  | utils.compileAndRunWithOrigin(this.id, string, url || '', lineOffset || 0, columnOffset || 0, false); | 
|  | } | 
|  |  | 
|  | addModule(string, url, lineOffset, columnOffset) { | 
|  | utils.compileAndRunWithOrigin(this.id, string, url, lineOffset || 0, columnOffset || 0, true); | 
|  | } | 
|  |  | 
|  | loadScript(fileName) { | 
|  | this.addScript(utils.read(fileName)); | 
|  | } | 
|  |  | 
|  | connect() { | 
|  | return new InspectorTest.Session(this); | 
|  | } | 
|  |  | 
|  | setupInjectedScriptEnvironment(session) { | 
|  | let scriptSource = ''; | 
|  | // First define all getters on Object.prototype. | 
|  | let injectedScriptSource = utils.read('src/inspector/injected-script-source.js'); | 
|  | let getterRegex = /\.[a-zA-Z0-9]+/g; | 
|  | let match; | 
|  | let getters = new Set(); | 
|  | while (match = getterRegex.exec(injectedScriptSource)) { | 
|  | getters.add(match[0].substr(1)); | 
|  | } | 
|  | scriptSource += `(function installSettersAndGetters() { | 
|  | let defineProperty = Object.defineProperty; | 
|  | let ObjectPrototype = Object.prototype; | 
|  | let ArrayPrototype = Array.prototype; | 
|  | defineProperty(ArrayPrototype, 0, { | 
|  | set() { debugger; throw 42; }, get() { debugger; throw 42; }, | 
|  | __proto__: null | 
|  | });`, | 
|  | scriptSource += Array.from(getters).map(getter => ` | 
|  | defineProperty(ObjectPrototype, '${getter}', { | 
|  | set() { debugger; throw 42; }, get() { debugger; throw 42; }, | 
|  | __proto__: null | 
|  | }); | 
|  | `).join('\n') + '})();'; | 
|  | this.addScript(scriptSource); | 
|  |  | 
|  | if (session) { | 
|  | InspectorTest.log('WARNING: setupInjectedScriptEnvironment with debug flag for debugging only and should not be landed.'); | 
|  | InspectorTest.log('WARNING: run test with --expose-inspector-scripts flag to get more details.'); | 
|  | InspectorTest.log('WARNING: you can additionally comment rjsmin in xxd.py to get unminified injected-script-source.js.'); | 
|  | session.setupScriptMap(); | 
|  | session.Protocol.Debugger.enable(); | 
|  | session.Protocol.Debugger.onPaused(message => { | 
|  | let callFrames = message.params.callFrames; | 
|  | session.logSourceLocations(callFrames.map(frame => frame.location)); | 
|  | }) | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | InspectorTest.Session = class { | 
|  | constructor(contextGroup) { | 
|  | this.contextGroup = contextGroup; | 
|  | this._dispatchTable = new Map(); | 
|  | this._eventHandlers = new Map(); | 
|  | this._requestId = 0; | 
|  | this.Protocol = this._setupProtocol(); | 
|  | InspectorTest._sessions.add(this); | 
|  | this.id = utils.connectSession(contextGroup.id, '', this._dispatchMessage.bind(this)); | 
|  | } | 
|  |  | 
|  | disconnect() { | 
|  | InspectorTest._sessions.delete(this); | 
|  | utils.disconnectSession(this.id); | 
|  | } | 
|  |  | 
|  | reconnect() { | 
|  | var state = utils.disconnectSession(this.id); | 
|  | this.id = utils.connectSession(this.contextGroup.id, state, this._dispatchMessage.bind(this)); | 
|  | } | 
|  |  | 
|  | async addInspectedObject(serializable) { | 
|  | return this.Protocol.Runtime.evaluate({expression: `inspector.addInspectedObject(${this.id}, ${JSON.stringify(serializable)})`}); | 
|  | } | 
|  |  | 
|  | sendRawCommand(requestId, command, handler) { | 
|  | if (InspectorTest._dumpInspectorProtocolMessages) | 
|  | utils.print("frontend: " + command); | 
|  | this._dispatchTable.set(requestId, handler); | 
|  | utils.sendMessageToBackend(this.id, command); | 
|  | } | 
|  |  | 
|  | setupScriptMap() { | 
|  | if (this._scriptMap) | 
|  | return; | 
|  | this._scriptMap = new Map(); | 
|  | } | 
|  |  | 
|  | logCallFrames(callFrames) { | 
|  | for (var frame of callFrames) { | 
|  | var functionName = frame.functionName || '(anonymous)'; | 
|  | var url = frame.url ? frame.url : this._scriptMap.get(frame.location.scriptId).url; | 
|  | var lineNumber = frame.location ? frame.location.lineNumber : frame.lineNumber; | 
|  | var columnNumber = frame.location ? frame.location.columnNumber : frame.columnNumber; | 
|  | InspectorTest.log(`${functionName} (${url}:${lineNumber}:${columnNumber})`); | 
|  | } | 
|  | } | 
|  |  | 
|  | logSourceLocation(location, forceSourceRequest) { | 
|  | var scriptId = location.scriptId; | 
|  | if (!this._scriptMap || !this._scriptMap.has(scriptId)) { | 
|  | InspectorTest.log("setupScriptMap should be called before Protocol.Debugger.enable."); | 
|  | InspectorTest.completeTest(); | 
|  | } | 
|  | var script = this._scriptMap.get(scriptId); | 
|  | if (!script.scriptSource || forceSourceRequest) { | 
|  | return this.Protocol.Debugger.getScriptSource({ scriptId }) | 
|  | .then(message => script.scriptSource = message.result.scriptSource) | 
|  | .then(dumpSourceWithLocation); | 
|  | } | 
|  | return Promise.resolve().then(dumpSourceWithLocation); | 
|  |  | 
|  | function dumpSourceWithLocation() { | 
|  | var lines = script.scriptSource.split('\n'); | 
|  | var line = lines[location.lineNumber]; | 
|  | line = line.slice(0, location.columnNumber) + '#' + (line.slice(location.columnNumber) || ''); | 
|  | lines[location.lineNumber] = line; | 
|  | lines = lines.filter(line => line.indexOf('//# sourceURL=') === -1); | 
|  | InspectorTest.log(lines.slice(Math.max(location.lineNumber - 1, 0), location.lineNumber + 2).join('\n')); | 
|  | InspectorTest.log(''); | 
|  | } | 
|  | } | 
|  |  | 
|  | logSourceLocations(locations) { | 
|  | if (locations.length == 0) return Promise.resolve(); | 
|  | return this.logSourceLocation(locations[0]).then(() => this.logSourceLocations(locations.splice(1))); | 
|  | } | 
|  |  | 
|  | async logBreakLocations(inputLocations) { | 
|  | let locations = inputLocations.slice(); | 
|  | let scriptId = locations[0].scriptId; | 
|  | let script = this._scriptMap.get(scriptId); | 
|  | if (!script.scriptSource) { | 
|  | let message = await this.Protocol.Debugger.getScriptSource({scriptId}); | 
|  | script.scriptSource = message.result.scriptSource; | 
|  | } | 
|  | let lines = script.scriptSource.split('\n'); | 
|  | locations = locations.sort((loc1, loc2) => { | 
|  | if (loc2.lineNumber !== loc1.lineNumber) return loc2.lineNumber - loc1.lineNumber; | 
|  | return loc2.columnNumber - loc1.columnNumber; | 
|  | }); | 
|  | for (let location of locations) { | 
|  | let line = lines[location.lineNumber]; | 
|  | line = line.slice(0, location.columnNumber) + locationMark(location.type) + line.slice(location.columnNumber); | 
|  | lines[location.lineNumber] = line; | 
|  | } | 
|  | lines = lines.filter(line => line.indexOf('//# sourceURL=') === -1); | 
|  | InspectorTest.log(lines.join('\n') + '\n'); | 
|  | return inputLocations; | 
|  |  | 
|  | function locationMark(type) { | 
|  | if (type === 'return') return '|R|'; | 
|  | if (type === 'call') return '|C|'; | 
|  | if (type === 'debuggerStatement') return '|D|'; | 
|  | return '|_|'; | 
|  | } | 
|  | } | 
|  |  | 
|  | async logTypeProfile(typeProfile, source) { | 
|  | let entries = typeProfile.entries; | 
|  |  | 
|  | // Sort in reverse order so we can replace entries without invalidating | 
|  | // the other offsets. | 
|  | entries = entries.sort((a, b) => b.offset - a.offset); | 
|  |  | 
|  | for (let entry of entries) { | 
|  | source = source.slice(0, entry.offset) + typeAnnotation(entry.types) + | 
|  | source.slice(entry.offset); | 
|  | } | 
|  | InspectorTest.log(source); | 
|  | return typeProfile; | 
|  |  | 
|  | function typeAnnotation(types) { | 
|  | return `/*${types.map(t => t.name).join(', ')}*/`; | 
|  | } | 
|  | } | 
|  |  | 
|  | logAsyncStackTrace(asyncStackTrace) { | 
|  | while (asyncStackTrace) { | 
|  | if (asyncStackTrace.promiseCreationFrame) { | 
|  | var frame = asyncStackTrace.promiseCreationFrame; | 
|  | InspectorTest.log(`-- ${asyncStackTrace.description} (${frame.url}:${frame.lineNumber}:${frame.columnNumber})--`); | 
|  | } else { | 
|  | InspectorTest.log(`-- ${asyncStackTrace.description} --`); | 
|  | } | 
|  | this.logCallFrames(asyncStackTrace.callFrames); | 
|  | asyncStackTrace = asyncStackTrace.parent; | 
|  | } | 
|  | } | 
|  |  | 
|  | _sendCommandPromise(method, params) { | 
|  | if (InspectorTest._commandsForLogging.has(method)) | 
|  | utils.print(method + ' called'); | 
|  | var requestId = ++this._requestId; | 
|  | var messageObject = { "id": requestId, "method": method, "params": params }; | 
|  | return new Promise(fulfill => this.sendRawCommand(requestId, JSON.stringify(messageObject), fulfill)); | 
|  | } | 
|  |  | 
|  | _setupProtocol() { | 
|  | return new Proxy({}, { get: (target, agentName, receiver) => new Proxy({}, { | 
|  | get: (target, methodName, receiver) => { | 
|  | const eventPattern = /^on(ce)?([A-Z][A-Za-z0-9]+)/; | 
|  | var match = eventPattern.exec(methodName); | 
|  | if (!match) | 
|  | return args => this._sendCommandPromise(`${agentName}.${methodName}`, args || {}); | 
|  | var eventName = match[2]; | 
|  | eventName = eventName.charAt(0).toLowerCase() + eventName.slice(1); | 
|  | if (match[1]) | 
|  | return () => this._waitForEventPromise(`${agentName}.${eventName}`); | 
|  | return listener => this._eventHandlers.set(`${agentName}.${eventName}`, listener); | 
|  | } | 
|  | })}); | 
|  | } | 
|  |  | 
|  | _dispatchMessage(messageString) { | 
|  | var messageObject = JSON.parse(messageString); | 
|  | if (InspectorTest._dumpInspectorProtocolMessages) | 
|  | utils.print("backend: " + JSON.stringify(messageObject)); | 
|  | try { | 
|  | var messageId = messageObject["id"]; | 
|  | if (typeof messageId === "number") { | 
|  | var handler = this._dispatchTable.get(messageId); | 
|  | if (handler) { | 
|  | handler(messageObject); | 
|  | this._dispatchTable.delete(messageId); | 
|  | } | 
|  | } else { | 
|  | var eventName = messageObject["method"]; | 
|  | var eventHandler = this._eventHandlers.get(eventName); | 
|  | if (this._scriptMap && eventName === "Debugger.scriptParsed") | 
|  | this._scriptMap.set(messageObject.params.scriptId, JSON.parse(JSON.stringify(messageObject.params))); | 
|  | if (eventName === "Debugger.scriptParsed" && messageObject.params.url === "wait-for-pending-tasks.js") | 
|  | return; | 
|  | if (eventHandler) | 
|  | eventHandler(messageObject); | 
|  | } | 
|  | } catch (e) { | 
|  | InspectorTest.log("Exception when dispatching message: " + e + "\n" + e.stack + "\n message = " + JSON.stringify(messageObject, null, 2)); | 
|  | InspectorTest.completeTest(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | _waitForEventPromise(eventName) { | 
|  | return new Promise(fulfill => { | 
|  | this._eventHandlers.set(eventName, result => { | 
|  | delete this._eventHandlers.delete(eventName); | 
|  | fulfill(result); | 
|  | }); | 
|  | }); | 
|  | } | 
|  | }; | 
|  |  | 
|  | InspectorTest.runTestSuite = function(testSuite) { | 
|  | function nextTest() { | 
|  | if (!testSuite.length) { | 
|  | InspectorTest.completeTest(); | 
|  | return; | 
|  | } | 
|  | var fun = testSuite.shift(); | 
|  | InspectorTest.log("\nRunning test: " + fun.name); | 
|  | fun(nextTest); | 
|  | } | 
|  | nextTest(); | 
|  | } | 
|  |  | 
|  | InspectorTest.runAsyncTestSuite = async function(testSuite) { | 
|  | for (var test of testSuite) { | 
|  | InspectorTest.log("\nRunning test: " + test.name); | 
|  | try { | 
|  | await test(); | 
|  | } catch (e) { | 
|  | utils.print(e.stack); | 
|  | } | 
|  | } | 
|  | InspectorTest.completeTest(); | 
|  | } | 
|  |  | 
|  | InspectorTest.start = function(description) { | 
|  | try { | 
|  | InspectorTest.log(description); | 
|  | var contextGroup = new InspectorTest.ContextGroup(); | 
|  | var session = contextGroup.connect(); | 
|  | return { session: session, contextGroup: contextGroup, Protocol: session.Protocol }; | 
|  | } catch (e) { | 
|  | utils.print(e.stack); | 
|  | } | 
|  | } |