|  | // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | // See also timeline_message_handler.js. | 
|  |  | 
|  | var traceObject; | 
|  | var pendingRequests; | 
|  | var loadingOverlay; | 
|  |  | 
|  | function onModelLoaded(model) { | 
|  | viewer.globalMode = true; | 
|  | viewer.model = model; | 
|  | } | 
|  |  | 
|  | function clearTimeline() { | 
|  | viewer.model = undefined; | 
|  | } | 
|  |  | 
|  | function fetchUri(uri, onLoad, onError) { | 
|  | var xhr = new XMLHttpRequest(); | 
|  | xhr.open('GET', uri, true); | 
|  | xhr.responseType = 'text'; | 
|  | xhr.addEventListener('load', onLoad); | 
|  | xhr.addEventListener('error', onError); | 
|  | xhr.send(); | 
|  | console.log('GET ' + uri); | 
|  | } | 
|  |  | 
|  | function onImportFail(err) { | 
|  | var overlay = new tr.ui.b.Overlay(); | 
|  | overlay.textContent = tr.b.normalizeException(err).message; | 
|  | overlay.title = 'Import error'; | 
|  | overlay.visible = true; | 
|  | console.log('import failed'); | 
|  | } | 
|  |  | 
|  | function compareTimestamp(a, b) { return a.ts - b.ts; } | 
|  | function compareBeginTimestamp(a, b) { return a.begin.ts - b.begin.ts; } | 
|  | function compareEndTimestamp(a, b) { return a.end.ts - b.end.ts; } | 
|  |  | 
|  | var basicModelEventsWaterfall = [ | 
|  | // Sort events and remove orphan async ends. | 
|  | function filterUnwantedEvents(events) { | 
|  | events = events.slice(); | 
|  | events.sort(compareTimestamp); | 
|  | var threads = {}; | 
|  | return events.filter(function (event) { | 
|  | if (event.ph === 'E') { | 
|  | return threads[event.tid] && threads[event.tid].pop(); | 
|  | } | 
|  | var result = event.args && event.args.mode === 'basic'; | 
|  | if (event.ph === 'B') { | 
|  | threads[event.tid] = threads[event.tid] || []; | 
|  | threads[event.tid].push(result); | 
|  | } | 
|  | return result; | 
|  | }); | 
|  | } | 
|  | ]; | 
|  |  | 
|  | var frameModelEventsWaterfall = [ | 
|  | // Sort events and remove orphan async ends. | 
|  | function filterUnwantedEvents(events) { | 
|  | events = events.slice(); | 
|  | events.sort(compareTimestamp); | 
|  | var threads = {}; | 
|  | return events.filter(function (event) { | 
|  | if (event.ph === 'E') { | 
|  | if (!threads[event.tid]) { | 
|  | return false | 
|  | } | 
|  | threads[event.tid] -= 1; | 
|  | } else if (event.ph === 'B') { | 
|  | threads[event.tid] = (threads[event.tid] || 0) + 1; | 
|  | } | 
|  | return true; | 
|  | }); | 
|  | }, | 
|  | // Clone the events (we want to preserve them for dumps). | 
|  | function cloneDeep(input) { | 
|  | if (typeof input === 'object') { | 
|  | if (Array.isArray(input)) { | 
|  | return input.map(cloneDeep); | 
|  | } else { | 
|  | var clone = {}; | 
|  | Object.keys(input).forEach(function (key) { | 
|  | clone[key] = cloneDeep(input[key]); | 
|  | }); | 
|  | return clone; | 
|  | } | 
|  | } | 
|  | return input; | 
|  | }, | 
|  | // Group nested sync begin end sequences on every thread. | 
|  | // | 
|  | // Example: | 
|  | // Input = [B,B,E,B,E,E,B,E,B,B,E,E] | 
|  | // Output = [[B,B,E,B,E,E],[B,E],[B,B,E,E]] | 
|  | function groupIsolatedPerThreadSequences(events) { | 
|  | var sequences = [], | 
|  | timeless = [], | 
|  | threadOpen = {}; | 
|  | threadSequences = {}; | 
|  | events.forEach(function (event) { | 
|  | if (event.ph === 'M') { | 
|  | timeless.push(event); | 
|  | } else if (event.ph === 'B') { | 
|  | threadOpen[event.tid] = Math.max(threadOpen[event.tid] || 0) + 1; | 
|  | threadSequences[event.tid] = threadSequences[event.tid] || []; | 
|  | threadSequences[event.tid].push(event); | 
|  | } else if (event.ph === 'E') { | 
|  | threadSequences[event.tid].push(event); | 
|  | threadOpen[event.tid] -= 1; | 
|  | if (threadOpen[event.tid] == 0) { | 
|  | threadSequences[event.tid].sort() | 
|  | sequences.push(threadSequences[event.tid]); | 
|  | threadSequences[event.tid] = []; | 
|  | } | 
|  | } else if (threadSequences[event.tid]){ | 
|  | threadSequences[event.tid] = threadSequences[event.tid] || []; | 
|  | threadSequences[event.tid].push(event); | 
|  | } | 
|  | }) | 
|  | return { | 
|  | timeless: timeless, | 
|  | sequences: sequences | 
|  | }; | 
|  | }, | 
|  | // Transform every sequence into an object for rapid begin end analysis and | 
|  | // block types lookup. | 
|  | // | 
|  | // Example: | 
|  | // Input = [B1,B2,E2,B3,E3,E1] | 
|  | // Output = { | 
|  | //   begin: B1, | 
|  | //   end: E1, | 
|  | //   events: [B1,B2,E2,B3,E3,E1], | 
|  | //   isGPU: ..., | 
|  | //   isVSync: ..., | 
|  | //   isFramework: ..., | 
|  | //   isShiftable: ..., | 
|  | // } | 
|  | function sequenceToBlockDescriptor(input) { | 
|  | return { | 
|  | timeless: input.timeless, | 
|  | blocks: input.sequences.map(function (events) { | 
|  | var begin, | 
|  | end, | 
|  | isGPU, | 
|  | isVSync, | 
|  | isFramework; | 
|  | events.forEach(function (event) { | 
|  | if (event.ph === 'B') { | 
|  | begin = begin || event; | 
|  | } else if (event.ph === 'E') { | 
|  | end = event; | 
|  | } | 
|  | }); | 
|  | isGPU = begin.name === 'GPU Workload'; | 
|  | isVSync = begin.name === 'VSYNC'; | 
|  | isFramework = begin.name === 'Framework Workload'; | 
|  | return { | 
|  | begin: begin, | 
|  | end: end, | 
|  | events: events, | 
|  | isGPU: isGPU, | 
|  | isVSync: isVSync, | 
|  | isFramework: isFramework, | 
|  | isShiftable: !(isGPU || isVSync || isFramework) | 
|  | }; | 
|  | }) | 
|  | }; | 
|  | }, | 
|  | // Remove all the blocks that ended before the first VSYNC. | 
|  | // These events do not give any information to the analysis. | 
|  | function removePreVSyncBlocks(input) { | 
|  | input.blocks.sort(compareEndTimestamp); | 
|  | var sawVSyncBlock = false; | 
|  | return { | 
|  | timeless: input.timeless, | 
|  | blocks: input.blocks.filter(function (block) { | 
|  | sawVSyncBlock = sawVSyncBlock || block.isVSync; | 
|  | return sawVSyncBlock; | 
|  | }) | 
|  | }; | 
|  | }, | 
|  | // Remove all the GPU blocks that started before the first Framework block. | 
|  | // They are orphans of other frames. | 
|  | function removePreFrameworkGPUBlocks(input) { | 
|  | input.blocks.sort(compareBeginTimestamp); | 
|  | var firstFrameworkBlockBeginTimestamp = 0; | 
|  | return { | 
|  | timeless: input.timeless, | 
|  | blocks: input.blocks.filter(function (block) { | 
|  | if (block.isFramework) { | 
|  | firstFrameworkBlockBeginTimestamp = | 
|  | firstFrameworkBlockBeginTimestamp || block.begin.ts; | 
|  | } else if (block.isGPU) { | 
|  | if (!firstFrameworkBlockBeginTimestamp) { | 
|  | return false; | 
|  | } else if (block.begin.ts < firstFrameworkBlockBeginTimestamp) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | }) | 
|  | }; | 
|  | }, | 
|  | // Merge all shiftable blocks that are between two Framework blocks. | 
|  | // By merging them we preserve their relative timing. | 
|  | function mergeShiftableBlocks(input) { | 
|  | input.blocks.sort(compareEndTimestamp); | 
|  | var begin, | 
|  | end, | 
|  | events = [], | 
|  | shiftableBlocks = [], | 
|  | blocks; | 
|  | blocks = input.blocks.filter(function (block) { | 
|  | if (block.isShiftable) { | 
|  | begin = begin || block.begin; | 
|  | end = block.end; | 
|  | events = events.concat(block.events); | 
|  | return false; | 
|  | } else if (block.isFramework) { | 
|  | if (events.length) { | 
|  | shiftableBlocks.push({ | 
|  | begin: begin, | 
|  | end: end, | 
|  | events: events | 
|  | }); | 
|  | } | 
|  | } | 
|  | return true; | 
|  | }); | 
|  | if (events.length) { | 
|  | shiftableBlocks.push({ | 
|  | begin: begin, | 
|  | end: end, | 
|  | events: events | 
|  | }); | 
|  | } | 
|  | return { | 
|  | timeless: input.timeless, | 
|  | blocks: blocks.concat(shiftableBlocks) | 
|  | }; | 
|  | }, | 
|  | // Remove all VSyncs that didn't started an actual frame. | 
|  | function filterFramelessVSyncs(input) { | 
|  | input.blocks.sort(compareBeginTimestamp); | 
|  | var lastVSyncBlock, | 
|  | blocks, | 
|  | vSyncBlocks = []; | 
|  | blocks = input.blocks.filter(function (block) { | 
|  | if (block.isVSync) { | 
|  | lastVSyncBlock = block; | 
|  | return false; | 
|  | } else if (block.isFramework) { | 
|  | vSyncBlocks.push(lastVSyncBlock); | 
|  | } | 
|  | return true; | 
|  | }); | 
|  | return { | 
|  | timeless: input.timeless, | 
|  | blocks: blocks.concat(vSyncBlocks) | 
|  | }; | 
|  | }, | 
|  | // Group blocks by type. | 
|  | // | 
|  | // Example: | 
|  | // Input = [S1, V1, F1, V2, G1, F2, V3, G2, F3] | 
|  | // Output = { | 
|  | //   gpu: [G1, G2], | 
|  | //   vsync: [V1, V2, V3], | 
|  | //   framework: [F1, F2, F3], | 
|  | //   shiftable: [S1] | 
|  | // } | 
|  | function groupBlocksByFrames(input) { | 
|  | return { | 
|  | timeless: input.timeless, | 
|  | gpu: input.blocks.filter(function (b) { return b.isGPU; }), | 
|  | vsync: input.blocks.filter(function (b) { return b.isVSync; }), | 
|  | framework: input.blocks.filter(function (b) { return b.isFramework; }), | 
|  | shiftable: input.blocks.filter(function (b) { return b.isShiftable; }) | 
|  | }; | 
|  | }, | 
|  | // Remove possible out of sync GPU Blocks. | 
|  | // If the buffer has already delete the VSync and the Framework, but not the | 
|  | // GPU it can potentially be still alive. | 
|  | function groupBlocksByFrames(input) { | 
|  | var gpu = input.gpu, | 
|  | framework = input.framework; | 
|  | while (gpu.length && | 
|  | gpu[0].begin.args.frame !== framework[0].begin.args.frame) { | 
|  | gpu.shift(); | 
|  | } | 
|  | return input; | 
|  | }, | 
|  | // Group blocks related to the same frame. | 
|  | // Input = { | 
|  | //   gpu: [G1, G2], | 
|  | //   vsync: [V1, V2], | 
|  | //   framework: [F1, F2], | 
|  | //   shiftable: [S1] | 
|  | // } | 
|  | // Output = [{V1, F1, G1, S1}, {V2, F2, G2}, {V3, F3, G3}] | 
|  | function groupBlocksByFrames(input) { | 
|  | var shiftable = input.shiftable.slice(); | 
|  | return { | 
|  | timeless: input.timeless, | 
|  | frames: input.vsync.map(function (vsync, i) { | 
|  | var frame = { | 
|  | begin: vsync.begin, | 
|  | vsync: vsync, | 
|  | framework: input.framework[i], | 
|  | deadline: parseInt(vsync.begin.args.deadline) + 1000 | 
|  | }; | 
|  | if (i < input.gpu.length) { | 
|  | frame.gpu = input.gpu[i]; | 
|  | } | 
|  | if (shiftable.length && shiftable[0].begin.ts < framework.begin.ts ) { | 
|  | frame.shiftable = shiftable.shift(); | 
|  | } | 
|  | return frame; | 
|  | }) | 
|  | }; | 
|  | }, | 
|  | // Move Framework and GPU as back in time as possible | 
|  | // | 
|  | // Example: | 
|  | // Before | 
|  | //                               [GPU] | 
|  | //     [VSYNC] | 
|  | //  [SHIFTABLE]     [FRAMEWORK] | 
|  | // After | 
|  | //             |[GPU] | 
|  | //     [VSYNC] | | 
|  | //  [SHIFTABLE]|[FRAMEWORK] | 
|  | function shiftEvents(input) { | 
|  | input.frames.forEach(function (frame) { | 
|  | var earlierTimestamp = frame.vsync.end.ts, | 
|  | shift; | 
|  | if (frame.shiftable) { | 
|  | frame.shiftable.events.forEach(function (event) { | 
|  | if (event.tid === frame.framework.begin.tid) { | 
|  | earlierTimestamp = Math.max(earlierTimestamp, event.ts); | 
|  | } | 
|  | }); | 
|  | } | 
|  | if (frame.gpu) { | 
|  | if (frame.shiftable) { | 
|  | frame.shiftable.events.forEach(function (event) { | 
|  | if (event.tid === frame.gpu.begin.tid) { | 
|  | earlierTimestamp = Math.max(earlierTimestamp, event.ts); | 
|  | } | 
|  | }); | 
|  | } | 
|  | shift = earlierTimestamp - frame.gpu.begin.ts; | 
|  | frame.gpu.events.forEach(function (event) { | 
|  | event.ts += shift; | 
|  | }); | 
|  | } | 
|  | shift = earlierTimestamp - frame.framework.begin.ts; | 
|  | frame.framework.events.forEach(function (event) { | 
|  | event.ts += shift; | 
|  | }); | 
|  | frame.end = frame.framework.end; | 
|  | if (frame.gpu && frame.framework.end.ts < frame.gpu.end.ts) { | 
|  | frame.end = frame.gpu.end; | 
|  | } | 
|  | }); | 
|  | return input; | 
|  | }, | 
|  | // Group events in frame (precomputation for next stage). | 
|  | function groupEventsInFrame(input) { | 
|  | input.frames.forEach(function (frame) { | 
|  | var events = frame.vsync.events; | 
|  | events = events.concat(frame.framework.events); | 
|  | if (frame.gpu) { | 
|  | events = events.concat(frame.gpu.events); | 
|  | } | 
|  | if (frame.shiftable) { | 
|  | events = events.concat(frame.shiftable.events); | 
|  | } | 
|  | events.sort(compareTimestamp); | 
|  | frame.events = events; | 
|  | }); | 
|  | return input; | 
|  | }, | 
|  | // Move frames in order to do not overlap. | 
|  | // | 
|  | // Example: | 
|  | // Before | 
|  | //              |[GPU1--------------------] | 
|  | //                                     |[GPU2----] | 
|  | //     [VSYNC1] |             [VSYNC2] | | 
|  | //  [SHIFTABLE1]|[FRAMEWORK1]          |[FRAMEWORK2] | 
|  | // After | 
|  | //              |[GPU1--------------------]|         |[GPU2-----] | 
|  | //     [VSYNC1] |                          |[VSYNC2] | | 
|  | //  [SHIFTABLE1]|[FRAMEWORK1]              |         |[FRAMEWORK2] | 
|  | // OtherExample: | 
|  | // Before | 
|  | //     {FRAME BUDGET1-------------------------} | 
|  | //                            {FRAME BUDGET2-------------------------} | 
|  | //              |[GPU1] | 
|  | //                                     |[GPU2] | 
|  | //     [VSYNC1] |             [VSYNC2] | | 
|  | //  [SHIFTABLE1]|[FRAMEWORK1]          |[FRAMEWORK2] | 
|  | // After | 
|  | //     {FRAME BUDGET1-------------------------}|{FRAME BUDGET2---------------- | 
|  | //              |[GPU1]                        |         |[GPU2-----] | 
|  | //     [VSYNC1] |                              |[VSYNC2] | | 
|  | //  [SHIFTABLE1]|[FRAMEWORK1]                  |         |[FRAMEWORK2] | 
|  | function shiftBlocks(input) { | 
|  | function minThreadtimestamps(frame) { | 
|  | var timestamps = {}; | 
|  | frame.events.forEach(function (event) { | 
|  | if (event.tid != undefined) { | 
|  | timestamps[event.tid] = timestamps[event.tid] || event.ts; | 
|  | } | 
|  | }); | 
|  | return timestamps; | 
|  | } | 
|  | function maxThreadTimestamps(frame) { | 
|  | var timestamps = {}; | 
|  | frame.events.forEach(function (event) { | 
|  | if (event.tid != undefined) { | 
|  | timestamps[event.tid] = event.ts; | 
|  | } | 
|  | }); | 
|  | return timestamps; | 
|  | } | 
|  | input.frames.slice(1).forEach(function (current, index) { | 
|  | var previous = input.frames[index], | 
|  | shift = Math.max(previous.end.ts, previous.deadline) - current.begin.ts, | 
|  | maxThreadTimestamp = maxThreadTimestamps(previous), | 
|  | minThreadTimestamp = minThreadtimestamps(current); | 
|  | Object.keys(maxThreadTimestamp).forEach(function (tid) { | 
|  | if (minThreadTimestamp[tid]) { | 
|  | var delta = maxThreadTimestamp[tid] - minThreadTimestamp[tid]; | 
|  | shift = Math.max(shift, delta); | 
|  | } | 
|  | }); | 
|  | current.events.forEach(function (event) { | 
|  | event.ts += shift; | 
|  | }); | 
|  | current.deadline += shift; | 
|  | }); | 
|  | return input; | 
|  | }, | 
|  | // Add auxiliary events to frame (Frame Budget and Frame Length). | 
|  | // Example: | 
|  | // Before | 
|  | //              |[GPU1--------------------]|         |[GPU2-----] | 
|  | //     [VSYNC1] |                          |[VSYNC2] | | 
|  | //  [SHIFTABLE1]|[FRAMEWORK1]              |         |[FRAMEWORK2] | 
|  | // After | 
|  | //     [Budget1---------------------------]|[Budget2-------------------------- | 
|  | //     [Length1---------------------------]|[Length2------------] | 
|  | //              |[GPU1--------------------]|         |[GPU2-----] | 
|  | //     [VSYNC1] |                          |[VSYNC2] | | 
|  | //  [SHIFTABLE1]|[FRAMEWORK1]              |         |[FRAMEWORK2] | 
|  | function addAuxiliaryEvents(input) { | 
|  | input.frames.forEach(function (frame) { | 
|  | frame.events.unshift({ | 
|  | args: {name: "Frame Budgets"}, | 
|  | name: "thread_name", | 
|  | ph: "M", | 
|  | pid: frame.begin.pid, | 
|  | tid: "budgets", | 
|  | }); | 
|  | frame.events.unshift({ | 
|  | args: {name: "Frames"}, | 
|  | name: "thread_name", | 
|  | ph: "M", | 
|  | pid: frame.begin.pid, | 
|  | tid: "frames", | 
|  | }); | 
|  | var duration = Math.floor((frame.end.ts - frame.begin.ts) / 1000), | 
|  | frameName = "Frame " + duration + "ms"; | 
|  | frame.events = frame.events.concat({ | 
|  | ph: "B", | 
|  | name: "Frame Budget", | 
|  | cat: "budgets", | 
|  | pid: frame.begin.pid, | 
|  | tid: "budgets", | 
|  | ts: frame.begin.ts | 
|  | }, { | 
|  | ph: "E", | 
|  | name: "Frame Budget", | 
|  | cat: "budgets", | 
|  | pid: frame.begin.pid, | 
|  | tid: "budgets", | 
|  | ts: frame.deadline, | 
|  | cname: 'rail_response' | 
|  | }, { | 
|  | ph: "B", | 
|  | name: frameName, | 
|  | cat: "frames", | 
|  | pid: frame.begin.pid, | 
|  | tid: "frames", | 
|  | ts: frame.begin.ts | 
|  | }, { | 
|  | ph: "E", | 
|  | name: frameName, | 
|  | cat: "frames", | 
|  | pid: frame.begin.pid, | 
|  | tid: "frames", | 
|  | ts: frame.end.ts, | 
|  | cname: frame.end.ts > frame.deadline ? 'terrible' : 'good' | 
|  | }); | 
|  | }); | 
|  | return input; | 
|  | }, | 
|  | // Restore the events array used by catapult. | 
|  | function linearizeBlocks(input) { | 
|  | return input.frames.reduce(function (events, frame) { | 
|  | return events.concat(frame.events); | 
|  | }, input.timeless); | 
|  | } | 
|  | ]; | 
|  |  | 
|  | function basicModelEventsMap(events) { | 
|  | return basicModelEventsWaterfall.reduce(function (input, step) { | 
|  | return step(input); | 
|  | }, events); | 
|  | } | 
|  |  | 
|  | function frameModelEventsMap(events) { | 
|  | return frameModelEventsWaterfall.reduce(function (input, step) { | 
|  | return step(input); | 
|  | }, events); | 
|  | } | 
|  |  | 
|  | function updateTimeline(events) { | 
|  | if (window.location.hash.indexOf('mode=basic') > -1) { | 
|  | events = { | 
|  | 'stackFrames': events['stackFrames'], | 
|  | 'traceEvents': basicModelEventsMap(events['traceEvents']) | 
|  | }; | 
|  | } | 
|  | if (window.location.hash.indexOf('view=frame') > -1) { | 
|  | events = { | 
|  | 'stackFrames': events['stackFrames'], | 
|  | 'traceEvents': frameModelEventsMap(events['traceEvents']) | 
|  | }; | 
|  | } | 
|  | var model = new tr.Model(); | 
|  | var importer = new tr.importer.Import(model); | 
|  | var p = importer.importTracesWithProgressDialog([events]); | 
|  | p.then(onModelLoaded.bind(undefined, model), onImportFail); | 
|  | } | 
|  |  | 
|  | function showLoadingOverlay(msg) { | 
|  | if (typeof tr === "undefined") { | 
|  | return; | 
|  | } | 
|  | if (!loadingOverlay) { | 
|  | loadingOverlay = new tr.ui.b.Overlay(); | 
|  | } | 
|  | loadingOverlay.textContent = msg; | 
|  | loadingOverlay.title = 'Loading...'; | 
|  | loadingOverlay.visible = true; | 
|  | } | 
|  |  | 
|  | function hideLoadingOverlay() { | 
|  | if (!loadingOverlay) { | 
|  | return; | 
|  | } | 
|  | loadingOverlay.visible = false; | 
|  | loadingOverlay = undefined; | 
|  | } | 
|  |  | 
|  | function gotReponse() { | 
|  | pendingRequests--; | 
|  | if (pendingRequests === 0) { | 
|  | console.log('Got all timeline parts'); | 
|  | updateTimeline(traceObject); | 
|  | hideLoadingOverlay(); | 
|  | } | 
|  | } | 
|  |  | 
|  | function processTimelineResponse(response) { | 
|  | if (response.error) { | 
|  | // Maybe profiling is disabled. | 
|  | console.log('ERROR ' + response.error.message); | 
|  | } else { | 
|  | var result = response['result']; | 
|  | console.log(result); | 
|  |  | 
|  | if (result["type"] == "Timeline") { | 
|  | traceObject.traceEvents = traceObject.traceEvents.concat(result['traceEvents']); | 
|  | } else if (result["type"] == "CpuSamples") { | 
|  | var pid = result["pid"]; | 
|  | var functions = result["functions"]; | 
|  | var samples = result["samples"]; | 
|  | var frames = traceObject.stackFrames; | 
|  | var id = 0; | 
|  | for (var i = 0; i < samples.length; i++) { | 
|  | var sample = samples[i]; | 
|  | var stack = sample["stack"]; | 
|  | for (var j = 0; j < stack.length; j++) { | 
|  | var profileFunc = functions[stack[j]]; | 
|  | var func = profileFunc["function"]; | 
|  | var name = func["name"]; | 
|  | while (func["owner"] != undefined) { | 
|  | func = func["owner"]; | 
|  | name = func["name"] + "." + name; | 
|  | } | 
|  | var frame = { | 
|  | 'category': 'Dart', | 
|  | 'name': name, | 
|  | 'resolvedUrl': profileFunc["resolvedUrl"], | 
|  | }; | 
|  | if (j != 0) { | 
|  | frame['parent'] = "" + pendingRequests + "-" + id; | 
|  | } | 
|  | id++; | 
|  | frames["" + pendingRequests + "-" + id] = frame; | 
|  | } | 
|  | traceObject.traceEvents.push({ | 
|  | 'ph': 'P', // kind = sample event | 
|  | 'name': '', // Blank to keep about:tracing happy | 
|  | 'pid': pid, | 
|  | 'tid': sample['tid'], | 
|  | 'ts': sample['timestamp'], | 
|  | 'cat': 'Dart', | 
|  | 'sf': "" + pendingRequests + "-" + id, | 
|  | }); | 
|  | } | 
|  | } else { | 
|  | console.log("Unknown timeline content type"); | 
|  | } | 
|  | } | 
|  |  | 
|  | gotReponse(); | 
|  | } | 
|  |  | 
|  | function fetchTimelineOnLoad(event) { | 
|  | var xhr = event.target; | 
|  | var response = JSON.parse(xhr.responseText); | 
|  | processTimelineResponse(response); | 
|  | } | 
|  |  | 
|  | function fetchTimelineOnError(event) { | 
|  | var xhr = event.target; | 
|  | console.log(xhr.statusText); | 
|  | gotReponse(); | 
|  | } | 
|  |  | 
|  | function fetchCPUProfile(vmAddress, isolateIds, timeOrigin, timeExtent) { | 
|  | showLoadingOverlay('Fetching CPU profile(s) from Dart VM'); | 
|  | var parser = document.createElement('a'); | 
|  | parser.href = vmAddress; | 
|  | pendingRequests += isolateIds.length; | 
|  | for (var i = 0; i < isolateIds.length; i++) { | 
|  | var isolateId = isolateIds[i]; | 
|  | var requestUri = 'http://' + | 
|  | parser.hostname + | 
|  | ':' + | 
|  | parser.port + | 
|  | parser.pathname.replace(/\/ws$/, "") + | 
|  | '/getCpuSamples?tags=None&isolateId=' + | 
|  | isolateId + | 
|  | '&timeOriginMicros=' + timeOrigin + | 
|  | '&timeExtentMicros=' + timeExtent; | 
|  | fetchUri(requestUri, fetchTimelineOnLoad, fetchTimelineOnError); | 
|  | } | 
|  | } | 
|  |  | 
|  | function fetchTimeline(vmAddress, isolateIds, mode) { | 
|  | // Reset combined timeline. | 
|  | traceObject = { | 
|  | 'stackFrames': {}, | 
|  | 'traceEvents': [] | 
|  | }; | 
|  | timelineMode = mode; | 
|  | pendingRequests = 1; | 
|  |  | 
|  | showLoadingOverlay('Fetching timeline from Dart VM'); | 
|  | var parser = document.createElement('a'); | 
|  | parser.href = vmAddress; | 
|  | var requestUri = 'http://' + | 
|  | parser.hostname + | 
|  | ':' + | 
|  | parser.port + | 
|  | parser.pathname.replace(/\/ws$/, "") + | 
|  | '/getVMTimeline'; | 
|  | fetchUri(requestUri, function(event) { | 
|  | // Grab the response. | 
|  | var xhr = event.target; | 
|  | var response = JSON.parse(xhr.responseText); | 
|  | // Extract the time origin and extent. | 
|  | var timeOrigin = response['result']['timeOriginMicros']; | 
|  | var timeExtent = response['result']['timeExtentMicros']; | 
|  | console.assert(Number.isInteger(timeOrigin), timeOrigin); | 
|  | console.assert(Number.isInteger(timeExtent), timeExtent); | 
|  | console.log(timeOrigin); | 
|  | console.log(timeExtent); | 
|  | // fetchCPUProfile. | 
|  | fetchCPUProfile(vmAddress, isolateIds, timeOrigin, timeExtent); | 
|  | // This must happen after 'fetchCPUProfile'; | 
|  | processTimelineResponse(response, mode); | 
|  | }, fetchTimelineOnError); | 
|  | } | 
|  |  | 
|  | function populateTimeline() { | 
|  | updateTimeline(traceObject); | 
|  | hideLoadingOverlay(); | 
|  | } | 
|  |  | 
|  | function saveTimeline() { | 
|  | if (pendingRequests > 0) { | 
|  | var overlay = new tr.ui.b.Overlay(); | 
|  | overlay.textContent = 'Cannot save timeline while fetching one.'; | 
|  | overlay.title = 'Save error'; | 
|  | overlay.visible = true; | 
|  | console.log('cannot save timeline while fetching one.'); | 
|  | return; | 
|  | } | 
|  | if (!traceObject || | 
|  | !traceObject.traceEvents || | 
|  | (traceObject.traceEvents.length === 0)) { | 
|  | var overlay = new tr.ui.b.Overlay(); | 
|  | overlay.textContent = 'Cannot save an empty timeline.'; | 
|  | overlay.title = 'Save error'; | 
|  | overlay.visible = true; | 
|  | console.log('Cannot save an empty timeline.'); | 
|  | return; | 
|  | } | 
|  | var blob = new Blob([JSON.stringify(traceObject)], | 
|  | {type: 'application/json'}); | 
|  | var blobUrl = URL.createObjectURL(blob); | 
|  | var link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a'); | 
|  | link.href = blobUrl; | 
|  | var now = new Date(); | 
|  | var defaultFilename = 'dart-timeline-' + | 
|  | now.getFullYear() + | 
|  | '-' + | 
|  | (now.getMonth() + 1) + | 
|  | '-' + | 
|  | now.getDate() + | 
|  | '.json'; | 
|  | var filename = window.prompt('Save as', defaultFilename); | 
|  | if (filename) { | 
|  | link.download = filename; | 
|  | link.click(); | 
|  | } | 
|  | } | 
|  |  | 
|  | function loadTimeline() { | 
|  | if (pendingRequests > 0) { | 
|  | var overlay = new tr.ui.b.Overlay(); | 
|  | overlay.textContent = 'Cannot load timeline while fetching one.'; | 
|  | overlay.title = 'Save error'; | 
|  | overlay.visible = true; | 
|  | console.log('Cannot load timeline while fetching one.'); | 
|  | return; | 
|  | } | 
|  | var inputElement = document.createElement('input'); | 
|  | inputElement.type = 'file'; | 
|  | inputElement.multiple = false; | 
|  |  | 
|  | var changeFired = false; | 
|  | inputElement.addEventListener('change', function(e) { | 
|  | if (changeFired) | 
|  | return; | 
|  | changeFired = true; | 
|  |  | 
|  | var file = inputElement.files[0]; | 
|  | var reader = new FileReader(); | 
|  | reader.onload = function(event) { | 
|  | try { | 
|  | traceObject = JSON.parse(event.target.result); | 
|  | updateTimeline(traceObject); | 
|  | } catch (error) { | 
|  | tr.ui.b.Overlay.showError('Error while loading file: ' + error); | 
|  | } | 
|  | }; | 
|  | reader.onerror = function(event) { | 
|  | tr.ui.b.Overlay.showError('Error while loading file: ' + event); | 
|  | }; | 
|  | reader.onabort = function(event) { | 
|  | tr.ui.b.Overlay.showError('Error while loading file: ' + event); | 
|  | } | 
|  | reader.readAsText(file); | 
|  | }); | 
|  | inputElement.click(); | 
|  | } | 
|  |  | 
|  | function refreshTimeline() { | 
|  | fetchTimeline(timeline_vm_address, timeline_isolates); | 
|  | } | 
|  |  | 
|  | window.addEventListener('DOMContentLoaded', function() { | 
|  | var container = document.createElement('track-view-container'); | 
|  | container.id = 'track_view_container'; | 
|  | viewer = document.createElement('tr-ui-timeline-view'); | 
|  | viewer.track_view_container = container; | 
|  | viewer.appendChild(container); | 
|  | viewer.id = 'trace-viewer'; | 
|  | viewer.globalMode = true; | 
|  | document.body.appendChild(viewer); | 
|  | timeline_loaded = true; | 
|  | console.log('DOMContentLoaded'); | 
|  | document.getElementById('trace-viewer').highlightVSync = true; | 
|  | if (timeline_vm_address != undefined) { | 
|  | refreshTimeline(); | 
|  | timeline_vm_address = undefined; | 
|  | } | 
|  | }); | 
|  |  | 
|  | console.log('timeline.js loaded'); |