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 */ 10var EventType = null; 11var EventTypeNames = null; 12var EventPhase = null; 13var EventSourceType = null; 14var EventSourceTypeNames = null; 15var LogLevelType = null; 16var ClientInfo = null; 17var NetError = null; 18var QuicError = null; 19var QuicRstStreamError = null; 20var LoadFlag = null; 21var CertStatusFlag = null; 22var LoadState = null; 23var AddressFamily = null; 24 25/** 26 * Dictionary of all constants, used for saving log files. 27 */ 28var Constants = null; 29 30/** 31 * Object to communicate between the renderer and the browser. 32 * @type {!BrowserBridge} 33 */ 34var g_browser = null; 35 36/** 37 * This class is the root view object of the page. It owns all the other 38 * views, and manages switching between them. It is also responsible for 39 * initializing the views and the BrowserBridge. 40 */ 41var MainView = (function() { 42 'use strict'; 43 44 // We inherit from WindowView 45 var superClass = WindowView; 46 47 /** 48 * Main entry point. Called once the page has loaded. 49 * @constructor 50 */ 51 function MainView() { 52 assertFirstConstructorCall(MainView); 53 54 if (hasTouchScreen()) 55 document.body.classList.add('touch'); 56 57 // This must be initialized before the tabs, so they can register as 58 // observers. 59 g_browser = BrowserBridge.getInstance(); 60 61 // This must be the first constants observer, so other constants observers 62 // can safely use the globals, rather than depending on walking through 63 // the constants themselves. 64 g_browser.addConstantsObserver(new ConstantsObserver()); 65 66 // Create the tab switcher. 67 this.initTabs_(); 68 69 // Cut out a small vertical strip at the top of the window, to display 70 // a high level status (i.e. if we are capturing events, or displaying a 71 // log file). Below it we will position the main tabs and their content 72 // area. 73 this.topBarView_ = TopBarView.getInstance(this); 74 var verticalSplitView = new VerticalSplitView( 75 this.topBarView_, this.tabSwitcher_); 76 77 superClass.call(this, verticalSplitView); 78 79 // Trigger initial layout. 80 this.resetGeometry(); 81 82 window.onhashchange = this.onUrlHashChange_.bind(this); 83 84 // Select the initial view based on the current URL. 85 window.onhashchange(); 86 87 // Tell the browser that we are ready to start receiving log events. 88 this.topBarView_.switchToSubView('capture'); 89 g_browser.sendReady(); 90 } 91 92 cr.addSingletonGetter(MainView); 93 94 // Tracks if we're viewing a loaded log file, so views can behave 95 // appropriately. Global so safe to call during construction. 96 var isViewingLoadedLog = false; 97 98 MainView.isViewingLoadedLog = function() { 99 return isViewingLoadedLog; 100 }; 101 102 MainView.prototype = { 103 // Inherit the superclass's methods. 104 __proto__: superClass.prototype, 105 106 // This is exposed both so the log import/export code can enumerate all the 107 // tabs, and for testing. 108 tabSwitcher: function() { 109 return this.tabSwitcher_; 110 }, 111 112 /** 113 * Prevents receiving/sending events to/from the browser, so loaded data 114 * will not be mixed with current Chrome state. Also hides any interactive 115 * HTML elements that send messages to the browser. Cannot be undone 116 * without reloading the page. Must be called before passing loaded data 117 * to the individual views. 118 * 119 * @param {string} opt_fileName The name of the log file that has been 120 * loaded, if we're loading a log file. 121 */ 122 onLoadLog: function(opt_fileName) { 123 isViewingLoadedLog = true; 124 125 this.stopCapturing(); 126 if (opt_fileName != undefined) { 127 // If there's a file name, a log file was loaded, so swap out the status 128 // bar to indicate we're no longer capturing events. Also disable 129 // hiding cookies, so if the log dump has them, they'll be displayed. 130 this.topBarView_.switchToSubView('loaded').setFileName(opt_fileName); 131 $(ExportView.PRIVACY_STRIPPING_CHECKBOX_ID).checked = false; 132 SourceTracker.getInstance().setPrivacyStripping(false); 133 } else { 134 // Otherwise, the "Stop Capturing" button was presumably pressed. 135 // Don't disable hiding cookies, so created log dumps won't have them, 136 // unless the user toggles the option. 137 this.topBarView_.switchToSubView('halted'); 138 } 139 }, 140 141 switchToViewOnlyMode: function() { 142 // Since this won't be dumped to a file, we don't want to remove 143 // cookies and credentials. 144 log_util.createLogDumpAsync('', log_util.loadLogFile, false); 145 }, 146 147 stopCapturing: function() { 148 g_browser.disable(); 149 document.styleSheets[0].insertRule( 150 '.hide-when-not-capturing { display: none; }', 0); 151 }, 152 153 initTabs_: function() { 154 this.tabIdToHash_ = {}; 155 this.hashToTabId_ = {}; 156 157 this.tabSwitcher_ = new TabSwitcherView( 158 $(TopBarView.TAB_DROPDOWN_MENU_ID), 159 this.onTabSwitched_.bind(this)); 160 161 // Helper function to add a tab given the class for a view singleton. 162 var addTab = function(viewClass) { 163 var tabId = viewClass.TAB_ID; 164 var tabHash = viewClass.TAB_HASH; 165 var tabName = viewClass.TAB_NAME; 166 var view = viewClass.getInstance(); 167 168 if (!tabId || !view || !tabHash || !tabName) { 169 throw Error('Invalid view class for tab'); 170 } 171 172 if (tabHash.charAt(0) != '#') { 173 throw Error('Tab hashes must start with a #'); 174 } 175 176 this.tabSwitcher_.addTab(tabId, view, tabName); 177 this.tabIdToHash_[tabId] = tabHash; 178 this.hashToTabId_[tabHash] = tabId; 179 }.bind(this); 180 181 // Populate the main tabs. Even tabs that don't contain information for 182 // the running OS should be created, so they can load log dumps from other 183 // OSes. 184 addTab(CaptureView); 185 addTab(ExportView); 186 addTab(ImportView); 187 addTab(ProxyView); 188 addTab(EventsView); 189 addTab(WaterfallView); 190 addTab(TimelineView); 191 addTab(DnsView); 192 addTab(SocketsView); 193 addTab(SpdyView); 194 addTab(QuicView); 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 288function 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 */ 295ConstantsObserver.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 // certStatusFlag may not be present when loading old log Files 315 if (typeof(Constants.certStatusFlag) == 'object') 316 CertStatusFlag = Constants.certStatusFlag; 317 else 318 CertStatusFlag = {}; 319 320 timeutil.setTimeTickOffset(Constants.timeTickOffset); 321}; 322 323/** 324 * Returns true if it's given a valid-looking constants object. 325 * @param {Object} receivedConstants The received map of constants. 326 * @return {boolean} True if the |receivedConstants| object appears valid. 327 */ 328function areValidConstants(receivedConstants) { 329 return typeof(receivedConstants) == 'object' && 330 typeof(receivedConstants.logEventTypes) == 'object' && 331 typeof(receivedConstants.clientInfo) == 'object' && 332 typeof(receivedConstants.logEventPhase) == 'object' && 333 typeof(receivedConstants.logSourceType) == 'object' && 334 typeof(receivedConstants.logLevelType) == 'object' && 335 typeof(receivedConstants.loadFlag) == 'object' && 336 typeof(receivedConstants.netError) == 'object' && 337 typeof(receivedConstants.addressFamily) == 'object' && 338 typeof(receivedConstants.timeTickOffset) == 'string' && 339 typeof(receivedConstants.logFormatVersion) == 'number'; 340} 341 342/** 343 * Returns the name for netError. 344 * 345 * Example: netErrorToString(-105) should return 346 * "ERR_NAME_NOT_RESOLVED". 347 * @param {number} netError The net error code. 348 * @return {string} The name of the given error. 349 */ 350function netErrorToString(netError) { 351 var str = getKeyWithValue(NetError, netError); 352 if (str == '?') 353 return str; 354 return 'ERR_' + str; 355} 356 357/** 358 * Returns the name for quicError. 359 * 360 * Example: quicErrorToString(25) should return 361 * "TIMED_OUT". 362 * @param {number} quicError The QUIC error code. 363 * @return {string} The name of the given error. 364 */ 365function quicErrorToString(quicError) { 366 return getKeyWithValue(QuicError, quicError); 367} 368 369/** 370 * Returns the name for quicRstStreamError. 371 * 372 * Example: quicRstStreamErrorToString(3) should return 373 * "BAD_APPLICATION_PAYLOAD". 374 * @param {number} quicRstStreamError The QUIC RST_STREAM error code. 375 * @return {string} The name of the given error. 376 */ 377function quicRstStreamErrorToString(quicRstStreamError) { 378 return getKeyWithValue(QuicRstStreamError, quicRstStreamError); 379} 380 381/** 382 * Returns a string representation of |family|. 383 * @param {number} family An AddressFamily 384 * @return {string} A representation of the given family. 385 */ 386function addressFamilyToString(family) { 387 var str = getKeyWithValue(AddressFamily, family); 388 // All the address family start with ADDRESS_FAMILY_*. 389 // Strip that prefix since it is redundant and only clutters the output. 390 return str.replace(/^ADDRESS_FAMILY_/, ''); 391} 392