├── .gitignore ├── .npmrc ├── find-expensive-digest.png ├── first-paint-code-snippet.png ├── .travis.yml ├── images └── update-code-snippets.png ├── first-paint-code-snippet-remote.png ├── ng-run-digest-cycle.js ├── first-paint.js ├── test-script-injection.js ├── first-paint-remote.js ├── boilerplate.js ├── utils ├── jscs.json ├── eslint.json └── .jshintrc ├── ng-count-digest-cycle-simple.js ├── ng-monitor-digest-cycle.js ├── ng-idle-apply-timing.js ├── bower.json ├── local-storage-size.js ├── time-method-call.js ├── profile-method-call.js ├── ng-throw-error.js ├── github-pull-request-template.js ├── ng-find-scope-property.js ├── ng-profile-data-change.js ├── test └── count-digest-cycles.html ├── ng-count-watchers.js ├── ng-find-expensive-digest.js ├── profile-prototype-method.js ├── Gruntfile.js ├── ng-profile-local-digest.js ├── profile-separate-calls.js ├── ng-profile-scope-method.js ├── ng-count-digest-cycles.js ├── keys-vs-values.js ├── package.json ├── expensive-keys.js ├── update-code-snippets.js ├── ng-scope-size.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/krasimir/code-snippets/master/find-expensive-digest.png -------------------------------------------------------------------------------- /first-paint-code-snippet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krasimir/code-snippets/master/first-paint-code-snippet.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "0.12" 5 | branches: 6 | only: 7 | - master 8 | -------------------------------------------------------------------------------- /images/update-code-snippets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krasimir/code-snippets/master/images/update-code-snippets.png -------------------------------------------------------------------------------- /first-paint-code-snippet-remote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krasimir/code-snippets/master/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 | -------------------------------------------------------------------------------- /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 testScriptInjection() { 7 | var el = document.createElement('script'); 8 | el.innerText = 'alert("hi there")'; 9 | document.body.appendChild(el); // runs the code by default 10 | }()); 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 angular, performance */ 9 | angular.element(document.body).injector().invoke(function timeApply($rootScope) { 10 | console.profile('$apply'); 11 | var started = performance.now(); 12 | $rootScope.$apply(); 13 | var takes = performance.now() - started; 14 | console.log('idle $apply takes', takes, 'ms'); 15 | console.profileEnd(); 16 | return takes; 17 | }); 18 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-snippets", 3 | "main": "first-paint.js", 4 | "version": "0.5.0", 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 | "testing", 20 | "test", 21 | "exploratory", 22 | "mock", 23 | "chrome", 24 | "devtools", 25 | "code", 26 | "snippets" 27 | ], 28 | "description": "Chrome DevTools code snippets ", 29 | "authors": [ 30 | "Gleb Bahmutov " 31 | ] 32 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /github-pull-request-template.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var textareaId = '#pull_request_body'; 3 | var textarea = document.querySelector(textareaId); 4 | var template = ''; 5 | var firstLine; 6 | 7 | template += firstLine = 'ID (ticket/card/issue): '; 8 | template += '\n\n'; 9 | template += '## Task/Problem\n\n'; 10 | template += '## Solution\n\n'; 11 | template += '## Steps to reproduce\n\n'; 12 | template += '## UAT\n\n'; 13 | template += '## Code review\n\n\n'; 14 | template += '- [ ] Unit tests passed\n'; 15 | template += '- [ ] System tests passed\n'; 16 | 17 | if (textarea) { 18 | textarea.value = template; 19 | textarea.focus(); 20 | textarea.scrollTop = 0; 21 | textarea.selectionStart = textarea.selectionEnd = firstLine.length; 22 | } else { 23 | alert('You are either not on the PR page or there is no ' + textareaId + ' element.'); 24 | } 25 | })(); -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ng-count-watchers.js: -------------------------------------------------------------------------------- 1 | // taken from http://ng.malsup.com/#!/counting-watchers 2 | (function countAngularWatchers(angular) { 3 | var i, data, scope, 4 | count = 0, 5 | all = document.all, 6 | len = all.length, 7 | test = {}; 8 | 9 | var mostWatchers = 0; 10 | 11 | function countScopeWatchers(scope, element) { 12 | test[scope.$id] = true; 13 | var n = scope.$$watchers.length; 14 | count += n; 15 | if (n > mostWatchers) { 16 | console.log('most watchers', n); 17 | console.log(element); 18 | mostWatchers = n; 19 | } 20 | } 21 | 22 | // go through each element. Count watchers if it has scope or isolate scope 23 | for (i = 0; i < len; i += 1) { 24 | var el = angular.element(all[i]); 25 | data = el.data(); 26 | scope = data.$scope || data.$isolateScope; 27 | if (scope && scope.$$watchers) { 28 | if ( !test[ scope.$id ] ) { 29 | countScopeWatchers(scope, el); 30 | } 31 | } 32 | } 33 | console.log('this page has', count, 'angular watchers'); 34 | return count; 35 | }(window.angular)); 36 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.5.0", 5 | "author": "Gleb Bahmutov ", 6 | "bugs": { 7 | "url": "https://github.com/bahmutov/code-snippets/issues" 8 | }, 9 | "contributors": [], 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "eslint-rules": "0.4.3", 13 | "git-issues": "1.2.0", 14 | "grunt": "0.4.5", 15 | "grunt-cli": "0.1.13", 16 | "grunt-contrib-jshint": "0.11.3", 17 | "grunt-deps-ok": "0.8.0", 18 | "grunt-eslint": "2.1.0", 19 | "grunt-gh-pages": "1.0.0", 20 | "grunt-jscs": "1.2.0", 21 | "grunt-nice-package": "0.9.4", 22 | "grunt-npm2bower-sync": "0.9.1", 23 | "jshint-summary": "0.4.0", 24 | "matchdep": "1.0.0", 25 | "pre-git": "1.2.11" 26 | }, 27 | "engines": { 28 | "node": "> 0.10.*" 29 | }, 30 | "homepage": "https://github.com/bahmutov/code-snippets", 31 | "keywords": [ 32 | "testing", 33 | "test", 34 | "exploratory", 35 | "mock", 36 | "chrome", 37 | "devtools", 38 | "code", 39 | "snippets" 40 | ], 41 | "license": "MIT", 42 | "main": "first-paint.js", 43 | "repository": { 44 | "type": "git", 45 | "url": "git@github.com:bahmutov/code-snippets.git" 46 | }, 47 | "scripts": { 48 | "test": "grunt", 49 | "grunt": "grunt", 50 | "build": "grunt", 51 | "issues": "git-issues", 52 | "commit": "git-issues && commit-wizard" 53 | }, 54 | "config": { 55 | "pre-git": { 56 | "commit-msg": "validate-commit-msg", 57 | "pre-commit": [ 58 | "npm test", 59 | "npm version" 60 | ], 61 | "pre-push": [], 62 | "post-commit": [], 63 | "post-merge": [] 64 | } 65 | }, 66 | "czConfig": { 67 | "path": "node_modules/cz-conventional-changelog" 68 | } 69 | } -------------------------------------------------------------------------------- /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" : false, 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 12 | [code-snippets-icon]: https://nodei.co/npm/code-snippets.png?downloads=true 13 | [code-snippets-url]: https://npmjs.org/package/code-snippets 14 | [code-snippets-ci-image]: https://travis-ci.org/bahmutov/code-snippets.png?branch=master 15 | [code-snippets-ci-url]: https://travis-ci.org/bahmutov/code-snippets 16 | [code-snippets-dependencies-image]: https://david-dm.org/bahmutov/code-snippets.png 17 | [code-snippets-dependencies-url]: https://david-dm.org/bahmutov/code-snippets 18 | [code-snippets-devdependencies-image]: https://david-dm.org/bahmutov/code-snippets/dev-status.png 19 | [code-snippets-devdependencies-url]: https://david-dm.org/bahmutov/code-snippets#info=devDependencies 20 | [code-snippets-codacy-image]: https://www.codacy.com/project/badge/99acaf40b1f1483c80016eb31fbaef49 21 | [code-snippets-codacy-url]: https://www.codacy.com/public/bahmutov/code-snippets.git 22 | 23 | ![fist paint](https://raw.githubusercontent.com/bahmutov/code-snippets/master/first-paint-code-snippet.png) 24 | 25 | Read [Code Snippets tutorial][1], 26 | [Performance profiling using DevTools code snippets][2] and 27 | [How to improve Angular application performance using code snippets][3]. 28 | 29 | Note: code snippets do NOT have access to the full console API, for example no access to 30 | `console.monitor`. 31 | 32 | ## Snippets 33 | 34 | ### Security 35 | 36 | * [test-script-injection.js](test-script-injection.js) - tries to create a new 37 | inline script tag to test if page allows it. 38 | 39 | ### DOM and CPU generic performance 40 | 41 | * [boilerplate.js](boilerplate.js) - boilerplate for loading and running a remote code script 42 | (see [remote download](#remote-download)). 43 | * [first-paint.js](first-paint.js) - time from page reload to first visible contents. 44 | * [timing.js](timing.js) - Detailed page timing information, 45 | from [addyosmani/timing.js](https://github.com/addyosmani/timing.js). 46 | * [time-method-call.js](time-method-call.js) - measures single method call time. 47 | * [profile-method-call.js](profile-method-call.js) - profiles a single method call. 48 | * [profile-prototype-method.js](profile-prototype-method.js) - profiles a single method call 49 | that is on a prototype object, not on an instance. 50 | * [profile-separate-calls.js](profile-separate-calls.js) can profile actions where separate 51 | method calls start and stop the operation. 52 | 53 | ### Storage measurements 54 | 55 | * [local-storage-size.js](local-storage-size.js) - measures size of the strings in the `localStorage`. 56 | * [expensive-keys.js](expensive-keys.js) - measures how much space individual keys and their values 57 | take up in a collection of objects, read [Measuring Space Allocation][measure]. 58 | * [keys-vs-values.js](keys-vs-values.js) - measures length of keys vs length of values in an array. 59 | 60 | ### Angular performance 61 | 62 | * [ng-count-watchers.js](ng-count-watchers.js) - counts total watchers in the page. 63 | More watchers - slower digest cycle. 64 | * [ng-idle-apply-timing.js](ng-idle-apply-timing.js) - measures how long a digest cycle takes without 65 | any data changes. This measures purely how long all watched expressions take to compute and compare 66 | to previous values (dirty checking). 67 | * [ng-profile-scope-method.js](ng-profile-scope-method.js) - installs profile calls around a given 68 | scope method. When the method completes, the original non-instrumented version will be restored. 69 | The browser will have timeline and CPU profile. 70 | * [ng-run-digest-cycle.js](ng-run-digest-cycle.js) - triggers digest cycle starting with root scope. 71 | * [ng-profile-data-change.js](ng-profile-data-change.js) - changes data on the scope, runs digest cycle 72 | to profile listeners. 73 | * [ng-scope-size.js](ng-scope-size.js) - finds total size of all user objects attached to all scopes. 74 | Smaller data - faster copying and comparison during digest cycle. 75 | * [ng-find-scope-property.js](ng-find-scope-property.js) - finds all scopes that own a property 76 | with given name. 77 | * [ng-profile-local-digest.js](ng-profile-local-digest.js) - runs idle digest cycle starting at the scope 78 | that surrounds given selector. Useful to find parts of the page with expensive watchers. 79 | * [ng-find-expensive-digest.js](ng-find-expensive-digest.js) builds upon ng-profile-local-digest.js to measure 80 | digest duration for several selectors and print sorted table starting with the slowest digest duration. 81 | * [ng-monitor-digest-cycle.js](ng-monitor-digest-cycle.js) - prints a string every time a digest cycle runs. 82 | * [ng-count-digest-cycles.js](ng-count-digest-cycles.js) - counts number of full digest cycles (from the root scope) 83 | that run when a scope method executes. Useful because sometimes you can get away with just a local digest 84 | cycle, rather than a full update. See [Local Angular scopes](http://glebbahmutov.com/blog/local-angular-scopes/). 85 | * [ng-count-digest-cycle-simple.js](ng-count-digest-cycle-simple.js) - keeps counting number of times 86 | the digest cycle runs. 87 | * [ng-throw-error.js](ng-throw-error.js) throws an error from the digest cycle; useful for checking 88 | if your [exception handler](http://glebbahmutov.com/blog/catch-all-errors-in-angular-app/) is working. 89 | 90 | All snippets, including mine are distributed under MIT license. 91 | 92 | ## Updating local code snippets 93 | 94 | You can update local code snippets by downloading new versions from this github repository. 95 | Create a new code snippet and copy the source from [update-code-snippets.js](update-code-snippets.js). 96 | 97 | You will run this code snippet in an unusual way. First, open any web page, even an empty tab. 98 | Open the DevTools in **undocked** mode (Command+Option+I on Mac). Then open the DevTools **again**, 99 | *while focused* on the first DevTools. This will open the second DevTools instance with the source for the 100 | first DevTools panels. If you inspect the `localStorage` variable in the second DevTools window, you will 101 | find lots of interesting stuff, including all the code snippets in the `localStorage.scriptSnippets` property. 102 | 103 | Whenever you want to update the your local code snippets in the Chrome DevTools, execute the `update-code-snippets.js` 104 | snippet in the second DevTools instance. The update script looks at the your current code snippets and 105 | tries to download a file with same name from the code snippets github repository (via [RawGit][RawGit]). 106 | If the remote file has been downloaded successfully, it will replace the snippet. 107 | After all snippets are checked, reopen the DevTools to load the updated source code. 108 | 109 | ![update code snippets](images/update-code-snippets.png) 110 | 111 | Note, that only the latest source is downloaded, not any particular release. 112 | Also, only code snippets with names matching existing files in this repo are replaced. If you do not 113 | want to override a code snippet - just rename it, for example, remove the `.js` extension. 114 | 115 | ## Remote download a single script 116 | 117 | You can download and run a single snippet by using the following boilerplate 118 | (scripts are via downloaded via [RawGit][RawGit]) 119 | 120 | ```js 121 | (function firstPaintRemote() { 122 | // form rawGit proxy url 123 | var ghUrl = 'bahmutov/code-snippets/master/first-paint.js'; 124 | var rawUrl = 'https://rawgit.com/' + ghUrl; 125 | // download and run the script 126 | var head = document.getElementsByTagName('head')[0]; 127 | var script = document.createElement('script'); 128 | script.type = 'text/javascript'; 129 | script.src = rawUrl; 130 | head.appendChild(script); 131 | }()); 132 | ``` 133 | 134 | ![remote](https://raw.githubusercontent.com/bahmutov/code-snippets/master/first-paint-code-snippet-remote.png) 135 | 136 | ### Small print 137 | 138 | Author: Gleb Bahmutov © 2014 139 | 140 | * [@bahmutov](https://twitter.com/bahmutov) 141 | * [glebbahmutov.com](http://glebbahmutov.com) 142 | * [blog](http://glebbahmutov.com/blog/) 143 | 144 | License: MIT - do anything with the code, but don't blame me if it does not work. 145 | 146 | Spread the word: tweet, star on github, etc. 147 | 148 | Support: if you find any problems with this module, email / tweet / 149 | [open issue](https://github.com/bahmutov/code-snippets/issues?state=open) on Github 150 | 151 | [1]: http://glebbahmutov.com/blog/chrome-dev-tools-code-snippets/ 152 | [2]: http://glebbahmutov.com/blog/performance-profiling-using-devtools-code-snippets/ 153 | [3]: http://glebbahmutov.com/blog/improving-angular-web-app-performance-example/ 154 | [measure]: http://glebbahmutov.com/blog/measure-space-allocation/ 155 | [RawGit]: https://rawgit.com/ 156 | --------------------------------------------------------------------------------