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; }', 0); 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(CrosLogVisualizerView); 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(CrosLogVisualizerView.TAB_ID, 208 cr.isChromeOS); 209 }, 210 211 /** 212 * This function is called by the tab switcher when the current tab has been 213 * changed. It will update the current URL to reflect the new active tab, 214 * so the back can be used to return to previous view. 215 */ 216 onTabSwitched_: function(oldTabId, newTabId) { 217 // Update data needed by newly active tab, as it may be 218 // significantly out of date. 219 if (g_browser) 220 g_browser.checkForUpdatedInfo(); 221 222 // Change the URL to match the new tab. 223 224 var newTabHash = this.tabIdToHash_[newTabId]; 225 var parsed = parseUrlHash_(window.location.hash); 226 if (parsed.tabHash != newTabHash) { 227 window.location.hash = newTabHash; 228 } 229 }, 230 231 onUrlHashChange_: function() { 232 var parsed = parseUrlHash_(window.location.hash); 233 234 if (!parsed) 235 return; 236 237 if (!parsed.tabHash) { 238 // Default to the export tab. 239 parsed.tabHash = ExportView.TAB_HASH; 240 } 241 242 var tabId = this.hashToTabId_[parsed.tabHash]; 243 244 if (tabId) { 245 this.tabSwitcher_.switchToTab(tabId); 246 if (parsed.parameters) { 247 var view = this.tabSwitcher_.getTabView(tabId); 248 view.setParameters(parsed.parameters); 249 } 250 } 251 }, 252 253 }; 254 255 /** 256 * Takes the current hash in form of "#tab¶m1=value1¶m2=value2&..." 257 * and parses it into a dictionary. 258 * 259 * Parameters and values are decoded with decodeURIComponent(). 260 */ 261 function parseUrlHash_(hash) { 262 var parameters = hash.split('&'); 263 264 var tabHash = parameters[0]; 265 if (tabHash == '' || tabHash == '#') { 266 tabHash = undefined; 267 } 268 269 // Split each string except the first around the '='. 270 var paramDict = null; 271 for (var i = 1; i < parameters.length; i++) { 272 var paramStrings = parameters[i].split('='); 273 if (paramStrings.length != 2) 274 continue; 275 if (paramDict == null) 276 paramDict = {}; 277 var key = decodeURIComponent(paramStrings[0]); 278 var value = decodeURIComponent(paramStrings[1]); 279 paramDict[key] = value; 280 } 281 282 return {tabHash: tabHash, parameters: paramDict}; 283 } 284 285 return MainView; 286 })(); 287 288 function ConstantsObserver() {} 289 290 /** 291 * Loads all constants from |constants|. On failure, global dictionaries are 292 * not modifed. 293 * @param {Object} receivedConstants The map of received constants. 294 */ 295 ConstantsObserver.prototype.onReceivedConstants = function(receivedConstants) { 296 if (!areValidConstants(receivedConstants)) 297 return; 298 299 Constants = receivedConstants; 300 301 EventType = Constants.logEventTypes; 302 EventTypeNames = makeInverseMap(EventType); 303 EventPhase = Constants.logEventPhase; 304 EventSourceType = Constants.logSourceType; 305 EventSourceTypeNames = makeInverseMap(EventSourceType); 306 LogLevelType = Constants.logLevelType; 307 ClientInfo = Constants.clientInfo; 308 LoadFlag = Constants.loadFlag; 309 NetError = Constants.netError; 310 QuicError = Constants.quicError; 311 QuicRstStreamError = Constants.quicRstStreamError; 312 AddressFamily = Constants.addressFamily; 313 LoadState = Constants.loadState; 314 315 timeutil.setTimeTickOffset(Constants.timeTickOffset); 316 }; 317 318 /** 319 * Returns true if it's given a valid-looking constants object. 320 * @param {Object} receivedConstants The received map of constants. 321 * @return {boolean} True if the |receivedConstants| object appears valid. 322 */ 323 function areValidConstants(receivedConstants) { 324 return typeof(receivedConstants) == 'object' && 325 typeof(receivedConstants.logEventTypes) == 'object' && 326 typeof(receivedConstants.clientInfo) == 'object' && 327 typeof(receivedConstants.logEventPhase) == 'object' && 328 typeof(receivedConstants.logSourceType) == 'object' && 329 typeof(receivedConstants.logLevelType) == 'object' && 330 typeof(receivedConstants.loadFlag) == 'object' && 331 typeof(receivedConstants.netError) == 'object' && 332 typeof(receivedConstants.addressFamily) == 'object' && 333 typeof(receivedConstants.timeTickOffset) == 'string' && 334 typeof(receivedConstants.logFormatVersion) == 'number'; 335 } 336 337 /** 338 * Returns the name for netError. 339 * 340 * Example: netErrorToString(-105) should return 341 * "ERR_NAME_NOT_RESOLVED". 342 * @param {number} netError The net error code. 343 * @return {string} The name of the given error. 344 */ 345 function netErrorToString(netError) { 346 var str = getKeyWithValue(NetError, netError); 347 if (str == '?') 348 return str; 349 return 'ERR_' + str; 350 } 351 352 /** 353 * Returns the name for quicError. 354 * 355 * Example: quicErrorToString(25) should return 356 * "TIMED_OUT". 357 * @param {number} quicError The QUIC error code. 358 * @return {string} The name of the given error. 359 */ 360 function quicErrorToString(quicError) { 361 return getKeyWithValue(QuicError, quicError); 362 } 363 364 /** 365 * Returns the name for quicRstStreamError. 366 * 367 * Example: quicRstStreamErrorToString(3) should return 368 * "BAD_APPLICATION_PAYLOAD". 369 * @param {number} quicRstStreamError The QUIC RST_STREAM error code. 370 * @return {string} The name of the given error. 371 */ 372 function quicRstStreamErrorToString(quicRstStreamError) { 373 return getKeyWithValue(QuicRstStreamError, quicRstStreamError); 374 } 375 376 /** 377 * Returns a string representation of |family|. 378 * @param {number} family An AddressFamily 379 * @return {string} A representation of the given family. 380 */ 381 function addressFamilyToString(family) { 382 var str = getKeyWithValue(AddressFamily, family); 383 // All the address family start with ADDRESS_FAMILY_*. 384 // Strip that prefix since it is redundant and only clutters the output. 385 return str.replace(/^ADDRESS_FAMILY_/, ''); 386 } 387