Home | History | Annotate | Download | only in net_internals
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * Dictionary of constants (Initialized soon after loading by data from browser,
      7  * updated on load log).  The *Types dictionaries map strings to numeric IDs,
      8  * while the *TypeNames are the other way around.
      9  */
     10 var EventType = null;
     11 var EventTypeNames = null;
     12 var EventPhase = null;
     13 var EventSourceType = null;
     14 var EventSourceTypeNames = null;
     15 var LogLevelType = null;
     16 var ClientInfo = null;
     17 var NetError = null;
     18 var QuicError = null;
     19 var QuicRstStreamError = null;
     20 var LoadFlag = null;
     21 var LoadState = null;
     22 var AddressFamily = null;
     23 
     24 /**
     25  * Dictionary of all constants, used for saving log files.
     26  */
     27 var Constants = null;
     28 
     29 /**
     30  * Object to communicate between the renderer and the browser.
     31  * @type {!BrowserBridge}
     32  */
     33 var g_browser = null;
     34 
     35 /**
     36  * This class is the root view object of the page.  It owns all the other
     37  * views, and manages switching between them.  It is also responsible for
     38  * initializing the views and the BrowserBridge.
     39  */
     40 var MainView = (function() {
     41   'use strict';
     42 
     43   // We inherit from WindowView
     44   var superClass = WindowView;
     45 
     46   /**
     47    * Main entry point. Called once the page has loaded.
     48    *  @constructor
     49    */
     50   function MainView() {
     51     assertFirstConstructorCall(MainView);
     52 
     53     if (hasTouchScreen())
     54       document.body.classList.add('touch');
     55 
     56     // This must be initialized before the tabs, so they can register as
     57     // observers.
     58     g_browser = BrowserBridge.getInstance();
     59 
     60     // This must be the first constants observer, so other constants observers
     61     // can safely use the globals, rather than depending on walking through
     62     // the constants themselves.
     63     g_browser.addConstantsObserver(new ConstantsObserver());
     64 
     65     // Create the tab switcher.
     66     this.initTabs_();
     67 
     68     // Cut out a small vertical strip at the top of the window, to display
     69     // a high level status (i.e. if we are capturing events, or displaying a
     70     // log file). Below it we will position the main tabs and their content
     71     // area.
     72     this.topBarView_ = TopBarView.getInstance(this);
     73     var verticalSplitView = new VerticalSplitView(
     74         this.topBarView_, this.tabSwitcher_);
     75 
     76     superClass.call(this, verticalSplitView);
     77 
     78     // Trigger initial layout.
     79     this.resetGeometry();
     80 
     81     window.onhashchange = this.onUrlHashChange_.bind(this);
     82 
     83     // Select the initial view based on the current URL.
     84     window.onhashchange();
     85 
     86     // Tell the browser that we are ready to start receiving log events.
     87     this.topBarView_.switchToSubView('capture');
     88     g_browser.sendReady();
     89   }
     90 
     91   cr.addSingletonGetter(MainView);
     92 
     93   // Tracks if we're viewing a loaded log file, so views can behave
     94   // appropriately.  Global so safe to call during construction.
     95   var isViewingLoadedLog = false;
     96 
     97   MainView.isViewingLoadedLog = function() {
     98     return isViewingLoadedLog;
     99   };
    100 
    101   MainView.prototype = {
    102     // Inherit the superclass's methods.
    103     __proto__: superClass.prototype,
    104 
    105     // This is exposed both so the log import/export code can enumerate all the
    106     // tabs, and for testing.
    107     tabSwitcher: function() {
    108       return this.tabSwitcher_;
    109     },
    110 
    111     /**
    112      * Prevents receiving/sending events to/from the browser, so loaded data
    113      * will not be mixed with current Chrome state.  Also hides any interactive
    114      * HTML elements that send messages to the browser.  Cannot be undone
    115      * without reloading the page.  Must be called before passing loaded data
    116      * to the individual views.
    117      *
    118      * @param {string} opt_fileName The name of the log file that has been
    119      *     loaded, if we're loading a log file.
    120      */
    121     onLoadLog: function(opt_fileName) {
    122       isViewingLoadedLog = true;
    123 
    124       this.stopCapturing();
    125       if (opt_fileName != undefined) {
    126         // If there's a file name, a log file was loaded, so swap out the status
    127         // bar to indicate we're no longer capturing events.  Also disable
    128         // hiding cookies, so if the log dump has them, they'll be displayed.
    129         this.topBarView_.switchToSubView('loaded').setFileName(opt_fileName);
    130         $(ExportView.PRIVACY_STRIPPING_CHECKBOX_ID).checked = false;
    131         SourceTracker.getInstance().setPrivacyStripping(false);
    132       } else {
    133         // Otherwise, the "Stop Capturing" button was presumably pressed.
    134         // Don't disable hiding cookies, so created log dumps won't have them,
    135         // unless the user toggles the option.
    136         this.topBarView_.switchToSubView('halted');
    137       }
    138     },
    139 
    140     switchToViewOnlyMode: function() {
    141       // Since this won't be dumped to a file, we don't want to remove
    142       // cookies and credentials.
    143       log_util.createLogDumpAsync('', log_util.loadLogFile, false);
    144     },
    145 
    146     stopCapturing: function() {
    147       g_browser.disable();
    148       document.styleSheets[0].insertRule(
    149           '.hide-when-not-capturing { display: none; }');
    150     },
    151 
    152     initTabs_: function() {
    153       this.tabIdToHash_ = {};
    154       this.hashToTabId_ = {};
    155 
    156       this.tabSwitcher_ = new TabSwitcherView(
    157           $(TopBarView.TAB_DROPDOWN_MENU_ID),
    158           this.onTabSwitched_.bind(this));
    159 
    160       // Helper function to add a tab given the class for a view singleton.
    161       var addTab = function(viewClass) {
    162         var tabId = viewClass.TAB_ID;
    163         var tabHash = viewClass.TAB_HASH;
    164         var tabName = viewClass.TAB_NAME;
    165         var view = viewClass.getInstance();
    166 
    167         if (!tabId || !view || !tabHash || !tabName) {
    168           throw Error('Invalid view class for tab');
    169         }
    170 
    171         if (tabHash.charAt(0) != '#') {
    172           throw Error('Tab hashes must start with a #');
    173         }
    174 
    175         this.tabSwitcher_.addTab(tabId, view, tabName);
    176         this.tabIdToHash_[tabId] = tabHash;
    177         this.hashToTabId_[tabHash] = tabId;
    178       }.bind(this);
    179 
    180       // Populate the main tabs.  Even tabs that don't contain information for
    181       // the running OS should be created, so they can load log dumps from other
    182       // OSes.
    183       addTab(CaptureView);
    184       addTab(ExportView);
    185       addTab(ImportView);
    186       addTab(ProxyView);
    187       addTab(EventsView);
    188       addTab(WaterfallView);
    189       addTab(TimelineView);
    190       addTab(DnsView);
    191       addTab(SocketsView);
    192       addTab(SpdyView);
    193       addTab(QuicView);
    194       addTab(HttpPipelineView);
    195       addTab(HttpCacheView);
    196       addTab(ModulesView);
    197       addTab(TestView);
    198       addTab(CrosLogAnalyzerView);
    199       addTab(HSTSView);
    200       addTab(LogsView);
    201       addTab(BandwidthView);
    202       addTab(PrerenderView);
    203       addTab(CrosView);
    204 
    205       this.tabSwitcher_.showMenuItem(LogsView.TAB_ID, cr.isChromeOS);
    206       this.tabSwitcher_.showMenuItem(CrosView.TAB_ID, cr.isChromeOS);
    207       this.tabSwitcher_.showMenuItem(CrosLogAnalyzerView.TAB_ID, cr.isChromeOS);
    208     },
    209 
    210     /**
    211      * This function is called by the tab switcher when the current tab has been
    212      * changed. It will update the current URL to reflect the new active tab,
    213      * so the back can be used to return to previous view.
    214      */
    215     onTabSwitched_: function(oldTabId, newTabId) {
    216       // Update data needed by newly active tab, as it may be
    217       // significantly out of date.
    218       if (g_browser)
    219         g_browser.checkForUpdatedInfo();
    220 
    221       // Change the URL to match the new tab.
    222 
    223       var newTabHash = this.tabIdToHash_[newTabId];
    224       var parsed = parseUrlHash_(window.location.hash);
    225       if (parsed.tabHash != newTabHash) {
    226         window.location.hash = newTabHash;
    227       }
    228     },
    229 
    230     onUrlHashChange_: function() {
    231       var parsed = parseUrlHash_(window.location.hash);
    232 
    233       if (!parsed)
    234         return;
    235 
    236       if (!parsed.tabHash) {
    237         // Default to the export tab.
    238         parsed.tabHash = ExportView.TAB_HASH;
    239       }
    240 
    241       var tabId = this.hashToTabId_[parsed.tabHash];
    242 
    243       if (tabId) {
    244         this.tabSwitcher_.switchToTab(tabId);
    245         if (parsed.parameters) {
    246           var view = this.tabSwitcher_.getTabView(tabId);
    247           view.setParameters(parsed.parameters);
    248         }
    249       }
    250     },
    251 
    252   };
    253 
    254   /**
    255    * Takes the current hash in form of "#tab&param1=value1&param2=value2&..."
    256    * and parses it into a dictionary.
    257    *
    258    * Parameters and values are decoded with decodeURIComponent().
    259    */
    260   function parseUrlHash_(hash) {
    261     var parameters = hash.split('&');
    262 
    263     var tabHash = parameters[0];
    264     if (tabHash == '' || tabHash == '#') {
    265       tabHash = undefined;
    266     }
    267 
    268     // Split each string except the first around the '='.
    269     var paramDict = null;
    270     for (var i = 1; i < parameters.length; i++) {
    271       var paramStrings = parameters[i].split('=');
    272       if (paramStrings.length != 2)
    273         continue;
    274       if (paramDict == null)
    275         paramDict = {};
    276       var key = decodeURIComponent(paramStrings[0]);
    277       var value = decodeURIComponent(paramStrings[1]);
    278       paramDict[key] = value;
    279     }
    280 
    281     return {tabHash: tabHash, parameters: paramDict};
    282   }
    283 
    284   return MainView;
    285 })();
    286 
    287 function ConstantsObserver() {}
    288 
    289 /**
    290  * Loads all constants from |constants|.  On failure, global dictionaries are
    291  * not modifed.
    292  * @param {Object} receivedConstants The map of received constants.
    293  */
    294 ConstantsObserver.prototype.onReceivedConstants = function(receivedConstants) {
    295   if (!areValidConstants(receivedConstants))
    296     return;
    297 
    298   Constants = receivedConstants;
    299 
    300   EventType = Constants.logEventTypes;
    301   EventTypeNames = makeInverseMap(EventType);
    302   EventPhase = Constants.logEventPhase;
    303   EventSourceType = Constants.logSourceType;
    304   EventSourceTypeNames = makeInverseMap(EventSourceType);
    305   LogLevelType = Constants.logLevelType;
    306   ClientInfo = Constants.clientInfo;
    307   LoadFlag = Constants.loadFlag;
    308   NetError = Constants.netError;
    309   QuicError = Constants.quicError;
    310   QuicRstStreamError = Constants.quicRstStreamError;
    311   AddressFamily = Constants.addressFamily;
    312   LoadState = Constants.loadState;
    313 
    314   timeutil.setTimeTickOffset(Constants.timeTickOffset);
    315 };
    316 
    317 /**
    318  * Returns true if it's given a valid-looking constants object.
    319  * @param {Object} receivedConstants The received map of constants.
    320  * @return {boolean} True if the |receivedConstants| object appears valid.
    321  */
    322 function areValidConstants(receivedConstants) {
    323   return typeof(receivedConstants) == 'object' &&
    324          typeof(receivedConstants.logEventTypes) == 'object' &&
    325          typeof(receivedConstants.clientInfo) == 'object' &&
    326          typeof(receivedConstants.logEventPhase) == 'object' &&
    327          typeof(receivedConstants.logSourceType) == 'object' &&
    328          typeof(receivedConstants.logLevelType) == 'object' &&
    329          typeof(receivedConstants.loadFlag) == 'object' &&
    330          typeof(receivedConstants.netError) == 'object' &&
    331          typeof(receivedConstants.addressFamily) == 'object' &&
    332          typeof(receivedConstants.timeTickOffset) == 'string' &&
    333          typeof(receivedConstants.logFormatVersion) == 'number';
    334 }
    335 
    336 /**
    337  * Returns the name for netError.
    338  *
    339  * Example: netErrorToString(-105) should return
    340  * "ERR_NAME_NOT_RESOLVED".
    341  * @param {number} netError The net error code.
    342  * @return {string} The name of the given error.
    343  */
    344 function netErrorToString(netError) {
    345   var str = getKeyWithValue(NetError, netError);
    346   if (str == '?')
    347     return str;
    348   return 'ERR_' + str;
    349 }
    350 
    351 /**
    352  * Returns the name for quicError.
    353  *
    354  * Example: quicErrorToString(25) should return
    355  * "TIMED_OUT".
    356  * @param {number} quicError The QUIC error code.
    357  * @return {string} The name of the given error.
    358  */
    359 function quicErrorToString(quicError) {
    360   return getKeyWithValue(QuicError, quicError);
    361 }
    362 
    363 /**
    364  * Returns the name for quicRstStreamError.
    365  *
    366  * Example: quicRstStreamErrorToString(3) should return
    367  * "BAD_APPLICATION_PAYLOAD".
    368  * @param {number} quicRstStreamError The QUIC RST_STREAM error code.
    369  * @return {string} The name of the given error.
    370  */
    371 function quicRstStreamErrorToString(quicRstStreamError) {
    372   return getKeyWithValue(QuicRstStreamError, quicRstStreamError);
    373 }
    374 
    375 /**
    376  * Returns a string representation of |family|.
    377  * @param {number} family An AddressFamily
    378  * @return {string} A representation of the given family.
    379  */
    380 function addressFamilyToString(family) {
    381   var str = getKeyWithValue(AddressFamily, family);
    382   // All the address family start with ADDRESS_FAMILY_*.
    383   // Strip that prefix since it is redundant and only clutters the output.
    384   return str.replace(/^ADDRESS_FAMILY_/, '');
    385 }
    386