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.
5 // Class to track the progress events received by a particular plugin instance.
6 function EventStateMachine() {
7 // Work around how JS binds 'this'.
9 // Given a particular state, what are the acceptable event types.
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 },
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 = {
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';
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';
47 this_.currentState = event_type;
48 this_.stateHistogram[this_.currentState]++;
51 // True if an event with lengthComputable is ever triggered.
52 this.stateSawLengthComputable = false;
53 // The last event.total seen from an event with lengthComputable being true.
54 this.stateProgressTotal = -1;
55 // The last event.loaded seen from an event with lengthComputable being true.
56 this.stateProgressPrev = -1;
57 // Function to record progress stats.
58 this.recordProgress = function(event) {
59 // Can either record progress from a progress event with lengthComputable,
60 // or from a loadend event.
61 if (event.type == 'progress' && event.lengthComputable) {
62 this.stateSawLengthComputable = true;
63 this.stateProgressTotal = event.total;
64 this.stateProgressPrev = event.loaded;
65 } else if (event.type == 'loadend' && event.lengthComputable) {
66 this.stateProgressTotal = event.total;
67 this.stateProgressPrev = event.loaded;
72 // event_machines is a collection of EventStateMachines, one for each element
73 // id that dispatches an event of a type we are listening for.
74 window.event_machines = { };
75 // Look up the EventStateMachine for the id.
76 function lookupEventMachine(element_id) {
77 var event_machine = window.event_machines[element_id];
78 if (event_machine == undefined) {
79 // This is the first event for this target. Create an EventStateMachine.
80 event_machine = new EventStateMachine();
81 window.event_machines[element_id] = event_machine;
85 // Sets up event listeners on the body element for all the progress
86 // event types. Delegation to the body allows this to be done only once
88 var setListeners = function(body_element) {
89 var eventListener = function(e) {
90 // Find the target element of the event.
91 var target_element = e.target;
92 // Body only dispatches for elements having the 'naclModule' CSS class.
93 if (target_element.className != 'naclModule') {
96 var element_id = target_element.id;
97 // Look up the EventStateMachine for the target of the event.
98 var event_machine = lookupEventMachine(element_id);
99 // Update the state of the machine.
100 event_machine.transitionTo(e.type);
101 // Record progress information if possible.
102 event_machine.recordProgress(e);
104 // Add the listener for all of the ProgressEvent event types.
105 body_element.addEventListener('loadstart', eventListener, true);
106 body_element.addEventListener('progress', eventListener, true);
107 body_element.addEventListener('error', eventListener, true);
108 body_element.addEventListener('abort', eventListener, true);
109 body_element.addEventListener('load', eventListener, true);
110 body_element.addEventListener('loadend', eventListener, true);
113 // Performs some tests to make sure that progress events follow the expected
114 // state transitions to end in an expected state.
115 function testProgressEventStateMachine(tester,
122 var eventMachine = lookupEventMachine(embedId);
123 // Test the expected number of occurrences, with some duplication.
124 tester.addTest('begin_count_' + embedId, function() {
125 // There should be no 'BEGIN' event.
126 assertEqual(eventMachine.stateHistogram['BEGIN'], 0);
128 tester.addTest('loadstart_count_' + embedId, function() {
129 // There should be one 'loadstart' event.
130 assertEqual(eventMachine.stateHistogram['loadstart'], 1);
132 tester.addTest('progress_min_count_' + embedId, function() {
133 // There should be at least one progress event when the manifest file is
134 // loaded and another when the .nexe is loaded.
135 assert(eventMachine.stateHistogram['progress'] >= progressMinCount);
137 tester.addTest('progress_samples_' + embedId, function() {
138 console.log('stateSawLengthComputable ' +
139 eventMachine.stateSawLengthComputable);
140 console.log('stateProgressPrev ' +
141 eventMachine.stateProgressPrev);
142 console.log('stateProgressTotal ' +
143 eventMachine.stateProgressTotal);
145 assert(eventMachine.stateSawLengthComputable);
146 // Progress events are not necessarily monotonic. For glibc, each DSO
147 // will trigger a different series of progress events with different totals.
148 // For glibc, the final loadend progress event may even correspond to
149 // the very first load event, instead of corresponding to the last...
150 // So, all we check is that the latest values make some sense.
151 assert(eventMachine.stateProgressPrev > 0);
152 assert(eventMachine.stateProgressTotal > 0);
153 assert(eventMachine.stateProgressPrev <= eventMachine.stateProgressTotal);
155 tester.addTest('error_count_' + embedId, function() {
156 // Check that the right number of 'error' events were dispatched.
157 assertEqual(eventMachine.stateHistogram['error'], errorCount);
159 tester.addTest('abort_count_' + embedId, function() {
160 // Check that the right number of 'abort' events were dispatched.
161 assertEqual(eventMachine.stateHistogram['abort'], abortCount);
163 tester.addTest('load_count_' + embedId, function() {
164 // Check that the right number of 'load' events were dispatched.
165 assertEqual(eventMachine.stateHistogram['load'], loadCount);
167 tester.addTest('loadend_count_' + embedId, function() {
168 // There should be one 'loadend' event.
169 assertEqual(eventMachine.stateHistogram['loadend'], 1);
171 tester.addTest('unexpected_count_' + embedId, function() {
172 // There should be no 'UNEXPECTED' event.
173 assertEqual(eventMachine.stateHistogram['UNEXPECTED'], 0);
175 tester.addTest('end_state_' + embedId, function() {
176 // Test that the progress events followed the expected sequence to
177 // completion in the 'loadend' state.
178 assertEqual(eventMachine.currentState, 'loadend');
180 tester.addTest('last_error_string_' + embedId, function() {
181 // If an error or abort was reported, check that lastError is set
182 // to the correct value.
183 if ((eventMachine.stateHistogram['error'] > 0 ||
184 eventMachine.stateHistogram['abort'] > 0)) {
185 var embed = $(embedId);
186 assertEqual(embed.lastError, lastError);