├── .gitignore ├── .npmrc ├── find-expensive-digest.png ├── first-paint-code-snippet.png ├── images └── update-code-snippets.png ├── first-paint-code-snippet-remote.png ├── ng-run-digest-cycle.js ├── first-paint.js ├── css-layout.js ├── .travis.yml ├── first-paint-remote.js ├── boilerplate.js ├── utils ├── jscs.json ├── eslint.json └── .jshintrc ├── ng-count-digest-cycle-simple.js ├── ng-monitor-digest-cycle.js ├── test-script-injection.js ├── local-storage-size.js ├── bower.json ├── time-method-call.js ├── profile-method-call.js ├── ng-throw-error.js ├── ng-idle-apply-timing.js ├── ng-find-scope-property.js ├── ng-profile-data-change.js ├── test └── count-digest-cycles.html ├── github-pull-request-template.js ├── ng-find-expensive-digest.js ├── profile-prototype-method.js ├── Gruntfile.js ├── ng-profile-local-digest.js ├── remove-all-but.js ├── profile-separate-calls.js ├── ng-profile-scope-method.js ├── ng-count-digest-cycles.js ├── keys-vs-values.js ├── package.json ├── expensive-keys.js ├── ng-count-watchers.js ├── update-code-snippets.js ├── ng-scope-size.js ├── harlem-shake-xss.js ├── timing.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /find-expensive-digest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahmutov/code-snippets/HEAD/find-expensive-digest.png -------------------------------------------------------------------------------- /first-paint-code-snippet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahmutov/code-snippets/HEAD/first-paint-code-snippet.png -------------------------------------------------------------------------------- /images/update-code-snippets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahmutov/code-snippets/HEAD/images/update-code-snippets.png -------------------------------------------------------------------------------- /first-paint-code-snippet-remote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahmutov/code-snippets/HEAD/first-paint-code-snippet-remote.png -------------------------------------------------------------------------------- /ng-run-digest-cycle.js: -------------------------------------------------------------------------------- 1 | // runs application digest cycle starting from root scope 2 | /* global angular */ 3 | angular.element(document).injector().get('$rootScope').$apply(); 4 | -------------------------------------------------------------------------------- /first-paint.js: -------------------------------------------------------------------------------- 1 | // taken from https://www.youtube.com/watch?v=S9sktFzL3tQ 2 | (function timeFirstPaint() { 3 | /* global chrome */ 4 | var fp = chrome.loadTimes().firstPaintTime - chrome.loadTimes().startLoadTime; 5 | console.log('first paint: ' + fp); 6 | }()); 7 | -------------------------------------------------------------------------------- /css-layout.js: -------------------------------------------------------------------------------- 1 | // tiny CSS layout "debugger" 2 | // from https://gist.github.com/addyosmani/fd3999ea7fce242756b1 3 | // puts random color border around each element 4 | /* global $$ */ 5 | /* jshint -W016 */ 6 | [].forEach.call($$('*'), 7 | function (a) { 8 | a.style.outline = '1px solid #' + (~~(Math.random() * (1 << 24))).toString(16); 9 | }); 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | node_js: 9 | - '4' 10 | before_install: 11 | - npm i -g npm@^2.0.0 12 | before_script: 13 | - npm prune 14 | after_success: 15 | - npm run semantic-release 16 | branches: 17 | except: 18 | - "/^v\\d+\\.\\d+\\.\\d+$/" 19 | -------------------------------------------------------------------------------- /first-paint-remote.js: -------------------------------------------------------------------------------- 1 | (function firstPaintRemote() { 2 | // form rawGit proxy url 3 | var ghUrl = 'bahmutov/code-snippets/master/first-paint.js'; 4 | var rawUrl = 'https://rawgit.com/' + ghUrl; 5 | // download and run the script 6 | var head = document.getElementsByTagName('head')[0]; 7 | var script = document.createElement('script'); 8 | script.type = 'text/javascript'; 9 | script.src = rawUrl; 10 | head.appendChild(script); 11 | }()); 12 | -------------------------------------------------------------------------------- /boilerplate.js: -------------------------------------------------------------------------------- 1 | (function downloadAndRunCodeSnippet() { 2 | // form rawGit proxy url 3 | var ghUrl = 'bahmutov/code-snippets/master/first-paint.js'; 4 | var rawUrl = 'https://rawgit.com/' + ghUrl; 5 | // download and run the script 6 | var head = document.getElementsByTagName('head')[0]; 7 | var script = document.createElement('script'); 8 | script.type = 'text/javascript'; 9 | script.src = rawUrl; 10 | head.appendChild(script); 11 | }()); 12 | -------------------------------------------------------------------------------- /utils/jscs.json: -------------------------------------------------------------------------------- 1 | { 2 | "requireCurlyBraces": true, 3 | "requireDotNotation": true, 4 | "requireSpaceAfterLineComment": true, 5 | "validateQuoteMarks": "'", 6 | "validateIndentation": 2, 7 | "requireSpacesInFunctionExpression": { 8 | "beforeOpeningCurlyBrace": true 9 | }, 10 | "requireSpacesInAnonymousFunctionExpression": { 11 | "beforeOpeningRoundBrace": true, 12 | "beforeOpeningCurlyBrace": true 13 | }, 14 | "requireSpacesInsideObjectBrackets": "all" 15 | } 16 | -------------------------------------------------------------------------------- /ng-count-digest-cycle-simple.js: -------------------------------------------------------------------------------- 1 | /* 2 | uses console to print a message every time 3 | the digest cycle runs. 4 | */ 5 | (function monitorDigestCycle(angular) { 6 | var injector = angular.element(document.body).injector(); 7 | if (!injector) { 8 | throw new Error('Missing Angular injector on the document body'); 9 | } 10 | var $rootScope = injector.get('$rootScope'); 11 | function dummy() { 12 | console.count('digest cycle'); 13 | } 14 | window.stopWatching = $rootScope.$watch(dummy); 15 | console.log('run window.stopWatching() to stop watching the digest cycle'); 16 | }(window.angular)); 17 | -------------------------------------------------------------------------------- /ng-monitor-digest-cycle.js: -------------------------------------------------------------------------------- 1 | /* 2 | uses console.monitor to print a message every time 3 | the digest cycle runs. 4 | */ 5 | (function monitorDigestCycle(angular) { 6 | var injector = angular.element(document.body).injector(); 7 | if (!injector) { 8 | throw new Error('Missing Angular injector on the document body'); 9 | } 10 | var $rootScope = injector.get('$rootScope'); 11 | function dummy() { 12 | console.count('digest cycle'); 13 | } 14 | window.stopWatching = $rootScope.$watch(dummy); 15 | console.log('run window.stopWatching() to stop watching the digest cycle'); 16 | }(window.angular)); 17 | -------------------------------------------------------------------------------- /test-script-injection.js: -------------------------------------------------------------------------------- 1 | /* 2 | This code snippet checks if the page allows creating 3 | and executing new inline scripts (script-injection attacks) 4 | See https://github.com/bahmutov/disable-inline-javascript-tutorial 5 | */ 6 | (function testInlineScriptInjection() { 7 | var el = document.createElement('script'); 8 | el.innerText = 'alert("hi there")'; 9 | document.body.appendChild(el); // runs the code by default 10 | }()); 11 | 12 | (function testExternalScriptInjection() { 13 | var el = document.createElement('script'); 14 | el.src = 'https://rawgit.com/hakimel/reveal.js/tree/master/js'; 15 | document.body.appendChild(el); 16 | }()); 17 | -------------------------------------------------------------------------------- /local-storage-size.js: -------------------------------------------------------------------------------- 1 | // based on answer to question 2 | // http://stackoverflow.com/questions/4391575/how-to-find-the-size-of-localstorage 3 | (function showLocalStorageSize() { 4 | function stringSizeBytes(str) { 5 | return str.length * 2; 6 | } 7 | 8 | function toMB(bytes) { 9 | return bytes / 1024 / 1024; 10 | } 11 | 12 | function toSize(key) { 13 | return { 14 | name: key, 15 | size: stringSizeBytes(localStorage[key]) 16 | }; 17 | } 18 | 19 | function toSizeMB(info) { 20 | info.size = toMB(info.size).toFixed(2) + ' MB'; 21 | return info; 22 | } 23 | 24 | var sizes = Object.keys(localStorage).map(toSize).map(toSizeMB); 25 | 26 | console.table(sizes); 27 | 28 | }()); 29 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-snippets", 3 | "main": "first-paint.js", 4 | "version": "0.0.0-semantic-release", 5 | "homepage": "https://github.com/bahmutov/code-snippets", 6 | "license": "MIT", 7 | "ignore": [ 8 | "**/.*", 9 | "node_modules", 10 | "bower_components", 11 | "test", 12 | "tests", 13 | "docs", 14 | "Gruntfile.js", 15 | "package.json", 16 | "index.html" 17 | ], 18 | "keywords": [ 19 | "chrome", 20 | "code", 21 | "devtools", 22 | "exploratory", 23 | "mock", 24 | "snippets", 25 | "test", 26 | "testing" 27 | ], 28 | "description": "Chrome DevTools code snippets ", 29 | "authors": [ 30 | "Gleb Bahmutov " 31 | ] 32 | } -------------------------------------------------------------------------------- /time-method-call.js: -------------------------------------------------------------------------------- 1 | (function timeMethodCall(root) { 2 | 'use strict'; 3 | 4 | var name = 'primesApp'; 5 | var methodName = 'findFirstPrimes'; 6 | 7 | var object = root[name]; 8 | console.assert(object, 'cannot find object ' + name + ' to profile'); 9 | 10 | var originalMethod = object[methodName]; 11 | console.assert(typeof originalMethod === 'function', 'cannot find method ' + methodName); 12 | 13 | object[methodName] = function () { 14 | console.time(methodName); 15 | originalMethod.call(object); 16 | console.timeEnd(methodName); 17 | 18 | object[methodName] = originalMethod; 19 | console.log('restored the original method call'); 20 | }; 21 | console.log('wrapped', name + '.' + methodName + ' in timing calls'); 22 | }(this)); 23 | -------------------------------------------------------------------------------- /profile-method-call.js: -------------------------------------------------------------------------------- 1 | (function profileMethodCall(root) { 2 | 'use strict'; 3 | 4 | var name = 'primesApp'; 5 | var methodName = 'findFirstPrimes'; 6 | 7 | var object = root[name]; 8 | console.assert(object, 'cannot find object ' + name + ' to profile'); 9 | 10 | var originalMethod = object[methodName]; 11 | console.assert(typeof originalMethod === 'function', 'cannot find method ' + methodName); 12 | 13 | object[methodName] = function () { 14 | console.profile(methodName); 15 | originalMethod.call(object); 16 | console.profileEnd(methodName); 17 | 18 | object[methodName] = originalMethod; 19 | console.log('restored the original method call'); 20 | }; 21 | console.log('wrapped', name + '.' + methodName + ' in profiling calls'); 22 | }(this)); 23 | -------------------------------------------------------------------------------- /ng-throw-error.js: -------------------------------------------------------------------------------- 1 | // throws an exception from the digest cycle 2 | // useful to test your exception handler service 3 | // http://glebbahmutov.com/blog/catch-all-errors-in-angular-app/ 4 | (function throwErrorFromAngular(angular) { 5 | 6 | if (typeof angular === 'undefined') { 7 | throw new Error('Cannot find angular on the page'); 8 | } 9 | // select any element in the Elements panel that is inside Angular app 10 | /* global $0 */ 11 | var injector = angular.element($0).injector(); 12 | if (!injector) { 13 | throw new Error('Cannot find injector, please select an element\n' + 14 | 'INSIDE an angular app in the Elements panel'); 15 | } 16 | injector.get('$rootScope').$apply(function () { 17 | throw new Error('Error from Angular digest cycle'); 18 | }); 19 | }(window.angular)); 20 | -------------------------------------------------------------------------------- /ng-idle-apply-timing.js: -------------------------------------------------------------------------------- 1 | // Measures how long a idle apply cycle takes 2 | // More watchers - longer cycle 3 | // More complicated expression logic - longer cycle 4 | // Also creates CPU profile for debugging bottlenecks (Chrome DevTools) 5 | 6 | // assumes the angular application is at least around the document's body 7 | 8 | /* global performance */ 9 | (function profileIdleTiming(angular) { 10 | 11 | function timeApply($rootScope) { 12 | console.profile('$apply'); 13 | var started = performance.now(); 14 | $rootScope.$apply(); 15 | var takes = performance.now() - started; 16 | console.log('idle $apply takes', takes, 'ms'); 17 | console.profileEnd(); 18 | return takes; 19 | } 20 | timeApply.$inject = ['$rootScope']; 21 | 22 | angular.element(document.body).injector().invoke(timeApply); 23 | }(window.angular)); 24 | -------------------------------------------------------------------------------- /ng-find-scope-property.js: -------------------------------------------------------------------------------- 1 | // finds all properties with given name attached to the scopes 2 | (function (window) { 3 | 4 | function findScopeProperty(name) { 5 | var i, data, scope, 6 | all = document.all, 7 | len = all.length, 8 | test = {}; 9 | var found = []; 10 | 11 | /* global angular */ 12 | /* eslint no-for-loops:0 */ 13 | for (i = 0; i < len; i++) { 14 | data = angular.element(all[i]).data(); 15 | scope = data.$scope || data.$isolateScope; 16 | if (scope && scope.hasOwnProperty(name)) { 17 | if ( !test[ scope.$id ] ) { 18 | test[ scope.$id ] = true; 19 | found.push(scope); 20 | } 21 | } 22 | } 23 | if (!found.length) { 24 | console.log('could not find any scopes with', name, 'property'); 25 | } else { 26 | console.log('found', found.length, 'scopes with property', name); 27 | } 28 | return found; 29 | } 30 | 31 | window.findScopeProperty = findScopeProperty; 32 | }(window)); 33 | -------------------------------------------------------------------------------- /ng-profile-data-change.js: -------------------------------------------------------------------------------- 1 | /* 2 | Changes data to some dummy on the scope, then runs digest cycle. 3 | Saves CPU profile. 4 | */ 5 | (function profileDataChange() { 6 | var selector = 'study-line-chart'; 7 | var propertyName = 'selectedBenchmarks'; 8 | var name = selector + ':' + propertyName; 9 | 10 | /* global angular */ 11 | var el = angular.element(selector); 12 | var scope = el.scope() || el.isolateScope(); 13 | console.assert(scope, 'cannot find scope from ' + name); 14 | 15 | var property = scope[propertyName]; 16 | console.assert(property, 'missing ' + name); 17 | 18 | function digestCycle() { 19 | angular.element(document).injector().get('$rootScope').$apply(); 20 | } 21 | 22 | property.foo = 'foo'; 23 | 24 | console.profile(name); 25 | console.time(name); 26 | 27 | digestCycle(); 28 | 29 | console.timeStamp('finished', name); 30 | 31 | console.timeEnd(name); 32 | console.profileEnd(); 33 | 34 | delete property.foo; 35 | 36 | digestCycle(); 37 | }()); 38 | -------------------------------------------------------------------------------- /test/count-digest-cycles.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 25 | 26 | 27 | 28 |

Count digest cycles

29 |
30 |

controller

31 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /utils/eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | // this is JSON file, but ESLint allows C-style comments 3 | 4 | "env": { 5 | "browser": true, 6 | "node": false 7 | }, 8 | 9 | // 0 - turn rule off 10 | // 1 - rule generates warnings 11 | // 2 - rule generates errors 12 | "rules": { 13 | "camelcase": 0, 14 | "camel_case": 0, 15 | "strict": 0, 16 | "no-undef": 1, 17 | "no-console": 0, 18 | "no-unused-vars": 1, 19 | "no-underscore-dangle": 1, 20 | "quotes": [2, "single"], 21 | "brace-style": [2, "1tbs"], 22 | "eol-last": 1, 23 | "semi": [2, "always"], 24 | "no-extra-strict": 0, 25 | "no-single-line-objects": 2, 26 | "no-nested-ternary": 2, 27 | "no-space-before-semi": 2, 28 | "no-shadow": 2, 29 | "no-undef-init": 2, 30 | "no-undefined": 0, 31 | "no-sequences": 2, 32 | "no-for-loops": 2, 33 | "no-process-exit": 0 34 | }, 35 | 36 | "globals": { 37 | "navigator": true, 38 | "console": true, 39 | "Promise": true 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /github-pull-request-template.js: -------------------------------------------------------------------------------- 1 | /* 2 | Read the following blog post 3 | http://krasimirtsonev.com/blog/article/enforce-standards-while-submitting-a-pull-request 4 | */ 5 | (function () { 6 | var textareaId = '#pull_request_body'; 7 | var textarea = document.querySelector(textareaId); 8 | var template = ''; 9 | var firstLine; 10 | 11 | template += firstLine = 'ID (ticket/card/issue): '; 12 | template += '\n\n'; 13 | template += '## Task/Problem\n\n'; 14 | template += '## Solution\n\n'; 15 | template += '## Steps to reproduce\n\n'; 16 | template += '## UAT\n\n'; 17 | template += '## Code review\n\n\n'; 18 | template += '- [ ] Unit tests passed\n'; 19 | template += '- [ ] System tests passed\n'; 20 | 21 | if (textarea) { 22 | textarea.value = template; 23 | textarea.focus(); 24 | textarea.scrollTop = 0; 25 | textarea.selectionStart = textarea.selectionEnd = firstLine.length; 26 | } else { 27 | /* global alert */ 28 | /* eslint no-alert:0 */ 29 | alert('You are either not on the PR page or there is no ' + textareaId + ' element.'); 30 | } 31 | })(); 32 | -------------------------------------------------------------------------------- /ng-find-expensive-digest.js: -------------------------------------------------------------------------------- 1 | // assumes profileDirectiveDigest(selector) 2 | // use: findExpensiveDigest(selector1, selector2, ...); 3 | 4 | /* global profileDirectiveDigest */ 5 | (function (window) { 6 | 7 | function findExpensiveDigest() { 8 | if (typeof profileDirectiveDigest !== 'function') { 9 | throw new Error('cannot find profileDirectiveDigest function'); 10 | } 11 | var selectors = Array.prototype.slice.call(arguments, 0); 12 | var durations = selectors.map(function timeSelectorDigest(selector) { 13 | /* global performance */ 14 | var started = performance.now(); 15 | profileDirectiveDigest(selector); 16 | var takes = performance.now() - started; 17 | return takes; 18 | }); 19 | var merged = selectors.map(function (selector, k) { 20 | return { 21 | selector: selector, 22 | takes: durations[k] 23 | }; 24 | }); 25 | merged = merged.sort(function (a, b) { 26 | return b.takes - a.takes; 27 | }); 28 | console.log('elements with expensive digest cycles'); 29 | console.table(merged); 30 | } 31 | 32 | window.findExpensiveDigest = findExpensiveDigest; 33 | }(window)); 34 | -------------------------------------------------------------------------------- /profile-prototype-method.js: -------------------------------------------------------------------------------- 1 | /* 2 | Almost the same as profile method call, but for wrapping methods that are on prototype 3 | function Foo() {} 4 | Foo.prototype.getName = function () { return this.name; } 5 | var foo = new Foo(); 6 | foo.getName(); 7 | 8 | // profile getName without getting foo reference 9 | profilePrototypeCall with proto = Foo.prototype; 10 | and method name 'getName' 11 | */ 12 | (function profilePrototypeCall(proto, methodName) { 13 | 'use strict'; 14 | 15 | console.assert(proto, 'cannot find prototype to profile'); 16 | console.assert(typeof methodName === 'string', 'expected method name'); 17 | 18 | var originalMethod = proto[methodName]; 19 | console.assert(typeof originalMethod === 'function', 'cannot find method ' + methodName); 20 | 21 | proto[methodName] = function () { 22 | console.profile(methodName); 23 | 24 | originalMethod.apply(this, arguments); 25 | 26 | console.profileEnd(methodName); 27 | 28 | proto[methodName] = originalMethod; 29 | console.log('restored the prototype method call', methodName); 30 | }; 31 | console.log('wrapped', methodName + ' in profiling calls'); 32 | 33 | // some prototype and method name 34 | }(this.Photostack.prototype, '_rotateItem')); 35 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 'use strict'; 3 | 4 | var sourceFiles = [ 5 | '*.js', '!Gruntfile.js', 6 | '!timing.js' // based on 3rd party script 7 | ]; 8 | 9 | grunt.initConfig({ 10 | pkg: grunt.file.readJSON('package.json'), 11 | 12 | sync: { 13 | all: { 14 | options: { 15 | sync: ['author', 'name', 'version', 'main', 16 | 'private', 'license', 'keywords', 'homepage'], 17 | } 18 | } 19 | }, 20 | 21 | jshint: { 22 | all: sourceFiles, 23 | options: { 24 | jshintrc: 'utils/.jshintrc', 25 | reporter: require('jshint-summary') 26 | } 27 | }, 28 | 29 | eslint: { 30 | target: sourceFiles, 31 | options: { 32 | config: 'utils/eslint.json', 33 | rulesdir: ['./node_modules/eslint-rules'] 34 | } 35 | }, 36 | 37 | jscs: { 38 | src: sourceFiles, 39 | options: { 40 | config: 'utils/jscs.json' 41 | } 42 | } 43 | }); 44 | 45 | var plugins = require('matchdep').filterDev('grunt-*'); 46 | plugins.forEach(grunt.loadNpmTasks); 47 | 48 | grunt.registerTask('lint', ['jshint', 'eslint', 'jscs']); 49 | grunt.registerTask('default', ['nice-package', 'deps-ok', 'sync', 'lint']); 50 | }; 51 | -------------------------------------------------------------------------------- /ng-profile-local-digest.js: -------------------------------------------------------------------------------- 1 | /* measures how long an idle digest cycle (nothing has changed, just running all 2 | dirty checking watchers) takes for a scope surrounding given selector. 3 | Use: run this code snippet, then profileDirectiveDigest('#foo'); to measure 4 | watchers in the #foo element (and its children along the scope tree). 5 | */ 6 | (function (window) { 7 | var performance = window.performance; 8 | 9 | function getDiff(start, end) { 10 | return (end - start).toFixed(4); 11 | } 12 | 13 | function profileDirectiveDigest(selector) { 14 | console.assert(selector && typeof selector === 'string', 'expected selector', selector); 15 | var el = document.querySelector(selector); 16 | console.assert(el, 'cannot find element with selector', selector); 17 | 18 | /* global angular */ 19 | var ngEl = angular.element(el); 20 | var scope = ngEl.scope() || ngEl.isolateScope(); 21 | var startTime; 22 | var endTime; 23 | 24 | console.assert(scope, 'cannot find scope from element', selector); 25 | startTime = performance.now(); 26 | scope.$digest(); 27 | endTime = performance.now(); 28 | console.log(selector, getDiff(startTime, endTime) + ' digest'); 29 | } 30 | 31 | window.profileDirectiveDigest = profileDirectiveDigest; 32 | }(window)); 33 | -------------------------------------------------------------------------------- /remove-all-but.js: -------------------------------------------------------------------------------- 1 | /* 2 | Removes all elements except for the trees rooted 3 | in the given selectors. Selectors are queried using querySelectorAll 4 | 5 | For example, given a document 6 | 7 | body 8 | div.foo 9 | span 10 | div#bar 11 | div#baz 12 | hello 13 | 14 | and command with selectors ('.foo', '#baz') will leave 15 | everything in place, but command ('#baz') will leave just 16 | 17 | body 18 | div#bar 19 | div#baz 20 | hello 21 | */ 22 | (function hideAllBut() { 23 | 'use strict'; 24 | 25 | const selectors = Array.from(arguments); 26 | if (!selectors.length) { 27 | throw new Error('Need at least one selector to leave'); 28 | } 29 | 30 | const keep = selectors.reduce(function (all, selector) { 31 | return all.concat(Array.from(document.querySelectorAll(selector))); 32 | }, []); 33 | 34 | function shouldKeep(el) { 35 | return keep.some(function (keepElement) { 36 | return keepElement.contains(el) || el.contains(keepElement); 37 | }); 38 | } 39 | 40 | const all = Array.from(document.body.querySelectorAll('*')); 41 | var removed = 0; 42 | 43 | all.forEach(function (el) { 44 | if (!shouldKeep(el)) { 45 | el.parentNode.removeChild(el); 46 | removed += 1; 47 | } 48 | }); 49 | 50 | console.log('removed %d elements', removed); 51 | }('.foo')); 52 | -------------------------------------------------------------------------------- /profile-separate-calls.js: -------------------------------------------------------------------------------- 1 | // time action with separate start and finish callbacks 2 | // for example 3 | /* 4 | worker.onmessage = function (e) { 5 | console.log('worker has finished'); 6 | renderPrimes(e.data); 7 | }; 8 | document.addEventListener('DOMContentLoaded', function() { 9 | document.querySelector('#find').addEventListener('click', function () { 10 | var n = Number(document.querySelector('#n').value); 11 | primesApp.findFirstPrimes(n); 12 | }); 13 | }); 14 | 15 | you can profile as 16 | 17 | timeSeparateCallback(primesApp, 'findFirstPrimes', worker, 'onmessage'); 18 | */ 19 | (function timeSeparateCallback(obj1, methodName1, obj2, methodName2) { 20 | 'use strict'; 21 | 22 | console.assert(obj1, 'missing first object'); 23 | console.assert(obj2, 'missing second object'); 24 | 25 | var m1 = obj1[methodName1]; 26 | console.assert(typeof m1 === 'function', 'cannot find first method ' + methodName1); 27 | var m2 = obj2[methodName2]; 28 | console.assert(typeof m2 === 'function', 'cannot find first method ' + methodName2); 29 | 30 | obj1[methodName1] = function () { 31 | console.profile('separate'); 32 | console.time('separate'); 33 | m1.apply(obj1, arguments); 34 | }; 35 | 36 | obj2[methodName2] = function () { 37 | console.timeEnd('separate'); 38 | console.profileEnd('separate'); 39 | m2.apply(obj2, arguments); 40 | }; 41 | 42 | console.log('wrapped', methodName1, 'and', methodName2, 'in timing calls'); 43 | 44 | // modify the arguments below 45 | }(window, 'postMessage', window, 'onmessage')); 46 | -------------------------------------------------------------------------------- /ng-profile-scope-method.js: -------------------------------------------------------------------------------- 1 | /* 2 | Wraps a method on a scope (found from selector + method name) 3 | with profiling. After method stops, restores the original non-profiled method 4 | Method can return a promise. 5 | Typical use: profile button click. 6 | 7 | $scope.myMethod = function () ... 8 | where $scope could be determined from element 'my-selector' 9 | */ 10 | (function profileScopeMethod() { 11 | var selector = '#find'; 12 | var methodName = 'find'; 13 | var name = selector + ':' + methodName; 14 | 15 | /* global angular */ 16 | var el = angular.element(document.querySelector(selector)); 17 | var scope = el.scope() || el.isolateScope(); 18 | console.assert(scope, 'cannot find scope from ' + name); 19 | 20 | var fn = scope[methodName]; 21 | console.assert(typeof fn === 'function', 'missing ' + methodName); 22 | var $timeout = el.injector().get('$timeout'); 23 | var $q = el.injector().get('$q'); 24 | 25 | scope[methodName] = function () { 26 | console.profile(name); 27 | console.time(name); 28 | 29 | // method can return a value or a promise 30 | var returned = fn(); 31 | $q.when(returned).finally(function finishedMethod() { 32 | console.timeStamp('finished', methodName); 33 | 34 | $timeout(function afterDOMUpdate() { 35 | console.timeStamp('dom updated after', methodName); 36 | console.timeEnd(name); 37 | console.profileEnd(); 38 | scope[methodName] = fn; 39 | console.log('restored', name); 40 | }, 0); 41 | }); 42 | }; 43 | console.log('wrapped', name, 'for measurements'); 44 | }()); 45 | -------------------------------------------------------------------------------- /ng-count-digest-cycles.js: -------------------------------------------------------------------------------- 1 | /* 2 | Adds a watch expression on the root scope that counts the number of times 3 | it was called. After method stops, restores the original method and 4 | prints number of times digest cycle was run. 5 | Method can return a promise. 6 | Typical use: see how many times the full digest cycle is triggered. 7 | 8 | $scope.myMethod = function () ... 9 | where $scope could be determined from element 'my-selector' 10 | */ 11 | (function countDigestCycles() { 12 | var selector = 'button'; 13 | var methodName = 'doSomething'; 14 | var name = selector + ':' + methodName; 15 | 16 | /* global angular */ 17 | var el = angular.element(document.getElementById(selector)); 18 | var scope = el.scope() || el.isolateScope(); 19 | console.assert(scope, 'cannot find scope from ' + name); 20 | 21 | var fn = scope[methodName]; 22 | console.assert(typeof fn === 'function', 'missing ' + methodName); 23 | var $rootScope = el.injector().get('$rootScope'); 24 | 25 | var count = 0; 26 | $rootScope.$watch(function () { 27 | count += 1; 28 | console.log('digest cycle ran', count, 'times'); 29 | }); 30 | 31 | var $q = el.injector().get('$q'); 32 | 33 | scope[methodName] = function () { 34 | 35 | count = 0; 36 | 37 | // method can return a value or a promise 38 | var returned = fn(); 39 | $q.when(returned).finally(function finishedMethod() { 40 | scope.$$postDigest(function () { 41 | scope[methodName] = fn; 42 | console.log('restored', methodName); 43 | }); 44 | }); 45 | }; 46 | console.log('wrapped', name, 'for measuring number of digest cycles'); 47 | 48 | }()); 49 | -------------------------------------------------------------------------------- /keys-vs-values.js: -------------------------------------------------------------------------------- 1 | // measures how much memory object keys take vs values in collection of objects 2 | (function keysVsValuesInit(root) { 3 | function stringSize(str) { 4 | // JavaScript strings are unicode UTF-16 up to 2 bytes per character 5 | return str.length * 2; 6 | } 7 | 8 | function objectSize(obj) { 9 | if (typeof obj === 'string') { 10 | return stringSize(obj); 11 | } 12 | return stringSize(JSON.stringify(obj)); 13 | } 14 | 15 | function values(obj) { 16 | return Object.keys(obj).map(function (key) { 17 | return obj[key]; 18 | }); 19 | } 20 | 21 | function listSize(values) { 22 | return values.reduce(function (total, value) { 23 | return objectSize(value) + total; 24 | }, 0); 25 | } 26 | 27 | function keysValues(obj) { 28 | if (typeof obj === 'object') { 29 | return { 30 | keys: stringSize( Object.keys(obj).join('') ), 31 | values: listSize( values(obj) ) 32 | }; 33 | } else { 34 | return { 35 | keys: 0, 36 | values: objectSize(obj) 37 | }; 38 | } 39 | } 40 | 41 | function keysVsValues(items) { 42 | if (!Array.isArray(items) && typeof items === 'object') { 43 | return keysVsValues([items]); 44 | } 45 | 46 | console.assert(Array.isArray(items)); 47 | return items.reduce(function (sizes, item) { 48 | var size = keysValues(item); 49 | sizes.keys += size.keys; 50 | sizes.values += size.values; 51 | return sizes; 52 | }, { 53 | keys: 0, 54 | values: 0 55 | }); 56 | } 57 | 58 | root.keysVsValues = keysVsValues; 59 | console.log('try keysVsValues();'); 60 | }(this)); 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-snippets", 3 | "description": "Chrome DevTools code snippets ", 4 | "version": "0.0.0-semantic-release", 5 | "author": "Gleb Bahmutov ", 6 | "bugs": { 7 | "url": "https://github.com/bahmutov/code-snippets/issues" 8 | }, 9 | "config": { 10 | "pre-git": { 11 | "commit-msg": "simple", 12 | "pre-commit": [ 13 | "npm test" 14 | ], 15 | "pre-push": [ 16 | "npm run size" 17 | ], 18 | "post-commit": [], 19 | "post-merge": [] 20 | } 21 | }, 22 | "contributors": [], 23 | "dependencies": {}, 24 | "devDependencies": { 25 | "eslint-rules": "0.4.3", 26 | "git-issues": "1.2.0", 27 | "grunt": "0.4.5", 28 | "grunt-cli": "0.1.13", 29 | "grunt-contrib-jshint": "0.11.3", 30 | "grunt-deps-ok": "0.9.0", 31 | "grunt-eslint": "2.1.0", 32 | "grunt-gh-pages": "1.0.0", 33 | "grunt-jscs": "1.2.0", 34 | "grunt-nice-package": "0.10.2", 35 | "grunt-npm2bower-sync": "0.9.1", 36 | "jshint-summary": "0.4.0", 37 | "matchdep": "1.0.0", 38 | "pre-git": "3.1.2", 39 | "semantic-release": "4.3.5" 40 | }, 41 | "engines": { 42 | "node": "> 0.10.*" 43 | }, 44 | "files": [ 45 | "*.js", 46 | "!Gruntfile.js" 47 | ], 48 | "homepage": "https://github.com/bahmutov/code-snippets", 49 | "keywords": [ 50 | "chrome", 51 | "code", 52 | "devtools", 53 | "exploratory", 54 | "mock", 55 | "snippets", 56 | "test", 57 | "testing" 58 | ], 59 | "license": "MIT", 60 | "main": "first-paint.js", 61 | "repository": { 62 | "type": "git", 63 | "url": "https://github.com/bahmutov/code-snippets.git" 64 | }, 65 | "scripts": { 66 | "build": "grunt", 67 | "commit": "git-issues && commit-wizard", 68 | "grunt": "grunt", 69 | "issues": "git-issues", 70 | "semantic-release": "semantic-release pre && npm publish && semantic-release post", 71 | "size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";", 72 | "test": "grunt" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /utils/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "maxerr" : 50, 3 | 4 | "indent" : 2, 5 | "bitwise" : true, 6 | "camelcase" : true, 7 | "curly" : true, 8 | "eqeqeq" : true, 9 | "forin" : true, 10 | "immed" : false, 11 | "latedef" : false, 12 | "newcap" : true, 13 | "noarg" : true, 14 | "noempty" : true, 15 | "nonew" : false, 16 | "plusplus" : false, 17 | "quotmark" : "single", 18 | "undef" : true, 19 | "unused" : true, 20 | "strict" : false, 21 | "trailing" : false, 22 | "maxparams" : 4, 23 | "maxdepth" : 3, 24 | "maxstatements" : 20, 25 | "maxcomplexity" : 20, 26 | "maxlen" : 120, 27 | 28 | "asi" : false, 29 | "boss" : false, 30 | "debug" : false, 31 | "eqnull" : false, 32 | "es5" : false, 33 | "esnext" : true, 34 | "moz" : false, 35 | "evil" : false, 36 | "expr" : false, 37 | "funcscope" : false, 38 | "globalstrict" : false, 39 | "iterator" : false, 40 | "lastsemic" : false, 41 | "laxbreak" : false, 42 | "laxcomma" : false, 43 | "loopfunc" : false, 44 | "multistr" : false, 45 | "proto" : false, 46 | "scripturl" : false, 47 | "smarttabs" : true, 48 | "shadow" : false, 49 | "sub" : false, 50 | "supernew" : false, 51 | "validthis" : false, 52 | 53 | "browser" : true, 54 | "couch" : false, 55 | "devel" : true, 56 | "dojo" : false, 57 | "jquery" : false, 58 | "mootools" : false, 59 | "node" : false, 60 | "nonstandard" : false, 61 | "prototypejs" : false, 62 | "rhino" : false, 63 | "worker" : false, 64 | "wsh" : false, 65 | "yui" : false, 66 | 67 | "nomen" : false, 68 | "onevar" : false, 69 | "passfail" : false, 70 | "white" : false, 71 | 72 | "globals" : { 73 | "Promise": true 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /expensive-keys.js: -------------------------------------------------------------------------------- 1 | // measures how much memory a key and its value takes up in a collection of objects 2 | (function expensiveKeysInit(root) { 3 | function stringSize(str) { 4 | // JavaScript strings are unicode UTF-16 up to 2 bytes per character 5 | return str.length * 2; 6 | } 7 | 8 | function objectSize(obj) { 9 | return stringSize(JSON.stringify(obj)); 10 | } 11 | 12 | var value = function value(key) { 13 | return function (object) { 14 | return object[key]; 15 | }; 16 | }; 17 | 18 | var pickValue = function (key, items) { 19 | return items.map(value(key)); 20 | }; 21 | 22 | function keySize(items, key) { 23 | return stringSize(key) * items.length + objectSize(pickValue(key, items)); 24 | } 25 | 26 | function zip(keys, values) { 27 | var result = {}; 28 | keys.forEach(function (key, index) { 29 | result[key] = values[index]; 30 | }); 31 | return result; 32 | } 33 | 34 | function toMB(bytes) { 35 | return bytes / 1024 / 1024; 36 | } 37 | 38 | function toSizeMB(size) { 39 | return toMB(size).toFixed(2) + ' MB'; 40 | } 41 | 42 | function valuesInMB(obj) { 43 | var result = {}; 44 | Object.keys(obj).forEach(function (key) { 45 | var val = obj[key]; 46 | if (typeof val === 'number') { 47 | result[key] = toSizeMB(obj[key]); 48 | } 49 | }); 50 | return result; 51 | } 52 | 53 | function propertySizes(keys, items) { 54 | if (arguments.length === 1) { 55 | items = keys; 56 | if (!Array.isArray(items) && typeof items === 'object') { 57 | items = [items]; 58 | } 59 | if (!items.length) { 60 | return {}; 61 | } 62 | keys = Object.keys(items[0]); 63 | } 64 | 65 | var keyInItemsSize = keySize.bind(null, items); 66 | var sizes = keys.map(keyInItemsSize); 67 | var result = zip(keys, sizes); 68 | result.mb = valuesInMB.bind(null, result); 69 | return result; 70 | } 71 | 72 | root.expensiveKeys = propertySizes; 73 | console.log('try expensiveKeys();'); 74 | console.log('you can call .mb() method on the returned object'); 75 | }(this)); 76 | -------------------------------------------------------------------------------- /ng-count-watchers.js: -------------------------------------------------------------------------------- 1 | // taken from http://ng.malsup.com/#!/counting-watchers 2 | /* 3 | Usage 4 | - just run the script and it will compute number of watchers on the page 5 | - select an element on the page using "Elements" tab 6 | and run `countAngularWatchers(angular, $0)` to count total watchers 7 | in the tree rooted at the selected element. Useful to find where the 8 | large numbers of watchers are "hiding" 9 | */ 10 | (function countAngularWatchers(angular, start) { 11 | window.countAngularWatchers = countAngularWatchers; 12 | 13 | function allDescendents(list, node) { 14 | list.push(node); 15 | Array.prototype.forEach.call(node.childNodes, function (child) { 16 | allDescendents(list, child); 17 | }); 18 | } 19 | 20 | var i, data, scope, 21 | count = 0, 22 | all = document.all, 23 | test = {}, 24 | watchers = []; 25 | 26 | if (start) { 27 | all = []; 28 | allDescendents(all, start); 29 | } 30 | console.log('counting watchers in', all.length, 'elements'); 31 | var len = all.length; 32 | 33 | var mostWatchers = 0; 34 | var maxWatchersToPrint = 20; 35 | 36 | function countScopeWatchers(scope, element) { 37 | test[scope.$id] = true; 38 | var n = scope.$$watchers.length; 39 | count += n; 40 | if (n > 0){ 41 | watchers.push.apply(watchers, scope.$$watchers); 42 | } 43 | if (n > mostWatchers) { 44 | console.log('most watchers', n); 45 | console.log(element); 46 | mostWatchers = n; 47 | 48 | } 49 | } 50 | 51 | function countWatchersInData(el) { 52 | data = el.data(); 53 | if (data) { 54 | scope = data.$scope || data.$isolateScope; 55 | if (scope && scope.$$watchers) { 56 | if (!test[scope.$id]) { 57 | countScopeWatchers(scope, el); 58 | } 59 | } 60 | } 61 | } 62 | 63 | // go through each element. Count watchers if it has scope or isolate scope 64 | for (i = 0; i < len; i += 1) { 65 | var el = angular.element(all[i]); 66 | countWatchersInData(el); 67 | } 68 | console.log('this page has ' + watchers.length + ' angular watchers'); 69 | if (watchers.length < maxWatchersToPrint) { 70 | console.log('the watchers are:', watchers); 71 | } 72 | return count; 73 | }(window.angular)); 74 | -------------------------------------------------------------------------------- /update-code-snippets.js: -------------------------------------------------------------------------------- 1 | // updates code snippets from remote repo 2 | // needs to run in the secondary DevTools window (undocked) 3 | // see "Updating local code snippets" 4 | var source = localStorage.scriptSnippets; 5 | if (typeof source === 'undefined') { 6 | throw new Error('Cannot find scriptSnippets, are you running in the secondary DevTools?\n' + 7 | 'see https://github.com/bahmutov/code-snippets#updating-local-code-snippets'); 8 | } 9 | var snippets = JSON.parse(source); 10 | console.log('I have', snippets.length, 'code snippets'); 11 | console.table(snippets); 12 | 13 | // read code snippets from this repo via RawGit.com 14 | var repo = 'https://rawgit.com/bahmutov/code-snippets/master/'; 15 | 16 | function fetch(url) { 17 | return new Promise(function (resolve, reject) { 18 | var request = new XMLHttpRequest(); 19 | request.open('GET', url, true); 20 | 21 | request.onload = function () { 22 | if (request.status >= 200 && request.status < 400) { 23 | resolve(request.responseText); 24 | } else { 25 | reject(request.responseText); 26 | } 27 | }; 28 | 29 | request.onerror = function (err) { 30 | reject(err); 31 | }; 32 | 33 | request.send(); 34 | }); 35 | } 36 | 37 | var updated = []; 38 | 39 | function updateSnippet(k, id, filename) { 40 | return fetch(repo + filename) 41 | .then(function (source) { 42 | console.log('fetched new source for', id, filename); 43 | // console.log(source); 44 | updated.push({ 45 | index: k, 46 | id: id, 47 | content: source, 48 | name: filename 49 | }); 50 | }, function () { 51 | console.error('cannot find remote for', filename); 52 | }); 53 | } 54 | 55 | function chainSnippet(chain, snippet, k) { 56 | return chain.then(function () { 57 | return updateSnippet(k, snippet.id, snippet.name); 58 | }); 59 | } 60 | 61 | var allChecked = snippets.reduce(chainSnippet, Promise.resolve()); 62 | allChecked.then(function () { 63 | console.log('fetched', updated.length, 'snippets'); 64 | updated.forEach(function (update) { 65 | var snippet = snippets[update.index]; 66 | console.assert(update.name === snippet.name, 67 | 'name mismatch for update', update, snippet); 68 | snippet.content = update.content; 69 | }); 70 | if (updated.length) { 71 | localStorage.scriptSnippets = JSON.stringify(snippets); 72 | console.table(updated); 73 | console.log('please reopen DevTools to load updated code snippets'); 74 | } else { 75 | console.log('nothing to update.'); 76 | } 77 | }); 78 | -------------------------------------------------------------------------------- /ng-scope-size.js: -------------------------------------------------------------------------------- 1 | // Finds total size of objects attached to the scopes 2 | (function ngScopeSize() { 3 | 4 | var i, data, scope, 5 | count = 0, 6 | all = document.all, 7 | len = all.length, 8 | test = {}, 9 | scopes = 0; 10 | 11 | /* 12 | sizeof.js modified for angular scope client data computation 13 | A function to calculate the approximate memory usage of objects 14 | Created by Stephen Morley - http://code.stephenmorley.org/ - and released under 15 | the terms of the CC0 1.0 Universal legal code: 16 | http://creativecommons.org/publicdomain/zero/1.0/legalcode 17 | */ 18 | 19 | /* Returns the approximate memory usage, in bytes, of the specified object. The 20 | * parameter is: 21 | * 22 | * object - the object whose size should be determined 23 | */ 24 | /* jshint -W073, -W071 */ 25 | function sizeof(object){ 26 | 27 | // initialise the list of objects and size 28 | var objects = [object]; 29 | var size = 0; 30 | 31 | // loop over the objects 32 | for (var index = 0; index < objects.length; index += 1) { 33 | 34 | // determine the type of the object 35 | switch (typeof objects[index]) { 36 | 37 | case 'boolean': { 38 | size += 4; break; 39 | } 40 | 41 | case 'number': { 42 | size += 8; break; 43 | } 44 | 45 | case 'string': { 46 | size += 2 * objects[index].length; 47 | break; 48 | } 49 | 50 | case 'object': { 51 | 52 | // loop over the keys 53 | for (var key in objects[index]) { 54 | if (!objects[index].hasOwnProperty(key)) { 55 | continue; 56 | } 57 | if (key[0] === '$' || key === 'this' || key === 'constructor' || key === 'lenth') { 58 | continue; // angular's internal property ($apply, etc) 59 | } 60 | 61 | // determine whether the value has already been processed 62 | var processed = false; 63 | /* jshint -W073 */ 64 | for (var search = 0; search < objects.length; search += 1){ 65 | if (objects[search] === objects[index][key]){ 66 | processed = true; 67 | break; 68 | } 69 | } 70 | 71 | // queue the value to be processed if appropriate 72 | if (!processed) { 73 | objects.push(objects[index][key]); 74 | } 75 | 76 | } 77 | } 78 | 79 | } 80 | 81 | } 82 | 83 | // return the calculated size 84 | return size; 85 | 86 | } 87 | 88 | // go through each element. Count watchers if it has scope or isolate scope 89 | /* eslint no-for-loops:0 */ 90 | for (i = 0; i < len; i++) { 91 | /* global angular */ 92 | data = angular.element(all[i]).data(); 93 | scope = data.$scope || data.$isolateScope; 94 | if (scope) { 95 | if ( !test[ scope.$id ] ) { 96 | test[ scope.$id ] = true; 97 | count += sizeof(scope); 98 | scopes += 1; 99 | } 100 | } 101 | } 102 | console.log(scopes, 'scopes have', count, 'bytes attached'); 103 | return count; 104 | 105 | }()); 106 | -------------------------------------------------------------------------------- /harlem-shake-xss.js: -------------------------------------------------------------------------------- 1 | // ready to dance, from https://github.com/DinisCruz/XSS-Pocs/blob/master/pocs/dance-xss.js 2 | /* jshint -W101 */ 3 | /* jshint -W061 */ 4 | /* eslint no-eval:0 */ 5 | eval(atob('IWZ1bmN0aW9uKCl7ZnVuY3Rpb24gYSgpe3ZhciBhPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImxpbmsiKTthLnNldEF0dHJpYnV0ZSgidHlwZSIsInRleHQvY3NzIiksYS5zZXRBdHRyaWJ1dGUoInJlbCIsInN0eWxlc2hlZXQiKSxhLnNldEF0dHJpYnV0ZSgiaHJlZiIsdyksYS5zZXRBdHRyaWJ1dGUoImNsYXNzIix4KSxkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKGEpfWZ1bmN0aW9uIGIoKXtmb3IodmFyIGE9ZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSh4KSxiPTA7YjxhLmxlbmd0aDtiKyspZG9jdW1lbnQuYm9keS5yZW1vdmVDaGlsZChhW2JdKX1mdW5jdGlvbiBjKCl7dmFyIGE9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZGl2Iik7YS5zZXRBdHRyaWJ1dGUoImNsYXNzIix2KSxkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKGEpLHNldFRpbWVvdXQoZnVuY3Rpb24oKXtkb2N1bWVudC5ib2R5LnJlbW92ZUNoaWxkKGEpfSwxMDApfWZ1bmN0aW9uIGQoYSl7cmV0dXJue2hlaWdodDphLm9mZnNldEhlaWdodCx3aWR0aDphLm9mZnNldFdpZHRofX1mdW5jdGlvbiBlKGEpe3ZhciBiPWQoYSk7cmV0dXJuIGIuaGVpZ2h0Pm4mJmIuaGVpZ2h0PHAmJmIud2lkdGg+byYmYi53aWR0aDxxfWZ1bmN0aW9uIGYoYSl7Zm9yKHZhciBiPWEsYz0wO2I7KWMrPWIub2Zmc2V0VG9wLGI9Yi5vZmZzZXRQYXJlbnQ7cmV0dXJuIGN9ZnVuY3Rpb24gZygpe3ZhciBhPWRvY3VtZW50LmRvY3VtZW50RWxlbWVudDtyZXR1cm4gd2luZG93LmlubmVyV2lkdGg/d2luZG93LmlubmVySGVpZ2h0OmEmJiFpc05hTihhLmNsaWVudEhlaWdodCk/YS5jbGllbnRIZWlnaHQ6MH1mdW5jdGlvbiBoKCl7cmV0dXJuIHdpbmRvdy5wYWdlWU9mZnNldD93aW5kb3cucGFnZVlPZmZzZXQ6TWF0aC5tYXgoZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnNjcm9sbFRvcCxkb2N1bWVudC5ib2R5LnNjcm9sbFRvcCl9ZnVuY3Rpb24gaShhKXt2YXIgYj1mKGEpO3JldHVybiBiPj16JiZiPD15K3p9ZnVuY3Rpb24gaigpe3ZhciBhPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImF1ZGlvIik7YS5zZXRBdHRyaWJ1dGUoImNsYXNzIix4KSxhLnNyYz1yLGEubG9vcD0hMSxhLmFkZEV2ZW50TGlzdGVuZXIoImNhbnBsYXkiLGZ1bmN0aW9uKCl7c2V0VGltZW91dChmdW5jdGlvbigpe2soQil9LDUwMCksc2V0VGltZW91dChmdW5jdGlvbigpe20oKSxjKCk7Zm9yKHZhciBhPTA7YTxFLmxlbmd0aDthKyspbChFW2FdKX0sMTU1MDApfSwhMCksYS5hZGRFdmVudExpc3RlbmVyKCJlbmRlZCIsZnVuY3Rpb24oKXttKCksYigpfSwhMCksYS5pbm5lckhUTUw9IiA8cD5JZiB5b3UgYXJlIHJlYWRpbmcgdGhpcywgaXQgaXMgYmVjYXVzZSB5b3VyIGJyb3dzZXIgZG9lcyBub3Qgc3VwcG9ydCB0aGUgYXVkaW8gZWxlbWVudC4gV2UgcmVjb21tZW5kIHRoYXQgeW91IGdldCBhIG5ldyBicm93c2VyLjwvcD4gPHA+Iixkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKGEpLGEucGxheSgpfWZ1bmN0aW9uIGsoYSl7YS5jbGFzc05hbWUrPSIgIitzKyIgIit0fWZ1bmN0aW9uIGwoYSl7YS5jbGFzc05hbWUrPSIgIitzKyIgIit1W01hdGguZmxvb3IoTWF0aC5yYW5kb20oKSp1Lmxlbmd0aCldfWZ1bmN0aW9uIG0oKXtmb3IodmFyIGE9ZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZShzKSxiPW5ldyBSZWdFeHAoIlxiIitzKyJcYiIpLGM9MDtjPGEubGVuZ3RoOylhW2NdLmNsYXNzTmFtZT1hW2NdLmNsYXNzTmFtZS5yZXBsYWNlKGIsIiIpfWZvcih2YXIgbj0zMCxvPTMwLHA9MzUwLHE9MzUwLHI9Ii8vczMuYW1hem9uYXdzLmNvbS9tb292d2ViLW1hcmtldGluZy9wbGF5Z3JvdW5kL2hhcmxlbS1zaGFrZS5tcDMiLHM9Im13LWhhcmxlbV9zaGFrZV9tZSIsdD0iaW1fZmlyc3QiLHU9WyJpbV9kcnVuayIsImltX2Jha2VkIiwiaW1fdHJpcHBpbiIsImltX2Jsb3duIl0sdj0ibXctc3Ryb2JlX2xpZ2h0Iix3PSIvL3MzLmFtYXpvbmF3cy5jb20vbW9vdndlYi1tYXJrZXRpbmcvcGxheWdyb3VuZC9oYXJsZW0tc2hha2Utc3R5bGUuY3NzIix4PSJtd19hZGRlZF9jc3MiLHk9ZygpLHo9aCgpLEE9ZG9jdW1lbnQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoIioiKSxCPW51bGwsQz0wO0M8QS5sZW5ndGg7QysrKXt2YXIgRD1BW0NdO2lmKGUoRCkmJmkoRCkpe0I9RDticmVha319aWYobnVsbD09PUQpcmV0dXJuIHZvaWQgY29uc29sZS53YXJuKCJDb3VsZCBub3QgZmluZCBhIG5vZGUgb2YgdGhlIHJpZ2h0IHNpemUuIFBsZWFzZSB0cnkgYSBkaWZmZXJlbnQgcGFnZS4iKTthKCksaigpO2Zvcih2YXIgRT1bXSxDPTA7QzxBLmxlbmd0aDtDKyspe3ZhciBEPUFbQ107ZShEKSYmRS5wdXNoKEQpfX0oKSxjb25zb2xlLmxvZygiRGFuY2Ugc2hvdWxkIHN0YXJ0IGFueSBtaW51dGUgbm93IiksaGlzdG9yeS5wdXNoU3RhdGUoe30sInNlY3VyZSIsIi9ub3RoaW5nL3RvL3NlZS9oZXJlLmh0bWwiKTs=')); 6 | -------------------------------------------------------------------------------- /timing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Timing.js 1.0.1 3 | * Copyright 2014 Addy Osmani 4 | */ 5 | (function(window) { 6 | 'use strict'; 7 | 8 | /** 9 | * Navigation Timing API helpers 10 | * timing.getTimes(); 11 | **/ 12 | window.timing = window.timing || { 13 | /** 14 | * Outputs extended measurements using Navigation Timing API 15 | * @param Object opts Options (simple (bool) - opts out of full data view) 16 | * @return Object measurements 17 | */ 18 | getTimes: function(opts) { 19 | var performance = window.performance || window.webkitPerformance || window.msPerformance || window.mozPerformance; 20 | var timing = performance.timing; 21 | var api = {}; 22 | opts = opts || {}; 23 | 24 | if (timing) { 25 | if (opts && !opts.simple) { 26 | for (var k in timing) { 27 | if (timing.hasOwnProperty(k)) { 28 | api[k] = timing[k]; 29 | } 30 | } 31 | } 32 | 33 | 34 | // Time to first paint 35 | if (api.firstPaint === undefined) { 36 | // All times are relative times to the start time within the 37 | // same objects 38 | var firstPaint = 0; 39 | 40 | // Chrome 41 | if (window.chrome && window.chrome.loadTimes) { 42 | // Convert to ms 43 | firstPaint = window.chrome.loadTimes().firstPaintTime * 1000; 44 | api.firstPaintTime = firstPaint - (window.chrome.loadTimes().startLoadTime * 1000); 45 | } 46 | // IE 47 | else if (typeof window.performance.timing.msFirstPaint === 'number') { 48 | firstPaint = window.performance.timing.msFirstPaint; 49 | api.firstPaintTime = firstPaint - window.performance.timing.navigationStart; 50 | } 51 | // Firefox 52 | // This will use the first times after MozAfterPaint fires 53 | //else if (window.performance.timing.navigationStart && typeof InstallTrigger !== 'undefined') { 54 | // api.firstPaint = window.performance.timing.navigationStart; 55 | // api.firstPaintTime = mozFirstPaintTime - window.performance.timing.navigationStart; 56 | //} 57 | if (opts && !opts.simple) { 58 | api.firstPaint = firstPaint; 59 | } 60 | } 61 | 62 | // Total time from start to load 63 | api.loadTime = timing.loadEventEnd - timing.navigationStart; 64 | // Time spent constructing the DOM tree 65 | api.domReadyTime = timing.domComplete - timing.domInteractive; 66 | // Time consumed prepaing the new page 67 | api.readyStart = timing.fetchStart - timing.navigationStart; 68 | // Time spent during redirection 69 | api.redirectTime = timing.redirectEnd - timing.redirectStart; 70 | // AppCache 71 | api.appcacheTime = timing.domainLookupStart - timing.fetchStart; 72 | // Time spent unloading documents 73 | api.unloadEventTime = timing.unloadEventEnd - timing.unloadEventStart; 74 | // DNS query time 75 | api.lookupDomainTime = timing.domainLookupEnd - timing.domainLookupStart; 76 | // TCP connection time 77 | api.connectTime = timing.connectEnd - timing.connectStart; 78 | // Time spent during the request 79 | api.requestTime = timing.responseEnd - timing.requestStart; 80 | // Request to completion of the DOM loading 81 | api.initDomTreeTime = timing.domInteractive - timing.responseEnd; 82 | // Load event time 83 | api.loadEventTime = timing.loadEventEnd - timing.loadEventStart; 84 | } 85 | 86 | return api; 87 | }, 88 | /** 89 | * Uses console.table() to print a complete table of timing information 90 | * @param Object opts Options (simple (bool) - opts out of full data view) 91 | */ 92 | printTable: function(opts) { 93 | var table = {}; 94 | var data = this.getTimes(opts); 95 | Object.keys(data).sort().forEach(function(k) { 96 | table[k] = { 97 | ms: data[k], 98 | s: +((data[k] / 1000).toFixed(2)) 99 | }; 100 | }); 101 | console.table(table); 102 | }, 103 | /** 104 | * Uses console.table() to print a summary table of timing information 105 | */ 106 | printSimpleTable: function() { 107 | this.printTable({ 108 | simple: true 109 | }); 110 | } 111 | }; 112 | 113 | // By default, print the simple table 114 | return timing.printSimpleTable(); 115 | 116 | })(this); 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chrome DevTools code snippets 2 | 3 | > Performance, debugging and testing code snippets to be run in Chrome DevTools 4 | 5 | [![NPM][code-snippets-icon] ][code-snippets-url] 6 | 7 | [![Build status][code-snippets-ci-image] ][code-snippets-ci-url] 8 | [![dependencies][code-snippets-dependencies-image] ][code-snippets-dependencies-url] 9 | [![devdependencies][code-snippets-devdependencies-image] ][code-snippets-devdependencies-url] 10 | [![Codacy Badge][code-snippets-codacy-image] ][code-snippets-codacy-url] 11 | [![semantic-release][semantic-image] ][semantic-url] 12 | 13 | [code-snippets-icon]: https://nodei.co/npm/code-snippets.png?downloads=true 14 | [code-snippets-url]: https://npmjs.org/package/code-snippets 15 | [code-snippets-ci-image]: https://travis-ci.org/bahmutov/code-snippets.png?branch=master 16 | [code-snippets-ci-url]: https://travis-ci.org/bahmutov/code-snippets 17 | [code-snippets-dependencies-image]: https://david-dm.org/bahmutov/code-snippets.png 18 | [code-snippets-dependencies-url]: https://david-dm.org/bahmutov/code-snippets 19 | [code-snippets-devdependencies-image]: https://david-dm.org/bahmutov/code-snippets/dev-status.png 20 | [code-snippets-devdependencies-url]: https://david-dm.org/bahmutov/code-snippets#info=devDependencies 21 | [code-snippets-codacy-image]: https://www.codacy.com/project/badge/99acaf40b1f1483c80016eb31fbaef49 22 | [code-snippets-codacy-url]: https://www.codacy.com/public/bahmutov/code-snippets.git 23 | [semantic-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg 24 | [semantic-url]: https://github.com/semantic-release/semantic-release 25 | 26 | ![fist paint](https://raw.githubusercontent.com/bahmutov/code-snippets/master/first-paint-code-snippet.png) 27 | 28 | Read [Code Snippets tutorial][1], 29 | [Performance profiling using DevTools code snippets][2] and 30 | [How to improve Angular application performance using code snippets][3]. 31 | 32 | Note: code snippets do NOT have access to the full console API, for example no access to 33 | `console.monitor`. 34 | 35 | ## Snippets 36 | 37 | ### Security 38 | 39 | * [test-script-injection.js](test-script-injection.js) - tries to create a new 40 | inline script tag to test if page allows it. 41 | * [harlem-shake-xss.js](harlem-shake-xss.js) - little XSS script that injects 42 | [Harlem Shake music](https://github.com/DinisCruz/XSS-Pocs/blob/master/pocs/dance-xss.js) 43 | 44 | ### DOM and CPU generic performance 45 | 46 | * [boilerplate.js](boilerplate.js) - boilerplate for loading and running a remote code script 47 | (see [remote download](#remote-download)). 48 | * [first-paint.js](first-paint.js) - time from page reload to first visible contents. 49 | * [timing.js](timing.js) - Detailed page timing information, 50 | from [addyosmani/timing.js](https://github.com/addyosmani/timing.js). 51 | * [time-method-call.js](time-method-call.js) - measures single method call time. 52 | * [profile-method-call.js](profile-method-call.js) - profiles a single method call. 53 | * [profile-prototype-method.js](profile-prototype-method.js) - profiles a single method call 54 | that is on a prototype object, not on an instance. 55 | * [profile-separate-calls.js](profile-separate-calls.js) can profile actions where separate 56 | method calls start and stop the operation. 57 | * [css-layout.js](css-layout.js) draws boundary around every DOM element for 58 | clarity. 59 | 60 | ### Storage measurements 61 | 62 | * [local-storage-size.js](local-storage-size.js) - measures size of the strings in the `localStorage`. 63 | * [expensive-keys.js](expensive-keys.js) - measures how much space individual keys and their values 64 | take up in a collection of objects, read [Measuring Space Allocation][measure]. 65 | * [keys-vs-values.js](keys-vs-values.js) - measures length of keys vs length of values in an array. 66 | 67 | ### Angular performance 68 | 69 | * [ng-count-watchers.js](ng-count-watchers.js) - counts total watchers in the page. 70 | More watchers - slower digest cycle. 71 | * [ng-idle-apply-timing.js](ng-idle-apply-timing.js) - measures how long a digest cycle takes without 72 | any data changes. This measures purely how long all watched expressions take to compute and compare 73 | to previous values (dirty checking). 74 | * [ng-profile-scope-method.js](ng-profile-scope-method.js) - installs profile calls around a given 75 | scope method. When the method completes, the original non-instrumented version will be restored. 76 | The browser will have timeline and CPU profile. 77 | * [ng-run-digest-cycle.js](ng-run-digest-cycle.js) - triggers digest cycle starting with root scope. 78 | * [ng-profile-data-change.js](ng-profile-data-change.js) - changes data on the scope, runs digest cycle 79 | to profile listeners. 80 | * [ng-scope-size.js](ng-scope-size.js) - finds total size of all user objects attached to all scopes. 81 | Smaller data - faster copying and comparison during digest cycle. 82 | * [ng-find-scope-property.js](ng-find-scope-property.js) - finds all scopes that own a property 83 | with given name. 84 | * [ng-profile-local-digest.js](ng-profile-local-digest.js) - runs idle digest cycle starting at the scope 85 | that surrounds given selector. Useful to find parts of the page with expensive watchers. 86 | * [ng-find-expensive-digest.js](ng-find-expensive-digest.js) builds upon ng-profile-local-digest.js to measure 87 | digest duration for several selectors and print sorted table starting with the slowest digest duration. 88 | * [ng-monitor-digest-cycle.js](ng-monitor-digest-cycle.js) - prints a string every time a digest cycle runs. 89 | * [ng-count-digest-cycles.js](ng-count-digest-cycles.js) - counts number of full digest cycles (from the root scope) 90 | that run when a scope method executes. Useful because sometimes you can get away with just a local digest 91 | cycle, rather than a full update. See [Local Angular scopes](http://glebbahmutov.com/blog/local-angular-scopes/). 92 | * [ng-count-digest-cycle-simple.js](ng-count-digest-cycle-simple.js) - keeps counting number of times 93 | the digest cycle runs. 94 | * [ng-throw-error.js](ng-throw-error.js) throws an error from the digest cycle; useful for checking 95 | if your [exception handler](http://glebbahmutov.com/blog/catch-all-errors-in-angular-app/) is working. 96 | 97 | ## Misc snippets 98 | 99 | * [github-pull-request-template.js](github-pull-request-template.js) - better GitHub pull request 100 | text, based on the blog post [Enforce standards while submitting a pull request](http://krasimirtsonev.com/blog/article/enforce-standards-while-submitting-a-pull-request) by [Krasimir Tsonev](https://github.com/krasimir). 101 | * [remove-all-but.js](remove-all-but.js) - removes all elements in the page, except the ones in 102 | the trees with specified selectors. Can be used to quickly clean up the page and leave just the essentials. 103 | 104 | All snippets, including mine are distributed under MIT license. 105 | 106 | ## Updating local code snippets 107 | 108 | You can update local code snippets by downloading new versions from this github repository. 109 | Create a new code snippet and copy the source from [update-code-snippets.js](update-code-snippets.js). 110 | 111 | Note: the approach below does not work any more, 112 | see [the open issue](https://github.com/bahmutov/code-snippets/issues/23). 113 | 114 | You will run this code snippet in an unusual way. First, open any web page, even an empty tab. 115 | Open the DevTools in **undocked** mode (Command+Option+I on Mac). Then open the DevTools **again**, 116 | *while focused* on the first DevTools. This will open the second DevTools instance with the source for the 117 | first DevTools panels. If you inspect the `localStorage` variable in the second DevTools window, you will 118 | find lots of interesting stuff, including all the code snippets in the `localStorage.scriptSnippets` property. 119 | 120 | Whenever you want to update the your local code snippets in the Chrome DevTools, execute the `update-code-snippets.js` 121 | snippet in the second DevTools instance. The update script looks at the your current code snippets and 122 | tries to download a file with same name from the code snippets github repository (via [RawGit][RawGit]). 123 | If the remote file has been downloaded successfully, it will replace the snippet. 124 | After all snippets are checked, reopen the DevTools to load the updated source code. 125 | 126 | ![update code snippets](images/update-code-snippets.png) 127 | 128 | Note, that only the latest source is downloaded, not any particular release. 129 | Also, only code snippets with names matching existing files in this repo are replaced. If you do not 130 | want to override a code snippet - just rename it, for example, remove the `.js` extension. 131 | 132 | ## Remote download a single script 133 | 134 | You can download and run a single snippet by using the following boilerplate 135 | (scripts are via downloaded via [RawGit][RawGit]) 136 | 137 | ```js 138 | (function firstPaintRemote() { 139 | // form rawGit proxy url 140 | var ghUrl = 'bahmutov/code-snippets/master/first-paint.js'; 141 | var rawUrl = 'https://rawgit.com/' + ghUrl; 142 | // download and run the script 143 | var head = document.getElementsByTagName('head')[0]; 144 | var script = document.createElement('script'); 145 | script.type = 'text/javascript'; 146 | script.src = rawUrl; 147 | head.appendChild(script); 148 | }()); 149 | ``` 150 | 151 | ![remote](https://raw.githubusercontent.com/bahmutov/code-snippets/master/first-paint-code-snippet-remote.png) 152 | 153 | ### Small print 154 | 155 | Author: Gleb Bahmutov © 2014 156 | 157 | * [@bahmutov](https://twitter.com/bahmutov) 158 | * [glebbahmutov.com](http://glebbahmutov.com) 159 | * [blog](http://glebbahmutov.com/blog/) 160 | 161 | License: MIT - do anything with the code, but don't blame me if it does not work. 162 | 163 | Spread the word: tweet, star on github, etc. 164 | 165 | Support: if you find any problems with this module, email / tweet / 166 | [open issue](https://github.com/bahmutov/code-snippets/issues?state=open) on Github 167 | 168 | [1]: http://glebbahmutov.com/blog/chrome-dev-tools-code-snippets/ 169 | [2]: http://glebbahmutov.com/blog/performance-profiling-using-devtools-code-snippets/ 170 | [3]: http://glebbahmutov.com/blog/improving-angular-web-app-performance-example/ 171 | [measure]: http://glebbahmutov.com/blog/measure-space-allocation/ 172 | [RawGit]: https://rawgit.com/ 173 | --------------------------------------------------------------------------------