tenfourfox/dom/wifi/StateMachine.jsm

206 lines
5.4 KiB
JavaScript

/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
this.EXPORTED_SYMBOLS = ["StateMachine"];
const DEBUG = false;
this.StateMachine = function(aDebugTag) {
function debug(aMsg) {
dump('-------------- StateMachine:' + aDebugTag + ': ' + aMsg);
}
var sm = {};
var _initialState;
var _curState;
var _prevState;
var _paused;
var _eventQueue = [];
var _deferredEventQueue = [];
var _defaultEventHandler;
// Public interfaces.
sm.setDefaultEventHandler = function(aDefaultEventHandler) {
_defaultEventHandler = aDefaultEventHandler;
};
sm.start = function(aInitialState) {
_initialState = aInitialState;
sm.gotoState(_initialState);
};
sm.sendEvent = function (aEvent) {
if (!_initialState) {
if (DEBUG) {
debug('StateMachine is not running. Call StateMachine.start() first.');
}
return;
}
_eventQueue.push(aEvent);
asyncCall(handleFirstEvent);
};
sm.getPreviousState = function() {
return _prevState;
};
sm.getCurrentState = function() {
return _curState;
};
// State object maker.
// @param aName string for this state's name.
// @param aDelegate object:
// .handleEvent: required.
// .enter: called before entering this state (optional).
// .exit: called before exiting this state (optional).
sm.makeState = function (aName, aDelegate) {
if (!aDelegate.handleEvent) {
throw "handleEvent is a required delegate function.";
}
var nop = function() {};
return {
name: aName,
enter: (aDelegate.enter || nop),
exit: (aDelegate.exit || nop),
handleEvent: aDelegate.handleEvent
};
};
sm.deferEvent = function (aEvent) {
// The definition of a 'deferred event' is:
// We are not able to handle this event now but after receiving
// certain event or entering a new state, we might be able to handle
// it. For example, we couldn't handle CONNECT_EVENT in the
// diconnecting state. But once we finish doing "disconnecting", we
// could then handle CONNECT_EVENT!
//
// So, the deferred event may be handled in the following cases:
// 1. Once we entered a new state.
// 2. Once we handled a regular event.
if (DEBUG) {
debug('Deferring event: ' + JSON.stringify(aEvent));
}
_deferredEventQueue.push(aEvent);
};
// Goto the new state. If the current state is null, the exit
// function won't be called.
sm.gotoState = function (aNewState) {
if (_curState) {
if (DEBUG) {
debug("exiting state: " + _curState.name);
}
_curState.exit();
}
_prevState = _curState;
_curState = aNewState;
if (DEBUG) {
debug("entering state: " + _curState.name);
}
_curState.enter();
// We are in the new state now. We got a chance to handle the
// deferred events.
handleDeferredEvents();
sm.resume();
};
// No incoming event will be handled after you call pause().
// (But they will be queued.)
sm.pause = function() {
_paused = true;
};
// Continue to handle incoming events.
sm.resume = function() {
_paused = false;
asyncCall(handleFirstEvent);
};
//----------------------------------------------------------
// Private stuff
//----------------------------------------------------------
function asyncCall(f) {
Services.tm.currentThread.dispatch(f, Ci.nsIThread.DISPATCH_NORMAL);
}
function handleFirstEvent() {
var hadDeferredEvents;
if (0 === _eventQueue.length) {
return;
}
if (_paused) {
return; // The state machine is paused now.
}
hadDeferredEvents = _deferredEventQueue.length > 0;
handleOneEvent(_eventQueue.shift()); // The handler may defer this event.
// We've handled one event. If we had deferred events before, now is
// a good chance to handle them.
if (hadDeferredEvents) {
handleDeferredEvents();
}
// Continue to handle the next regular event.
handleFirstEvent();
}
function handleDeferredEvents() {
if (_deferredEventQueue.length && DEBUG) {
debug('Handle deferred events: ' + _deferredEventQueue.length);
}
for (let i = 0; i < _deferredEventQueue.length; i++) {
handleOneEvent(_deferredEventQueue.shift());
}
}
function handleOneEvent(aEvent)
{
if (DEBUG) {
debug('Handling event: ' + JSON.stringify(aEvent));
}
var handled = _curState.handleEvent(aEvent);
if (undefined === handled) {
throw "handleEvent returns undefined: " + _curState.name;
}
if (!handled) {
// Event is not handled in the current state. Try handleEventCommon().
handled = (_defaultEventHandler ? _defaultEventHandler(aEvent) : handled);
}
if (undefined === handled) {
throw "handleEventCommon returns undefined: " + _curState.name;
}
if (!handled) {
if (DEBUG) {
debug('!!!!!!!!! FIXME !!!!!!!!! Event not handled: ' + JSON.stringify(aEvent));
}
}
return handled;
}
return sm;
};