├── icon16.png ├── icon48.png ├── icon128.png ├── .gitignore ├── main.html ├── background └── background.js ├── ddp-analyzer.html ├── manifest.json ├── ddp-analyzer.js ├── main.js ├── tracker-profiler.html ├── inject.js ├── tracker-profiler.js └── _locales └── en └── messages.json /icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slava/blaze-tools/HEAD/icon16.png -------------------------------------------------------------------------------- /icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slava/blaze-tools/HEAD/icon48.png -------------------------------------------------------------------------------- /icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slava/blaze-tools/HEAD/icon128.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .AppleDouble 3 | .LSOverride 4 | ._* 5 | .Spotlight-V100 6 | .Trashes 7 | [._]*.s[a-w][a-z] 8 | [._]s[a-w][a-z] 9 | *.un~ 10 | Session.vim 11 | .netrwhist 12 | *~ 13 | 14 | -------------------------------------------------------------------------------- /main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Main Page. Doesn't really contain anything. 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /background/background.js: -------------------------------------------------------------------------------- 1 | // var settings = new Store("settings", { 2 | // "sample_setting": "This is how you use Store.js to remember values" 3 | // }); 4 | 5 | 6 | //example of using a message handler from the inject scripts 7 | chrome.extension.onMessage.addListener( 8 | function(request, sender, sendResponse) { 9 | chrome.pageAction.show(sender.tab.id); 10 | sendResponse(); 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /ddp-analyzer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Blaze tools", 3 | "version": "0.0.1", 4 | "manifest_version": 2, 5 | "description": "An experimental Chrome extension", 6 | "homepage_url": "https://meteor.com", 7 | "icons": { 8 | "16": "icon16.png", 9 | "48": "icon48.png", 10 | "128": "icon128.png" 11 | }, 12 | "default_locale": "en", 13 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", 14 | "background": { 15 | "scripts": [ 16 | "background/background.js" 17 | ], 18 | "persistent": true 19 | }, 20 | "devtools_page": "main.html", 21 | "permissions": [ 22 | "tabs", 23 | "https://*/*", 24 | "http://*/*" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /ddp-analyzer.js: -------------------------------------------------------------------------------- 1 | ReactiveVar = Package['reactive-var'].ReactiveVar; 2 | LocalCollection = Package['minimongo'].LocalCollection; 3 | _ = Package['underscore']._; 4 | 5 | Connections = new LocalCollection(); 6 | Messages = new LocalCollection(); 7 | 8 | var handleMessage = function (o) { 9 | if (! Connections.findOne({ name: o.name })) { 10 | Connections.insert({ name: o.name }); 11 | } 12 | 13 | Messages.insert(_.extend(o, { 14 | ts: new Date 15 | })); 16 | }; 17 | 18 | setInterval(function () { 19 | inject('DDPAnalyzer.DevTools.getBuffered()', function (err, buffer) { 20 | if (err) { 21 | console.error(err); 22 | return; 23 | } 24 | 25 | for (var i = 0; i < buffer.length; i++) { 26 | handleMessage(buffer[i]); 27 | } 28 | }); 29 | }, 500); 30 | 31 | Template.main.helpers({ 32 | connections: function () { 33 | return Connections.find(); 34 | } 35 | }); 36 | 37 | Template.ddpMessages.helpers({ 38 | messages: function () { 39 | var connection = this.connection.name; 40 | return Messages.find({ name: connection }, { sort: { ts: -1 } }); 41 | } 42 | }); 43 | 44 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | chrome.devtools.panels.create( 2 | "Tracker Profiler", 3 | "icon128.png", 4 | "tracker-profiler.html", 5 | function (panel) { 6 | } 7 | ); 8 | chrome.devtools.panels.create( 9 | "DDP Analyzer", 10 | "icon128.png", 11 | "ddp-analyzer.html", 12 | function (panel) { 13 | } 14 | ); 15 | 16 | function getBlazeTmplInstForSelection () { 17 | if (! window.Blaze) 18 | return {}; 19 | 20 | try { Blaze.getView($0) } catch (err) { 21 | return {}; 22 | } 23 | 24 | var info = { 25 | viewInst: Blaze.getView($0), 26 | data: Blaze.getData($0), 27 | template: null, 28 | view: null 29 | }; 30 | 31 | for (var v = info.viewInst; !!v; v = v.parentView) { 32 | var m = v.name.match(/^Template\.(.*)/); 33 | if (!info.template && m && m[1]) { 34 | info.template = m[1]; 35 | } 36 | if (!info.view && !m) { 37 | info.view = v.name; 38 | } 39 | 40 | if (info.view && info.template) 41 | break; 42 | } 43 | 44 | return info; 45 | } 46 | 47 | chrome.devtools.panels.elements.createSidebarPane("Blaze", function (sidebar) { 48 | function updateSidebar () { 49 | sidebar.setExpression("(" + getBlazeTmplInstForSelection.toString() + ")()"); 50 | } 51 | // TODO: make this reactive 52 | chrome.devtools.panels.elements.onSelectionChanged.addListener(updateSidebar); 53 | 54 | updateSidebar(); 55 | }); 56 | 57 | -------------------------------------------------------------------------------- /tracker-profiler.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 32 | 33 | 34 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /inject.js: -------------------------------------------------------------------------------- 1 | inject = function (script, cb) { 2 | if (! window.chrome.devtools) { 3 | if (script.match(/start/)) 4 | cb(); 5 | else if (script.match(/stop/)) 6 | cb(null, stubData); 7 | return; 8 | } 9 | chrome.devtools.inspectedWindow.eval(script, function (res, exc) { 10 | if (exc && exc.isError) { 11 | cb(exc.description, null); 12 | } else if (exc && exc.isException) { 13 | cb(exc.value, null); 14 | } else 15 | cb(null, res); 16 | }); 17 | } 18 | 19 | var stubData = [{"aveTime":158,"totalTime":158,"recomputations":1,"funcName":"DynamicTemplate:materialize","_id":"937"},{"aveTime":22,"totalTime":22,"recomputations":1,"funcName":"DynamicTemplateanonymous","_id":"936"},{"aveTime":8,"totalTime":16,"recomputations":2,"funcName":"Template.menuItem:updater","_id":"607"},{"aveTime":9,"totalTime":9,"recomputations":1,"funcName":"onLocationChange","_id":"27"},{"aveTime":3.5,"totalTime":7,"recomputations":2,"funcName":"with:setData","_id":"31"},{"aveTime":1,"totalTime":2,"recomputations":2,"funcName":"Spacebars_withanonymous","_id":"82"},{"aveTime":1,"totalTime":2,"recomputations":2,"funcName":"lookup:getSetting:materialize","_id":"68"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"if:condition","_id":"4698"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"if:updater","_id":"4687"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"lookup:name:materialize","_id":"4684"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"Spacebars_withanonymous","_id":"4664"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"lookup:_:materialize","_id":"4653"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"lookup:_:materialize","_id":"4649"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"Template.menuItem:updater","_id":"4591"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"with:setData","_id":"4571"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"lookup:checkContext:materialize","_id":"4548"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"lookup:_:materialize","_id":"4537"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"with:setData","_id":"4515"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"lookup:_:materialize","_id":"4509"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"if:condition","_id":"4473"}]; 20 | 21 | 22 | -------------------------------------------------------------------------------- /tracker-profiler.js: -------------------------------------------------------------------------------- 1 | ReactiveVar = Package['reactive-var'].ReactiveVar; 2 | _ = Package['underscore']._; 3 | debug = new ReactiveVar(); 4 | started = new ReactiveVar(false); 5 | data = new ReactiveVar([]); 6 | sortProp = new ReactiveVar('totalTime'); 7 | 8 | Template.main.helpers({ 9 | debug: function () { 10 | return JSON.stringify(debug.get(), null, 2); 11 | }, 12 | started: function () { 13 | return started.get(); 14 | } 15 | }); 16 | 17 | var tp = 'Package["slava:tracker-profiler"].'; 18 | Template.main.events({ 19 | 'click #start': function () { 20 | inject(tp + 'TrackerProfiler.start()', function (err, res) { 21 | if (err) 22 | debug.set(err); 23 | else 24 | started.set(true); 25 | }); 26 | return false; 27 | }, 28 | 'click #stop': function () { 29 | inject(tp + 'TrackerProfiler.stop()', function (err, res) { 30 | if (err) { 31 | debug.set(err) 32 | } else { 33 | data.set(res); 34 | } 35 | }); 36 | 37 | started.set(false); 38 | return false; 39 | }, 40 | 'change input': function (e) { 41 | sortProp.set(e.target.id); 42 | return false; 43 | } 44 | }); 45 | 46 | var colors = _.shuffle(randomColors(1000)); 47 | var sum = function (a) { 48 | return _.reduce(a, function(memo, num){ return memo + num; }, 0); 49 | } 50 | 51 | var getData = function () { 52 | var res = data.get(); 53 | res = _.groupBy(res, 'funcName'); 54 | var i = 0; 55 | res = _.map(res, function (group, name) { 56 | return { 57 | name: name, 58 | values: _.pluck(group, sortProp.get()) 59 | }; 60 | }); 61 | res = _.sortBy(res, function (group) { 62 | return -sum(group.values); 63 | }); 64 | // XXX this hackery should be removed once we have @index 65 | _.each(res, function (x, i) { x.i = i; }); 66 | return res; 67 | }; 68 | Template.chart.helpers({ 69 | rows: function () { 70 | return getData(); 71 | }, 72 | offset: function () { 73 | return this.i * (60 + 5); 74 | }, 75 | maxSum: function () { 76 | return _.max(_.map(getData(), function (row) { 77 | return sum(row.values); 78 | })); 79 | } 80 | }); 81 | 82 | Template.row.helpers({ 83 | orderedValues: function () { 84 | return _.map(this.data.values, function (v, i) { 85 | return { 86 | value: v, 87 | i: i 88 | }; 89 | }); 90 | }, 91 | offset: function () { 92 | var p = Template.instance().data; 93 | return sum(p.data.values.slice(0, this.i)) / p.maxUnit * p.maxWidth; 94 | }, 95 | width: function () { 96 | var p = Template.instance().data; 97 | return this.value / p.maxUnit * p.maxWidth; 98 | }, 99 | color: function () { 100 | var p = Template.instance().data; 101 | return colors[this.i % colors.length]; 102 | } 103 | }); 104 | 105 | function randomColors(total) { 106 | var r = []; 107 | for (var x=0; xare planning to allow localization, right? You have no idea who will be using your extension! You have no idea who will be translating it! At least support the basics, it's not hard, and having the framework in place will let you transition much more easily later on." 16 | ,"description":"drive the point home. It's good for you." 17 | } 18 | ,"l10nFirstParagraph": { 19 | "message":"When the options page loads, elements decorated with data-l10n will automatically be localized!" 20 | ,"description":"inform that elements will be localized on load" 21 | } 22 | ,"l10nSecondParagraph": { 23 | "message":"If you need more complex localization, you can also define data-l10n-args. This should contain $containerType$ filled with $dataType$, which will be passed into Chrome's i18n API as $functionArgs$. In fact, this paragraph does just that, and wraps the args in mono-space font. Easy!" 24 | ,"description":"introduce the data-l10n-args attribute. End on a lame note." 25 | ,"placeholders": { 26 | "containerType": { 27 | "content":"$1" 28 | ,"example":"'array', 'list', or something similar" 29 | ,"description":"type of the args container" 30 | } 31 | ,"dataType": { 32 | "content":"$2" 33 | ,"example":"string" 34 | ,"description":"type of data in each array index" 35 | } 36 | ,"functionArgs": { 37 | "content":"$3" 38 | ,"example":"arguments" 39 | ,"description":"whatever you call what you pass into a function/method. args, params, etc." 40 | } 41 | } 42 | } 43 | ,"l10nThirdParagraph": { 44 | "message":"Message contents are passed right into innerHTML without processing - include any tags (or even scripts) that you feel like. If you have an input field, the placeholder will be set instead, and buttons will have the value attribute set." 45 | ,"description":"inform that we handle placeholders, buttons, and direct HTML input" 46 | } 47 | ,"l10nButtonsBefore": { 48 | "message":"Different types of buttons are handled as well. <button> elements have their html set:" 49 | } 50 | ,"l10nButton": { 51 | "message":"in a button" 52 | } 53 | ,"l10nButtonsBetween": { 54 | "message":"while <input type='submit'> and <input type='button'> get their 'value' set (note: no HTML):" 55 | } 56 | ,"l10nSubmit": { 57 | "message":"a submit value" 58 | } 59 | ,"l10nButtonsAfter": { 60 | "message":"Awesome, no?" 61 | } 62 | ,"l10nExtras": { 63 | "message":"You can even set data-l10n on things like the <title> tag, which lets you have translatable page titles, or fieldset <legend> tags, or anywhere else - the default Boil.localize() behavior will check every tag in the document, not just the body." 64 | ,"description":"inform about places which may not be obvious, like , etc" 65 | } 66 | } 67 | --------------------------------------------------------------------------------