| <!DOCTYPE html> |
| <!-- |
| Copyright (c) 2015 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. |
| --> |
| <link rel="import" href="/base/base.html"> |
| <link rel="import" href="/core/auditor.html"> |
| <link rel="import" href="/core/trace_model/event_info.html"> |
| <link rel="import" href="/extras/audits/utils.html"> |
| <link rel="import" href="/extras/audits/chrome_model_helper.html"> |
| <link rel="import" href="/extras/audits/rail_interaction_record.html"> |
| |
| <script> |
| 'use strict'; |
| |
| /** |
| * @fileoverview Base class for trace data Auditors. |
| */ |
| tv.exportTo('tv.e.audits', function() { |
| function RAILIRFinder(model, modelHelper) { |
| this.model = model; |
| this.modelHelper = modelHelper; |
| }; |
| |
| RAILIRFinder.supportsModelHelper = function(modelHelper) { |
| return modelHelper.browser !== undefined; |
| }; |
| |
| RAILIRFinder.prototype = { |
| getAllLatencyEvents: function() { |
| return this.modelHelper.browser.getLatencyEventsInRange( |
| this.model.bounds); |
| }, |
| |
| getAllGestures: function() { |
| var allLatencyEvents = this.getAllLatencyEvents(); |
| |
| function latencyEventToStartPoint(latencyEvent) { |
| return { |
| start: latencyEvent.start, |
| end: latencyEvent.start, |
| latencyEvent: latencyEvent |
| }; |
| } |
| var latencyStartPoints = allLatencyEvents.map(latencyEventToStartPoint); |
| |
| function joinStartPointsIntoGesture(startPoints) { |
| var latencyEvents = startPoints.map(function(startPoint) { |
| return startPoint.latencyEvent; |
| }); |
| |
| var e0 = latencyEvents[0]; |
| var eN = latencyEvents[latencyEvents.length - 1]; |
| |
| var gesture = { |
| expectedStart: e0.start, |
| expectedEnd: eN.start, |
| latencyEvents: latencyEvents |
| }; |
| return gesture; |
| } |
| |
| return tv.e.audits.mergeEvents( |
| latencyStartPoints, 250, joinStartPointsIntoGesture); |
| }, |
| |
| getAllExpectedFlingingRanges: function() { |
| if (this.modelHelper.browser === undefined) |
| return; |
| var ops = []; |
| var FLING_OP_START = 'start'; |
| var FLING_OP_CANCEL = 'cancel'; |
| var FLING_OP_ANIM_END = 'anim-end'; |
| |
| |
| // Add ops for input fling start & cancel. |
| var allLatencyEvents = this.getAllLatencyEvents(); |
| allLatencyEvents.forEach(function(latencyEvent) { |
| if (latencyEvent.subSlices.length == 0) |
| return; |
| var s0title = latencyEvent.subSlices[0].title; |
| if (s0title === 'InputLatency:GestureFlingStart') { |
| ops.push({ |
| op: FLING_OP_START, guid: latencyEvent.guid, |
| opTs: latencyEvent.start, |
| latencyEvent: latencyEvent |
| }); |
| } else if (s0title === 'InputLatency:GestureFlingCancel') { |
| ops.push({ |
| op: FLING_OP_CANCEL, guid: latencyEvent.guid, |
| opTs: latencyEvent.start, |
| latencyEvent: latencyEvent |
| }); |
| } |
| }); |
| |
| // Add ops for fling animations. |
| function isFlingAnimationEvent(event) { |
| return event.title === 'InputHandlerProxy::HandleGestureFling::started'; |
| }; |
| var browserHelper = this.modelHelper.browser; |
| var allFlingAnimationEvents = browserHelper.getAllAsyncSlicesMatching( |
| isFlingAnimationEvent); |
| allFlingAnimationEvents.forEach(function(flingAnimationEvent) { |
| ops.push({ |
| op: FLING_OP_ANIM_END, guid: flingAnimationEvent.guid, |
| opTs: flingAnimationEvent.end, |
| flingAnimationEvent: flingAnimationEvent |
| }); |
| }); |
| |
| // Sort ops, stably. |
| ops.sort(function(x, y) { |
| var delta = x.opTs - y.opTs; |
| if (delta != 0) |
| return delta; |
| return x.guid - y.guid; |
| }); |
| |
| // Create expected-to-fling ranges based on UX expectations. |
| var allExpectedFlingRanges = []; |
| var currentFling = undefined; |
| ops.forEach(function(op) { |
| switch (op.op) { |
| case FLING_OP_START: |
| if (currentFling) |
| throw new Error('OMG THERE IS ALREADY A FLING GOING WAT IS THIS'); |
| currentFling = { |
| expectedStart: op.latencyEvent.start, |
| actualStart: op.latencyEvent.end, |
| expectedEnd: undefined, |
| actualEnd: undefined |
| }; |
| break; |
| case FLING_OP_CANCEL: |
| if (!currentFling) |
| return; |
| currentFling.expectedEnd = op.latencyEvent.start; |
| currentFling.actualEnd = op.latencyEvent.end; |
| allExpectedFlingRanges.push(currentFling); |
| currentFling = undefined; |
| break; |
| case FLING_OP_ANIM_END: |
| if (!currentFling) |
| return; |
| currentFling.expectedEnd = op.flingAnimationEvent.end; |
| currentFling.actualEnd = op.flingAnimationEvent.end; |
| allExpectedFlingRanges.push(currentFling); |
| currentFling = undefined; |
| break; |
| } |
| }); |
| |
| if (currentFling) { |
| currentFling.expectedEnd = this.model.bounds.max; |
| currentFling.actualEnd = this.model.bounds.max; |
| allExpectedFlingRanges.push(currentFling); |
| currentFling = undefined; |
| } |
| return allExpectedFlingRanges; |
| }, |
| |
| getAllExpected60Ranges: function() { |
| return this.getAllExpectedFlingingRanges(); |
| }, |
| |
| findGestureTapDowns: function() { |
| // InputLatency:GestureTapDown/ |
| }, |
| |
| findAllInteractionRecords: function() { |
| var rirs = []; |
| var gestures = this.getAllGestures(); |
| var gestureIRs = gestures.map(function gestureToIR(gesture) { |
| var colorId = tv.b.ui.getColorIdForGeneralPurposeString('mt_input'); |
| var ir = new tv.e.audits.RAILInteractionRecord( |
| 'railInput', colorId, |
| gesture.expectedStart, gesture.expectedEnd - gesture.expectedStart); |
| ir.associatedEvents.push.apply( |
| ir.associatedEvents, |
| gesture.latencyEvents); |
| return ir; |
| }); |
| rirs.push.apply(rirs, gestureIRs); |
| |
| var flings = this.getAllExpected60Ranges(); |
| var flingIRs = flings.map(function flingToIR(e60r) { |
| var colorId = tv.b.ui.getColorIdForGeneralPurposeString('mt_fling'); |
| var ir = new tv.e.audits.RAILInteractionRecord( |
| 'railFling', colorId, |
| e60r.expectedStart, e60r.expectedEnd - e60r.expectedStart); |
| return ir; |
| }); |
| rirs.push.apply(rirs, flingIRs); |
| |
| return rirs; |
| } |
| }; |
| |
| return { |
| RAILIRFinder: RAILIRFinder |
| }; |
| }); |
| </script> |