"use strict"; // Copyright 2012 United States Government, as represented by the Secretary of Defense, Under // Secretary of Defense (Personnel & Readiness). // // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software distributed under the License // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express // or implied. See the License for the specific language governing permissions and limitations under // the License. /// @module vwf/kernel/model /// @requires vwf/model define( [ "module", "vwf/model" ], function( module, model ) { return model.load( module, { // == Module Definition ==================================================================== initialize: function() { this.state.enabled = true; // kernel reentry allowed? this.state.blocked = false; // kernel reentry attempted? }, /// Allow kernel reentry from the drivers. enable: function() { this.state.enabled = true; this.state.blocked = false; }, /// Prevent kernel reentry from the drivers. disable: function() { this.state.enabled = false; this.state.blocked = false; }, /// Indicate if kernel reentry is currently enabled. enabled: function() { return this.state.enabled; }, /// Indicate if kernel reentry is currently disabled. disabled: function() { return ! this.state.enabled; }, /// Indicate if a driver attempted to call back into the kernel while reentry was disabled, /// and clear the *blocked* flag. blocked: function() { var blocked = this.state.blocked; this.state.blocked = false; return blocked; }, /// Invoke a task and record any async actions that it initiates. After the actions have /// completed, execute their callbacks, then call a completion callback. /// /// @param {Function} task /// The task to execute and monitor for async actions. `task` is invoked with no /// arguments. /// @param {Function} callback /// Invoked after the async actions have completed. /// @param {Object} [that] /// The `this` value for the `task` and `callback` functions. capturingAsyncs: function( task, callback, that ) { // Create an array to capture the callbacks and results from async actions. When // `this.state.asyncs` exists, async actions hand off their callbacks to `asyncs.defer`. var asyncs = this.state.asyncs = []; asyncs.defer = defer; asyncs.completed = 0; asyncs.callback = callback; asyncs.that = that; // Invoke the task. task.call( that ); // Detach the array from `this.state.asyncs` to stop capturing async actions. this.state.asyncs = undefined; // If there were no async actions, call the completion callback immediately. if ( asyncs.completed == asyncs.length ) { asyncs.callback.call( asyncs.that ); } /// The `this.state.asyncs` array `defer` method. /// /// Wrap a callback function with a new function that will defer the original callback /// until a collection of actions have completed, then call the deferred callbacks /// followed by a completion callback. function defer( callback /* result */ ) { var self = this; // Save the original callback. The wrapping callback will save the result here when // received. var deferred = { callback: callback /* result */, result: undefined }; this.push( deferred ); // Return a new callback in place of the original. Record the result, then if all // actions have completed, call the original callbacks, then call the completion // function. return function( result ) { deferred.result = result; if ( ++self.completed == self.length ) { // Call the original callbacks. self.forEach( function( deferred ) { deferred.callback && deferred.callback( deferred.result ); } ); // Call the completion callback. if ( self.callback ) { self.callback.call( self.that ); } } } }; }, }, function( modelFunctionName ) { // == Model API ============================================================================ // The kernel bypasses vwf/kernel/model and calls directly into the first driver stage. return undefined; }, function( kernelFunctionName ) { // == Kernel API =========================================================================== switch ( kernelFunctionName ) { // -- Read-write functions ------------------------------------------------------------- // TODO: setState // TODO: getState // TODO: hashState case "createNode": return function( nodeComponent, nodeAnnotation, baseURI, when, callback /* nodeID */ ) { // Interpret `createNode( nodeComponent, when, callback )` as // `createNode( nodeComponent, undefined, undefined, when, callback )` and // `createNode( nodeComponent, nodeAnnotation, when, callback )` as // `createNode( nodeComponent, nodeAnnotation, undefined, when, callback )`. // `nodeAnnotation` was added in 0.6.12, and `baseURI` was added in 0.6.25. if ( typeof baseURI == "function" || baseURI instanceof Function ) { callback = baseURI; when = nodeAnnotation; baseURI = undefined; nodeAnnotation = undefined; } else if ( typeof when == "function" || when instanceof Function ) { callback = when; when = baseURI; baseURI = undefined; } // Make the call. if ( this.state.enabled ) { if ( when === undefined ) { if ( this.state.asyncs ) { callback = this.state.asyncs.defer( callback /* nodeID */ ); } return this.kernel[kernelFunctionName]( nodeComponent, nodeAnnotation, function( nodeID ) { callback && callback( nodeID ); } ); } else { this.kernel.plan( undefined, kernelFunctionName, undefined, [ nodeComponent, nodeAnnotation ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "deleteNode": return function( nodeID, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID ); } else { this.kernel.plan( nodeID, kernelFunctionName, undefined, undefined, when, callback /* result */ ); } } else { this.state.blocked = true; } }; // TODO: setNode // TODO: getNode case "createChild": return function( nodeID, childName, childComponent, childURI, when, callback /* childID */ ) { if ( this.state.enabled ) { if ( when === undefined ) { if ( this.state.asyncs ) { callback = this.state.asyncs.defer( callback /* childID */ ); } return this.kernel[kernelFunctionName]( nodeID, childName, childComponent, childURI, function( childID ) { callback && callback( childID ); } ); } else { this.kernel.plan( nodeID, kernelFunctionName, childName, [ childComponent, childURI ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "deleteChild": return function( nodeID, childName, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, childName ); } else { this.kernel.plan( nodeID, kernelFunctionName, childName, undefined, when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "addChild": return function( nodeID, childID, childName, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, childID, childName ); } else { this.kernel.plan( nodeID, kernelFunctionName, undefined, [ childID, childName ], when, callback /* result */ ); // TODO: swap childID & childName? } } else { this.state.blocked = true; } }; case "removeChild": return function( nodeID, childID, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, childID ); } else { this.kernel.plan( nodeID, kernelFunctionName, undefined, [ childID ], when, callback /* result */ ); // TODO: swap childID & childName? } } else { this.state.blocked = true; } }; case "setProperties": return function( nodeID, properties, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, properties ); } else { this.kernel.plan( nodeID, kernelFunctionName, undefined, [ properties ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "getProperties": return function( nodeID, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID ); } else { this.kernel.plan( nodeID, kernelFunctionName, undefined, undefined, when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "createProperty": return function( nodeID, propertyName, propertyValue, propertyGet, propertySet, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, propertyName, propertyValue, propertyGet, propertySet ); } else { this.kernel.plan( nodeID, kernelFunctionName, propertyName, [ propertyValue, propertyGet, propertySet ], when, callback /* result */ ); // TODO: { value: propertyValue, get: propertyGet, set: propertySet } ? -- vwf.receive() needs to parse } } else { this.state.blocked = true; } }; // TODO: deleteProperty case "setProperty": return function( nodeID, propertyName, propertyValue, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, propertyName, propertyValue ); } else { this.kernel.plan( nodeID, kernelFunctionName, propertyName, [ propertyValue ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "getProperty": return function( nodeID, propertyName, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, propertyName ); } else { this.kernel.plan( nodeID, kernelFunctionName, propertyName, undefined, when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "createMethod": return function( nodeID, methodName, methodParameters, methodBody, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, methodName, methodParameters, methodBody ); } else { this.kernel.plan( nodeID, kernelFunctionName, methodName, [ methodParameters, methodBody ], when, callback /* result */ ); // TODO: { parameters: methodParameters, body: methodBody } ? -- vwf.receive() needs to parse } } else { this.state.blocked = true; } }; // TODO: deleteMethod case "setMethod": return function( nodeID, methodName, methodHandler, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, methodName, methodHandler ); } else { this.kernel.plan( nodeID, kernelFunctionName, methodName, [ methodHandler ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "getMethod": return function( nodeID, methodName, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, methodName ); } else { this.kernel.plan( nodeID, kernelFunctionName, methodName, undefined, when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "callMethod": return function( nodeID, methodName, methodParameters, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, methodName, methodParameters ); } else { this.kernel.plan( nodeID, kernelFunctionName, methodName, [ methodParameters ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "createEvent": return function( nodeID, eventName, eventParameters, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, eventName, eventParameters ); } else { this.kernel.plan( nodeID, kernelFunctionName, eventName, [ eventParameters ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; // TODO: deleteEvent case "addEventListener": return function( nodeID, eventName, eventHandler, eventContextID, eventPhases, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, eventName, eventHandler, eventContextID, eventPhases ); } else { this.kernel.plan( nodeID, kernelFunctionName, eventName, [ eventHandler, eventContextID, eventPhases ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "removeEventListener": return function( nodeID, eventName, eventListenerID, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, eventName, eventListenerID ); } else { this.kernel.plan( nodeID, kernelFunctionName, eventName, [ eventListenerID ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "flushEventListeners": return function( nodeID, eventName, eventContextID, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, eventName, eventContextID ); } else { this.kernel.plan( nodeID, kernelFunctionName, eventName, [ eventContextID ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "fireEvent": return function( nodeID, eventName, eventParameters, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, eventName, eventParameters ); } else { this.kernel.plan( nodeID, kernelFunctionName, eventName, [ eventParameters ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "dispatchEvent": return function( nodeID, eventName, eventParameters, eventNodeParameters, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, eventName, eventParameters, eventNodeParameters ); } else { this.kernel.plan( nodeID, kernelFunctionName, eventName, [ eventParameters, eventNodeParameters ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "execute": return function( nodeID, scriptText, scriptType, when, callback /* result */ ) { if ( this.state.enabled ) { if ( when === undefined ) { if ( this.state.asyncs ) { callback = this.state.asyncs.defer( callback /* result */ ); } return this.kernel[kernelFunctionName]( nodeID, scriptText, scriptType, function( result ) { callback && callback( result ); } ); } else { this.kernel.plan( nodeID, kernelFunctionName, undefined, [ scriptText, scriptType ], when, callback /* result */ ); // TODO: { text: scriptText, type: scriptType } ? -- vwf.receive() needs to parse } } else { this.state.blocked = true; } }; case "random": return function( nodeID, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID ); } else { this.kernel.plan( nodeID, kernelFunctionName, undefined, undefined, when, callback /* result */ ); } } else { this.state.blocked = true; } }; case "seed": return function( nodeID, seed, when, callback ) { if ( this.state.enabled ) { if ( when === undefined ) { return this.kernel[kernelFunctionName]( nodeID, seed ); } else { this.kernel.plan( nodeID, kernelFunctionName, undefined, [ seed ], when, callback /* result */ ); } } else { this.state.blocked = true; } }; // -- Read-only functions -------------------------------------------------------------- case "time": case "client": case "moniker": case "application": case "intrinsics": case "uri": case "name": case "prototype": case "prototypes": case "behaviors": case "ancestors": case "parent": case "children": case "descendants": case "find": case "test": case "findClients": return function() { return this.kernel[kernelFunctionName].apply( this.kernel, arguments ); }; } } ); } );