@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,516 lines (1,323 loc) • 116 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
/*
* IMPORTANT NOTICE
* With 1.54, ui5loader.js and its new features are not yet a public API.
* The loader must only be used via the well-known and documented UI5 APIs
* such as sap.ui.define, sap.ui.require, etc.
* Any direct usage of ui5loader.js or its features is not supported and
* might break in future releases.
*/
/*global sap:true, console, document, Promise, XMLHttpRequest */
(function(__global) {
"use strict";
/*
* Helper function that removes any query and/or hash parts from the given URL.
*
* @param {string} href URL to remove query and hash from
* @returns {string}
*/
function pathOnly(href) {
var p = href.search(/[?#]/);
return p < 0 ? href : href.slice(0, p);
}
/*
* Helper function that returns the document base URL without search parameters and hash.
*
* @returns {string}
*/
function docBase() {
return pathOnly(document.baseURI);
}
/*
* Whether the current browser is IE11, derived from the compatibility check for the URL Web API.
*/
var isIE11 = false;
/**
* Resolve a given URL, either against the base URL of the current document or against a given base URL.
*
* If no base URL is given, the URL will be resolved relative to the baseURI of the current document.
* If a base URL is given, that base will first be resolved relative to the document's baseURI,
* then the URL will be resolved relative to the resolved base.
*
* @param {string} sURI Relative or absolute URL that should be resolved
* @param {string} [sBase=document.baseURI] Base URL relative to which the URL should be resolved
* @returns {string} Resolved URL
*/
var resolveURL = (function(_URL) {
// feature check: URI support
// Can URL be used as a constructor (fails in IE 11)?
try {
new _URL('index.html', 'http://localhost:8080/');
} catch (e) {
isIE11 = true;
_URL = null;
}
if ( _URL ) {
return function(sURI, sBase) {
// For a spec see https://url.spec.whatwg.org/
// For browser support see https://developer.mozilla.org/en/docs/Web/API/URL
return new _URL(sURI, sBase ? new _URL(sBase, docBase()) : docBase()).toString();
};
}
// fallback for IE11: use a shadow document with <base> and <a>nchor tag
var doc = document.implementation.createHTMLDocument("Dummy doc for resolveURI");
var base = doc.createElement('base');
base.href = docBase();
doc.head.appendChild(base);
var anchor = doc.createElement("A");
doc.body.appendChild(anchor);
return function (sURI, sBase) {
base.href = docBase();
if ( sBase != null ) {
// first resolve sBase relative to location
anchor.href = sBase;
// then use it as base
base.href = anchor.href;
}
anchor.href = sURI;
// console.log("(" + sURI + "," + sBase + ") => (" + base.href + "," + anchor.href + ")");
return anchor.href;
};
}(__global.URL || __global.webkitURL));
// ---- helpers -------------------------------------------------------------------------------
function noop() {}
function forEach(obj, callback) {
Object.keys(obj).forEach(function(key) {
callback(key, obj[key]);
});
}
function executeInSeparateTask(fn) {
setTimeout(fn, 0);
}
function executeInMicroTask(fn) {
Promise.resolve().then(fn);
}
// ---- hooks & configuration -----------------------------------------------------------------
/**
* Log functionality.
*
* Can be set to an object with the methods shown below (subset of sap/base/Log).
* Logging methods never must fail. Should they ever throw errors, then the internal state
* of the loader will be broken.
*
* By default, all methods are implemented as NOOPs.
*
* @type {{debug:function(),info:function(),warning:function(),error:function(),isLoggable:function():boolean}}
* @private
*/
var log = {
debug: noop,
info: noop,
warning: noop,
error: noop,
isLoggable: noop
}; // Null Object pattern: dummy logger which is used as long as no logger is injected
/**
* Basic assert functionality.
*
* Can be set to a function that gets a value (the expression t be asserted) as first
* parameter and a message as second parameter. When the expression coerces to false,
* the assertion is violated and the message should be emitted (logged, thrown, whatever).
*
* By default, this is implemented as a NOOP.
* @type {function(any,string)}
* @private
*/
var assert = noop; // Null Object pattern: dummy assert which is used as long as no assert is injected
/**
* Callback for performance measurement.
*
* When set, it must be an object with methods <code>start</code> and <code>end</code>.
* @type {{start:function(string,any),end:function(string)}}
* @private
*/
var measure;
/**
* Source code transformation hook.
*
* To be used by code coverage, only supported in sync mode.
* @private
*/
var translate;
/**
* Method used by sap.ui.require to simulate asynchronous behavior.
*
* The default executes the given function in a separate browser task.
* Can be changed to execute in a micro task to save idle time in case of
* many nested sap.ui.require calls.
*/
var simulateAsyncCallback = executeInSeparateTask;
/*
* Activates strictest possible compliance with AMD spec
* - no multiple executions of the same module
* - at most one anonymous module definition per file, zero for adhoc definitions
*/
var strictModuleDefinitions = true;
/**
* Whether asynchronous loading can be used at all.
* When activated, require will load asynchronously, else synchronously.
* @type {boolean}
* @private
*/
var bGlobalAsyncMode = false;
/**
* Whether ui5loader currently exposes its AMD implementation as global properties
* <code>define</code> and <code>require</code>. Defaults to <code>false</code>.
* @type {boolean}
* @private
*/
var bExposeAsAMDLoader = false;
/**
* How the loader should react to calls of sync APIs or when global names are accessed:
* 0: tolerate
* 1: warn
* 2: reject
* @type {int}
* @private
*/
var syncCallBehavior = 0;
/**
* Default base URL for modules, used when no other configuration is provided.
* In case the base url is removed via <code>registerResourcePath("", null)</code>
* it will be reset to this URL instead.
* @const
* @type {string}
* @private
*/
var DEFAULT_BASE_URL = "./";
/**
* Temporarily saved reference to the original value of the global define variable.
*
* @type {any}
* @private
*/
var vOriginalDefine;
/**
* Temporarily saved reference to the original value of the global require variable.
*
* @type {any}
* @private
*/
var vOriginalRequire;
/**
* A map of URL prefixes keyed by the corresponding module name prefix.
*
* Note that the empty prefix ('') will always match and thus serves as a fallback.
* @type {Object.<string,{url:string,absoluteUrl:string}>}
* @see jQuery.sap.registerModulePath
* @private
*/
var mUrlPrefixes = Object.create(null);
mUrlPrefixes[''] = {
url: DEFAULT_BASE_URL,
absoluteUrl: resolveURL(DEFAULT_BASE_URL, document.baseURI)
};
/**
* Mapping of module IDs.
*
* Each entry is a map of its own, keyed by the module ID prefix for which it should be
* applied. Each contained map maps module ID prefixes to module ID prefixes.
*
* All module ID prefixes must not have extensions.
* @type {Object.<string,Object.<string,string>>}
* @private
*/
var mMaps = Object.create(null),
/**
* Information about third party modules, keyed by the module's resource name (including extension '.js').
*
* Each module shim object can have the following properties:
* <ul>
* <li><i>boolean</i>: [amd=false] Whether the module uses an AMD loader if present. If set to <code>true</code>,
* UI5 will disable an AMD loader while loading such a module to force the module to expose its content
* via global names.</li>
* <li><i>string[]|string</i>: [exports=undefined] Global name (or names) that are exported by the module.
* If one ore multiple names are defined, the first one will be read from the global object and will be
* used as value of the module. Each name can be a dot separated hierarchical name (will be resolved with
* <code>getGlobalProperty</code>)</li>
* <li><i>string[]</i>: [deps=undefined] List of modules that the module depends on. The modules will be loaded
* first before loading the module itself. Note that the stored dependencies also include the extension '.js'
* for easier evaluation, but <code>config({shim:...})</code> expects them without the extension for
* compatibility with the AMD-JS specification.</li>
* </ul>
*
* @see config method
* @type {Object.<string,{amd:boolean,exports:(string|string[]),deps:string[]}>}
* @private
*/
mShims = Object.create(null),
/**
* Dependency Cache information.
* Maps the name of a module to a list of its known dependencies.
* @type {Object.<string,string[]>}
* @private
*/
mDepCache = Object.create(null),
/**
* Whether the loader should try to load debug sources.
* @type {boolean}
* @private
*/
bDebugSources = false,
/**
* Indicates partial or total debug mode.
*
* Can be set to a function which checks whether preloads should be ignored for the given module.
* If undefined, all preloads will be used.
* @type {function(string):boolean|undefined}
* @private
*/
fnIgnorePreload;
// ---- internal state ------------------------------------------------------------------------
/**
* Map of modules that have been loaded or required so far, keyed by their name.
*
* @type {Object<string,Module>}
* @private
*/
var mModules = Object.create(null),
/**
* Whether (sap.ui.)define calls must be executed synchronously in the current context.
*
* The initial value is <code>null</code>. During the execution of a module loading operation
* ((sap.ui.)require or (sap.ui.)define etc.), it is set to true or false depending on the
* legacy synchronicity behavior of the operation.
*
* Problem: when AMD modules are loaded with hard coded script tags and when some later inline
* script expects the module export synchronously, then the (sap.ui.)define must be executed
* synchronously.
* Most prominent example: unit tests that include QUnitUtils as a script tag and use qutils
* in one of their inline scripts.
* @type {boolean}
* @private
*/
bForceSyncDefines = null,
/**
* Stack of modules that are currently being executed in case of synchronous processing.
*
* Allows to identify the executing module (e.g. when resolving dependencies or in case of
* in case of bundles like sap-ui-core).
*
* @type {Array.<{name:string,used:boolean}>}
* @private
*/
_execStack = [ ],
/**
* A prefix that will be added to module loading log statements and which reflects the nesting of module executions.
* @type {string}
* @private
*/
sLogPrefix = "",
/**
* Counter used to give anonymous modules a unique module ID.
* @type {int}
* @private
*/
iAnonymousModuleCount = 0,
/**
* IE only: max size a script should have when executing it with execScript, otherwise fallback to eval.
* @type {int}
* @const
* @private
*/
MAX_EXEC_SCRIPT_LENGTH = 512 * 1024;
// ---- Names and Paths -----------------------------------------------------------------------
/**
* Name conversion function that converts a name in unified resource name syntax to a name in UI5 module name syntax.
* If the name cannot be converted (e.g. doesn't end with '.js'), then <code>undefined</code> is returned.
*
* @param {string} sName Name in unified resource name syntax
* @returns {string|undefined} Name in UI5 (legacy) module name syntax (dot separated)
* or <code>undefined</code> when the name can't be converted
* @private
*/
function urnToUI5(sName) {
// UI5 module name syntax is only defined for JS resources
if ( !/\.js$/.test(sName) ) {
return undefined;
}
sName = sName.slice(0, -3);
if ( /^jquery\.sap\./.test(sName) ) {
return sName; // do nothing
}
return sName.replace(/\//g, ".");
}
function urnToIDAndType(sResourceName) {
var basenamePos = sResourceName.lastIndexOf('/'),
dotPos = sResourceName.lastIndexOf('.');
if ( dotPos > basenamePos ) {
return {
id: sResourceName.slice(0, dotPos),
type: sResourceName.slice(dotPos)
};
}
return {
id: sResourceName,
type: ''
};
}
var rJSSubTypes = /(\.controller|\.fragment|\.view|\.designtime|\.support)?.js$/;
function urnToBaseIDAndSubType(sResourceName) {
var m = rJSSubTypes.exec(sResourceName);
if ( m ) {
return {
baseID: sResourceName.slice(0, m.index),
subType: m[0]
};
}
}
var rDotSegmentAnywhere = /(?:^|\/)\.+(?=\/|$)/;
var rDotSegment = /^\.*$/;
/**
* Normalizes a resource name by resolving any relative name segments.
*
* A segment consisting of a single dot <code>./</code>, when used at the beginning of a name refers
* to the containing package of the <code>sBaseName</code>. When used inside a name, it is ignored.
*
* A segment consisting of two dots <code>../</code> refers to the parent package. It can be used
* anywhere in a name, but the resolved name prefix up to that point must not be empty.
*
* Example: A name <code>../common/validation.js</code> defined in <code>sap/myapp/controller/mycontroller.controller.js</code>
* will resolve to <code>sap/myapp/common/validation.js</code>.
*
* When <code>sBaseName</code> is <code>null</code> (e.g. for a <code>sap.ui.require</code> call),
* the resource name must not start with a relative name segment or an error will be thrown.
*
* @param {string} sResourceName Name to resolve
* @param {string|null} sBaseName Name of a reference module relative to which the name will be resolved
* @returns {string} Resolved name
* @throws {Error} When a relative name should be resolved but not basename is given;
* or when upward navigation (../) is requested on the root level
* or when a name segment consists of 3 or more dots only
* @private
*/
function normalize(sResourceName, sBaseName) {
var p = sResourceName.search(rDotSegmentAnywhere),
aSegments,
sSegment,
i,j,l;
// check whether the name needs to be resolved at all - if not, just return the sModuleName as it is.
if ( p < 0 ) {
return sResourceName;
}
// if the name starts with a relative segment then there must be a base name (a global sap.ui.require doesn't support relative names)
if ( p === 0 ) {
if ( sBaseName == null ) {
throw new Error("relative name not supported ('" + sResourceName + "'");
}
// prefix module name with the parent package
sResourceName = sBaseName.slice(0, sBaseName.lastIndexOf('/') + 1) + sResourceName;
}
aSegments = sResourceName.split('/');
// process path segments
for (i = 0, j = 0, l = aSegments.length; i < l; i++) {
sSegment = aSegments[i];
if ( rDotSegment.test(sSegment) ) {
if (sSegment === '.' || sSegment === '') {
// ignore '.' as it's just a pointer to current package. ignore '' as it results from double slashes (ignored by browsers as well)
continue;
} else if (sSegment === '..') {
// move to parent directory
if ( j === 0 ) {
throw new Error("Can't navigate to parent of root ('" + sResourceName + "')");
}
j--;
} else {
throw new Error("Illegal path segment '" + sSegment + "' ('" + sResourceName + "')");
}
} else {
aSegments[j++] = sSegment;
}
}
aSegments.length = j;
return aSegments.join('/');
}
/**
* Adds a resource path to the resources map.
*
* @param {string} sResourceNamePrefix prefix is used as map key
* @param {string} sUrlPrefix path to the resource
*/
function registerResourcePath(sResourceNamePrefix, sUrlPrefix) {
sResourceNamePrefix = String(sResourceNamePrefix || "");
if ( sUrlPrefix == null ) {
// remove a registered URL prefix, if it wasn't for the empty resource name prefix
if ( sResourceNamePrefix ) {
if ( mUrlPrefixes[sResourceNamePrefix] ) {
delete mUrlPrefixes[sResourceNamePrefix];
log.info("registerResourcePath ('" + sResourceNamePrefix + "') (registration removed)");
}
return;
}
// otherwise restore the default
sUrlPrefix = DEFAULT_BASE_URL;
log.info("registerResourcePath ('" + sResourceNamePrefix + "') (default registration restored)");
}
// cast to string and remove query parameters and/or hash
sUrlPrefix = pathOnly(String(sUrlPrefix));
// ensure that the prefix ends with a '/'
if ( sUrlPrefix.slice(-1) !== '/' ) {
sUrlPrefix += '/';
}
mUrlPrefixes[sResourceNamePrefix] = {
url: sUrlPrefix,
// calculate absolute URL, only to be used by 'guessResourceName'
absoluteUrl: resolveURL(sUrlPrefix)
};
}
/**
* Retrieves path to a given resource by finding the longest matching prefix for the resource name
*
* @param {string} sResourceName name of the resource stored in the resources map
* @param {string} sSuffix url suffix
*
* @returns {string} resource path
*/
function getResourcePath(sResourceName, sSuffix) {
var sNamePrefix = sResourceName,
p = sResourceName.length,
sPath;
// search for a registered name prefix, starting with the full name and successively removing one segment
while ( p > 0 && !mUrlPrefixes[sNamePrefix] ) {
p = sNamePrefix.lastIndexOf('/');
// Note: an empty segment at p = 0 (leading slash) will be ignored
sNamePrefix = p > 0 ? sNamePrefix.slice(0, p) : '';
}
assert((p > 0 || sNamePrefix === '') && mUrlPrefixes[sNamePrefix], "there always must be a mapping");
sPath = mUrlPrefixes[sNamePrefix].url + sResourceName.slice(p + 1); // also skips a leading slash!
//remove trailing slash
if ( sPath.slice(-1) === '/' ) {
sPath = sPath.slice(0, -1);
}
return sPath + (sSuffix || '');
}
/**
* Returns the reporting mode for synchronous calls
*
* @returns {int} sync call behavior
*/
function getSyncCallBehavior() {
return syncCallBehavior;
}
function guessResourceName(sURL) {
var sNamePrefix,
sUrlPrefix,
sResourceName;
// Make sure to have an absolute URL without query parameters or hash
// to check against absolute prefix URLs
sURL = pathOnly(resolveURL(sURL));
for (sNamePrefix in mUrlPrefixes) {
// Note: configured URL prefixes are guaranteed to end with a '/'
// But to support the legacy scenario promoted by the application tools ( "registerModulePath('Application','Application')" )
// the prefix check here has to be done without the slash
sUrlPrefix = mUrlPrefixes[sNamePrefix].absoluteUrl.slice(0, -1);
if ( sURL.indexOf(sUrlPrefix) === 0 ) {
// calc resource name
sResourceName = sNamePrefix + sURL.slice(sUrlPrefix.length);
// remove a leading '/' (occurs if name prefix is empty and if match was a full segment match
if ( sResourceName.charAt(0) === '/' ) {
sResourceName = sResourceName.slice(1);
}
if ( mModules[sResourceName] && mModules[sResourceName].data != undefined ) {
return sResourceName;
}
}
}
}
/**
* Find the most specific map config that matches the given context resource
* @param {string} sContext Resource name to be used as context
* @returns {Object<string,string>|undefined} Most specific map or <code>undefined</code>
*/
function findMapForContext(sContext) {
var p, mMap;
if ( sContext != null ) {
// maps are defined on module IDs, reduce URN to module ID
sContext = urnToIDAndType(sContext).id;
p = sContext.length;
mMap = mMaps[sContext];
while ( p > 0 && mMap == null ) {
p = sContext.lastIndexOf('/');
if ( p > 0 ) { // Note: an empty segment at p = 0 (leading slash) will be ignored
sContext = sContext.slice(0, p);
mMap = mMaps[sContext];
}
}
}
// if none is found, fallback to '*' map
return mMap || mMaps['*'];
}
function getMappedName(sResourceName, sRequestingResourceName) {
var mMap = findMapForContext(sRequestingResourceName),
sPrefix, p;
// resolve relative names
sResourceName = normalize(sResourceName, sRequestingResourceName);
// if there's a map, search for the most specific matching entry
if ( mMap != null ) {
// start with the full ID and successively remove one segment
sPrefix = urnToIDAndType(sResourceName).id;
p = sPrefix.length;
while ( p > 0 && mMap[sPrefix] == null ) {
p = sPrefix.lastIndexOf('/');
// Note: an empty segment at p = 0 (leading slash) will be ignored
sPrefix = p > 0 ? sPrefix.slice(0, p) : '';
}
if ( p > 0 ) {
if ( log.isLoggable() ) {
log.debug('module ID ' + sResourceName + " mapped to " + mMap[sPrefix] + sResourceName.slice(p));
}
return mMap[sPrefix] + sResourceName.slice(p); // also skips a leading slash!
}
}
return sResourceName;
}
function getGlobalObject(oObject, aNames, l, bCreate) {
for (var i = 0; oObject && i < l; i++) {
if (!oObject[aNames[i]] && bCreate ) {
oObject[aNames[i]] = {};
}
oObject = oObject[aNames[i]];
}
return oObject;
}
function getGlobalProperty(sName) {
var aNames = sName ? sName.split(".") : [];
if ( syncCallBehavior && aNames.length > 1 ) {
log.error("[nosync] getGlobalProperty called to retrieve global name '" + sName + "'");
}
return getGlobalObject(__global, aNames, aNames.length);
}
function setGlobalProperty(sName, vValue) {
var aNames = sName ? sName.split(".") : [],
oObject;
if ( aNames.length > 0 ) {
oObject = getGlobalObject(__global, aNames, aNames.length - 1, true);
oObject[aNames[aNames.length - 1]] = vValue;
}
}
// ---- Modules -------------------------------------------------------------------------------
function wrapExport(value) {
return { moduleExport: value };
}
function unwrapExport(wrapper) {
return wrapper.moduleExport;
}
/**
* Module neither has been required nor preloaded nor declared, but someone asked for it.
*/
var INITIAL = 0,
/**
* Module has been preloaded, but not required or declared.
*/
PRELOADED = -1,
/**
* Module has been declared.
*/
LOADING = 1,
/**
* Module has been loaded, but not yet executed.
*/
LOADED = 2,
/**
* Module is currently being executed
*/
EXECUTING = 3,
/**
* Module has been loaded and executed without errors.
*/
READY = 4,
/**
* Module either could not be loaded or execution threw an error
*/
FAILED = 5,
/**
* Special content value used internally until the content of a module has been determined
*/
NOT_YET_DETERMINED = {};
/**
* A module/resource as managed by the module system.
*
* Each module is an object with the following properties
* <ul>
* <li>{int} state one of the module states defined in this function</li>
* <li>{string} url URL where the module has been loaded from</li>
* <li>{any} data temp. raw content of the module (between loaded and ready or when preloaded)</li>
* <li>{string} group the bundle with which a resource was loaded or null</li>
* <li>{string} error an error description for state <code>FAILED</code></li>
* <li>{any} content the content of the module as exported via define()<(li>
* </ul>
*
* @param {string} name Name of the module, including extension
*/
function Module(name) {
this.name = name;
this.state = INITIAL;
/*
* Whether processing of the module is complete.
* This is very similar to, but not the same as state >= READY because declareModule() sets state=READY very early.
* That state transition is 'legacy' from the library-all files; it needs to be checked whether it can be removed.
*/
this.settled = false;
this.url =
this._deferred =
this.data =
this.group =
this.error =
this.pending = null;
this.content = NOT_YET_DETERMINED;
}
Module.prototype.deferred = function() {
if ( this._deferred == null ) {
var deferred = this._deferred = {};
deferred.promise = new Promise(function(resolve,reject) {
deferred.resolve = resolve;
deferred.reject = reject;
});
// avoid 'Uncaught (in promise)' log entries
deferred.promise.catch(noop);
}
return this._deferred;
};
Module.prototype.api = function() {
if ( this._api == null ) {
this._exports = {};
this._api = {
id: this.name.slice(0,-3),
exports: this._exports,
url: this.url,
config: noop
};
}
return this._api;
};
/**
* Sets the module state to READY and either determines the value or sets
* it from the given parameter.
* @param {any} value Module value
*/
Module.prototype.ready = function(value) {
// should throw, but some tests and apps would fail
assert(!this.settled, "Module " + this.name + " is already settled");
this.state = READY;
this.settled = true;
if ( arguments.length > 0 ) {
// check arguments.length to allow a value of undefined
this.content = value;
}
this.deferred().resolve(wrapExport(this.value()));
if ( this.aliases ) {
value = this.value();
this.aliases.forEach(function(alias) {
Module.get(alias).ready(value);
});
}
};
Module.prototype.fail = function(err) {
// should throw, but some tests and apps would fail
assert(!this.settled, "Module " + this.name + " is already settled");
this.settled = true;
if ( this.state !== FAILED ) {
this.state = FAILED;
this.error = err;
this.deferred().reject(err);
if ( this.aliases ) {
this.aliases.forEach(function(alias) {
Module.get(alias).fail(err);
});
}
}
};
Module.prototype.addPending = function(sDependency) {
(this.pending || (this.pending = [])).push(sDependency);
};
Module.prototype.addAlias = function(sAliasName) {
(this.aliases || (this.aliases = [])).push(sAliasName);
// add this module as pending dependency to the original
Module.get(sAliasName).addPending(this.name);
};
Module.prototype.preload = function(url, data, bundle) {
if ( this.state === INITIAL && !(fnIgnorePreload && fnIgnorePreload(this.name)) ) {
this.state = PRELOADED;
this.url = url;
this.data = data;
this.group = bundle;
}
return this;
};
/**
* Determines the value of this module.
*
* If the module hasn't been loaded or executed yet, <code>undefined</code> will be returned.
*
* @returns {any} Export of the module or <code>undefined</code>
* @private
*/
Module.prototype.value = function() {
if ( this.state === READY ) {
if ( this.content === NOT_YET_DETERMINED ) {
// Determine the module value lazily.
// For AMD modules this has already been done on execution of the factory function.
// For other modules that are required synchronously, it has been done after execution.
// For the few remaining scenarios (like global scripts), it is done here
var oShim = mShims[this.name],
sExport = oShim && (Array.isArray(oShim.exports) ? oShim.exports[0] : oShim.exports);
// best guess for thirdparty modules or legacy modules that don't use sap.ui.define
this.content = getGlobalProperty( sExport || urnToUI5(this.name) );
}
return this.content;
}
return undefined;
};
/**
* Checks whether this module depends on the given module.
*
* When a module definition (define) is executed, the requested dependencies are added
* as 'pending' to the Module instance. This function checks if the oDependantModule is
* reachable from this module when following the pending dependency information.
*
* Note: when module aliases are introduced (all module definitions in a file use an ID that differs
* from the request module ID), then the alias module is also added as a "pending" dependency.
*
* @param {Module} oDependantModule Module which has a dependency to <code>oModule</code>
* @returns {boolean} Whether this module depends on the given one.
* @private
*/
Module.prototype.dependsOn = function(oDependantModule) {
var dependant = oDependantModule.name,
visited = Object.create(null);
// log.debug("checking for a cycle between", this.name, "and", dependant);
function visit(mod) {
if ( !visited[mod] ) {
// log.debug(" ", mod);
visited[mod] = true;
var pending = mModules[mod] && mModules[mod].pending;
return Array.isArray(pending) &&
(pending.indexOf(dependant) >= 0 || pending.some(visit));
}
return false;
}
return this.name === dependant || visit(this.name);
};
/**
* Find or create a module by its unified resource name.
*
* If the module doesn't exist yet, a new one is created in state INITIAL.
*
* @param {string} sModuleName Name of the module in URN syntax
* @returns {Module} Module with that name, newly created if it didn't exist yet
* @static
*/
Module.get = function(sModuleName) {
return mModules[sModuleName] || (mModules[sModuleName] = new Module(sModuleName));
};
/*
* Determines the currently executing module.
*/
function getExecutingModule() {
if ( _execStack.length > 0 ) {
return _execStack[_execStack.length - 1].name;
}
return document.currentScript && document.currentScript.getAttribute("data-sap-ui-module");
}
// --------------------------------------------------------------------------------------------
var _globalDefine,
_globalDefineAMD;
function updateDefineAndInterceptAMDFlag(newDefine) {
// no change, do nothing
if ( _globalDefine === newDefine ) {
return;
}
// first cleanup on an old loader
if ( _globalDefine ) {
_globalDefine.amd = _globalDefineAMD;
_globalDefine =
_globalDefineAMD = undefined;
}
// remember the new define
_globalDefine = newDefine;
// intercept access to the 'amd' property of the new define, if it's not our own define
if ( newDefine && !newDefine.ui5 ) {
_globalDefineAMD = _globalDefine.amd;
Object.defineProperty(_globalDefine, "amd", {
get: function() {
var sCurrentModule = getExecutingModule();
if ( sCurrentModule && mShims[sCurrentModule] && mShims[sCurrentModule].amd ) {
log.debug("suppressing define.amd for " + sCurrentModule);
return undefined;
}
return _globalDefineAMD;
},
set: function(newDefineAMD) {
_globalDefineAMD = newDefineAMD;
log.debug("define.amd became " + (newDefineAMD ? "active" : "unset"));
},
configurable: true // we have to allow a redefine for debug mode or restart from CDN etc.
});
}
}
try {
Object.defineProperty(__global, "define", {
get: function() {
return _globalDefine;
},
set: function(newDefine) {
updateDefineAndInterceptAMDFlag(newDefine);
log.debug("define became " + (newDefine ? "active" : "unset"));
},
configurable: true // we have to allow a redefine for debug mode or restart from CDN etc.
});
} catch (e) {
log.warning("could not intercept changes to window.define, ui5loader won't be able to a change of the AMD loader");
}
updateDefineAndInterceptAMDFlag(__global.define);
// --------------------------------------------------------------------------------------------
function ensureStacktrace(oError) {
if (!oError.stack) {
try {
throw oError;
} catch (ex) {
return ex;
}
}
return oError;
}
function makeNestedError(msg, cause) {
var oError = new Error(msg + ": " + cause.message);
oError.cause = cause;
oError.loadError = cause.loadError;
ensureStacktrace(oError);
ensureStacktrace(cause);
// concat the error stack for better traceability of loading issues
if ( oError.stack && cause.stack ) {
oError.stack = oError.stack + "\nCaused by: " + cause.stack;
}
// @evo-todo
// for non Chrome browsers we log the caused by stack manually in the console
// if (__global.console && !Device.browser.chrome) {
// /*eslint-disable no-console */
// console.error(oError.message + "\nCaused by: " + oCausedByStack);
// /*eslint-enable no-console */
// }
return oError;
}
function declareModule(sModuleName) {
var oModule;
// sModuleName must be a unified resource name of type .js
assert(/\.js$/.test(sModuleName), "must be a Javascript module");
oModule = Module.get(sModuleName);
if ( oModule.state > INITIAL ) {
return oModule;
}
if ( log.isLoggable() ) {
log.debug(sLogPrefix + "declare module '" + sModuleName + "'");
}
// avoid cycles
oModule.state = READY;
return oModule;
}
/**
* Define an already loaded module synchronously.
* Finds or creates a module by its unified resource name and resolves it with the given value.
*
* @param {string} sResourceName Name of the module in URN syntax
* @param {any} vValue Content of the module
*/
function defineModuleSync(sResourceName, vValue) {
Module.get(sResourceName).ready(vValue);
}
/**
* Queue of modules for which sap.ui.define has been called (in async mode), but which have not been executed yet.
* When loading modules via script tag, only the onload handler knows the relationship between executed sap.ui.define calls and
* module name. It then resolves the pending modules in the queue. Only one entry can get the name of the module
* if there are more entries, then this is an error
*/
function ModuleDefinitionQueue() {
var aQueue = [],
iRun = 0,
vTimer;
this.push = function(name, deps, factory, _export) {
if ( log.isLoggable() ) {
log.debug(sLogPrefix + "pushing define() call"
+ (document.currentScript ? " from " + document.currentScript.src : "")
+ " to define queue #" + iRun);
}
aQueue.push({
name: name,
deps: deps,
factory: factory,
_export: _export,
guess: document.currentScript && document.currentScript.getAttribute('data-sap-ui-module')
});
// trigger queue processing via a timer in case the currently executing script is not managed by the loader
if ( !vTimer ) {
vTimer = setTimeout(this.process.bind(this, null, "timer"));
}
};
this.clear = function() {
aQueue = [];
if ( vTimer ) {
clearTimeout(vTimer);
vTimer = null;
}
};
/**
* Process the queue of module definitions, assuming that the original request was for
* <code>oRequestedModule</code>. If there is an unnamed module definition, it is assumed to be
* the one for the requested module.
*
* When called via timer, <code>oRequestedModule</code> will be undefined.
*
* @param {Module} [oRequestedModule] Module for which the current script was loaded.
* @param {string} [sInitiator] A string describing the caller of <code>process</code>
*/
this.process = function(oRequestedModule, sInitiator) {
var bLoggable = log.isLoggable(),
iCurrentRun = iRun++,
aQueueCopy = aQueue,
sModuleName = null;
// clear the queue and timer early, we've already taken a copy of the queue
this.clear();
if ( oRequestedModule ) {
// if a module execution error was detected, stop processing the queue
if ( oRequestedModule.execError ) {
if ( bLoggable ) {
log.debug("module execution error detected, ignoring queued define calls (" + aQueueCopy.length + ")");
}
oRequestedModule.fail(oRequestedModule.execError);
return;
}
}
/*
* Name of the requested module, null when unknown or already consumed.
*
* - when no module request is known (e.g. script was embedded in the page as an unmanaged script tag),
* then no name is known and unnamed module definitions will be reported as an error
* - multiple unnamed module definitions also are reported as an error
* - when the name of a named module definition matches the name of requested module, the name is 'consumed'.
* Any later unnamed module definition will be reported as an error, too
*/
sModuleName = oRequestedModule && oRequestedModule.name;
// check whether there's a module definition for the requested module
aQueueCopy.forEach(function(oEntry) {
if ( oEntry.name == null ) {
if ( sModuleName != null ) {
oEntry.name = sModuleName;
sModuleName = null;
} else {
// multiple modules have been queued, but only one module can inherit the name from the require call
if ( strictModuleDefinitions ) {
var oError = new Error(
"Modules that use an anonymous define() call must be loaded with a require() call; " +
"they must not be executed via script tag or nested into other modules. ");
if ( oRequestedModule ) {
oRequestedModule.fail(oError);
} else {
throw oError;
}
}
// give anonymous modules a unique pseudo ID
oEntry.name = '~anonymous~' + (++iAnonymousModuleCount) + '.js';
log.error(
"Modules that use an anonymous define() call must be loaded with a require() call; " +
"they must not be executed via script tag or nested into other modules. " +
"All other usages will fail in future releases or when standard AMD loaders are used. " +
"Now using substitute name " + oEntry.name);
}
} else if ( oRequestedModule && oEntry.name === oRequestedModule.name ) {
if ( sModuleName == null && !strictModuleDefinitions ) {
// if 'strictModuleDefinitions' is active, double execution will be reported anyhow
log.error(
"Duplicate module definition: both, an unnamed module and a module with the expected name exist." +
"This use case will fail in future releases or when standard AMD loaders are used. ");
}
sModuleName = null;
}
});
// if not, assign an alias if there's at least one queued module definition
if ( sModuleName && aQueueCopy.length > 0 ) {
if ( bLoggable ) {
log.debug(
"No queued module definition matches the ID of the request. " +
"Now assuming that the first definition '" + aQueueCopy[0].name + "' is an alias of '" + sModuleName + "'");
}
Module.get(aQueueCopy[0].name).addAlias(sModuleName);
sModuleName = null;
}
if ( bLoggable ) {
log.debug(sLogPrefix + "[" + sInitiator + "] "
+ "processing define queue #" + iCurrentRun
+ (oRequestedModule ? " for '" + oRequestedModule.name + "'" : "")
+ " with entries [" + aQueueCopy.map(function(entry) { return "'" + entry.name + "'"; }) + "]");
}
aQueueCopy.forEach(function(oEntry) {
// start to resolve the dependencies
executeModuleDefinition(oEntry.name, oEntry.deps, oEntry.factory, oEntry._export, /* bAsync = */ true);
});
if ( sModuleName != null && !oRequestedModule.settled ) {
// module name still not consumed, might be a non-UI5 module (e.g. in 'global' format)
if ( bLoggable ) {
log.debug(sLogPrefix + "no queued module definition for the requested module found, assume the module to be ready");
}
oRequestedModule.data = undefined; // allow GC
oRequestedModule.ready(); // no export known, has to be retrieved via global name
}
if ( bLoggable ) {
log.debug(sLogPrefix + "processing define queue #" + iCurrentRun + " done");
}
};
}
var queue = new ModuleDefinitionQueue();
/**
* Loads the source for the given module with a sync XHR.
* @param {Module} oModule Module to load the source for
* @throws {Error} When loading failed for some reason.
*/
function loadSyncXHR(oModule) {
var xhr = new XMLHttpRequest();
function enrichXHRError(error) {
error = error || ensureStacktrace(new Error(xhr.status + " - " + xhr.statusText));
error.status = xhr.status;
error.statusText = xhr.statusText;
error.loadError = true;
return error;
}
xhr.addEventListener('load', function(e) {
// File protocol (file://) always has status code 0
if ( xhr.status === 200 || xhr.status === 0 ) {
oModule.state = LOADED;
oModule.data = xhr.responseText;
} else {
oModule.error = enrichXHRError();
}
});
// Note: according to whatwg spec, error event doesn't fire for sync send(), instead an error is thrown
// we register a handler, in case a browser doesn't follow the spec
xhr.addEventListener('error', function(e) {
oModule.error = enrichXHRError();
});
xhr.open('GET', oModule.url, false);
try {
xhr.send();
} catch (error) {
oModule.error = enrichXHRError(error);
}
}
/**
* Global event handler to detect script execution errors.
* Only works for browsers that support <code>document.currentScript</code>.
* @private
*/
if ( 'currentScript' in document ) {
window.addEventListener('error', function onUncaughtError(errorEvent) {
var sModuleName = document.currentScript && document.currentScript.getAttribute('data-sap-ui-module');
var oModule = sModuleName && Module.get(sModuleName);
if ( oModule && oModule.execError == null ) {
// if a currently executing module can be identified, attach the error to it and suppress reporting
if ( log.isLoggable() ) {
log.debug("unhandled exception occurred while executing " + sModuleName + ": " + errorEvent.message);
}
oModule.execError = errorEvent.error || {
name: 'Error',
message: errorEvent.message
};
return false;
}
});
}
function loadScript(oModule, sAlternativeURL) {
var oScript;
function onload(e) {
if ( log.isLoggable() ) {
log.debug("JavaScript resource loaded: " + oModule.name);
}
oScript.removeEventListener('load', onload);
oScript.removeEventListener('error', onerror);
queue.process(oModule, "onload");
}
function onerror(e) {
oScript.removeEventListener('load', onload);
oScript.removeEventListener('error', onerror);
if (sAlternativeURL) {
log.warning("retry loading JavaScript resource: " + oModule.name);
if (oScript && oScript.parentNode) {
oScript.parentNode.removeChild(oScript);
}
oModule.url = sAlternativeURL;
loadScript(oModule, /* sAlternativeURL= */ null);
return;
}
log.error("failed to load JavaScript resource: " + oModule.name);
oModule.fail(
ensureStacktrace(new Error("failed to load '" + oModule.name + "' from " + oModule.url + ": script load error")));
}
oScript = document.createElement('SCRIPT');
// Accessing the 'src' property of the script in this strange way prevents Safari 12 (or WebKit) from
// wrongly optimizing access. SF12 seems to check at optimization time whether there's a setter for the
// property and optimize accordingly. When a setter is defined or changed at a later point in time (e.g.
// by the AppCacheBuster), then the optimization seems not to be updated and the new setter is ignored
// BCP 1970035485
oScript["s" + "rc"] = oModule.url;
//oScript.src = oModule.url;
oScript.setAttribute("data-sap-ui-module", oModule.name);
if ( sAlternativeURL !== undefined ) {
if ( mShims[oModule.name] && mShims[oModule.name].amd ) {
oScript.setAttribute("data-sap-ui-module-amd", "true");
}
oScript.addEventListener('load', onload);
oScript.addEventListener('error', onerror);
}
document.head.appendChild(oScript);
}
function preloadDependencies(sModuleName) {
var knownDependencies = mDepCache[sModuleName];
if ( Array.isArray(knownDependencies) ) {
log.debug("preload dependencies for " + sModuleName + ": " + knownDependencies);
knownDependencies.forEach(function(dep) {
dep = getMappedName(dep, sModuleName);
if ( /\.js$/.test(dep) ) {
requireModule(null, dep, /* always async */ true);
} // else: TODO handle non-JS resources, e.g. link rel=prefetch
});
}
}
/**
* Loads the given module if needed and returns the module export or a promise on it.
*
* If loading is still ongoing for the requested module and if there is a cycle detected between
* the requesting module and the module to be loaded, then <code>undefined</code> (or a promise on
* <code>undefined</code>) will be returned as intermediate module export to resolve the cycle.
*
* @param {Module} oRequestingModule The module in whose context the new module has to be loaded;
* this is needed to detect cycles
* @param {string} sModuleName Name of the module to be loaded, in URN form and with '.js' extension
* @param {boolean} bAsync Whether the operation can be executed asynchronously
* @param {boolean} [bSkipShimDeps=false] Whether shim dependencies should be ignored (used by recursive calls)
* @param {boolean} [bSkipBundle=false] Whether bundle information should be ignored (used by recursive calls)
* @returns {any|Promise} Returns the module export in sync mode or a promise on it in async mode
* @throws {Error} When loading failed in sync mode
*
* @private
*/
function requireModule(oRequestingModule, sModuleName, bAsync, bSkipShimDeps, bSkipBundle) {
var bLoggable = log.isLoggable(),
oSplitName = urnToBaseIDAndSubType(sModuleName),
oShim = mShims[sModuleName],
oModule, aExtensions, i, sMsg, bExecutedNow;
// only for robustness, should not be possible by design (all callers append '.js')
if ( !oSplitName ) {
throw new Error("can only require Javascript module, not " + sModuleName);
}
// Module names should not start with a "/"
if (sModuleName[0] == "/") {
log.error("Module names that start with a slash should not be used, as they are reserved for future use.");
}
oModule = Module.get(sModuleName);
// when there's a shim with dependencies for the module
// resolve them first before requiring the module again with bSkipShimDeps = true
if ( oShim && oShim.deps && !bSkipShimDeps ) {
if ( bLoggable ) {
log.debug("require dependencies of raw module " + sModuleName);
}
return requireAll(oModule, oShim.deps, function() {
// set bSkipShimDeps to true to prevent endless recursion
return requireModule(oRequestingModule, sModuleName, bAsync, /* bSkipShimDeps = */ true, bSkipBundle);
}, function(oErr) {
oModule.fail(oErr);
if ( bAsync ) {
return;
}
throw oErr;
}, bAsync);
}
// when there's bundle information for the module
// require the bundle first before requiring the module again with bSkipBundle = true
if ( oModule.state === INITIAL && oModule.group && oModule.group !== sModuleName && !bSkipBundle ) {
if ( bLoggable ) {
log.debug(sLogPrefix + "require bundle '" + oModule.group + "'"
+ " containing '" + sModuleName + "'");
}
if ( bAsync ) {
return requireModule(null, oModule.group, bAsync).catch(noop).then(function() {
// set bSkipBundle to true to prevent endless recursion
return requireModule(oRequestingModule, sModuleName, bAsync, bSkipShimDeps, /* bSkipBundle = */ true);
});
} else {
try {
requireModule(null, oModule.group, bAsync);
} catch (oError) {
if ( bLoggable ) {
log.error(sLogPrefix + "require bundle '" + oModule.group + "' failed (ignored)");
}
}
}
}
if ( bLoggable ) {
log.debug(sLogPrefix + "require '" + sModuleName + "'"
+ (oRequestingModule ? " (dependency of '" + oRequestingModule.name + "')" : ""));
}
// check if module has been loaded already
if ( oModule.state !== INITIAL ) {
if ( oModule.state === EXECUTING && oModule.data != null && !bAsync && oModule.async ) {
oModule.state = PRELOADED;
oModule.async = bAsync;
oModule.pending = null; // TODO or is this still needed ?
}
if ( oModule.state === PRELOADED ) {
oModule.state = LOADED;
oModule.async = bAsync;
bExecutedNow = true;
measure && measure.start(sModuleName, "Require module " + sModuleName + " (preloaded)", ["require"]);
execModule(sModuleName, bAsync);
measure && measure.end(sModuleName);
}
if ( oModule.state === READY ) {
if ( !bExecutedNow && bLoggable ) {
log.debug(sLogPrefix + "module '" + sModuleName + "' has already been loaded (skipped).");
}
// Note: this intentionally does not return oModule.promise() as the export might be temporary in case of cycles
// or it might have changed after repeated module execution
return bAsync ? Promise.resolve(wrapExport(oModule.value())) : wrapExport(oModule.value());
} else if ( oModule.state === FAILED ) {
if ( bAsync ) {
return oModule.deferred().promise;
} else {
throw (bExecutedNow
? oModule.error
: makeNestedError("found in negative cache: '" + sModuleName + "' from " + oModule.url, oModule.error));
}
} else {
// currently loading or executing
if ( bAsync ) {
// break up cyclic dependencies
if ( oRequestingModule && oModule.dependsOn(oRequestingModule) ) {
if ( log.isLoggable() ) {
log.debug("cycle detected between '" + oRequestingModule.name + "' and '" + sModuleName + "', returning undefined for '" + sModuleName + "'");
}
// Note: this must be a separate promise as the fulfillment is not the final one
return Promise.resolve(wrapExport(undefined));
}
return oModule.deferred().promise;
}
if ( !bAsync && !oModule.async ) {
// sync pending, return undefined
if ( log.isLoggable() ) {
log.debug("cycle detected between '" + (oRequestingModule ? oRequestingModule.name : "unknown") + "' and '" + sModuleName + "', returning undefined for '" + sModuleName + "'");
}
return wrapExport(undefined);
}
// async pending, load sync again
log.warning("Sync request triggered for '" + sModuleName + "' while async request was already pending." +
" Loading a module twice might cause issues and should be avoided by fully migrating to async APIs.");
}
}
if ( isIE11 && bAsync && oShim && oShim.amd ) {
// in IE11, we force AMD/UMD modules into sync loading to apply the define.amd workaround
// in other browsers, we intercept read access to window.define, see ensureDefineInterceptor
bAsync = false;
}
measure && measure.start(sModuleName, "Require module " + sModuleName, ["require"]);
//