progress_event_listener.js revision 5821806d5e7f356e8fa4b058a389a808ea183019
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
3// found in the LICENSE file.
4
5// Class to track the progress events received by a particular plugin instance.
6function EventStateMachine() {
7  // Work around how JS binds 'this'.
8  var this_ = this;
9  // Given a particular state, what are the acceptable event types.
10  this.expectedNext = {
11    'BEGIN': { 'loadstart': 1 },
12    'loadstart': { 'progress': 1, 'error': 1, 'abort': 1, 'load': 1 },
13    'progress': { 'progress': 1, 'error': 1, 'abort': 1, 'load': 1 },
14    'error': { 'loadend': 1 },
15    'abort': { 'loadend': 1 },
16    'load': { 'loadend': 1 },
17    'loadend': { },
18    'UNEXPECTED': { },
19  };
20  // The current state (and index into expectedNext).
21  this.currentState = 'BEGIN';
22  // For each recognized state, a count of the times it was reached.
23  this.stateHistogram = {
24    'BEGIN': 0,
25    'loadstart': 0,
26    'progress': 0,
27    'error': 0,
28    'abort': 0,
29    'load': 0,
30    'loadend': 0,
31    'UNEXPECTED': 0
32  };
33  // The state transition function.
34  this.transitionTo = function(event_type) {
35    // The index values of this_.expectedNext are the only valid states.
36    // Invalid event types are normalized to 'UNEXPECTED'.
37    if (this_.expectedNext[event_type] == undefined) {
38      console.log('unexpected ' + event_type);
39      event_type = 'UNEXPECTED';
40    }
41    // Check that the next event type is expected from the current state.
42    // If not, we transition to the state 'UNEXPECTED'.
43    if (!(event_type in this_.expectedNext[this_.currentState])) {
44      console.log('unexpected ' + event_type + ' from ' + this_.currentState);
45      event_type = 'UNEXPECTED';
46    }
47    this_.currentState = event_type;
48    this_.stateHistogram[this_.currentState]++;
49  }
50}
51
52// event_machines is a collection of EventStateMachines, one for each element
53// id that dispatches an event of a type we are listening for.
54window.event_machines = { };
55// Look up the EventStateMachine for the id.
56function lookupEventMachine(element_id) {
57  var event_machine = window.event_machines[element_id];
58  if (event_machine == undefined) {
59    // This is the first event for this target.  Create an EventStateMachine.
60    event_machine = new EventStateMachine();
61    window.event_machines[element_id] = event_machine;
62  }
63  return event_machine;
64}
65// Sets up event listeners on the body element for all the progress
66// event types.  Delegation to the body allows this to be done only once
67// per document.
68var setListeners = function(body_element) {
69  var eventListener = function(e) {
70    // Find the target element of the event.
71    var target_element = e.target;
72    // Body only dispatches for elements having the 'naclModule' CSS class.
73    if (target_element.className != 'naclModule') {
74      return;
75    }
76    var element_id = target_element.id;
77    // Look up the EventStateMachine for the target of the event.
78    var event_machine = lookupEventMachine(element_id);
79    // Update the state of the machine.
80    event_machine.transitionTo(e.type);
81  }
82  // Add the listener for all of the ProgressEvent event types.
83  body_element.addEventListener('loadstart', eventListener, true);
84  body_element.addEventListener('progress', eventListener, true);
85  body_element.addEventListener('error', eventListener, true);
86  body_element.addEventListener('abort', eventListener, true);
87  body_element.addEventListener('load', eventListener, true);
88  body_element.addEventListener('loadend', eventListener, true);
89}
90
91// Performs some tests to make sure that progress events follow the expected
92// state transitions to end in an expected state.
93function testProgressEventStateMachine(tester,
94                                       embedId,
95                                       progressMinCount,
96                                       errorCount,
97                                       abortCount,
98                                       loadCount,
99                                       lastError) {
100  var eventMachine = lookupEventMachine(embedId);
101  // Test the expected number of occurrences, with some duplication.
102  tester.addTest('begin_count_' + embedId, function() {
103    // There should be no 'BEGIN' event.
104    assertEqual(eventMachine.stateHistogram['BEGIN'], 0);
105  });
106  tester.addTest('loadstart_count_' + embedId, function() {
107    // There should be one 'loadstart' event.
108    assertEqual(eventMachine.stateHistogram['loadstart'], 1);
109  });
110  tester.addTest('progress_min_count_' + embedId, function() {
111    // There should be at least one progress event when the manifest file is
112    // loaded and another when the .nexe is loaded.
113    assert(eventMachine.stateHistogram['progress'] >= progressMinCount);
114  });
115  tester.addTest('error_count_' + embedId, function() {
116    // Check that the right number of 'error' events were dispatched.
117    assertEqual(eventMachine.stateHistogram['error'], errorCount);
118  });
119  tester.addTest('abort_count_' + embedId, function() {
120    // Check that the right number of 'abort' events were dispatched.
121    assertEqual(eventMachine.stateHistogram['abort'], abortCount);
122  });
123  tester.addTest('load_count_' + embedId, function() {
124    // Check that the right number of 'load' events were dispatched.
125    assertEqual(eventMachine.stateHistogram['load'], loadCount);
126  })
127  tester.addTest('loadend_count_' + embedId, function() {
128    // There should be one 'loadend' event.
129    assertEqual(eventMachine.stateHistogram['loadend'], 1);
130  });
131  tester.addTest('unexpected_count_' + embedId, function() {
132    // There should be no 'UNEXPECTED' event.
133    assertEqual(eventMachine.stateHistogram['UNEXPECTED'], 0);
134  });
135  tester.addTest('end_state_' + embedId, function() {
136    // Test that the progress events followed the expected sequence to
137    // completion in the 'loadend' state.
138    assertEqual(eventMachine.currentState, 'loadend');
139  });
140  tester.addTest('last_error_string_' + embedId, function() {
141    // If an error or abort was reported, check that lastError is set
142    // to the correct value.
143    if ((eventMachine.stateHistogram['error'] > 0 ||
144         eventMachine.stateHistogram['abort'] > 0)) {
145      var embed = $(embedId);
146      assertEqual(embed.lastError, lastError);
147    }
148  });
149}
150