UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

272 lines (236 loc) 8.2 kB
/*! * OpenUI5 * (c) Copyright 2009-2023 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Provides class sap.ui.core.Rendering sap.ui.define([ "sap/base/Log", "sap/ui/base/EventProvider", "sap/ui/performance/trace/Interaction", "sap/ui/performance/Measurement" ], function( Log, EventProvider, Interaction, Measurement ) { "use strict"; /** * A private logger instance used for 'debugRendering' logging. * * It can be activated by setting the URL parameter sap-ui-xx-debugRerendering to true. * If activated, stack traces of invalidate() calls will be recorded and if new * invalidations occur during rendering, they will be logged to the console together * with the causing stack traces. * * @private */ var oRenderLog = Log.getLogger("sap.ui.Rendering", ( // Note that the sap-ui-config option still is expected in camel case. // Lower case is only accepted here because of the config normalization which will be removed in future (window["sap-ui-config"] && (window["sap-ui-config"]["xx-debugRendering"] || window["sap-ui-config"]["xx-debugrendering"]) ) || /sap-ui-xx-debug(R|-r)endering=(true|x|X)/.test(document.location.search) ) ? Log.Level.DEBUG : Math.min(Log.Level.INFO, Log.getLevel()) ); var MAX_RENDERING_ITERATIONS = 20; var _sRerenderTimer; var mUIAreas = {}; /** * Tasks that are called just before the rendering starts. * @private */ var aPrerenderingTasks = []; var _oEventProvider = new EventProvider(); var _bRendering = false; function _renderPendingUIUpdates(sCaller) { // start performance measurement Measurement.start("renderPendingUIUpdates","Render pending UI updates in all UIAreas"); oRenderLog.debug("Render pending UI updates: start (" + (sCaller || "by timer" ) + ")"); var bUIUpdated = false, bLooped = MAX_RENDERING_ITERATIONS > 0, iLoopCount = 0; _bRendering = true; do { if ( bLooped ) { // try to detect long running ('endless') rendering loops iLoopCount++; // if we run another iteration despite the tracking mode, we complain ourselves if ( iLoopCount > MAX_RENDERING_ITERATIONS ) { _bRendering = false; throw new Error("Rendering has been re-started too many times (" + iLoopCount + "). Add URL parameter sap-ui-xx-debugRendering=true for a detailed analysis."); } if ( iLoopCount > 1 ) { oRenderLog.debug("Render pending UI updates: iteration " + iLoopCount); } } // clear a pending timer so that the next call to re-render will create a new timer if (_sRerenderTimer) { if ( _sRerenderTimer !== this ) { // 'this' is used as a marker for a delayed initial rendering, no timer to cleanup then clearTimeout(_sRerenderTimer); // explicitly stop the timer, as this call might be a synchronous call (applyChanges) while still a timer is running } _sRerenderTimer = undefined; if (Rendering.aFnDone.length > 0) { Rendering.aFnDone.pop()(); } } runPrerenderingTasks(); var mUIAreasSnapshot = mUIAreas; mUIAreas = {}; for (var sId in mUIAreasSnapshot) { bUIUpdated = mUIAreasSnapshot[sId].rerender() || bUIUpdated; } // eslint-disable-next-line no-unmodified-loop-condition } while ( bLooped && _sRerenderTimer ); // iterate if there are new rendering tasks _bRendering = false; // TODO: Provide information on what actually was re-rendered... if (bUIUpdated) { Rendering.fireUIUpdated(); } oRenderLog.debug("Render pending UI updates: finished"); // end performance measurement Measurement.end("renderPendingUIUpdates"); } var Rendering = { // to protect against nested rendering we use an array of Steps instead of a single one aFnDone: [], /** * Notify async Interaction step. * * @private * @ui5-restricted sap.ui.core.Core */ notifyInteractionStep: function () { Rendering.aFnDone.push(Interaction.notifyAsyncStep()); }, /** * Adds a task that is guaranteed to run once, just before the next rendering. A rendering * request is not triggered. * * @param {function} fnPrerenderingTask * A function that is called before the rendering * @param {boolean} [bFirst=false] * Whether the task should become the first one, not the last one * @private */ addPrerenderingTask: function (fnPrerenderingTask, bFirst) { if (bFirst) { aPrerenderingTasks.unshift(fnPrerenderingTask); } else { aPrerenderingTasks.push(fnPrerenderingTask); } }, /** * Asks all UIAreas to execute any pending rendering tasks. * * The execution of rendering tasks might require multiple iterations * until either no more rendering tasks are produced or until * MAX_RENDERING_ITERATIONS are reached. * * With a value of MAX_RENDERING_ITERATIONS=0 the loop can be avoided * and the remaining tasks are executed after another timeout. * * @param {string} sCaller The Caller id * @private */ renderPendingUIUpdates: function(sCaller, iTimeout) { if (iTimeout !== undefined) { _sRerenderTimer = setTimeout(_renderPendingUIUpdates.bind(null, sCaller), iTimeout); } else { Rendering.aFnDone.push(Interaction.notifyAsyncStep()); _renderPendingUIUpdates(sCaller); } }, /** * Suspends rendering until it will be resumed by calling <code>sap.ui.core.Rendering.resume</code> * * @private * @ui5-restricted sap.ui.core */ suspend: function() { /** * The ID of a timer that will execute the next rendering. * * A non-falsy value indicates that a timer exists already, or at least that no * new timer needs to be created as. During the boot phase, this member is set * to the special value <code>this</code> which is non-falsy and which should never * represent a valid timer ID (no chance of misinterpretation). */ _sRerenderTimer = this; //eslint-disable-line consistent-this }, /** * Resumes rendering if it was suspended by calling <code>sap.ui.core.Rendering.suspend</code> and * triggers a rerendering. * * @param {string } sReason The resume reason that will be logged when rendering. * @private * @ui5-restricted sap.ui.core */ resume: function(sReason) { Rendering.renderPendingUIUpdates(sReason, 0); }, /** * Returns <code>true</code> if there are any pending rendering tasks or when * such rendering tasks are currently being executed. * * @return {boolean} true if there are pending (or executing) rendering tasks. * @private */ getUIDirty: function() { return !!(_sRerenderTimer || _bRendering); }, /** * @name sap.ui.core.Core#UIUpdated * @event * @private * @function */ attachUIUpdated: function(fnFunction, oListener) { _oEventProvider.attachEvent("UIUpdated", fnFunction, oListener); }, detachUIUpdated: function(fnFunction, oListener) { _oEventProvider.detachEvent("UIUpdated", fnFunction, oListener); }, fireUIUpdated: function(mParameters) { _oEventProvider.fireEvent("UIUpdated", mParameters); }, /** * Returns the internal rendering Logger. * * @returns {sap.base.Log} The Rendering logger * @private */ getLogger: function() { return oRenderLog; }, /** * Registers an invalidated UIArea for rerendering. * * @param {sap.ui.core.UIArea} oUIArea The invalidated UIArea * @private */ invalidateUIArea: function(oUIArea) { mUIAreas[oUIArea.getId()] = oUIArea; if ( !_sRerenderTimer ) { // TODO: we should handle xx-waitForTheme here... oRenderLog.debug("Registering timer for delayed re-rendering"); // start async interaction step Rendering.aFnDone.push(Interaction.notifyAsyncStep()); Rendering.renderPendingUIUpdates('invalidated UIArea', 0); // decoupled for collecting several invalidations into one redraw } } }; /** * Runs all prerendering tasks and resets the list. * @private */ function runPrerenderingTasks() { var aTasks = aPrerenderingTasks.slice(); aPrerenderingTasks = []; aTasks.forEach(function (fnPrerenderingTask) { fnPrerenderingTask(); }); } return Rendering; });