// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * Dictionary of constants (initialized by browser). */ var LogEventType = null; var LogEventPhase = null; var ClientInfo = null; var LogSourceType = null; var LogLevelType = null; var NetError = null; var LoadFlag = null; var AddressFamily = null; /** * Object to communicate between the renderer and the browser. * @type {!BrowserBridge} */ var g_browser = null; /** * Main entry point. called once the page has loaded. */ function onLoaded() { g_browser = new BrowserBridge(); // Create the view which displays events lists, and lets you select, filter // and delete them. var eventsView = new EventsView('eventsListTableBody', 'filterInput', 'filterCount', 'deleteSelected', 'deleteAll', 'selectAll', 'sortById', 'sortBySource', 'sortByDescription', // IDs for the details view. 'detailsTabHandles', 'detailsLogTab', 'detailsTimelineTab', 'detailsLogBox', 'detailsTimelineBox', // IDs for the layout boxes. 'filterBox', 'eventsBox', 'actionBox', 'splitterBox'); // Create a view which will display info on the proxy setup. var proxyView = new ProxyView('proxyTabContent', 'proxyOriginalSettings', 'proxyEffectiveSettings', 'proxyReloadSettings', 'badProxiesTableBody', 'clearBadProxies', 'proxyResolverLog'); // Create a view which will display information on the host resolver. var dnsView = new DnsView('dnsTabContent', 'hostResolverCacheTbody', 'clearHostResolverCache', 'hostResolverDefaultFamily', 'hostResolverIPv6Disabled', 'hostResolverEnableIPv6', 'hostResolverCacheCapacity', 'hostResolverCacheTTLSuccess', 'hostResolverCacheTTLFailure'); // Create a view which will display import/export options to control the // captured data. var dataView = new DataView('dataTabContent', 'exportedDataText', 'exportToText', 'securityStrippingCheckbox', 'byteLoggingCheckbox', 'passivelyCapturedCount', 'activelyCapturedCount', 'dataViewDeleteAll', 'dataViewDumpDataDiv', 'dataViewLoadDataDiv', 'dataViewLoadLogFile', 'dataViewCapturingTextSpan', 'dataViewLoggingTextSpan'); // Create a view which will display the results and controls for connection // tests. var testView = new TestView('testTabContent', 'testUrlInput', 'connectionTestsForm', 'testSummary'); // Create a view which allows the user to query and alter the HSTS database. var hstsView = new HSTSView('hstsTabContent', 'hstsQueryInput', 'hstsQueryForm', 'hstsQueryOutput', 'hstsAddInput', 'hstsAddForm', 'hstsCheckInput', 'hstsAddPins', 'hstsDeleteInput', 'hstsDeleteForm'); var httpCacheView = new HttpCacheView('httpCacheTabContent', 'httpCacheStats'); var socketsView = new SocketsView('socketsTabContent', 'socketPoolDiv', 'socketPoolGroupsDiv', 'socketPoolCloseIdleButton', 'socketPoolFlushButton'); var spdyView = new SpdyView('spdyTabContent', 'spdyEnabledSpan', 'spdyUseAlternateProtocolSpan', 'spdyForceAlwaysSpan', 'spdyForceOverSslSpan', 'spdyNextProtocolsSpan', 'spdyAlternateProtocolMappingsDiv', 'spdySessionNoneSpan', 'spdySessionLinkSpan', 'spdySessionDiv'); var serviceView; if (g_browser.isPlatformWindows()) { serviceView = new ServiceProvidersView('serviceProvidersTab', 'serviceProvidersTabContent', 'serviceProvidersTbody', 'namespaceProvidersTbody'); } var httpThrottlingView = new HttpThrottlingView( 'httpThrottlingTabContent', 'enableHttpThrottlingCheckbox'); // Create a view which lets you tab between the different sub-views. var categoryTabSwitcher = new TabSwitcherView('categoryTabHandles'); g_browser.setTabSwitcher(categoryTabSwitcher); // Populate the main tabs. categoryTabSwitcher.addTab('eventsTab', eventsView, false); categoryTabSwitcher.addTab('proxyTab', proxyView, false); categoryTabSwitcher.addTab('dnsTab', dnsView, false); categoryTabSwitcher.addTab('socketsTab', socketsView, false); categoryTabSwitcher.addTab('spdyTab', spdyView, false); categoryTabSwitcher.addTab('httpCacheTab', httpCacheView, false); categoryTabSwitcher.addTab('dataTab', dataView, false); if (g_browser.isPlatformWindows()) categoryTabSwitcher.addTab('serviceProvidersTab', serviceView, false); categoryTabSwitcher.addTab('testTab', testView, false); categoryTabSwitcher.addTab('hstsTab', hstsView, false); categoryTabSwitcher.addTab('httpThrottlingTab', httpThrottlingView, false); // Build a map from the anchor name of each tab handle to its "tab ID". // We will consider navigations to the #hash as a switch tab request. var anchorMap = {}; var tabIds = categoryTabSwitcher.getAllTabIds(); for (var i = 0; i < tabIds.length; ++i) { var aNode = document.getElementById(tabIds[i]); anchorMap[aNode.hash] = tabIds[i]; } // Default the empty hash to the data tab. anchorMap['#'] = anchorMap[''] = 'dataTab'; window.onhashchange = onUrlHashChange.bind(null, anchorMap, categoryTabSwitcher); // Make this category tab widget the primary view, that fills the whole page. var windowView = new WindowView(categoryTabSwitcher); // Trigger initial layout. windowView.resetGeometry(); // Select the initial view based on the current URL. window.onhashchange(); // Inform observers a log file is not currently being displayed. g_browser.setIsViewingLogFile_(false); // Tell the browser that we are ready to start receiving log events. g_browser.sendReady(); } /** * This class provides a "bridge" for communicating between the javascript and * the browser. * * @constructor */ function BrowserBridge() { // List of observers for various bits of browser state. this.logObservers_ = []; this.connectionTestsObservers_ = []; this.hstsObservers_ = []; this.httpThrottlingObservers_ = []; this.pollableDataHelpers_ = {}; this.pollableDataHelpers_.proxySettings = new PollableDataHelper('onProxySettingsChanged', this.sendGetProxySettings.bind(this)); this.pollableDataHelpers_.badProxies = new PollableDataHelper('onBadProxiesChanged', this.sendGetBadProxies.bind(this)); this.pollableDataHelpers_.httpCacheInfo = new PollableDataHelper('onHttpCacheInfoChanged', this.sendGetHttpCacheInfo.bind(this)); this.pollableDataHelpers_.hostResolverInfo = new PollableDataHelper('onHostResolverInfoChanged', this.sendGetHostResolverInfo.bind(this)); this.pollableDataHelpers_.socketPoolInfo = new PollableDataHelper('onSocketPoolInfoChanged', this.sendGetSocketPoolInfo.bind(this)); this.pollableDataHelpers_.spdySessionInfo = new PollableDataHelper('onSpdySessionInfoChanged', this.sendGetSpdySessionInfo.bind(this)); this.pollableDataHelpers_.spdyStatus = new PollableDataHelper('onSpdyStatusChanged', this.sendGetSpdyStatus.bind(this)); this.pollableDataHelpers_.spdyAlternateProtocolMappings = new PollableDataHelper('onSpdyAlternateProtocolMappingsChanged', this.sendGetSpdyAlternateProtocolMappings.bind( this)); if (this.isPlatformWindows()) { this.pollableDataHelpers_.serviceProviders = new PollableDataHelper('onServiceProvidersChanged', this.sendGetServiceProviders.bind(this)); } // Cache of the data received. this.numPassivelyCapturedEvents_ = 0; this.capturedEvents_ = []; // Next unique id to be assigned to a log entry without a source. // Needed to simplify deletion, identify associated GUI elements, etc. this.nextSourcelessEventId_ = -1; // True when viewing a log file rather than actively logged events. // When viewing a log file, all tabs are hidden except the event view, // and all received events are ignored. this.isViewingLogFile_ = false; // True when cookies and authentication information should be removed from // displayed events. When true, such information should be hidden from // all pages. this.enableSecurityStripping_ = true; } /* * Takes the current hash in form of "#tab¶m1=value1¶m2=value2&...". * Puts the parameters in an object, and passes the resulting object to * |categoryTabSwitcher|. Uses tab and |anchorMap| to find a tab ID, * which it also passes to the tab switcher. * * Parameters and values are decoded with decodeURIComponent(). */ function onUrlHashChange(anchorMap, categoryTabSwitcher) { var parameters = window.location.hash.split('&'); var tabId = anchorMap[parameters[0]]; if (!tabId) return; // Split each string except the first around the '='. var paramDict = null; for (var i = 1; i < parameters.length; i++) { var paramStrings = parameters[i].split('='); if (paramStrings.length != 2) continue; if (paramDict == null) paramDict = {}; var key = decodeURIComponent(paramStrings[0]); var value = decodeURIComponent(paramStrings[1]); paramDict[key] = value; } categoryTabSwitcher.switchToTab(tabId, paramDict); } /** * Delay in milliseconds between updates of certain browser information. */ BrowserBridge.POLL_INTERVAL_MS = 5000; //------------------------------------------------------------------------------ // Messages sent to the browser //------------------------------------------------------------------------------ BrowserBridge.prototype.sendReady = function() { chrome.send('notifyReady'); // Some of the data we are interested is not currently exposed as a stream, // so we will poll the browser to find out when it changes and then notify // the observers. window.setInterval(this.checkForUpdatedInfo.bind(this, false), BrowserBridge.POLL_INTERVAL_MS); }; BrowserBridge.prototype.isPlatformWindows = function() { return /Win/.test(navigator.platform); }; BrowserBridge.prototype.sendGetProxySettings = function() { // The browser will call receivedProxySettings on completion. chrome.send('getProxySettings'); }; BrowserBridge.prototype.sendReloadProxySettings = function() { chrome.send('reloadProxySettings'); }; BrowserBridge.prototype.sendGetBadProxies = function() { // The browser will call receivedBadProxies on completion. chrome.send('getBadProxies'); }; BrowserBridge.prototype.sendGetHostResolverInfo = function() { // The browser will call receivedHostResolverInfo on completion. chrome.send('getHostResolverInfo'); }; BrowserBridge.prototype.sendClearBadProxies = function() { chrome.send('clearBadProxies'); }; BrowserBridge.prototype.sendClearHostResolverCache = function() { chrome.send('clearHostResolverCache'); }; BrowserBridge.prototype.sendStartConnectionTests = function(url) { chrome.send('startConnectionTests', [url]); }; BrowserBridge.prototype.sendHSTSQuery = function(domain) { chrome.send('hstsQuery', [domain]); }; BrowserBridge.prototype.sendHSTSAdd = function(domain, include_subdomains, pins) { chrome.send('hstsAdd', [domain, include_subdomains, pins]); }; BrowserBridge.prototype.sendHSTSDelete = function(domain) { chrome.send('hstsDelete', [domain]); }; BrowserBridge.prototype.sendGetHttpCacheInfo = function() { chrome.send('getHttpCacheInfo'); }; BrowserBridge.prototype.sendGetSocketPoolInfo = function() { chrome.send('getSocketPoolInfo'); }; BrowserBridge.prototype.sendCloseIdleSockets = function() { chrome.send('closeIdleSockets'); }; BrowserBridge.prototype.sendFlushSocketPools = function() { chrome.send('flushSocketPools'); }; BrowserBridge.prototype.sendGetSpdySessionInfo = function() { chrome.send('getSpdySessionInfo'); }; BrowserBridge.prototype.sendGetSpdyStatus = function() { chrome.send('getSpdyStatus'); }; BrowserBridge.prototype.sendGetSpdyAlternateProtocolMappings = function() { chrome.send('getSpdyAlternateProtocolMappings'); }; BrowserBridge.prototype.sendGetServiceProviders = function() { chrome.send('getServiceProviders'); }; BrowserBridge.prototype.enableIPv6 = function() { chrome.send('enableIPv6'); }; BrowserBridge.prototype.setLogLevel = function(logLevel) { chrome.send('setLogLevel', ['' + logLevel]); }; BrowserBridge.prototype.enableHttpThrottling = function(enable) { chrome.send('enableHttpThrottling', [enable]); }; BrowserBridge.prototype.loadLogFile = function() { chrome.send('loadLogFile'); } //------------------------------------------------------------------------------ // Messages received from the browser //------------------------------------------------------------------------------ BrowserBridge.prototype.receivedLogEntries = function(logEntries) { // Does nothing if viewing a log file. if (this.isViewingLogFile_) return; this.addLogEntries(logEntries); }; BrowserBridge.prototype.receivedLogEventTypeConstants = function(constantsMap) { LogEventType = constantsMap; }; BrowserBridge.prototype.receivedClientInfo = function(info) { ClientInfo = info; }; BrowserBridge.prototype.receivedLogEventPhaseConstants = function(constantsMap) { LogEventPhase = constantsMap; }; BrowserBridge.prototype.receivedLogSourceTypeConstants = function(constantsMap) { LogSourceType = constantsMap; }; BrowserBridge.prototype.receivedLogLevelConstants = function(constantsMap) { LogLevelType = constantsMap; }; BrowserBridge.prototype.receivedLoadFlagConstants = function(constantsMap) { LoadFlag = constantsMap; }; BrowserBridge.prototype.receivedNetErrorConstants = function(constantsMap) { NetError = constantsMap; }; BrowserBridge.prototype.receivedAddressFamilyConstants = function(constantsMap) { AddressFamily = constantsMap; }; BrowserBridge.prototype.receivedTimeTickOffset = function(timeTickOffset) { this.timeTickOffset_ = timeTickOffset; }; BrowserBridge.prototype.receivedProxySettings = function(proxySettings) { this.pollableDataHelpers_.proxySettings.update(proxySettings); }; BrowserBridge.prototype.receivedBadProxies = function(badProxies) { this.pollableDataHelpers_.badProxies.update(badProxies); }; BrowserBridge.prototype.receivedHostResolverInfo = function(hostResolverInfo) { this.pollableDataHelpers_.hostResolverInfo.update(hostResolverInfo); }; BrowserBridge.prototype.receivedSocketPoolInfo = function(socketPoolInfo) { this.pollableDataHelpers_.socketPoolInfo.update(socketPoolInfo); }; BrowserBridge.prototype.receivedSpdySessionInfo = function(spdySessionInfo) { this.pollableDataHelpers_.spdySessionInfo.update(spdySessionInfo); }; BrowserBridge.prototype.receivedSpdyStatus = function(spdyStatus) { this.pollableDataHelpers_.spdyStatus.update(spdyStatus); }; BrowserBridge.prototype.receivedSpdyAlternateProtocolMappings = function(spdyAlternateProtocolMappings) { this.pollableDataHelpers_.spdyAlternateProtocolMappings.update( spdyAlternateProtocolMappings); }; BrowserBridge.prototype.receivedServiceProviders = function(serviceProviders) { this.pollableDataHelpers_.serviceProviders.update(serviceProviders); }; BrowserBridge.prototype.receivedPassiveLogEntries = function(entries) { // Due to an expected race condition, it is possible to receive actively // captured log entries before the passively logged entries are received. // // When that happens, we create a copy of the actively logged entries, delete // all entries, and, after handling all the passively logged entries, add back // the deleted actively logged entries. var earlyActivelyCapturedEvents = this.capturedEvents_.slice(0); if (earlyActivelyCapturedEvents.length > 0) this.deleteAllEvents(); this.numPassivelyCapturedEvents_ = entries.length; for (var i = 0; i < entries.length; ++i) entries[i].wasPassivelyCaptured = true; this.receivedLogEntries(entries); // Add back early actively captured events, if any. if (earlyActivelyCapturedEvents.length) this.receivedLogEntries(earlyActivelyCapturedEvents); }; BrowserBridge.prototype.receivedStartConnectionTestSuite = function() { for (var i = 0; i < this.connectionTestsObservers_.length; ++i) this.connectionTestsObservers_[i].onStartedConnectionTestSuite(); }; BrowserBridge.prototype.receivedStartConnectionTestExperiment = function( experiment) { for (var i = 0; i < this.connectionTestsObservers_.length; ++i) { this.connectionTestsObservers_[i].onStartedConnectionTestExperiment( experiment); } }; BrowserBridge.prototype.receivedCompletedConnectionTestExperiment = function(info) { for (var i = 0; i < this.connectionTestsObservers_.length; ++i) { this.connectionTestsObservers_[i].onCompletedConnectionTestExperiment( info.experiment, info.result); } }; BrowserBridge.prototype.receivedCompletedConnectionTestSuite = function() { for (var i = 0; i < this.connectionTestsObservers_.length; ++i) this.connectionTestsObservers_[i].onCompletedConnectionTestSuite(); }; BrowserBridge.prototype.receivedHSTSResult = function(info) { for (var i = 0; i < this.hstsObservers_.length; ++i) this.hstsObservers_[i].onHSTSQueryResult(info); }; BrowserBridge.prototype.receivedHttpCacheInfo = function(info) { this.pollableDataHelpers_.httpCacheInfo.update(info); }; BrowserBridge.prototype.receivedHttpThrottlingEnabledPrefChanged = function( enabled) { for (var i = 0; i < this.httpThrottlingObservers_.length; ++i) { this.httpThrottlingObservers_[i].onHttpThrottlingEnabledPrefChanged( enabled); } }; BrowserBridge.prototype.loadedLogFile = function(logFileContents) { var match; // Replace carriage returns with linebreaks and then split around linebreaks. var lines = logFileContents.replace(/\r/g, '\n').split('\n'); var entries = []; var numInvalidLines = 0; for (var i = 0; i < lines.length; ++i) { if (lines[i].trim().length == 0) continue; // Parse all valid lines, skipping any others. try { var entry = JSON.parse(lines[i]); if (entry && typeof(entry) == 'object' && entry.phase != undefined && entry.source != undefined && entry.time != undefined && entry.type != undefined) { entries.push(entry); continue; } } catch (err) { } ++numInvalidLines; console.log('Unable to parse log line: ' + lines[i]); } if (entries.length == 0) { window.alert('Loading log file failed.'); return; } this.deleteAllEvents(); this.setIsViewingLogFile_(true); var validEntries = []; for (var i = 0; i < entries.length; ++i) { entries[i].wasPassivelyCaptured = true; if (LogEventType[entries[i].type] != undefined && LogSourceType[entries[i].source.type] != undefined && LogEventPhase[entries[i].phase] != undefined) { entries[i].type = LogEventType[entries[i].type]; entries[i].source.type = LogSourceType[entries[i].source.type]; entries[i].phase = LogEventPhase[entries[i].phase]; validEntries.push(entries[i]); } else { // TODO(mmenke): Do something reasonable when the event type isn't // found, which could happen when event types are // removed or added between versions. Could also happen // with source types, but less likely. console.log( 'Unrecognized values in log entry: ' + JSON.stringify(entry)); } } this.numPassivelyCapturedEvents_ = validEntries.length; this.addLogEntries(validEntries); var numInvalidEntries = entries.length - validEntries.length; if (numInvalidEntries > 0 || numInvalidLines > 0) { window.alert( numInvalidLines.toString() + ' could not be parsed as JSON strings, and ' + numInvalidEntries.toString() + ' entries don\'t have valid data.\n\n' + 'Unparseable lines may indicate log file corruption.\n' + 'Entries with invalid data may be caused by version differences.\n\n' + 'See console for more information.'); } } //------------------------------------------------------------------------------ /** * Sets the |categoryTabSwitcher_| of BrowserBridge. Since views depend on * g_browser being initialized, have to have a BrowserBridge prior to tab * construction. */ BrowserBridge.prototype.setTabSwitcher = function(categoryTabSwitcher) { this.categoryTabSwitcher_ = categoryTabSwitcher; }; /** * Adds a listener of log entries. |observer| will be called back when new log * data arrives, through: * * observer.onLogEntryAdded(logEntry) */ BrowserBridge.prototype.addLogObserver = function(observer) { this.logObservers_.push(observer); }; /** * Adds a listener of the proxy settings. |observer| will be called back when * data is received, through: * * observer.onProxySettingsChanged(proxySettings) * * |proxySettings| is a dictionary with (up to) two properties: * * "original" -- The settings that chrome was configured to use * (i.e. system settings.) * "effective" -- The "effective" proxy settings that chrome is using. * (decides between the manual/automatic modes of the * fetched settings). * * Each of these two configurations is formatted as a string, and may be * omitted if not yet initialized. * * TODO(eroman): send a dictionary instead. */ BrowserBridge.prototype.addProxySettingsObserver = function(observer) { this.pollableDataHelpers_.proxySettings.addObserver(observer); }; /** * Adds a listener of the proxy settings. |observer| will be called back when * data is received, through: * * observer.onBadProxiesChanged(badProxies) * * |badProxies| is an array, where each entry has the property: * badProxies[i].proxy_uri: String identify the proxy. * badProxies[i].bad_until: The time when the proxy stops being considered * bad. Note the time is in time ticks. */ BrowserBridge.prototype.addBadProxiesObserver = function(observer) { this.pollableDataHelpers_.badProxies.addObserver(observer); }; /** * Adds a listener of the host resolver info. |observer| will be called back * when data is received, through: * * observer.onHostResolverInfoChanged(hostResolverInfo) */ BrowserBridge.prototype.addHostResolverInfoObserver = function(observer) { this.pollableDataHelpers_.hostResolverInfo.addObserver(observer); }; /** * Adds a listener of the socket pool. |observer| will be called back * when data is received, through: * * observer.onSocketPoolInfoChanged(socketPoolInfo) */ BrowserBridge.prototype.addSocketPoolInfoObserver = function(observer) { this.pollableDataHelpers_.socketPoolInfo.addObserver(observer); }; /** * Adds a listener of the SPDY info. |observer| will be called back * when data is received, through: * * observer.onSpdySessionInfoChanged(spdySessionInfo) */ BrowserBridge.prototype.addSpdySessionInfoObserver = function(observer) { this.pollableDataHelpers_.spdySessionInfo.addObserver(observer); }; /** * Adds a listener of the SPDY status. |observer| will be called back * when data is received, through: * * observer.onSpdyStatusChanged(spdyStatus) */ BrowserBridge.prototype.addSpdyStatusObserver = function(observer) { this.pollableDataHelpers_.spdyStatus.addObserver(observer); }; /** * Adds a listener of the AlternateProtocolMappings. |observer| will be called * back when data is received, through: * * observer.onSpdyAlternateProtocolMappingsChanged( * spdyAlternateProtocolMappings) */ BrowserBridge.prototype.addSpdyAlternateProtocolMappingsObserver = function(observer) { this.pollableDataHelpers_.spdyAlternateProtocolMappings.addObserver(observer); }; /** * Adds a listener of the service providers info. |observer| will be called * back when data is received, through: * * observer.onServiceProvidersChanged(serviceProviders) */ BrowserBridge.prototype.addServiceProvidersObserver = function(observer) { this.pollableDataHelpers_.serviceProviders.addObserver(observer); }; /** * Adds a listener for the progress of the connection tests. * The observer will be called back with: * * observer.onStartedConnectionTestSuite(); * observer.onStartedConnectionTestExperiment(experiment); * observer.onCompletedConnectionTestExperiment(experiment, result); * observer.onCompletedConnectionTestSuite(); */ BrowserBridge.prototype.addConnectionTestsObserver = function(observer) { this.connectionTestsObservers_.push(observer); }; /** * Adds a listener for the http cache info results. * The observer will be called back with: * * observer.onHttpCacheInfoChanged(info); */ BrowserBridge.prototype.addHttpCacheInfoObserver = function(observer) { this.pollableDataHelpers_.httpCacheInfo.addObserver(observer); }; /** * Adds a listener for the results of HSTS (HTTPS Strict Transport Security) * queries. The observer will be called back with: * * observer.onHSTSQueryResult(result); */ BrowserBridge.prototype.addHSTSObserver = function(observer) { this.hstsObservers_.push(observer); }; /** * Adds a listener for HTTP throttling-related events. |observer| will be called * back when HTTP throttling is enabled/disabled, through: * * observer.onHttpThrottlingEnabledPrefChanged(enabled); */ BrowserBridge.prototype.addHttpThrottlingObserver = function(observer) { this.httpThrottlingObservers_.push(observer); }; /** * The browser gives us times in terms of "time ticks" in milliseconds. * This function converts the tick count to a Date() object. * * @param {String} timeTicks. * @returns {Date} The time that |timeTicks| represents. */ BrowserBridge.prototype.convertTimeTicksToDate = function(timeTicks) { // Note that the subtraction by 0 is to cast to a number (probably a float // since the numbers are big). var timeStampMs = (this.timeTickOffset_ - 0) + (timeTicks - 0); var d = new Date(); d.setTime(timeStampMs); return d; }; /** * Returns a list of all captured events. */ BrowserBridge.prototype.getAllCapturedEvents = function() { return this.capturedEvents_; }; /** * Returns the number of events that were captured while we were * listening for events. */ BrowserBridge.prototype.getNumActivelyCapturedEvents = function() { return this.capturedEvents_.length - this.numPassivelyCapturedEvents_; }; /** * Returns the number of events that were captured passively by the * browser prior to when the net-internals page was started. */ BrowserBridge.prototype.getNumPassivelyCapturedEvents = function() { return this.numPassivelyCapturedEvents_; }; /** * Sends each entry to all log observers, and updates |capturedEvents_|. * Also assigns unique ids to log entries without a source. */ BrowserBridge.prototype.addLogEntries = function(logEntries) { for (var e = 0; e < logEntries.length; ++e) { var logEntry = logEntries[e]; // Assign unique ID, if needed. if (logEntry.source.id == 0) { logEntry.source.id = this.nextSourcelessEventId_; --this.nextSourcelessEventId_; } this.capturedEvents_.push(logEntry); for (var i = 0; i < this.logObservers_.length; ++i) this.logObservers_[i].onLogEntryAdded(logEntry); } }; /** * Deletes captured events with source IDs in |sourceIds|. */ BrowserBridge.prototype.deleteEventsBySourceId = function(sourceIds) { var sourceIdDict = {}; for (var i = 0; i < sourceIds.length; i++) sourceIdDict[sourceIds[i]] = true; var newEventList = []; for (var i = 0; i < this.capturedEvents_.length; ++i) { var id = this.capturedEvents_[i].source.id; if (id in sourceIdDict) { if (this.capturedEvents_[i].wasPassivelyCaptured) --this.numPassivelyCapturedEvents_; continue; } newEventList.push(this.capturedEvents_[i]); } this.capturedEvents_ = newEventList; for (var i = 0; i < this.logObservers_.length; ++i) this.logObservers_[i].onLogEntriesDeleted(sourceIds); }; /** * Deletes all captured events. */ BrowserBridge.prototype.deleteAllEvents = function() { this.capturedEvents_ = []; this.numPassivelyCapturedEvents_ = 0; for (var i = 0; i < this.logObservers_.length; ++i) this.logObservers_[i].onAllLogEntriesDeleted(); }; /** * Sets the value of |enableSecurityStripping_| and informs log observers * of the change. */ BrowserBridge.prototype.setSecurityStripping = function(enableSecurityStripping) { this.enableSecurityStripping_ = enableSecurityStripping; for (var i = 0; i < this.logObservers_.length; ++i) { if (this.logObservers_[i].onSecurityStrippingChanged) this.logObservers_[i].onSecurityStrippingChanged(); } }; /** * Returns whether or not cookies and authentication information should be * displayed for events that contain them. */ BrowserBridge.prototype.getSecurityStripping = function() { return this.enableSecurityStripping_; }; /** * Informs log observers whether or not future events will be from a log file. * Hides all tabs except the events and data tabs when viewing a log file, shows * them all otherwise. */ BrowserBridge.prototype.setIsViewingLogFile_ = function(isViewingLogFile) { this.isViewingLogFile_ = isViewingLogFile; var tabIds = this.categoryTabSwitcher_.getAllTabIds(); for (var i = 0; i < this.logObservers_.length; ++i) this.logObservers_[i].onSetIsViewingLogFile(isViewingLogFile); // Shows/hides tabs not used when viewing a log file. for (var i = 0; i < tabIds.length; ++i) { if (tabIds[i] == 'eventsTab' || tabIds[i] == 'dataTab') continue; this.categoryTabSwitcher_.showTabHandleNode(tabIds[i], !isViewingLogFile); } if (isViewingLogFile) { var activeTab = this.categoryTabSwitcher_.findActiveTab(); if (activeTab.id != 'eventsTab') this.categoryTabSwitcher_.switchToTab('dataTab', null); } }; /** * Returns true if a log file is currently being viewed. */ BrowserBridge.prototype.isViewingLogFile = function() { return this.isViewingLogFile_; }; /** * If |force| is true, calls all startUpdate functions. Otherwise, just * runs updates with active observers. */ BrowserBridge.prototype.checkForUpdatedInfo = function(force) { for (name in this.pollableDataHelpers_) { var helper = this.pollableDataHelpers_[name]; if (force || helper.hasActiveObserver()) helper.startUpdate(); } }; /** * Calls all startUpdate functions and, if |callback| is non-null, * calls it with the results of all updates. */ BrowserBridge.prototype.updateAllInfo = function(callback) { if (callback) new UpdateAllObserver(callback, this.pollableDataHelpers_); this.checkForUpdatedInfo(true); }; /** * This is a helper class used by BrowserBridge, to keep track of: * - the list of observers interested in some piece of data. * - the last known value of that piece of data. * - the name of the callback method to invoke on observers. * - the update function. * @constructor */ function PollableDataHelper(observerMethodName, startUpdateFunction) { this.observerMethodName_ = observerMethodName; this.startUpdate = startUpdateFunction; this.observerInfos_ = []; } PollableDataHelper.prototype.getObserverMethodName = function() { return this.observerMethodName_; }; /** * This is a helper class used by PollableDataHelper, to keep track of * each observer and whether or not it has received any data. The * latter is used to make sure that new observers get sent data on the * update following their creation. * @constructor */ function ObserverInfo(observer) { this.observer = observer; this.hasReceivedData = false; } PollableDataHelper.prototype.addObserver = function(observer) { this.observerInfos_.push(new ObserverInfo(observer)); }; PollableDataHelper.prototype.removeObserver = function(observer) { for (var i = 0; i < this.observerInfos_.length; ++i) { if (this.observerInfos_[i].observer == observer) { this.observerInfos_.splice(i, 1); return; } } }; /** * Helper function to handle calling all the observers, but ONLY if the data has * actually changed since last time or the observer has yet to receive any data. * This is used for data we received from browser on an update loop. */ PollableDataHelper.prototype.update = function(data) { var prevData = this.currentData_; var changed = false; // If the data hasn't changed since last time, will only need to notify // observers that have not yet received any data. if (!prevData || JSON.stringify(prevData) != JSON.stringify(data)) { changed = true; this.currentData_ = data; } // Notify the observers of the change, as needed. for (var i = 0; i < this.observerInfos_.length; ++i) { var observerInfo = this.observerInfos_[i]; if (changed || !observerInfo.hasReceivedData) { observerInfo.observer[this.observerMethodName_](this.currentData_); observerInfo.hasReceivedData = true; } } }; /** * Returns true if one of the observers actively wants the data * (i.e. is visible). */ PollableDataHelper.prototype.hasActiveObserver = function() { for (var i = 0; i < this.observerInfos_.length; ++i) { if (this.observerInfos_[i].observer.isActive()) return true; } return false; }; /** * This is a helper class used by BrowserBridge to send data to * a callback once data from all polls has been received. * * It works by keeping track of how many polling functions have * yet to receive data, and recording the data as it it received. * * @constructor */ function UpdateAllObserver(callback, pollableDataHelpers) { this.callback_ = callback; this.observingCount_ = 0; this.updatedData_ = {}; for (name in pollableDataHelpers) { ++this.observingCount_; var helper = pollableDataHelpers[name]; helper.addObserver(this); this[helper.getObserverMethodName()] = this.onDataReceived_.bind(this, helper, name); } } UpdateAllObserver.prototype.isActive = function() { return true; }; UpdateAllObserver.prototype.onDataReceived_ = function(helper, name, data) { helper.removeObserver(this); --this.observingCount_; this.updatedData_[name] = data; if (this.observingCount_ == 0) this.callback_(this.updatedData_); };