b2g/chrome/content/devtools.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 'use strict';
     7 const DEVELOPER_HUD_LOG_PREFIX = 'DeveloperHUD';
     9 XPCOMUtils.defineLazyGetter(this, 'devtools', function() {
    10   const {devtools} = Cu.import('resource://gre/modules/devtools/Loader.jsm', {});
    11   return devtools;
    12 });
    14 XPCOMUtils.defineLazyGetter(this, 'DebuggerClient', function() {
    15   return Cu.import('resource://gre/modules/devtools/dbg-client.jsm', {}).DebuggerClient;
    16 });
    18 XPCOMUtils.defineLazyGetter(this, 'WebConsoleUtils', function() {
    19   return devtools.require('devtools/toolkit/webconsole/utils').Utils;
    20 });
    22 XPCOMUtils.defineLazyGetter(this, 'EventLoopLagFront', function() {
    23   return devtools.require('devtools/server/actors/eventlooplag').EventLoopLagFront;
    24 });
    26 XPCOMUtils.defineLazyGetter(this, 'MemoryFront', function() {
    27   return devtools.require('devtools/server/actors/memory').MemoryFront;
    28 });
    31 /**
    32  * The Developer HUD is an on-device developer tool that displays widgets,
    33  * showing visual debug information about apps. Each widget corresponds to a
    34  * metric as tracked by a metric watcher (e.g. consoleWatcher).
    35  */
    36 let developerHUD = {
    38   _targets: new Map(),
    39   _frames: new Map(),
    40   _client: null,
    41   _conn: null,
    42   _watchers: [],
    43   _logging: true,
    45   /**
    46    * This method registers a metric watcher that will watch one or more metrics
    47    * on app frames that are being tracked. A watcher must implement the
    48    * `trackTarget(target)` and `untrackTarget(target)` methods, register
    49    * observed metrics with `target.register(metric)`, and keep them up-to-date
    50    * with `target.update(metric, message)` when necessary.
    51    */
    52   registerWatcher: function dwp_registerWatcher(watcher) {
    53     this._watchers.unshift(watcher);
    54   },
    56   init: function dwp_init() {
    57     if (this._client)
    58       return;
    60     if (!DebuggerServer.initialized) {
    61       RemoteDebugger.start();
    62     }
    64     // We instantiate a local debugger connection so that watchers can use our
    65     // DebuggerClient to send requests to tab actors (e.g. the consoleActor).
    66     // Note the special usage of the private _serverConnection, which we need
    67     // to call connectToChild and set up child process actors on a frame we
    68     // intend to track. These actors will use the connection to communicate with
    69     // our DebuggerServer in the parent process.
    70     let transport = DebuggerServer.connectPipe();
    71     this._conn = transport._serverConnection;
    72     this._client = new DebuggerClient(transport);
    74     for (let w of this._watchers) {
    75       if (w.init) {
    76         w.init(this._client);
    77       }
    78     }
    80     Services.obs.addObserver(this, 'remote-browser-shown', false);
    81     Services.obs.addObserver(this, 'inprocess-browser-shown', false);
    82     Services.obs.addObserver(this, 'message-manager-disconnect', false);
    84     let systemapp = document.querySelector('#systemapp');
    85     this.trackFrame(systemapp);
    87     let frames = systemapp.contentWindow.document.querySelectorAll('iframe[mozapp]');
    88     for (let frame of frames) {
    89       this.trackFrame(frame);
    90     }
    92     SettingsListener.observe('hud.logging', this._logging, enabled => {
    93       this._logging = enabled;
    94     });
    95   },
    97   uninit: function dwp_uninit() {
    98     if (!this._client)
    99       return;
   101     for (let frame of this._targets.keys()) {
   102       this.untrackFrame(frame);
   103     }
   105     Services.obs.removeObserver(this, 'remote-browser-shown');
   106     Services.obs.removeObserver(this, 'inprocess-browser-shown');
   107     Services.obs.removeObserver(this, 'message-manager-disconnect');
   109     this._client.close();
   110     delete this._client;
   111   },
   113   /**
   114    * This method will ask all registered watchers to track and update metrics
   115    * on an app frame.
   116    */
   117   trackFrame: function dwp_trackFrame(frame) {
   118     if (this._targets.has(frame))
   119       return;
   121     DebuggerServer.connectToChild(this._conn, frame).then(actor => {
   122       let target = new Target(frame, actor);
   123       this._targets.set(frame, target);
   125       for (let w of this._watchers) {
   126         w.trackTarget(target);
   127       }
   128     });
   129   },
   131   untrackFrame: function dwp_untrackFrame(frame) {
   132     let target = this._targets.get(frame);
   133     if (target) {
   134       for (let w of this._watchers) {
   135         w.untrackTarget(target);
   136       }
   138       target.destroy();
   139       this._targets.delete(frame);
   140     }
   141   },
   143   observe: function dwp_observe(subject, topic, data) {
   144     if (!this._client)
   145       return;
   147     let frame;
   149     switch(topic) {
   151       // listen for frame creation in OOP (device) as well as in parent process (b2g desktop)
   152       case 'remote-browser-shown':
   153       case 'inprocess-browser-shown':
   154         let frameLoader = subject;
   155         // get a ref to the app <iframe>
   156         frameLoader.QueryInterface(Ci.nsIFrameLoader);
   157         // Ignore notifications that aren't from a BrowserOrApp
   158         if (!frameLoader.ownerIsBrowserOrAppFrame) {
   159           return;
   160         }
   161         frame = frameLoader.ownerElement;
   162         if (!frame.appManifestURL) // Ignore all frames but app frames
   163           return;
   164         this.trackFrame(frame);
   165         this._frames.set(frameLoader.messageManager, frame);
   166         break;
   168       // Every time an iframe is destroyed, its message manager also is
   169       case 'message-manager-disconnect':
   170         let mm = subject;
   171         frame = this._frames.get(mm);
   172         if (!frame)
   173           return;
   174         this.untrackFrame(frame);
   175         this._frames.delete(mm);
   176         break;
   177     }
   178   },
   180   log: function dwp_log(message) {
   181     if (this._logging) {
   182       dump(DEVELOPER_HUD_LOG_PREFIX + ': ' + message + '\n');
   183     }
   184   }
   186 };
   189 /**
   190  * A Target object represents all there is to know about a Firefox OS app frame
   191  * that is being tracked, e.g. a pointer to the frame, current values of watched
   192  * metrics, and how to notify the front-end when metrics have changed.
   193  */
   194 function Target(frame, actor) {
   195   this.frame = frame;
   196   this.actor = actor;
   197   this.metrics = new Map();
   198 }
   200 Target.prototype = {
   202   /**
   203    * Register a metric that can later be updated. Does not update the front-end.
   204    */
   205   register: function target_register(metric) {
   206     this.metrics.set(metric, 0);
   207   },
   209   /**
   210    * Modify one of a target's metrics, and send out an event to notify relevant
   211    * parties (e.g. the developer HUD, automated tests, etc).
   212    */
   213   update: function target_update(metric, message) {
   214     if (!metric.name) {
   215       throw new Error('Missing metric.name');
   216     }
   218     if (!metric.value) {
   219       metric.value = 0;
   220     }
   222     let metrics = this.metrics;
   223     if (metrics) {
   224       metrics.set(metric.name, metric.value);
   225     }
   227     let data = {
   228       metrics: [], // FIXME(Bug 982066) Remove this field.
   229       manifest: this.frame.appManifestURL,
   230       metric: metric,
   231       message: message
   232     };
   234     // FIXME(Bug 982066) Remove this loop.
   235     if (metrics && metrics.size > 0) {
   236       for (let name of metrics.keys()) {
   237         data.metrics.push({name: name, value: metrics.get(name)});
   238       }
   239     }
   241     if (message) {
   242       developerHUD.log('[' + data.manifest + '] ' + data.message);
   243     }
   244     this._send(data);
   245   },
   247   /**
   248    * Nicer way to call update() when the metric value is a number that needs
   249    * to be incremented.
   250    */
   251   bump: function target_bump(metric, message) {
   252     metric.value = (this.metrics.get(metric.name) || 0) + 1;
   253     this.update(metric, message);
   254   },
   256   /**
   257    * Void a metric value and make sure it isn't displayed on the front-end
   258    * anymore.
   259    */
   260   clear: function target_clear(metric) {
   261     metric.value = 0;
   262     this.update(metric);
   263   },
   265   /**
   266    * Tear everything down, including the front-end by sending a message without
   267    * widgets.
   268    */
   269   destroy: function target_destroy() {
   270     delete this.metrics;
   271     this._send({});
   272   },
   274   _send: function target_send(data) {
   275     shell.sendEvent(this.frame, 'developer-hud-update', Cu.cloneInto(data, this.frame));
   276   }
   278 };
   281 /**
   282  * The Console Watcher tracks the following metrics in apps: reflows, warnings,
   283  * and errors, with security errors reported separately.
   284  */
   285 let consoleWatcher = {
   287   _client: null,
   288   _targets: new Map(),
   289   _watching: {
   290     reflows: false,
   291     warnings: false,
   292     errors: false,
   293     security: false
   294   },
   295   _security: [
   296     'Mixed Content Blocker',
   297     'Mixed Content Message',
   298     'CSP',
   299     'Invalid HSTS Headers',
   300     'Insecure Password Field',
   301     'SSL',
   302     'CORS'
   303   ],
   305   init: function cw_init(client) {
   306     this._client = client;
   307     this.consoleListener = this.consoleListener.bind(this);
   309     let watching = this._watching;
   311     for (let key in watching) {
   312       let metric = key;
   313       SettingsListener.observe('hud.' + metric, watching[metric], watch => {
   314         // Watch or unwatch the metric.
   315         if (watching[metric] = watch) {
   316           return;
   317         }
   319         // If unwatched, remove any existing widgets for that metric.
   320         for (let target of this._targets.values()) {
   321           target.clear({name: metric});
   322         }
   323       });
   324     }
   326     client.addListener('logMessage', this.consoleListener);
   327     client.addListener('pageError', this.consoleListener);
   328     client.addListener('consoleAPICall', this.consoleListener);
   329     client.addListener('reflowActivity', this.consoleListener);
   330   },
   332   trackTarget: function cw_trackTarget(target) {
   333     target.register('reflows');
   334     target.register('warnings');
   335     target.register('errors');
   336     target.register('security');
   338     this._client.request({
   339       to: target.actor.consoleActor,
   340       type: 'startListeners',
   341       listeners: ['LogMessage', 'PageError', 'ConsoleAPI', 'ReflowActivity']
   342     }, (res) => {
   343       this._targets.set(target.actor.consoleActor, target);
   344     });
   345   },
   347   untrackTarget: function cw_untrackTarget(target) {
   348     this._client.request({
   349       to: target.actor.consoleActor,
   350       type: 'stopListeners',
   351       listeners: ['LogMessage', 'PageError', 'ConsoleAPI', 'ReflowActivity']
   352     }, (res) => { });
   354     this._targets.delete(target.actor.consoleActor);
   355   },
   357   consoleListener: function cw_consoleListener(type, packet) {
   358     let target = this._targets.get(packet.from);
   359     let metric = {};
   360     let output = '';
   362     switch (packet.type) {
   364       case 'pageError':
   365         let pageError = packet.pageError;
   367         if (pageError.warning || pageError.strict) {
   368           metric.name = 'warnings';
   369           output += 'warning (';
   370         } else {
   371           metric.name = 'errors';
   372           output += 'error (';
   373         }
   375         if (this._security.indexOf(pageError.category) > -1) {
   376           metric.name = 'security';
   377         }
   379         let {errorMessage, sourceName, category, lineNumber, columnNumber} = pageError;
   380         output += category + '): "' + (errorMessage.initial || errorMessage) +
   381           '" in ' + sourceName + ':' + lineNumber + ':' + columnNumber;
   382         break;
   384       case 'consoleAPICall':
   385         switch (packet.message.level) {
   387           case 'error':
   388             metric.name = 'errors';
   389             output += 'error (console)';
   390             break;
   392           case 'warn':
   393             metric.name = 'warnings';
   394             output += 'warning (console)';
   395             break;
   397           default:
   398             return;
   399         }
   400         break;
   402       case 'reflowActivity':
   403         metric.name = 'reflows';
   405         let {start, end, sourceURL, interruptible} = packet;
   406         metric.interruptible = interruptible;
   407         let duration = Math.round((end - start) * 100) / 100;
   408         output += 'reflow: ' + duration + 'ms';
   409         if (sourceURL) {
   410           output += ' ' + this.formatSourceURL(packet);
   411         }
   412         break;
   414       default:
   415         return;
   416     }
   418     if (!this._watching[metric.name]) {
   419       return;
   420     }
   422     target.bump(metric, output);
   423   },
   425   formatSourceURL: function cw_formatSourceURL(packet) {
   426     // Abbreviate source URL
   427     let source = WebConsoleUtils.abbreviateSourceURL(packet.sourceURL);
   429     // Add function name and line number
   430     let {functionName, sourceLine} = packet;
   431     source = 'in ' + (functionName || '<anonymousFunction>') +
   432       ', ' + source + ':' + sourceLine;
   434     return source;
   435   }
   436 };
   437 developerHUD.registerWatcher(consoleWatcher);
   440 let eventLoopLagWatcher = {
   441   _client: null,
   442   _fronts: new Map(),
   443   _active: false,
   445   init: function(client) {
   446     this._client = client;
   448     SettingsListener.observe('hud.jank', false, this.settingsListener.bind(this));
   449   },
   451   settingsListener: function(value) {
   452     if (this._active == value) {
   453       return;
   454     }
   455     this._active = value;
   457     // Toggle the state of existing fronts.
   458     let fronts = this._fronts;
   459     for (let target of fronts.keys()) {
   460       if (value) {
   461         fronts.get(target).start();
   462       } else {
   463         fronts.get(target).stop();
   464         target.clear({name: 'jank'});
   465       }
   466     }
   467   },
   469   trackTarget: function(target) {
   470     target.register('jank');
   472     let front = new EventLoopLagFront(this._client, target.actor);
   473     this._fronts.set(target, front);
   475     front.on('event-loop-lag', time => {
   476       target.update({name: 'jank', value: time}, 'jank: ' + time + 'ms');
   477     });
   479     if (this._active) {
   480       front.start();
   481     }
   482   },
   484   untrackTarget: function(target) {
   485     let fronts = this._fronts;
   486     if (fronts.has(target)) {
   487       fronts.get(target).destroy();
   488       fronts.delete(target);
   489     }
   490   }
   491 };
   492 developerHUD.registerWatcher(eventLoopLagWatcher);
   495 /**
   496  * The Memory Watcher uses devtools actors to track memory usage.
   497  */
   498 let memoryWatcher = {
   500   _client: null,
   501   _fronts: new Map(),
   502   _timers: new Map(),
   503   _watching: {
   504     jsobjects: false,
   505     jsstrings: false,
   506     jsother: false,
   507     dom: false,
   508     style: false,
   509     other: false
   510   },
   511   _active: false,
   513   init: function mw_init(client) {
   514     this._client = client;
   515     let watching = this._watching;
   517     for (let key in watching) {
   518       let category = key;
   519       SettingsListener.observe('hud.' + category, false, watch => {
   520         watching[category] = watch;
   521       });
   522     }
   524     SettingsListener.observe('hud.appmemory', false, enabled => {
   525       if (this._active = enabled) {
   526         for (let target of this._fronts.keys()) {
   527           this.measure(target);
   528         }
   529       } else {
   530         for (let target of this._fronts.keys()) {
   531           clearTimeout(this._timers.get(target));
   532           target.clear({name: 'memory'});
   533         }
   534       }
   535     });
   536   },
   538   measure: function mw_measure(target) {
   540     // TODO Also track USS (bug #976024).
   542     let watch = this._watching;
   543     let front = this._fronts.get(target);
   545     front.measure().then((data) => {
   547       let total = 0;
   548       if (watch.jsobjects) {
   549         total += parseInt(data.jsObjectsSize);
   550       }
   551       if (watch.jsstrings) {
   552         total += parseInt(data.jsStringsSize);
   553       }
   554       if (watch.jsother) {
   555         total += parseInt(data.jsOtherSize);
   556       }
   557       if (watch.dom) {
   558         total += parseInt(data.domSize);
   559       }
   560       if (watch.style) {
   561         total += parseInt(data.styleSize);
   562       }
   563       if (watch.other) {
   564         total += parseInt(data.otherSize);
   565       }
   566       // TODO Also count images size (bug #976007).
   568       target.update({name: 'memory', value: total});
   569       let duration = parseInt(data.jsMilliseconds) + parseInt(data.nonJSMilliseconds);
   570       let timer = setTimeout(() => this.measure(target), 100 * duration);
   571       this._timers.set(target, timer);
   572     }, (err) => {
   573       console.error(err);
   574     });
   575   },
   577   trackTarget: function mw_trackTarget(target) {
   578     target.register('uss');
   579     target.register('memory');
   580     this._fronts.set(target, MemoryFront(this._client, target.actor));
   581     if (this._active) {
   582       this.measure(target);
   583     }
   584   },
   586   untrackTarget: function mw_untrackTarget(target) {
   587     let front = this._fronts.get(target);
   588     if (front) {
   589       front.destroy();
   590       clearTimeout(this._timers.get(target));
   591       this._fronts.delete(target);
   592       this._timers.delete(target);
   593     }
   594   }
   595 };
   596 developerHUD.registerWatcher(memoryWatcher);

mercurial