├── .gitattributes ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGES.md ├── InjectedScript ├── DebuggerScript.js ├── InjectedScriptHost.js └── InjectedScriptSource.js ├── LICENSE ├── appveyor.yml ├── binding.gyp ├── package.json ├── readme.md ├── src ├── InjectedScriptHost.cc ├── InjectedScriptHost.h ├── debug.cc └── tools.h ├── test ├── binding.js ├── helpers │ └── debugger.js ├── injected-script.js └── v8-debug.js ├── tools ├── NODE_NEXT.js ├── annotate-tag.js ├── commit-changes.js ├── exec.js ├── history.js ├── prepublish-to-npm.js ├── publish-to-npm.js ├── push-to-githab.js ├── release.js ├── update-changelog.js └── update-npm-version.js └── v8-debug.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text eol=lf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | node_modules/* 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.log 2 | build/!(debug) 3 | node_modules 4 | test 5 | v8-debug-*.tgz 6 | .gitattributes 7 | .gitignore 8 | .npmignore 9 | .travis.yml 10 | appveyor.yml 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: cpp 3 | compiler: gcc 4 | 5 | os: 6 | - linux 7 | - osx 8 | 9 | addons: 10 | apt: 11 | sources: 12 | - ubuntu-toolchain-r-test 13 | packages: 14 | - gcc-4.9 15 | - g++-4.9 16 | 17 | env: 18 | matrix: 19 | - export NODE_VERSION="0.10" 20 | - export NODE_VERSION="0.12" 21 | - export NODE_VERSION="4" 22 | - export NODE_VERSION="5" 23 | - export NODE_VERSION="6" 24 | - export NODE_VERSION="7" 25 | global: 26 | - node_pre_gyp_region="eu-central-1" 27 | 28 | git: 29 | depth: 1 30 | 31 | before_install: 32 | - if [ $TRAVIS_OS_NAME == "linux" ]; then 33 | export CC=/usr/bin/gcc-4.9; 34 | export CXX=/usr/bin/g++-4.9; 35 | fi 36 | 37 | - rm -rf ~/.nvm/ && git clone --depth 1 "https://github.com/creationix/nvm.git" ~/.nvm 38 | - source ~/.nvm/nvm.sh 39 | - nvm install $NODE_VERSION 40 | - nvm use $NODE_VERSION 41 | 42 | - if [ $NODE_VERSION == "0.10" ]; then 43 | npm -g install npm@latest-2; 44 | fi 45 | 46 | - export PATH="./node_modules/.bin/:$PATH" 47 | 48 | - PUBLISH_BINARY=false 49 | - if [[ $TRAVIS_TAG ]]; then PUBLISH_BINARY=true; fi; 50 | 51 | - platform=$(uname -s | sed "y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/") 52 | 53 | install: 54 | - npm install --build-from-source 55 | - npm test 56 | 57 | before_script: 58 | - if [[ $PUBLISH_BINARY == true ]]; then node-pre-gyp package testpackage publish --verbose; fi; 59 | 60 | script: 61 | - INSTALL_RESULT=0 62 | - if [[ $PUBLISH_BINARY == true ]]; then 63 | INSTALL_RESULT=$(npm install --fallback-to-build=false > /dev/null)$? || true; 64 | fi; 65 | - if [[ $INSTALL_RESULT != 0 ]]; then node-pre-gyp unpublish; false; fi 66 | - node-pre-gyp clean 67 | 68 | after_success: 69 | - node-pre-gyp info 70 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | 2017-03-28, Version 1.0.1 2 | ========================= 3 | 4 | * fix: fix the deprecated warnings (#40) (淘小杰) 5 | 6 | 7 | 2017-03-23, Version 1.0.0 8 | ========================= 9 | 10 | * feat: config node7 on appveyor (#39) (淘小杰) 11 | 12 | * fix: make it work on node7.x (#38) (淘小杰) 13 | 14 | * Update release script (Yury Puzynya) 15 | 16 | 17 | 2016-05-10, Version 0.7.7 18 | ========================= 19 | 20 | * Return node 4 x86 build (Yury Puzynya) 21 | 22 | 23 | 2016-05-08, Version 0.7.6 24 | ========================= 25 | 26 | 27 | 28 | 2016-05-08, Version 0.7.5 29 | ========================= 30 | 31 | 32 | 33 | 2016-04-28, Version 0.7.4 34 | ========================= 35 | 36 | * fix publishing (Yury Puzynya) 37 | 38 | 39 | 2016-04-28, Version 0.7.3 40 | ========================= 41 | 42 | * Update nan (Yury Puzynya) 43 | 44 | * Exclude 4.0.0-win32-ia32 from prebuilts (Yury Puzynya) 45 | 46 | 47 | 2016-03-12, Version 0.7.2 48 | ========================= 49 | 50 | * New release script (Yury Puzynya) 51 | 52 | 53 | 2016-01-26, Version 0.7.1 54 | ========================= 55 | 56 | * Fix prepublish script (Yury Puzynya) 57 | 58 | * Fix #21: this.extendedProcessDebugJSONRequest_ is not a function (Shawn Van Ittersum) 59 | 60 | * Update build script (Yury Puzynya) 61 | 62 | * Update prepublish (Yury Puzynya) 63 | 64 | 65 | 2015-12-06, Version 0.7.0 66 | ========================= 67 | 68 | * Add prepublish script (Yury Puzynya) 69 | 70 | * Add .npmignore (Yury Puzynya) 71 | 72 | * Update .gitignore (Yury Puzynya) 73 | 74 | 75 | 2015-12-06, Version 0.6.2 76 | ========================= 77 | 78 | * Update build scripts (Yury Puzynya) 79 | 80 | 81 | 2015-11-29, Version 0.6.1 82 | ========================= 83 | 84 | * Build configuration updated (Yury Puzynya) 85 | 86 | 87 | 2015-10-22, Version 0.6.0 88 | ========================= 89 | 90 | * Code prepared for NODE_NEXT logic (Yury Puzynya) 91 | 92 | * InjectedScriptSource updated (Yury Puzynya) 93 | 94 | * Added InjectedScriptHost.cc (Yury Puzynya) 95 | 96 | * Fixed preinstall script (Yury Puzynya) 97 | 98 | 99 | 2015-09-17, Version 0.5.4 100 | ========================= 101 | 102 | 103 | 104 | 2015-09-13, Version 0.5.3 105 | ========================= 106 | 107 | * Update travis build (Yury Puzynya) 108 | 109 | * Fix src for node v4 (Yury Puzynya) 110 | 111 | * Updated cloud build (Yury Puzynya) 112 | 113 | 114 | 2015-08-09, Version 0.5.2 115 | ========================= 116 | 117 | * Version changed to 0.5.2 (3y3) 118 | 119 | * Fix error rethrowing (3y3) 120 | 121 | 122 | 2015-08-09, Version 0.5.1 123 | ========================= 124 | 125 | * Version changed to 0.5.1 (3y3) 126 | 127 | * Update cloud build (3y3) 128 | 129 | * Fix build for iojs-3 (3y3) 130 | 131 | * Readme.md updated (3y3) 132 | 133 | 134 | 2015-07-23, Version 0.5.0 135 | ========================= 136 | 137 | * Version changed to 0.5.0 (3y3) 138 | 139 | * Readme.md updated (3y3) 140 | 141 | * Added `getFromFrame` func (3y3) 142 | 143 | 144 | 2015-05-19, Version 0.4.6 145 | ========================= 146 | 147 | * Version changed to 0.4.6 (3y3) 148 | 149 | * Enable win_delay_load_hook for 1.* (3y3) 150 | 151 | 152 | 2015-05-19, Version 0.4.5 153 | ========================= 154 | 155 | * Version changed to 0.4.5 (3y3) 156 | 157 | * Cleanup CI configs (3y3) 158 | 159 | 160 | 2015-05-09, Version 0.4.4 161 | ========================= 162 | 163 | * Version changed to 0.4.4 (3y3) 164 | 165 | * Update CI settings (3y3) 166 | 167 | * Unbundle node-pre-gyp (3y3) 168 | 169 | * Update TravisCI badge (3y3) 170 | 171 | 172 | 2015-05-01, Version 0.4.3 173 | ========================= 174 | 175 | * Version changed to 0.4.3 (3y3) 176 | 177 | * Update dependencies (3y3) 178 | 179 | * Added npm version badge (3y3) 180 | 181 | 182 | 2015-02-22, Version 0.4.2 183 | ========================= 184 | 185 | * Version changed to 0.4.2 (3y3) 186 | 187 | * Webkit API refactoring (3y3) 188 | 189 | * Allocate Debug Context for 0.12+ (3y3) 190 | 191 | 192 | 2015-02-19, Version 0.4.1 193 | ========================= 194 | 195 | * Version changed to 0.4.1 (3y3) 196 | 197 | * Fixes for 0.10.36 (3y3) 198 | 199 | * Bump node-pre-gyp to ^0.6.4 for iojs (Jim Cummins) 200 | 201 | 202 | 2015-02-15, Version 0.4.0 203 | ========================= 204 | 205 | * Version changed to 0.4.0 (3y3) 206 | 207 | * Temporary fix for io.js testing (3y3) 208 | 209 | * Fix for 0.12 and io.js (3y3) 210 | 211 | * Update build matrix (3y3) 212 | 213 | 214 | 2015-02-08, Version 0.3.5 215 | ========================= 216 | 217 | * Version changed to 0.3.5 (3y3) 218 | 219 | * Skip failing test for build (3y3) 220 | 221 | * Add v0.12 to build matrix (3y3) 222 | 223 | * Rename Signal to SendCommand (3y3) 224 | 225 | * Upgrade npm dependencies, notably nan to 1.6.2 (James Ide) 226 | 227 | * Suppress compile warnings (3y3) 228 | 229 | * Added AppVeyor badge (3y3) 230 | 231 | * Update build matrix (3y3) 232 | 233 | * Fix errors and warning when compiling with iojs (Leo Koppelkamm) 234 | 235 | 236 | 2015-01-25, Version 0.3.4 237 | ========================= 238 | 239 | * Version changed to 0.3.4 (3y3) 240 | 241 | * Use seq = 0 by default instead of 1 (3y3) 242 | 243 | * Fix Linux compatibility (3y3) 244 | 245 | * Update `nan` to 1.5.0 (3y3) 246 | 247 | * Fix tests race condition (3y3) 248 | 249 | * Fix paths for injected usage (3y3) 250 | 251 | * Added test for registerAgentCommand (3y3) 252 | 253 | * Added Webkit protocol support (3y3) 254 | 255 | * Node-pre-gyp updated to ^0.6.0 (3y3) 256 | 257 | * allow_natives_syntax flag moved to `allowNatives` method (3y3) 258 | 259 | * Depreceted undocumented method `mirror` (3y3) 260 | 261 | 262 | 2015-01-10, Version 0.3.3 263 | ========================= 264 | 265 | * Version changed to 0.3.3 (3y3) 266 | 267 | 268 | 2015-01-09, Version 0.3.2 269 | ========================= 270 | 271 | * Version changed to 0.3.2 (3y3) 272 | 273 | * Finish prebuild binaries (3y3) 274 | 275 | * Linux/OSX prebuild binaries (3y3) 276 | 277 | * Added `registerAsync` command (3y3) 278 | 279 | * Convert line endings to LF (3y3) 280 | 281 | 282 | 2014-12-07, Version 0.3.1 283 | ========================= 284 | 285 | * Windows prebuild binaries (3y3) 286 | 287 | 288 | 2014-11-27, Version 0.3.0 289 | ========================= 290 | 291 | * Version changed to 0.3.0 (3y3) 292 | 293 | * Update readme.md (Moshe Kolodny) 294 | 295 | * Readme updated (3y3) 296 | 297 | * API updated (3y3) 298 | 299 | 300 | 2014-10-02, Version 0.2.0 301 | ========================= 302 | 303 | * Version changed to 0.2.0 (3y3) 304 | 305 | * Allow natives syntax flag enabled (3y3) 306 | 307 | * Ignore v8 protocol serializer for custom commands (3y3) 308 | 309 | * v8-debug.js refactored (3y3) 310 | 311 | * Added runInDebugContext function (3y3) 312 | 313 | * Fixed newline in readme.md (3y3) 314 | 315 | 316 | 2014-09-28, Version 0.1.5 317 | ========================= 318 | 319 | * Version changed to 0.1.5 (3y3) 320 | 321 | * Fix compatibility with 0.10.x (3y3) 322 | 323 | 324 | 2014-09-28, Version 0.1.4 325 | ========================= 326 | 327 | * Version changed to 0.1.4 (3y3) 328 | 329 | * Fast fix for Node v0.11.14 (3y3) 330 | 331 | * Move DebugCommandProcessor to closure (3y3) 332 | 333 | * Added mirror API (3y3) 334 | 335 | 336 | 2014-06-29, Version 0.1.3 337 | ========================= 338 | 339 | * Version changed to 0.1.3 (3y3) 340 | 341 | * Remove 'v8-debug' from cache after disconnect (3y3) 342 | 343 | * Safely handling errors (3y3) 344 | 345 | 346 | 2014-06-26, Version 0.1.2 347 | ========================= 348 | 349 | * First release! 350 | -------------------------------------------------------------------------------- /InjectedScript/DebuggerScript.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | "use strict"; 31 | 32 | (function () { 33 | 34 | var DebuggerScript = {}; 35 | 36 | DebuggerScript.PauseOnExceptionsState = { 37 | DontPauseOnExceptions: 0, 38 | PauseOnAllExceptions: 1, 39 | PauseOnUncaughtExceptions: 2 40 | }; 41 | 42 | DebuggerScript.ScopeInfoDetails = { 43 | AllScopes: 0, 44 | FastAsyncScopes: 1, 45 | NoScopes: 2 46 | }; 47 | 48 | DebuggerScript._pauseOnExceptionsState = DebuggerScript.PauseOnExceptionsState.DontPauseOnExceptions; 49 | Debug.clearBreakOnException(); 50 | Debug.clearBreakOnUncaughtException(); 51 | 52 | DebuggerScript.getAfterCompileScript = function(eventData) 53 | { 54 | return DebuggerScript._formatScript(eventData.script_.script_); 55 | } 56 | 57 | DebuggerScript.getWorkerScripts = function() 58 | { 59 | var result = []; 60 | var scripts = Debug.scripts(); 61 | for (var i = 0; i < scripts.length; ++i) { 62 | var script = scripts[i]; 63 | // Workers don't share same V8 heap now so there is no need to complicate stuff with 64 | // the context id like we do to discriminate between scripts from different pages. 65 | // However we need to filter out v8 native scripts. 66 | if (script.context_data && script.context_data === "worker") 67 | result.push(DebuggerScript._formatScript(script)); 68 | } 69 | return result; 70 | } 71 | 72 | DebuggerScript.getFunctionScopes = function(fun) 73 | { 74 | var mirror = MakeMirror(fun); 75 | var count = mirror.scopeCount(); 76 | if (count == 0) 77 | return null; 78 | var result = []; 79 | for (var i = 0; i < count; i++) { 80 | var scopeDetails = mirror.scope(i).details(); 81 | var scopeObject = DebuggerScript._buildScopeObject(scopeDetails.type(), scopeDetails.object()); 82 | if (!scopeObject) 83 | continue; 84 | result.push({ 85 | type: scopeDetails.type(), 86 | object: scopeObject 87 | }); 88 | } 89 | return result; 90 | } 91 | 92 | DebuggerScript.getCollectionEntries = function(object) 93 | { 94 | var mirror = MakeMirror(object, true /* transient */); 95 | if (mirror.isMap()) 96 | return mirror.entries(); 97 | if (mirror.isSet() || mirror.isIterator()) { 98 | var result = []; 99 | var values = mirror.isSet() ? mirror.values() : mirror.preview(); 100 | for (var i = 0; i < values.length; ++i) 101 | result.push({ value: values[i] }); 102 | return result; 103 | } 104 | } 105 | 106 | DebuggerScript.getInternalProperties = function(value) 107 | { 108 | var properties = ObjectMirror.GetInternalProperties(value); 109 | var result = []; 110 | for (var i = 0; i < properties.length; i++) { 111 | var mirror = properties[i]; 112 | result.push({ 113 | name: mirror.name(), 114 | value: mirror.value().value() 115 | }); 116 | } 117 | return result; 118 | } 119 | 120 | DebuggerScript.setFunctionVariableValue = function(functionValue, scopeIndex, variableName, newValue) 121 | { 122 | var mirror = MakeMirror(functionValue); 123 | if (!mirror.isFunction()) 124 | throw new Error("Function value has incorrect type"); 125 | return DebuggerScript._setScopeVariableValue(mirror, scopeIndex, variableName, newValue); 126 | } 127 | 128 | DebuggerScript._setScopeVariableValue = function(scopeHolder, scopeIndex, variableName, newValue) 129 | { 130 | var scopeMirror = scopeHolder.scope(scopeIndex); 131 | if (!scopeMirror) 132 | throw new Error("Incorrect scope index"); 133 | scopeMirror.setVariableValue(variableName, newValue); 134 | return undefined; 135 | } 136 | 137 | DebuggerScript.getScripts = function(contextData) 138 | { 139 | var result = []; 140 | 141 | if (!contextData) 142 | return result; 143 | var comma = contextData.indexOf(","); 144 | if (comma === -1) 145 | return result; 146 | // Context data is a string in the following format: 147 | // ("page"|"injected")"," 148 | var idSuffix = contextData.substring(comma); // including the comma 149 | 150 | var scripts = Debug.scripts(); 151 | for (var i = 0; i < scripts.length; ++i) { 152 | var script = scripts[i]; 153 | if (script.context_data && script.context_data.lastIndexOf(idSuffix) != -1) 154 | result.push(DebuggerScript._formatScript(script)); 155 | } 156 | return result; 157 | } 158 | 159 | DebuggerScript._formatScript = function(script) 160 | { 161 | var lineEnds = script.line_ends; 162 | var lineCount = lineEnds.length; 163 | var endLine = script.line_offset + lineCount - 1; 164 | var endColumn; 165 | // V8 will not count last line if script source ends with \n. 166 | if (script.source[script.source.length - 1] === '\n') { 167 | endLine += 1; 168 | endColumn = 0; 169 | } else { 170 | if (lineCount === 1) 171 | endColumn = script.source.length + script.column_offset; 172 | else 173 | endColumn = script.source.length - (lineEnds[lineCount - 2] + 1); 174 | } 175 | 176 | return { 177 | id: script.id, 178 | name: script.nameOrSourceURL(), 179 | sourceURL: script.source_url, 180 | sourceMappingURL: script.source_mapping_url, 181 | source: script.source, 182 | startLine: script.line_offset, 183 | startColumn: script.column_offset, 184 | endLine: endLine, 185 | endColumn: endColumn, 186 | isContentScript: !!script.context_data && script.context_data.indexOf("injected") == 0 187 | }; 188 | } 189 | 190 | DebuggerScript.setBreakpoint = function(execState, info) 191 | { 192 | var positionAlignment = info.interstatementLocation ? Debug.BreakPositionAlignment.BreakPosition : Debug.BreakPositionAlignment.Statement; 193 | var breakId = Debug.setScriptBreakPointById(info.sourceID, info.lineNumber, info.columnNumber, info.condition, undefined, positionAlignment); 194 | 195 | var locations = Debug.findBreakPointActualLocations(breakId); 196 | if (!locations.length) 197 | return undefined; 198 | info.lineNumber = locations[0].line; 199 | info.columnNumber = locations[0].column; 200 | return breakId.toString(); 201 | } 202 | 203 | DebuggerScript.removeBreakpoint = function(execState, info) 204 | { 205 | Debug.findBreakPoint(info.breakpointId, true); 206 | } 207 | 208 | DebuggerScript.pauseOnExceptionsState = function() 209 | { 210 | return DebuggerScript._pauseOnExceptionsState; 211 | } 212 | 213 | DebuggerScript.setPauseOnExceptionsState = function(newState) 214 | { 215 | DebuggerScript._pauseOnExceptionsState = newState; 216 | 217 | if (DebuggerScript.PauseOnExceptionsState.PauseOnAllExceptions === newState) 218 | Debug.setBreakOnException(); 219 | else 220 | Debug.clearBreakOnException(); 221 | 222 | if (DebuggerScript.PauseOnExceptionsState.PauseOnUncaughtExceptions === newState) 223 | Debug.setBreakOnUncaughtException(); 224 | else 225 | Debug.clearBreakOnUncaughtException(); 226 | } 227 | 228 | DebuggerScript.frameCount = function(execState) 229 | { 230 | return execState.frameCount(); 231 | } 232 | 233 | DebuggerScript.currentCallFrame = function(execState, data) 234 | { 235 | var maximumLimit = data >> 2; 236 | var scopeDetailsLevel = data & 3; 237 | 238 | var frameCount = execState.frameCount(); 239 | if (maximumLimit && maximumLimit < frameCount) 240 | frameCount = maximumLimit; 241 | var topFrame = undefined; 242 | for (var i = frameCount - 1; i >= 0; i--) { 243 | var frameMirror = execState.frame(i); 244 | topFrame = DebuggerScript._frameMirrorToJSCallFrame(frameMirror, topFrame, scopeDetailsLevel); 245 | } 246 | return topFrame; 247 | } 248 | 249 | DebuggerScript.currentCallFrameByIndex = function(execState, index) 250 | { 251 | if (index < 0) 252 | return undefined; 253 | var frameCount = execState.frameCount(); 254 | if (index >= frameCount) 255 | return undefined; 256 | return DebuggerScript._frameMirrorToJSCallFrame(execState.frame(index), undefined, DebuggerScript.ScopeInfoDetails.NoScopes); 257 | } 258 | 259 | DebuggerScript.stepIntoStatement = function(execState) 260 | { 261 | execState.prepareStep(Debug.StepAction.StepIn, 1); 262 | } 263 | 264 | DebuggerScript.stepFrameStatement = function(execState) 265 | { 266 | execState.prepareStep(Debug.StepAction.StepFrame, 1); 267 | } 268 | 269 | DebuggerScript.stepOverStatement = function(execState, callFrame) 270 | { 271 | execState.prepareStep(Debug.StepAction.StepNext, 1); 272 | } 273 | 274 | DebuggerScript.stepOutOfFunction = function(execState, callFrame) 275 | { 276 | execState.prepareStep(Debug.StepAction.StepOut, 1); 277 | } 278 | 279 | // Returns array in form: 280 | // [ 0, ] in case of success 281 | // or [ 1, , , , ] in case of compile error, numbers are 1-based. 282 | // or throws exception with message. 283 | DebuggerScript.liveEditScriptSource = function(scriptId, newSource, preview) 284 | { 285 | var scripts = Debug.scripts(); 286 | var scriptToEdit = null; 287 | for (var i = 0; i < scripts.length; i++) { 288 | if (scripts[i].id == scriptId) { 289 | scriptToEdit = scripts[i]; 290 | break; 291 | } 292 | } 293 | if (!scriptToEdit) 294 | throw("Script not found"); 295 | 296 | var changeLog = []; 297 | try { 298 | var result = Debug.LiveEdit.SetScriptSource(scriptToEdit, newSource, preview, changeLog); 299 | return [0, result]; 300 | } catch (e) { 301 | if (e instanceof Debug.LiveEdit.Failure && "details" in e) { 302 | var details = e.details; 303 | if (details.type === "liveedit_compile_error") { 304 | var startPosition = details.position.start; 305 | return [1, String(e), String(details.syntaxErrorMessage), Number(startPosition.line), Number(startPosition.column)]; 306 | } 307 | } 308 | throw e; 309 | } 310 | } 311 | 312 | DebuggerScript.clearBreakpoints = function(execState, info) 313 | { 314 | Debug.clearAllBreakPoints(); 315 | } 316 | 317 | DebuggerScript.setBreakpointsActivated = function(execState, info) 318 | { 319 | Debug.debuggerFlags().breakPointsActive.setValue(info.enabled); 320 | } 321 | 322 | DebuggerScript.getScriptSource = function(eventData) 323 | { 324 | return eventData.script().source(); 325 | } 326 | 327 | DebuggerScript.setScriptSource = function(eventData, source) 328 | { 329 | if (eventData.script().data() === "injected-script") 330 | return; 331 | eventData.script().setSource(source); 332 | } 333 | 334 | DebuggerScript.getScriptName = function(eventData) 335 | { 336 | return eventData.script().script_.nameOrSourceURL(); 337 | } 338 | 339 | DebuggerScript.getBreakpointNumbers = function(eventData) 340 | { 341 | var breakpoints = eventData.breakPointsHit(); 342 | var numbers = []; 343 | if (!breakpoints) 344 | return numbers; 345 | 346 | for (var i = 0; i < breakpoints.length; i++) { 347 | var breakpoint = breakpoints[i]; 348 | var scriptBreakPoint = breakpoint.script_break_point(); 349 | numbers.push(scriptBreakPoint ? scriptBreakPoint.number() : breakpoint.number()); 350 | } 351 | return numbers; 352 | } 353 | 354 | DebuggerScript.isEvalCompilation = function(eventData) 355 | { 356 | var script = eventData.script(); 357 | return (script.compilationType() === Debug.ScriptCompilationType.Eval); 358 | } 359 | 360 | // NOTE: This function is performance critical, as it can be run on every 361 | // statement that generates an async event (like addEventListener) to support 362 | // asynchronous call stacks. Thus, when possible, initialize the data lazily. 363 | DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror, callerFrame, scopeDetailsLevel) 364 | { 365 | // Stuff that can not be initialized lazily (i.e. valid while paused with a valid break_id). 366 | // The frameMirror and scopeMirror can be accessed only while paused on the debugger. 367 | var frameDetails = frameMirror.details(); 368 | 369 | var funcObject = frameDetails.func(); 370 | var sourcePosition = frameDetails.sourcePosition(); 371 | var thisObject = frameDetails.receiver(); 372 | 373 | var isAtReturn = !!frameDetails.isAtReturn(); 374 | var returnValue = isAtReturn ? frameDetails.returnValue() : undefined; 375 | 376 | var scopeMirrors = (scopeDetailsLevel === DebuggerScript.ScopeInfoDetails.NoScopes ? [] : frameMirror.allScopes(scopeDetailsLevel === DebuggerScript.ScopeInfoDetails.FastAsyncScopes)); 377 | var scopeTypes = new Array(scopeMirrors.length); 378 | var scopeObjects = new Array(scopeMirrors.length); 379 | for (var i = 0; i < scopeMirrors.length; ++i) { 380 | var scopeDetails = scopeMirrors[i].details(); 381 | scopeTypes[i] = scopeDetails.type(); 382 | scopeObjects[i] = scopeDetails.object(); 383 | } 384 | 385 | // Calculated lazily. 386 | var scopeChain; 387 | var funcMirror; 388 | var location; 389 | 390 | function lazyScopeChain() 391 | { 392 | if (!scopeChain) { 393 | scopeChain = []; 394 | for (var i = 0, j = 0; i < scopeObjects.length; ++i) { 395 | var scopeObject = DebuggerScript._buildScopeObject(scopeTypes[i], scopeObjects[i]); 396 | if (scopeObject) { 397 | scopeTypes[j] = scopeTypes[i]; 398 | scopeChain[j] = scopeObject; 399 | ++j; 400 | } 401 | } 402 | scopeTypes.length = scopeChain.length; 403 | scopeObjects = null; // Free for GC. 404 | } 405 | return scopeChain; 406 | } 407 | 408 | function lazyScopeTypes() 409 | { 410 | if (!scopeChain) 411 | lazyScopeChain(); 412 | return scopeTypes; 413 | } 414 | 415 | function ensureFuncMirror() 416 | { 417 | if (!funcMirror) { 418 | funcMirror = MakeMirror(funcObject); 419 | if (!funcMirror.isFunction()) 420 | funcMirror = new UnresolvedFunctionMirror(funcObject); 421 | } 422 | return funcMirror; 423 | } 424 | 425 | function ensureLocation() 426 | { 427 | if (!location) { 428 | var script = ensureFuncMirror().script(); 429 | if (script) 430 | location = script.locationFromPosition(sourcePosition, true); 431 | if (!location) 432 | location = { line: 0, column: 0 }; 433 | } 434 | return location; 435 | } 436 | 437 | function line() 438 | { 439 | return ensureLocation().line; 440 | } 441 | 442 | function column() 443 | { 444 | return ensureLocation().column; 445 | } 446 | 447 | function sourceID() 448 | { 449 | var script = ensureFuncMirror().script(); 450 | return script && script.id(); 451 | } 452 | 453 | function scriptName() 454 | { 455 | var script = ensureFuncMirror().script(); 456 | return script && script.name(); 457 | } 458 | 459 | function functionName() 460 | { 461 | var func = ensureFuncMirror(); 462 | if (!func.resolved()) 463 | return undefined; 464 | var displayName; 465 | var valueMirror = func.property("displayName").value(); 466 | if (valueMirror && valueMirror.isString()) 467 | displayName = valueMirror.value(); 468 | return displayName || func.name() || func.inferredName(); 469 | } 470 | 471 | function evaluate(expression, scopeExtension) 472 | { 473 | return frameMirror.evaluate(expression, false, scopeExtension).value(); 474 | } 475 | 476 | function restart() 477 | { 478 | return Debug.LiveEdit.RestartFrame(frameMirror); 479 | } 480 | 481 | function setVariableValue(scopeNumber, variableName, newValue) 482 | { 483 | return DebuggerScript._setScopeVariableValue(frameMirror, scopeNumber, variableName, newValue); 484 | } 485 | 486 | function stepInPositions() 487 | { 488 | var stepInPositionsV8 = frameMirror.stepInPositions(); 489 | var stepInPositionsProtocol; 490 | if (stepInPositionsV8) { 491 | stepInPositionsProtocol = []; 492 | var script = ensureFuncMirror().script(); 493 | if (script) { 494 | var scriptId = String(script.id()); 495 | for (var i = 0; i < stepInPositionsV8.length; i++) { 496 | var item = { 497 | scriptId: scriptId, 498 | lineNumber: stepInPositionsV8[i].position.line, 499 | columnNumber: stepInPositionsV8[i].position.column 500 | }; 501 | stepInPositionsProtocol.push(item); 502 | } 503 | } 504 | } 505 | return JSON.stringify(stepInPositionsProtocol); 506 | } 507 | 508 | return { 509 | "sourceID": sourceID, 510 | "line": line, 511 | "column": column, 512 | "scriptName": scriptName, 513 | "functionName": functionName, 514 | "thisObject": thisObject, 515 | "scopeChain": lazyScopeChain, 516 | "scopeType": lazyScopeTypes, 517 | "evaluate": evaluate, 518 | "caller": callerFrame, 519 | "restart": restart, 520 | "setVariableValue": setVariableValue, 521 | "stepInPositions": stepInPositions, 522 | "isAtReturn": isAtReturn, 523 | "returnValue": returnValue 524 | }; 525 | } 526 | 527 | DebuggerScript._buildScopeObject = function(scopeType, scopeObject) 528 | { 529 | var result; 530 | switch (scopeType) { 531 | case ScopeType.Local: 532 | case ScopeType.Closure: 533 | case ScopeType.Catch: 534 | case ScopeType.Block: 535 | case ScopeType.Script: 536 | // For transient objects we create a "persistent" copy that contains 537 | // the same properties. 538 | // Reset scope object prototype to null so that the proto properties 539 | // don't appear in the local scope section. 540 | var properties = MakeMirror(scopeObject, true /* transient */).properties(); 541 | // Almost always Script scope will be empty, so just filter out that noise. 542 | // Also drop empty Block scopes, should we get any. 543 | if (!properties.length && (scopeType === ScopeType.Script || scopeType === ScopeType.Block)) 544 | break; 545 | result = { __proto__: null }; 546 | for (var j = 0; j < properties.length; j++) { 547 | var name = properties[j].name(); 548 | if (name.charAt(0) === ".") 549 | continue; // Skip internal variables like ".arguments" 550 | result[name] = properties[j].value_; 551 | } 552 | break; 553 | case ScopeType.Global: 554 | case ScopeType.With: 555 | result = scopeObject; 556 | break; 557 | } 558 | return result; 559 | } 560 | 561 | DebuggerScript.getPromiseDetails = function(eventData) 562 | { 563 | return { 564 | "promise": eventData.promise().value(), 565 | "parentPromise": eventData.parentPromise().value(), 566 | "status": eventData.status() 567 | }; 568 | } 569 | 570 | // We never resolve Mirror by its handle so to avoid memory leaks caused by Mirrors in the cache we disable it. 571 | ToggleMirrorCache(false); 572 | 573 | return DebuggerScript; 574 | })(); 575 | -------------------------------------------------------------------------------- /InjectedScript/InjectedScriptHost.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | (function (binding, DebuggerScript) { 4 | function InjectedScriptHost() {} 5 | 6 | InjectedScriptHost.prototype = binding.InjectedScriptHost; 7 | 8 | InjectedScriptHost.prototype.isHTMLAllCollection = function(object) { 9 | //We don't have `all` collection in NodeJS 10 | return false; 11 | }; 12 | 13 | InjectedScriptHost.prototype.suppressWarningsAndCallFunction = function(func, receiver, args) { 14 | return this.callFunction(func, receiver, args); 15 | }; 16 | 17 | InjectedScriptHost.prototype.functionDetails = function(fun) { 18 | var details = this.functionDetailsWithoutScopes(fun); 19 | var scopes = DebuggerScript.getFunctionScopes(fun); 20 | 21 | if (scopes && scopes.length) { 22 | details.rawScopes = scopes; 23 | } 24 | 25 | return details; 26 | }; 27 | 28 | InjectedScriptHost.prototype.getInternalProperties = function(value) { 29 | return DebuggerScript.getInternalProperties(value); 30 | }; 31 | 32 | return new InjectedScriptHost(); 33 | }); 34 | -------------------------------------------------------------------------------- /InjectedScript/InjectedScriptSource.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 Apple Inc. All rights reserved. 3 | * Copyright (C) 2013 Google Inc. All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 15 | * its contributors may be used to endorse or promote products derived 16 | * from this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | "use strict"; 31 | 32 | /** 33 | * @param {!InjectedScriptHostClass} InjectedScriptHost 34 | * @param {!Window|!WorkerGlobalScope} inspectedGlobalObject 35 | * @param {number} injectedScriptId 36 | */ 37 | (function (InjectedScriptHost, inspectedGlobalObject, injectedScriptId) { 38 | 39 | /** 40 | * Protect against Object overwritten by the user code. 41 | * @suppress {duplicate} 42 | */ 43 | var Object = /** @type {function(new:Object, *=)} */ ({}.constructor); 44 | 45 | /** 46 | * @param {!Array.} array 47 | * @param {...} var_args 48 | * @template T 49 | */ 50 | function push(array, var_args) 51 | { 52 | for (var i = 1; i < arguments.length; ++i) 53 | array[array.length] = arguments[i]; 54 | } 55 | 56 | /** 57 | * @param {(!Arguments.|!NodeList)} array 58 | * @param {number=} index 59 | * @return {!Array.} 60 | * @template T 61 | */ 62 | function slice(array, index) 63 | { 64 | var result = []; 65 | for (var i = index || 0, j = 0; i < array.length; ++i, ++j) 66 | result[j] = array[i]; 67 | return result; 68 | } 69 | 70 | /** 71 | * @param {!Array.} array1 72 | * @param {!Array.} array2 73 | * @return {!Array.} 74 | * @template T 75 | */ 76 | function concat(array1, array2) 77 | { 78 | var result = []; 79 | for (var i = 0; i < array1.length; ++i) 80 | push(result, array1[i]); 81 | for (var i = 0; i < array2.length; ++i) 82 | push(result, array2[i]); 83 | return result; 84 | } 85 | 86 | /** 87 | * @param {*} obj 88 | * @return {string} 89 | * @suppress {uselessCode} 90 | */ 91 | function toString(obj) 92 | { 93 | // We don't use String(obj) because String could be overridden. 94 | // Also the ("" + obj) expression may throw. 95 | try { 96 | return "" + obj; 97 | } catch (e) { 98 | var name = InjectedScriptHost.internalConstructorName(obj) || InjectedScriptHost.subtype(obj) || (typeof obj); 99 | return "#<" + name + ">"; 100 | } 101 | } 102 | 103 | /** 104 | * @param {*} obj 105 | * @return {string} 106 | */ 107 | function toStringDescription(obj) 108 | { 109 | if (typeof obj === "number" && obj === 0 && 1 / obj < 0) 110 | return "-0"; // Negative zero. 111 | return toString(obj); 112 | } 113 | 114 | /** 115 | * Please use this bind, not the one from Function.prototype 116 | * @param {function(...)} func 117 | * @param {?Object} thisObject 118 | * @param {...} var_args 119 | * @return {function(...)} 120 | */ 121 | function bind(func, thisObject, var_args) 122 | { 123 | var args = slice(arguments, 2); 124 | 125 | /** 126 | * @param {...} var_args 127 | */ 128 | function bound(var_args) 129 | { 130 | return InjectedScriptHost.callFunction(func, thisObject, concat(args, slice(arguments))); 131 | } 132 | bound.toString = function() 133 | { 134 | return "bound: " + toString(func); 135 | }; 136 | return bound; 137 | } 138 | 139 | /** 140 | * @param {T} obj 141 | * @return {T} 142 | * @template T 143 | */ 144 | function nullifyObjectProto(obj) 145 | { 146 | if (obj && typeof obj === "object") 147 | obj.__proto__ = null; 148 | return obj; 149 | } 150 | 151 | /** 152 | * @param {number|string} obj 153 | * @return {boolean} 154 | */ 155 | function isUInt32(obj) 156 | { 157 | if (typeof obj === "number") 158 | return obj >>> 0 === obj && (obj > 0 || 1 / obj > 0); 159 | return "" + (obj >>> 0) === obj; 160 | } 161 | 162 | /** 163 | * FireBug's array detection. 164 | * @param {*} obj 165 | * @return {boolean} 166 | */ 167 | function isArrayLike(obj) 168 | { 169 | if (typeof obj !== "object") 170 | return false; 171 | try { 172 | if (typeof obj.splice === "function") { 173 | var len = obj.length; 174 | return typeof len === "number" && isUInt32(len); 175 | } 176 | } catch (e) { 177 | } 178 | return false; 179 | } 180 | 181 | /** 182 | * @param {number} a 183 | * @param {number} b 184 | * @return {number} 185 | */ 186 | function max(a, b) 187 | { 188 | return a > b ? a : b; 189 | } 190 | 191 | /** 192 | * FIXME: Remove once ES6 is supported natively by JS compiler. 193 | * @param {*} obj 194 | * @return {boolean} 195 | */ 196 | function isSymbol(obj) 197 | { 198 | var type = typeof obj; 199 | return (type === "symbol"); 200 | } 201 | 202 | /** 203 | * @param {string} str 204 | * @param {string} searchElement 205 | * @param {number=} fromIndex 206 | * @return {number} 207 | */ 208 | function indexOf(str, searchElement, fromIndex) 209 | { 210 | var len = str.length; 211 | var n = fromIndex || 0; 212 | var k = max(n >= 0 ? n : len + n, 0); 213 | 214 | while (k < len) { 215 | if (str[k] === searchElement) 216 | return k; 217 | ++k; 218 | } 219 | return -1; 220 | } 221 | 222 | /** 223 | * DOM Attributes which have observable side effect on getter, in the form of 224 | * {interfaceName1: {attributeName1: true, 225 | * attributeName2: true, 226 | * ...}, 227 | * interfaceName2: {...}, 228 | * ...} 229 | * @type {!Object>} 230 | * @const 231 | */ 232 | var domAttributesWithObservableSideEffectOnGet = nullifyObjectProto({}); 233 | domAttributesWithObservableSideEffectOnGet["Request"] = nullifyObjectProto({}); 234 | domAttributesWithObservableSideEffectOnGet["Request"]["body"] = true; 235 | domAttributesWithObservableSideEffectOnGet["Response"] = nullifyObjectProto({}); 236 | domAttributesWithObservableSideEffectOnGet["Response"]["body"] = true; 237 | 238 | /** 239 | * @param {!Object} object 240 | * @param {string} attribute 241 | * @return {boolean} 242 | */ 243 | function doesAttributeHaveObservableSideEffectOnGet(object, attribute) 244 | { 245 | for (var interfaceName in domAttributesWithObservableSideEffectOnGet) { 246 | var isInstance = InjectedScriptHost.suppressWarningsAndCallFunction(function(object, interfaceName) { 247 | return /* suppressBlacklist */ typeof inspectedGlobalObject[interfaceName] === "function" && object instanceof inspectedGlobalObject[interfaceName]; 248 | }, null, [object, interfaceName]); 249 | if (isInstance) { 250 | return attribute in domAttributesWithObservableSideEffectOnGet[interfaceName]; 251 | } 252 | } 253 | return false; 254 | } 255 | 256 | /** 257 | * @constructor 258 | */ 259 | var InjectedScript = function() 260 | { 261 | } 262 | 263 | /** 264 | * @type {!Object.} 265 | * @const 266 | */ 267 | InjectedScript.primitiveTypes = { 268 | "undefined": true, 269 | "boolean": true, 270 | "number": true, 271 | "string": true, 272 | __proto__: null 273 | } 274 | 275 | InjectedScript.prototype = { 276 | /** 277 | * @param {*} object 278 | * @return {boolean} 279 | */ 280 | isPrimitiveValue: function(object) 281 | { 282 | // FIXME(33716): typeof document.all is always 'undefined'. 283 | return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object); 284 | }, 285 | 286 | /** 287 | * @param {*} object 288 | * @param {string} groupName 289 | * @param {boolean} canAccessInspectedGlobalObject 290 | * @param {boolean} generatePreview 291 | * @return {!RuntimeAgent.RemoteObject} 292 | */ 293 | wrapObject: function(object, groupName, canAccessInspectedGlobalObject, generatePreview) 294 | { 295 | if (canAccessInspectedGlobalObject) 296 | return this._wrapObject(object, groupName, false, generatePreview); 297 | return this._fallbackWrapper(object); 298 | }, 299 | 300 | /** 301 | * @param {*} object 302 | * @return {!RuntimeAgent.RemoteObject} 303 | */ 304 | _fallbackWrapper: function(object) 305 | { 306 | var result = { __proto__: null }; 307 | result.type = typeof object; 308 | if (this.isPrimitiveValue(object)) 309 | result.value = object; 310 | else 311 | result.description = toString(object); 312 | return /** @type {!RuntimeAgent.RemoteObject} */ (result); 313 | }, 314 | 315 | /** 316 | * @param {boolean} canAccessInspectedGlobalObject 317 | * @param {!Object} table 318 | * @param {!Array.|string|boolean} columns 319 | * @return {!RuntimeAgent.RemoteObject} 320 | */ 321 | wrapTable: function(canAccessInspectedGlobalObject, table, columns) 322 | { 323 | if (!canAccessInspectedGlobalObject) 324 | return this._fallbackWrapper(table); 325 | var columnNames = null; 326 | if (typeof columns === "string") 327 | columns = [columns]; 328 | if (InjectedScriptHost.subtype(columns) === "array") { 329 | columnNames = []; 330 | for (var i = 0; i < columns.length; ++i) 331 | columnNames[i] = toString(columns[i]); 332 | } 333 | return this._wrapObject(table, "console", false, true, columnNames, true); 334 | }, 335 | 336 | /** 337 | * @param {*} object 338 | * @return {*} 339 | */ 340 | _inspect: function(object) 341 | { 342 | if (arguments.length === 0) 343 | return; 344 | 345 | var objectId = this._wrapObject(object, ""); 346 | var hints = { __proto__: null }; 347 | 348 | InjectedScriptHost.inspect(objectId, hints); 349 | return object; 350 | }, 351 | 352 | /** 353 | * This method cannot throw. 354 | * @param {*} object 355 | * @param {string=} objectGroupName 356 | * @param {boolean=} forceValueType 357 | * @param {boolean=} generatePreview 358 | * @param {?Array.=} columnNames 359 | * @param {boolean=} isTable 360 | * @param {boolean=} doNotBind 361 | * @param {*=} customObjectConfig 362 | * @return {!RuntimeAgent.RemoteObject} 363 | * @suppress {checkTypes} 364 | */ 365 | _wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames, isTable, doNotBind, customObjectConfig) 366 | { 367 | try { 368 | return new InjectedScript.RemoteObject(object, objectGroupName, doNotBind, forceValueType, generatePreview, columnNames, isTable, undefined, customObjectConfig); 369 | } catch (e) { 370 | try { 371 | var description = injectedScript._describe(e); 372 | } catch (ex) { 373 | var description = ""; 374 | } 375 | return new InjectedScript.RemoteObject(description); 376 | } 377 | }, 378 | 379 | /** 380 | * @param {!Object|symbol} object 381 | * @param {string=} objectGroupName 382 | * @return {string} 383 | */ 384 | _bind: function(object, objectGroupName) 385 | { 386 | var id = InjectedScriptHost.bind(object, objectGroupName || ""); 387 | return "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}"; 388 | }, 389 | 390 | /** 391 | * @param {string} objectId 392 | * @return {!Object} 393 | */ 394 | _parseObjectId: function(objectId) 395 | { 396 | return nullifyObjectProto(/** @type {!Object} */ (InjectedScriptHost.eval("(" + objectId + ")"))); 397 | }, 398 | 399 | clearLastEvaluationResult: function() 400 | { 401 | delete this._lastResult; 402 | }, 403 | 404 | /** 405 | * @param {string} objectId 406 | * @param {boolean} ownProperties 407 | * @param {boolean} accessorPropertiesOnly 408 | * @param {boolean} generatePreview 409 | * @return {!Array.|boolean} 410 | */ 411 | getProperties: function(objectId, ownProperties, accessorPropertiesOnly, generatePreview) 412 | { 413 | var parsedObjectId = this._parseObjectId(objectId); 414 | var object = this._objectForId(parsedObjectId); 415 | var objectGroupName = InjectedScriptHost.idToObjectGroupName(parsedObjectId.id); 416 | 417 | if (!this._isDefined(object) || isSymbol(object)) 418 | return false; 419 | object = /** @type {!Object} */ (object); 420 | var descriptors = []; 421 | var iter = this._propertyDescriptors(object, ownProperties, accessorPropertiesOnly, undefined); 422 | // Go over properties, wrap object values. 423 | for (var descriptor of iter) { 424 | if ("get" in descriptor) 425 | descriptor.get = this._wrapObject(descriptor.get, objectGroupName); 426 | if ("set" in descriptor) 427 | descriptor.set = this._wrapObject(descriptor.set, objectGroupName); 428 | if ("value" in descriptor) 429 | descriptor.value = this._wrapObject(descriptor.value, objectGroupName, false, generatePreview); 430 | if (!("configurable" in descriptor)) 431 | descriptor.configurable = false; 432 | if (!("enumerable" in descriptor)) 433 | descriptor.enumerable = false; 434 | if ("symbol" in descriptor) 435 | descriptor.symbol = this._wrapObject(descriptor.symbol, objectGroupName); 436 | push(descriptors, descriptor); 437 | } 438 | return descriptors; 439 | }, 440 | 441 | /** 442 | * @param {string} objectId 443 | * @return {!Array.|boolean} 444 | */ 445 | getInternalProperties: function(objectId) 446 | { 447 | var parsedObjectId = this._parseObjectId(objectId); 448 | var object = this._objectForId(parsedObjectId); 449 | var objectGroupName = InjectedScriptHost.idToObjectGroupName(parsedObjectId.id); 450 | if (!this._isDefined(object) || isSymbol(object)) 451 | return false; 452 | object = /** @type {!Object} */ (object); 453 | var descriptors = []; 454 | var internalProperties = InjectedScriptHost.getInternalProperties(object); 455 | if (internalProperties) { 456 | for (var i = 0; i < internalProperties.length; i += 2) { 457 | var descriptor = { 458 | name: internalProperties[i], 459 | value: this._wrapObject(internalProperties[i + 1], objectGroupName), 460 | __proto__: null 461 | }; 462 | push(descriptors, descriptor); 463 | } 464 | } 465 | return descriptors; 466 | }, 467 | 468 | /** 469 | * @param {string} functionId 470 | * @return {!DebuggerAgent.FunctionDetails|string} 471 | */ 472 | getFunctionDetails: function(functionId) 473 | { 474 | var parsedFunctionId = this._parseObjectId(functionId); 475 | var func = this._objectForId(parsedFunctionId); 476 | if (typeof func !== "function") 477 | return "Cannot resolve function by id."; 478 | var details = nullifyObjectProto(/** @type {!DebuggerAgent.FunctionDetails} */ (InjectedScriptHost.functionDetails(func))); 479 | if ("rawScopes" in details) { 480 | var objectGroupName = InjectedScriptHost.idToObjectGroupName(parsedFunctionId.id); 481 | var rawScopes = details["rawScopes"]; 482 | delete details["rawScopes"]; 483 | var scopes = []; 484 | for (var i = 0; i < rawScopes.length; ++i) 485 | scopes[i] = InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName); 486 | details.scopeChain = scopes; 487 | } 488 | return details; 489 | }, 490 | 491 | /** 492 | * @param {string} objectId 493 | * @return {!DebuggerAgent.GeneratorObjectDetails|string} 494 | */ 495 | getGeneratorObjectDetails: function(objectId) 496 | { 497 | var parsedObjectId = this._parseObjectId(objectId); 498 | var object = this._objectForId(parsedObjectId); 499 | if (!object || typeof object !== "object") 500 | return "Could not find object with given id"; 501 | var details = nullifyObjectProto(/** @type {?DebuggerAgent.GeneratorObjectDetails} */ (InjectedScriptHost.generatorObjectDetails(object))); 502 | if (!details) 503 | return "Object is not a generator"; 504 | var objectGroupName = InjectedScriptHost.idToObjectGroupName(parsedObjectId.id); 505 | details["function"] = this._wrapObject(details["function"], objectGroupName); 506 | return details; 507 | }, 508 | 509 | /** 510 | * @param {string} objectId 511 | * @return {!Array.|string} 512 | */ 513 | getCollectionEntries: function(objectId) 514 | { 515 | var parsedObjectId = this._parseObjectId(objectId); 516 | var object = this._objectForId(parsedObjectId); 517 | if (!object || typeof object !== "object") 518 | return "Could not find object with given id"; 519 | var entries = InjectedScriptHost.collectionEntries(object); 520 | if (!entries) 521 | return "Object with given id is not a collection"; 522 | var objectGroupName = InjectedScriptHost.idToObjectGroupName(parsedObjectId.id); 523 | for (var i = 0; i < entries.length; ++i) { 524 | var entry = nullifyObjectProto(entries[i]); 525 | if ("key" in entry) 526 | entry.key = this._wrapObject(entry.key, objectGroupName); 527 | entry.value = this._wrapObject(entry.value, objectGroupName); 528 | entries[i] = entry; 529 | } 530 | return entries; 531 | }, 532 | 533 | /** 534 | * @param {!Object} object 535 | * @param {boolean=} ownProperties 536 | * @param {boolean=} accessorPropertiesOnly 537 | * @param {?Array.=} propertyNamesOnly 538 | */ 539 | _propertyDescriptors: function*(object, ownProperties, accessorPropertiesOnly, propertyNamesOnly) 540 | { 541 | var propertyProcessed = { __proto__: null }; 542 | 543 | /** 544 | * @param {?Object} o 545 | * @param {!Iterable.|!Array.} properties 546 | */ 547 | function* process(o, properties) 548 | { 549 | for (var property of properties) { 550 | if (propertyProcessed[property]) 551 | continue; 552 | 553 | var name = property; 554 | if (isSymbol(property)) 555 | name = /** @type {string} */ (injectedScript._describe(property)); 556 | 557 | try { 558 | propertyProcessed[property] = true; 559 | var descriptor = nullifyObjectProto(InjectedScriptHost.suppressWarningsAndCallFunction(Object.getOwnPropertyDescriptor, Object, [o, property])); 560 | if (descriptor) { 561 | if (accessorPropertiesOnly && !("get" in descriptor || "set" in descriptor)) 562 | continue; 563 | if ("get" in descriptor && "set" in descriptor && name != "__proto__" && InjectedScriptHost.isDOMWrapper(object) && !doesAttributeHaveObservableSideEffectOnGet(object, name)) { 564 | descriptor.value = InjectedScriptHost.suppressWarningsAndCallFunction(function(attribute) { return this[attribute]; }, object, [name]); 565 | delete descriptor.get; 566 | delete descriptor.set; 567 | } 568 | } else { 569 | // Not all bindings provide proper descriptors. Fall back to the writable, configurable property. 570 | if (accessorPropertiesOnly) 571 | continue; 572 | try { 573 | descriptor = { name: name, value: o[property], writable: false, configurable: false, enumerable: false, __proto__: null }; 574 | if (o === object) 575 | descriptor.isOwn = true; 576 | yield descriptor; 577 | } catch (e) { 578 | // Silent catch. 579 | } 580 | continue; 581 | } 582 | } catch (e) { 583 | if (accessorPropertiesOnly) 584 | continue; 585 | var descriptor = { __proto__: null }; 586 | descriptor.value = e; 587 | descriptor.wasThrown = true; 588 | } 589 | 590 | descriptor.name = name; 591 | if (o === object) 592 | descriptor.isOwn = true; 593 | if (isSymbol(property)) 594 | descriptor.symbol = property; 595 | yield descriptor; 596 | } 597 | } 598 | 599 | /** 600 | * @param {number} length 601 | */ 602 | function* arrayIndexNames(length) 603 | { 604 | for (var i = 0; i < length; ++i) 605 | yield "" + i; 606 | } 607 | 608 | if (propertyNamesOnly) { 609 | for (var i = 0; i < propertyNamesOnly.length; ++i) { 610 | var name = propertyNamesOnly[i]; 611 | for (var o = object; this._isDefined(o); o = o.__proto__) { 612 | if (InjectedScriptHost.suppressWarningsAndCallFunction(Object.prototype.hasOwnProperty, o, [name])) { 613 | for (var descriptor of process(o, [name])) 614 | yield descriptor; 615 | break; 616 | } 617 | if (ownProperties) 618 | break; 619 | } 620 | } 621 | return; 622 | } 623 | 624 | var skipGetOwnPropertyNames; 625 | try { 626 | skipGetOwnPropertyNames = InjectedScriptHost.isTypedArray(object) && object.length > 500000; 627 | } catch (e) { 628 | } 629 | 630 | for (var o = object; this._isDefined(o); o = o.__proto__) { 631 | if (skipGetOwnPropertyNames && o === object) { 632 | // Avoid OOM crashes from getting all own property names of a large TypedArray. 633 | for (var descriptor of process(o, arrayIndexNames(o.length))) 634 | yield descriptor; 635 | } else { 636 | // First call Object.keys() to enforce ordering of the property descriptors. 637 | for (var descriptor of process(o, Object.keys(/** @type {!Object} */ (o)))) 638 | yield descriptor; 639 | for (var descriptor of process(o, Object.getOwnPropertyNames(/** @type {!Object} */ (o)))) 640 | yield descriptor; 641 | } 642 | if (Object.getOwnPropertySymbols) { 643 | for (var descriptor of process(o, Object.getOwnPropertySymbols(/** @type {!Object} */ (o)))) 644 | yield descriptor; 645 | } 646 | if (ownProperties) { 647 | if (object.__proto__ && !accessorPropertiesOnly) 648 | yield { name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false, isOwn: true, __proto__: null }; 649 | break; 650 | } 651 | } 652 | }, 653 | 654 | /** 655 | * @param {string} expression 656 | * @param {string} objectGroup 657 | * @param {boolean} injectCommandLineAPI 658 | * @param {boolean} returnByValue 659 | * @param {boolean} generatePreview 660 | * @return {*} 661 | */ 662 | evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview) 663 | { 664 | return this._evaluateAndWrap(null, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview); 665 | }, 666 | 667 | /** 668 | * @param {string} objectId 669 | * @param {string} expression 670 | * @param {string} args 671 | * @param {boolean} returnByValue 672 | * @return {!Object|string} 673 | */ 674 | callFunctionOn: function(objectId, expression, args, returnByValue) 675 | { 676 | var parsedObjectId = this._parseObjectId(objectId); 677 | var object = this._objectForId(parsedObjectId); 678 | if (!this._isDefined(object)) 679 | return "Could not find object with given id"; 680 | 681 | if (args) { 682 | var resolvedArgs = []; 683 | var callArgs = /** @type {!Array.} */ (InjectedScriptHost.eval(args)); 684 | for (var i = 0; i < callArgs.length; ++i) { 685 | try { 686 | resolvedArgs[i] = this._resolveCallArgument(callArgs[i]); 687 | } catch (e) { 688 | return toString(e); 689 | } 690 | } 691 | } 692 | 693 | var objectGroup = InjectedScriptHost.idToObjectGroupName(parsedObjectId.id); 694 | 695 | /** 696 | * @suppressReceiverCheck 697 | * @param {*} object 698 | * @param {boolean=} forceValueType 699 | * @param {boolean=} generatePreview 700 | * @param {?Array.=} columnNames 701 | * @param {boolean=} isTable 702 | * @param {*=} customObjectConfig 703 | * @return {!RuntimeAgent.RemoteObject} 704 | * @this {InjectedScript} 705 | */ 706 | function wrap(object, forceValueType, generatePreview, columnNames, isTable, customObjectConfig) 707 | { 708 | return this._wrapObject(object, objectGroup, forceValueType, generatePreview, columnNames, isTable, false, customObjectConfig); 709 | } 710 | 711 | try { 712 | 713 | var remoteObjectAPI = { bindRemoteObject: bind(wrap, this), __proto__: null}; 714 | InjectedScriptHost.setNonEnumProperty(inspectedGlobalObject, "__remoteObjectAPI", remoteObjectAPI); 715 | 716 | var func = InjectedScriptHost.eval("with (typeof __remoteObjectAPI !== 'undefined' ? __remoteObjectAPI : { __proto__: null }) {(" + expression + ")}"); 717 | if (typeof func !== "function") 718 | return "Given expression does not evaluate to a function"; 719 | 720 | return { wasThrown: false, 721 | result: this._wrapObject(InjectedScriptHost.callFunction(func, object, resolvedArgs), objectGroup, returnByValue), 722 | __proto__: null }; 723 | } catch (e) { 724 | return this._createThrownValue(e, objectGroup, false); 725 | } finally { 726 | try { 727 | delete inspectedGlobalObject["__remoteObjectAPI"]; 728 | } catch(e) { 729 | } 730 | } 731 | }, 732 | 733 | /** 734 | * @param {string|undefined} objectGroupName 735 | * @param {*} jsonMLObject 736 | * @throws {string} error message 737 | */ 738 | _substituteObjectTagsInCustomPreview: function(objectGroupName, jsonMLObject) 739 | { 740 | var maxCustomPreviewRecursionDepth = 20; 741 | this._customPreviewRecursionDepth = (this._customPreviewRecursionDepth || 0) + 1 742 | try { 743 | if (this._customPreviewRecursionDepth >= maxCustomPreviewRecursionDepth) 744 | throw new Error("Too deep hierarchy of inlined custom previews"); 745 | 746 | if (!isArrayLike(jsonMLObject)) 747 | return; 748 | 749 | if (jsonMLObject[0] === "object") { 750 | var attributes = jsonMLObject[1]; 751 | var originObject = attributes["object"]; 752 | var config = attributes["config"]; 753 | if (typeof originObject === "undefined") 754 | throw new Error("Illegal format: obligatory attribute \"object\" isn't specified"); 755 | 756 | jsonMLObject[1] = this._wrapObject(originObject, objectGroupName, false, false, null, false, false, config); 757 | return; 758 | } 759 | 760 | for (var i = 0; i < jsonMLObject.length; ++i) 761 | this._substituteObjectTagsInCustomPreview(objectGroupName, jsonMLObject[i]); 762 | } finally { 763 | this._customPreviewRecursionDepth--; 764 | } 765 | }, 766 | 767 | /** 768 | * Resolves a value from CallArgument description. 769 | * @param {!RuntimeAgent.CallArgument} callArgumentJson 770 | * @return {*} resolved value 771 | * @throws {string} error message 772 | */ 773 | _resolveCallArgument: function(callArgumentJson) 774 | { 775 | callArgumentJson = nullifyObjectProto(callArgumentJson); 776 | var objectId = callArgumentJson.objectId; 777 | if (objectId) { 778 | var parsedArgId = this._parseObjectId(objectId); 779 | if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId) 780 | throw "Arguments should belong to the same JavaScript world as the target object."; 781 | 782 | var resolvedArg = this._objectForId(parsedArgId); 783 | if (!this._isDefined(resolvedArg)) 784 | throw "Could not find object with given id"; 785 | 786 | return resolvedArg; 787 | } else if ("value" in callArgumentJson) { 788 | var value = callArgumentJson.value; 789 | if (callArgumentJson.type === "number" && typeof value !== "number") 790 | value = Number(value); 791 | return value; 792 | } 793 | return undefined; 794 | }, 795 | 796 | /** 797 | * @param {?JavaScriptCallFrame} callFrame 798 | * @param {string} expression 799 | * @param {string} objectGroup 800 | * @param {boolean} injectCommandLineAPI 801 | * @param {boolean} returnByValue 802 | * @param {boolean} generatePreview 803 | * @param {!Array.=} scopeChain 804 | * @return {!Object} 805 | */ 806 | _evaluateAndWrap: function(callFrame, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, scopeChain) 807 | { 808 | var wrappedResult = this._evaluateOn(callFrame, objectGroup, expression, injectCommandLineAPI, scopeChain); 809 | if (!wrappedResult.exceptionDetails) { 810 | return { wasThrown: false, 811 | result: this._wrapObject(wrappedResult.result, objectGroup, returnByValue, generatePreview), 812 | __proto__: null }; 813 | } 814 | return this._createThrownValue(wrappedResult.result, objectGroup, generatePreview, wrappedResult.exceptionDetails); 815 | }, 816 | 817 | /** 818 | * @param {*} value 819 | * @param {string|undefined} objectGroup 820 | * @param {boolean} generatePreview 821 | * @param {!DebuggerAgent.ExceptionDetails=} exceptionDetails 822 | * @return {!Object} 823 | */ 824 | _createThrownValue: function(value, objectGroup, generatePreview, exceptionDetails) 825 | { 826 | var remoteObject = this._wrapObject(value, objectGroup, false, generatePreview && InjectedScriptHost.subtype(value) !== "error"); 827 | if (!remoteObject.description){ 828 | try { 829 | remoteObject.description = toStringDescription(value); 830 | } catch (e) {} 831 | } 832 | return { wasThrown: true, result: remoteObject, exceptionDetails: exceptionDetails, __proto__: null }; 833 | }, 834 | 835 | /** 836 | * @param {?JavaScriptCallFrame} callFrame 837 | * @param {string} objectGroup 838 | * @param {string} expression 839 | * @param {boolean} injectCommandLineAPI 840 | * @param {!Array.=} scopeChain 841 | * @return {*} 842 | */ 843 | _evaluateOn: function(callFrame, objectGroup, expression, injectCommandLineAPI, scopeChain) 844 | { 845 | // Only install command line api object for the time of evaluation. 846 | // Surround the expression in with statements to inject our command line API so that 847 | // the window object properties still take more precedent than our API functions. 848 | 849 | var scopeExtensionForEval = (callFrame && injectCommandLineAPI) ? new CommandLineAPI(this._commandLineAPIImpl, callFrame) : undefined; 850 | 851 | injectCommandLineAPI = !scopeExtensionForEval && !callFrame && injectCommandLineAPI && !("__commandLineAPI" in inspectedGlobalObject); 852 | var injectScopeChain = scopeChain && scopeChain.length && !("__scopeChainForEval" in inspectedGlobalObject); 853 | 854 | try { 855 | var prefix = ""; 856 | var suffix = ""; 857 | if (injectCommandLineAPI) { 858 | InjectedScriptHost.setNonEnumProperty(inspectedGlobalObject, "__commandLineAPI", new CommandLineAPI(this._commandLineAPIImpl, callFrame)); 859 | prefix = "with (typeof __commandLineAPI !== 'undefined' ? __commandLineAPI : { __proto__: null }) {"; 860 | suffix = "}"; 861 | } 862 | if (injectScopeChain) { 863 | InjectedScriptHost.setNonEnumProperty(inspectedGlobalObject, "__scopeChainForEval", scopeChain); 864 | for (var i = 0; i < scopeChain.length; ++i) { 865 | prefix = "with (typeof __scopeChainForEval !== 'undefined' ? __scopeChainForEval[" + i + "] : { __proto__: null }) {" + (suffix ? " " : "") + prefix; 866 | if (suffix) 867 | suffix += " }"; 868 | else 869 | suffix = "}"; 870 | } 871 | } 872 | 873 | if (prefix) 874 | expression = prefix + "\n" + expression + "\n" + suffix; 875 | var wrappedResult = callFrame ? callFrame.evaluateWithExceptionDetails(expression, scopeExtensionForEval) : InjectedScriptHost.evaluateWithExceptionDetails(expression); 876 | if (objectGroup === "console" && !wrappedResult.exceptionDetails) 877 | this._lastResult = wrappedResult.result; 878 | return wrappedResult; 879 | } finally { 880 | if (injectCommandLineAPI) { 881 | try { 882 | delete inspectedGlobalObject["__commandLineAPI"]; 883 | } catch(e) { 884 | } 885 | } 886 | if (injectScopeChain) { 887 | try { 888 | delete inspectedGlobalObject["__scopeChainForEval"]; 889 | } catch(e) { 890 | } 891 | } 892 | } 893 | }, 894 | 895 | /** 896 | * @param {?Object} callFrame 897 | * @param {number} asyncOrdinal 898 | * @return {!Array.|boolean} 899 | */ 900 | wrapCallFrames: function(callFrame, asyncOrdinal) 901 | { 902 | if (!callFrame) 903 | return false; 904 | 905 | var result = []; 906 | var depth = 0; 907 | do { 908 | result[depth] = new InjectedScript.CallFrameProxy(depth, callFrame, asyncOrdinal); 909 | callFrame = callFrame.caller; 910 | ++depth; 911 | } while (callFrame); 912 | return result; 913 | }, 914 | 915 | /** 916 | * @param {!JavaScriptCallFrame} topCallFrame 917 | * @param {boolean} isAsyncStack 918 | * @param {string} callFrameId 919 | * @param {string} expression 920 | * @param {string} objectGroup 921 | * @param {boolean} injectCommandLineAPI 922 | * @param {boolean} returnByValue 923 | * @param {boolean} generatePreview 924 | * @return {*} 925 | */ 926 | evaluateOnCallFrame: function(topCallFrame, isAsyncStack, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview) 927 | { 928 | var callFrame = this._callFrameForId(topCallFrame, callFrameId); 929 | if (!callFrame) 930 | return "Could not find call frame with given id"; 931 | if (isAsyncStack) 932 | return this._evaluateAndWrap(null, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, callFrame.scopeChain); 933 | return this._evaluateAndWrap(callFrame, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview); 934 | }, 935 | 936 | /** 937 | * @param {!JavaScriptCallFrame} topCallFrame 938 | * @param {string} callFrameId 939 | * @return {*} 940 | */ 941 | restartFrame: function(topCallFrame, callFrameId) 942 | { 943 | var callFrame = this._callFrameForId(topCallFrame, callFrameId); 944 | if (!callFrame) 945 | return "Could not find call frame with given id"; 946 | return callFrame.restart(); 947 | }, 948 | 949 | /** 950 | * @param {!JavaScriptCallFrame} topCallFrame 951 | * @param {string} callFrameId 952 | * @return {*} a stepIn position array ready for protocol JSON or a string error 953 | */ 954 | getStepInPositions: function(topCallFrame, callFrameId) 955 | { 956 | var callFrame = this._callFrameForId(topCallFrame, callFrameId); 957 | if (!callFrame) 958 | return "Could not find call frame with given id"; 959 | var stepInPositionsUnpacked = JSON.parse(callFrame.stepInPositions); 960 | if (typeof stepInPositionsUnpacked !== "object") 961 | return "Step in positions not available"; 962 | return stepInPositionsUnpacked; 963 | }, 964 | 965 | /** 966 | * Either callFrameId or functionObjectId must be specified. 967 | * @param {!JavaScriptCallFrame} topCallFrame 968 | * @param {string|boolean} callFrameId or false 969 | * @param {string|boolean} functionObjectId or false 970 | * @param {number} scopeNumber 971 | * @param {string} variableName 972 | * @param {string} newValueJsonString RuntimeAgent.CallArgument structure serialized as string 973 | * @return {string|undefined} undefined if success or an error message 974 | */ 975 | setVariableValue: function(topCallFrame, callFrameId, functionObjectId, scopeNumber, variableName, newValueJsonString) 976 | { 977 | try { 978 | var newValueJson = /** @type {!RuntimeAgent.CallArgument} */ (InjectedScriptHost.eval("(" + newValueJsonString + ")")); 979 | var resolvedValue = this._resolveCallArgument(newValueJson); 980 | if (typeof callFrameId === "string") { 981 | var callFrame = this._callFrameForId(topCallFrame, callFrameId); 982 | if (!callFrame) 983 | return "Could not find call frame with given id"; 984 | callFrame.setVariableValue(scopeNumber, variableName, resolvedValue) 985 | } else { 986 | var parsedFunctionId = this._parseObjectId(/** @type {string} */ (functionObjectId)); 987 | var func = this._objectForId(parsedFunctionId); 988 | if (typeof func !== "function") 989 | return "Could not resolve function by id"; 990 | InjectedScriptHost.setFunctionVariableValue(func, scopeNumber, variableName, resolvedValue); 991 | } 992 | } catch (e) { 993 | return toString(e); 994 | } 995 | return undefined; 996 | }, 997 | 998 | /** 999 | * @param {!JavaScriptCallFrame} topCallFrame 1000 | * @param {string} callFrameId 1001 | * @return {?JavaScriptCallFrame} 1002 | */ 1003 | _callFrameForId: function(topCallFrame, callFrameId) 1004 | { 1005 | var parsedCallFrameId = nullifyObjectProto(/** @type {!Object} */ (InjectedScriptHost.eval("(" + callFrameId + ")"))); 1006 | var ordinal = parsedCallFrameId["ordinal"]; 1007 | var callFrame = topCallFrame; 1008 | while (--ordinal >= 0 && callFrame) 1009 | callFrame = callFrame.caller; 1010 | return callFrame; 1011 | }, 1012 | 1013 | /** 1014 | * @param {!Object} objectId 1015 | * @return {!Object|symbol|undefined} 1016 | */ 1017 | _objectForId: function(objectId) 1018 | { 1019 | return objectId.injectedScriptId === injectedScriptId ? /** @type{!Object|symbol|undefined} */ (InjectedScriptHost.objectForId(objectId.id)) : void 0; 1020 | }, 1021 | 1022 | /** 1023 | * @param {*} object 1024 | * @return {boolean} 1025 | */ 1026 | _isDefined: function(object) 1027 | { 1028 | return !!object || this._isHTMLAllCollection(object); 1029 | }, 1030 | 1031 | /** 1032 | * @param {*} object 1033 | * @return {boolean} 1034 | */ 1035 | _isHTMLAllCollection: function(object) 1036 | { 1037 | // document.all is reported as undefined, but we still want to process it. 1038 | return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object); 1039 | }, 1040 | 1041 | /** 1042 | * @param {*} obj 1043 | * @return {?string} 1044 | */ 1045 | _subtype: function(obj) 1046 | { 1047 | if (obj === null) 1048 | return "null"; 1049 | 1050 | if (this.isPrimitiveValue(obj)) 1051 | return null; 1052 | 1053 | var subtype = InjectedScriptHost.subtype(obj); 1054 | if (subtype) 1055 | return subtype; 1056 | 1057 | if (isArrayLike(obj)) 1058 | return "array"; 1059 | 1060 | // If owning frame has navigated to somewhere else window properties will be undefined. 1061 | return null; 1062 | }, 1063 | 1064 | /** 1065 | * @param {*} obj 1066 | * @return {?string} 1067 | */ 1068 | _describe: function(obj) 1069 | { 1070 | if (this.isPrimitiveValue(obj)) 1071 | return null; 1072 | 1073 | var subtype = this._subtype(obj); 1074 | 1075 | if (subtype === "regexp") 1076 | return toString(obj); 1077 | 1078 | if (subtype === "date") 1079 | return toString(obj); 1080 | 1081 | if (subtype === "node") { 1082 | var description = obj.nodeName.toLowerCase(); 1083 | switch (obj.nodeType) { 1084 | case 1 /* Node.ELEMENT_NODE */: 1085 | description += obj.id ? "#" + obj.id : ""; 1086 | var className = obj.className; 1087 | description += (className && typeof className === "string") ? "." + className.trim().replace(/\s+/g, ".") : ""; 1088 | break; 1089 | case 10 /*Node.DOCUMENT_TYPE_NODE */: 1090 | description = ""; 1091 | break; 1092 | } 1093 | return description; 1094 | } 1095 | 1096 | var className = InjectedScriptHost.internalConstructorName(obj); 1097 | if (subtype === "array") { 1098 | if (typeof obj.length === "number") 1099 | className += "[" + obj.length + "]"; 1100 | return className; 1101 | } 1102 | 1103 | // NodeList in JSC is a function, check for array prior to this. 1104 | if (typeof obj === "function") 1105 | return toString(obj); 1106 | 1107 | if (isSymbol(obj)) { 1108 | try { 1109 | return /** @type {string} */ (InjectedScriptHost.callFunction(Symbol.prototype.toString, obj)) || "Symbol"; 1110 | } catch (e) { 1111 | return "Symbol"; 1112 | } 1113 | } 1114 | 1115 | if (InjectedScriptHost.subtype(obj) === "error") { 1116 | try { 1117 | var stack = obj.stack; 1118 | var message = obj.message && obj.message.length ? ": " + obj.message : ""; 1119 | var firstCallFrame = /^\s+at\s/m.exec(stack); 1120 | var stackMessageEnd = firstCallFrame ? firstCallFrame.index : -1; 1121 | if (stackMessageEnd !== -1) { 1122 | var stackTrace = stack.substr(stackMessageEnd); 1123 | return className + message + "\n" + stackTrace; 1124 | } 1125 | return className + message; 1126 | } catch(e) { 1127 | } 1128 | } 1129 | 1130 | return className; 1131 | }, 1132 | 1133 | /** 1134 | * @param {boolean} enabled 1135 | */ 1136 | setCustomObjectFormatterEnabled: function(enabled) 1137 | { 1138 | this._customObjectFormatterEnabled = enabled; 1139 | } 1140 | } 1141 | 1142 | /** 1143 | * @type {!InjectedScript} 1144 | * @const 1145 | */ 1146 | var injectedScript = new InjectedScript(); 1147 | 1148 | /** 1149 | * @constructor 1150 | * @param {*} object 1151 | * @param {string=} objectGroupName 1152 | * @param {boolean=} doNotBind 1153 | * @param {boolean=} forceValueType 1154 | * @param {boolean=} generatePreview 1155 | * @param {?Array.=} columnNames 1156 | * @param {boolean=} isTable 1157 | * @param {boolean=} skipEntriesPreview 1158 | * @param {*=} customObjectConfig 1159 | */ 1160 | InjectedScript.RemoteObject = function(object, objectGroupName, doNotBind, forceValueType, generatePreview, columnNames, isTable, skipEntriesPreview, customObjectConfig) 1161 | { 1162 | this.type = typeof object; 1163 | if (this.type === "undefined" && injectedScript._isHTMLAllCollection(object)) 1164 | this.type = "object"; 1165 | 1166 | if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) { 1167 | // We don't send undefined values over JSON. 1168 | if (this.type !== "undefined") 1169 | this.value = object; 1170 | 1171 | // Null object is object with 'null' subtype. 1172 | if (object === null) 1173 | this.subtype = "null"; 1174 | 1175 | // Provide user-friendly number values. 1176 | if (this.type === "number") { 1177 | this.description = toStringDescription(object); 1178 | // Override "value" property for values that can not be JSON-stringified. 1179 | switch (this.description) { 1180 | case "NaN": 1181 | case "Infinity": 1182 | case "-Infinity": 1183 | case "-0": 1184 | this.value = this.description; 1185 | break; 1186 | } 1187 | } 1188 | 1189 | return; 1190 | } 1191 | 1192 | object = /** @type {!Object} */ (object); 1193 | 1194 | if (!doNotBind) 1195 | this.objectId = injectedScript._bind(object, objectGroupName); 1196 | var subtype = injectedScript._subtype(object); 1197 | if (subtype) 1198 | this.subtype = subtype; 1199 | var className = InjectedScriptHost.internalConstructorName(object); 1200 | if (className) 1201 | this.className = className; 1202 | this.description = injectedScript._describe(object); 1203 | 1204 | if (generatePreview && this.type === "object" && this.subtype !== "node") 1205 | this.preview = this._generatePreview(object, undefined, columnNames, isTable, skipEntriesPreview); 1206 | 1207 | if (injectedScript._customObjectFormatterEnabled) { 1208 | var customPreview = this._customPreview(object, objectGroupName, customObjectConfig); 1209 | if (customPreview) 1210 | this.customPreview = customPreview; 1211 | } 1212 | } 1213 | 1214 | InjectedScript.RemoteObject.prototype = { 1215 | 1216 | /** 1217 | * @param {*} object 1218 | * @param {string=} objectGroupName 1219 | * @param {*=} customObjectConfig 1220 | * @return {?RuntimeAgent.CustomPreview} 1221 | */ 1222 | _customPreview: function(object, objectGroupName, customObjectConfig) 1223 | { 1224 | /** 1225 | * @param {!Error} error 1226 | */ 1227 | function logError(error) 1228 | { 1229 | Promise.resolve().then(inspectedGlobalObject.console.error.bind(inspectedGlobalObject.console, "Custom Formatter Failed: " + error.message)); 1230 | } 1231 | 1232 | try { 1233 | var formatters = inspectedGlobalObject["devtoolsFormatters"]; 1234 | if (!formatters || !isArrayLike(formatters)) 1235 | return null; 1236 | 1237 | for (var i = 0; i < formatters.length; ++i) { 1238 | try { 1239 | var formatted = formatters[i].header(object, customObjectConfig); 1240 | if (!formatted) 1241 | continue; 1242 | 1243 | var hasBody = formatters[i].hasBody(object, customObjectConfig); 1244 | injectedScript._substituteObjectTagsInCustomPreview(objectGroupName, formatted); 1245 | var formatterObjectId = injectedScript._bind(formatters[i], objectGroupName); 1246 | var result = {header: JSON.stringify(formatted), hasBody: !!hasBody, formatterObjectId: formatterObjectId}; 1247 | if (customObjectConfig) 1248 | result["configObjectId"] = injectedScript._bind(customObjectConfig, objectGroupName); 1249 | return result; 1250 | } catch (e) { 1251 | logError(e); 1252 | } 1253 | } 1254 | } catch (e) { 1255 | logError(e); 1256 | } 1257 | return null; 1258 | }, 1259 | 1260 | /** 1261 | * @return {!RuntimeAgent.ObjectPreview} preview 1262 | */ 1263 | _createEmptyPreview: function() 1264 | { 1265 | var preview = { 1266 | type: /** @type {!RuntimeAgent.ObjectPreviewType.} */ (this.type), 1267 | description: this.description || toStringDescription(this.value), 1268 | lossless: true, 1269 | overflow: false, 1270 | properties: [], 1271 | __proto__: null 1272 | }; 1273 | if (this.subtype) 1274 | preview.subtype = /** @type {!RuntimeAgent.ObjectPreviewSubtype.} */ (this.subtype); 1275 | return preview; 1276 | }, 1277 | 1278 | /** 1279 | * @param {!Object} object 1280 | * @param {?Array.=} firstLevelKeys 1281 | * @param {?Array.=} secondLevelKeys 1282 | * @param {boolean=} isTable 1283 | * @param {boolean=} skipEntriesPreview 1284 | * @return {!RuntimeAgent.ObjectPreview} preview 1285 | */ 1286 | _generatePreview: function(object, firstLevelKeys, secondLevelKeys, isTable, skipEntriesPreview) 1287 | { 1288 | var preview = this._createEmptyPreview(); 1289 | var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0; 1290 | 1291 | var propertiesThreshold = { 1292 | properties: isTable ? 1000 : max(5, firstLevelKeysCount), 1293 | indexes: isTable ? 1000 : max(100, firstLevelKeysCount), 1294 | __proto__: null 1295 | }; 1296 | 1297 | try { 1298 | var descriptors = injectedScript._propertyDescriptors(object, undefined, undefined, firstLevelKeys); 1299 | 1300 | this._appendPropertyDescriptors(preview, descriptors, propertiesThreshold, secondLevelKeys, isTable); 1301 | if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) 1302 | return preview; 1303 | 1304 | // Add internal properties to preview. 1305 | var rawInternalProperties = InjectedScriptHost.getInternalProperties(object) || []; 1306 | var internalProperties = []; 1307 | for (var i = 0; i < rawInternalProperties.length; i += 2) { 1308 | push(internalProperties, { 1309 | name: rawInternalProperties[i], 1310 | value: rawInternalProperties[i + 1], 1311 | isOwn: true, 1312 | enumerable: true, 1313 | __proto__: null 1314 | }); 1315 | } 1316 | this._appendPropertyDescriptors(preview, internalProperties, propertiesThreshold, secondLevelKeys, isTable); 1317 | 1318 | if (this.subtype === "map" || this.subtype === "set" || this.subtype === "iterator") 1319 | this._appendEntriesPreview(object, preview, skipEntriesPreview); 1320 | 1321 | } catch (e) { 1322 | preview.lossless = false; 1323 | } 1324 | 1325 | return preview; 1326 | }, 1327 | 1328 | /** 1329 | * @param {!RuntimeAgent.ObjectPreview} preview 1330 | * @param {!Array.<*>|!Iterable.<*>} descriptors 1331 | * @param {!Object} propertiesThreshold 1332 | * @param {?Array.=} secondLevelKeys 1333 | * @param {boolean=} isTable 1334 | */ 1335 | _appendPropertyDescriptors: function(preview, descriptors, propertiesThreshold, secondLevelKeys, isTable) 1336 | { 1337 | for (var descriptor of descriptors) { 1338 | if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) 1339 | break; 1340 | if (!descriptor) 1341 | continue; 1342 | if (descriptor.wasThrown) { 1343 | preview.lossless = false; 1344 | continue; 1345 | } 1346 | 1347 | var name = descriptor.name; 1348 | 1349 | // Ignore __proto__ property, stay lossless. 1350 | if (name === "__proto__") 1351 | continue; 1352 | 1353 | // Ignore non-enumerable members on prototype, stay lossless. 1354 | if (!descriptor.isOwn && !descriptor.enumerable) 1355 | continue; 1356 | 1357 | // Ignore length property of array, stay lossless. 1358 | if (this.subtype === "array" && name === "length") 1359 | continue; 1360 | 1361 | // Ignore size property of map, set, stay lossless. 1362 | if ((this.subtype === "map" || this.subtype === "set") && name === "size") 1363 | continue; 1364 | 1365 | // Never preview prototype properties, turn lossy. 1366 | if (!descriptor.isOwn) { 1367 | preview.lossless = false; 1368 | continue; 1369 | } 1370 | 1371 | // Ignore computed properties, turn lossy. 1372 | if (!("value" in descriptor)) { 1373 | preview.lossless = false; 1374 | continue; 1375 | } 1376 | 1377 | var value = descriptor.value; 1378 | var type = typeof value; 1379 | 1380 | // Never render functions in object preview, turn lossy 1381 | if (type === "function" && (this.subtype !== "array" || !isUInt32(name))) { 1382 | preview.lossless = false; 1383 | continue; 1384 | } 1385 | 1386 | // Special-case HTMLAll. 1387 | if (type === "undefined" && injectedScript._isHTMLAllCollection(value)) 1388 | type = "object"; 1389 | 1390 | // Render own properties. 1391 | if (value === null) { 1392 | this._appendPropertyPreview(preview, { name: name, type: "object", subtype: "null", value: "null", __proto__: null }, propertiesThreshold); 1393 | continue; 1394 | } 1395 | 1396 | var maxLength = 100; 1397 | if (InjectedScript.primitiveTypes[type]) { 1398 | if (type === "string" && value.length > maxLength) { 1399 | value = this._abbreviateString(value, maxLength, true); 1400 | preview.lossless = false; 1401 | } 1402 | this._appendPropertyPreview(preview, { name: name, type: type, value: toStringDescription(value), __proto__: null }, propertiesThreshold); 1403 | continue; 1404 | } 1405 | 1406 | var property = { name: name, type: type, __proto__: null }; 1407 | var subtype = injectedScript._subtype(value); 1408 | if (subtype) 1409 | property.subtype = subtype; 1410 | 1411 | if (secondLevelKeys === null || secondLevelKeys) { 1412 | var subPreview = this._generatePreview(value, secondLevelKeys || undefined, undefined, isTable); 1413 | property.valuePreview = subPreview; 1414 | if (!subPreview.lossless) 1415 | preview.lossless = false; 1416 | if (subPreview.overflow) 1417 | preview.overflow = true; 1418 | } else { 1419 | var description = ""; 1420 | if (type !== "function") 1421 | description = this._abbreviateString(/** @type {string} */ (injectedScript._describe(value)), maxLength, subtype === "regexp"); 1422 | property.value = description; 1423 | preview.lossless = false; 1424 | } 1425 | this._appendPropertyPreview(preview, property, propertiesThreshold); 1426 | } 1427 | }, 1428 | 1429 | /** 1430 | * @param {!RuntimeAgent.ObjectPreview} preview 1431 | * @param {!Object} property 1432 | * @param {!Object} propertiesThreshold 1433 | */ 1434 | _appendPropertyPreview: function(preview, property, propertiesThreshold) 1435 | { 1436 | if (toString(property.name >>> 0) === property.name) 1437 | propertiesThreshold.indexes--; 1438 | else 1439 | propertiesThreshold.properties--; 1440 | if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) { 1441 | preview.overflow = true; 1442 | preview.lossless = false; 1443 | } else { 1444 | push(preview.properties, property); 1445 | } 1446 | }, 1447 | 1448 | /** 1449 | * @param {!Object} object 1450 | * @param {!RuntimeAgent.ObjectPreview} preview 1451 | * @param {boolean=} skipEntriesPreview 1452 | */ 1453 | _appendEntriesPreview: function(object, preview, skipEntriesPreview) 1454 | { 1455 | var entries = InjectedScriptHost.collectionEntries(object); 1456 | if (!entries) 1457 | return; 1458 | if (skipEntriesPreview) { 1459 | if (entries.length) { 1460 | preview.overflow = true; 1461 | preview.lossless = false; 1462 | } 1463 | return; 1464 | } 1465 | preview.entries = []; 1466 | var entriesThreshold = 5; 1467 | for (var i = 0; i < entries.length; ++i) { 1468 | if (preview.entries.length >= entriesThreshold) { 1469 | preview.overflow = true; 1470 | preview.lossless = false; 1471 | break; 1472 | } 1473 | var entry = nullifyObjectProto(entries[i]); 1474 | var previewEntry = { 1475 | value: generateValuePreview(entry.value), 1476 | __proto__: null 1477 | }; 1478 | if ("key" in entry) 1479 | previewEntry.key = generateValuePreview(entry.key); 1480 | push(preview.entries, previewEntry); 1481 | } 1482 | 1483 | /** 1484 | * @param {*} value 1485 | * @return {!RuntimeAgent.ObjectPreview} 1486 | */ 1487 | function generateValuePreview(value) 1488 | { 1489 | var remoteObject = new InjectedScript.RemoteObject(value, undefined, true, undefined, true, undefined, undefined, true); 1490 | var valuePreview = remoteObject.preview || remoteObject._createEmptyPreview(); 1491 | if (!valuePreview.lossless) 1492 | preview.lossless = false; 1493 | return valuePreview; 1494 | } 1495 | }, 1496 | 1497 | /** 1498 | * @param {string} string 1499 | * @param {number} maxLength 1500 | * @param {boolean=} middle 1501 | * @return {string} 1502 | */ 1503 | _abbreviateString: function(string, maxLength, middle) 1504 | { 1505 | if (string.length <= maxLength) 1506 | return string; 1507 | if (middle) { 1508 | var leftHalf = maxLength >> 1; 1509 | var rightHalf = maxLength - leftHalf - 1; 1510 | return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf); 1511 | } 1512 | return string.substr(0, maxLength) + "\u2026"; 1513 | }, 1514 | 1515 | __proto__: null 1516 | } 1517 | 1518 | /** 1519 | * @constructor 1520 | * @param {number} ordinal 1521 | * @param {!JavaScriptCallFrame} callFrame 1522 | * @param {number} asyncOrdinal 1523 | */ 1524 | InjectedScript.CallFrameProxy = function(ordinal, callFrame, asyncOrdinal) 1525 | { 1526 | this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + (asyncOrdinal ? ",\"asyncOrdinal\":" + asyncOrdinal : "") + "}"; 1527 | this.functionName = callFrame.functionName; 1528 | this.functionLocation = { scriptId: toString(callFrame.sourceID), lineNumber: callFrame.functionLine, columnNumber: callFrame.functionColumn, __proto__: null }; 1529 | this.location = { scriptId: toString(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column, __proto__: null }; 1530 | this.scopeChain = this._wrapScopeChain(callFrame); 1531 | this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace"); 1532 | if (callFrame.isAtReturn) 1533 | this.returnValue = injectedScript._wrapObject(callFrame.returnValue, "backtrace"); 1534 | } 1535 | 1536 | InjectedScript.CallFrameProxy.prototype = { 1537 | /** 1538 | * @param {!JavaScriptCallFrame} callFrame 1539 | * @return {!Array.} 1540 | */ 1541 | _wrapScopeChain: function(callFrame) 1542 | { 1543 | var scopeChain = callFrame.scopeChain; 1544 | var scopeChainProxy = []; 1545 | for (var i = 0; i < scopeChain.length; ++i) 1546 | scopeChainProxy[i] = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace"); 1547 | return scopeChainProxy; 1548 | }, 1549 | 1550 | __proto__: null 1551 | } 1552 | 1553 | /** 1554 | * @const 1555 | * @type {!Object.} 1556 | */ 1557 | InjectedScript.CallFrameProxy._scopeTypeNames = { 1558 | 0: "global", 1559 | 1: "local", 1560 | 2: "with", 1561 | 3: "closure", 1562 | 4: "catch", 1563 | 5: "block", 1564 | 6: "script", 1565 | __proto__: null 1566 | }; 1567 | 1568 | /** 1569 | * @param {number} scopeTypeCode 1570 | * @param {*} scopeObject 1571 | * @param {string} groupId 1572 | * @return {!DebuggerAgent.Scope} 1573 | */ 1574 | InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId) 1575 | { 1576 | return { 1577 | object: injectedScript._wrapObject(scopeObject, groupId), 1578 | type: InjectedScript.CallFrameProxy._scopeTypeNames[scopeTypeCode], 1579 | __proto__: null 1580 | }; 1581 | } 1582 | 1583 | /** 1584 | * @constructor 1585 | * @param {!CommandLineAPIImpl} commandLineAPIImpl 1586 | * @param {?JavaScriptCallFrame} callFrame 1587 | */ 1588 | function CommandLineAPI(commandLineAPIImpl, callFrame) 1589 | { 1590 | /** 1591 | * @param {string} member 1592 | * @return {boolean} 1593 | */ 1594 | function inScopeVariables(member) 1595 | { 1596 | if (!callFrame) 1597 | return (member in inspectedGlobalObject); 1598 | 1599 | var scopeChain = callFrame.scopeChain; 1600 | for (var i = 0; i < scopeChain.length; ++i) { 1601 | if (member in scopeChain[i]) 1602 | return true; 1603 | } 1604 | return false; 1605 | } 1606 | 1607 | /** 1608 | * @param {string} name The name of the method for which a toString method should be generated. 1609 | * @return {function():string} 1610 | */ 1611 | function customToStringMethod(name) 1612 | { 1613 | return function() 1614 | { 1615 | var funcArgsSyntax = ""; 1616 | try { 1617 | var funcSyntax = "" + commandLineAPIImpl[name]; 1618 | funcSyntax = funcSyntax.replace(/\n/g, " "); 1619 | funcSyntax = funcSyntax.replace(/^function[^\(]*\(([^\)]*)\).*$/, "$1"); 1620 | funcSyntax = funcSyntax.replace(/\s*,\s*/g, ", "); 1621 | funcSyntax = funcSyntax.replace(/\bopt_(\w+)\b/g, "[$1]"); 1622 | funcArgsSyntax = funcSyntax.trim(); 1623 | } catch (e) { 1624 | } 1625 | return "function " + name + "(" + funcArgsSyntax + ") { [Command Line API] }"; 1626 | }; 1627 | } 1628 | 1629 | for (var i = 0; i < CommandLineAPI.members_.length; ++i) { 1630 | var member = CommandLineAPI.members_[i]; 1631 | if (inScopeVariables(member)) 1632 | continue; 1633 | 1634 | this[member] = bind(commandLineAPIImpl[member], commandLineAPIImpl); 1635 | this[member].toString = customToStringMethod(member); 1636 | } 1637 | 1638 | for (var i = 0; i < 5; ++i) { 1639 | var member = "$" + i; 1640 | if (inScopeVariables(member)) 1641 | continue; 1642 | 1643 | this.__defineGetter__("$" + i, bind(commandLineAPIImpl._inspectedObject, commandLineAPIImpl, i)); 1644 | } 1645 | 1646 | this.$_ = injectedScript._lastResult; 1647 | 1648 | this.__proto__ = null; 1649 | } 1650 | 1651 | // NOTE: Please keep the list of API methods below snchronized to that in WebInspector.RuntimeModel! 1652 | // NOTE: Argument names of these methods will be printed in the console, so use pretty names! 1653 | /** 1654 | * @type {!Array.} 1655 | * @const 1656 | */ 1657 | CommandLineAPI.members_ = [ 1658 | "$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd", 1659 | "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", "getEventListeners", 1660 | "debug", "undebug", "monitor", "unmonitor", "table" 1661 | ]; 1662 | 1663 | /** 1664 | * @constructor 1665 | */ 1666 | function CommandLineAPIImpl() 1667 | { 1668 | } 1669 | 1670 | CommandLineAPIImpl.prototype = { 1671 | /** 1672 | * @param {string} selector 1673 | * @param {!Node=} opt_startNode 1674 | * @return {*} 1675 | */ 1676 | $: function (selector, opt_startNode) 1677 | { 1678 | if (this._canQuerySelectorOnNode(opt_startNode)) 1679 | return opt_startNode.querySelector(selector); 1680 | 1681 | return inspectedGlobalObject.document.querySelector(selector); 1682 | }, 1683 | 1684 | /** 1685 | * @param {string} selector 1686 | * @param {!Node=} opt_startNode 1687 | * @return {*} 1688 | */ 1689 | $$: function (selector, opt_startNode) 1690 | { 1691 | if (this._canQuerySelectorOnNode(opt_startNode)) 1692 | return slice(opt_startNode.querySelectorAll(selector)); 1693 | return slice(inspectedGlobalObject.document.querySelectorAll(selector)); 1694 | }, 1695 | 1696 | /** 1697 | * @param {!Node=} node 1698 | * @return {boolean} 1699 | */ 1700 | _canQuerySelectorOnNode: function(node) 1701 | { 1702 | return !!node && InjectedScriptHost.subtype(node) === "node" && (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE); 1703 | }, 1704 | 1705 | /** 1706 | * @param {string} xpath 1707 | * @param {!Node=} opt_startNode 1708 | * @return {*} 1709 | */ 1710 | $x: function(xpath, opt_startNode) 1711 | { 1712 | var doc = (opt_startNode && opt_startNode.ownerDocument) || inspectedGlobalObject.document; 1713 | var result = doc.evaluate(xpath, opt_startNode || doc, null, XPathResult.ANY_TYPE, null); 1714 | switch (result.resultType) { 1715 | case XPathResult.NUMBER_TYPE: 1716 | return result.numberValue; 1717 | case XPathResult.STRING_TYPE: 1718 | return result.stringValue; 1719 | case XPathResult.BOOLEAN_TYPE: 1720 | return result.booleanValue; 1721 | default: 1722 | var nodes = []; 1723 | var node; 1724 | while (node = result.iterateNext()) 1725 | push(nodes, node); 1726 | return nodes; 1727 | } 1728 | }, 1729 | 1730 | /** 1731 | * @return {*} 1732 | */ 1733 | dir: function(var_args) 1734 | { 1735 | return InjectedScriptHost.callFunction(inspectedGlobalObject.console.dir, inspectedGlobalObject.console, slice(arguments)); 1736 | }, 1737 | 1738 | /** 1739 | * @return {*} 1740 | */ 1741 | dirxml: function(var_args) 1742 | { 1743 | return InjectedScriptHost.callFunction(inspectedGlobalObject.console.dirxml, inspectedGlobalObject.console, slice(arguments)); 1744 | }, 1745 | 1746 | /** 1747 | * @return {!Array.} 1748 | */ 1749 | keys: function(object) 1750 | { 1751 | return Object.keys(object); 1752 | }, 1753 | 1754 | /** 1755 | * @return {!Array.<*>} 1756 | */ 1757 | values: function(object) 1758 | { 1759 | var result = []; 1760 | for (var key in object) 1761 | push(result, object[key]); 1762 | return result; 1763 | }, 1764 | 1765 | /** 1766 | * @return {*} 1767 | */ 1768 | profile: function(opt_title) 1769 | { 1770 | return InjectedScriptHost.callFunction(inspectedGlobalObject.console.profile, inspectedGlobalObject.console, slice(arguments)); 1771 | }, 1772 | 1773 | /** 1774 | * @return {*} 1775 | */ 1776 | profileEnd: function(opt_title) 1777 | { 1778 | return InjectedScriptHost.callFunction(inspectedGlobalObject.console.profileEnd, inspectedGlobalObject.console, slice(arguments)); 1779 | }, 1780 | 1781 | /** 1782 | * @param {!Object} object 1783 | * @param {!Array.|string=} opt_types 1784 | */ 1785 | monitorEvents: function(object, opt_types) 1786 | { 1787 | if (!object || !object.addEventListener || !object.removeEventListener) 1788 | return; 1789 | var types = this._normalizeEventTypes(opt_types); 1790 | for (var i = 0; i < types.length; ++i) { 1791 | object.removeEventListener(types[i], this._logEvent, false); 1792 | object.addEventListener(types[i], this._logEvent, false); 1793 | } 1794 | }, 1795 | 1796 | /** 1797 | * @param {!Object} object 1798 | * @param {!Array.|string=} opt_types 1799 | */ 1800 | unmonitorEvents: function(object, opt_types) 1801 | { 1802 | if (!object || !object.addEventListener || !object.removeEventListener) 1803 | return; 1804 | var types = this._normalizeEventTypes(opt_types); 1805 | for (var i = 0; i < types.length; ++i) 1806 | object.removeEventListener(types[i], this._logEvent, false); 1807 | }, 1808 | 1809 | /** 1810 | * @param {*} object 1811 | * @return {*} 1812 | */ 1813 | inspect: function(object) 1814 | { 1815 | return injectedScript._inspect(object); 1816 | }, 1817 | 1818 | copy: function(object) 1819 | { 1820 | var string; 1821 | if (injectedScript._subtype(object) === "node") { 1822 | string = object.outerHTML; 1823 | } else if (injectedScript.isPrimitiveValue(object)) { 1824 | string = toString(object); 1825 | } else { 1826 | try { 1827 | string = JSON.stringify(object, null, " "); 1828 | } catch (e) { 1829 | string = toString(object); 1830 | } 1831 | } 1832 | 1833 | var hints = { copyToClipboard: true, __proto__: null }; 1834 | var remoteObject = injectedScript._wrapObject(string, "") 1835 | InjectedScriptHost.inspect(remoteObject, hints); 1836 | }, 1837 | 1838 | clear: function() 1839 | { 1840 | InjectedScriptHost.clearConsoleMessages(); 1841 | }, 1842 | 1843 | /** 1844 | * @param {!Node} node 1845 | * @return {!Array.|undefined} 1846 | */ 1847 | getEventListeners: function(node) 1848 | { 1849 | var result = nullifyObjectProto(InjectedScriptHost.getEventListeners(node)); 1850 | if (!result) 1851 | return result; 1852 | /** @this {{type: string, listener: function(), useCapture: boolean}} */ 1853 | var removeFunc = function() 1854 | { 1855 | node.removeEventListener(this.type, this.listener, this.useCapture); 1856 | } 1857 | for (var type in result) { 1858 | var listeners = result[type]; 1859 | for (var i = 0, listener; listener = listeners[i]; ++i) { 1860 | listener["type"] = type; 1861 | listener["remove"] = removeFunc; 1862 | } 1863 | } 1864 | return result; 1865 | }, 1866 | 1867 | debug: function(fn) 1868 | { 1869 | InjectedScriptHost.debugFunction(fn); 1870 | }, 1871 | 1872 | undebug: function(fn) 1873 | { 1874 | InjectedScriptHost.undebugFunction(fn); 1875 | }, 1876 | 1877 | monitor: function(fn) 1878 | { 1879 | InjectedScriptHost.monitorFunction(fn); 1880 | }, 1881 | 1882 | unmonitor: function(fn) 1883 | { 1884 | InjectedScriptHost.unmonitorFunction(fn); 1885 | }, 1886 | 1887 | table: function(data, opt_columns) 1888 | { 1889 | InjectedScriptHost.callFunction(inspectedGlobalObject.console.table, inspectedGlobalObject.console, slice(arguments)); 1890 | }, 1891 | 1892 | /** 1893 | * @param {number} num 1894 | */ 1895 | _inspectedObject: function(num) 1896 | { 1897 | return InjectedScriptHost.inspectedObject(num); 1898 | }, 1899 | 1900 | /** 1901 | * @param {!Array.|string=} types 1902 | * @return {!Array.} 1903 | */ 1904 | _normalizeEventTypes: function(types) 1905 | { 1906 | if (typeof types === "undefined") 1907 | types = ["mouse", "key", "touch", "pointer", "control", "load", "unload", "abort", "error", "select", "input", "change", "submit", "reset", "focus", "blur", "resize", "scroll", "search", "devicemotion", "deviceorientation"]; 1908 | else if (typeof types === "string") 1909 | types = [types]; 1910 | 1911 | var result = []; 1912 | for (var i = 0; i < types.length; ++i) { 1913 | if (types[i] === "mouse") 1914 | push(result, "click", "dblclick", "mousedown", "mouseeenter", "mouseleave", "mousemove", "mouseout", "mouseover", "mouseup", "mouseleave", "mousewheel"); 1915 | else if (types[i] === "key") 1916 | push(result, "keydown", "keyup", "keypress", "textInput"); 1917 | else if (types[i] === "touch") 1918 | push(result, "touchstart", "touchmove", "touchend", "touchcancel"); 1919 | else if (types[i] === "pointer") 1920 | push(result, "pointerover", "pointerout", "pointerenter", "pointerleave", "pointerdown", "pointerup", "pointermove", "pointercancel", "gotpointercapture", "lostpointercapture"); 1921 | else if (types[i] === "control") 1922 | push(result, "resize", "scroll", "zoom", "focus", "blur", "select", "input", "change", "submit", "reset"); 1923 | else 1924 | push(result, types[i]); 1925 | } 1926 | return result; 1927 | }, 1928 | 1929 | /** 1930 | * @param {!Event} event 1931 | */ 1932 | _logEvent: function(event) 1933 | { 1934 | inspectedGlobalObject.console.log(event.type, event); 1935 | } 1936 | } 1937 | 1938 | injectedScript._commandLineAPIImpl = new CommandLineAPIImpl(); 1939 | return injectedScript; 1940 | }) 1941 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, 3y3 Ghoti 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 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. 8 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT HOLDER OR 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. 9 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | node_pre_gyp_accessKeyId: 3 | secure: vioM9wfAwkcAV2Btx0T6sdI+NxwnbSJLm00V49KzLJY= 4 | node_pre_gyp_secretAccessKey: 5 | secure: jJxKoyWputzRz2EjAT9i/vBzYt2+lcjKZ5D6O5TBaS9+anpYHn2XWbOut5dkOgh0 6 | node_pre_gyp_region: eu-central-1 7 | matrix: 8 | - NODE_VERSION: 7 9 | platform: x64 10 | - NODE_VERSION: 7 11 | platform: x86 12 | - NODE_VERSION: 6 13 | platform: x64 14 | - NODE_VERSION: 6 15 | platform: x86 16 | - NODE_VERSION: 5 17 | platform: x64 18 | - NODE_VERSION: 5 19 | platform: x86 20 | - NODE_VERSION: 4 21 | platform: x64 22 | - NODE_VERSION: 4 23 | platform: x86 24 | - NODE_VERSION: 0.12 25 | platform: x64 26 | - NODE_VERSION: 0.12 27 | platform: x86 28 | - NODE_VERSION: 0.10 29 | platform: x64 30 | - NODE_VERSION: 0.10 31 | platform: x86 32 | 33 | os: unstable 34 | 35 | install: 36 | - SET PATH=%cd%\node_modules\.bin\;%PATH% 37 | 38 | - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:NODE_VERSION) $env:platform 39 | 40 | - IF %NODE_VERSION% LSS 1 npm -g install npm 41 | - IF %NODE_VERSION% LSS 1 set PATH=%APPDATA%\npm;%PATH% 42 | # work around an issue with node-gyp v3.3.1 and node 4x 43 | # package.json has no certificates in it so we're cool 44 | # https://github.com/nodejs/node-gyp/issues/921 45 | - IF %NODE_VERSION% == 4 npm config set -g cafile=package.json 46 | - IF %NODE_VERSION% == 4 npm config set -g strict-ssl=false 47 | 48 | # Check if new tag released and publish binary in this case. 49 | - SET PUBLISH_BINARY=%APPVEYOR_REPO_TAG% 50 | 51 | build_script: 52 | - npm install --build-from-source --msvs_version=2013 53 | 54 | test_script: 55 | - npm test 56 | 57 | on_success: 58 | - IF %PUBLISH_BINARY% == true (node-pre-gyp package publish 2>&1) 59 | - IF %PUBLISH_BINARY% == true (node-pre-gyp clean) 60 | - IF %PUBLISH_BINARY% == true (npm install --fallback-to-build=false) 61 | - IF %PUBLISH_BINARY% == true (node-pre-gyp info) 62 | 63 | deploy: OFF 64 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'variables': { 3 | 'node_next': '", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/node-inspector/v8-debug.git" 10 | }, 11 | "license": { 12 | "name": "BSD", 13 | "url": "https://github.com/node-inspector/v8-debug/blob/master/LICENSE" 14 | }, 15 | "binary": { 16 | "module_name": "debug", 17 | "module_path": "./build/{module_name}/v{version}/{node_abi}-{platform}-{arch}/", 18 | "remote_path": "./{module_name}/v{version}/", 19 | "package_name": "{node_abi}-{platform}-{arch}.tar.gz", 20 | "host": "https://node-inspector.s3.amazonaws.com/" 21 | }, 22 | "keywords": [ 23 | "v8", 24 | "debugger" 25 | ], 26 | "engines": { 27 | "node": ">=0.10" 28 | }, 29 | "main": "v8-debug", 30 | "dependencies": { 31 | "nan": "^2.3.2", 32 | "node-pre-gyp": "^0.6.5" 33 | }, 34 | "devDependencies": { 35 | "aws-sdk": "^2.1.8", 36 | "mocha": "^2.1.0", 37 | "chai": "^1.10.0", 38 | "rimraf": "^2.4.4" 39 | }, 40 | "scripts": { 41 | "preinstall": "node -e 'process.exit(0)'", 42 | "install": "node-pre-gyp install --fallback-to-build", 43 | "rebuild": "node-pre-gyp rebuild", 44 | "release": "node ./tools/release.js $@", 45 | "test": "mocha --debug" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/node-inspector/v8-debug.svg?branch=master)](https://travis-ci.org/node-inspector/v8-debug) 2 | [![Build status](https://ci.appveyor.com/api/projects/status/rb02h15b61xyryhx/branch/master?svg=true)](https://ci.appveyor.com/project/3y3/v8-debug-145/branch/master) 3 | [![npm version](https://badge.fury.io/js/v8-debug.svg)](http://badge.fury.io/js/v8-debug) 4 | 5 | # v8-debug 6 | Provides extending API for [node](http://github.com/ry/node) internal debugger protocol (based on [v8 debugger protocol](https://code.google.com/p/v8/wiki/DebuggerProtocol)) 7 | 8 | This is a part of [node-inspector](http://github.com/node-inspector/node-inspector). 9 | 10 | ## Installation 11 | ``` 12 | npm install v8-debug 13 | ``` 14 | ## API 15 | 16 | ### registerCommand(name, callback) 17 | Registers new debug processor command, like `lookup`. 18 | 19 | `callback` accepts two arguments - **request** and **response**. 20 | 21 | You need to modify `response.body` if you want to return something to caller. 22 | ```js 23 | debug.registerCommand('_lookup', function(request, response) { 24 | var test = request.attributes; 25 | //do someting here 26 | //and modify response 27 | response.body = { 28 | test: test 29 | }; 30 | }); 31 | ``` 32 | 33 | ### registerEvent(eventName) 34 | This is a shortcut for: 35 | ```js 36 | debug.registerCommand('someEvent', debug.commandToEvent); 37 | ``` 38 | 39 | ### execCommand(commandName, attributes) 40 | Calls debug processor command like 'lookup'. 41 | 42 | `attributes` will be passed to `registerCommand.callback` as `request.attributes`. 43 | 44 | `attributes` needs to be valid JSON object. 45 | ```js 46 | debug.registerCommand('_lookup', function(request, response) { 47 | var test = request.attributes; 48 | //do someting here 49 | //and modify response 50 | response.body = { 51 | test: test 52 | }; 53 | }); 54 | 55 | debug.execCommand('_lookup', { attr: 'test' }); 56 | ``` 57 | 58 | ### emitEvent(eventName, attributes) 59 | This is a semantic alias for `execCommand` 60 | ```js 61 | debug.emitEvent('myEvent', { attr: 'test' }); 62 | ``` 63 | 64 | ### commandToEvent(request, response) 65 | `response` object has a different structure for commands and events. 66 | 67 | By default `registerCommand.callback` receives command's response. 68 | 69 | This is a small converter. 70 | ```js 71 | debug.registerCommand('someEvent1', function(request, response) { 72 | debug.commandToEvent(request, response); 73 | }); 74 | 75 | debug.registerCommand('someEvent2', debug.commandToEvent); 76 | ``` 77 | Use `debug.registerEvent` instead of this. 78 | 79 | ### runInDebugContext(script) 80 | (alias `get`) 81 | 82 | Evaluates string or function (will be stringifyed) in debug context. 83 | ```js 84 | var MakeMirror = debug.get('MakeMirror'); 85 | var mirror = MakeMirror({ test: 1 }); 86 | ``` 87 | 88 | ### getFromFrame(index, value) 89 | Tries to receive a `value` from targeted frame scopes 90 | ```js 91 | function a(options) { 92 | //... 93 | b(); 94 | } 95 | 96 | function b() { 97 | // There is no info about `options` object 98 | var options = debug.getFromFrame(1, 'options'); 99 | } 100 | ``` 101 | 102 | ### enableWebkitProtocol() 103 | Enables experimental usage of WebKit protocol 104 | 105 | ### registerAgentCommand(command, parameters, callback) 106 | Experimental method for registering WebKit protocol handlers 107 | 108 | ## Usage 109 | 110 | Simple console.log checking 111 | ```js 112 | var debug = require('v8-debug'); 113 | 114 | debug.registerEvent('console.log'); 115 | 116 | console.log = (function(fn) { 117 | return function() { 118 | debug.emitEvent('console.log', {message: arguments[0]} /*, userdata*/); 119 | return fn.apply(console, arguments); 120 | } 121 | } (console.log)); 122 | ``` 123 | 124 | For more experience see [protocol documentation](https://github.com/buggerjs/bugger-v8-client/blob/master/PROTOCOL.md) 125 | -------------------------------------------------------------------------------- /src/InjectedScriptHost.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "InjectedScriptHost.h" 4 | #include "tools.h" 5 | 6 | using v8::Isolate; 7 | using v8::Handle; 8 | using v8::Local; 9 | using v8::Value; 10 | using v8::Boolean; 11 | using v8::Number; 12 | using v8::Integer; 13 | using v8::String; 14 | using v8::Object; 15 | using v8::Array; 16 | using v8::Message; 17 | using v8::Function; 18 | using Nan::To; 19 | using Nan::New; 20 | using Nan::Get; 21 | using Nan::Set; 22 | using Nan::ForceSet; 23 | using Nan::SetMethod; 24 | using Nan::EscapableHandleScope; 25 | using Nan::Undefined; 26 | using Nan::TryCatch; 27 | using Nan::ThrowError; 28 | using Nan::ThrowTypeError; 29 | using Nan::MaybeLocal; 30 | using Nan::EmptyString; 31 | using Nan::Utf8String; 32 | 33 | namespace nodex { 34 | void InjectedScriptHost::Initialize(Handle target) { 35 | Local injectedScriptHost = New(); 36 | SetMethod(injectedScriptHost, "eval", Eval); 37 | SetMethod(injectedScriptHost, "evaluateWithExceptionDetails", EvaluateWithExceptionDetails); 38 | SetMethod(injectedScriptHost, "setNonEnumProperty", SetNonEnumProperty); 39 | SetMethod(injectedScriptHost, "subtype", Subtype); 40 | SetMethod(injectedScriptHost, "internalConstructorName", InternalConstructorName); 41 | SetMethod(injectedScriptHost, "functionDetailsWithoutScopes", FunctionDetailsWithoutScopes); 42 | SetMethod(injectedScriptHost, "callFunction", CallFunction); 43 | 44 | SET(target, "InjectedScriptHost", injectedScriptHost); 45 | } 46 | 47 | Handle InjectedScriptHost::createExceptionDetails(Handle message) { 48 | EscapableHandleScope scope; 49 | 50 | Local exceptionDetails = New(); 51 | SET(exceptionDetails, "text", message->Get()); 52 | 53 | SET(exceptionDetails, "url", message->GetScriptOrigin().ResourceName()); 54 | SET(exceptionDetails, "scriptId", New((int32_t)message->GetScriptOrigin().ScriptID()->Value())); 55 | SET(exceptionDetails, "line", New(message->GetLineNumber())); 56 | SET(exceptionDetails, "column", New(message->GetStartColumn())); 57 | 58 | if (!message->GetStackTrace().IsEmpty()) 59 | SET(exceptionDetails, "stackTrace", message->GetStackTrace()->AsArray()); 60 | else 61 | SET(exceptionDetails, "stackTrace", Undefined()); 62 | 63 | return scope.Escape(exceptionDetails); 64 | }; 65 | 66 | NAN_METHOD(InjectedScriptHost::EvaluateWithExceptionDetails) { 67 | if (info.Length() < 1) 68 | return ThrowError("One argument expected."); 69 | 70 | Local wrappedResult = New(); 71 | Local expression = CHK(To(info[0])); 72 | if (expression.IsEmpty()) 73 | return ThrowTypeError("The argument must be a string."); 74 | 75 | TryCatch tryCatch; 76 | MaybeLocal result; 77 | RUNSCRIPT(expression, result); 78 | 79 | if (tryCatch.HasCaught()) { 80 | SET(wrappedResult, "result", tryCatch.Exception()); 81 | SET(wrappedResult, "exceptionDetails", createExceptionDetails(tryCatch.Message())); 82 | } else { 83 | SET(wrappedResult, "result", CHK(result)); 84 | SET(wrappedResult, "exceptionDetails", Undefined()); 85 | } 86 | 87 | RETURN(wrappedResult); 88 | }; 89 | 90 | NAN_METHOD(InjectedScriptHost::SetNonEnumProperty) { 91 | if (info.Length() < 3) 92 | return ThrowError("Three arguments expected."); 93 | if (!info[0]->IsObject()) 94 | return ThrowTypeError("Argument 0 must be an object."); 95 | if (!info[1]->IsString()) 96 | return ThrowTypeError("Argument 1 must be a string."); 97 | 98 | Local object = CHK(To(info[0])); 99 | ForceSet(object, info[1], info[2], v8::DontEnum); 100 | 101 | RETURN(Undefined()); 102 | }; 103 | 104 | NAN_METHOD(InjectedScriptHost::Subtype) { 105 | if (info.Length() < 1) 106 | return ThrowError("One argument expected."); 107 | 108 | Local value = info[0]; 109 | if (value->IsArray() || value->IsTypedArray() || value->IsArgumentsObject()) 110 | RETURN(CHK(New("array"))); 111 | 112 | if (value->IsDate()) 113 | RETURN(CHK(New("date"))); 114 | 115 | if (value->IsRegExp()) 116 | RETURN(CHK(New("regexp"))); 117 | 118 | if (value->IsMap() || value->IsWeakMap()) 119 | RETURN(CHK(New("map"))); 120 | 121 | if (value->IsSet() || value->IsWeakSet()) 122 | RETURN(CHK(New("set"))); 123 | 124 | if (value->IsMapIterator() || value->IsSetIterator()) 125 | RETURN(CHK(New("iterator"))); 126 | 127 | if (value->IsGeneratorObject()) 128 | RETURN(CHK(New("generator"))); 129 | 130 | if (value->IsNativeError()) 131 | RETURN(CHK(New("error"))); 132 | 133 | RETURN(Undefined()); 134 | }; 135 | 136 | Local InjectedScriptHost::functionDisplayName(Handle function) { 137 | EscapableHandleScope scope; 138 | 139 | Local value = CHK(To(function->GetDisplayName())); 140 | if (value->Length()) 141 | return scope.Escape(value); 142 | 143 | value = CHK(To(function->GetName())); 144 | if (value->Length()) 145 | return scope.Escape(value); 146 | 147 | value = CHK(To(function->GetInferredName())); 148 | if (value->Length()) 149 | return scope.Escape(value); 150 | 151 | return scope.Escape(EmptyString()); 152 | }; 153 | 154 | NAN_METHOD(InjectedScriptHost::InternalConstructorName) { 155 | if (info.Length() < 1) 156 | return ThrowError("One argument expected."); 157 | if (!info[0]->IsObject()) 158 | return ThrowTypeError("The argument must be an object."); 159 | 160 | Local object = CHK(To(info[0])); 161 | Local result = object->GetConstructorName(); 162 | 163 | const char* result_type; 164 | if (result.IsEmpty() || result->IsNull() || result->IsUndefined()) 165 | result_type = ""; 166 | else 167 | result_type = *Utf8String(info[0]); 168 | 169 | if (!result.IsEmpty() && strcmp(result_type, "Object") == 0) { 170 | Local constructorSymbol = CHK(New("constructor")); 171 | if (object->HasRealNamedProperty(constructorSymbol) && !object->HasRealNamedCallbackProperty(constructorSymbol)) { 172 | TryCatch tryCatch; 173 | Local constructor = Nan::GetRealNamedProperty(object, constructorSymbol).ToLocalChecked(); 174 | if (!constructor.IsEmpty() && constructor->IsFunction()) { 175 | Local constructorName = functionDisplayName(Handle::Cast(constructor)); 176 | if (!constructorName.IsEmpty() && !tryCatch.HasCaught()) 177 | result = constructorName; 178 | } 179 | } 180 | if (strcmp(result_type, "Object") == 0 && object->IsFunction()) 181 | result = CHK(New("Function")); 182 | } 183 | 184 | RETURN(result); 185 | } 186 | 187 | NAN_METHOD(InjectedScriptHost::FunctionDetailsWithoutScopes) { 188 | if (info.Length() < 1) 189 | return ThrowError("One argument expected."); 190 | 191 | if (!info[0]->IsFunction()) 192 | return ThrowTypeError("The argument must be a function."); 193 | 194 | Local function = Local::Cast(info[0]); 195 | int32_t lineNumber = function->GetScriptLineNumber(); 196 | int32_t columnNumber = function->GetScriptColumnNumber(); 197 | 198 | Local location = New(); 199 | SET(location, "lineNumber", New(lineNumber)); 200 | SET(location, "columnNumber", New(columnNumber)); 201 | SET(location, "scriptId", CHK(To(New(function->ScriptId())))); 202 | 203 | Local result = New(); 204 | SET(result, "location", location); 205 | 206 | Handle name = functionDisplayName(function); 207 | SET(result, "functionName", name.IsEmpty() ? EmptyString() : name); 208 | 209 | SET(result, "isGenerator", New(function->IsGeneratorFunction())); 210 | 211 | RETURN(result); 212 | } 213 | 214 | NAN_METHOD(InjectedScriptHost::CallFunction) { 215 | if (info.Length() < 2 || info.Length() > 3) 216 | return ThrowError("Two or three arguments expected."); 217 | if (!info[0]->IsFunction()) 218 | return ThrowTypeError("Argument 0 must be a function."); 219 | 220 | Handle function = Handle::Cast(info[0]); 221 | Handle receiver = info[1]; 222 | 223 | TryCatch tryCatch; 224 | MaybeLocal result; 225 | 226 | if (info.Length() < 3 || info[2]->IsUndefined()) { 227 | result = function->Call(receiver, 0, NULL); 228 | MAYBE_RETHROW(); 229 | RETURN(CHK(result)); 230 | } 231 | 232 | if (!info[2]->IsArray()) 233 | return ThrowTypeError("Argument 2 must be an array."); 234 | 235 | Handle arguments = Handle::Cast(info[2]); 236 | int argc = arguments->Length(); 237 | Handle *argv = new Handle[argc]; 238 | for (int i = 0; i < argc; ++i) 239 | argv[i] = CHK(Get(arguments, i)); 240 | 241 | result = function->Call(receiver, argc, argv); 242 | delete [] argv; 243 | 244 | MAYBE_RETHROW(); 245 | RETURN(CHK(result)); 246 | }; 247 | 248 | NAN_METHOD(InjectedScriptHost::Eval) { 249 | if (info.Length() < 1) 250 | return ThrowError("One argument expected."); 251 | 252 | Local expression = info[0]->ToString(); 253 | if (expression.IsEmpty()) 254 | return ThrowTypeError("The argument must be a string."); 255 | 256 | TryCatch tryCatch; 257 | MaybeLocal result; 258 | RUNSCRIPT(expression, result); 259 | MAYBE_RETHROW(); 260 | 261 | RETURN(CHK(result)); 262 | }; 263 | } 264 | -------------------------------------------------------------------------------- /src/InjectedScriptHost.h: -------------------------------------------------------------------------------- 1 | #ifndef X_INJECTED_SCRIPT_HOST_ 2 | #define X_INJECTED_SCRIPT_HOST_ 3 | 4 | #include 5 | 6 | namespace nodex { 7 | 8 | class InjectedScriptHost { 9 | public: 10 | static void Initialize(v8::Handle target); 11 | static NAN_METHOD(Eval); 12 | static NAN_METHOD(EvaluateWithExceptionDetails); 13 | static NAN_METHOD(SetNonEnumProperty); 14 | static NAN_METHOD(Subtype); 15 | static NAN_METHOD(InternalConstructorName); 16 | static NAN_METHOD(FunctionDetailsWithoutScopes); 17 | static NAN_METHOD(CallFunction); 18 | /* 19 | static v8::Local New(const v8::CpuProfile* node); 20 | static Nan::Persistent profiles; 21 | */ 22 | private: 23 | static v8::Handle createExceptionDetails(v8::Handle message); 24 | static v8::Local functionDisplayName(v8::Handle function); 25 | 26 | /* 27 | static NAN_METHOD(Delete); 28 | static void Initialize(); 29 | static Nan::Persistent profile_template_; 30 | static uint32_t uid_counter; 31 | */ 32 | }; 33 | 34 | } //namespace nodex 35 | #endif // X_INJECTED_SCRIPT_HOST_ 36 | -------------------------------------------------------------------------------- /src/debug.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "tools.h" 5 | #if (NODE_NEXT) 6 | #include "InjectedScriptHost.h" 7 | #endif 8 | 9 | using v8::Isolate; 10 | using v8::Local; 11 | using v8::Value; 12 | using v8::String; 13 | using v8::Object; 14 | using v8::Context; 15 | using v8::Function; 16 | using Nan::To; 17 | using Nan::New; 18 | using Nan::Set; 19 | using Nan::SetMethod; 20 | using Nan::HandleScope; 21 | using Nan::Undefined; 22 | using Nan::TryCatch; 23 | using Nan::ThrowError; 24 | using Nan::RunScript; 25 | using Nan::MaybeLocal; 26 | 27 | namespace nodex { 28 | 29 | class Debug { 30 | public: 31 | 32 | static NAN_METHOD(Call) { 33 | if (info.Length() < 1) { 34 | return ThrowError("Error"); 35 | } 36 | 37 | Local fn = Local::Cast(info[0]); 38 | 39 | #if (NODE_MODULE_VERSION > 47) 40 | Local context = fn->GetIsolate()->GetCurrentContext(); 41 | v8::Debug::Call(context, fn); 42 | #else 43 | v8::Debug::Call(fn); 44 | #endif 45 | RETURN(Undefined()); 46 | }; 47 | 48 | static NAN_METHOD(SendCommand) { 49 | String::Value command(info[0]); 50 | #if (NODE_MODULE_VERSION > 11) 51 | #if (NODE_MODULE_VERSION > 47) 52 | Isolate* debug_isolate = v8::Debug::GetDebugContext(Isolate::GetCurrent())->GetIsolate(); 53 | #else 54 | Isolate* debug_isolate = v8::Debug::GetDebugContext()->GetIsolate(); 55 | #endif 56 | v8::HandleScope debug_scope(debug_isolate); 57 | v8::Debug::SendCommand(debug_isolate, *command, command.length()); 58 | #else 59 | v8::Debug::SendCommand(*command, command.length()); 60 | #endif 61 | RETURN(Undefined()); 62 | }; 63 | 64 | static NAN_METHOD(RunScript) { 65 | Local expression = CHK(To(info[0])); 66 | 67 | if (expression.IsEmpty()) 68 | RETURN(Undefined()); 69 | 70 | #if (NODE_MODULE_VERSION > 47) 71 | Local debug_context = v8::Debug::GetDebugContext(Isolate::GetCurrent()); 72 | #else 73 | Local debug_context = v8::Debug::GetDebugContext(); 74 | #endif 75 | #if (NODE_MODULE_VERSION > 45) 76 | if (debug_context.IsEmpty()) { 77 | // Force-load the debug context. 78 | v8::Debug::GetMirror(info.GetIsolate()->GetCurrentContext(), info[0]); 79 | #if (NODE_MODULE_VERSION > 47) 80 | debug_context = v8::Debug::GetDebugContext(Isolate::GetCurrent()); 81 | #else 82 | debug_context = v8::Debug::GetDebugContext(); 83 | #endif 84 | } 85 | #endif 86 | 87 | Context::Scope context_scope(debug_context); 88 | 89 | TryCatch tryCatch; 90 | MaybeLocal result; 91 | RUNSCRIPT(expression, result); 92 | MAYBE_RETHROW(); 93 | RETURN(CHK(result)); 94 | }; 95 | 96 | static NAN_METHOD(AllowNatives) { 97 | const char allow_natives_syntax[] = "--allow_natives_syntax"; 98 | v8::V8::SetFlagsFromString(allow_natives_syntax, sizeof(allow_natives_syntax) - 1); 99 | 100 | RETURN(Undefined()); 101 | } 102 | 103 | private: 104 | Debug() {} 105 | ~Debug() {} 106 | }; 107 | 108 | NAN_MODULE_INIT(Initialize) { 109 | #if (NODE_NEXT) 110 | InjectedScriptHost::Initialize(target); 111 | #endif 112 | 113 | SetMethod(target, "call", Debug::Call); 114 | SetMethod(target, "sendCommand", Debug::SendCommand); 115 | SetMethod(target, "runScript", Debug::RunScript); 116 | SetMethod(target, "allowNatives", Debug::AllowNatives); 117 | } 118 | 119 | NODE_MODULE(debug, Initialize) 120 | } 121 | -------------------------------------------------------------------------------- /src/tools.h: -------------------------------------------------------------------------------- 1 | #ifndef X_DEBUG_TOOLS_ 2 | #define X_DEBUG_TOOLS_ 3 | 4 | #define CHK(VALUE) \ 5 | VALUE.ToLocalChecked() 6 | 7 | #define RETURN(VALUE) { \ 8 | info.GetReturnValue().Set(VALUE); \ 9 | return; \ 10 | } 11 | 12 | #define SET(TARGET, NAME, VALUE) \ 13 | Nan::Set(TARGET, CHK(Nan::New(NAME)), VALUE) 14 | 15 | #define RUNSCRIPT(EXPRESSION, RESULT) while (true) { \ 16 | Nan::MaybeLocal script = Nan::CompileScript(EXPRESSION);\ 17 | if (tryCatch.HasCaught()) break; \ 18 | RESULT = Nan::RunScript(CHK(script)); \ 19 | break; \ 20 | } 21 | 22 | #define MAYBE_RETHROW() \ 23 | if (tryCatch.HasCaught()) { \ 24 | tryCatch.ReThrow(); \ 25 | return; \ 26 | } 27 | 28 | #endif // X_DEBUG_TOOLS_ 29 | -------------------------------------------------------------------------------- /test/binding.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var binary = require('node-pre-gyp'); 3 | var path = require('path'); 4 | var binding_path = binary.find(path.resolve(path.join(__dirname,'../package.json'))); 5 | 6 | 7 | describe('binding', function() { 8 | var binding = require(binding_path); 9 | it('source was builded and can be accessed from script', function() { 10 | expect(binding).to.be.instanceof(Object); 11 | }); 12 | 13 | describe('core', function() { 14 | describe('function `call`', function() { 15 | it('should rethrow ReferenceError', function() { 16 | expect(binding.call.bind(null, function() { 17 | "use strict"; 18 | if (error_here) return; 19 | })).to.throw(ReferenceError); 20 | }); 21 | 22 | it('should rethrow SyntaxError', function() { 23 | expect(binding.call.bind(null, function() { 24 | eval('['); 25 | })).to.throw(SyntaxError); 26 | }); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/helpers/debugger.js: -------------------------------------------------------------------------------- 1 | function Debugger(port) { 2 | require('net').createConnection(port) 3 | .on('connect', console.log.bind(console, 'Debugger connected to port ' + port)) 4 | .on('error', console.error.bind(console)) 5 | .on('close', process.exit.bind(process)) 6 | .setEncoding('utf8'); 7 | } 8 | 9 | var debugger_ = new Debugger(5858); -------------------------------------------------------------------------------- /test/injected-script.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var binary = require('node-pre-gyp'); 3 | var path = require('path'); 4 | var binding_path = binary.find(path.resolve(path.join(__dirname,'../package.json'))); 5 | var NODE_NEXT = require('../tools/NODE_NEXT.js'); 6 | 7 | if (!NODE_NEXT) return; 8 | 9 | describe('binding', function() { 10 | var binding = require(binding_path); 11 | it('source was builded and can be accessed from script', function() { 12 | expect(binding.InjectedScriptHost).to.be.instanceof(Object); 13 | }); 14 | 15 | describe('InjectedScriptHost', function() { 16 | var host = binding.InjectedScriptHost; 17 | 18 | describe('function `subtype`', function() { 19 | checksTypeValid(new Array(), 'array'); 20 | checksTypeValid(new Date(), 'date'); 21 | checksTypeValid(new RegExp(), 'regexp'); 22 | checksTypeValid(new Error(), 'error'); 23 | checksTypeValid(new String(), undefined); 24 | 25 | function checksTypeValid(value, type) { 26 | it('checks ' + type + ' subtype', function() { 27 | expect(host.subtype(value)).to.equal(type); 28 | }); 29 | } 30 | 31 | it('should throw on wrong arguments', function() { 32 | expect(host.subtype).to.throw(); 33 | }); 34 | }); 35 | 36 | describe('function `setNonEnumProperty`', function() { 37 | it('should set non enumerable property to object', function() { 38 | var object = { 39 | 'visibleProp': '1' 40 | }; 41 | host.setNonEnumProperty(object, 'hiddenProp', 'value'); 42 | var keys = Object.keys(object); 43 | expect(keys).to.deep.equal(['visibleProp']); 44 | expect(object.hiddenProp).to.equal('value'); 45 | }); 46 | 47 | throwsOnArgs([]); 48 | throwsOnArgs([{}, 'a']); 49 | throwsOnArgs([{}, null, 'b']); 50 | throwsOnArgs([null, {}, 'b']); 51 | 52 | function throwsOnArgs(argvList) { 53 | it('should throw on wrong arguments ' + JSON.stringify(argvList), function() { 54 | expect(host.setNonEnumProperty.bind.apply( 55 | host.setNonEnumProperty, [host].concat(argvList))).to.throw(); 56 | }); 57 | } 58 | 59 | it('should not throw on valid arguments', function() { 60 | expect(host.setNonEnumProperty.bind(host, {}, 'a', null)).to.not.throw(); 61 | expect(host.setNonEnumProperty.bind(host, {}, 'a', 'b')).to.not.throw(); 62 | }); 63 | }); 64 | 65 | describe('function `internalConstructorName`', function() { 66 | checksNameValid(new Number(), 'Number'); 67 | checksNameValid(new Object(), 'Object'); 68 | 69 | function checksNameValid(value, name) { 70 | it('checks new ' + name + '() constructor name', function() { 71 | expect(host.internalConstructorName(value)).to.equal(name); 72 | }); 73 | } 74 | 75 | throwsOnArgs([]); 76 | throwsOnArgs([1]); 77 | throwsOnArgs([null]); 78 | 79 | function throwsOnArgs(argvList) { 80 | it('should throw on wrong arguments ' + JSON.stringify(argvList), function() { 81 | expect(host.internalConstructorName.bind.apply( 82 | host.internalConstructorName, [host].concat(argvList))).to.throw(); 83 | }); 84 | } 85 | }); 86 | 87 | describe('function `functionDetailsWithoutScopes`', function() { 88 | it('should return valid details', function() { 89 | function example() {} 90 | 91 | var details = host.functionDetailsWithoutScopes(example); 92 | expect(details).to.include.keys(['location', 'functionName']); 93 | expect(details.location).to.include.keys(['lineNumber', 'columnNumber', 'scriptId']); 94 | }); 95 | 96 | throwsOnArgs([]); 97 | throwsOnArgs([null]); 98 | 99 | function throwsOnArgs(argvList) { 100 | it('should throw on wrong arguments ' + JSON.stringify(argvList), function() { 101 | expect(host.functionDetailsWithoutScopes.bind.apply( 102 | host.functionDetailsWithoutScopes, [host].concat(argvList))).to.throw(); 103 | }); 104 | } 105 | }); 106 | 107 | describe('function `eval`', function() { 108 | it('should evaluate expression', function() { 109 | expect(host.eval("[1]")).to.deep.equal([1]); 110 | }); 111 | 112 | it('should throw on wrong arguments', function() { 113 | expect(host.eval).to.throw(); 114 | }); 115 | 116 | it('should throw on wrong expression', function() { 117 | expect(host.eval.bind(null, "[1")).to.throw(SyntaxError); 118 | }); 119 | }); 120 | 121 | describe('function `evaluateWithExceptionDetails`', function() { 122 | it('should evaluate expression', function() { 123 | expect(host.evaluateWithExceptionDetails("[1]")).to.deep.equal({ 124 | result: [1], 125 | exceptionDetails: undefined 126 | }); 127 | }); 128 | 129 | it('should throw on wrong arguments', function() { 130 | expect(host.evaluateWithExceptionDetails).to.throw(); 131 | }); 132 | }); 133 | 134 | describe('function `callFunction`', function() { 135 | it('should call function without args', function(done) { 136 | host.callFunction(done, this); 137 | }); 138 | 139 | it('should call function with args', function(done) { 140 | host.callFunction(function(arg) { 141 | expect(arg).to.equal(1); 142 | done(); 143 | }, this, [1]); 144 | }); 145 | 146 | it('should throw on wrong arguments', function() { 147 | expect(host.callFunction.bind(null, null, null, [1])).to.throw(); 148 | expect(host.callFunction.bind(null, null, null, 1)).to.throw(); 149 | }); 150 | 151 | it('should rethrow ReferenceError', function() { 152 | expect(host.callFunction.bind(null, function() { 153 | 'use strict'; 154 | if (error_here) return; 155 | }, this)).to.throw(ReferenceError); 156 | }); 157 | }); 158 | }); 159 | }); 160 | -------------------------------------------------------------------------------- /test/v8-debug.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect, 2 | v8debug = require('../'); 3 | 4 | var NODE_NEXT = require('../tools/NODE_NEXT'); 5 | 6 | var _debugger = require('child_process').spawn('node', ['./test/helpers/debugger.js']); 7 | 8 | _debugger.stderr.on('data', function(data) { 9 | throw new Error(data); 10 | }); 11 | 12 | describe('v8-debug', function() { 13 | before(function(done) { 14 | _debugger.stdout.on('data', function(data) { 15 | console.log(' ' + data); 16 | done(); 17 | }); 18 | }); 19 | 20 | describe('function `runInDebugContext`', function() { 21 | it('returns Debug object', function() { 22 | var Debug = v8debug.runInDebugContext('Debug'); 23 | expect(typeof Debug).to.equal('object'); 24 | }); 25 | 26 | it('returns compiled function object', function() { 27 | var Debug = v8debug.runInDebugContext(function(){return Debug;}); 28 | expect(typeof Debug).to.equal('object'); 29 | }); 30 | }); 31 | 32 | describe('Webkit protocol', function() { 33 | it('if disabled, registerAgentCommand should throw error', function() { 34 | expect(v8debug.registerAgentCommand.bind(v8debug, 'command', [], function() {})).to.throw(); 35 | }); 36 | 37 | if (NODE_NEXT) { 38 | it('enableWebkitProtocol should enable Webkit protocol', function() { 39 | v8debug.enableWebkitProtocol(); 40 | expect(v8debug.enableWebkitProtocol.bind(v8debug)).to.not.throw(); 41 | }); 42 | 43 | it('if enabled registerAgentCommand should register command', function(done) { 44 | expect(v8debug.registerAgentCommand.bind(v8debug, 'command', [], function() { 45 | done(); 46 | })).to.not.throw(); 47 | process.nextTick(function() { 48 | v8debug.sendCommand('command'); 49 | }); 50 | }); 51 | } else { 52 | it('enableWebkitProtocol should throw error', function() { 53 | expect(v8debug.enableWebkitProtocol).to.throw(); 54 | }); 55 | } 56 | }); 57 | 58 | describe('events.', function() { 59 | it('Emits `close` on disconnect command', function(done) { 60 | v8debug.on('close', done); 61 | process.nextTick(function() { 62 | v8debug.sendCommand('disconnect'); 63 | }); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /tools/NODE_NEXT.js: -------------------------------------------------------------------------------- 1 | return module.exports = process.versions.modules > 45; 2 | -------------------------------------------------------------------------------- /tools/annotate-tag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const co = require('co'); 4 | const exec = require('./exec'); 5 | 6 | module.exports = co.wrap(function * (version) { 7 | const history = yield require('./history')(version); 8 | const tagname = 'v' + version; 9 | 10 | return yield exec('git tag -a "' + tagname + '" -m "' + history + '"'); 11 | }); 12 | -------------------------------------------------------------------------------- /tools/commit-changes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const exec = require('./exec'); 4 | const changed = [/*'ChangeLog.md',*/ 'package.json'].join(' '); 5 | 6 | module.exports = 7 | (version) => exec('git commit -m "' + version + '" ' + changed); 8 | -------------------------------------------------------------------------------- /tools/exec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const exec = require('child_process').exec; 4 | 5 | module.exports = 6 | (expression) => new Promise( 7 | (resolve, reject) => exec(expression, 8 | (error, result) => error ? reject(error) : resolve(String(result).trim()) 9 | )); 10 | -------------------------------------------------------------------------------- /tools/history.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const co = require('co'); 4 | const exec = require('./exec'); 5 | 6 | module.exports = co.wrap(function * () { 7 | const _lasttag = yield exec('git rev-list --tags --max-count=1'); 8 | const _version = yield exec('git describe --tags --abbrev=0 ' + _lasttag); 9 | const version = _version ? ' ' + _version + '..' : ''; 10 | 11 | return ' ' + (yield exec('git log --no-merges --pretty="format: * %s (%an) %H%n"' + version)); 12 | }); 13 | -------------------------------------------------------------------------------- /tools/prepublish-to-npm.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const co = require('co'); 4 | const rimraf = require('rimraf'); 5 | const gyp = require('node-pre-gyp'); 6 | const versions = ['0.10.0', '0.12.0', '4.0.0', '5.0.0', '6.0.0']; 7 | const matrix = { 8 | x64: ['win32', 'linux', 'darwin'], 9 | ia32: ['win32'] 10 | }; 11 | 12 | class Target { 13 | constructor(arch, platform, version) { 14 | this.target = version; 15 | this.target_platform = platform; 16 | this.target_arch = arch; 17 | } 18 | } 19 | 20 | const install = target => new Promise((resolve, reject) => { 21 | const prog = Object.assign(new gyp.Run(), {opts: target}); 22 | 23 | prog.commands.install([], error => error ? reject(error) : resolve()); 24 | }); 25 | 26 | module.exports = co.wrap(function * () { 27 | rimraf.sync('./build'); 28 | 29 | const targets = []; 30 | Object.keys(matrix).forEach( 31 | (arch) => matrix[arch].forEach( 32 | (platform) => versions.forEach( 33 | (version) => targets.push(new Target(arch, platform, version)) 34 | ))); 35 | 36 | while (targets.length) yield install(targets.pop()); 37 | }); 38 | -------------------------------------------------------------------------------- /tools/publish-to-npm.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const exec = require('./exec'); 4 | 5 | module.exports = 6 | (version) => exec('npm publish'); 7 | -------------------------------------------------------------------------------- /tools/push-to-githab.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const exec = require('./exec'); 4 | 5 | module.exports = 6 | (version) => exec('git push && git push origin "v' + version + '"'); 7 | -------------------------------------------------------------------------------- /tools/release.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const co = require('co'); 4 | const args = process.argv.slice(2); 5 | const version = args.splice(0, 1); 6 | 7 | const npm = require('./update-npm-version'); 8 | const changelog = require('./update-changelog'); 9 | const tag = require('./annotate-tag'); 10 | const commit = require('./commit-changes'); 11 | const push = require('./push-to-githab'); 12 | 13 | const prepublish = require('./prepublish-to-npm'); 14 | const publish = require('./publish-to-npm'); 15 | 16 | const EXAMPLE = ' Example:\n' + 17 | 'node release.js 1.0.0 --build\n' + 18 | 'node release.js 1.0.0 --publish' 19 | 20 | const SEMVER = /^\d+(\.\d+(\.\d+(-.*)?)?(-.*)?)?(-.*)?$/; 21 | 22 | console.assert(version, 'Wrong usage.' + EXAMPLE); 23 | console.assert(SEMVER.test(version), version + ' is not correct semver'); 24 | 25 | const BUILD = args.some( 26 | (arg) => /^(-b|--build)$/.test(arg)); 27 | 28 | const PUBLISH = args.some( 29 | (arg) => /^(-p|--publish)$/.test(arg)); 30 | 31 | console.assert(BUILD || PUBLISH, 'No mode selected.' + EXAMPLE); 32 | 33 | return co(function * () { 34 | if (BUILD) { 35 | console.log('--Update the version in package.json--'); 36 | yield npm(version); 37 | 38 | // TODO: enable changelog on 1.0 version 39 | // console.log('--Update ChangeLog.md--'); 40 | // changelog(); 41 | 42 | console.log('--Commit the changes--'); 43 | yield commit(version); 44 | 45 | console.log('--Tag the release--') 46 | yield tag(version); 47 | 48 | console.log('--Push to github--'); 49 | yield push(version); 50 | } else if (PUBLISH) { 51 | console.log('--Download prebuilt binaries--'); 52 | yield prepublish(); 53 | 54 | console.log('--Publish to npm--'); 55 | yield publish(); 56 | } 57 | }).catch((error) => { 58 | console.error(error.stack); 59 | return process.exit(1); 60 | }); 61 | -------------------------------------------------------------------------------- /tools/update-changelog.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var exists = fs.existsSync; 3 | var read = fs.readFileSync; 4 | var write = fs.writeFileSync; 5 | 6 | var history = require('./history'); 7 | 8 | module.exports = function changelog(filename) { 9 | filename = filename || 'CHANGELOG.md'; 10 | 11 | var _changelog = exists(filename) ? read(filename) : ''; 12 | var _history = history(); 13 | 14 | write(filename, _history + '\n' + _changelog); 15 | }; 16 | 17 | //test -n "$EDITOR" && $EDITOR $CHANGELOG 18 | -------------------------------------------------------------------------------- /tools/update-npm-version.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const exec = require('./exec'); 4 | 5 | module.exports = 6 | (version) => exec('npm version --git-tag-version=false "' + version + '"'); 7 | -------------------------------------------------------------------------------- /v8-debug.js: -------------------------------------------------------------------------------- 1 | var binary = require('node-pre-gyp'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var binding_path = binary.find(path.resolve(path.join(__dirname,'./package.json'))); 5 | var binding = require(binding_path); 6 | var EventEmitter = require('events').EventEmitter; 7 | var inherits = require('util').inherits; 8 | var extend = require('util')._extend; 9 | 10 | var NODE_NEXT = require('./tools/NODE_NEXT'); 11 | 12 | // Don't cache debugger module 13 | delete require.cache[module.id]; 14 | 15 | function InjectedScriptDir(link) { 16 | return require.resolve(__dirname + '/InjectedScript/' + link); 17 | }; 18 | var DebuggerScriptLink = InjectedScriptDir('DebuggerScript.js'); 19 | var InjectedScriptLink = InjectedScriptDir('InjectedScriptSource.js'); 20 | var InjectedScriptHostLink = InjectedScriptDir('InjectedScriptHost.js'); 21 | 22 | var overrides = { 23 | extendedProcessDebugJSONRequestHandles_: {}, 24 | extendedProcessDebugJSONRequestAsyncHandles_: {}, 25 | extendedProcessDebugJSONRequest_: function(json_request) { 26 | var request; // Current request. 27 | var response; // Generated response. 28 | try { 29 | try { 30 | // Convert the JSON string to an object. 31 | request = JSON.parse(json_request); 32 | 33 | var handle = this.extendedProcessDebugJSONRequestHandles_[request.command]; 34 | var asyncHandle = this.extendedProcessDebugJSONRequestAsyncHandles_[request.command]; 35 | var asyncResponse; 36 | 37 | if (!handle && !asyncHandle) return; 38 | 39 | // Create an initial response. 40 | response = this.createResponse(request); 41 | 42 | if (request.arguments) { 43 | var args = request.arguments; 44 | if (args.maxStringLength !== undefined) { 45 | response.setOption('maxStringLength', args.maxStringLength); 46 | } 47 | if (args.asyncResponse) { 48 | asyncResponse = args.asyncResponse; 49 | } 50 | } 51 | 52 | if (asyncHandle) { 53 | if (asyncResponse) return JSON.stringify(asyncResponse); 54 | 55 | asyncHandle.call(this, request, response, function(error) { 56 | sendCommand(request.command, { 57 | asyncResponse: error || response 58 | }); 59 | }.bind(this)); 60 | 61 | return '{"seq":0,"type":"response","success":true}'; 62 | } 63 | 64 | handle.call(this, request, response); 65 | } catch (e) { 66 | // If there is no response object created one (without command). 67 | if (!response) { 68 | response = this.createResponse(); 69 | } 70 | response.success = false; 71 | response.message = e.toString(); 72 | } 73 | 74 | // Return the response as a JSON encoded string. 75 | try { 76 | if (response.running !== undefined) { 77 | // Response controls running state. 78 | this.running_ = response.running; 79 | } 80 | response.running = this.running_; 81 | return JSON.stringify(response); 82 | } catch (e) { 83 | // Failed to generate response - return generic error. 84 | return '{"seq":' + response.seq + ',' + 85 | '"request_seq":' + request.seq + ',' + 86 | '"type":"response",' + 87 | '"success":false,' + 88 | '"message":"Internal error: ' + e.toString() + '"}'; 89 | } 90 | } catch (e) { 91 | // Failed in one of the catch blocks above - most generic error. 92 | return '{"seq":0,"type":"response","success":false,"message":"Internal error"}'; 93 | } 94 | }, 95 | processDebugRequest: function WRAPPED_BY_NODE_INSPECTOR(request) { 96 | return (this.extendedProcessDebugJSONRequest_ 97 | && this.extendedProcessDebugJSONRequest_(request)) 98 | || this.processDebugJSONRequest(request); 99 | } 100 | }; 101 | 102 | inherits(V8Debug, EventEmitter); 103 | function V8Debug() { 104 | this._webkitProtocolEnabled = false; 105 | 106 | // NOTE: Call `_setDebugEventListener` before all other changes in Debug Context. 107 | // After node 0.12.0 this function serves to allocate Debug Context 108 | // like a persistent value, that saves all our changes. 109 | this._setDebugEventListener(); 110 | this._wrapDebugCommandProcessor(); 111 | 112 | this.once('close', function() { 113 | this._unwrapDebugCommandProcessor(); 114 | this._unsetDebugEventListener(); 115 | process.nextTick(function() { 116 | this.removeAllListeners(); 117 | }.bind(this)); 118 | }); 119 | } 120 | 121 | V8Debug.prototype._setDebugEventListener = function() { 122 | var Debug = this.get('Debug'); 123 | Debug.setListener(function(_, execState, event) { 124 | // TODO(3y3): Handle events here 125 | }); 126 | }; 127 | 128 | V8Debug.prototype._unsetDebugEventListener = function() { 129 | var Debug = this.get('Debug'); 130 | Debug.setListener(null); 131 | }; 132 | 133 | V8Debug.prototype._wrapDebugCommandProcessor = function() { 134 | var proto = this.get('DebugCommandProcessor.prototype'); 135 | overrides.processDebugRequest_ = proto.processDebugRequest; 136 | extend(proto, overrides); 137 | overrides.extendedProcessDebugJSONRequestHandles_['disconnect'] = function(request, response) { 138 | this.emit('close'); 139 | this.processDebugJSONRequest(request); 140 | }.bind(this); 141 | }; 142 | 143 | V8Debug.prototype._unwrapDebugCommandProcessor = function() { 144 | var proto = this.get('DebugCommandProcessor.prototype'); 145 | proto.processDebugRequest = proto.processDebugRequest_; 146 | delete proto.processDebugRequest_; 147 | delete proto.extendedProcessDebugJSONRequest_; 148 | delete proto.extendedProcessDebugJSONRequestHandles_; 149 | delete proto.extendedProcessDebugJSONRequestAsyncHandles_; 150 | }; 151 | 152 | V8Debug.prototype.register = 153 | V8Debug.prototype.registerCommand = function(name, func) { 154 | overrides.extendedProcessDebugJSONRequestHandles_[name] = func; 155 | }; 156 | 157 | V8Debug.prototype.registerAsync = 158 | V8Debug.prototype.registerAsyncCommand = function(name, func) { 159 | overrides.extendedProcessDebugJSONRequestAsyncHandles_[name] = func; 160 | }; 161 | 162 | V8Debug.prototype.command = 163 | V8Debug.prototype.sendCommand = 164 | V8Debug.prototype.emitEvent = sendCommand; 165 | function sendCommand(name, attributes, userdata) { 166 | var message = { 167 | seq: 0, 168 | type: 'request', 169 | command: name, 170 | arguments: attributes || {} 171 | }; 172 | binding.sendCommand(JSON.stringify(message)); 173 | }; 174 | 175 | V8Debug.prototype.commandToEvent = function(request, response) { 176 | response.type = 'event'; 177 | response.event = response.command; 178 | response.body = request.arguments || {}; 179 | delete response.command; 180 | delete response.request_seq; 181 | }; 182 | 183 | V8Debug.prototype.registerEvent = function(name) { 184 | overrides.extendedProcessDebugJSONRequestHandles_[name] = this.commandToEvent; 185 | }; 186 | 187 | V8Debug.prototype.get = 188 | V8Debug.prototype.runInDebugContext = function(script) { 189 | if (typeof script == 'function') script = script.toString() + '()'; 190 | 191 | script = /\);$/.test(script) ? script : '(' + script + ');'; 192 | 193 | return binding.runScript(script); 194 | }; 195 | 196 | V8Debug.prototype.getFromFrame = function(index, value) { 197 | var result; 198 | 199 | binding.call(function(execState) { 200 | var _index = index + 1; 201 | var _count = execState.frameCount(); 202 | if (_count > _index + 1 ) { 203 | var frame = execState.frame(_index + 1); 204 | _count = frame.scopeCount(); 205 | _index = 0; 206 | while (_count --> 0) { 207 | var scope = frame.scope(_index).scopeObject().value(); 208 | if (scope[value]) { 209 | result = scope[value]; 210 | return; 211 | } 212 | } 213 | } 214 | }); 215 | 216 | return result; 217 | }; 218 | 219 | V8Debug.prototype.enableWebkitProtocol = function() { 220 | if (!NODE_NEXT) { 221 | throw new Error('WebKit protocol is not supported on target node version (' + process.version + ')'); 222 | } 223 | 224 | if (this._webkitProtocolEnabled) return; 225 | 226 | var DebuggerScriptSource, 227 | DebuggerScript, 228 | InjectedScriptSource, 229 | InjectedScript, 230 | InjectedScriptHostSource, 231 | InjectedScriptHost; 232 | 233 | function prepareSource(source) { 234 | return 'var ToggleMirrorCache = ToggleMirrorCache || function() {};\n' + 235 | '(function() {' + 236 | ('' + source).replace(/^.*?"use strict";(\r?\n.*?)*\(/m, '\r\n"use strict";\nreturn (') + 237 | '}());'; 238 | } 239 | 240 | DebuggerScriptSource = prepareSource(fs.readFileSync(DebuggerScriptLink, 'utf8')); 241 | DebuggerScript = this.runInDebugContext(DebuggerScriptSource); 242 | 243 | InjectedScriptSource = prepareSource(fs.readFileSync(InjectedScriptLink, 'utf8')); 244 | InjectedScript = this.runInDebugContext(InjectedScriptSource); 245 | 246 | InjectedScriptHostSource = prepareSource(fs.readFileSync(InjectedScriptHostLink, 'utf8')); 247 | InjectedScriptHost = this.runInDebugContext(InjectedScriptHostSource)(binding, DebuggerScript); 248 | 249 | var injectedScript = InjectedScript(InjectedScriptHost, global, 1); 250 | 251 | this.registerAgentCommand = function(command, parameters, callback) { 252 | this.registerCommand(command, new WebkitProtocolCallback(parameters, callback)); 253 | }; 254 | 255 | this._webkitProtocolEnabled = true; 256 | 257 | function WebkitProtocolCallback(argsList, callback) { 258 | return function(request, response) { 259 | InjectedScriptHost.execState = this.exec_state_; 260 | 261 | var args = argsList.map(function(name) { 262 | return request.arguments[name]; 263 | }); 264 | 265 | callback.call(this, args, response, injectedScript, DebuggerScript); 266 | 267 | InjectedScriptHost.execState = null; 268 | } 269 | } 270 | }; 271 | 272 | V8Debug.prototype.registerAgentCommand = function(command, parameters, callback) { 273 | throw new Error('Use "enableWebkitProtocol" before using this method'); 274 | }; 275 | 276 | module.exports = new V8Debug(); 277 | --------------------------------------------------------------------------------