Home | History | Annotate | Download | only in ppapi_browser
      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.
      6 function 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   // 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;
     68     }
     69   }
     70 }
     71 
     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;
     82   }
     83   return event_machine;
     84 }
     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
     87 // per document.
     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') {
     94       return;
     95     }
     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);
    103   }
    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);
    111 }
    112 
    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,
    116                                        embedId,
    117                                        progressMinCount,
    118                                        errorCount,
    119                                        abortCount,
    120                                        loadCount,
    121                                        lastError) {
    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);
    127   });
    128   tester.addTest('loadstart_count_' + embedId, function() {
    129     // There should be one 'loadstart' event.
    130     assertEqual(eventMachine.stateHistogram['loadstart'], 1);
    131   });
    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);
    136   });
    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);
    144 
    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);
    154   });
    155   tester.addTest('error_count_' + embedId, function() {
    156     // Check that the right number of 'error' events were dispatched.
    157     assertEqual(eventMachine.stateHistogram['error'], errorCount);
    158   });
    159   tester.addTest('abort_count_' + embedId, function() {
    160     // Check that the right number of 'abort' events were dispatched.
    161     assertEqual(eventMachine.stateHistogram['abort'], abortCount);
    162   });
    163   tester.addTest('load_count_' + embedId, function() {
    164     // Check that the right number of 'load' events were dispatched.
    165     assertEqual(eventMachine.stateHistogram['load'], loadCount);
    166   })
    167   tester.addTest('loadend_count_' + embedId, function() {
    168     // There should be one 'loadend' event.
    169     assertEqual(eventMachine.stateHistogram['loadend'], 1);
    170   });
    171   tester.addTest('unexpected_count_' + embedId, function() {
    172     // There should be no 'UNEXPECTED' event.
    173     assertEqual(eventMachine.stateHistogram['UNEXPECTED'], 0);
    174   });
    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');
    179   });
    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);
    187     }
    188   });
    189 }
    190