@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,502 lines (1,311 loc) • 123 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2023 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, Blob, console, document, Promise, URL, XMLHttpRequest */
(function(__global) {
"use strict";
if ( __global.Promise === undefined || !__global.Promise.prototype.finally || __global.URLSearchParams === undefined ) {
var page = document.documentElement, pageStyle = page.style,
msg = "Microsoft Internet Explorer 11 and other legacy browsers are no longer supported. For more information, see ",
hrefText = "Internet Explorer 11 will no longer be supported by various SAP UI technologies in newer releases",
href = "https://blogs.sap.com/2021/02/02/internet-explorer-11-will-no-longer-be-supported-by-various-sap-ui-technologies-in-newer-releases/";
page.innerHTML = '<body style="margin:0;padding:0;overflow-y:hidden;background-color:#f7f7f7;text-align:center;width:100%;position:absolute;top:50%;transform:translate(0,-50%);"><div style="color:#32363a;font-family:Arial,Helvetica,sans-serif;font-size:.875rem;">' +
msg + '<a href="' + href + '" style="color:#4076b4;">' + hrefText + '</a>.</div></body>';
pageStyle.margin = pageStyle.padding = "0";
pageStyle.width = pageStyle.height = "100%";
if (__global.stop) { // Check for __global.stop first because Safari 11 has __global.stop and document.execCommand but document.execCommand('Stop') does not work in Safari 11
__global.stop();
} else {
document.execCommand('Stop');
}
throw new Error(msg + href);
}
}(window));
(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);
}
/**
* 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.
*
* Search parameters or a hash of the chosen base will be ignored.
*
* @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
*/
function resolveURL(sURI, sBase) {
sBase = pathOnly(sBase ? resolveURL(sBase) : document.baseURI);
return new URL(sURI, sBase).href;
}
// ---- 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 to 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.
* See {@link sap.ui.loader.config}, option <code>paths</code>.
* @type {Object<string,{url:string,absoluteUrl:string}>}
* @private
*/
var mUrlPrefixes = Object.create(null);
mUrlPrefixes[''] = {
url: DEFAULT_BASE_URL,
absoluteUrl: resolveURL(DEFAULT_BASE_URL)
};
/**
* 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;
// ---- break preload execution into tasks ----------------------------------------------------
/**
* Default value for `iMaxTaskDuration`.
*
* A value of -1 switched the scheduling off, a value of zero postpones each execution
*/
var DEFAULT_MAX_TASK_DURATION = -1; // off
/**
* Maximum accumulated task execution time (threshold)
* Can be configured via the private API property `maxTaskDuration`.
*/
var iMaxTaskDuration = DEFAULT_MAX_TASK_DURATION;
/**
* The earliest elapsed time at which a new browser task will be enforced.
* Will be updated when a new task starts.
*/
var iMaxTaskTime = Date.now() + iMaxTaskDuration;
/**
* A promise that fulfills when the new browser task has been reached.
* All postponed callback executions will be executed after this promise.
* `null` as long as the elapsed time threshold is not reached.
*/
var pWaitForNextTask;
/**
* Message channel which will be used to create a new browser task
* without being subject to timer throttling.
* Will be created lazily on first usage.
*/
var oNextTaskMessageChannel;
/**
* Update elapsed time threshold.
*
* The threshold will be updated only if executions currently are not postponed.
* Otherwise, the next task will anyhow update the threshold.
*/
function updateMaxTaskTime() {
if ( pWaitForNextTask == null ) {
iMaxTaskTime = Date.now() + iMaxTaskDuration;
}
}
/**
* Update duration limit and elapsed time threshold.
*/
function updateMaxTaskDuration(v) {
v = Number(v);
var iBeginOfCurrentTask = iMaxTaskTime - iMaxTaskDuration;
// limit to range [-1 ... Infinity], any other value incl. NaN restores the default
iMaxTaskDuration = v >= -1 ? v : DEFAULT_MAX_TASK_DURATION;
// Update the elapsed time threshold only if executions currently are not postponed.
// Otherwise, the next task will be the first to honor the new maximum duration.
if ( pWaitForNextTask == null ) {
iMaxTaskTime = iBeginOfCurrentTask + iMaxTaskDuration;
}
}
function waitForNextTask() {
if ( pWaitForNextTask == null ) {
/**
* Post a message to a MessageChannel to create a new task, without suffering from timer throttling
* In the new task, use a setTimeout(,0) to allow for better queuing of other events (like CSS loading)
*/
pWaitForNextTask = new Promise(function(resolve) {
if ( oNextTaskMessageChannel == null ) {
oNextTaskMessageChannel = new MessageChannel();
oNextTaskMessageChannel.port2.start();
}
oNextTaskMessageChannel.port2.addEventListener("message", function() {
setTimeout(function() {
pWaitForNextTask = null;
iMaxTaskTime = Date.now() + iMaxTaskDuration;
resolve();
}, 0);
}, {
once: true
});
oNextTaskMessageChannel.port1.postMessage(null);
});
}
return pWaitForNextTask;
}
/**
* Creates a function which schedules the execution of the given callback.
*
* The scheduling tries to limit the duration of browser tasks. When the configurable
* limit is reached, the creation of a new browser task is triggered and all subsequently
* scheduled callbacks will be postponed until the new browser task starts executing.
* In the new browser task, scheduling starts anew.
*
* The limit for the duration of browser tasks is configured via `iMaxTaskDuration`.
* By setting `iMaxTaskDuration` to a negative value, the whole scheduling mechanism is
* switched off. In that case, the returned function will execute the callback immediately.
*
* If a value of zero is set, each callback will be executed in a separate browser task.
* For preloaded modules, this essentially mimics the browser behavior of single file loading,
* but without the network and server delays.
*
* For larger values, at least one callback will be executed in each new browser task. When,
* after the execution of the callback, the configured threshold has been reached, all further
* callbacks will be postponed.
*
* Note: This is a heuristic only. Neither is the measurement of the task duration accurate,
* nor is there a way to know in advance the execution time of a callback.
*
* @param {function(any):void} fnCallback
* Function to schedule
* @returns {function(any):void}
* A function to call instead of the original callback; it takes care of scheduling
* and executing the original callback.
* @private
*/
function scheduleExecution(fnCallback) {
if ( iMaxTaskDuration < 0 ) {
return fnCallback;
}
return function() {
if ( pWaitForNextTask == null ) {
fnCallback.call(undefined, arguments[0]);
// if time limit is reached now, postpone future task
if ( Date.now() >= iMaxTaskTime ) {
waitForNextTask();
}
return;
}
pWaitForNextTask.then(scheduleExecution(fnCallback).bind(undefined, arguments[0]));
};
}
// ---- 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;
}
/**
* Try to find a resource name that would be mapped to the given URL.
*
* If multiple path mappings would create a match, the returned name is not necessarily
* the best (longest) match. The first match which is found, will be returned.
*
* When <code>bLoadedResourcesOnly</code> is set, only those resources will be taken
* into account for which content has been loaded already.
*
* @param {string} sURL URL to guess the resource name for
* @param {boolean} [bLoadedResourcesOnly=false] Whether the guess should be limited to already loaded resources
* @returns {string|undefined} Resource name or <code>undefined</code> if no matching name could be found
* @private
*/
function guessResourceName(sURL, bLoadedResourcesOnly) {
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.lastIndexOf(sUrlPrefix, 0) === 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 ( !bLoadedResourcesOnly || 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.failWith = function(msg, cause) {
var err = makeModuleError(msg, this, cause);
this.fail(err);
return err;
};
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) {
if (!mModules[sModuleName]) {
mModules[sModuleName] = new Module(sModuleName);
}
return mModules[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 isModuleError(err) {
return err && err.name === "ModuleError";
}
/**
* Wraps the given 'cause' in a new error with the given message and with name 'ModuleError'.
*
* The new message and the message of the cause are combined. The stacktrace of the
* new error and of the cause are combined (with a separating 'Caused by').
*
* Instead of the final message string, a template is provided which can contain placeholders
* for the module ID ({id}) and module URL ({url}). Providing a template without concrete
* values allows to detect the repeated nesting of the same error. In such a case, only
* the innermost cause will be kept (affects both, stack trace as well as the cause property).
* The message, however, will contain the full chain of module IDs.
*
* @param {string} template Message string template with placeholders
* @param {Module} module Module for which the error occurred
* @param {Error} cause original error
* @returns {Error} New module error
*/
function makeModuleError(template, module, cause) {
var modules = "'" + module.name + "'";
if (isModuleError(cause)) {
// update the chain of modules (increasing the indent)
modules = modules + "\n -> " + cause._modules.replace(/ -> /g, " -> ");
// omit repeated occurrences of the same kind of error
if ( template === cause._template ) {
cause = cause.cause;
}
}
// create the message string from the template and the cause's message
var message =
template.replace(/\{id\}/, modules).replace(/\{url\}/, module.url)
+ (cause ? ": " + cause.message : "");
var error = new Error(message);
error.name = "ModuleError";
error.cause = cause;
if ( cause && cause.stack ) {
error.stack = error.stack + "\nCaused by: " + cause.stack;
}
// the following properties are only for internal usage
error._template = template;
error._modules = modules;
return error;
}
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
*
* @param {boolean} [nested] Whether this is a nested queue used during sync execution of a module
*/
function ModuleDefinitionQueue(nested) {
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);
}
var sModule = document.currentScript && document.currentScript.getAttribute('data-sap-ui-module');
aQueue.push({
name: name,
deps: deps,
factory: factory,
_export: _export,
guess: sModule
});
// trigger queue processing via a timer in case the currently executing script is not managed by the loader
if ( !vTimer && !nested && sModule == null ) {
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 createXHRLoadError(error) {
error = new Error(xhr.statusText ? xhr.status + " - " + xhr.statusText : xhr.status);
error.name = "XHRLoadError";
error.status = xhr.status;
error.statusText = xhr.statusText;
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 = createXHRLoadError();
}
});
// 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 = createXHRLoadError();
});
xhr.open('GET', oModule.url, false);
try {
xhr.send();
} catch (error) {
oModule.error = error;
}
}
/**
* Global event handler to detect script execution errors.
* @private
*/
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) {
updateMaxTaskTime();
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) {
updateMaxTaskTime();
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.failWith("failed to load {id} from {url}", new Error("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) ) {