blob: ab1810cb322d46ddbe8601ab08b03c2b566f128d [file] [log] [blame]
<!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="/extras/rail/rail_interaction_record.html">
<script>
'use strict';
/**
* @fileoverview The Response phase of RAIL.
*/
tr.exportTo('tr.e.rail', function() {
function ResponseInteractionRecord(start, duration) {
tr.e.rail.RAILInteractionRecord.call(
this, 'Response', 'rail_response',
start, duration);
}
ResponseInteractionRecord.prototype = {
__proto__: tr.e.rail.RAILInteractionRecord.prototype,
get normalizedUserPain() {
// User pain is derived from the time between when the user thinks they
// begin an interation (expectedStart) and the time when the screen first
// changes to reflect the interaction (actualEnd). There may be a delay
// between expectedStart and when chrome first starts processing the
// interaction (actualStart) if the main thread is busy. The user doesn't
// know when actualStart is, they only know when expectedStart is. User
// pain, by definition, considers only what the user experiences, so
// "duration" is defined as actualEnd - expectedStart.
// User pain is hypothesized to rise exponentially as duration rises
// between 150ms and 1000ms.
// TODO(benjhayden, nduca) Experiment to validate this hypothesis.
// This function is graphed at
// https://www.desmos.com/calculator/fqhkz1ohia
var PAIN_BEGINS_AT_MS = 150;
var PAIN_PEAKS_AT_MS = 1000;
// PAIN_POWER is a tunable parameter that affects the shape of this
// function. It must be greater than 0. At about 0.7, this function is
// approximately linear, so if, in the course of tuning this parameter,
// you want to set it close to 0.7, then you might as well replace this
// entire function with a call to lerp(). If you want to set it close to
// 1, then you might as well skip the Math.pow() step in
// computeRawUserPain() altogether.
var PAIN_POWER = 1.5;
// Prevent raising a negative number to a fractional power, which produces
// NaN.
if (this.duration < PAIN_BEGINS_AT_MS)
return 0;
function computeRawUserPain(durationMs) {
var painDurationMs = durationMs - PAIN_BEGINS_AT_MS;
// Convert ms to seconds because this value is about to be passed
// to Math.exp(), so it must be small enough that Math.exp() will not
// run out of IEEE754 exponent bits and return Infinity.
var MS_PER_SECONDS = 1000;
var painDurationSeconds = painDurationMs / MS_PER_SECONDS;
// Raising painDurationSeconds to a non-unity power makes things more
// interesting and gives us a parameter to tune to change the shape of
// the function.
var tunedPainDurationSeconds = Math.pow(painDurationSeconds,
PAIN_POWER);
// exp() has some nice mathematical properties, but we could vary the
// base from e to some other positive non-unity number for another way
// to change the shape of the function if necessary.
return Math.exp(tunedPainDurationSeconds);
// (If you modify this function to be non-monotonic anywhere, then you
// should consider how that affects the clamp() at the end.)
}
var rawUserPain = computeRawUserPain(this.duration);
var minRawUserPain = computeRawUserPain(PAIN_BEGINS_AT_MS);
var maxRawUserPain = computeRawUserPain(PAIN_PEAKS_AT_MS);
var normalizedUserPain = tr.b.normalize(
rawUserPain, minRawUserPain, maxRawUserPain);
// normalizedUserPain might actually be negative if this.duration is less
// than PAIN_BEGINS_AT_MS, and it might be greater than 1 if this.duration
// is greater than PAIN_PEAKS_AT_MS, so clamp it to between 0 and 1.
return tr.b.clamp(normalizedUserPain, 0, 1);
},
get normalizedEfficiency() {
return 1;
}
};
return {
ResponseInteractionRecord: ResponseInteractionRecord
};
});
</script>