├── .babelrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── README.md ├── app ├── ActiveBreakpointsList.js ├── AppView.js ├── AvailableBreakpointsList.js ├── BreakpointTypeSelector.js └── app.js ├── breakpoints ├── breakpointCombinations.js ├── consoleInterface.js ├── debugObj.js ├── getCallbackFromUserFriendlyCallbackArgument.js └── predefinedBreakpoints.js ├── console-api.md ├── dev ├── CONTRIBUTING.md ├── copy-live-demo-code-to-gh-pages.sh ├── make-webstore-package.sh ├── update-bookmarklet.js └── update-snippet.js ├── devtools-panel.js ├── dist └── javascript-breakpoint-collection.js ├── extension ├── background.js ├── content-script.js ├── devtools.html ├── icon.png ├── manifest.json ├── panel.css ├── panel.html └── show-panel.js ├── gh-pages ├── bookmarklet.html ├── chrome-web-store.png ├── expand-stack-trace-button.png ├── expanded-stack-trace.png ├── expanded-trace-message.png ├── index.html ├── live-demo-copied-code │ ├── devtools-panel.js │ ├── javascript-breakpoint-collection.js │ └── panel.css └── live-demo.html ├── injected-script.js ├── karma.conf.js ├── node-test.js ├── package-lock.json ├── package.json ├── tests ├── app-spec.js ├── breakpointCombinations-spec.js ├── debugObj-spec.js ├── injected-integration-spec.js └── predefinedBreakpoints-spec.js ├── webpack-test.config.js ├── webpack.config.js └── webstore-assets ├── demo-with-page.png ├── extension-icon.png ├── icon-large.png ├── screenshot-old.png ├── screenshot.png └── scroll-trace-demo.png /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: ['es2015', 'react'], 3 | plugins: ["transform-object-rest-spread"] 4 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | extension/build/* 3 | dist/node.js 4 | dist/node.js.map 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | app/* 2 | breakpoints/* 3 | gh-pages/* 4 | tests/* 5 | webstore-assets/* 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.1" 4 | sudo: required 5 | dist: trusty 6 | before_install: 7 | - export CHROME_BIN=google-chrome-beta 8 | - export DISPLAY=:99.0 9 | - sh -e /etc/init.d/xvfb start 10 | - wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - 11 | - sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' 12 | - sudo apt-get update 13 | - sudo apt-get install google-chrome-beta 14 | script: 15 | - npm run build 16 | - npm run ci-test 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript Breakpoint Collection [![Build status](https://api.travis-ci.org/mattzeunert/javascript-breakpoint-collection.svg?branch=master)](https://travis-ci.org/mattzeunert/javascript-breakpoint-collection) 2 | 3 | Find out what part of your code is causing a behavior in the browser. For example, you can pause when the window scroll position is updated or when cookie data is written. 4 | 5 | [Live Demo](http://www.mattzeunert.com/javascript-breakpoint-collection/live-demo.html) 6 | 7 | Either use the UI or add breakpoints from the console: 8 | 9 | breakpoints.debugScroll() 10 | breakpoints.debugPropertySet(obj, "propertyName", "trace") // trace instead of pausing 11 | breakpoints.debugCookieWrites(function(){ /* whatever */ }) 12 | breakpoints.resetLastBreakpoint() 13 | 14 | Learn more about the [Console API](https://github.com/mattzeunert/javascript-breakpoint-collection/blob/master/console-api.md). 15 | 16 | ## Chrome Extension 17 | 18 | [Install from Chrome Web Store](https://chrome.google.com/webstore/detail/javascript-breakpoint-col/kgpjjblahlmjlfljfpcneapmeblichbp) 19 | 20 | ![Breakpoint Extension Screenshot](https://cloud.githubusercontent.com/assets/1303660/14769837/c9bf8438-0a59-11e6-8a16-5cff6886adbc.png) 21 | 22 | Example trace message: 23 | 24 | ![Breakpoint Extension Trace Message Example](https://cloud.githubusercontent.com/assets/1303660/15340896/ba2182ba-1c83-11e6-88ab-73cc59daf956.png) 25 | 26 | ## Bookmarklet 27 | 28 | Get the bookmarklet 29 | 30 | ## Snippet 31 | 32 | Just paste the contents of [this file](https://github.com/mattzeunert/javascript-breakpoint-collection/blob/master/dist/javascript-breakpoint-collection.js) in the console. 33 | 34 | ## NPM 35 | 36 | Download the module from NPM: 37 | 38 | npm install javascript-breakpoint-collection 39 | 40 | Then load the module: 41 | 42 | var breakpoints = require("javascript-breakpoint-collection") 43 | 44 | ## Development 45 | 46 | See [Contributing.md](https://github.com/mattzeunert/javascript-breakpoint-collection/blob/master/dev/CONTRIBUTING.md). 47 | -------------------------------------------------------------------------------- /app/ActiveBreakpointsList.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import {deactivateBreakpoint, updateBreakpointType} from "./app" 3 | import BreakpointTypeSelector from "./BreakpointTypeSelector" 4 | 5 | class ActiveBreakpointsListItem extends React.Component { 6 | render(){ 7 | var title = this.props.breakpoint.details.title; 8 | return
11 | {title} 12 | 18 |
19 | updateBreakpointType(this.props.breakpoint, breakpointType)} /> 22 |
23 |
24 | } 25 | } 26 | 27 | export default class ActiveBreakpointsList extends React.Component { 28 | render(){ 29 | if (this.props.breakpoints.length === 0) { 30 | return
31 | Click on a breakpoint on the left to activate it. 32 |
33 | } 34 | return
35 | {this.props.breakpoints.map((bp, i) => 36 | 37 | )} 38 |
39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/AppView.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import {appState, registerAppView} from "./app" 3 | import AvailableBreakpointsList from "./AvailableBreakpointsList" 4 | import ActiveBreakpointsList from "./ActiveBreakpointsList" 5 | 6 | export default class AppView extends React.Component { 7 | constructor(props){ 8 | super(props); 9 | this.state = { 10 | availableBreakpointsListSearchString: "" 11 | } 12 | } 13 | componentDidMount(){ 14 | registerAppView(this); 15 | } 16 | render(){ 17 | return
18 |
19 |
20 | JavaScript Breakpoint Collection 21 |
22 |
23 |

24 | Click on a breakpoint on the right to add it. 25 |

26 |

27 | To debug property access and function calls on any object, use the code below in the console. 28 |

29 |
30 |
31 |                             breakpoints.debugPropertySet(object, "propertyName");
32 |                         
33 |
34 |                             breakpoints.debugPropertyGet(document, "cookie", "trace");
35 |                         
36 |
37 |                             breakpoints.debugPropertyCall(localStorage, "setItem");
38 |                         
39 |
40 |
41 |                             breakpoints.debugScroll(function(details){"{"}
42 |     console.log('JS changed scroll position', details)
43 | {"}"}); 44 |
45 |
46 | 47 | Learn more 48 | 49 |
50 |

51 | 52 | Report an issue or request a feature 53 | 54 |

55 |
56 |
57 |
58 | this.setState({ 63 | availableBreakpointsListSearchString: e.target.value 64 | })} /> 65 |

Add Breakpoint

66 |
67 | 70 |
71 |
72 |
73 |

Active Breakpoints

74 |
75 | 76 |
77 |
78 |
79 | } 80 | update(){ 81 | this.setState({sth: Math.random()}) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/AvailableBreakpointsList.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import {activateBreakpoint, setTypeOfMostRecentBreakpointToDebugger} from "./app" 3 | 4 | class AvailableBreakpointsListItem extends React.Component { 5 | render(){ 6 | var convertToDebuggerTypeButton = null; 7 | var title = this.props.breakpoint.title; 8 | var plusButton =
+
; 9 | if (this.props.recentlyActivated) { 10 | convertToDebuggerTypeButton =
20 | Click again to change trace to debugger 21 |
22 | plusButton = null; 23 | title = String.fromCharCode(160); //nbsp 24 | } 25 | 26 | return
this.props.onClick()} 28 | onMouseLeave={() => this.props.onMouseLeave()} 29 | className="unactivated-breakpoint-list-item" 30 | data-test-marker-available-bp-title={title}> 31 | {title} 32 | {convertToDebuggerTypeButton} 33 | {plusButton} 34 |
35 | } 36 | } 37 | 38 | export default class AvailableBreakpointsList extends React.Component { 39 | constructor(props){ 40 | super(props); 41 | this.state = { 42 | recentlyActivatedBreakpoint: null 43 | } 44 | } 45 | render(){ 46 | return
47 | {this.getBreakpointsToShow().map( 48 | (bp) => { 49 | var recentlyActivated = bp===this.state.recentlyActivatedBreakpoint; 50 | return { 54 | if (this.state.recentlyActivatedBreakpoint !== null) { 55 | this.setState({recentlyActivatedBreakpoint: null}) 56 | } 57 | }} 58 | onClick={() => { 59 | if (recentlyActivated) { 60 | setTypeOfMostRecentBreakpointToDebugger(); 61 | this.setState({recentlyActivatedBreakpoint: null}) 62 | } else { 63 | activateBreakpoint(bp); 64 | this.setState({recentlyActivatedBreakpoint: bp}) 65 | } 66 | }} 67 | breakpoint={bp} /> 68 | } 69 | )} 70 |
71 | } 72 | getBreakpointsToShow(){ 73 | return this.props.breakpoints.filter((breakpoint) => { 74 | return breakpoint.title.toLowerCase().indexOf(this.props.search.toLowerCase()) > -1; 75 | }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/BreakpointTypeSelector.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | function getStyle(isSelected){ 4 | var style = {}; 5 | if (isSelected) { 6 | style.color = "white"; 7 | style.backgroundColor = "#698CFE"; 8 | style.border = "1px solid #698CFE"; 9 | } else { 10 | style.color = "black"; 11 | style.backgroundColor = "white"; 12 | } 13 | 14 | return style; 15 | } 16 | 17 | export default class BreakpointTypeSelector extends React.Component { 18 | render(){ 19 | var options = ["trace", "debugger"]; 20 | if (this.props.value === "custom") { 21 | options.push("custom") 22 | } 23 | 24 | return
25 | {options.map((option, i) => { 26 | var isSelected = this.props.value === option; 27 | return
this.props.onChange(option)}> 32 | {option} 33 |
34 | })} 35 |
36 | } 37 | } -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | import predefinedBreakpoints from "../breakpoints/predefinedBreakpoints" 2 | 3 | var appState = { 4 | registeredBreakpoints: [], 5 | predefinedBreakpoints 6 | } 7 | 8 | var appViews = []; 9 | 10 | export function activateBreakpoint(breakpoint, options){ 11 | var code = "window.breakpoints." + breakpoint.title + "('trace')" 12 | evalInInspectedWindow(code); 13 | } 14 | 15 | export function deactivateBreakpoint(breakpoint) { 16 | var code = "window.breakpoints.__internal.disableBreakpoint(" + breakpoint.id + ");" 17 | evalInInspectedWindow(code); 18 | } 19 | 20 | export function updateBreakpointType(breakpoint, traceOrDebugger){ 21 | var id = breakpoint.id; 22 | var code = "window.breakpoints.__internal.updateBreakpointType('"+ id + "', '" + traceOrDebugger + "');" 23 | evalInInspectedWindow(code) 24 | } 25 | 26 | export function setTypeOfMostRecentBreakpointToDebugger(){ 27 | evalInInspectedWindow("breakpoints.__internal.setTypeOfMostRecentBreakpointToDebugger()") 28 | } 29 | 30 | function checkIfBreakpointsInstalledOnPage(callback) { 31 | evalInInspectedWindow("window.breakpoints !== undefined", function(result){ 32 | callback(result); 33 | }) 34 | } 35 | 36 | function isRunningInDevToolsPanel(){ 37 | return typeof chrome !== "undefined" && chrome.devtools && chrome.devtools.inspectedWindow; 38 | } 39 | 40 | function evalInInspectedWindow(code, callback){ 41 | if (isRunningInDevToolsPanel()) { 42 | chrome.devtools.inspectedWindow.eval(code, afterEval); 43 | } else { 44 | try { 45 | var returnValue = eval(code); 46 | afterEval(returnValue) 47 | } catch (err) { 48 | afterEval(null, {value: err, isException: true}); 49 | } 50 | } 51 | 52 | function afterEval(result, err){ 53 | if (err && err.isException) { 54 | console.log("Exception occured in eval'd code", err.value) 55 | console.log("Code that was run: ", code) 56 | } 57 | else { 58 | if (callback) { 59 | callback(result); 60 | } 61 | } 62 | } 63 | } 64 | 65 | function readBreakpointsFromPage(){ 66 | evalInInspectedWindow("breakpoints.__internal.getRegisteredBreakpoints();", function(regBp){ 67 | appState.registeredBreakpoints = regBp; 68 | updateApp(); 69 | }); 70 | } 71 | 72 | function installBreakpointsOnPage(callback){ 73 | var src; 74 | if (isRunningInDevToolsPanel()){ 75 | src = chrome.extension.getURL('build/javascript-breakpoint-collection.js'); 76 | } else { 77 | src = "extension/build/javascript-breakpoint-collection.js" 78 | } 79 | var code = ` 80 | var s = document.createElement('script'); 81 | s.src = '${src}' 82 | s.onload = function() { 83 | this.parentNode.removeChild(this); 84 | }; 85 | (document.head || document.documentElement).appendChild(s); 86 | `; 87 | evalInInspectedWindow(code, function(){ 88 | callCallbackIfHasBeenInstalled(); 89 | 90 | function callCallbackIfHasBeenInstalled(){ 91 | checkIfBreakpointsInstalledOnPage(function(isInstalled){ 92 | if (isInstalled) { 93 | callback() 94 | } else { 95 | setTimeout(function(){ 96 | callCallbackIfHasBeenInstalled(); 97 | }, 50) 98 | } 99 | }) 100 | } 101 | }); 102 | } 103 | 104 | export function registerAppView(appView){ 105 | appViews.push(appView) 106 | } 107 | 108 | function updateApp(){ 109 | appViews.forEach(function(appView){ 110 | appView.update() 111 | }) 112 | } 113 | 114 | checkIfBreakpointsInstalledOnPage(function(isInstalled){ 115 | if (isInstalled) { 116 | readBreakpointsFromPage(); 117 | } else { 118 | installBreakpointsOnPage(function(){ 119 | readBreakpointsFromPage(); 120 | }) 121 | } 122 | }) 123 | 124 | if (isRunningInDevToolsPanel()) { 125 | var backgroundPageConnection = chrome.runtime.connect({ 126 | name: "devtools-page" 127 | }); 128 | 129 | backgroundPageConnection.onMessage.addListener(function (message) { 130 | // console.log("readBreakpointsFromPage b/c bg page said so") 131 | readBreakpointsFromPage(); 132 | }); 133 | } else { 134 | window.addEventListener("RebroadcastExtensionMessage", function(){ 135 | readBreakpointsFromPage(); 136 | }); 137 | } 138 | 139 | export {appState} 140 | -------------------------------------------------------------------------------- /breakpoints/breakpointCombinations.js: -------------------------------------------------------------------------------- 1 | import debugObj, {resetDebug, updateDebugIdCallback, disableBreakpointsDuringAllFunctionCalls} from "./debugObj" 2 | import getCallbackFromUserFriendlyCallbackArgument from "./getCallbackFromUserFriendlyCallbackArgument" 3 | var registeredBreakpoints = []; 4 | 5 | function debugPropertyCall(object, prop, callback){ 6 | return debugObj(object, prop, { 7 | propertyCallBefore: { 8 | fn: callback 9 | } 10 | }) 11 | } 12 | 13 | var debugPropertyGet = function(object, propertyName, callback){ 14 | return debugObj(object, propertyName, { 15 | propertyGetBefore: { 16 | fn: callback 17 | } 18 | }) 19 | } 20 | var debugPropertySet = function(object, propertyName, callback) { 21 | return debugObj(object, propertyName, { 22 | propertySetBefore: { 23 | fn: callback 24 | } 25 | }) 26 | } 27 | 28 | var breakpointCombinations = { 29 | register(fn, bpDetails, predefinedBreakpoint) { 30 | var debugIds = []; 31 | var _debugPropertyGet = function(object, propertyName, callback){ 32 | debugIds.push(debugPropertyGet(object, propertyName, callback)); 33 | } 34 | var _debugPropertySet = function(object, propertyName, callback){ 35 | debugIds.push(debugPropertySet(object, propertyName, callback)); 36 | } 37 | var _debugPropertyCall = function(object, propertyName, callback){ 38 | debugIds.push(debugPropertyCall(object, propertyName, callback)); 39 | } 40 | fn(_debugPropertyGet, _debugPropertySet, _debugPropertyCall); 41 | 42 | var id = Math.floor(Math.random() * 1000000000) 43 | var bp = { 44 | id: id, 45 | debugIds, 46 | details: bpDetails, 47 | createdAt: new Date(), 48 | predefinedBreakpoint 49 | } 50 | registeredBreakpoints.push(bp); 51 | 52 | return id; 53 | }, 54 | disable(id){ 55 | var bp = registeredBreakpoints.filter(function(bp){ 56 | return bp.id == id; 57 | })[0]; 58 | if (bp === undefined) { 59 | console.log("Couldn't find breakpoint with id", id) 60 | return; 61 | } 62 | bp.debugIds.forEach(function(debugId){ 63 | resetDebug(debugId); 64 | }); 65 | registeredBreakpoints = registeredBreakpoints.filter(function(bp){ 66 | return bp.id != id; 67 | }) 68 | }, 69 | updateType(id, newType){ 70 | if (newType !== "debugger" && newType !== "trace") { 71 | throw new Error("Invalid breakpoint type") 72 | } 73 | 74 | var bp = registeredBreakpoints.filter(function(bp){ 75 | return bp.id == id; 76 | })[0]; 77 | 78 | var callback = getCallbackFromUserFriendlyCallbackArgument(newType, bp.predefinedBreakpoint); 79 | bp.debugIds.forEach(function(debugId){ 80 | updateDebugIdCallback(debugId, callback) 81 | }); 82 | 83 | bp.details.type = newType; 84 | }, 85 | resetAll(){ 86 | registeredBreakpoints.forEach(function(breakpoint){ 87 | breakpointCombinations.disable(breakpoint.id); 88 | }); 89 | }, 90 | getRegisteredBreakpoints(){ 91 | return registeredBreakpoints; 92 | }, 93 | resetLastBreakpoint(){ 94 | var breakpointToReset = registeredBreakpoints[registeredBreakpoints.length - 1]; 95 | breakpointCombinations.disable(breakpointToReset.id); 96 | }, 97 | setTypeOfMostRecentBreakpointToDebugger(){ 98 | var mostRecentBreakpoint = registeredBreakpoints[registeredBreakpoints.length - 1]; 99 | breakpointCombinations.updateType(mostRecentBreakpoint.id, "debugger") 100 | } 101 | } 102 | 103 | disableBreakpointsDuringAllFunctionCalls(breakpointCombinations); 104 | 105 | export default breakpointCombinations; 106 | -------------------------------------------------------------------------------- /breakpoints/consoleInterface.js: -------------------------------------------------------------------------------- 1 | import debugObj, {debugObjBreakpointRegistry, objectsAndPropsByDebugId, disableBreakpointsDuringAllFunctionCalls} from "./debugObj" 2 | import predefinedBreakpoints from "./predefinedBreakpoints" 3 | import getCallbackFromUserFriendlyCallbackArgument from "./getCallbackFromUserFriendlyCallbackArgument" 4 | import breakpointCombinations from "./breakpointCombinations" 5 | 6 | function pushRegisteredBreakpointsToExtension() { 7 | if (typeof CustomEvent === "undefined" 8 | || typeof window === "undefined" 9 | || !window.dispatchEvent 10 | ) { 11 | return; // probably in a Node environment 12 | } 13 | var event = new CustomEvent("RebroadcastExtensionMessage", { 14 | type: "updateRegisteredBreakpoints", 15 | registeredBreakpoints: breakpointCombinations.getRegisteredBreakpoints() 16 | }); 17 | window.dispatchEvent(event); 18 | } 19 | 20 | var __internal = { 21 | updateBreakpointType: function(id, newType){ 22 | breakpointCombinations.updateType(id, newType) 23 | pushRegisteredBreakpointsToExtension(); 24 | }, 25 | disableBreakpoint: function(id){ 26 | breakpointCombinations.disable(id); 27 | pushRegisteredBreakpointsToExtension(); 28 | }, 29 | registerBreakpoint: function(){ 30 | var id = breakpointCombinations.register.apply(null, arguments); 31 | pushRegisteredBreakpointsToExtension(); 32 | return id; 33 | }, 34 | isBreakpointCollectionExtension: true, 35 | debug: { 36 | debugObj, 37 | debugObjBreakpointRegistry, 38 | objectsAndPropsByDebugId 39 | }, 40 | getRegisteredBreakpoints: function(){ 41 | return breakpointCombinations.getRegisteredBreakpoints(); 42 | }, 43 | setTypeOfMostRecentBreakpointToDebugger: function(){ 44 | breakpointCombinations.setTypeOfMostRecentBreakpointToDebugger(); 45 | pushRegisteredBreakpointsToExtension(); 46 | } 47 | } 48 | 49 | disableBreakpointsDuringAllFunctionCalls(__internal) 50 | 51 | function publicDebugPropertyAccess(obj, prop, callback, accessType) { 52 | var functionName = { 53 | "get": "debugPropertyGet", 54 | "set": "debugPropertySet", 55 | "call": "debugPropertyCall" 56 | }[accessType]; 57 | 58 | callback = getCallbackFromUserFriendlyCallbackArgument(callback); 59 | __internal.registerBreakpoint(function( 60 | debugPropertyGet, debugPropertySet, debugPropertyCall 61 | ){ 62 | var debugFunctions = { 63 | debugPropertyGet, 64 | debugPropertySet, 65 | debugPropertyCall 66 | } 67 | debugFunctions[functionName](obj, prop, callback); 68 | }, { 69 | title: functionName + " (" + prop + ")", 70 | type: callback.callbackType 71 | }); 72 | } 73 | 74 | var breakpoints = { 75 | debugPropertyGet: function(obj, prop, callback){ 76 | return publicDebugPropertyAccess(obj, prop, callback, "get") 77 | }, 78 | debugPropertySet: function(obj, prop, callback){ 79 | return publicDebugPropertyAccess(obj, prop, callback, "set") 80 | }, 81 | debugPropertyCall: function(obj, prop, callback){ 82 | return publicDebugPropertyAccess(obj, prop, callback, "call") 83 | }, 84 | resetAllBreakpoints: function(){ 85 | breakpointCombinations.resetAll() 86 | pushRegisteredBreakpointsToExtension(); 87 | }, 88 | resetLastBreakpoint: function(){ 89 | if (breakpointCombinations.getRegisteredBreakpoints().length === 0) { 90 | console.log("No breakpoints are currently registered") 91 | return; 92 | } 93 | breakpointCombinations.resetLastBreakpoint(); 94 | pushRegisteredBreakpointsToExtension(); 95 | }, 96 | __internal 97 | } 98 | 99 | predefinedBreakpoints.forEach(function(breakpoint){ 100 | breakpoints[breakpoint.title] = function(callback){ 101 | callback = getCallbackFromUserFriendlyCallbackArgument(callback, breakpoint); 102 | 103 | var details = { 104 | title: breakpoint.title, 105 | type: callback.callbackType 106 | } 107 | 108 | var fn = function(debugPropertyGet, debugPropertySet, debugPropertyCall){ 109 | if (breakpoint.debugPropertyGets) { 110 | breakpoint.debugPropertyGets.forEach(function(property){ 111 | debugPropertyGet(eval(property.obj), property.prop, callback) 112 | }) 113 | } 114 | if (breakpoint.debugPropertySets) { 115 | breakpoint.debugPropertySets.forEach(function(property){ 116 | debugPropertySet(eval(property.obj), property.prop, callback) 117 | }) 118 | } 119 | if (breakpoint.debugCalls) { 120 | breakpoint.debugCalls.forEach(function(property){ 121 | debugPropertyCall(eval(property.obj), property.prop, callback) 122 | }) 123 | } 124 | } 125 | 126 | __internal.registerBreakpoint(fn, details, breakpoint); 127 | pushRegisteredBreakpointsToExtension(); 128 | } 129 | }); 130 | 131 | disableBreakpointsDuringAllFunctionCalls(breakpoints); 132 | 133 | export default breakpoints 134 | export { pushRegisteredBreakpointsToExtension } 135 | -------------------------------------------------------------------------------- /breakpoints/debugObj.js: -------------------------------------------------------------------------------- 1 | var registry = new Map(); 2 | var objectsAndPropsByDebugId = {} 3 | 4 | var hookNames = [ 5 | "propertyGetBefore", 6 | "propertyGetAfter", 7 | "propertySetBefore", 8 | "propertySetAfter", 9 | "propertyCallBefore", 10 | "propertyCallAfter" 11 | ]; 12 | 13 | function getPropertyDescriptor(object, propertyName){ 14 | try { 15 | var descriptor = Object.getOwnPropertyDescriptor(object, propertyName); 16 | } catch (err){ 17 | var newError = Error ("Are you sure the property \"" + propertyName + "\" exists?"); 18 | newError.originalError = err; 19 | throw newError; 20 | } 21 | if (!object){ 22 | throw new Error("Descriptor " + propertyName + " not found"); 23 | } 24 | if (!descriptor) { 25 | return getPropertyDescriptor(Object.getPrototypeOf(object), propertyName); 26 | } 27 | return descriptor; 28 | } 29 | 30 | export { registry as debugObjBreakpointRegistry, objectsAndPropsByDebugId } 31 | 32 | // counter instead of boolean to allow nested calls of runWithBreakpointsDisabled 33 | var timesBreakpointsWereDisabled = 0; 34 | export function runWithBreakpointsDisabled(fn){ 35 | timesBreakpointsWereDisabled++; 36 | var retVal = fn(); 37 | timesBreakpointsWereDisabled--; 38 | return retVal; 39 | } 40 | 41 | export function disableBreakpointsDuringAllFunctionCalls(object){ 42 | for (var functionName in object){ 43 | let fn = object[functionName]; 44 | if (typeof fn !== "function") { 45 | continue; 46 | } 47 | 48 | object[functionName] = function(){ 49 | var thisArg = this; 50 | var args = arguments; 51 | return runWithBreakpointsDisabled(function(){ 52 | return fn.apply(thisArg, args); 53 | }) 54 | } 55 | } 56 | } 57 | 58 | function areBreakpointsDisabled(){ 59 | return timesBreakpointsWereDisabled > 0; 60 | } 61 | 62 | export default function debugObj(obj, prop, hooks) { 63 | var debugId = Math.floor(Math.random() * 100000000000).toString() 64 | objectsAndPropsByDebugId[debugId] = { 65 | obj, 66 | prop 67 | } 68 | 69 | if (registry.get(obj) === undefined) { 70 | registry.set(obj, {}); 71 | } 72 | 73 | if (registry.get(obj)[prop] === undefined) { 74 | registry.get(obj)[prop] = {hooks: {}}; 75 | 76 | var originalProp = getPropertyDescriptor(obj, prop); 77 | var isSimpleValue = "value" in originalProp; // rather than getter + setter 78 | 79 | Object.defineProperty(obj, prop, { 80 | get: function(){ 81 | var retVal; 82 | 83 | triggerHook("propertyGetBefore", { 84 | thisArgument: this 85 | }); 86 | 87 | if (isSimpleValue) { 88 | retVal = originalProp.value; 89 | } else { 90 | retVal = originalProp.get.apply(this, arguments); 91 | } 92 | 93 | triggerHook("propertyGetAfter", { 94 | thisArgument: this 95 | }); 96 | 97 | if (typeof retVal === "function") { 98 | return function(){ 99 | var args = Array.prototype.slice.call(arguments); 100 | 101 | triggerHook("propertyCallBefore", { 102 | callArguments: args, 103 | thisArgument: this 104 | }); 105 | 106 | var fnRetVal = retVal.apply(this, arguments); 107 | 108 | triggerHook("propertyCallAfter", { 109 | callArguments: args, 110 | thisArgument: this 111 | }); 112 | 113 | return fnRetVal; 114 | } 115 | } 116 | 117 | return retVal; 118 | }, 119 | set: function(newValue){ 120 | var retVal; 121 | 122 | triggerHook("propertySetBefore", { 123 | newPropertyValue: newValue, 124 | thisArgument: this 125 | }); 126 | 127 | if (isSimpleValue) { 128 | retVal = originalProp.value = newValue; 129 | } else { 130 | retVal = originalProp.set.apply(this, arguments); 131 | } 132 | 133 | triggerHook("propertySetAfter", { 134 | newPropertyValue: newValue, 135 | thisArgument: this 136 | }); 137 | 138 | return retVal; 139 | } 140 | }); 141 | } 142 | 143 | 144 | hookNames.forEach(function(hookName){ 145 | if (hooks[hookName] !== undefined) { 146 | if (registry.get(obj)[prop].hooks[hookName] === undefined) { 147 | registry.get(obj)[prop].hooks[hookName] = []; 148 | } 149 | var hook = hooks[hookName]; 150 | registry.get(obj)[prop].hooks[hookName].push({ 151 | id: debugId, 152 | fn: hook.fn, 153 | data: hook.data 154 | }) 155 | } 156 | }); 157 | 158 | return debugId; 159 | 160 | function triggerHook(hookName, additionalHookInfo) { 161 | if (areBreakpointsDisabled()) { 162 | return; 163 | } 164 | var hooks = registry.get(obj)[prop].hooks; 165 | var hooksWithName = hooks[hookName]; 166 | 167 | var infoForHook = { 168 | object: obj, 169 | propertyName: prop, 170 | ...additionalHookInfo 171 | } 172 | 173 | if (hooksWithName !== undefined && hooksWithName.length > 0) { 174 | hooksWithName.forEach(function(hook){ 175 | hook.fn({ 176 | ...infoForHook, 177 | accessType: getAccessTypeFromHookName(hookName), 178 | data: hook.data 179 | }); 180 | }) 181 | } 182 | } 183 | } 184 | 185 | function getAccessTypeFromHookName(hookName){ 186 | var accessType = ""; 187 | if (hookName === "propertyGetBefore" || hookName === "propertyGetAfter") { 188 | accessType = "get"; 189 | } 190 | if (hookName === "propertySetBefore" || hookName === "propertySetAfter") { 191 | accessType = "set"; 192 | } 193 | if (hookName === "propertyCallBefore" || hookName === "propertyCallAfter") { 194 | accessType = "call"; 195 | } 196 | return accessType; 197 | } 198 | 199 | function updateEachHook(obj, prop, cb){ 200 | var hooks = registry.get(obj)[prop].hooks; 201 | hookNames.forEach(function(hookName){ 202 | var hooksWithName = hooks[hookName]; 203 | if (hooksWithName !== undefined) { 204 | hooks[hookName] = hooksWithName.map(function(hook){ 205 | return cb(hook) 206 | }) 207 | } 208 | }) 209 | } 210 | 211 | export function updateDebugIdCallback(debugId, callback){ 212 | var objAndProp = objectsAndPropsByDebugId[debugId]; 213 | updateEachHook(objAndProp.obj, objAndProp.prop, function(hook){ 214 | if (hook.id === debugId) { 215 | return { 216 | id: debugId, 217 | fn: callback, 218 | data: hook.data 219 | } 220 | } else { 221 | return hook; 222 | } 223 | }); 224 | } 225 | 226 | export function resetDebug(id){ 227 | var objAndProp = objectsAndPropsByDebugId[id]; 228 | var hooks = registry.get(objAndProp.obj)[objAndProp.prop].hooks; 229 | for (var hookName in hooks) { 230 | var hooksWithName = hooks[hookName]; 231 | hooks[hookName] = hooksWithName.filter(function(hook){ 232 | return hook.id != id; 233 | }) 234 | } 235 | 236 | delete objectsAndPropsByDebugId[id]; 237 | } 238 | -------------------------------------------------------------------------------- /breakpoints/getCallbackFromUserFriendlyCallbackArgument.js: -------------------------------------------------------------------------------- 1 | import {runWithBreakpointsDisabled} from "./debugObj" 2 | 3 | function getDebuggerFunction() { 4 | var debuggerFunc = function() { 5 | debugger; 6 | } 7 | debuggerFunc.callbackType = "debugger"; 8 | return debuggerFunc; 9 | } 10 | 11 | export default function getCallbackFromUserFriendlyCallbackArgument(callback, predefinedBreakpoint){ 12 | if (typeof callback === "function") { 13 | callback.callbackType = "custom" 14 | return callback; 15 | } else if (typeof callback === "string") { 16 | if (callback === "debugger") { 17 | return getDebuggerFunction(); 18 | 19 | } else if (callback === "trace") { 20 | return getTraceFunction(predefinedBreakpoint); 21 | } else { 22 | throw new Error("Invalid string callback") 23 | } 24 | } else if(typeof callback=== "undefined") { 25 | return getDebuggerFunction(); 26 | } else { 27 | throw new Error("Invalid callback type: " + typeof callback) 28 | } 29 | } 30 | 31 | function getTraceFunction(predefinedBreakpoint) { 32 | var traceFn; 33 | if (predefinedBreakpoint) { 34 | if (predefinedBreakpoint.getTraceInfo) { 35 | traceFn = function(){ 36 | try { 37 | var traceArgs = predefinedBreakpoint.getTraceInfo.apply(null, arguments); 38 | 39 | runWithBreakpointsDisabled(function(){ 40 | console.trace.apply(console, traceArgs); 41 | }); 42 | } catch (err) { 43 | console.error("Generating trace message failed", err); 44 | } 45 | } 46 | } 47 | else if (predefinedBreakpoint.traceMessage) { 48 | traceFn = function(){ 49 | runWithBreakpointsDisabled(function(){ 50 | console.trace(predefinedBreakpoint.traceMessage) 51 | }) 52 | } 53 | } 54 | } 55 | else { 56 | traceFn = function(debugInfo){ 57 | runWithBreakpointsDisabled(function() { 58 | showTraceMessageForCustomBreakpoints(debugInfo); 59 | }); 60 | } 61 | } 62 | 63 | traceFn.callbackType = "trace"; 64 | return traceFn; 65 | } 66 | 67 | function showTraceMessageForCustomBreakpoints(debugInfo) { 68 | var truncate = function(str, isArray) { 69 | const MAX_LENGTH = 25; 70 | 71 | if (str.length > MAX_LENGTH) { 72 | return str.substring(0, MAX_LENGTH) + "..." + (isArray ? "]" : ""); 73 | } 74 | return str; 75 | }; 76 | 77 | try { 78 | var message = "About to " + debugInfo.accessType + " property '" + debugInfo.propertyName + "' "; 79 | 80 | if (debugInfo.accessType == "set") { 81 | var newPropertyValue = debugInfo.newPropertyValue; 82 | var newPropertyType = typeof newPropertyValue; 83 | 84 | var isArray = (newPropertyValue !== undefined && newPropertyValue != null && 85 | newPropertyValue.constructor === Array); 86 | 87 | if (newPropertyType === "string") { 88 | newPropertyValue = truncate(newPropertyValue, false); 89 | } else if (isArray) { 90 | try { 91 | newPropertyValue = JSON.stringify(newPropertyValue); 92 | newPropertyValue = truncate(newPropertyValue, true); 93 | } catch(e) { 94 | newPropertyValue = newPropertyValue.toString(); // fallback to a shallow version 95 | newPropertyValue = "[" + truncate(newPropertyValue, false) + "]"; 96 | } 97 | } 98 | 99 | if (isArray) { 100 | console.trace(message + "to " + newPropertyValue + " on this object: ", debugInfo.object); 101 | } else { 102 | console.trace(message + "to %o ", newPropertyValue," on this object: ", debugInfo.object); 103 | } 104 | } 105 | else { 106 | console.trace(message + "on this object: ", debugInfo.object); 107 | } 108 | } catch (err) { 109 | // in case something else breaks the trace message, we don't want to break the whole app 110 | console.error("Generating trace message failed", err); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /breakpoints/predefinedBreakpoints.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | title: "debugScroll", 4 | debugCalls: [{ 5 | obj: "window", 6 | prop: "scrollTo" 7 | }, { 8 | obj: "window", 9 | prop: "scrollBy" 10 | }], 11 | debugPropertySets: [{ 12 | obj: "document.body", 13 | prop: "scrollTop" 14 | }, { 15 | obj: "document.body", 16 | prop: "scrollLeft" 17 | }, { 18 | obj: "Element.prototype", 19 | prop: "scrollTop" 20 | }, { 21 | obj: "Element.prototype", 22 | prop: "scrollLeft" 23 | }], 24 | getTraceInfo: function(details){ 25 | if (details.propertyName == "scrollTo" 26 | || details.propertyName == "scrollBy") { 27 | return [ 28 | "The scroll position of the window was changed by a window." + 29 | details.propertyName 30 | + " call with", details.callArguments]; 31 | } 32 | return [ 33 | "The scroll position of", 34 | details.thisArgument, 35 | "was changed by setting the " + details.propertyName + " property to " + details.newPropertyValue 36 | ]; 37 | } 38 | }, 39 | { 40 | title: "debugCookieReads", 41 | debugPropertyGets: [{ 42 | obj: "document", 43 | prop: "cookie" 44 | }], 45 | traceMessage: "Reading cookie contents" 46 | }, 47 | { 48 | title: "debugCookieWrites", 49 | debugPropertySets: [{ 50 | obj: "document", 51 | prop: "cookie" 52 | }], 53 | traceMessage: "Updating cookie contents" 54 | }, 55 | { 56 | title: "debugAlertCalls", 57 | debugCalls: [{ 58 | obj: "window", 59 | prop: "alert" 60 | }], 61 | traceMessage: "Showing alert box" 62 | }, { 63 | title: "debugElementSelection", 64 | debugCalls: [{ 65 | obj: "document", 66 | prop: "getElementById" 67 | }, { 68 | obj: "document", 69 | prop: "getElementsByClassName" 70 | }, { 71 | obj: "document", 72 | prop: "getElementsByName" 73 | }, { 74 | obj: "document", 75 | prop: "getElementsByTagName" 76 | }, { 77 | obj: "document", 78 | prop: "getElementsByTagNameNS" 79 | }, { 80 | obj: "document", 81 | prop: "getElementsByClassName" 82 | }, { 83 | obj: "document", 84 | prop: "querySelector" 85 | }, { 86 | obj: "document", 87 | prop: "querySelectorAll" 88 | }, { 89 | obj: "document", 90 | prop: "evaluate" // xpath 91 | }], 92 | getTraceInfo: function(details){ 93 | return ["Selecting DOM elements \"" + details.callArguments[0] + "\" using " + details.propertyName]; 94 | } 95 | }, 96 | { 97 | title: "debugConsoleErrorCalls", 98 | debugCalls: [{ 99 | obj: "window.console", 100 | prop: "error" 101 | }], 102 | traceMessage: "Calling console.error" 103 | }, 104 | { 105 | title: "debugConsoleLogCalls", 106 | debugCalls: [{ 107 | obj: "window.console", 108 | prop: "log" 109 | }], 110 | traceMessage: "Calling console.log" 111 | }, 112 | { 113 | title: "debugConsoleTraceCalls", 114 | debugCalls: [{ 115 | obj: "window.console", 116 | prop: "trace" 117 | }], 118 | traceMessage: "Calling console.trace" 119 | }, 120 | { 121 | title: "debugMathRandom", 122 | debugCalls: [ 123 | { 124 | obj: "window.Math", 125 | prop: "random" 126 | } 127 | ], 128 | getTraceInfo: function(){ 129 | return ["Calling Math.random"] 130 | } 131 | }, 132 | { 133 | title: "debugTimerCreation", 134 | debugCalls: [ 135 | { 136 | obj: "window", 137 | prop: "setTimeout" 138 | }, 139 | { 140 | obj: "window", 141 | prop: "setInterval" 142 | } 143 | ], 144 | getTraceInfo: function(details){ 145 | return ["Creating timer using " + details.propertyName] 146 | } 147 | }, 148 | { 149 | title: "debugLocalStorageReads", 150 | debugCalls: [{ 151 | obj: "window.localStorage", 152 | prop: "getItem" 153 | }], 154 | getTraceInfo: function(details){ 155 | return ["Reading localStorage data for key \"" + details.callArguments[0] + "\""]; 156 | } 157 | }, 158 | { 159 | title: "debugLocalStorageWrites", 160 | debugCalls: [{ 161 | obj: "window.localStorage", 162 | prop: "setItem" 163 | }, { 164 | obj: "window.localStorage", 165 | prop: "clear" 166 | }], 167 | getTraceInfo: function(details){ 168 | if (details.propertyName == "clear") { 169 | return ["Clearing all localStorage data"]; 170 | } 171 | return ["Writing localStorage data for key \"" + details.callArguments[0] + "\""]; 172 | } 173 | } 174 | ]; 175 | -------------------------------------------------------------------------------- /console-api.md: -------------------------------------------------------------------------------- 1 | # Console API 2 | 3 | ## Adding breakpoints from the console 4 | 5 | To add a pre-defined breakpoint just call its function on the `breakpoints` object: 6 | 7 | breakpoints.debugScroll() 8 | 9 | If you want to debug a custom object you have to pass in the object and property you 10 | want to debug. 11 | 12 | var obj = {test: "value"} 13 | breakpoints.debugPropertySet(obj, "test") 14 | obj.test = "hi" // Pauses execution 15 | 16 | You can use the following three functions: 17 | 18 | - **debugPropertyGet** to pause when a value is read, e.g. just running `obj.test` 19 | - **debugPropertySet** to pause when a value is set, e.g. `obj.test = "hi"` 20 | - **debugPropertyCall** to pause when a value is called, g.. `obj.sayHello()` 21 | 22 | The pre-defined breakpoints are just combinations of these calls. For example: 23 | 24 | breakpoints.debugLocalStorageWrites() 25 | 26 | Is equivalent to: 27 | 28 | breakpoints.debugPropertyCall(localStorage, "setItem") 29 | breakpoints.debugPropertyCall(localStorage, "clear") 30 | 31 | ## Tracepoints 32 | 33 | If you don't pass any additional parameters into the functions above they will pause 34 | execution when the breakpoint is hit. 35 | 36 | However, you can also simply show a trace message without interrupting execution, by 37 | passing in "trace" as a third argument: 38 | 39 | breakpoints.debugPropertySet(obj, "test", "trace") 40 | 41 | ![Trace message in console](https://cloud.githubusercontent.com/assets/1303660/14970397/6faeeafc-10c0-11e6-8fcd-8f24b78225ff.png) 42 | 43 | Or, for pre-defined breakpoints, passing in "trace" as the first parameter: 44 | 45 | breakpoints.debugLocalStorageWrites("trace") 46 | 47 | ## Custom callbacks 48 | 49 | Instead of "trace" you can also pass in a custom function that should be called when 50 | the breakpoint is hit 51 | 52 | breakpoints.debugScroll(function(details){ 53 | // do whatever here 54 | console.log("In debugScroll callback with details:", details) 55 | }) 56 | 57 | The details will vary depending on the breakpoint, but generally will have this information: 58 | 59 | - **object** a reference to the object being debugged 60 | - **propertyName** the name of the property being debugged 61 | - **callArguments** when using debugPropertyCalls this will contain the arguments the function 62 | is being called with 63 | - **thisArgument** when using debugPropertyCalls this will be the execution context the function is called with. 64 | 65 | ## Resetting breakpoints 66 | 67 | You can either use `breakpoints.resetLastBreakpoint()` to reset the most recently 68 | created breakpoint, or use `breakpoints.resetAllBreakpoints()` to reset all breakpoints 69 | added using JS Breakpoint Collection. 70 | 71 | ## Links 72 | 73 | [Bookmarklet](http://www.mattzeunert.com/javascript-breakpoint-collection/bookmarklet.html) 74 | [Chrome Extension](https://chrome.google.com/webstore/detail/javascript-breakpoint-col/kgpjjblahlmjlfljfpcneapmeblichbp) 75 | [Snippet](https://github.com/mattzeunert/javascript-breakpoint-collection/blob/master/dist/javascript-breakpoint-collection.js) 76 | -------------------------------------------------------------------------------- /dev/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | Install the dependencies: 4 | 5 | npm install webpack -g 6 | npm install 7 | 8 | To build the project: 9 | 10 | webpack --watch 11 | 12 | And run the tests: 13 | 14 | npm run test 15 | 16 | ### Loading the extension in Chrome 17 | 18 | 1. Go to [chrome://extensions/](chrome://extensions/) 19 | 2. Enter Developer Mode 20 | 3. Load Unpacked Extension 21 | 4. Select "extension" directory in this repo 22 | 23 | ### Updating the bookmarklet 24 | 25 | Run `node ./dev/update-bookmarklet.js`. 26 | 27 | ## Update the snippet 28 | 29 | Run `node ./dev/update-snippet.js`. 30 | 31 | ### Update website 32 | 33 | `git subtree push --prefix gh-pages origin gh-pages` 34 | 35 | ### Upload to Chrome Web Store 36 | 37 | Run `./dev/make-webstore-package.sh` and then upload dist.zip. 38 | 39 | ## Releases to update 40 | 41 | - Chrome extension 42 | - Bookmarklet 43 | - NPM module 44 | - Live demo 45 | - Snippet 46 | -------------------------------------------------------------------------------- /dev/copy-live-demo-code-to-gh-pages.sh: -------------------------------------------------------------------------------- 1 | OUT=gh-pages/live-demo-copied-code 2 | cp extension/panel.css $OUT/panel.css 3 | cp extension/build/devtools-panel.js $OUT/devtools-panel.js 4 | cp extension/build/javascript-breakpoint-collection.js $OUT/javascript-breakpoint-collection.js 5 | -------------------------------------------------------------------------------- /dev/make-webstore-package.sh: -------------------------------------------------------------------------------- 1 | cp -r extension/ webstore 2 | rm webstore/build/*.map 3 | zip -r webstore.zip webstore 4 | rm -r webstore 5 | -------------------------------------------------------------------------------- /dev/update-bookmarklet.js: -------------------------------------------------------------------------------- 1 | var UglifyJS = require("uglify-js"); 2 | 3 | var result = UglifyJS.minify("./extension/build/javascript-breakpoint-collection.js", { 4 | compress: false 5 | }); 6 | var bookmarkletContent = "javascript:window.__BP_SHOW_CONSOLE_API_MESSAGE = true;eval(decodeURIComponent('" + encodeURIComponent(result.code).replace(/'/g, "\\'") + "'))"; 7 | 8 | var fs = require("fs"); 9 | var htmlContent = fs.readFileSync("./gh-pages/bookmarklet.html").toString() 10 | htmlContent = htmlContent.replace( 11 | /data\-bookmarklet\-href([\s\S]*)data\-bookmarklet\-href\-end/i, 12 | "data-bookmarklet-href href=\"" + bookmarkletContent + "\" data-bookmarklet-href-end" 13 | ); 14 | fs.writeFileSync("./gh-pages/bookmarklet.html", htmlContent) 15 | -------------------------------------------------------------------------------- /dev/update-snippet.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | var code = fs.readFileSync("extension/build/javascript-breakpoint-collection.js") 3 | code = "window.__BP_SHOW_CONSOLE_API_MESSAGE = true;\n" + code; 4 | fs.writeFileSync("dist/javascript-breakpoint-collection.js", code) 5 | -------------------------------------------------------------------------------- /devtools-panel.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom" 3 | import AppView from "./app/AppView.js" 4 | 5 | ReactDOM.render(, document.getElementById("app-content")); 6 | -------------------------------------------------------------------------------- /dist/javascript-breakpoint-collection.js: -------------------------------------------------------------------------------- 1 | window.__BP_SHOW_CONSOLE_API_MESSAGE = true; 2 | /******/ (function(modules) { // webpackBootstrap 3 | /******/ // The module cache 4 | /******/ var installedModules = {}; 5 | /******/ 6 | /******/ // The require function 7 | /******/ function __webpack_require__(moduleId) { 8 | /******/ 9 | /******/ // Check if module is in cache 10 | /******/ if(installedModules[moduleId]) 11 | /******/ return installedModules[moduleId].exports; 12 | /******/ 13 | /******/ // Create a new module (and put it into the cache) 14 | /******/ var module = installedModules[moduleId] = { 15 | /******/ exports: {}, 16 | /******/ id: moduleId, 17 | /******/ loaded: false 18 | /******/ }; 19 | /******/ 20 | /******/ // Execute the module function 21 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 22 | /******/ 23 | /******/ // Flag the module as loaded 24 | /******/ module.loaded = true; 25 | /******/ 26 | /******/ // Return the exports of the module 27 | /******/ return module.exports; 28 | /******/ } 29 | /******/ 30 | /******/ 31 | /******/ // expose the modules object (__webpack_modules__) 32 | /******/ __webpack_require__.m = modules; 33 | /******/ 34 | /******/ // expose the module cache 35 | /******/ __webpack_require__.c = installedModules; 36 | /******/ 37 | /******/ // __webpack_public_path__ 38 | /******/ __webpack_require__.p = ""; 39 | /******/ 40 | /******/ // Load entry module and return exports 41 | /******/ return __webpack_require__(0); 42 | /******/ }) 43 | /************************************************************************/ 44 | /******/ ({ 45 | 46 | /***/ 0: 47 | /***/ function(module, exports, __webpack_require__) { 48 | 49 | "use strict"; 50 | 51 | var _consoleInterface = __webpack_require__(165); 52 | 53 | var _consoleInterface2 = _interopRequireDefault(_consoleInterface); 54 | 55 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 56 | 57 | module.exports = _consoleInterface2.default; 58 | var isInBrowser = typeof window !== "undefined"; 59 | if (isInBrowser) { 60 | if (window.breakpoints !== undefined) { 61 | if (!window.breakpoints.__internal || !window.breakpoints.__internal.isBreakpointCollectionExtension) { 62 | console.log("Breakpoints extension can't load, global `breakpoints` variable is already defined"); 63 | } 64 | } else { 65 | window.breakpoints = _consoleInterface2.default; 66 | 67 | (0, _consoleInterface.pushRegisteredBreakpointsToExtension)(); 68 | 69 | if (window.__BP_SHOW_CONSOLE_API_MESSAGE) { 70 | delete window.__BP_SHOW_CONSOLE_API_MESSAGE; 71 | console.log("Breakpoints Collection API docs: https://github.com/mattzeunert/javascript-breakpoint-collection/blob/master/console-api.md"); 72 | } 73 | } 74 | } 75 | 76 | /***/ }, 77 | 78 | /***/ 161: 79 | /***/ function(module, exports) { 80 | 81 | "use strict"; 82 | 83 | Object.defineProperty(exports, "__esModule", { 84 | value: true 85 | }); 86 | exports.default = [{ 87 | title: "debugScroll", 88 | debugCalls: [{ 89 | obj: "window", 90 | prop: "scrollTo" 91 | }, { 92 | obj: "window", 93 | prop: "scrollBy" 94 | }], 95 | debugPropertySets: [{ 96 | obj: "document.body", 97 | prop: "scrollTop" 98 | }, { 99 | obj: "document.body", 100 | prop: "scrollLeft" 101 | }, { 102 | obj: "Element.prototype", 103 | prop: "scrollTop" 104 | }, { 105 | obj: "Element.prototype", 106 | prop: "scrollLeft" 107 | }], 108 | getTraceInfo: function getTraceInfo(details) { 109 | if (details.propertyName == "scrollTo" || details.propertyName == "scrollBy") { 110 | return ["The scroll position of the window was changed by a window." + details.propertyName + " call with", details.callArguments]; 111 | } 112 | return ["The scroll position of", details.thisArgument, "was changed by setting the " + details.propertyName + " property to " + details.newPropertyValue]; 113 | } 114 | }, { 115 | title: "debugCookieReads", 116 | debugPropertyGets: [{ 117 | obj: "document", 118 | prop: "cookie" 119 | }], 120 | traceMessage: "Reading cookie contents" 121 | }, { 122 | title: "debugCookieWrites", 123 | debugPropertySets: [{ 124 | obj: "document", 125 | prop: "cookie" 126 | }], 127 | traceMessage: "Updating cookie contents" 128 | }, { 129 | title: "debugAlertCalls", 130 | debugCalls: [{ 131 | obj: "window", 132 | prop: "alert" 133 | }], 134 | traceMessage: "Showing alert box" 135 | }, { 136 | title: "debugElementSelection", 137 | debugCalls: [{ 138 | obj: "document", 139 | prop: "getElementById" 140 | }, { 141 | obj: "document", 142 | prop: "getElementsByClassName" 143 | }, { 144 | obj: "document", 145 | prop: "getElementsByName" 146 | }, { 147 | obj: "document", 148 | prop: "getElementsByTagName" 149 | }, { 150 | obj: "document", 151 | prop: "getElementsByTagNameNS" 152 | }, { 153 | obj: "document", 154 | prop: "getElementsByClassName" 155 | }, { 156 | obj: "document", 157 | prop: "querySelector" 158 | }, { 159 | obj: "document", 160 | prop: "querySelectorAll" 161 | }, { 162 | obj: "document", 163 | prop: "evaluate" // xpath 164 | }], 165 | getTraceInfo: function getTraceInfo(details) { 166 | return ["Selecting DOM elements \"" + details.callArguments[0] + "\" using " + details.propertyName]; 167 | } 168 | }, { 169 | title: "debugConsoleErrorCalls", 170 | debugCalls: [{ 171 | obj: "window.console", 172 | prop: "error" 173 | }], 174 | traceMessage: "Calling console.error" 175 | }, { 176 | title: "debugConsoleLogCalls", 177 | debugCalls: [{ 178 | obj: "window.console", 179 | prop: "log" 180 | }], 181 | traceMessage: "Calling console.log" 182 | }, { 183 | title: "debugConsoleTraceCalls", 184 | debugCalls: [{ 185 | obj: "window.console", 186 | prop: "trace" 187 | }], 188 | traceMessage: "Calling console.trace" 189 | }, { 190 | title: "debugMathRandom", 191 | debugCalls: [{ 192 | obj: "window.Math", 193 | prop: "random" 194 | }], 195 | getTraceInfo: function getTraceInfo() { 196 | return ["Calling Math.random"]; 197 | } 198 | }, { 199 | title: "debugTimerCreation", 200 | debugCalls: [{ 201 | obj: "window", 202 | prop: "setTimeout" 203 | }, { 204 | obj: "window", 205 | prop: "setInterval" 206 | }], 207 | getTraceInfo: function getTraceInfo(details) { 208 | return ["Creating timer using " + details.propertyName]; 209 | } 210 | }, { 211 | title: "debugLocalStorageReads", 212 | debugCalls: [{ 213 | obj: "window.localStorage", 214 | prop: "getItem" 215 | }], 216 | getTraceInfo: function getTraceInfo(details) { 217 | return ["Reading localStorage data for key \"" + details.callArguments[0] + "\""]; 218 | } 219 | }, { 220 | title: "debugLocalStorageWrites", 221 | debugCalls: [{ 222 | obj: "window.localStorage", 223 | prop: "setItem" 224 | }, { 225 | obj: "window.localStorage", 226 | prop: "clear" 227 | }], 228 | getTraceInfo: function getTraceInfo(details) { 229 | if (details.propertyName == "clear") { 230 | return ["Clearing all localStorage data"]; 231 | } 232 | return ["Writing localStorage data for key \"" + details.callArguments[0] + "\""]; 233 | } 234 | }]; 235 | 236 | /***/ }, 237 | 238 | /***/ 165: 239 | /***/ function(module, exports, __webpack_require__) { 240 | 241 | "use strict"; 242 | 243 | Object.defineProperty(exports, "__esModule", { 244 | value: true 245 | }); 246 | exports.pushRegisteredBreakpointsToExtension = undefined; 247 | 248 | var _debugObj = __webpack_require__(166); 249 | 250 | var _debugObj2 = _interopRequireDefault(_debugObj); 251 | 252 | var _predefinedBreakpoints = __webpack_require__(161); 253 | 254 | var _predefinedBreakpoints2 = _interopRequireDefault(_predefinedBreakpoints); 255 | 256 | var _getCallbackFromUserFriendlyCallbackArgument = __webpack_require__(167); 257 | 258 | var _getCallbackFromUserFriendlyCallbackArgument2 = _interopRequireDefault(_getCallbackFromUserFriendlyCallbackArgument); 259 | 260 | var _breakpointCombinations = __webpack_require__(168); 261 | 262 | var _breakpointCombinations2 = _interopRequireDefault(_breakpointCombinations); 263 | 264 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 265 | 266 | function pushRegisteredBreakpointsToExtension() { 267 | if (typeof CustomEvent === "undefined" || typeof window === "undefined" || !window.dispatchEvent) { 268 | return; // probably in a Node environment 269 | } 270 | var event = new CustomEvent("RebroadcastExtensionMessage", { 271 | type: "updateRegisteredBreakpoints", 272 | registeredBreakpoints: _breakpointCombinations2.default.getRegisteredBreakpoints() 273 | }); 274 | window.dispatchEvent(event); 275 | } 276 | 277 | var __internal = { 278 | updateBreakpointType: function updateBreakpointType(id, newType) { 279 | _breakpointCombinations2.default.updateType(id, newType); 280 | pushRegisteredBreakpointsToExtension(); 281 | }, 282 | disableBreakpoint: function disableBreakpoint(id) { 283 | _breakpointCombinations2.default.disable(id); 284 | pushRegisteredBreakpointsToExtension(); 285 | }, 286 | registerBreakpoint: function registerBreakpoint() { 287 | var id = _breakpointCombinations2.default.register.apply(null, arguments); 288 | pushRegisteredBreakpointsToExtension(); 289 | return id; 290 | }, 291 | isBreakpointCollectionExtension: true, 292 | debug: { 293 | debugObj: _debugObj2.default, 294 | debugObjBreakpointRegistry: _debugObj.debugObjBreakpointRegistry, 295 | objectsAndPropsByDebugId: _debugObj.objectsAndPropsByDebugId 296 | }, 297 | getRegisteredBreakpoints: function getRegisteredBreakpoints() { 298 | return _breakpointCombinations2.default.getRegisteredBreakpoints(); 299 | }, 300 | setTypeOfMostRecentBreakpointToDebugger: function setTypeOfMostRecentBreakpointToDebugger() { 301 | _breakpointCombinations2.default.setTypeOfMostRecentBreakpointToDebugger(); 302 | pushRegisteredBreakpointsToExtension(); 303 | } 304 | }; 305 | 306 | (0, _debugObj.disableBreakpointsDuringAllFunctionCalls)(__internal); 307 | 308 | function publicDebugPropertyAccess(obj, prop, callback, accessType) { 309 | var functionName = { 310 | "get": "debugPropertyGet", 311 | "set": "debugPropertySet", 312 | "call": "debugPropertyCall" 313 | }[accessType]; 314 | 315 | callback = (0, _getCallbackFromUserFriendlyCallbackArgument2.default)(callback); 316 | __internal.registerBreakpoint(function (debugPropertyGet, debugPropertySet, debugPropertyCall) { 317 | var debugFunctions = { 318 | debugPropertyGet: debugPropertyGet, 319 | debugPropertySet: debugPropertySet, 320 | debugPropertyCall: debugPropertyCall 321 | }; 322 | debugFunctions[functionName](obj, prop, callback); 323 | }, { 324 | title: functionName + " (" + prop + ")", 325 | type: callback.callbackType 326 | }); 327 | } 328 | 329 | var breakpoints = { 330 | debugPropertyGet: function debugPropertyGet(obj, prop, callback) { 331 | return publicDebugPropertyAccess(obj, prop, callback, "get"); 332 | }, 333 | debugPropertySet: function debugPropertySet(obj, prop, callback) { 334 | return publicDebugPropertyAccess(obj, prop, callback, "set"); 335 | }, 336 | debugPropertyCall: function debugPropertyCall(obj, prop, callback) { 337 | return publicDebugPropertyAccess(obj, prop, callback, "call"); 338 | }, 339 | resetAllBreakpoints: function resetAllBreakpoints() { 340 | _breakpointCombinations2.default.resetAll(); 341 | pushRegisteredBreakpointsToExtension(); 342 | }, 343 | resetLastBreakpoint: function resetLastBreakpoint() { 344 | if (_breakpointCombinations2.default.getRegisteredBreakpoints().length === 0) { 345 | console.log("No breakpoints are currently registered"); 346 | return; 347 | } 348 | _breakpointCombinations2.default.resetLastBreakpoint(); 349 | pushRegisteredBreakpointsToExtension(); 350 | }, 351 | __internal: __internal 352 | }; 353 | 354 | _predefinedBreakpoints2.default.forEach(function (breakpoint) { 355 | breakpoints[breakpoint.title] = function (callback) { 356 | callback = (0, _getCallbackFromUserFriendlyCallbackArgument2.default)(callback, breakpoint); 357 | 358 | var details = { 359 | title: breakpoint.title, 360 | type: callback.callbackType 361 | }; 362 | 363 | var fn = function fn(debugPropertyGet, debugPropertySet, debugPropertyCall) { 364 | if (breakpoint.debugPropertyGets) { 365 | breakpoint.debugPropertyGets.forEach(function (property) { 366 | debugPropertyGet(eval(property.obj), property.prop, callback); 367 | }); 368 | } 369 | if (breakpoint.debugPropertySets) { 370 | breakpoint.debugPropertySets.forEach(function (property) { 371 | debugPropertySet(eval(property.obj), property.prop, callback); 372 | }); 373 | } 374 | if (breakpoint.debugCalls) { 375 | breakpoint.debugCalls.forEach(function (property) { 376 | debugPropertyCall(eval(property.obj), property.prop, callback); 377 | }); 378 | } 379 | }; 380 | 381 | __internal.registerBreakpoint(fn, details, breakpoint); 382 | pushRegisteredBreakpointsToExtension(); 383 | }; 384 | }); 385 | 386 | (0, _debugObj.disableBreakpointsDuringAllFunctionCalls)(breakpoints); 387 | 388 | exports.default = breakpoints; 389 | exports.pushRegisteredBreakpointsToExtension = pushRegisteredBreakpointsToExtension; 390 | 391 | /***/ }, 392 | 393 | /***/ 166: 394 | /***/ function(module, exports) { 395 | 396 | "use strict"; 397 | 398 | Object.defineProperty(exports, "__esModule", { 399 | value: true 400 | }); 401 | 402 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 403 | 404 | exports.runWithBreakpointsDisabled = runWithBreakpointsDisabled; 405 | exports.disableBreakpointsDuringAllFunctionCalls = disableBreakpointsDuringAllFunctionCalls; 406 | exports.default = debugObj; 407 | exports.updateDebugIdCallback = updateDebugIdCallback; 408 | exports.resetDebug = resetDebug; 409 | var registry = new Map(); 410 | var objectsAndPropsByDebugId = {}; 411 | 412 | var hookNames = ["propertyGetBefore", "propertyGetAfter", "propertySetBefore", "propertySetAfter", "propertyCallBefore", "propertyCallAfter"]; 413 | 414 | function getPropertyDescriptor(object, propertyName) { 415 | try { 416 | var descriptor = Object.getOwnPropertyDescriptor(object, propertyName); 417 | } catch (err) { 418 | var newError = Error("Are you sure the property \"" + propertyName + "\" exists?"); 419 | newError.originalError = err; 420 | throw newError; 421 | } 422 | if (!object) { 423 | throw new Error("Descriptor " + propertyName + " not found"); 424 | } 425 | if (!descriptor) { 426 | return getPropertyDescriptor(Object.getPrototypeOf(object), propertyName); 427 | } 428 | return descriptor; 429 | } 430 | 431 | exports.debugObjBreakpointRegistry = registry; 432 | exports.objectsAndPropsByDebugId = objectsAndPropsByDebugId; 433 | 434 | // counter instead of boolean to allow nested calls of runWithBreakpointsDisabled 435 | 436 | var timesBreakpointsWereDisabled = 0; 437 | function runWithBreakpointsDisabled(fn) { 438 | timesBreakpointsWereDisabled++; 439 | var retVal = fn(); 440 | timesBreakpointsWereDisabled--; 441 | return retVal; 442 | } 443 | 444 | function disableBreakpointsDuringAllFunctionCalls(object) { 445 | var _loop = function _loop() { 446 | var fn = object[functionName]; 447 | if (typeof fn !== "function") { 448 | return "continue"; 449 | } 450 | 451 | object[functionName] = function () { 452 | var thisArg = this; 453 | var args = arguments; 454 | return runWithBreakpointsDisabled(function () { 455 | return fn.apply(thisArg, args); 456 | }); 457 | }; 458 | }; 459 | 460 | for (var functionName in object) { 461 | var _ret = _loop(); 462 | 463 | if (_ret === "continue") continue; 464 | } 465 | } 466 | 467 | function areBreakpointsDisabled() { 468 | return timesBreakpointsWereDisabled > 0; 469 | } 470 | 471 | function debugObj(obj, prop, hooks) { 472 | var debugId = Math.floor(Math.random() * 100000000000).toString(); 473 | objectsAndPropsByDebugId[debugId] = { 474 | obj: obj, 475 | prop: prop 476 | }; 477 | 478 | if (registry.get(obj) === undefined) { 479 | registry.set(obj, {}); 480 | } 481 | 482 | if (registry.get(obj)[prop] === undefined) { 483 | registry.get(obj)[prop] = { hooks: {} }; 484 | 485 | var originalProp = getPropertyDescriptor(obj, prop); 486 | var isSimpleValue = "value" in originalProp; // rather than getter + setter 487 | 488 | Object.defineProperty(obj, prop, { 489 | get: function get() { 490 | var retVal; 491 | 492 | triggerHook("propertyGetBefore", { 493 | thisArgument: this 494 | }); 495 | 496 | if (isSimpleValue) { 497 | retVal = originalProp.value; 498 | } else { 499 | retVal = originalProp.get.apply(this, arguments); 500 | } 501 | 502 | triggerHook("propertyGetAfter", { 503 | thisArgument: this 504 | }); 505 | 506 | if (typeof retVal === "function") { 507 | return function () { 508 | var args = Array.prototype.slice.call(arguments); 509 | 510 | triggerHook("propertyCallBefore", { 511 | callArguments: args, 512 | thisArgument: this 513 | }); 514 | 515 | var fnRetVal = retVal.apply(this, arguments); 516 | 517 | triggerHook("propertyCallAfter", { 518 | callArguments: args, 519 | thisArgument: this 520 | }); 521 | 522 | return fnRetVal; 523 | }; 524 | } 525 | 526 | return retVal; 527 | }, 528 | set: function set(newValue) { 529 | var retVal; 530 | 531 | triggerHook("propertySetBefore", { 532 | newPropertyValue: newValue, 533 | thisArgument: this 534 | }); 535 | 536 | if (isSimpleValue) { 537 | retVal = originalProp.value = newValue; 538 | } else { 539 | retVal = originalProp.set.apply(this, arguments); 540 | } 541 | 542 | triggerHook("propertySetAfter", { 543 | newPropertyValue: newValue, 544 | thisArgument: this 545 | }); 546 | 547 | return retVal; 548 | } 549 | }); 550 | } 551 | 552 | hookNames.forEach(function (hookName) { 553 | if (hooks[hookName] !== undefined) { 554 | if (registry.get(obj)[prop].hooks[hookName] === undefined) { 555 | registry.get(obj)[prop].hooks[hookName] = []; 556 | } 557 | var hook = hooks[hookName]; 558 | registry.get(obj)[prop].hooks[hookName].push({ 559 | id: debugId, 560 | fn: hook.fn, 561 | data: hook.data 562 | }); 563 | } 564 | }); 565 | 566 | return debugId; 567 | 568 | function triggerHook(hookName, additionalHookInfo) { 569 | if (areBreakpointsDisabled()) { 570 | return; 571 | } 572 | var hooks = registry.get(obj)[prop].hooks; 573 | var hooksWithName = hooks[hookName]; 574 | 575 | var infoForHook = _extends({ 576 | object: obj, 577 | propertyName: prop 578 | }, additionalHookInfo); 579 | 580 | if (hooksWithName !== undefined && hooksWithName.length > 0) { 581 | hooksWithName.forEach(function (hook) { 582 | hook.fn(_extends({}, infoForHook, { 583 | accessType: getAccessTypeFromHookName(hookName), 584 | data: hook.data 585 | })); 586 | }); 587 | } 588 | } 589 | } 590 | 591 | function getAccessTypeFromHookName(hookName) { 592 | var accessType = ""; 593 | if (hookName === "propertyGetBefore" || hookName === "propertyGetAfter") { 594 | accessType = "get"; 595 | } 596 | if (hookName === "propertySetBefore" || hookName === "propertySetAfter") { 597 | accessType = "set"; 598 | } 599 | if (hookName === "propertyCallBefore" || hookName === "propertyCallAfter") { 600 | accessType = "call"; 601 | } 602 | return accessType; 603 | } 604 | 605 | function updateEachHook(obj, prop, cb) { 606 | var hooks = registry.get(obj)[prop].hooks; 607 | hookNames.forEach(function (hookName) { 608 | var hooksWithName = hooks[hookName]; 609 | if (hooksWithName !== undefined) { 610 | hooks[hookName] = hooksWithName.map(function (hook) { 611 | return cb(hook); 612 | }); 613 | } 614 | }); 615 | } 616 | 617 | function updateDebugIdCallback(debugId, callback) { 618 | var objAndProp = objectsAndPropsByDebugId[debugId]; 619 | updateEachHook(objAndProp.obj, objAndProp.prop, function (hook) { 620 | if (hook.id === debugId) { 621 | return { 622 | id: debugId, 623 | fn: callback, 624 | data: hook.data 625 | }; 626 | } else { 627 | return hook; 628 | } 629 | }); 630 | } 631 | 632 | function resetDebug(id) { 633 | var objAndProp = objectsAndPropsByDebugId[id]; 634 | var hooks = registry.get(objAndProp.obj)[objAndProp.prop].hooks; 635 | for (var hookName in hooks) { 636 | var hooksWithName = hooks[hookName]; 637 | hooks[hookName] = hooksWithName.filter(function (hook) { 638 | return hook.id != id; 639 | }); 640 | } 641 | 642 | delete objectsAndPropsByDebugId[id]; 643 | } 644 | 645 | /***/ }, 646 | 647 | /***/ 167: 648 | /***/ function(module, exports, __webpack_require__) { 649 | 650 | "use strict"; 651 | 652 | Object.defineProperty(exports, "__esModule", { 653 | value: true 654 | }); 655 | 656 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; 657 | 658 | exports.default = getCallbackFromUserFriendlyCallbackArgument; 659 | 660 | var _debugObj = __webpack_require__(166); 661 | 662 | function getDebuggerFunction() { 663 | var debuggerFunc = function debuggerFunc() { 664 | debugger; 665 | }; 666 | debuggerFunc.callbackType = "debugger"; 667 | return debuggerFunc; 668 | } 669 | 670 | function getCallbackFromUserFriendlyCallbackArgument(callback, predefinedBreakpoint) { 671 | if (typeof callback === "function") { 672 | callback.callbackType = "custom"; 673 | return callback; 674 | } else if (typeof callback === "string") { 675 | if (callback === "debugger") { 676 | return getDebuggerFunction(); 677 | } else if (callback === "trace") { 678 | return getTraceFunction(predefinedBreakpoint); 679 | } else { 680 | throw new Error("Invalid string callback"); 681 | } 682 | } else if (typeof callback === "undefined") { 683 | return getDebuggerFunction(); 684 | } else { 685 | throw new Error("Invalid callback type: " + (typeof callback === "undefined" ? "undefined" : _typeof(callback))); 686 | } 687 | } 688 | 689 | function getTraceFunction(predefinedBreakpoint) { 690 | var traceFn; 691 | if (predefinedBreakpoint) { 692 | if (predefinedBreakpoint.getTraceInfo) { 693 | traceFn = function traceFn() { 694 | var traceArgs = predefinedBreakpoint.getTraceInfo.apply(null, arguments); 695 | (0, _debugObj.runWithBreakpointsDisabled)(function () { 696 | console.trace.apply(console, traceArgs); 697 | }); 698 | }; 699 | } else if (predefinedBreakpoint.traceMessage) { 700 | traceFn = function traceFn() { 701 | (0, _debugObj.runWithBreakpointsDisabled)(function () { 702 | console.trace(predefinedBreakpoint.traceMessage); 703 | }); 704 | }; 705 | } 706 | } else { 707 | traceFn = function traceFn(debugInfo) { 708 | (0, _debugObj.runWithBreakpointsDisabled)(function () { 709 | console.trace("About to " + debugInfo.accessType + " property '" + debugInfo.propertyName + "' on this object: ", debugInfo.object); 710 | }); 711 | }; 712 | } 713 | 714 | traceFn.callbackType = "trace"; 715 | return traceFn; 716 | } 717 | 718 | /***/ }, 719 | 720 | /***/ 168: 721 | /***/ function(module, exports, __webpack_require__) { 722 | 723 | "use strict"; 724 | 725 | Object.defineProperty(exports, "__esModule", { 726 | value: true 727 | }); 728 | 729 | var _debugObj = __webpack_require__(166); 730 | 731 | var _debugObj2 = _interopRequireDefault(_debugObj); 732 | 733 | var _getCallbackFromUserFriendlyCallbackArgument = __webpack_require__(167); 734 | 735 | var _getCallbackFromUserFriendlyCallbackArgument2 = _interopRequireDefault(_getCallbackFromUserFriendlyCallbackArgument); 736 | 737 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 738 | 739 | var registeredBreakpoints = []; 740 | 741 | function debugPropertyCall(object, prop, callback) { 742 | return (0, _debugObj2.default)(object, prop, { 743 | propertyCallBefore: { 744 | fn: callback 745 | } 746 | }); 747 | } 748 | 749 | var debugPropertyGet = function debugPropertyGet(object, propertyName, callback) { 750 | return (0, _debugObj2.default)(object, propertyName, { 751 | propertyGetBefore: { 752 | fn: callback 753 | } 754 | }); 755 | }; 756 | var debugPropertySet = function debugPropertySet(object, propertyName, callback) { 757 | return (0, _debugObj2.default)(object, propertyName, { 758 | propertySetBefore: { 759 | fn: callback 760 | } 761 | }); 762 | }; 763 | 764 | var breakpointCombinations = { 765 | register: function register(fn, bpDetails, predefinedBreakpoint) { 766 | var debugIds = []; 767 | var _debugPropertyGet = function _debugPropertyGet(object, propertyName, callback) { 768 | debugIds.push(debugPropertyGet(object, propertyName, callback)); 769 | }; 770 | var _debugPropertySet = function _debugPropertySet(object, propertyName, callback) { 771 | debugIds.push(debugPropertySet(object, propertyName, callback)); 772 | }; 773 | var _debugPropertyCall = function _debugPropertyCall(object, propertyName, callback) { 774 | debugIds.push(debugPropertyCall(object, propertyName, callback)); 775 | }; 776 | fn(_debugPropertyGet, _debugPropertySet, _debugPropertyCall); 777 | 778 | var id = Math.floor(Math.random() * 1000000000); 779 | var bp = { 780 | id: id, 781 | debugIds: debugIds, 782 | details: bpDetails, 783 | createdAt: new Date(), 784 | predefinedBreakpoint: predefinedBreakpoint 785 | }; 786 | registeredBreakpoints.push(bp); 787 | 788 | return id; 789 | }, 790 | disable: function disable(id) { 791 | var bp = registeredBreakpoints.filter(function (bp) { 792 | return bp.id == id; 793 | })[0]; 794 | if (bp === undefined) { 795 | console.log("Couldn't find breakpoint with id", id); 796 | return; 797 | } 798 | bp.debugIds.forEach(function (debugId) { 799 | (0, _debugObj.resetDebug)(debugId); 800 | }); 801 | registeredBreakpoints = registeredBreakpoints.filter(function (bp) { 802 | return bp.id != id; 803 | }); 804 | }, 805 | updateType: function updateType(id, newType) { 806 | if (newType !== "debugger" && newType !== "trace") { 807 | throw new Error("Invalid breakpoint type"); 808 | } 809 | 810 | var bp = registeredBreakpoints.filter(function (bp) { 811 | return bp.id == id; 812 | })[0]; 813 | 814 | var callback = (0, _getCallbackFromUserFriendlyCallbackArgument2.default)(newType, bp.predefinedBreakpoint); 815 | bp.debugIds.forEach(function (debugId) { 816 | (0, _debugObj.updateDebugIdCallback)(debugId, callback); 817 | }); 818 | 819 | bp.details.type = newType; 820 | }, 821 | resetAll: function resetAll() { 822 | registeredBreakpoints.forEach(function (breakpoint) { 823 | breakpointCombinations.disable(breakpoint.id); 824 | }); 825 | }, 826 | getRegisteredBreakpoints: function getRegisteredBreakpoints() { 827 | return registeredBreakpoints; 828 | }, 829 | resetLastBreakpoint: function resetLastBreakpoint() { 830 | var breakpointToReset = registeredBreakpoints[registeredBreakpoints.length - 1]; 831 | breakpointCombinations.disable(breakpointToReset.id); 832 | }, 833 | setTypeOfMostRecentBreakpointToDebugger: function setTypeOfMostRecentBreakpointToDebugger() { 834 | var mostRecentBreakpoint = registeredBreakpoints[registeredBreakpoints.length - 1]; 835 | breakpointCombinations.updateType(mostRecentBreakpoint.id, "debugger"); 836 | } 837 | }; 838 | 839 | (0, _debugObj.disableBreakpointsDuringAllFunctionCalls)(breakpointCombinations); 840 | 841 | exports.default = breakpointCombinations; 842 | 843 | /***/ } 844 | 845 | /******/ }); 846 | //# sourceMappingURL=javascript-breakpoint-collection.js.map -------------------------------------------------------------------------------- /extension/background.js: -------------------------------------------------------------------------------- 1 | chrome.runtime.onConnect.addListener(function(devToolsConnection) { 2 | // assign the listener function to a variable so we can remove it later 3 | var devToolsListener = function(message, sender, sendResponse) { 4 | // Inject a content script into the identified tab 5 | chrome.tabs.executeScript(message.tabId, 6 | { file: message.scriptToInject }); 7 | } 8 | // add the listener 9 | devToolsConnection.onMessage.addListener(devToolsListener); 10 | 11 | 12 | chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { 13 | devToolsConnection.postMessage(request) 14 | }); 15 | 16 | devToolsConnection.onDisconnect.addListener(function() { 17 | devToolsConnection.onMessage.removeListener(devToolsListener); 18 | }); 19 | }) -------------------------------------------------------------------------------- /extension/content-script.js: -------------------------------------------------------------------------------- 1 | var s = document.createElement('script'); 2 | s.src = chrome.extension.getURL('build/javascript-breakpoint-collection.js'); 3 | s.onload = function() { 4 | this.parentNode.removeChild(this); 5 | }; 6 | (document.head || document.documentElement).appendChild(s); 7 | 8 | window.addEventListener("RebroadcastExtensionMessage", function(evt) { 9 | try { 10 | chrome.runtime.sendMessage(evt) 11 | } catch (err) { 12 | // `Cannot read property 'name' of undefined` can be caused by background page refresh (e.g. alt+r) 13 | console.log("Breakpoints: Sending info to DevTools tab failed:", err) 14 | console.log("Breakpoints: This can occur after reloading the extension. Close and re-open the current tab to fix it.") 15 | } 16 | }, false); -------------------------------------------------------------------------------- /extension/devtools.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /extension/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzeunert/javascript-breakpoint-collection/2b6124c4e7bee442a12fdc3441710e0b9ba4e22e/extension/icon.png -------------------------------------------------------------------------------- /extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JavaScript Breakpoint Collection", 3 | "manifest_version": 2, 4 | "version": "1.7.1", 5 | "description": "Find what code is responsible for page behavior.", 6 | "content_scripts": [ 7 | { 8 | "matches": ["http://*/*", "https://*/*", "file://*/*"], 9 | 10 | "js": ["content-script.js"] 11 | } 12 | ], 13 | "background": { 14 | "scripts": ["background.js"] 15 | }, 16 | "icons": { 17 | "128": "icon.png" 18 | }, 19 | "permissions": ["http://*/*", "https://*/*", "file://*/*"], 20 | "minimum_chrome_version": "10.0", 21 | "devtools_page": "devtools.html", 22 | "web_accessible_resources": ["build/javascript-breakpoint-collection.js"] 23 | } 24 | -------------------------------------------------------------------------------- /extension/panel.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | h2 { 5 | padding: 10px; 6 | font-weight: normal; 7 | padding-top: 7px; 8 | padding-bottom: 6px; 9 | margin: 0; 10 | background: #fafafa; 11 | border-bottom: 1px solid #eee; 12 | } 13 | body { 14 | margin: 0; 15 | font-family: "Helvetica Neue", "Lucida Grande", sans-serif; 16 | font-size: 75%; 17 | } 18 | a { 19 | color: #698cfe; 20 | } 21 | .col-parent > div { 22 | width: 33.3333%; 23 | float: left; 24 | border-right: 1px solid #eee; 25 | height: 100%; 26 | background: white; 27 | } 28 | .col-parent > div:first-child { 29 | background: #fafafa; 30 | } 31 | .col-parent > div:last-child { 32 | border-right: none; 33 | } 34 | .col-parent .col-content { 35 | padding: 10px; 36 | padding-top: 0px; 37 | height: calc(100% - 36px); 38 | overflow: auto; 39 | } 40 | .unactivated-breakpoint-list-item { 41 | border-bottom: 1px solid #eee; 42 | padding: 10px; 43 | cursor: pointer; 44 | } 45 | .unactivated-breakpoint-list-item:hover { 46 | background: #fafafa; 47 | } 48 | .unactivated-breakpoint-list-item .plus { 49 | display: none; 50 | 51 | float: right; 52 | font-size: 24px; 53 | color: red; 54 | width: 20px; 55 | height: 20px; 56 | text-align: center; 57 | margin-top: -3px; 58 | line-height: 16px; 59 | } 60 | .unactivated-breakpoint-list-item:hover .plus { 61 | display: inline-block; 62 | } 63 | 64 | .activated-breakpiont-list-item { 65 | border-bottom: 1px solid #eee; 66 | padding: 10px; 67 | } 68 | .activated-breakpiont-list-item .delete { 69 | float: right; 70 | color: #777; 71 | font-size: 24px; 72 | line-height: 30px; 73 | text-align: center; 74 | width: 35px; 75 | height: 35px; 76 | padding: 0; 77 | background: none; 78 | border: none; 79 | border-radius: 550px; 80 | cursor: pointer; 81 | } 82 | .activated-breakpiont-list-item:active { 83 | outline: none; 84 | } 85 | .activated-breakpiont-list-item .delete:hover { 86 | color: red; 87 | } 88 | 89 | .breakpoint-type-selector__option { 90 | display: inline-block; 91 | margin-right: 4px; 92 | padding: 4px; 93 | padding-top: 2px; 94 | padding-bottom: 2px; 95 | border: 1px solid #ccc; 96 | cursor: pointer; 97 | border-radius: 4px; 98 | } 99 | .breakpoint-type-selector__option:hover { 100 | background: red !important; 101 | border-color: red !important; 102 | color: white !important; 103 | } 104 | 105 | .left-column-header { 106 | background: #698cfe; 107 | color: white; 108 | padding: 10px; 109 | font-weight: bold; 110 | border-bottom: 1px solid #eee; 111 | } 112 | 113 | .code-section { 114 | max-width: 100%; 115 | overflow: auto; 116 | padding: 10px; 117 | background: #eee; 118 | } 119 | .code-section pre { 120 | font-family: Arial; 121 | padding-bottom: 2px; 122 | margin: 0; 123 | } 124 | 125 | .available-breakpoints-search-input { 126 | float: right; 127 | margin-top: 8px; 128 | margin-right: 10px; 129 | background: #eee; 130 | border: none; 131 | padding: 3px; 132 | } 133 | -------------------------------------------------------------------------------- /extension/panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | -------------------------------------------------------------------------------- /extension/show-panel.js: -------------------------------------------------------------------------------- 1 | chrome.devtools.panels.create("Breakpoints", 2 | null, 3 | "panel.html", 4 | function(panel) { 5 | 6 | } 7 | ); 8 | 9 | window.addEventListener("RebroadcastExtensionMessage", function(evt) { 10 | chrome.runtime.sendMessage(evt.detail); 11 | }, false); 12 | -------------------------------------------------------------------------------- /gh-pages/bookmarklet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JS Breakpoint Collection - Bookmarklet 5 | 6 | 7 | 24 |

JS Breakpoint Collection — Bookmarklet

25 | Add the link below to your bookmarks.
26 | After clicking on it you'll have access to the breakpoints object in the console. 27 | 28 |

29 | 30 | JS Breakpoint Collection 33 | 34 |
35 |
36 | 37 | API documentation 38 |
39 | 40 | Return to project page on Github 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /gh-pages/chrome-web-store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzeunert/javascript-breakpoint-collection/2b6124c4e7bee442a12fdc3441710e0b9ba4e22e/gh-pages/chrome-web-store.png -------------------------------------------------------------------------------- /gh-pages/expand-stack-trace-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzeunert/javascript-breakpoint-collection/2b6124c4e7bee442a12fdc3441710e0b9ba4e22e/gh-pages/expand-stack-trace-button.png -------------------------------------------------------------------------------- /gh-pages/expanded-stack-trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzeunert/javascript-breakpoint-collection/2b6124c4e7bee442a12fdc3441710e0b9ba4e22e/gh-pages/expanded-stack-trace.png -------------------------------------------------------------------------------- /gh-pages/expanded-trace-message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzeunert/javascript-breakpoint-collection/2b6124c4e7bee442a12fdc3441710e0b9ba4e22e/gh-pages/expanded-trace-message.png -------------------------------------------------------------------------------- /gh-pages/index.html: -------------------------------------------------------------------------------- 1 | jbc test 2 | aa 3 | -------------------------------------------------------------------------------- /gh-pages/live-demo-copied-code/javascript-breakpoint-collection.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define([], factory); 6 | else { 7 | var a = factory(); 8 | for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i]; 9 | } 10 | })(this, function() { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | /******/ 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | /******/ 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | /******/ 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | /******/ 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | /******/ 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | /******/ 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | /******/ 39 | /******/ 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | /******/ 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | /******/ 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | /******/ 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ({ 54 | 55 | /***/ 0: 56 | /***/ function(module, exports, __webpack_require__) { 57 | 58 | "use strict"; 59 | 60 | var _consoleInterface = __webpack_require__(165); 61 | 62 | var _consoleInterface2 = _interopRequireDefault(_consoleInterface); 63 | 64 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 65 | 66 | module.exports = _consoleInterface2.default; 67 | var isInBrowser = typeof window !== "undefined"; 68 | if (isInBrowser) { 69 | if (window.breakpoints !== undefined) { 70 | if (!window.breakpoints.__internal || !window.breakpoints.__internal.isBreakpointCollectionExtension) { 71 | console.log("Breakpoints extension can't load, global `breakpoints` variable is already defined"); 72 | } 73 | } else { 74 | window.breakpoints = _consoleInterface2.default; 75 | 76 | (0, _consoleInterface.pushRegisteredBreakpointsToExtension)(); 77 | } 78 | } 79 | 80 | /***/ }, 81 | 82 | /***/ 161: 83 | /***/ function(module, exports) { 84 | 85 | "use strict"; 86 | 87 | Object.defineProperty(exports, "__esModule", { 88 | value: true 89 | }); 90 | exports.default = [{ 91 | title: "debugScroll", 92 | debugCalls: [{ 93 | obj: "window", 94 | prop: "scrollTo" 95 | }, { 96 | obj: "window", 97 | prop: "scrollBy" 98 | }], 99 | debugPropertySets: [{ 100 | obj: "document.body", 101 | prop: "scrollTop" 102 | }, { 103 | obj: "document.body", 104 | prop: "scrollLeft" 105 | }, { 106 | obj: "Element.prototype", 107 | prop: "scrollTop" 108 | }, { 109 | obj: "Element.prototype", 110 | prop: "scrollLeft" 111 | }], 112 | getTraceInfo: function getTraceInfo(details) { 113 | if (details.propertyName == "scrollTo" || details.propertyName == "scrollBy") { 114 | return ["The scroll position of the window was changed by a window." + details.propertyName + " call with", details.callArguments]; 115 | } 116 | return ["The scroll position of", details.thisArgument, "was changed by setting the " + details.propertyName + " property to " + details.newPropertyValue]; 117 | } 118 | }, { 119 | title: "debugCookieReads", 120 | debugPropertyGets: [{ 121 | obj: "document", 122 | prop: "cookie" 123 | }], 124 | traceMessage: "Reading cookie contents" 125 | }, { 126 | title: "debugCookieWrites", 127 | debugPropertySets: [{ 128 | obj: "document", 129 | prop: "cookie" 130 | }], 131 | traceMessage: "Updating cookie contents" 132 | }, { 133 | title: "debugAlertCalls", 134 | debugCalls: [{ 135 | obj: "window", 136 | prop: "alert" 137 | }], 138 | traceMessage: "Showing alert box" 139 | }, { 140 | title: "debugElementSelection", 141 | debugCalls: [{ 142 | obj: "document", 143 | prop: "getElementById" 144 | }, { 145 | obj: "document", 146 | prop: "getElementsByClassName" 147 | }, { 148 | obj: "document", 149 | prop: "getElementsByName" 150 | }, { 151 | obj: "document", 152 | prop: "getElementsByTagName" 153 | }, { 154 | obj: "document", 155 | prop: "getElementsByTagNameNS" 156 | }, { 157 | obj: "document", 158 | prop: "getElementsByClassName" 159 | }, { 160 | obj: "document", 161 | prop: "querySelector" 162 | }, { 163 | obj: "document", 164 | prop: "querySelectorAll" 165 | }, { 166 | obj: "document", 167 | prop: "evaluate" // xpath 168 | }], 169 | getTraceInfo: function getTraceInfo(details) { 170 | return ["Selecting DOM elements \"" + details.callArguments[0] + "\" using " + details.propertyName]; 171 | } 172 | }, { 173 | title: "debugConsoleErrorCalls", 174 | debugCalls: [{ 175 | obj: "window.console", 176 | prop: "error" 177 | }], 178 | traceMessage: "Calling console.error" 179 | }, { 180 | title: "debugConsoleLogCalls", 181 | debugCalls: [{ 182 | obj: "window.console", 183 | prop: "log" 184 | }], 185 | traceMessage: "Calling console.log" 186 | }, { 187 | title: "debugConsoleTraceCalls", 188 | debugCalls: [{ 189 | obj: "window.console", 190 | prop: "trace" 191 | }], 192 | traceMessage: "Calling console.trace" 193 | }, { 194 | title: "debugMathRandom", 195 | debugCalls: [{ 196 | obj: "window.Math", 197 | prop: "random" 198 | }], 199 | getTraceInfo: function getTraceInfo() { 200 | return ["Calling Math.random"]; 201 | } 202 | }, { 203 | title: "debugTimerCreation", 204 | debugCalls: [{ 205 | obj: "window", 206 | prop: "setTimeout" 207 | }, { 208 | obj: "window", 209 | prop: "setInterval" 210 | }], 211 | getTraceInfo: function getTraceInfo(details) { 212 | return ["Creating timer using " + details.propertyName]; 213 | } 214 | }, { 215 | title: "debugLocalStorageReads", 216 | debugCalls: [{ 217 | obj: "window.localStorage", 218 | prop: "getItem" 219 | }], 220 | getTraceInfo: function getTraceInfo(details) { 221 | return ["Reading localStorage data for key \"" + details.callArguments[0] + "\""]; 222 | } 223 | }, { 224 | title: "debugLocalStorageWrites", 225 | debugCalls: [{ 226 | obj: "window.localStorage", 227 | prop: "setItem" 228 | }, { 229 | obj: "window.localStorage", 230 | prop: "clear" 231 | }], 232 | getTraceInfo: function getTraceInfo(details) { 233 | if (details.propertyName == "clear") { 234 | return ["Clearing all localStorage data"]; 235 | } 236 | return ["Writing localStorage data for key \"" + details.callArguments[0] + "\""]; 237 | } 238 | }]; 239 | 240 | /***/ }, 241 | 242 | /***/ 165: 243 | /***/ function(module, exports, __webpack_require__) { 244 | 245 | "use strict"; 246 | 247 | Object.defineProperty(exports, "__esModule", { 248 | value: true 249 | }); 250 | exports.pushRegisteredBreakpointsToExtension = undefined; 251 | 252 | var _debugObj = __webpack_require__(166); 253 | 254 | var _debugObj2 = _interopRequireDefault(_debugObj); 255 | 256 | var _predefinedBreakpoints = __webpack_require__(161); 257 | 258 | var _predefinedBreakpoints2 = _interopRequireDefault(_predefinedBreakpoints); 259 | 260 | var _getCallbackFromUserFriendlyCallbackArgument = __webpack_require__(167); 261 | 262 | var _getCallbackFromUserFriendlyCallbackArgument2 = _interopRequireDefault(_getCallbackFromUserFriendlyCallbackArgument); 263 | 264 | var _breakpointCombinations = __webpack_require__(168); 265 | 266 | var _breakpointCombinations2 = _interopRequireDefault(_breakpointCombinations); 267 | 268 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 269 | 270 | function pushRegisteredBreakpointsToExtension() { 271 | if (typeof CustomEvent === "undefined" || typeof window === "undefined" || !window.dispatchEvent) { 272 | return; // probably in a Node environment 273 | } 274 | var event = new CustomEvent("RebroadcastExtensionMessage", { 275 | type: "updateRegisteredBreakpoints", 276 | registeredBreakpoints: _breakpointCombinations2.default.getRegisteredBreakpoints() 277 | }); 278 | window.dispatchEvent(event); 279 | } 280 | 281 | var __internal = { 282 | updateBreakpointType: function updateBreakpointType(id, newType) { 283 | _breakpointCombinations2.default.updateType(id, newType); 284 | pushRegisteredBreakpointsToExtension(); 285 | }, 286 | disableBreakpoint: function disableBreakpoint(id) { 287 | _breakpointCombinations2.default.disable(id); 288 | pushRegisteredBreakpointsToExtension(); 289 | }, 290 | registerBreakpoint: function registerBreakpoint() { 291 | var id = _breakpointCombinations2.default.register.apply(null, arguments); 292 | pushRegisteredBreakpointsToExtension(); 293 | return id; 294 | }, 295 | isBreakpointCollectionExtension: true, 296 | debug: { 297 | debugObj: _debugObj2.default, 298 | debugObjBreakpointRegistry: _debugObj.debugObjBreakpointRegistry, 299 | objectsAndPropsByDebugId: _debugObj.objectsAndPropsByDebugId 300 | }, 301 | getRegisteredBreakpoints: function getRegisteredBreakpoints() { 302 | return _breakpointCombinations2.default.getRegisteredBreakpoints(); 303 | }, 304 | setTypeOfMostRecentBreakpointToDebugger: function setTypeOfMostRecentBreakpointToDebugger() { 305 | _breakpointCombinations2.default.setTypeOfMostRecentBreakpointToDebugger(); 306 | pushRegisteredBreakpointsToExtension(); 307 | } 308 | }; 309 | 310 | (0, _debugObj.disableBreakpointsDuringAllFunctionCalls)(__internal); 311 | 312 | function publicDebugPropertyAccess(obj, prop, callback, accessType) { 313 | var functionName = { 314 | "get": "debugPropertyGet", 315 | "set": "debugPropertySet", 316 | "call": "debugPropertyCall" 317 | }[accessType]; 318 | 319 | callback = (0, _getCallbackFromUserFriendlyCallbackArgument2.default)(callback); 320 | __internal.registerBreakpoint(function (debugPropertyGet, debugPropertySet, debugPropertyCall) { 321 | var debugFunctions = { 322 | debugPropertyGet: debugPropertyGet, 323 | debugPropertySet: debugPropertySet, 324 | debugPropertyCall: debugPropertyCall 325 | }; 326 | debugFunctions[functionName](obj, prop, callback); 327 | }, { 328 | title: functionName + " (" + prop + ")", 329 | type: callback.callbackType 330 | }); 331 | } 332 | 333 | var breakpoints = { 334 | debugPropertyGet: function debugPropertyGet(obj, prop, callback) { 335 | return publicDebugPropertyAccess(obj, prop, callback, "get"); 336 | }, 337 | debugPropertySet: function debugPropertySet(obj, prop, callback) { 338 | return publicDebugPropertyAccess(obj, prop, callback, "set"); 339 | }, 340 | debugPropertyCall: function debugPropertyCall(obj, prop, callback) { 341 | return publicDebugPropertyAccess(obj, prop, callback, "call"); 342 | }, 343 | resetAllBreakpoints: function resetAllBreakpoints() { 344 | _breakpointCombinations2.default.resetAll(); 345 | pushRegisteredBreakpointsToExtension(); 346 | }, 347 | resetLastBreakpoint: function resetLastBreakpoint() { 348 | if (_breakpointCombinations2.default.getRegisteredBreakpoints().length === 0) { 349 | console.log("No breakpoints are currently registered"); 350 | return; 351 | } 352 | _breakpointCombinations2.default.resetLastBreakpoint(); 353 | pushRegisteredBreakpointsToExtension(); 354 | }, 355 | __internal: __internal 356 | }; 357 | 358 | _predefinedBreakpoints2.default.forEach(function (breakpoint) { 359 | breakpoints[breakpoint.title] = function (callback) { 360 | callback = (0, _getCallbackFromUserFriendlyCallbackArgument2.default)(callback, breakpoint); 361 | 362 | var details = { 363 | title: breakpoint.title, 364 | type: callback.callbackType 365 | }; 366 | 367 | var fn = function fn(debugPropertyGet, debugPropertySet, debugPropertyCall) { 368 | if (breakpoint.debugPropertyGets) { 369 | breakpoint.debugPropertyGets.forEach(function (property) { 370 | debugPropertyGet(eval(property.obj), property.prop, callback); 371 | }); 372 | } 373 | if (breakpoint.debugPropertySets) { 374 | breakpoint.debugPropertySets.forEach(function (property) { 375 | debugPropertySet(eval(property.obj), property.prop, callback); 376 | }); 377 | } 378 | if (breakpoint.debugCalls) { 379 | breakpoint.debugCalls.forEach(function (property) { 380 | debugPropertyCall(eval(property.obj), property.prop, callback); 381 | }); 382 | } 383 | }; 384 | 385 | __internal.registerBreakpoint(fn, details, breakpoint); 386 | pushRegisteredBreakpointsToExtension(); 387 | }; 388 | }); 389 | 390 | (0, _debugObj.disableBreakpointsDuringAllFunctionCalls)(breakpoints); 391 | 392 | exports.default = breakpoints; 393 | exports.pushRegisteredBreakpointsToExtension = pushRegisteredBreakpointsToExtension; 394 | 395 | /***/ }, 396 | 397 | /***/ 166: 398 | /***/ function(module, exports) { 399 | 400 | "use strict"; 401 | 402 | Object.defineProperty(exports, "__esModule", { 403 | value: true 404 | }); 405 | 406 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 407 | 408 | exports.runWithBreakpointsDisabled = runWithBreakpointsDisabled; 409 | exports.disableBreakpointsDuringAllFunctionCalls = disableBreakpointsDuringAllFunctionCalls; 410 | exports.default = debugObj; 411 | exports.updateDebugIdCallback = updateDebugIdCallback; 412 | exports.resetDebug = resetDebug; 413 | var registry = new Map(); 414 | var objectsAndPropsByDebugId = {}; 415 | 416 | var hookNames = ["propertyGetBefore", "propertyGetAfter", "propertySetBefore", "propertySetAfter", "propertyCallBefore", "propertyCallAfter"]; 417 | 418 | function getPropertyDescriptor(object, propertyName) { 419 | try { 420 | var descriptor = Object.getOwnPropertyDescriptor(object, propertyName); 421 | } catch (err) { 422 | var newError = Error("Are you sure the property \"" + propertyName + "\" exists?"); 423 | newError.originalError = err; 424 | throw newError; 425 | } 426 | if (!object) { 427 | throw new Error("Descriptor " + propertyName + " not found"); 428 | } 429 | if (!descriptor) { 430 | return getPropertyDescriptor(Object.getPrototypeOf(object), propertyName); 431 | } 432 | return descriptor; 433 | } 434 | 435 | exports.debugObjBreakpointRegistry = registry; 436 | exports.objectsAndPropsByDebugId = objectsAndPropsByDebugId; 437 | 438 | // counter instead of boolean to allow nested calls of runWithBreakpointsDisabled 439 | 440 | var timesBreakpointsWereDisabled = 0; 441 | function runWithBreakpointsDisabled(fn) { 442 | timesBreakpointsWereDisabled++; 443 | var retVal = fn(); 444 | timesBreakpointsWereDisabled--; 445 | return retVal; 446 | } 447 | 448 | function disableBreakpointsDuringAllFunctionCalls(object) { 449 | var _loop = function _loop() { 450 | var fn = object[functionName]; 451 | if (typeof fn !== "function") { 452 | return "continue"; 453 | } 454 | 455 | object[functionName] = function () { 456 | var thisArg = this; 457 | var args = arguments; 458 | return runWithBreakpointsDisabled(function () { 459 | return fn.apply(thisArg, args); 460 | }); 461 | }; 462 | }; 463 | 464 | for (var functionName in object) { 465 | var _ret = _loop(); 466 | 467 | if (_ret === "continue") continue; 468 | } 469 | } 470 | 471 | function areBreakpointsDisabled() { 472 | return timesBreakpointsWereDisabled > 0; 473 | } 474 | 475 | function debugObj(obj, prop, hooks) { 476 | var debugId = Math.floor(Math.random() * 100000000000).toString(); 477 | objectsAndPropsByDebugId[debugId] = { 478 | obj: obj, 479 | prop: prop 480 | }; 481 | 482 | if (registry.get(obj) === undefined) { 483 | registry.set(obj, {}); 484 | } 485 | 486 | if (registry.get(obj)[prop] === undefined) { 487 | registry.get(obj)[prop] = { hooks: {} }; 488 | 489 | var originalProp = getPropertyDescriptor(obj, prop); 490 | var isSimpleValue = "value" in originalProp; // rather than getter + setter 491 | 492 | Object.defineProperty(obj, prop, { 493 | get: function get() { 494 | var retVal; 495 | 496 | triggerHook("propertyGetBefore", { 497 | thisArgument: this 498 | }); 499 | 500 | if (isSimpleValue) { 501 | retVal = originalProp.value; 502 | } else { 503 | retVal = originalProp.get.apply(this, arguments); 504 | } 505 | 506 | triggerHook("propertyGetAfter", { 507 | thisArgument: this 508 | }); 509 | 510 | if (typeof retVal === "function") { 511 | return function () { 512 | var args = Array.prototype.slice.call(arguments); 513 | 514 | triggerHook("propertyCallBefore", { 515 | callArguments: args, 516 | thisArgument: this 517 | }); 518 | 519 | var fnRetVal = retVal.apply(this, arguments); 520 | 521 | triggerHook("propertyCallAfter", { 522 | callArguments: args, 523 | thisArgument: this 524 | }); 525 | 526 | return fnRetVal; 527 | }; 528 | } 529 | 530 | return retVal; 531 | }, 532 | set: function set(newValue) { 533 | var retVal; 534 | 535 | triggerHook("propertySetBefore", { 536 | newPropertyValue: newValue, 537 | thisArgument: this 538 | }); 539 | 540 | if (isSimpleValue) { 541 | retVal = originalProp.value = newValue; 542 | } else { 543 | retVal = originalProp.set.apply(this, arguments); 544 | } 545 | 546 | triggerHook("propertySetAfter", { 547 | newPropertyValue: newValue, 548 | thisArgument: this 549 | }); 550 | 551 | return retVal; 552 | } 553 | }); 554 | } 555 | 556 | hookNames.forEach(function (hookName) { 557 | if (hooks[hookName] !== undefined) { 558 | if (registry.get(obj)[prop].hooks[hookName] === undefined) { 559 | registry.get(obj)[prop].hooks[hookName] = []; 560 | } 561 | var hook = hooks[hookName]; 562 | registry.get(obj)[prop].hooks[hookName].push({ 563 | id: debugId, 564 | fn: hook.fn, 565 | data: hook.data 566 | }); 567 | } 568 | }); 569 | 570 | return debugId; 571 | 572 | function triggerHook(hookName, additionalHookInfo) { 573 | if (areBreakpointsDisabled()) { 574 | return; 575 | } 576 | var hooks = registry.get(obj)[prop].hooks; 577 | var hooksWithName = hooks[hookName]; 578 | 579 | var infoForHook = _extends({ 580 | object: obj, 581 | propertyName: prop 582 | }, additionalHookInfo); 583 | 584 | if (hooksWithName !== undefined && hooksWithName.length > 0) { 585 | hooksWithName.forEach(function (hook) { 586 | hook.fn(_extends({}, infoForHook, { 587 | accessType: getAccessTypeFromHookName(hookName), 588 | data: hook.data 589 | })); 590 | }); 591 | } 592 | } 593 | } 594 | 595 | function getAccessTypeFromHookName(hookName) { 596 | var accessType = ""; 597 | if (hookName === "propertyGetBefore" || hookName === "propertyGetAfter") { 598 | accessType = "get"; 599 | } 600 | if (hookName === "propertySetBefore" || hookName === "propertySetAfter") { 601 | accessType = "set"; 602 | } 603 | if (hookName === "propertyCallBefore" || hookName === "propertyCallAfter") { 604 | accessType = "call"; 605 | } 606 | return accessType; 607 | } 608 | 609 | function updateEachHook(obj, prop, cb) { 610 | var hooks = registry.get(obj)[prop].hooks; 611 | hookNames.forEach(function (hookName) { 612 | var hooksWithName = hooks[hookName]; 613 | if (hooksWithName !== undefined) { 614 | hooks[hookName] = hooksWithName.map(function (hook) { 615 | return cb(hook); 616 | }); 617 | } 618 | }); 619 | } 620 | 621 | function updateDebugIdCallback(debugId, callback) { 622 | var objAndProp = objectsAndPropsByDebugId[debugId]; 623 | updateEachHook(objAndProp.obj, objAndProp.prop, function (hook) { 624 | if (hook.id === debugId) { 625 | return { 626 | id: debugId, 627 | fn: callback, 628 | data: hook.data 629 | }; 630 | } else { 631 | return hook; 632 | } 633 | }); 634 | } 635 | 636 | function resetDebug(id) { 637 | var objAndProp = objectsAndPropsByDebugId[id]; 638 | var hooks = registry.get(objAndProp.obj)[objAndProp.prop].hooks; 639 | for (var hookName in hooks) { 640 | var hooksWithName = hooks[hookName]; 641 | hooks[hookName] = hooksWithName.filter(function (hook) { 642 | return hook.id != id; 643 | }); 644 | } 645 | 646 | delete objectsAndPropsByDebugId[id]; 647 | } 648 | 649 | /***/ }, 650 | 651 | /***/ 167: 652 | /***/ function(module, exports, __webpack_require__) { 653 | 654 | "use strict"; 655 | 656 | Object.defineProperty(exports, "__esModule", { 657 | value: true 658 | }); 659 | 660 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; 661 | 662 | exports.default = getCallbackFromUserFriendlyCallbackArgument; 663 | 664 | var _debugObj = __webpack_require__(166); 665 | 666 | function getDebuggerFunction() { 667 | var debuggerFunc = function debuggerFunc() { 668 | debugger; 669 | }; 670 | debuggerFunc.callbackType = "debugger"; 671 | return debuggerFunc; 672 | } 673 | 674 | function getCallbackFromUserFriendlyCallbackArgument(callback, predefinedBreakpoint) { 675 | if (typeof callback === "function") { 676 | callback.callbackType = "custom"; 677 | return callback; 678 | } else if (typeof callback === "string") { 679 | if (callback === "debugger") { 680 | return getDebuggerFunction(); 681 | } else if (callback === "trace") { 682 | return getTraceFunction(predefinedBreakpoint); 683 | } else { 684 | throw new Error("Invalid string callback"); 685 | } 686 | } else if (typeof callback === "undefined") { 687 | return getDebuggerFunction(); 688 | } else { 689 | throw new Error("Invalid callback type: " + (typeof callback === "undefined" ? "undefined" : _typeof(callback))); 690 | } 691 | } 692 | 693 | function getTraceFunction(predefinedBreakpoint) { 694 | var traceFn; 695 | if (predefinedBreakpoint) { 696 | if (predefinedBreakpoint.getTraceInfo) { 697 | traceFn = function traceFn() { 698 | var traceArgs = predefinedBreakpoint.getTraceInfo.apply(null, arguments); 699 | (0, _debugObj.runWithBreakpointsDisabled)(function () { 700 | console.trace.apply(console, traceArgs); 701 | }); 702 | }; 703 | } else if (predefinedBreakpoint.traceMessage) { 704 | traceFn = function traceFn() { 705 | (0, _debugObj.runWithBreakpointsDisabled)(function () { 706 | console.trace(predefinedBreakpoint.traceMessage); 707 | }); 708 | }; 709 | } 710 | } else { 711 | traceFn = function traceFn(debugInfo) { 712 | (0, _debugObj.runWithBreakpointsDisabled)(function () { 713 | console.trace("About to " + debugInfo.accessType + " property '" + debugInfo.propertyName + "' on this object: ", debugInfo.object); 714 | }); 715 | }; 716 | } 717 | 718 | traceFn.callbackType = "trace"; 719 | return traceFn; 720 | } 721 | 722 | /***/ }, 723 | 724 | /***/ 168: 725 | /***/ function(module, exports, __webpack_require__) { 726 | 727 | "use strict"; 728 | 729 | Object.defineProperty(exports, "__esModule", { 730 | value: true 731 | }); 732 | 733 | var _debugObj = __webpack_require__(166); 734 | 735 | var _debugObj2 = _interopRequireDefault(_debugObj); 736 | 737 | var _getCallbackFromUserFriendlyCallbackArgument = __webpack_require__(167); 738 | 739 | var _getCallbackFromUserFriendlyCallbackArgument2 = _interopRequireDefault(_getCallbackFromUserFriendlyCallbackArgument); 740 | 741 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 742 | 743 | var registeredBreakpoints = []; 744 | 745 | function debugPropertyCall(object, prop, callback) { 746 | return (0, _debugObj2.default)(object, prop, { 747 | propertyCallBefore: { 748 | fn: callback 749 | } 750 | }); 751 | } 752 | 753 | var debugPropertyGet = function debugPropertyGet(object, propertyName, callback) { 754 | return (0, _debugObj2.default)(object, propertyName, { 755 | propertyGetBefore: { 756 | fn: callback 757 | } 758 | }); 759 | }; 760 | var debugPropertySet = function debugPropertySet(object, propertyName, callback) { 761 | return (0, _debugObj2.default)(object, propertyName, { 762 | propertySetBefore: { 763 | fn: callback 764 | } 765 | }); 766 | }; 767 | 768 | var breakpointCombinations = { 769 | register: function register(fn, bpDetails, predefinedBreakpoint) { 770 | var debugIds = []; 771 | var _debugPropertyGet = function _debugPropertyGet(object, propertyName, callback) { 772 | debugIds.push(debugPropertyGet(object, propertyName, callback)); 773 | }; 774 | var _debugPropertySet = function _debugPropertySet(object, propertyName, callback) { 775 | debugIds.push(debugPropertySet(object, propertyName, callback)); 776 | }; 777 | var _debugPropertyCall = function _debugPropertyCall(object, propertyName, callback) { 778 | debugIds.push(debugPropertyCall(object, propertyName, callback)); 779 | }; 780 | fn(_debugPropertyGet, _debugPropertySet, _debugPropertyCall); 781 | 782 | var id = Math.floor(Math.random() * 1000000000); 783 | var bp = { 784 | id: id, 785 | debugIds: debugIds, 786 | details: bpDetails, 787 | createdAt: new Date(), 788 | predefinedBreakpoint: predefinedBreakpoint 789 | }; 790 | registeredBreakpoints.push(bp); 791 | 792 | return id; 793 | }, 794 | disable: function disable(id) { 795 | var bp = registeredBreakpoints.filter(function (bp) { 796 | return bp.id == id; 797 | })[0]; 798 | if (bp === undefined) { 799 | console.log("Couldn't find breakpoint with id", id); 800 | return; 801 | } 802 | bp.debugIds.forEach(function (debugId) { 803 | (0, _debugObj.resetDebug)(debugId); 804 | }); 805 | registeredBreakpoints = registeredBreakpoints.filter(function (bp) { 806 | return bp.id != id; 807 | }); 808 | }, 809 | updateType: function updateType(id, newType) { 810 | if (newType !== "debugger" && newType !== "trace") { 811 | throw new Error("Invalid breakpoint type"); 812 | } 813 | 814 | var bp = registeredBreakpoints.filter(function (bp) { 815 | return bp.id == id; 816 | })[0]; 817 | 818 | var callback = (0, _getCallbackFromUserFriendlyCallbackArgument2.default)(newType, bp.predefinedBreakpoint); 819 | bp.debugIds.forEach(function (debugId) { 820 | (0, _debugObj.updateDebugIdCallback)(debugId, callback); 821 | }); 822 | 823 | bp.details.type = newType; 824 | }, 825 | resetAll: function resetAll() { 826 | registeredBreakpoints.forEach(function (breakpoint) { 827 | breakpointCombinations.disable(breakpoint.id); 828 | }); 829 | }, 830 | getRegisteredBreakpoints: function getRegisteredBreakpoints() { 831 | return registeredBreakpoints; 832 | }, 833 | resetLastBreakpoint: function resetLastBreakpoint() { 834 | var breakpointToReset = registeredBreakpoints[registeredBreakpoints.length - 1]; 835 | breakpointCombinations.disable(breakpointToReset.id); 836 | }, 837 | setTypeOfMostRecentBreakpointToDebugger: function setTypeOfMostRecentBreakpointToDebugger() { 838 | var mostRecentBreakpoint = registeredBreakpoints[registeredBreakpoints.length - 1]; 839 | breakpointCombinations.updateType(mostRecentBreakpoint.id, "debugger"); 840 | } 841 | }; 842 | 843 | (0, _debugObj.disableBreakpointsDuringAllFunctionCalls)(breakpointCombinations); 844 | 845 | exports.default = breakpointCombinations; 846 | 847 | /***/ } 848 | 849 | /******/ }) 850 | }); 851 | ; 852 | //# sourceMappingURL=javascript-breakpoint-collection.js.map -------------------------------------------------------------------------------- /gh-pages/live-demo-copied-code/panel.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | h2 { 5 | padding: 10px; 6 | font-weight: normal; 7 | padding-top: 7px; 8 | padding-bottom: 6px; 9 | margin: 0; 10 | background: #fafafa; 11 | border-bottom: 1px solid #eee 12 | } 13 | body { 14 | margin: 0; 15 | font-family: 'Helvetica Neue', 'Lucida Grande', sans-serif; 16 | font-size: 75%; 17 | } 18 | a { 19 | color: #698CFE; 20 | } 21 | .col-parent > div { 22 | width: 33.3333%; 23 | float: left; 24 | border-right: 1px solid #eee; 25 | height: 100%; 26 | } 27 | .col-parent > div:first-child { 28 | background: #fafafa; 29 | } 30 | .col-parent > div:last-child { 31 | border-right: none; 32 | } 33 | .col-parent .col-content { 34 | padding: 10px; 35 | padding-top: 0px; 36 | height: calc(100% - 36px); 37 | overflow: auto; 38 | } 39 | .unactivated-breakpoint-list-item { 40 | border-bottom: 1px solid #eee; 41 | padding: 10px; 42 | cursor: pointer; 43 | } 44 | .unactivated-breakpoint-list-item:hover { 45 | background: #fafafa; 46 | } 47 | .unactivated-breakpoint-list-item .plus { 48 | display: none; 49 | 50 | float: right; 51 | font-size: 24px; 52 | color: red; 53 | width: 20px; 54 | height: 20px; 55 | text-align: center; 56 | margin-top: -3px; 57 | line-height: 16px; 58 | } 59 | .unactivated-breakpoint-list-item:hover .plus{ 60 | display: inline-block; 61 | } 62 | 63 | .activated-breakpiont-list-item { 64 | border-bottom: 1px solid #eee; 65 | padding: 10px; 66 | } 67 | .activated-breakpiont-list-item .delete { 68 | float: right; 69 | color: #777; 70 | font-size: 24px; 71 | line-height: 30px; 72 | text-align: center; 73 | width: 35px; 74 | height: 35px; 75 | padding: 0; 76 | background: none; 77 | border: none; 78 | border-radius: 550px; 79 | cursor: pointer; 80 | } 81 | .activated-breakpiont-list-item:active { 82 | outline: none; 83 | } 84 | .activated-breakpiont-list-item .delete:hover { 85 | color: red; 86 | } 87 | 88 | .breakpoint-type-selector__option { 89 | display: inline-block; 90 | margin-right: 4px; 91 | padding: 4px; 92 | padding-top: 2px; 93 | padding-bottom: 2px; 94 | border: 1px solid #ccc; 95 | cursor: pointer; 96 | border-radius: 4px; 97 | } 98 | .breakpoint-type-selector__option:hover { 99 | background: red !important; 100 | border-color: red !important; 101 | color: white !important; 102 | } 103 | 104 | .left-column-header { 105 | background: #698CFE; 106 | color: white; 107 | padding: 10px; 108 | font-weight: bold; 109 | border-bottom: 1px solid #eee; 110 | } 111 | 112 | .code-section { 113 | max-width: 100%; 114 | overflow: auto; 115 | padding: 10px; 116 | background: #eee; 117 | } 118 | .code-section pre { 119 | font-family: Arial; 120 | padding-bottom: 2px; 121 | margin: 0; 122 | } 123 | 124 | .available-breakpoints-search-input { 125 | float: right; 126 | margin-top: 8px; 127 | margin-right: 10px; 128 | background: #eee; 129 | border: none; 130 | padding: 3px; 131 | } 132 | -------------------------------------------------------------------------------- /gh-pages/live-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JS Breakpoint Collection - Live Demo 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 14 | 15 | 16 |

19 | JS Breakpoint Collection — Live Demo 20 |

21 | View on Github 22 |
23 | 24 |
25 |
26 |
27 | 28 | Find the code that's causing the scroll change in the box on the right 29 | 30 |
    31 |
  1. Open Chrome's DevTools so you can see the console
  2. 32 |
  3. Click on debugScroll in the list of breakpoints below
  4. 33 |
  5. A trace message will appear every time the scroll position is updated
  6. 34 |
  7. Expand the trace message to see the stack trace 35 | () 36 |
  8. 37 |
38 | 39 | 40 |
41 | 42 |
43 |
44 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus fermentum justo sit amet ullamcorper porta. In viverra mattis diam, non accumsan tellus accumsan a. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis egestas odio vitae urna fringilla lobortis. Sed ut gravida mauris. Sed laoreet leo id enim ornare imperdiet. Ut venenatis sem sit amet odio posuere finibus. Maecenas elementum est eget libero auctor, eget eleifend libero hendrerit. Integer non risus imperdiet ipsum convallis dapibus. Quisque finibus orci a maximus viverra. Aliquam ante lectus, ornare non purus eget, finibus ultrices ante. Etiam eu elit vulputate, rhoncus elit et, hendrerit ex. 45 |
46 | 62 |
63 |
64 |
65 | 70 |
71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /injected-script.js: -------------------------------------------------------------------------------- 1 | import consoleInterface , {pushRegisteredBreakpointsToExtension} from "./breakpoints/consoleInterface" 2 | 3 | module.exports = consoleInterface; 4 | var isInBrowser = typeof window !== "undefined"; 5 | if (isInBrowser) { 6 | if (window.breakpoints !== undefined) { 7 | if (!window.breakpoints.__internal || !window.breakpoints.__internal.isBreakpointCollectionExtension) { 8 | console.log("Breakpoints extension can't load, global `breakpoints` variable is already defined") 9 | } 10 | } else { 11 | window.breakpoints = consoleInterface; 12 | 13 | pushRegisteredBreakpointsToExtension(); 14 | 15 | if (window.__BP_SHOW_CONSOLE_API_MESSAGE){ 16 | delete window.__BP_SHOW_CONSOLE_API_MESSAGE; 17 | console.log("Breakpoints Collection API docs: https://github.com/mattzeunert/javascript-breakpoint-collection/blob/master/console-api.md") 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | module.exports = function (config) { 4 | var configuration = { 5 | browsers: ['Chrome'], 6 | singleRun: false, 7 | frameworks: ['jasmine'], 8 | files: ['webpack-test.config.js', 9 | { pattern: 'extension/build/javascript-breakpoint-collection.js', included: true, watched: true } 10 | ], 11 | proxies: { 12 | '/extension/': '/base/extension/' 13 | }, 14 | preprocessors: { 15 | 'webpack-test.config.js': ['webpack', 'sourcemap'] 16 | }, 17 | customLaunchers: { 18 | Chrome_travis_ci: { 19 | base: 'Chrome', 20 | flags: ['--no-sandbox'] 21 | } 22 | }, 23 | reporters: ['dots'], 24 | webpack: { 25 | module: { 26 | loaders: [ 27 | { 28 | test: /\.jsx?$/, 29 | exclude: /node_modules/, 30 | loader: 'babel-loader' 31 | }] 32 | }, 33 | watch: true, 34 | resolve: { 35 | extensions: ["", ".js", ".jsx", ".js.jsx"] 36 | }, 37 | devtool: 'inline-source-map', 38 | }, 39 | webpackServer: { 40 | noInfo: true 41 | } 42 | } 43 | if (process.env.TRAVIS) { 44 | configuration.browsers = ['Chrome_travis_ci']; 45 | } 46 | 47 | config.set(configuration); 48 | }; 49 | -------------------------------------------------------------------------------- /node-test.js: -------------------------------------------------------------------------------- 1 | 2 | console.log("start") 3 | var breakpoints = require("./dist/node.js") 4 | var obj = {"hi": "ho"} 5 | breakpoints.debugPropertyGet(obj, "hi", "trace") 6 | obj.hi 7 | console.log("end") 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javascript-breakpoint-collection", 3 | "version": "1.7.0", 4 | "description": "", 5 | "main": "dist/node.js", 6 | "scripts": { 7 | "test": "karma start", 8 | "build": "webpack", 9 | "ci-test": "karma start --single-run" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/mattzeunert/javascript-breakpoint-collection.git" 14 | }, 15 | "author": "Matt Zeunert", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/mattzeunert/javascript-breakpoint-collection/issues" 19 | }, 20 | "homepage": "https://github.com/mattzeunert/javascript-breakpoint-collection#readme", 21 | "dependencies": {}, 22 | "devDependencies": { 23 | "babel-core": "^6.7.4", 24 | "babel-loader": "^6.2.4", 25 | "babel-plugin-transform-object-rest-spread": "^6.6.5", 26 | "babel-preset-es2015": "^6.6.0", 27 | "babel-preset-react": "^6.5.0", 28 | "jasmine-core": "^2.4.1", 29 | "karma": "^0.13.22", 30 | "karma-chrome-launcher": "^0.2.3", 31 | "karma-jasmine": "^0.3.8", 32 | "karma-sourcemap-loader": "^0.3.7", 33 | "karma-webpack": "^1.7.0", 34 | "react-addons-test-utils": "^0.14.8", 35 | "uglify-js": "^2.6.2", 36 | "webpack": "^1.12.14", 37 | "react": "^0.14.8", 38 | "react-dom": "^0.14.8" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/app-spec.js: -------------------------------------------------------------------------------- 1 | import AppView from "../app/AppView" 2 | import TestUtils from "react-addons-test-utils" 3 | import React from "react" 4 | import ReactDOM from "react-dom" 5 | import AvailableBreakpointsList from "../app/AvailableBreakpointsList" 6 | import predefinedBreakpoints from "../breakpoints/predefinedBreakpoints" 7 | 8 | describe("Breakpoints UI/App", function(){ 9 | var component = TestUtils.renderIntoDocument(); 10 | var node = ReactDOM.findDOMNode(component); 11 | 12 | it("Shows a list of predefined breakpoints", function(){ 13 | var availableBreakpointsListComponent = TestUtils.findRenderedComponentWithType( 14 | component, 15 | AvailableBreakpointsList 16 | ); 17 | var listNode = ReactDOM.findDOMNode(availableBreakpointsListComponent); 18 | expect(listNode.children.length).toBe(predefinedBreakpoints.length) 19 | }) 20 | 21 | it("After adding a breakpoint it appears in the list of activated breakpoints", function(){ 22 | var addTraceToCookieReadsButton = node.querySelector("[data-test-marker-available-bp-title='debugCookieReads']") 23 | TestUtils.Simulate.click(addTraceToCookieReadsButton); 24 | 25 | var activatedBreakpoint = node.querySelector("[data-test-marker-activated-bp-title='debugCookieReads']") 26 | expect(activatedBreakpoint).not.toBe(null) 27 | }) 28 | 29 | it("Shows a trace message when reading the cookie", function(){ 30 | spyOn(console, "trace"); 31 | document.cookie; 32 | expect(console.trace).toHaveBeenCalled(); 33 | }) 34 | 35 | it("Removes the breakpoint when clicking on the delete button", function(){ 36 | var deleteButton = node.querySelector("[data-test-marker-delete-bp]"); 37 | TestUtils.Simulate.click(deleteButton); 38 | spyOn(console, "trace") 39 | document.cookie; 40 | expect(console.trace).not.toHaveBeenCalled(); 41 | }) 42 | 43 | it("Doesn't show the breakpoint in the list of activated breakpoint after removing the breakpoint", function(){ 44 | var activatedBreakpointElement = node.querySelector("[data-test-marker-activated-bp-title='debugCookieReads']"); 45 | expect(activatedBreakpointElement).toBe(null); 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /tests/breakpointCombinations-spec.js: -------------------------------------------------------------------------------- 1 | import breakpointCombinations from "../breakpoints/breakpointCombinations" 2 | 3 | describe("breakpointCombinations", function(){ 4 | it("Can register a breakpiontCombination that breaks in multiple situations", function(){ 5 | var obj = {hello: "world"} 6 | var propertyGetCallback = jasmine.createSpy(); 7 | var propertySetCallback = jasmine.createSpy(); 8 | var fn = function(debugPropertyGet, debugPropertySet, debugPropertyCall){ 9 | debugPropertyGet(obj, "hello", propertyGetCallback); 10 | debugPropertySet(obj, "hello", propertySetCallback); 11 | } 12 | breakpointCombinations.register(fn, {}); 13 | 14 | obj.hello; 15 | expect(propertyGetCallback).toHaveBeenCalled(); 16 | expect(propertySetCallback).not.toHaveBeenCalled(); 17 | 18 | obj.hello = "hi"; 19 | expect(propertySetCallback).toHaveBeenCalled(); 20 | }) 21 | 22 | it("Can disable a registerered breakpoint", function(){ 23 | var obj = {hello: "world"}; 24 | var callback = jasmine.createSpy(); 25 | var fn = function(debugPropertyGet) { 26 | debugPropertyGet(obj, "hello", callback); 27 | } 28 | var id = breakpointCombinations.register(fn, {}); 29 | breakpointCombinations.disable(id); 30 | obj.hello; 31 | expect(callback).not.toHaveBeenCalled(); 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /tests/debugObj-spec.js: -------------------------------------------------------------------------------- 1 | import "../extension/build/javascript-breakpoint-collection" 2 | import debugObj, {updateDebugIdCallback} from "../breakpoints/debugObj" 3 | 4 | describe("debugObj", function(){ 5 | it("When debugging a function it passes the call arguments and value of `this` into the hook function", function(){ 6 | var obj = { 7 | sayHi: function(){} 8 | }; 9 | var fn = jasmine.createSpy(); 10 | debugObj(obj, "sayHi", { 11 | propertyCallBefore: { 12 | fn: fn 13 | } 14 | }) 15 | obj.sayHi(1,2,3) 16 | expect(fn.calls.mostRecent().args[0].callArguments).toEqual([1,2,3]) 17 | expect(fn.calls.mostRecent().args[0].thisArgument).toBe(obj) 18 | 19 | }); 20 | it("Lets you provide additional data to be passed into the hook function", function(){ 21 | var obj = { 22 | sayHi: function(){} 23 | } 24 | var fn = jasmine.createSpy(); 25 | debugObj(obj, "sayHi", { 26 | propertyCallBefore: { 27 | fn: fn, 28 | data: { 29 | message: "test" 30 | } 31 | } 32 | }) 33 | 34 | obj.sayHi(); 35 | expect(fn.calls.mostRecent().args[0].data.message).toEqual("test"); 36 | }) 37 | 38 | it("Throws an exception if the property that should be debugged doesn't exist", function(){ 39 | var obj = {}; 40 | expect(function(){ 41 | debugObj(obj, "hi", {}); 42 | }).toThrowError("Are you sure the property \"hi\" exists?"); 43 | }) 44 | }); 45 | 46 | describe("updateDebugIdCallback", function(){ 47 | it("Preserves data after updating a callback", function(){ 48 | var obj = {hello: "world"} 49 | var fn = jasmine.createSpy(); 50 | var id = debugObj(obj, "hello", { 51 | propertyGetBefore: {fn: fn, data: "test" } 52 | }); 53 | 54 | var fn2 = jasmine.createSpy(); 55 | updateDebugIdCallback(id, fn2) 56 | 57 | obj.hello 58 | expect(fn).not.toHaveBeenCalled(); 59 | expect(fn2).toHaveBeenCalled(); 60 | expect(fn2.calls.mostRecent().args[0].data).toBe("test"); 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /tests/injected-integration-spec.js: -------------------------------------------------------------------------------- 1 | describe("Injected script integration test", function(){ 2 | it("Can update the type of a breakpoint", function(){ 3 | var obj = {hello: "world"} 4 | spyOn(console, "trace") 5 | breakpoints.debugPropertyGet(obj, "hello"); 6 | var id = breakpoints.__internal.getRegisteredBreakpoints()[0].id; 7 | breakpoints.__internal.updateBreakpointType(id, "trace"); 8 | var hi = obj.hello; 9 | 10 | expect(console.trace.calls.mostRecent().args[0]).toBe("About to get property 'hello' on this object: ") 11 | }) 12 | }); -------------------------------------------------------------------------------- /tests/predefinedBreakpoints-spec.js: -------------------------------------------------------------------------------- 1 | import "../extension/build/javascript-breakpoint-collection" 2 | 3 | describe("Combining multiple breakpoints on the same object", function(){ 4 | it("Can combine setters and getters", function(){ 5 | var obj = { 6 | hi: "there" 7 | }; 8 | var fn = jasmine.createSpy(); 9 | breakpoints.debugPropertyGet(obj, "hi", fn); 10 | breakpoints.debugPropertyGet(obj, "hi", fn); 11 | breakpoints.debugPropertySet(obj, "hi", fn); 12 | 13 | var sth = obj.hi; 14 | expect(fn.calls.count()).toBe(2); 15 | 16 | obj.hi = "hey"; 17 | expect(fn.calls.count()).toBe(3); 18 | }) 19 | it("Can debug multiple properties of an object", function(){ 20 | var obj = { 21 | hi: "there", 22 | hello: "world" 23 | } 24 | var fn = jasmine.createSpy(); 25 | breakpoints.debugPropertyGet(obj, "hi", fn); 26 | breakpoints.debugPropertyGet(obj, "hello", fn); 27 | 28 | var sth = obj.hi; 29 | expect(fn.calls.count()).toBe(1); 30 | 31 | sth = obj.hello; 32 | expect(fn.calls.count()).toBe(2); 33 | }) 34 | }); 35 | 36 | describe("breakpoints.resetAllBreakpoints", function(){ 37 | it("Disables all breakpoints", function(){ 38 | var obj = {hi: "there"} 39 | var fn1 = jasmine.createSpy(); 40 | breakpoints.debugPropertySet(obj, "hi", fn1); 41 | 42 | var fn2 = jasmine.createSpy(); 43 | breakpoints.debugCookieReads(fn2); 44 | 45 | breakpoints.resetAllBreakpoints(); 46 | var cookie = document.cookie; 47 | obj.hi = "hey"; 48 | 49 | expect(fn1).not.toHaveBeenCalled(); 50 | expect(fn2).not.toHaveBeenCalled(); 51 | }); 52 | }); 53 | 54 | describe("breakpoints.resetLastBreakpoint", function(){ 55 | it("Disables the last breakpoint that was added", function(){ 56 | var fn = jasmine.createSpy(); 57 | breakpoints.debugCookieReads(fn); 58 | breakpoints.resetLastBreakpoint(); 59 | var cookie = document.cookie; 60 | expect(fn).not.toHaveBeenCalled(); 61 | }) 62 | it("Disables the breakpoints in reverse order", function(){ 63 | var fn1 = jasmine.createSpy(); 64 | breakpoints.debugCookieReads(fn1); 65 | var fn2 = jasmine.createSpy(); 66 | breakpoints.debugCookieWrites(fn2); 67 | breakpoints.resetLastBreakpoint(); 68 | document.cookie = "test"; 69 | var cookie = document.cookie; 70 | expect(fn2).not.toHaveBeenCalled(); 71 | expect(fn1).toHaveBeenCalled(); 72 | breakpoints.resetLastBreakpoint(); 73 | cookie = document.cookie; 74 | expect(fn1.calls.count()).toBe(1); 75 | }) 76 | }) 77 | 78 | describe("debugPropertyGet", function(){ 79 | it("Calls a callback before a property is read", function(){ 80 | var obj = {hi: "there"}; 81 | var fn = jasmine.createSpy(); 82 | breakpoints.debugPropertyGet(obj, "hi", fn) 83 | var hi = obj.hi; 84 | expect(fn).toHaveBeenCalled(); 85 | }) 86 | 87 | it("Can show a trace message when a property is read", function(){ 88 | var obj = {hi: "there"}; 89 | var trace = spyOn(console, "trace") 90 | breakpoints.debugPropertyGet(obj, "hi", "trace"); 91 | var hi = obj.hi; 92 | expect(trace).toHaveBeenCalled(); 93 | expect(trace.calls.mostRecent().args[0]).toEqual("About to get property 'hi' on this object: "); 94 | }); 95 | }) 96 | 97 | describe("debugPropertySet", function(){ 98 | it("Calls a callback when an object property is set", function(){ 99 | var obj = {hi: "there"}; 100 | var fn = jasmine.createSpy(); 101 | breakpoints.debugPropertySet(obj, "hi", fn); 102 | obj.hi = "hello"; 103 | expect(fn).toHaveBeenCalled(); 104 | }) 105 | it("Can show a trace message when a string property is written", function(){ 106 | var obj = {hi: "there"}; 107 | var trace = spyOn(console, "trace") 108 | breakpoints.debugPropertySet(obj, "hi", "trace"); 109 | obj.hi = "Bob"; 110 | expect(trace).toHaveBeenCalled(); 111 | expect(trace.calls.mostRecent().args[0]).toEqual("About to set property 'hi' to %o "); 112 | expect(trace.calls.mostRecent().args[1]).toEqual("Bob"); 113 | expect(trace.calls.mostRecent().args[2]).toEqual(" on this object: "); 114 | expect(trace.calls.mostRecent().args[3]).toEqual(obj); 115 | }); 116 | it("Can show a trace message when a long string property is written", function(){ 117 | var obj = {hi: "there"}; 118 | var trace = spyOn(console, "trace") 119 | breakpoints.debugPropertySet(obj, "hi", "trace"); 120 | obj.hi = "Hubert Blaine Wolfeschlegelsteinhausenbergerdorff"; 121 | expect(trace).toHaveBeenCalled(); 122 | expect(trace.calls.mostRecent().args[0]).toEqual("About to set property 'hi' to %o "); 123 | expect(trace.calls.mostRecent().args[1]).toEqual("Hubert Blaine Wolfeschleg..."); 124 | expect(trace.calls.mostRecent().args[2]).toEqual(" on this object: "); 125 | expect(trace.calls.mostRecent().args[3]).toEqual(obj); 126 | }); 127 | it("Can show a trace message when a numeric property is written", function(){ 128 | var obj = {myNum: 0}; 129 | var trace = spyOn(console, "trace") 130 | breakpoints.debugPropertySet(obj, "myNum", "trace"); 131 | obj.myNum = 12345; 132 | expect(trace).toHaveBeenCalled(); 133 | expect(trace.calls.mostRecent().args[0]).toEqual("About to set property 'myNum' to %o "); 134 | expect(trace.calls.mostRecent().args[1]).toEqual(12345); 135 | expect(trace.calls.mostRecent().args[2]).toEqual(" on this object: "); 136 | expect(trace.calls.mostRecent().args[3]).toEqual(obj); 137 | }); 138 | it("Can show a trace message when a long numeric property is written", function(){ 139 | var obj = {myNum: 0}; 140 | var trace = spyOn(console, "trace") 141 | breakpoints.debugPropertySet(obj, "myNum", "trace"); 142 | obj.myNum = 432423495353284928492304982223432423423; 143 | expect(trace).toHaveBeenCalled(); 144 | expect(trace.calls.mostRecent().args[0]).toEqual("About to set property 'myNum' to %o "); 145 | expect(trace.calls.mostRecent().args[1]).toEqual(4.3242349535328495e+38); 146 | expect(trace.calls.mostRecent().args[2]).toEqual(" on this object: "); 147 | expect(trace.calls.mostRecent().args[3]).toEqual(obj); 148 | }); 149 | it("Can show a trace message when a boolean property is written", function(){ 150 | var obj = {available: false}; 151 | var trace = spyOn(console, "trace") 152 | breakpoints.debugPropertySet(obj, "available", "trace"); 153 | obj.available = true; 154 | expect(trace).toHaveBeenCalled(); 155 | expect(trace.calls.mostRecent().args[0]).toEqual("About to set property 'available' to %o "); 156 | expect(trace.calls.mostRecent().args[1]).toEqual(true); 157 | expect(trace.calls.mostRecent().args[2]).toEqual(" on this object: "); 158 | expect(trace.calls.mostRecent().args[3]).toEqual(obj); 159 | }); 160 | it("Can show a trace message when an object property is written", function(){ 161 | var obj = {complexObj: {someArray: [{ name: "Bob", age: 45 }, { name: "Sophie", age: 23 }]}}; 162 | var trace = spyOn(console, "trace") 163 | breakpoints.debugPropertySet(obj, "complexObj", "trace"); 164 | obj.complexObj = {someArray: [{ name: "Bob", age: 45 }, { name: "Sophie", age: 23 }, { name: "Lewis", age:15 }]}; 165 | expect(trace).toHaveBeenCalled(); 166 | expect(trace.calls.mostRecent().args[0]).toEqual("About to set property 'complexObj' to %o "); 167 | expect(trace.calls.mostRecent().args[1]).toEqual(Object({ someArray: [ Object({ name: 'Bob', age: 45 }), Object({ name: 'Sophie', age: 23 }), Object({ name: 'Lewis', age: 15 }) ] })); 168 | expect(trace.calls.mostRecent().args[2]).toEqual(" on this object: "); 169 | expect(trace.calls.mostRecent().args[3]).toEqual(obj); 170 | }); 171 | it("Can show a trace message when an array property is written", function(){ 172 | var obj = {myArray: [1,2,3]}; 173 | var trace = spyOn(console, "trace") 174 | breakpoints.debugPropertySet(obj, "myArray", "trace"); 175 | obj.myArray = [1,2,3,4,5]; 176 | expect(trace).toHaveBeenCalled(); 177 | expect(trace.calls.mostRecent().args[0]).toEqual("About to set property 'myArray' to [1,2,3,4,5] on this object: "); 178 | expect(trace.calls.mostRecent().args[1]).toEqual(obj); 179 | }); 180 | it("Can show a trace message when a long array property is written", function(){ 181 | var obj = {myArray: [1,2,3,4,5]}; 182 | var trace = spyOn(console, "trace") 183 | breakpoints.debugPropertySet(obj, "myArray", "trace"); 184 | obj.myArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]; 185 | expect(trace).toHaveBeenCalled(); 186 | expect(trace.calls.mostRecent().args[0]).toEqual("About to set property 'myArray' to [1,2,3,4,5,6,7,8,9,10,11,...] on this object: "); 187 | expect(trace.calls.mostRecent().args[1]).toEqual(obj); 188 | }); 189 | it("Can show a trace message when a complex array property is written", function(){ 190 | var obj = {myArray: []}; 191 | var trace = spyOn(console, "trace") 192 | breakpoints.debugPropertySet(obj, "myArray", "trace"); 193 | obj.myArray = [window, undefined, null]; 194 | expect(trace).toHaveBeenCalled(); 195 | expect(trace.calls.mostRecent().args[0]).toEqual("About to set property 'myArray' to [[object Window],,] on this object: "); 196 | expect(trace.calls.mostRecent().args[1]).toEqual(obj); 197 | }); 198 | }); 199 | 200 | describe("debugPropertyCall", function(){ 201 | it("Calls a callback when an object property containing a function is called", function(){ 202 | var obj = { 203 | sayHello: function(){ 204 | var hi = "hello"; 205 | } 206 | } 207 | var fn = jasmine.createSpy(); 208 | breakpoints.debugPropertyCall(obj, "sayHello", fn); 209 | obj.sayHello(); 210 | expect(fn).toHaveBeenCalled(); 211 | }) 212 | }) 213 | 214 | describe("debugCookieReads", function(){ 215 | it("Detects when the cookie is being read", function(){ 216 | var fn = jasmine.createSpy(); 217 | breakpoints.debugCookieReads(fn); 218 | var cookie = document.cookie; 219 | expect(fn).toHaveBeenCalled(); 220 | }) 221 | it("Doesn't call the callback when the cookie property is being set", function(){ 222 | var fn = jasmine.createSpy(); 223 | breakpoints.debugCookieReads(fn); 224 | document.cookie = "test"; 225 | expect(fn).not.toHaveBeenCalled(); 226 | }); 227 | it("Shows a predefined trace message", function(){ 228 | var fn = spyOn(console, "trace"); 229 | breakpoints.debugCookieReads("trace"); 230 | var cookie = document.cookie; 231 | expect(console.trace).toHaveBeenCalled(); 232 | expect(console.trace.calls.mostRecent().args[0]).toBe("Reading cookie contents"); 233 | }); 234 | }); 235 | 236 | describe("debugCookieWrites", function(){ 237 | it("Detects when the cookie is being set", function(){ 238 | var fn = jasmine.createSpy(); 239 | breakpoints.debugCookieWrites(fn); 240 | document.cookie = "hello"; 241 | expect(fn).toHaveBeenCalled(); 242 | }) 243 | }) 244 | 245 | describe("debugAlertCalls", function(){ 246 | it("Detects when an alert box is shown", function(){ 247 | var alertSpy = spyOn(window, "alert"); 248 | var fn = jasmine.createSpy(); 249 | breakpoints.debugAlertCalls(fn) 250 | window.alert("test"); 251 | expect(fn).toHaveBeenCalled(); 252 | }) 253 | }) 254 | 255 | describe("debugConsoleErrorCalls", function(){ 256 | it("Detects when console.error is called", function(){ 257 | var consoleErrorSpy = spyOn(console, "error"); 258 | var fn = jasmine.createSpy(); 259 | breakpoints.debugConsoleErrorCalls(fn); 260 | console.error("error") 261 | expect(fn).toHaveBeenCalled(); 262 | }); 263 | }); 264 | 265 | describe("debugConsoleLogCalls", function(){ 266 | it("Detects when console.log is called", function(){ 267 | var consoleErrorSpy = spyOn(console, "log"); 268 | var fn = jasmine.createSpy(); 269 | breakpoints.debugConsoleLogCalls(fn); 270 | console.log("log") 271 | expect(fn).toHaveBeenCalled(); 272 | }); 273 | }); 274 | 275 | describe("debugConsoleTraceCalls", function(){ 276 | it("Detects when console.trace is called", function(){ 277 | var consoleBackup = window.console; 278 | window.console = {trace: function(){}}; 279 | var fn = jasmine.createSpy(); 280 | breakpoints.debugConsoleTraceCalls(fn); 281 | console.trace("test"); 282 | expect(fn).toHaveBeenCalled(); 283 | window.console = consoleBackup; 284 | }) 285 | it("Works when showing trace messages when breakpoint is hit", function(){ 286 | var consoleBackup = window.console; 287 | window.console = {trace: function(){}}; 288 | spyOn(console, "trace") 289 | // The spy is replaced by the debugConsoleTraceCalls call, 290 | // so keep a reference to it 291 | var spy = window.console.trace; 292 | breakpoints.debugConsoleTraceCalls("trace"); 293 | console.trace("test"); 294 | expect(spy).toHaveBeenCalled(); 295 | window.console = consoleBackup; 296 | }) 297 | }); 298 | 299 | describe("debugMathRandom", function(){ 300 | it("Hits when Math.random is called and shows a trace message", function(){ 301 | spyOn(console, "trace"); 302 | breakpoints.debugMathRandom("trace"); 303 | Math.random(); 304 | expect(console.trace).toHaveBeenCalled(); 305 | expect(console.trace.calls.count()).toBe(1); 306 | }) 307 | }) 308 | 309 | describe("debugTimerCreation", function(){ 310 | it("Shows a trace message when setTimeout is called", function(){ 311 | spyOn(console, "trace"); 312 | breakpoints.debugTimerCreation("trace"); 313 | setTimeout(function(){}, 0); 314 | expect(console.trace).toHaveBeenCalled(); 315 | expect(console.trace.calls.mostRecent().args[0]).toBe("Creating timer using setTimeout") 316 | }); 317 | }) 318 | 319 | describe("debugScroll", function(){ 320 | it("Hits when window.scrollTo is called", function(){ 321 | spyOn(window, "scrollTo") 322 | var fn = jasmine.createSpy(); 323 | breakpoints.debugScroll(fn); 324 | window.scrollTo(50, 50); 325 | expect(fn).toHaveBeenCalled() 326 | }); 327 | it("Hits when window.scrollBy is called", function(){ 328 | spyOn(window, "scrollBy") 329 | var fn = jasmine.createSpy(); 330 | breakpoints.debugScroll(fn); 331 | window.scrollBy(50, 50); 332 | expect(fn).toHaveBeenCalled() 333 | }); 334 | it("Hits when scrollTop is set on the body", function(){ 335 | var fn = jasmine.createSpy(); 336 | breakpoints.debugScroll(fn); 337 | document.body.scrollTop = 50; 338 | expect(fn).toHaveBeenCalled(); 339 | }) 340 | it("Hits when scrollLeft is set on the body", function(){ 341 | var fn = jasmine.createSpy(); 342 | breakpoints.debugScroll(fn); 343 | document.body.scrollLeft = 50; 344 | expect(fn).toHaveBeenCalled(); 345 | }) 346 | it("Hits when scrollTop is set on an element", function(){ 347 | var div = document.createElement("div"); 348 | var fn = jasmine.createSpy(); 349 | breakpoints.debugScroll(fn); 350 | div.scrollTop = 40; 351 | expect(fn).toHaveBeenCalled(); 352 | }); 353 | it("Hits when scrollLeft is set on an element", function(){ 354 | var div = document.createElement("div"); 355 | var fn = jasmine.createSpy(); 356 | breakpoints.debugScroll(fn); 357 | div.scrollLeft = 40; 358 | expect(fn).toHaveBeenCalled(); 359 | }); 360 | it("Shows the scroll position message", function(){ 361 | spyOn(console, "trace"); 362 | breakpoints.debugScroll("trace"); 363 | var myParagraph = document.createElement("p"); 364 | myParagraph.id = "myParagraph"; 365 | var div = document.createElement("div"); 366 | div.appendChild(myParagraph); 367 | document.body.appendChild(div); 368 | document.getElementById("myParagraph").scrollTop = 10; 369 | expect(console.trace).toHaveBeenCalled(); 370 | var mostRecentCallArgs = console.trace.calls.mostRecent().args; 371 | expect(mostRecentCallArgs[0]).toBe("The scroll position of"); 372 | expect(mostRecentCallArgs[1].outerHTML).toBe("

"); 373 | expect(mostRecentCallArgs[2]).toBe("was changed by setting the scrollTop property to 10"); 374 | }) 375 | it("Shows the scroll position message", function(){ 376 | spyOn(console, "trace"); 377 | breakpoints.debugScroll("trace"); 378 | window.scrollTo(10, 10); 379 | expect(console.trace).toHaveBeenCalled(); 380 | var mostRecentCallArgs = console.trace.calls.mostRecent().args; 381 | expect(mostRecentCallArgs[0]).toBe("The scroll position of the window was changed by a window.scrollTo call with"); 382 | expect(mostRecentCallArgs[1]).toEqual([10, 10]); 383 | }) 384 | }) 385 | 386 | describe("debugLocalStorageReads", function(){ 387 | it("Hits when localStorage.getItem is called", function(){ 388 | var fn = jasmine.createSpy(); 389 | breakpoints.debugLocalStorageReads(fn); 390 | localStorage.getItem("hello"); 391 | expect(fn).toHaveBeenCalled(); 392 | }) 393 | it("Shows the data key in the trace message", function(){ 394 | spyOn(console, "trace"); 395 | breakpoints.debugLocalStorageReads("trace"); 396 | localStorage.getItem("hello"); 397 | expect(console.trace).toHaveBeenCalled(); 398 | expect(console.trace.calls.mostRecent().args[0]).toBe("Reading localStorage data for key \"hello\""); 399 | }) 400 | }) 401 | 402 | describe("debugLocalStorageWrites", function(){ 403 | it("Shows the data key in the trace message", function(){ 404 | spyOn(console, "trace"); 405 | breakpoints.debugLocalStorageWrites("trace"); 406 | localStorage.setItem("hi", "there"); 407 | expect(console.trace).toHaveBeenCalled(); 408 | expect(console.trace.calls.mostRecent().args[0]).toBe("Writing localStorage data for key \"hi\""); 409 | }) 410 | it("Hits when localStorage.setItem is called", function(){ 411 | var fn = jasmine.createSpy(); 412 | breakpoints.debugLocalStorageWrites(fn); 413 | localStorage.setItem("hi", "there"); 414 | expect(fn).toHaveBeenCalled(); 415 | }) 416 | it("Hits when localStorage.clear is called", function(){ 417 | var fn = jasmine.createSpy(); 418 | breakpoints.debugLocalStorageWrites(fn); 419 | localStorage.clear(); 420 | expect(fn).toHaveBeenCalled(); 421 | }) 422 | it("Shows the localStorage.clear trace message", function(){ 423 | spyOn(console, "trace"); 424 | breakpoints.debugLocalStorageWrites("trace"); 425 | localStorage.clear(); 426 | expect(console.trace).toHaveBeenCalled(); 427 | expect(console.trace.calls.mostRecent().args[0]).toBe("Clearing all localStorage data"); 428 | }) 429 | }) 430 | 431 | describe("debugElementSelection", function(){ 432 | it("Hits when document.getElementById is called", function(){ 433 | var fn = jasmine.createSpy(); 434 | breakpoints.debugElementSelection(fn); 435 | document.getElementById("test") 436 | expect(fn).toHaveBeenCalled(); 437 | }); 438 | it("Shows the function that was called in the trace message", function(){ 439 | spyOn(console, "trace"); 440 | breakpoints.debugElementSelection("trace"); 441 | document.getElementsByClassName("hello"); 442 | expect(console.trace).toHaveBeenCalled(); 443 | expect(console.trace.calls.mostRecent().args[0]).toBe("Selecting DOM elements \"hello\" using getElementsByClassName"); 444 | }) 445 | //it's all just a list of calls... no point in duplicating them all here 446 | }) 447 | -------------------------------------------------------------------------------- /webpack-test.config.js: -------------------------------------------------------------------------------- 1 | var context = require.context('./tests', true, /-spec\.jsx?$/); 2 | context.keys().forEach(context); 3 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | var toBuild = [ 5 | { 6 | entry: ["devtools-panel", "./devtools-panel.js"], 7 | path: __dirname + "/extension/build", 8 | libraryTarget: "var" 9 | }, 10 | { 11 | entry: ["javascript-breakpoint-collection", "./injected-script.js"], 12 | path: __dirname + "/extension/build", 13 | libraryTarget: "var" 14 | }, 15 | { 16 | entry: ["node", "./injected-script"], 17 | //needs separate build becasue otherwise the script fails 18 | // on pages with a global `define` function 19 | libraryTarget: "umd", 20 | path: __dirname + "/dist/" 21 | } 22 | ] 23 | 24 | function makeConfig(item){ 25 | var entry = {}; 26 | entry[item.entry[0]] = item.entry[1]; 27 | 28 | return { 29 | entry: entry, 30 | devtool: "source-map", 31 | output: { 32 | path: item.path, 33 | filename: '[name].js', 34 | libraryTarget: item.libraryTarget 35 | }, 36 | module: { 37 | loaders: [ 38 | { 39 | test: /.jsx?$/, 40 | loader: 'babel-loader', 41 | exclude: /node_modules/, 42 | presets: ['es2015', 'react'] 43 | } 44 | ] 45 | } 46 | }; 47 | } 48 | 49 | var configs = toBuild.map(makeConfig); 50 | 51 | module.exports = configs; 52 | -------------------------------------------------------------------------------- /webstore-assets/demo-with-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzeunert/javascript-breakpoint-collection/2b6124c4e7bee442a12fdc3441710e0b9ba4e22e/webstore-assets/demo-with-page.png -------------------------------------------------------------------------------- /webstore-assets/extension-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzeunert/javascript-breakpoint-collection/2b6124c4e7bee442a12fdc3441710e0b9ba4e22e/webstore-assets/extension-icon.png -------------------------------------------------------------------------------- /webstore-assets/icon-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzeunert/javascript-breakpoint-collection/2b6124c4e7bee442a12fdc3441710e0b9ba4e22e/webstore-assets/icon-large.png -------------------------------------------------------------------------------- /webstore-assets/screenshot-old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzeunert/javascript-breakpoint-collection/2b6124c4e7bee442a12fdc3441710e0b9ba4e22e/webstore-assets/screenshot-old.png -------------------------------------------------------------------------------- /webstore-assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzeunert/javascript-breakpoint-collection/2b6124c4e7bee442a12fdc3441710e0b9ba4e22e/webstore-assets/screenshot.png -------------------------------------------------------------------------------- /webstore-assets/scroll-trace-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzeunert/javascript-breakpoint-collection/2b6124c4e7bee442a12fdc3441710e0b9ba4e22e/webstore-assets/scroll-trace-demo.png --------------------------------------------------------------------------------