| OLD | NEW |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * This view displays options for importing/exporting the captured data. This | 6 * This view displays options for importing data from a log file. |
| 7 * view is responsible for the interface and file I/O. The BrowserBridge | |
| 8 * handles creation and loading of the data dumps. | |
| 9 * | |
| 10 * - Has a button to generate a JSON dump. | |
| 11 * | |
| 12 * - Has a button to import a JSON dump. | |
| 13 * | |
| 14 * - Shows how many events have been captured. | |
| 15 * @constructor | 7 * @constructor |
| 16 */ | 8 */ |
| 17 function DataView() { | 9 function ImportView() { |
| 18 const mainBoxId = 'dataTabContent'; | 10 const mainBoxId = 'importTabContent'; |
| 19 const downloadIframeId = 'dataViewDownloadIframe'; | |
| 20 const saveFileButtonId = 'dataViewSaveLogFile'; | |
| 21 const dataViewSaveStatusTextId = 'dataViewSaveStatusText'; | |
| 22 const securityStrippingCheckboxId = 'securityStrippingCheckbox'; | |
| 23 const byteLoggingCheckboxId = 'byteLoggingCheckbox'; | |
| 24 const passivelyCapturedCountId = 'passivelyCapturedCount'; | |
| 25 const activelyCapturedCountId = 'activelyCapturedCount'; | |
| 26 const deleteAllId = 'dataViewDeleteAll'; | |
| 27 const dumpDataDivId = 'dataViewDumpDataDiv'; | |
| 28 const loadedDivId = 'dataViewLoadedDiv'; | 11 const loadedDivId = 'dataViewLoadedDiv'; |
| 29 const loadedClientInfoTextId = 'dataViewLoadedClientInfoText'; | 12 const loadedClientInfoTextId = 'dataViewLoadedClientInfoText'; |
| 30 const loadLogFileDropTargetId = 'dataViewDropTarget'; | 13 const loadLogFileDropTargetId = 'dataViewDropTarget'; |
| 31 const loadLogFileId = 'dataViewLoadLogFile'; | 14 const loadLogFileId = 'dataViewLoadLogFile'; |
| 32 const dataViewLoadStatusTextId = 'dataViewLoadStatusText'; | 15 const dataViewLoadStatusTextId = 'dataViewLoadStatusText'; |
| 33 const capturingTextSpanId = 'dataViewCapturingTextSpan'; | |
| 34 const loggingTextSpanId = 'dataViewLoggingTextSpan'; | |
| 35 | 16 |
| 36 DivView.call(this, mainBoxId); | 17 DivView.call(this, mainBoxId); |
| 37 | 18 |
| 38 var securityStrippingCheckbox = $(securityStrippingCheckboxId); | |
| 39 securityStrippingCheckbox.onclick = | |
| 40 this.onSetSecurityStripping_.bind(this, securityStrippingCheckbox); | |
| 41 | |
| 42 var byteLoggingCheckbox = $(byteLoggingCheckboxId); | |
| 43 byteLoggingCheckbox.onclick = | |
| 44 this.onSetByteLogging_.bind(this, byteLoggingCheckbox); | |
| 45 | |
| 46 this.downloadIframe_ = $(downloadIframeId); | |
| 47 | |
| 48 this.saveFileButton_ = $(saveFileButtonId); | |
| 49 this.saveFileButton_.onclick = this.onSaveFile_.bind(this); | |
| 50 this.saveStatusText_ = $(dataViewSaveStatusTextId); | |
| 51 | |
| 52 this.activelyCapturedCountBox_ = $(activelyCapturedCountId); | |
| 53 this.passivelyCapturedCountBox_ = $(passivelyCapturedCountId); | |
| 54 $(deleteAllId).onclick = g_browser.sourceTracker.deleteAllSourceEntries.bind( | |
| 55 g_browser.sourceTracker); | |
| 56 | |
| 57 this.dumpDataDiv_ = $(dumpDataDivId); | |
| 58 this.capturingTextSpan_ = $(capturingTextSpanId); | |
| 59 this.loggingTextSpan_ = $(loggingTextSpanId); | |
| 60 | 19 |
| 61 this.loadedDiv_ = $(loadedDivId); | 20 this.loadedDiv_ = $(loadedDivId); |
| 62 this.loadedClientInfoText_ = $(loadedClientInfoTextId); | 21 this.loadedClientInfoText_ = $(loadedClientInfoTextId); |
| 63 | 22 |
| 64 this.loadFileElement_ = $(loadLogFileId); | 23 this.loadFileElement_ = $(loadLogFileId); |
| 65 this.loadFileElement_.onchange = this.logFileChanged.bind(this); | 24 this.loadFileElement_.onchange = this.logFileChanged.bind(this); |
| 66 this.loadStatusText_ = $(dataViewLoadStatusTextId); | 25 this.loadStatusText_ = $(dataViewLoadStatusTextId); |
| 67 | 26 |
| 68 var dropTarget = $(loadLogFileDropTargetId); | 27 var dropTarget = $(loadLogFileDropTargetId); |
| 69 dropTarget.ondragenter = this.onDrag.bind(this); | 28 dropTarget.ondragenter = this.onDrag.bind(this); |
| 70 dropTarget.ondragover = this.onDrag.bind(this); | 29 dropTarget.ondragover = this.onDrag.bind(this); |
| 71 dropTarget.ondrop = this.onDrop.bind(this); | 30 dropTarget.ondrop = this.onDrop.bind(this); |
| 72 | |
| 73 this.updateEventCounts_(); | |
| 74 | |
| 75 // Track blob for previous log dump so it can be revoked when a new dump is | |
| 76 // saved. | |
| 77 this.lastBlobURL_ = null; | |
| 78 | |
| 79 g_browser.sourceTracker.addObserver(this); | |
| 80 } | 31 } |
| 81 | 32 |
| 82 inherits(DataView, DivView); | 33 inherits(ImportView, DivView); |
| 83 | |
| 84 /** | |
| 85 * Called whenever a new event is received. | |
| 86 */ | |
| 87 DataView.prototype.onSourceEntryUpdated = function(sourceEntry) { | |
| 88 this.updateEventCounts_(); | |
| 89 }; | |
| 90 | |
| 91 /** | |
| 92 * Called whenever some log events are deleted. |sourceIds| lists | |
| 93 * the source IDs of all deleted log entries. | |
| 94 */ | |
| 95 DataView.prototype.onSourceEntriesDeleted = function(sourceIds) { | |
| 96 this.updateEventCounts_(); | |
| 97 }; | |
| 98 | |
| 99 /** | |
| 100 * Called whenever all log events are deleted. | |
| 101 */ | |
| 102 DataView.prototype.onAllSourceEntriesDeleted = function() { | |
| 103 this.updateEventCounts_(); | |
| 104 }; | |
| 105 | 34 |
| 106 /** | 35 /** |
| 107 * Called when a log file is loaded, after clearing the old log entries and | 36 * Called when a log file is loaded, after clearing the old log entries and |
| 108 * loading the new ones. Hides parts of the page related to saving data, and | 37 * loading the new ones. Returns true to indicate the view should |
| 109 * shows those related to loading. Returns true to indicate the view should | |
| 110 * still be visible. | 38 * still be visible. |
| 111 */ | 39 */ |
| 112 DataView.prototype.onLoadLogFinish = function(data) { | 40 ImportView.prototype.onLoadLogFinish = function(data) { |
| 113 setNodeDisplay(this.dumpDataDiv_, false); | |
| 114 setNodeDisplay(this.capturingTextSpan_, false); | |
| 115 setNodeDisplay(this.loggingTextSpan_, true); | |
| 116 setNodeDisplay(this.loadedDiv_, true); | 41 setNodeDisplay(this.loadedDiv_, true); |
| 117 this.updateLoadedClientInfo(); | 42 this.updateLoadedClientInfo(); |
| 118 return true; | 43 return true; |
| 119 }; | 44 }; |
| 120 | 45 |
| 121 /** | 46 /** |
| 122 * Updates the counters showing how many events have been captured. | |
| 123 */ | |
| 124 DataView.prototype.updateEventCounts_ = function() { | |
| 125 this.activelyCapturedCountBox_.textContent = | |
| 126 g_browser.sourceTracker.getNumActivelyCapturedEvents(); | |
| 127 this.passivelyCapturedCountBox_.textContent = | |
| 128 g_browser.sourceTracker.getNumPassivelyCapturedEvents(); | |
| 129 }; | |
| 130 | |
| 131 /** | |
| 132 * Depending on the value of the checkbox, enables or disables logging of | |
| 133 * actual bytes transferred. | |
| 134 */ | |
| 135 DataView.prototype.onSetByteLogging_ = function(byteLoggingCheckbox) { | |
| 136 if (byteLoggingCheckbox.checked) { | |
| 137 g_browser.setLogLevel(LogLevelType.LOG_ALL); | |
| 138 } else { | |
| 139 g_browser.setLogLevel(LogLevelType.LOG_ALL_BUT_BYTES); | |
| 140 } | |
| 141 }; | |
| 142 | |
| 143 /** | |
| 144 * Depending on the value of the checkbox, enables or disables stripping | |
| 145 * cookies and passwords from log dumps and displayed events. | |
| 146 */ | |
| 147 DataView.prototype.onSetSecurityStripping_ = | |
| 148 function(securityStrippingCheckbox) { | |
| 149 g_browser.sourceTracker.setSecurityStripping( | |
| 150 securityStrippingCheckbox.checked); | |
| 151 }; | |
| 152 | |
| 153 DataView.prototype.onSecurityStrippingChanged = function() { | |
| 154 }; | |
| 155 | |
| 156 /** | |
| 157 * Called when something is dragged over the drop target. | 47 * Called when something is dragged over the drop target. |
| 158 * | 48 * |
| 159 * Returns false to cancel default browser behavior when a single file is being | 49 * Returns false to cancel default browser behavior when a single file is being |
| 160 * dragged. When this happens, we may not receive a list of files for security | 50 * dragged. When this happens, we may not receive a list of files for security |
| 161 * reasons, which is why we allow the |files| array to be empty. | 51 * reasons, which is why we allow the |files| array to be empty. |
| 162 */ | 52 */ |
| 163 DataView.prototype.onDrag = function(event) { | 53 ImportView.prototype.onDrag = function(event) { |
| 164 return event.dataTransfer.types.indexOf('Files') == -1 || | 54 return event.dataTransfer.types.indexOf('Files') == -1 || |
| 165 event.dataTransfer.files.length > 1; | 55 event.dataTransfer.files.length > 1; |
| 166 }; | 56 }; |
| 167 | 57 |
| 168 /** | 58 /** |
| 169 * Called when something is dropped onto the drop target. If it's a single | 59 * Called when something is dropped onto the drop target. If it's a single |
| 170 * file, tries to load it as a log file. | 60 * file, tries to load it as a log file. |
| 171 */ | 61 */ |
| 172 DataView.prototype.onDrop = function(event) { | 62 ImportView.prototype.onDrop = function(event) { |
| 173 if (event.dataTransfer.types.indexOf('Files') == -1 || | 63 if (event.dataTransfer.types.indexOf('Files') == -1 || |
| 174 event.dataTransfer.files.length != 1) { | 64 event.dataTransfer.files.length != 1) { |
| 175 return; | 65 return; |
| 176 } | 66 } |
| 177 event.preventDefault(); | 67 event.preventDefault(); |
| 178 | 68 |
| 179 // Loading a log file may hide the currently active tab. Switch to the data | 69 // Loading a log file may hide the currently active tab. Switch to the import |
| 180 // tab to prevent this. | 70 // tab to prevent this. |
| 181 document.location.hash = 'data'; | 71 document.location.hash = 'import'; |
| 182 | 72 |
| 183 this.loadLogFile(event.dataTransfer.files[0]); | 73 this.loadLogFile(event.dataTransfer.files[0]); |
| 184 }; | 74 }; |
| 185 | 75 |
| 186 /** | 76 /** |
| 187 * Called when a log file is selected. | 77 * Called when a log file is selected. |
| 188 * | 78 * |
| 189 * Gets the log file from the input element and tries to read from it. | 79 * Gets the log file from the input element and tries to read from it. |
| 190 */ | 80 */ |
| 191 DataView.prototype.logFileChanged = function() { | 81 ImportView.prototype.logFileChanged = function() { |
| 192 this.loadLogFile(this.loadFileElement_.files[0]); | 82 this.loadLogFile(this.loadFileElement_.files[0]); |
| 193 }; | 83 }; |
| 194 | 84 |
| 195 /** | 85 /** |
| 196 * Attempts to read from the File |logFile|. | 86 * Attempts to read from the File |logFile|. |
| 197 */ | 87 */ |
| 198 DataView.prototype.loadLogFile = function(logFile) { | 88 ImportView.prototype.loadLogFile = function(logFile) { |
| 199 if (logFile) { | 89 if (logFile) { |
| 200 this.setLoadFileStatus('Loading log...', true); | 90 this.setLoadFileStatus('Loading log...', true); |
| 201 var fileReader = new FileReader(); | 91 var fileReader = new FileReader(); |
| 202 | 92 |
| 203 fileReader.onload = this.onLoadLogFile.bind(this, logFile); | 93 fileReader.onload = this.onLoadLogFile.bind(this, logFile); |
| 204 fileReader.onerror = this.onLoadLogFileError.bind(this); | 94 fileReader.onerror = this.onLoadLogFileError.bind(this); |
| 205 | 95 |
| 206 fileReader.readAsText(logFile); | 96 fileReader.readAsText(logFile); |
| 207 } | 97 } |
| 208 }; | 98 }; |
| 209 | 99 |
| 210 /** | 100 /** |
| 211 * Displays an error message when unable to read the selected log file. | 101 * Displays an error message when unable to read the selected log file. |
| 212 * Also clears the file input control, so the same file can be reloaded. | 102 * Also clears the file input control, so the same file can be reloaded. |
| 213 */ | 103 */ |
| 214 DataView.prototype.onLoadLogFileError = function(event) { | 104 ImportView.prototype.onLoadLogFileError = function(event) { |
| 215 this.loadFileElement_.value = null; | 105 this.loadFileElement_.value = null; |
| 216 this.setLoadFileStatus( | 106 this.setLoadFileStatus( |
| 217 'Error ' + getKeyWithValue(FileError, event.target.error.code) + | 107 'Error ' + getKeyWithValue(FileError, event.target.error.code) + |
| 218 '. Unable to read file.', | 108 '. Unable to read file.', |
| 219 false); | 109 false); |
| 220 }; | 110 }; |
| 221 | 111 |
| 222 DataView.prototype.onLoadLogFile = function(logFile, event) { | 112 ImportView.prototype.onLoadLogFile = function(logFile, event) { |
| 223 var result = loadLogFile(event.target.result, logFile.fileName); | 113 var result = loadLogFile(event.target.result, logFile.fileName); |
| 224 this.setLoadFileStatus(result, false); | 114 this.setLoadFileStatus(result, false); |
| 225 }; | 115 }; |
| 226 | 116 |
| 227 /** | 117 /** |
| 228 * Sets the load from file status text, displayed below the load file button, | 118 * Sets the load from file status text, displayed below the load file button, |
| 229 * to |text|. Also enables or disables the save/load buttons based on the value | 119 * to |text|. Also enables or disables the load buttons based on the value |
| 230 * of |isLoading|, which must be true if the load process is still ongoing, and | 120 * of |isLoading|, which must be true if the load process is still ongoing, and |
| 231 * false when the operation has stopped, regardless of success of failure. | 121 * false when the operation has stopped, regardless of success of failure. |
| 232 * Also, when loading is done, replaces the load button so the same file can be | 122 * Also, when loading is done, replaces the load button so the same file can be |
| 233 * loaded again. | 123 * loaded again. |
| 234 */ | 124 */ |
| 235 DataView.prototype.setLoadFileStatus = function(text, isLoading) { | 125 ImportView.prototype.setLoadFileStatus = function(text, isLoading) { |
| 236 this.enableLoadFileElement_(!isLoading); | 126 this.enableLoadFileElement_(!isLoading); |
| 237 this.enableSaveFileButton_(!isLoading); | |
| 238 this.loadStatusText_.textContent = text; | 127 this.loadStatusText_.textContent = text; |
| 239 this.saveStatusText_.textContent = ''; | |
| 240 | 128 |
| 241 if (!isLoading) { | 129 if (!isLoading) { |
| 242 // Clear the button, so the same file can be reloaded. Recreating the | 130 // Clear the button, so the same file can be reloaded. Recreating the |
| 243 // element seems to be the only way to do this. | 131 // element seems to be the only way to do this. |
| 244 var loadFileElementId = this.loadFileElement_.id; | 132 var loadFileElementId = this.loadFileElement_.id; |
| 245 var loadFileElementOnChange = this.loadFileElement_.onchange; | 133 var loadFileElementOnChange = this.loadFileElement_.onchange; |
| 246 this.loadFileElement_.outerHTML = this.loadFileElement_.outerHTML; | 134 this.loadFileElement_.outerHTML = this.loadFileElement_.outerHTML; |
| 247 this.loadFileElement_ = $(loadFileElementId); | 135 this.loadFileElement_ = $(loadFileElementId); |
| 248 this.loadFileElement_.onchange = loadFileElementOnChange; | 136 this.loadFileElement_.onchange = loadFileElementOnChange; |
| 249 } | 137 } |
| 250 }; | 138 }; |
| 251 | 139 |
| 252 /** | 140 ImportView.prototype.enableLoadFileElement_ = function(enabled) { |
| 253 * Sets the save to file status text, displayed below the save to file button, | |
| 254 * to |text|. Also enables or disables the save/load buttons based on the value | |
| 255 * of |isSaving|, which must be true if the save process is still ongoing, and | |
| 256 * false when the operation has stopped, regardless of success of failure. | |
| 257 */ | |
| 258 DataView.prototype.setSaveFileStatus = function(text, isSaving) { | |
| 259 this.enableSaveFileButton_(!isSaving); | |
| 260 this.enableLoadFileElement_(!isSaving); | |
| 261 this.loadStatusText_.textContent = ''; | |
| 262 this.saveStatusText_.textContent = text; | |
| 263 }; | |
| 264 | |
| 265 DataView.prototype.enableSaveFileButton_ = function(enabled) { | |
| 266 this.saveFileButton_.disabled = !enabled; | |
| 267 }; | |
| 268 | |
| 269 DataView.prototype.enableLoadFileElement_ = function(enabled) { | |
| 270 this.loadFileElement_.disabled = !enabled; | 141 this.loadFileElement_.disabled = !enabled; |
| 271 }; | 142 }; |
| 272 | 143 |
| 273 /** | 144 /** |
| 274 * If not already busy loading or saving a log dump, triggers asynchronous | |
| 275 * generation of log dump and starts waiting for it to complete. | |
| 276 */ | |
| 277 DataView.prototype.onSaveFile_ = function() { | |
| 278 if (this.saveFileButton_.disabled) | |
| 279 return; | |
| 280 // Clean up previous blob, if any, to reduce resource usage. | |
| 281 if (this.lastBlobURL_) { | |
| 282 window.webkitURL.revokeObjectURL(this.lastBlobURL_); | |
| 283 this.lastBlobURL_ = null; | |
| 284 } | |
| 285 this.setSaveFileStatus('Preparing data...', true); | |
| 286 | |
| 287 createLogDumpAsync(this.onLogDumpCreated_.bind(this)); | |
| 288 }; | |
| 289 | |
| 290 /** | |
| 291 * Creates a blob url and starts downloading it. | |
| 292 */ | |
| 293 DataView.prototype.onLogDumpCreated_ = function(dumpText) { | |
| 294 var blobBuilder = new WebKitBlobBuilder(); | |
| 295 blobBuilder.append(dumpText, 'native'); | |
| 296 var textBlob = blobBuilder.getBlob('octet/stream'); | |
| 297 this.lastBlobURL_ = window.webkitURL.createObjectURL(textBlob); | |
| 298 this.downloadIframe_.src = this.lastBlobURL_; | |
| 299 this.setSaveFileStatus('Dump successful', false); | |
| 300 }; | |
| 301 | |
| 302 /** | |
| 303 * Prints some basic information about the environment when the log was made. | 145 * Prints some basic information about the environment when the log was made. |
| 304 */ | 146 */ |
| 305 DataView.prototype.updateLoadedClientInfo = function() { | 147 ImportView.prototype.updateLoadedClientInfo = function() { |
| 306 this.loadedClientInfoText_.textContent = ''; | 148 this.loadedClientInfoText_.textContent = ''; |
| 307 if (typeof(ClientInfo) != 'object') | 149 if (typeof(ClientInfo) != 'object') |
| 308 return; | 150 return; |
| 309 | 151 |
| 310 var text = []; | 152 var text = []; |
| 311 | 153 |
| 312 // Dumps made with the command line option don't have a date. | 154 // Dumps made with the command line option don't have a date. |
| 313 if (ClientInfo.date) { | 155 if (ClientInfo.date) { |
| 314 text.push('Data exported on: ' + ClientInfo.date); | 156 text.push('Data exported on: ' + ClientInfo.date); |
| 315 text.push(''); | 157 text.push(''); |
| 316 } | 158 } |
| 317 | 159 |
| 318 text.push(ClientInfo.name + | 160 text.push(ClientInfo.name + |
| 319 ' ' + ClientInfo.version + | 161 ' ' + ClientInfo.version + |
| 320 ' (' + ClientInfo.official + | 162 ' (' + ClientInfo.official + |
| 321 ' ' + ClientInfo.cl + | 163 ' ' + ClientInfo.cl + |
| 322 ') ' + ClientInfo.version_mod); | 164 ') ' + ClientInfo.version_mod); |
| 323 text.push('OS Type: ' + ClientInfo.os_type); | 165 text.push('OS Type: ' + ClientInfo.os_type); |
| 324 text.push('Command line: ' + ClientInfo.command_line); | 166 text.push('Command line: ' + ClientInfo.command_line); |
| 325 | 167 |
| 326 this.loadedClientInfoText_.textContent = text.join('\n'); | 168 this.loadedClientInfoText_.textContent = text.join('\n'); |
| 327 }; | 169 }; |
| OLD | NEW |