1<!DOCTYPE html> 2<!-- 3Copyright 2015 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7<head> 8 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 9 10 <script type="text/javascript" src="https://apis.google.com/js/api.js"></script> 11 12 <link rel="import" href="/components/polymer/polymer.html"> 13 <link rel="import" href="/tracing/ui/extras/full_config.html"> 14 <link rel="import" href="/tracing/ui/timeline_view.html"> 15 <link rel="import" href="/tracing/ui/extras/drive/drive_comment_provider.html"> 16 17 <style> 18 body { 19 margin: 0; 20 padding: 0; 21 width: 100%; 22 height: 100%; 23 display: -webkit-flex; 24 -webkit-flex-direction: column; 25 } 26 body > x-timeline-view { 27 -webkit-flex: 1 1 auto; 28 overflow: hidden; 29 position: absolute; 30 top: 0px; 31 bottom: 0; 32 left: 0; 33 right: 0; 34 } 35 body > x-timeline-view:focus { 36 outline: none; 37 } 38 nav { 39 display: flex; 40 flex-direction: row; 41 justify-content: flex-end; 42 } 43 #navbar button { 44 height: 24px; 45 padding-bottom: 3px; 46 vertical-align: middle; 47 box-shadow: none; 48 background-color: #4d90fe; 49 background-image: -webkit-linear-gradient(top,#4d90fe,#4787ed); 50 border: 1px solid #3079ed; 51 color: #fff; 52 border-radius: 2px; 53 cursor: default; 54 font-size: 11px; 55 font-weight: bold; 56 text-align: center; 57 white-space: nowrap; 58 line-height: 27px; 59 min-width: 54px; 60 outline: 0px; 61 padding: 0 8px; 62 font: normal 13px arial,sans-serif; 63 margin: 5px; 64 } 65 #collabs { 66 display: flex; 67 flex-direction: row; 68 } 69 .collaborator-div { 70 display: inline-block; 71 vertical-align: middle; 72 min-height: 0; 73 width: 100px; 74 font-size: 11px; 75 font-weight: bold; 76 font: normal 13px arial,sans-serif; 77 margin: 10px; 78 } 79 .collaborator-img { 80 margin: 2px; 81 } 82 .collaborator-tooltip { 83 z-index: 10000; 84 transition: visibility 0,opacity .13s ease-in; 85 background-color: #2a2a2a; 86 border: 1px solid #fff; 87 color: #fff; 88 cursor: default; 89 display: block; 90 font-family: arial, sans-serif; 91 font-size: 11px; 92 font-weight: bold; 93 margin-left: -1px; 94 opacity: 1; 95 padding: 7px 9px; 96 word-break: break-word; 97 position: absolute; 98 } 99 .collaborator-tooltip-content { 100 color: #fff; 101 } 102 .collaborator-tooltip-arrow { 103 position: absolute; 104 top: -6px; 105 } 106 .collaborator-tooltip-arrow-before { 107 border-color: #fff transparent !important; 108 left: -6px; 109 border: 6px solid; 110 border-top-width: 0; 111 content: ''; 112 display: block; 113 height: 0; 114 position: absolute; 115 width: 0; 116 } 117 .collaborator-tooltip-arrow-after { 118 top: 1px; 119 border-color: #2a2a2a transparent !important; 120 left: -5px; 121 border: 5px solid; 122 border-top-width: 0; 123 content: ''; 124 display: block; 125 height: 0; 126 position: absolute; 127 width: 0; 128 } 129 130 </style> 131 <title>Trace Viewer</title> 132</head> 133<body> 134 <nav id="navbar"> 135 <div id="collabs"></div> 136 <button id="x-drive-save-to-disk">Save to disk</button> 137 <button id="x-drive-save-to-drive">Save to Drive</button> 138 <button id="x-drive-load-from-drive">Load from Drive</button> 139 <button id="x-drive-share">Share</button> 140 </nav> 141 <x-timeline-view> 142 </x-timeline-view> 143 144 <script> 145 'use strict'; 146 147 // Needs to be global as it's passed through the Google API as a 148 // GET parameter. 149 var onAPIClientLoaded_ = null; 150 151 (function() { 152 153 tr.exportTo('tr.ui.e.drive', function() { 154 var appId = '239864068844'; 155 var constants = { 156 APP_ID: appId, 157 ANCHOR_NAME: appId + '.trace_viewer', 158 DEVELOPER_KEY: 'AIzaSyDR-6_wL9vHg1_oz4JHk8IQAkv2_Y0Y8-M', 159 CLIENT_ID: '239864068844-c7gefbfdcp0j6grltulh2r88tsvl18c1.apps.' + 160 'googleusercontent.com', 161 SCOPE: [ 162 'https://www.googleapis.com/auth/drive', 163 'https://www.googleapis.com/auth/drive.install', 164 'https://www.googleapis.com/auth/drive.file', 165 'profile' 166 ] 167 }; 168 169 return { 170 getDriveFileId: function() { return driveFileId_; }, 171 constants: constants 172 }; 173 }); 174 175 176 var pickerApiLoaded_ = false; 177 var oauthToken_ = null; 178 179 var timelineViewEl_ = null; 180 var driveDocument_ = null; 181 var shareClient_ = null; 182 var fileIdToLoad_ = null; 183 var driveFileId_ = null; 184 185 function parseGETParameter(val) { 186 var result = null; 187 var tmp = []; 188 location.search.substr(1).split('&').forEach(function(item) { 189 tmp = item.split('='); 190 if (tmp[0] === val) 191 result = decodeURIComponent(tmp[1]); 192 }); 193 return result; 194 } 195 196 // Use the Google API Loader script to load the google.picker script. 197 onAPIClientLoaded_ = function() { 198 var driveState = parseGETParameter('state'); 199 if (driveState != null) { 200 var driveStateJson = JSON.parse(driveState); 201 fileIdToLoad_ = String(driveStateJson.ids); 202 } 203 204 gapi.load('picker', {'callback': onPickerApiLoad}); 205 gapi.load('auth', {'callback': function() { 206 onAuthApiLoad(true, onAuthResultSuccess); 207 setTimeout(function onRepeatAuthApiLoad() { 208 onAuthApiLoad(true, function() {}); 209 setTimeout(onRepeatAuthApiLoad, 30000); 210 }, 30000); 211 }}); 212 } 213 214 function onAuthApiLoad(tryImmediate, resultCallback) { 215 window.gapi.auth.authorize( 216 {'client_id': tr.ui.e.drive.constants.CLIENT_ID, 217 'scope': tr.ui.e.drive.constants.SCOPE, 'immediate': tryImmediate}, 218 function(authResult) { 219 handleAuthResult(authResult, tryImmediate, resultCallback); 220 }); 221 } 222 223 function onPickerApiLoad() { 224 pickerApiLoaded_ = true; 225 if (fileIdToLoad_ == null) 226 createPicker(); 227 } 228 229 function onAuthResultSuccess() { 230 if (fileIdToLoad_ == null) 231 createPicker(); 232 else 233 loadFileFromDrive(fileIdToLoad_); 234 } 235 236 function handleAuthResult(authResult, wasImmediate, resultCallback) { 237 if (authResult && !authResult.error) { 238 oauthToken_ = authResult.access_token; 239 resultCallback(); 240 } else if (wasImmediate) { 241 onAuthApiLoad(false); 242 } 243 } 244 245 function createPicker() { 246 if (pickerApiLoaded_ && oauthToken_) { 247 var view = new google.picker.View(google.picker.ViewId.DOCS); 248 view.setMimeTypes('application/json,application/octet-stream'); 249 var picker = new google.picker.PickerBuilder() 250 .enableFeature(google.picker.Feature.NAV_HIDDEN) 251 .enableFeature(google.picker.Feature.MULTISELECT_ENABLED) 252 .setAppId(tr.ui.e.drive.constants.APP_ID) 253 .setOAuthToken(oauthToken_) 254 .addView(view) 255 .addView(new google.picker.DocsUploadView()) 256 .setDeveloperKey(tr.ui.e.drive.constants.DEVELOPER_KEY) 257 .setCallback(pickerCallback) 258 .build(); 259 picker.setVisible(true); 260 } 261 } 262 263 function pickerCallback(data) { 264 if (data.action == google.picker.Action.PICKED) { 265 loadFileFromDrive(data.docs[0].id); 266 } 267 } 268 269 function initShareButton() { 270 shareClient_ = new gapi.drive.share.ShareClient( 271 tr.ui.e.drive.constants.APP_ID); 272 shareClient_.setItemIds([driveFileId_]); 273 } 274 275 function loadFileFromDrive(fileId) { 276 gapi.client.load('drive', 'v2', function() { 277 var request = gapi.client.drive.files.get({'fileId': fileId}); 278 request.execute(function(resp) { downloadFile(resp); }); 279 driveFileId_ = fileId; 280 gapi.load('drive-share', initShareButton); 281 }); 282 } 283 284 function downloadFile(file) { 285 if (file.downloadUrl) { 286 var downloadingOverlay = tr.ui.b.Overlay(); 287 downloadingOverlay.title = 'Downloading...'; 288 downloadingOverlay.userCanClose = false; 289 downloadingOverlay.msgEl = document.createElement('div'); 290 downloadingOverlay.appendChild(downloadingOverlay.msgEl); 291 downloadingOverlay.msgEl.style.margin = '20px'; 292 downloadingOverlay.update = function(msg) { 293 this.msgEl.textContent = msg; 294 } 295 downloadingOverlay.visible = true; 296 297 var accessToken = gapi.auth.getToken().access_token; 298 var xhr = new XMLHttpRequest(); 299 xhr.open('GET', file.downloadUrl); 300 xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken); 301 xhr.onload = function() { 302 downloadingOverlay.visible = false; 303 onDownloaded(file.title, xhr.responseText); 304 }; 305 xhr.onprogress = function(evt) { 306 downloadingOverlay.update( 307 Math.floor(evt.position * 100 / file.fileSize) + '% complete'); 308 }; 309 xhr.onerror = function() { alert('Failed downloading!'); }; 310 xhr.send(); 311 } else { 312 alert('No URL!'); 313 } 314 } 315 316 function displayAllCollaborators() { 317 var allCollaborators = driveDocument_.getCollaborators(); 318 var collaboratorCount = allCollaborators.length; 319 var collabspan = document.getElementById('collabs'); 320 collabspan.innerHTML = ''; 321 var imageList = []; 322 for (var i = 0; i < collaboratorCount; i++) { 323 var user = allCollaborators[i]; 324 325 var img = document.createElement('img'); 326 img.src = user.photoUrl; 327 img.alt = user.displayName; 328 img.height = 30; 329 img.width = 30; 330 img.className = 'collaborator-img'; 331 collabspan.appendChild(img); 332 imageList.push({'image': img, 'name': user.displayName}); 333 } 334 for (i = 0; i < imageList.length; i++) { 335 var collabTooltip = tr.ui.b.createDiv({ 336 className: 'collaborator-tooltip' 337 }); 338 var collabTooltipContent = tr.ui.b.createDiv({ 339 className: 'collaborator-tooltip-content' 340 }); 341 collabTooltipContent.textContent = imageList[i].name; 342 collabTooltip.appendChild(collabTooltipContent); 343 collabspan.appendChild(collabTooltip); 344 var collabTooltipArrow = tr.ui.b.createDiv({ 345 className: 'collaborator-tooltip-arrow'}); 346 collabTooltip.appendChild(collabTooltipArrow); 347 var collabTooltipArrowBefore = tr.ui.b.createDiv({ 348 className: 'collaborator-tooltip-arrow-before'}); 349 collabTooltipArrow.appendChild(collabTooltipArrowBefore); 350 var collabTooltipArrowAfter = tr.ui.b.createDiv({ 351 className: 'collaborator-tooltip-arrow-after'}); 352 collabTooltipArrow.appendChild(collabTooltipArrowAfter); 353 354 var rect = imageList[i].image.getBoundingClientRect(); 355 collabTooltip.style.top = (rect.bottom - 6) + 'px'; 356 collabTooltip.style.left = 357 (rect.left + 16 - (collabTooltip.offsetWidth / 2)) + 'px'; 358 collabTooltipArrow.style.left = (collabTooltip.offsetWidth / 2) + 'px'; 359 collabTooltip.style.visibility = 'hidden'; 360 function visibilityDelegate(element, visibility) { 361 return function() { 362 element.style.visibility = visibility; 363 } 364 } 365 imageList[i].image.addEventListener( 366 'mouseover', visibilityDelegate(collabTooltip, 'visible')); 367 imageList[i].image.addEventListener( 368 'mouseout', visibilityDelegate(collabTooltip, 'hidden')); 369 } 370 } 371 372 function onRealtimeFileLoaded(doc) { 373 if (driveDocument_) 374 driveDocument_.close(); 375 driveDocument_ = doc; 376 doc.addEventListener(gapi.drive.realtime.EventType.COLLABORATOR_JOINED, 377 displayAllCollaborators); 378 doc.addEventListener(gapi.drive.realtime.EventType.COLLABORATOR_LEFT, 379 displayAllCollaborators); 380 381 displayAllCollaborators(doc); 382 } 383 384 function onRealtimeError(e) { 385 alert('Error loading realtime: ' + e); 386 } 387 388 function onDownloaded(filename, content) { 389 gapi.load('auth:client,drive-realtime,drive-share', function() { 390 gapi.drive.realtime.load(driveFileId_, 391 onRealtimeFileLoaded, 392 null, 393 onRealtimeError); 394 395 }); 396 397 var traces = []; 398 var filenames = []; 399 filenames.push(filename); 400 traces.push(content); 401 createViewFromTraces(filenames, traces); 402 } 403 404 function createViewFromTraces(filenames, traces) { 405 var m = new tr.Model(); 406 var i = new tr.importer.Import(m); 407 var p = i.importTracesWithProgressDialog(traces); 408 p.then( 409 function() { 410 timelineViewEl_.model = m; 411 timelineViewEl_.updateDocumentFavicon(); 412 timelineViewEl_.globalMode = true; 413 timelineViewEl_.viewTitle = ''; 414 }, 415 function(err) { 416 var downloadingOverlay = new tr.ui.b.Overlay(); 417 downloadingOverlay.textContent = 418 tr.b.normalizeException(err).message; 419 downloadingOverlay.title = 'Import error'; 420 downloadingOverlay.visible = true; 421 }); 422 } 423 424 function onSaveToDiskClicked() { 425 throw new Error('Not implemented'); 426 } 427 428 function onSaveToDriveClicked() { 429 throw new Error('Not implemented'); 430 } 431 432 function onLoadFromDriveClicked() { 433 createPicker(); 434 } 435 436 function onLoad() { 437 timelineViewEl_ = document.querySelector('x-timeline-view'); 438 timelineViewEl_.globalMode = true; 439 var navbar = document.getElementById('navbar'); 440 timelineViewEl_.style.top = navbar.offsetHeight + 'px'; 441 tr.ui.b.decorate(timelineViewEl_, tr.ui.TimelineView); 442 } 443 444 window.addEventListener('load', onLoad); 445 446 document.getElementById('x-drive-save-to-disk').onclick = 447 onSaveToDiskClicked; 448 document.getElementById('x-drive-save-to-drive').onclick = 449 onSaveToDriveClicked; 450 document.getElementById('x-drive-load-from-drive').onclick = 451 onLoadFromDriveClicked; 452 document.getElementById('x-drive-share').onclick = function() { 453 shareClient_.showSettingsDialog(); 454 }; 455 456 }()); 457 458 </script> 459 <script type="text/javascript" 460 src="https://apis.google.com/js/client.js?onload=onAPIClientLoaded_"> 461 </script> 462</body> 463