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¶m1=value1¶m2=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