rayraymond | 2524371 | 2016-07-15 08:02:51 -0700 | [diff] [blame] | 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 ClientInfo = null; |
| 16 | var NetError = null; |
| 17 | var QuicError = null; |
| 18 | var QuicRstStreamError = null; |
| 19 | var LoadFlag = null; |
| 20 | var CertStatusFlag = null; |
| 21 | var LoadState = null; |
| 22 | var AddressFamily = null; |
| 23 | var SdchProblemCode = null; |
| 24 | var DataReductionProxyBypassEventType = null; |
| 25 | |
| 26 | /** |
| 27 | * Dictionary of all constants, used for saving log files. |
| 28 | */ |
| 29 | var Constants = null; |
| 30 | |
| 31 | /** |
| 32 | * Object to communicate between the renderer and the browser. |
| 33 | * @type {!BrowserBridge} |
| 34 | */ |
| 35 | var g_browser = null; |
| 36 | |
| 37 | /** |
| 38 | * This class is the root view object of the page. It owns all the other |
| 39 | * views, and manages switching between them. It is also responsible for |
| 40 | * initializing the views and the BrowserBridge. |
| 41 | */ |
| 42 | var MainView = (function() { |
| 43 | 'use strict'; |
| 44 | |
| 45 | // We inherit from WindowView |
| 46 | var superClass = WindowView; |
| 47 | |
| 48 | /** |
| 49 | * Main entry point. Called once the page has loaded. |
| 50 | * @constructor |
| 51 | */ |
| 52 | function MainView() { |
| 53 | assertFirstConstructorCall(MainView); |
| 54 | |
| 55 | if (hasTouchScreen()) |
| 56 | document.body.classList.add('touch'); |
| 57 | |
| 58 | // This must be initialized before the tabs, so they can register as |
| 59 | // observers. |
| 60 | g_browser = BrowserBridge.getInstance(); |
| 61 | |
| 62 | // This must be the first constants observer, so other constants observers |
| 63 | // can safely use the globals, rather than depending on walking through |
| 64 | // the constants themselves. |
| 65 | g_browser.addConstantsObserver(new ConstantsObserver()); |
| 66 | |
| 67 | // Create the tab switcher. |
| 68 | this.initTabs_(); |
| 69 | |
| 70 | // Cut out a small vertical strip at the top of the window, to display |
rayraymond | 7965c15 | 2016-07-26 10:44:54 -0700 | [diff] [blame] | 71 | // a high level status (i.e. if we are displaying a log file or not). |
| 72 | // Below it we will position the main tabs and their content area. |
rayraymond | 2524371 | 2016-07-15 08:02:51 -0700 | [diff] [blame] | 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 | // No log file loaded yet so set the status bar to that state. |
| 88 | this.topBarView_.switchToSubView('loaded').setFileName( |
| 89 | 'No log to display.'); |
| 90 | |
| 91 | // TODO(rayraymond): Follow-up is to completely remove all code from |
| 92 | // g_browser that interacts with sending/receiving messages from |
| 93 | // browser. |
| 94 | g_browser.disable(); |
| 95 | } |
| 96 | |
| 97 | cr.addSingletonGetter(MainView); |
| 98 | |
| 99 | // Tracks if we're viewing a loaded log file, so views can behave |
| 100 | // appropriately. Global so safe to call during construction. |
| 101 | var isViewingLoadedLog = false; |
| 102 | |
| 103 | MainView.isViewingLoadedLog = function() { |
| 104 | return isViewingLoadedLog; |
| 105 | }; |
| 106 | |
| 107 | MainView.prototype = { |
| 108 | // Inherit the superclass's methods. |
| 109 | __proto__: superClass.prototype, |
| 110 | |
| 111 | // This is exposed both so the log import/export code can enumerate all the |
| 112 | // tabs, and for testing. |
| 113 | tabSwitcher: function() { |
| 114 | return this.tabSwitcher_; |
| 115 | }, |
| 116 | |
| 117 | /** |
| 118 | * Prevents receiving/sending events to/from the browser, so loaded data |
| 119 | * will not be mixed with current Chrome state. Also hides any interactive |
| 120 | * HTML elements that send messages to the browser. Cannot be undone |
| 121 | * without reloading the page. Must be called before passing loaded data |
| 122 | * to the individual views. |
| 123 | * |
| 124 | * @param {string} opt_fileName The name of the log file that has been |
| 125 | * loaded, if we're loading a log file. |
| 126 | */ |
| 127 | onLoadLog: function(opt_fileName) { |
| 128 | isViewingLoadedLog = true; |
| 129 | |
rayraymond | 2524371 | 2016-07-15 08:02:51 -0700 | [diff] [blame] | 130 | if (opt_fileName != undefined) { |
rayraymond | 7965c15 | 2016-07-26 10:44:54 -0700 | [diff] [blame] | 131 | // If there's a file name, a log file was loaded, so display the |
| 132 | // file's name in the status bar. |
rayraymond | 2524371 | 2016-07-15 08:02:51 -0700 | [diff] [blame] | 133 | this.topBarView_.switchToSubView('loaded').setFileName(opt_fileName); |
| 134 | SourceTracker.getInstance().setPrivacyStripping(false); |
| 135 | } |
| 136 | }, |
| 137 | |
| 138 | switchToViewOnlyMode: function() { |
| 139 | // Since this won't be dumped to a file, we don't want to remove |
| 140 | // cookies and credentials. |
| 141 | log_util.createLogDumpAsync('', log_util.loadLogFile, false); |
| 142 | }, |
| 143 | |
rayraymond | 2524371 | 2016-07-15 08:02:51 -0700 | [diff] [blame] | 144 | initTabs_: function() { |
| 145 | this.tabIdToHash_ = {}; |
| 146 | this.hashToTabId_ = {}; |
| 147 | |
| 148 | this.tabSwitcher_ = new TabSwitcherView(this.onTabSwitched_.bind(this)); |
| 149 | |
| 150 | // Helper function to add a tab given the class for a view singleton. |
| 151 | var addTab = function(viewClass) { |
| 152 | var tabId = viewClass.TAB_ID; |
| 153 | var tabHash = viewClass.TAB_HASH; |
| 154 | var tabName = viewClass.TAB_NAME; |
| 155 | var view = viewClass.getInstance(); |
| 156 | |
| 157 | if (!tabId || !view || !tabHash || !tabName) { |
| 158 | throw Error('Invalid view class for tab'); |
| 159 | } |
| 160 | |
| 161 | if (tabHash.charAt(0) != '#') { |
| 162 | throw Error('Tab hashes must start with a #'); |
| 163 | } |
| 164 | |
| 165 | this.tabSwitcher_.addTab(tabId, view, tabName, tabHash); |
| 166 | this.tabIdToHash_[tabId] = tabHash; |
| 167 | this.hashToTabId_[tabHash] = tabId; |
| 168 | }.bind(this); |
| 169 | |
| 170 | // Populate the main tabs. Even tabs that don't contain information for |
| 171 | // the running OS should be created, so they can load log dumps from other |
| 172 | // OSes. |
| 173 | addTab(ImportView); |
| 174 | addTab(ProxyView); |
| 175 | addTab(EventsView); |
| 176 | addTab(TimelineView); |
| 177 | addTab(DnsView); |
| 178 | addTab(SocketsView); |
| 179 | addTab(AltSvcView); |
| 180 | addTab(SpdyView); |
| 181 | addTab(QuicView); |
| 182 | addTab(SdchView); |
| 183 | addTab(HttpCacheView); |
rayraymond | 7965c15 | 2016-07-26 10:44:54 -0700 | [diff] [blame] | 184 | addTab(ModulesView); |
| 185 | addTab(BandwidthView); |
| 186 | addTab(PrerenderView); |
rayraymond | 2524371 | 2016-07-15 08:02:51 -0700 | [diff] [blame] | 187 | addTab(CrosView); |
| 188 | |
rayraymond | 7965c15 | 2016-07-26 10:44:54 -0700 | [diff] [blame] | 189 | // Tab links start off hidden (besides import) since a log file has not |
| 190 | // been loaded yet. This must be done after all the tabs are added so |
| 191 | // that the width of the tab-list div is correctly styled. |
| 192 | for (var tabId in this.tabSwitcher_.getAllTabViews()) { |
| 193 | if (tabId != ImportView.TAB_ID) { |
| 194 | this.tabSwitcher_.showTabLink(tabId, false); |
| 195 | } |
| 196 | } |
| 197 | |
rayraymond | 2524371 | 2016-07-15 08:02:51 -0700 | [diff] [blame] | 198 | this.tabSwitcher_.showTabLink(CrosView.TAB_ID, cr.isChromeOS); |
| 199 | }, |
| 200 | |
| 201 | /** |
| 202 | * This function is called by the tab switcher when the current tab has been |
| 203 | * changed. It will update the current URL to reflect the new active tab, |
| 204 | * so the back can be used to return to previous view. |
| 205 | */ |
| 206 | onTabSwitched_: function(oldTabId, newTabId) { |
| 207 | // Update data needed by newly active tab, as it may be |
| 208 | // significantly out of date. |
| 209 | if (g_browser) |
| 210 | g_browser.checkForUpdatedInfo(); |
| 211 | |
| 212 | // Change the URL to match the new tab. |
| 213 | |
| 214 | var newTabHash = this.tabIdToHash_[newTabId]; |
| 215 | var parsed = parseUrlHash_(window.location.hash); |
| 216 | if (parsed.tabHash != newTabHash) { |
| 217 | window.location.hash = newTabHash; |
| 218 | } |
| 219 | }, |
| 220 | |
| 221 | onUrlHashChange_: function() { |
| 222 | var parsed = parseUrlHash_(window.location.hash); |
| 223 | |
| 224 | if (!parsed) |
| 225 | return; |
| 226 | |
| 227 | if (!parsed.tabHash) { |
| 228 | // Default to the import tab. |
| 229 | parsed.tabHash = ImportView.TAB_HASH; |
| 230 | } |
| 231 | |
| 232 | var tabId = this.hashToTabId_[parsed.tabHash]; |
| 233 | |
| 234 | if (tabId) { |
| 235 | this.tabSwitcher_.switchToTab(tabId); |
| 236 | if (parsed.parameters) { |
| 237 | var view = this.tabSwitcher_.getTabView(tabId); |
| 238 | view.setParameters(parsed.parameters); |
| 239 | } |
| 240 | } |
| 241 | }, |
| 242 | |
| 243 | }; |
| 244 | |
| 245 | /** |
| 246 | * Takes the current hash in form of "#tab¶m1=value1¶m2=value2&..." |
| 247 | * and parses it into a dictionary. |
| 248 | * |
| 249 | * Parameters and values are decoded with decodeURIComponent(). |
| 250 | */ |
| 251 | function parseUrlHash_(hash) { |
| 252 | var parameters = hash.split('&'); |
| 253 | |
| 254 | var tabHash = parameters[0]; |
| 255 | if (tabHash == '' || tabHash == '#') { |
| 256 | tabHash = undefined; |
| 257 | } |
| 258 | |
| 259 | // Split each string except the first around the '='. |
| 260 | var paramDict = null; |
| 261 | for (var i = 1; i < parameters.length; i++) { |
| 262 | var paramStrings = parameters[i].split('='); |
| 263 | if (paramStrings.length != 2) |
| 264 | continue; |
| 265 | if (paramDict == null) |
| 266 | paramDict = {}; |
| 267 | var key = decodeURIComponent(paramStrings[0]); |
| 268 | var value = decodeURIComponent(paramStrings[1]); |
| 269 | paramDict[key] = value; |
| 270 | } |
| 271 | |
| 272 | return {tabHash: tabHash, parameters: paramDict}; |
| 273 | } |
| 274 | |
| 275 | return MainView; |
| 276 | })(); |
| 277 | |
| 278 | function ConstantsObserver() {} |
| 279 | |
| 280 | /** |
| 281 | * Loads all constants from |constants|. On failure, global dictionaries are |
| 282 | * not modifed. |
| 283 | * @param {Object} receivedConstants The map of received constants. |
| 284 | */ |
| 285 | ConstantsObserver.prototype.onReceivedConstants = function(receivedConstants) { |
| 286 | if (!areValidConstants(receivedConstants)) |
| 287 | return; |
| 288 | |
| 289 | Constants = receivedConstants; |
| 290 | |
| 291 | EventType = Constants.logEventTypes; |
| 292 | EventTypeNames = makeInverseMap(EventType); |
| 293 | EventPhase = Constants.logEventPhase; |
| 294 | EventSourceType = Constants.logSourceType; |
| 295 | EventSourceTypeNames = makeInverseMap(EventSourceType); |
| 296 | ClientInfo = Constants.clientInfo; |
| 297 | LoadFlag = Constants.loadFlag; |
| 298 | NetError = Constants.netError; |
| 299 | QuicError = Constants.quicError; |
| 300 | QuicRstStreamError = Constants.quicRstStreamError; |
| 301 | AddressFamily = Constants.addressFamily; |
| 302 | LoadState = Constants.loadState; |
| 303 | SdchProblemCode = Constants.sdchProblemCode; |
| 304 | DataReductionProxyBypassEventType = |
| 305 | Constants.dataReductionProxyBypassEventType; |
| 306 | DataReductionProxyBypassActionType = |
| 307 | Constants.dataReductionProxyBypassActionType; |
| 308 | // certStatusFlag may not be present when loading old log Files |
| 309 | if (typeof(Constants.certStatusFlag) == 'object') |
| 310 | CertStatusFlag = Constants.certStatusFlag; |
| 311 | else |
| 312 | CertStatusFlag = {}; |
| 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.loadFlag) == 'object' && |
| 329 | typeof(receivedConstants.netError) == 'object' && |
| 330 | typeof(receivedConstants.addressFamily) == 'object' && |
| 331 | typeof(receivedConstants.timeTickOffset) == 'string' && |
| 332 | typeof(receivedConstants.logFormatVersion) == 'number'; |
| 333 | } |
| 334 | |
| 335 | /** |
| 336 | * Returns the name for netError. |
| 337 | * |
| 338 | * Example: netErrorToString(-105) should return |
| 339 | * "ERR_NAME_NOT_RESOLVED". |
| 340 | * @param {number} netError The net error code. |
| 341 | * @return {string} The name of the given error. |
| 342 | */ |
| 343 | function netErrorToString(netError) { |
| 344 | return getKeyWithValue(NetError, netError); |
| 345 | } |
| 346 | |
| 347 | /** |
| 348 | * Returns the name for quicError. |
| 349 | * |
| 350 | * Example: quicErrorToString(25) should return |
| 351 | * "TIMED_OUT". |
| 352 | * @param {number} quicError The QUIC error code. |
| 353 | * @return {string} The name of the given error. |
| 354 | */ |
| 355 | function quicErrorToString(quicError) { |
| 356 | return getKeyWithValue(QuicError, quicError); |
| 357 | } |
| 358 | |
| 359 | /** |
| 360 | * Returns the name for quicRstStreamError. |
| 361 | * |
| 362 | * Example: quicRstStreamErrorToString(3) should return |
| 363 | * "BAD_APPLICATION_PAYLOAD". |
| 364 | * @param {number} quicRstStreamError The QUIC RST_STREAM error code. |
| 365 | * @return {string} The name of the given error. |
| 366 | */ |
| 367 | function quicRstStreamErrorToString(quicRstStreamError) { |
| 368 | return getKeyWithValue(QuicRstStreamError, quicRstStreamError); |
| 369 | } |
| 370 | |
| 371 | /** |
| 372 | * Returns a string representation of |family|. |
| 373 | * @param {number} family An AddressFamily |
| 374 | * @return {string} A representation of the given family. |
| 375 | */ |
| 376 | function addressFamilyToString(family) { |
| 377 | var str = getKeyWithValue(AddressFamily, family); |
| 378 | // All the address family start with ADDRESS_FAMILY_*. |
| 379 | // Strip that prefix since it is redundant and only clutters the output. |
| 380 | return str.replace(/^ADDRESS_FAMILY_/, ''); |
| 381 | } |
| 382 | |
| 383 | /** |
| 384 | * Returns the name for sdchProblemCode. |
| 385 | * |
| 386 | * Example: sdchProblemCodeToString(5) should return |
| 387 | * "DECODE_BODY_ERROR". |
| 388 | * @param {number} sdchProblemCode The SDCH problem code. |
| 389 | * @return {string} The name of the given problem code. |
| 390 | */ |
| 391 | function sdchProblemCodeToString(sdchProblemCode) { |
| 392 | return getKeyWithValue(SdchProblemCode, sdchProblemCode); |
| 393 | } |
| 394 | |