diff --git a/platform/framework/src/Constants.js b/platform/framework/src/Constants.js index 6202495645..13eb346448 100644 --- a/platform/framework/src/Constants.js +++ b/platform/framework/src/Constants.js @@ -16,5 +16,13 @@ define({ "tests": "test", "configuration": {}, "extensions": {} - } + }, + PRIORITY_LEVELS: { + "fallback": Number.NEGATIVE_INFINITY, + "default": 100, + "optional": 200, + "preferred": 400, + "mandatory": Number.POSITIVE_INFINITY + }, + DEFAULT_PRIORITY: 0 }); \ No newline at end of file diff --git a/platform/framework/src/Main.js b/platform/framework/src/Main.js index c1ea28a5ac..d0c7978379 100644 --- a/platform/framework/src/Main.js +++ b/platform/framework/src/Main.js @@ -26,23 +26,26 @@ define( './resolve/RequireConfigurator', './register/CustomRegistrars', './register/ExtensionRegistrar', + './register/ExtensionSorter', './bootstrap/ApplicationBootstrapper' ], - function (require, - es6promise, - angular, - angularRoute, - Constants, - FrameworkInitializer, - BundleLoader, - ImplementationLoader, - ExtensionResolver, - BundleResolver, - RequireConfigurator, - CustomRegistrars, - ExtensionRegistrar, - ApplicationBootstrapper - ) { + function ( + require, + es6promise, + angular, + angularRoute, + Constants, + FrameworkInitializer, + BundleLoader, + ImplementationLoader, + ExtensionResolver, + BundleResolver, + RequireConfigurator, + CustomRegistrars, + ExtensionRegistrar, + ExtensionSorter, + ApplicationBootstrapper + ) { "use strict"; // Get a reference to Angular's injector, so we can get $http and $log @@ -68,6 +71,7 @@ define( registrar = new ExtensionRegistrar( app, new CustomRegistrars(app, $log), + new ExtensionSorter($log), $log ), bootstrapper = new ApplicationBootstrapper( diff --git a/platform/framework/src/register/ExtensionRegistrar.js b/platform/framework/src/register/ExtensionRegistrar.js index 2ca1b9feca..138659a12f 100644 --- a/platform/framework/src/register/ExtensionRegistrar.js +++ b/platform/framework/src/register/ExtensionRegistrar.js @@ -17,9 +17,11 @@ define( * @param {Object.} customRegistrars an object * containing custom registration functions, primarily for * Angular built-ins. + * @param {ExtensionSorter} sorter the sorter which will impose + * priority ordering upon extensions * @param {*} $log Angular's logging service */ - function ExtensionRegistrar(app, customRegistrars, $log) { + function ExtensionRegistrar(app, customRegistrars, sorter, $log) { // Track which extension categories have already been registered. // Exceptions will be thrown if the same extension category is // registered twice. @@ -163,7 +165,7 @@ define( Object.keys(extensionGroup).forEach(function (category) { registerExtensionsForCategory( category, - extensionGroup[category] + sorter.sort(extensionGroup[category]) ); }); diff --git a/platform/framework/src/register/ExtensionSorter.js b/platform/framework/src/register/ExtensionSorter.js new file mode 100644 index 0000000000..cfef4d5af1 --- /dev/null +++ b/platform/framework/src/register/ExtensionSorter.js @@ -0,0 +1,97 @@ +/*global define*/ + +define( + ["../Constants"], + function (Constants) { + "use strict"; + + /** + * Responsible for applying priority order to extensions in a + * given category. This will sort in reverse order of the numeric + * priority given for extensions in the `priority` priority (such + * that large values are registered first.) Extensions may also + * specify symbolic properties as strings (instead of numbers), + * which will be looked up from the table `Constants.PRIORITY_LEVELS`. + * @param $log Angular's logging service + * @constructor + */ + function ExtensionSorter($log) { + + // Handle unknown or malformed priorities specified by extensions + function unrecognizedPriority(extension) { + // Issue a warning + $log.warn([ + "Unrecognized priority '", + (extension || {}).priority, + "' specified for extension from ", + ((extension || {}).bundle || {}).path, + "; defaulting to ", + Constants.DEFAULT_PRIORITY + ].join('')); + + // Provide a return value (default priority) to make this + // useful in an expression. + return Constants.DEFAULT_PRIORITY; + } + + function getPriority(extension) { + var priority = + (extension || {}).priority || Constants.DEFAULT_PRIORITY; + + // If it's a symbolic priority, look it up + if (typeof priority === 'string') { + priority = Constants.PRIORITY_LEVELS[priority]; + } + + // Should be a number; otherwise, issue a warning and + // fall back to default priority level. + return (typeof priority === 'number') ? + priority : unrecognizedPriority(extension); + } + + // Attach a numeric priority to an extension; this is done in + // one pass outside of the comparator, mainly because getPriority + // may log warnings, and we only want this to happen once + // (instead of the many times that might occur during a sort.) + function prioritize(extension, index) { + return { + // The extension itself, for later unwrapping + extension: extension, + // The index, to provide a stable sort (see compare) + index: index, + // The numeric priority of the extension + priority: getPriority(extension) + }; + } + + // Unwrap the original extension + // (for use after ordering has been applied) + function deprioritize(prioritized) { + return prioritized.extension; + } + + // Compare two prioritized extensions + function compare(a, b) { + // Reverse order by numeric priority; or, original order. + return (b.priority - a.priority) || (a.index - b.index); + } + + return { + /** + * Sort extensions according to priority. + * + * @param {object[]} extensions array of resolved extensions + * @returns {object[]} the same extensions, in priority order + */ + sort: function (extensions) { + return (extensions || []) + .map(prioritize) + .sort(compare) + .map(deprioritize); + } + }; + } + + return ExtensionSorter; + } +); \ No newline at end of file