@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
808 lines (740 loc) • 28 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.
*/
/*global HTMLScriptElement */
sap.ui.define([
"sap/ui/performance/Measurement",
"sap/ui/performance/XHRInterceptor",
"sap/base/util/LoaderExtensions",
"sap/base/util/now",
"sap/base/util/uid",
"sap/base/Log",
"sap/ui/thirdparty/URI"
], function(Measurement, XHRInterceptor, LoaderExtensions, now, uid, Log, URI) {
"use strict";
var HOST = window.location.host, // static per session
INTERACTION = "INTERACTION",
isNavigation = false,
aInteractions = [],
oPendingInteraction = createMeasurement(),
mCompressedMimeTypes = {
"application/zip": true,
"application/vnd.rar": true,
"application/gzip": true,
"application/x-tar": true,
"application/java-archive": true,
"image/jpeg": true,
"application/pdf": true
},
sCompressedExtensions = "zip,rar,arj,z,gz,tar,lzh,cab,hqx,ace,jar,ear,war,jpg,jpeg,pdf,gzip";
function isCORSRequest(sUrl) {
var sHost = new URI(sUrl).host();
// url is relative or with same host
return sHost && sHost !== HOST;
}
function hexToAscii(sValue) {
var hex = sValue.toString();
var str = '';
for (var n = 0; n < hex.length; n += 2) {
str += String.fromCharCode(parseInt(hex.substr(n, 2), 16));
}
return str.trim();
}
function createMeasurement(iTime) {
return {
event: "startup", // event which triggered interaction - default is startup interaction
trigger: "undetermined", // control which triggered interaction
component: "undetermined", // component or app identifier
appVersion: "undetermined", // application version as from app descriptor
start: iTime || window.performance.timing.fetchStart, // interaction start - page fetchstart if initial
end: 0, // interaction end
navigation: 0, // sum over all navigation times
roundtrip: 0, // time from first request sent to last received response end - without gaps and ignored overlap
processing: 0, // client processing time
duration: 0, // interaction duration
requests: [], // Performance API requests during interaction
measurements: [], // Measurements
sapStatistics: [], // SAP Statistics for OData
requestTime: 0, // summ over all requests in the interaction (oPendingInteraction.requests[0].responseEnd-oPendingInteraction.requests[0].requestStart)
networkTime: 0, // request time minus server time from the header
bytesSent: 0, // sum over all requests bytes
bytesReceived: 0, // sum over all response bytes
requestCompression: "X", // ok per default, if compression does not match SAP rules we report an empty string
busyDuration: 0, // summed GlobalBusyIndicator duration during this interaction
id: uid(), //Interaction id
passportAction: "undetermined_startup_0" //default PassportAction for startup
};
}
function isCompleteMeasurement(oMeasurement) {
if (oMeasurement.start > oPendingInteraction.start && oMeasurement.end < oPendingInteraction.end) {
return oMeasurement;
}
}
/**
* Check if request is initiated by XHR
*
* @param {object} oRequestTiming
* @private
*/
function isXHR(oRequestTiming) {
// if the request has been completed it has complete timing figures)
var bComplete = oRequestTiming.startTime > 0 &&
oRequestTiming.startTime <= oRequestTiming.requestStart &&
oRequestTiming.requestStart <= oRequestTiming.responseEnd;
return bComplete && oRequestTiming.initiatorType === "xmlhttprequest";
}
function aggregateRequestTiming(oRequest) {
// aggregate navigation and roundtrip with respect to requests overlapping and times w/o requests (gaps)
this.end = oRequest.responseEnd > this.end ? oRequest.responseEnd : this.end;
// sum up request time as a grand total over all requests
oPendingInteraction.requestTime += (oRequest.responseEnd - oRequest.startTime);
// if there is a gap between requests we add the times to the aggrgate and shift the lower limits
if (this.roundtripHigherLimit <= oRequest.startTime) {
oPendingInteraction.navigation += (this.navigationHigherLimit - this.navigationLowerLimit);
oPendingInteraction.roundtrip += (this.roundtripHigherLimit - this.roundtripLowerLimit);
this.navigationLowerLimit = oRequest.startTime;
this.roundtripLowerLimit = oRequest.startTime;
}
// shift the limits if this request was completed later than the earlier requests
if (oRequest.responseEnd > this.roundtripHigherLimit) {
this.roundtripHigherLimit = oRequest.responseEnd;
}
if (oRequest.requestStart > this.navigationHigherLimit) {
this.navigationHigherLimit = oRequest.requestStart;
}
}
function aggregateRequestTimings(aRequests) {
var oTimings = {
start: aRequests[0].startTime,
end: aRequests[0].responseEnd,
navigationLowerLimit: aRequests[0].startTime,
navigationHigherLimit: aRequests[0].requestStart,
roundtripLowerLimit: aRequests[0].startTime,
roundtripHigherLimit: aRequests[0].responseEnd
};
// aggregate all timings by operating on the oTimings object
aRequests.forEach(aggregateRequestTiming, oTimings);
oPendingInteraction.navigation += (oTimings.navigationHigherLimit - oTimings.navigationLowerLimit);
oPendingInteraction.roundtrip += (oTimings.roundtripHigherLimit - oTimings.roundtripLowerLimit);
// calculate average network time per request
if (oPendingInteraction.networkTime) {
var iTotalNetworkTime = oPendingInteraction.requestTime - oPendingInteraction.networkTime;
oPendingInteraction.networkTime = iTotalNetworkTime / aRequests.length;
} else {
oPendingInteraction.networkTime = 0;
}
// in case processing is not determined, which means no re-rendering occured, take start to end
if (oPendingInteraction.processing === 0) {
var iRelativeStart = oPendingInteraction.start - window.performance.timing.fetchStart;
oPendingInteraction.duration = oTimings.end - iRelativeStart;
// calculate processing time of before requests start
oPendingInteraction.processing = oTimings.start - iRelativeStart;
}
}
function finalizeInteraction(iTime) {
if (oPendingInteraction) {
var aAllRequestTimings = window.performance.getEntriesByType("resource");
oPendingInteraction.end = iTime;
oPendingInteraction.duration = oPendingInteraction.processing;
oPendingInteraction.requests = aAllRequestTimings.filter(isXHR);
oPendingInteraction.completeRoundtrips = 0;
oPendingInteraction.measurements = Measurement.filterMeasurements(isCompleteMeasurement, true);
if (oPendingInteraction.requests.length > 0) {
aggregateRequestTimings(oPendingInteraction.requests);
}
oPendingInteraction.completeRoundtrips = oPendingInteraction.requests.length;
// calculate real processing time if any processing took place
// cannot be negative as then requests took longer than processing
var iProcessing = oPendingInteraction.processing - oPendingInteraction.navigation - oPendingInteraction.roundtrip;
oPendingInteraction.processing = iProcessing > -1 ? iProcessing : 0;
oPendingInteraction.completed = true;
Object.freeze(oPendingInteraction);
if (oPendingInteraction.duration !== 0 || oPendingInteraction.requests.length > 0 || isNavigation) {
aInteractions.push(oPendingInteraction);
var oFinshedInteraction = aInteractions[aInteractions.length - 1];
if (Interaction.onInteractionFinished && oFinshedInteraction) {
Interaction.onInteractionFinished(oFinshedInteraction);
}
if (Log.isLoggable()) {
Log.debug("Interaction step finished: trigger: " + oPendingInteraction.trigger + "; duration: " + oPendingInteraction.duration + "; requests: " + oPendingInteraction.requests.length, "Interaction.js");
}
}
oPendingInteraction = null;
oCurrentBrowserEvent = null;
isNavigation = false;
}
}
// component determination - heuristic
function createOwnerComponentInfo(oSrcElement) {
var sId, sVersion;
if (oSrcElement) {
var Component, oComponent;
Component = sap.ui.require("sap/ui/core/Component");
while (Component && oSrcElement && oSrcElement.getParent) {
oComponent = Component.getOwnerComponentFor(oSrcElement);
if (oComponent || oSrcElement instanceof Component) {
oComponent = oComponent || oSrcElement;
var oApp = oComponent.getManifestEntry("sap.app");
// get app id or module name for FESR
sId = oApp && oApp.id || oComponent.getMetadata().getName();
sVersion = oApp && oApp.applicationVersion && oApp.applicationVersion.version;
}
oSrcElement = oSrcElement.getParent();
}
}
return {
id: sId ? sId : "undetermined",
version: sVersion ? sVersion : ""
};
}
var bInteractionActive = false,
bInteractionProcessed = false,
oCurrentBrowserEvent,
iInteractionStepTimer,
bIdle = false,
bSuspended = false,
iInteractionCounter = 0,
iScrollEventDelayId = 0,
descScriptSrc = Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype, "src");
/* As UI5 resources gets also loaded via script tags we need to
* intercept this kind of loading as well. We assume that changing the
* 'src' property indicates a resource loading via a script tag. In some cases
* the src property will be updated multiple times, so we should intercept
* the same script tag only once (dataset.sapUiCoreInteractionHandled)
*/
function interceptScripts() {
Object.defineProperty(HTMLScriptElement.prototype, "src", {
set: function(val) {
var fnDone;
if (!this.dataset.sapUiCoreInteractionHandled) {
fnDone = Interaction.notifyAsyncStep();
this.addEventListener("load", function() {
fnDone();
});
this.addEventListener("error" , function() {
fnDone();
});
this.dataset.sapUiCoreInteractionHandled = "true";
}
descScriptSrc.set.call(this, val);
},
get: descScriptSrc.get
});
}
function registerXHROverrides() {
// store the byte size of the body
XHRInterceptor.register(INTERACTION, "send" ,function() {
if (this.pendingInteraction) {
// double string length for byte length as in js characters are stored as 16 bit ints
this.pendingInteraction.bytesSent += arguments[0] ? arguments[0].length : 0;
}
});
// store request header size
XHRInterceptor.register(INTERACTION, "setRequestHeader", function(sHeader, sValue) {
// count request header length consistent to what getAllResponseHeaders().length would return
if (!this.requestHeaderLength) {
this.requestHeaderLength = 0;
}
// assume request header byte size
this.requestHeaderLength += (sHeader + "").length + (sValue + "").length;
});
// register the response handler for data collection
XHRInterceptor.register(INTERACTION, "open", function () {
var sEpp,
sAction,
sRootContextID;
function handleInteraction(fnDone) {
if (this.readyState === 4) {
fnDone();
}
}
// we only need to take care of requests when we have a running interaction
if (oPendingInteraction) {
// only use Interaction for non CORS requests
if (!isCORSRequest(arguments[1])) {
//only track if FESR.clientID == EPP.Action && FESR.rootContextID == EPP.rootContextID
sEpp = Interaction.passportHeader.get(this);
if (sEpp && sEpp.length >= 370) {
sAction = hexToAscii(sEpp.substring(150, 230));
if (parseInt(sEpp.substring(8, 10), 16) > 2) { // version number > 2 --> extended passport
sRootContextID = sEpp.substring(372, 404);
}
}
if (!sEpp || sAction && sRootContextID && oPendingInteraction.passportAction.endsWith(sAction)) {
this.addEventListener("readystatechange", handleResponse.bind(this, oPendingInteraction.id));
}
}
this.addEventListener("readystatechange", handleInteraction.bind(this, Interaction.notifyAsyncStep()));
// assign the current interaction to the xhr for later response header retrieval.
this.pendingInteraction = oPendingInteraction;
// forward request url as fallback for compression check as IE11 does not support responseUrl on a XHR
this._ui5RequestUrl = arguments[1] || "";
}
});
}
// check if SAP compression rules are fulfilled
function checkCompression(sURL, sContentEncoding, sContentType, sContentLength) {
//remove hashes and queries + find extension (last . segment)
var fileExtension = sURL.split('.').pop().split(/\#|\?/)[0];
if (sContentEncoding === 'gzip' ||
sContentEncoding === 'br' ||
sContentType in mCompressedMimeTypes ||
(fileExtension && sCompressedExtensions.indexOf(fileExtension) !== -1) ||
sContentLength < 1024) {
return true;
} else {
return false;
}
}
// response handler which uses the custom properties we added to the xhr to retrieve information from the response headers
function handleResponse(sId) {
if (this.readyState === 4) {
if (this.pendingInteraction && !this.pendingInteraction.completed && oPendingInteraction.id === sId) {
// enrich interaction with information
var sContentLength = this.getResponseHeader("content-length"),
bCompressed = checkCompression(this.responseURL || this._ui5RequestUrl, this.getResponseHeader("content-encoding"), this.getResponseHeader("content-type"), sContentLength),
sFesrec = this.getResponseHeader("sap-perf-fesrec");
this.pendingInteraction.bytesReceived += sContentLength ? parseInt(sContentLength) : 0;
this.pendingInteraction.bytesReceived += this.getAllResponseHeaders().length;
this.pendingInteraction.bytesSent += this.requestHeaderLength || 0;
// this should be true only if all responses are compressed
this.pendingInteraction.requestCompression = bCompressed && (this.pendingInteraction.requestCompression !== false);
// sap-perf-fesrec header contains milliseconds
this.pendingInteraction.networkTime += sFesrec ? Math.round(parseFloat(sFesrec, 10) / 1000) : 0;
var sSapStatistics = this.getResponseHeader("sap-statistics");
if (sSapStatistics) {
var aTimings = window.performance.getEntriesByType("resource");
this.pendingInteraction.sapStatistics.push({
// add response url for mapping purposes
url: this.responseURL,
statistics: sSapStatistics,
timing: aTimings ? aTimings[aTimings.length - 1] : undefined
});
}
delete this.requestHeaderLength;
delete this.pendingInteraction;
}
}
}
/**
* Provides base functionality for interaction detection heuristics & API.
* Interaction detection works through the detection of relevant events and tracking of rendering activities.<br>
* An example:<br>
* The user clicks on a button<br>
* <ul>
* <li>"click" event gets detected via notification (<code>var notifyEventStart</code>)</li>
* <li>a click handler is registered on the button, so this is an interaction start (<code>var notifyStepStart</code>)</li>
* <li>some requests are made and rendering has finished (<code>var notifyStepEnd</code>)</li>
* </ul>
* All measurement takes place in {@link module:sap/ui/performance/Measurement}.
*
* @namespace
* @alias module:sap/ui/performance/trace/Interaction
*
* @public
* @since 1.76
*/
var Interaction = {
/**
* Gets all interaction measurements.
*
* @param {boolean} bFinalize finalize the current pending interaction so that it is contained in the returned array
* @return {object[]} all interaction measurements
*
* @static
* @public
* @since 1.76
*/
getAll : function(bFinalize) {
if (bFinalize) {
// force the finalization of the currently pending interaction
Interaction.end(true);
}
return aInteractions;
},
/**
* Gets all interaction measurements for which a provided filter function returns a truthy value.
*
* To filter for certain categories of measurements a fnFilter can be implemented like this
* <code>
* function(InteractionMeasurement) {
* return InteractionMeasurement.duration > 0
* }</code>
* @param {function} fnFilter a filter function that returns true if the passed measurement should be added to the result
* @return {object[]} all interaction measurements passing the filter function successfully
*
* @static
* @public
* @since 1.76
*/
filter : function(fnFilter) {
var aFilteredInteractions = [];
if (fnFilter) {
for (var i = 0, l = aInteractions.length; i < l; i++) {
if (fnFilter(aInteractions[i])) {
aFilteredInteractions.push(aInteractions[i]);
}
}
}
return aFilteredInteractions;
},
/**
* Gets the incomplete pending interaction.
*
* @return {object} interaction measurement
* @static
* @private
*/
getPending : function() {
return oPendingInteraction;
},
/**
* Clears all interaction measurements.
*
* @private
*/
clear : function() {
aInteractions = [];
},
/**
* Start an interaction measurements.
*
* @param {string} sType type of the event which triggered the interaction
* @param {object} oSrcElement the control on which the interaction was triggered
* @static
* @private
*/
start : function(sType, oSrcElement) {
var iTime = now();
if (oPendingInteraction) {
finalizeInteraction(iTime);
}
//reset async counter/timer
if (iInteractionStepTimer) {
clearTimeout(iInteractionStepTimer);
}
iInteractionCounter = 0;
// clear request timings for new interaction
if (window.performance.clearResourceTimings) {
window.performance.clearResourceTimings();
}
var oComponentInfo = createOwnerComponentInfo(oSrcElement);
// setup new pending interaction
oPendingInteraction = createMeasurement(iTime);
oPendingInteraction.event = sType;
oPendingInteraction.component = oComponentInfo.id;
oPendingInteraction.appVersion = oComponentInfo.version;
oPendingInteraction.start = iTime;
if (oSrcElement && oSrcElement.getId) {
oPendingInteraction.trigger = oSrcElement.getId();
}
/*eslint-disable no-console */
if (Log.isLoggable(null, "sap.ui.Performance")) {
console.time("INTERACTION: " + oPendingInteraction.trigger + " - " + oPendingInteraction.event);
}
/*eslint-enable no-console */
if (Log.isLoggable()) {
Log.debug("Interaction step started: trigger: " + oPendingInteraction.trigger + "; type: " + oPendingInteraction.event, "Interaction.js");
}
},
/**
* End an interaction measurements.
*
* @param {boolean} bForce forces end of interaction now and ignores further re-renderings
* @static
* @private
*/
end : function(bForce) {
if (oPendingInteraction) {
if (bForce) {
/*eslint-disable no-console */
if (Log.isLoggable(null, "sap.ui.Performance")) {
console.timeEnd("INTERACTION: " + oPendingInteraction.trigger + " - " + oPendingInteraction.event);
}
/*eslint-enable no-console */
finalizeInteraction(oPendingInteraction.preliminaryEnd || now());
if (Log.isLoggable()) {
Log.debug("Interaction ended...");
}
} else {
// set provisionary processing time from start to end and calculate later
oPendingInteraction.preliminaryEnd = now();
oPendingInteraction.processing = oPendingInteraction.preliminaryEnd - oPendingInteraction.start;
}
}
},
/**
* Returns true if the interaction detection was enabled explicitly, or implicitly along with fesr.
*
* @return {boolean} bActive State of the interaction detection
* @static
* @public
* @since 1.76
*/
getActive : function() {
return bInteractionActive;
},
/**
* Enables the interaction tracking.
*
* @param {boolean} bActive State of the interaction detection
*
* @static
* @public
* @since 1.76
*/
setActive : function(bActive) {
if (bActive && !bInteractionActive) {
registerXHROverrides();
interceptScripts();
//intercept resource loading from preloads
LoaderExtensions.notifyResourceLoading = Interaction.notifyAsyncStep;
}
bInteractionActive = bActive;
},
/**
* Mark interaction as navigation related
* @private
*/
notifyNavigation: function() {
isNavigation = true;
},
/**
* Start tracking busy time for a Control
* @param {sap.ui.core.Control} oControl
* @private
*/
notifyShowBusyIndicator : function(oControl) {
oControl._sapui_fesr_fDelayedStartTime = now() + oControl.getBusyIndicatorDelay();
},
/**
* End tracking busy time for a Control
* @param {sap.ui.core.Control} oControl
* @private
*/
notifyHideBusyIndicator : function(oControl) {
if (oControl._sapui_fesr_fDelayedStartTime) {
// The busy indicator shown duration d is calculated with:
// d = "time busy indicator was hidden" - "time busy indicator was requested" - "busy indicator delay"
var fBusyIndicatorShownDuration = now() - oControl._sapui_fesr_fDelayedStartTime;
Interaction.addBusyDuration((fBusyIndicatorShownDuration > 0) ? fBusyIndicatorShownDuration : 0);
delete oControl._sapui_fesr_fDelayedStartTime;
}
},
/**
* This method starts the actual interaction measurement when all criteria are met. As it is the starting point
* for the new interaction, the creation of the FESR headers for the last interaction is triggered here, so that
* the headers can be sent with the first request of the current interaction.<br>
*
* @param {string} sEventId The control event name
* @param {sap.ui.core.Element} oElement Element on which the interaction has been triggered
* @param {boolean} bForce Forces the interaction to start independently from a currently active browser event
* @static
* @private
*/
notifyStepStart : function(sEventId, oElement, bForce) {
if (bInteractionActive) {
if ((!oPendingInteraction && oCurrentBrowserEvent && !bInteractionProcessed) || bForce) {
var sType;
if (bForce) {
sType = "startup";
} else {
sType = sEventId;
}
Interaction.start(sType, oElement);
oPendingInteraction = Interaction.getPending();
// update pending interaction infos
if (oPendingInteraction && !oPendingInteraction.completed && Interaction.onInteractionStarted) {
oPendingInteraction.passportAction = Interaction.onInteractionStarted(oPendingInteraction, bForce);
}
oCurrentBrowserEvent = null;
//only handle the first browser event within a call stack. Ignore virtual/harmonization events.
bInteractionProcessed = true;
isNavigation = false;
setTimeout(function() {
//cleanup internal registry after actual call stack.
oCurrentBrowserEvent = null;
bInteractionProcessed = false;
}, 0);
}
}
},
/**
* Register async operation, that is relevant for a running interaction.
* Invoking the returned handle stops the async operation.
*
* @params {string} sStepName a step name
* @returns {function} The async handle
* @private
*/
notifyAsyncStep : function(sStepName) {
if (oPendingInteraction) {
/*eslint-disable no-console */
if (Log.isLoggable(null, "sap.ui.Performance") && sStepName) {
console.time(sStepName);
}
/*eslint-enable no-console */
var sInteractionId = oPendingInteraction.id;
Interaction.notifyAsyncStepStart();
return function() {
Interaction.notifyAsyncStepEnd(sInteractionId);
/*eslint-disable no-console */
if (Log.isLoggable(null, "sap.ui.Performance") && sStepName) {
console.timeEnd(sStepName);
}
/*eslint-enable no-console */
};
} else {
return function() {};
}
},
/**
* This methods resets the idle time check. Counts a running interaction relevant step.
*
* @private
*/
notifyAsyncStepStart : function() {
if (oPendingInteraction) {
iInteractionCounter++;
clearTimeout(iInteractionStepTimer);
bIdle = false;
if (Log.isLoggable()) {
Log.debug("Interaction relevant step started - Number of pending steps: " + iInteractionCounter);
}
}
},
/**
* Ends a running interaction relevant step by decreasing the internal count.
*
* @private
*/
notifyAsyncStepEnd : function(sId) {
if (oPendingInteraction && sId === oPendingInteraction.id) {
iInteractionCounter--;
Interaction.notifyStepEnd(true);
if (Log.isLoggable()) {
Log.debug("Interaction relevant step stopped - Number of pending steps: " + iInteractionCounter);
}
}
},
/**
* This method ends the started interaction measurement.
*
* @static
* @private
*/
notifyStepEnd : function(bCheckIdle) {
if (bInteractionActive && !bSuspended) {
if (iInteractionCounter === 0 || !bCheckIdle) {
if (bIdle || !bCheckIdle) {
Interaction.end(true);
if (Log.isLoggable()) {
Log.debug("Interaction stopped");
}
bIdle = false;
} else {
Interaction.end(); //set preliminary end time
bIdle = true;
if (iInteractionStepTimer) {
clearTimeout(iInteractionStepTimer);
}
iInteractionStepTimer = setTimeout(Interaction.notifyStepEnd, 250);
if (Log.isLoggable()) {
Log.debug("Interaction check for idle time - Number of pending steps: " + iInteractionCounter);
}
}
}
}
},
/**
* This method notifies if a relevant event has been triggered.
*
* @param {Event} oEvent Event whose processing has started
* @static
* @private
*/
notifyEventStart : function(oEvent) {
oCurrentBrowserEvent = bInteractionActive ? oEvent : null;
},
/**
* This method notifies if a scroll event has been triggered. Some controls require this special treatment,
* as the generic detection process via notifyEventStart is not sufficient.
*
* @param {Event} oEvent Scroll event whose processing has started
* @static
* @private
*/
notifyScrollEvent : function(oEvent) {
if (bInteractionActive) {
// notify for a newly started interaction, but not more often than every 250ms.
if (!iScrollEventDelayId) {
Interaction.notifyEventStart(oEvent);
} else {
clearTimeout(iScrollEventDelayId);
}
iScrollEventDelayId = setTimeout(function(){
Interaction.notifyStepStart(oEvent.sourceElement);
iScrollEventDelayId = 0;
Interaction.notifyStepEnd();
}, 250);
}
},
/**
* This method notifies if a relevant event has ended by detecting another interaction.
*
* @static
* @private
*/
notifyEventEnd : function() {
if (oCurrentBrowserEvent) {
// End interaction when a new potential interaction starts
if (oCurrentBrowserEvent.type.match(/^(mousedown|touchstart|keydown)$/)) {
Interaction.end(/*bForce*/true);
}
}
},
/**
* A hook which is called when an interaction is started.
*
* @param {object} oInteraction The pending interaction
* @private
*/
onInteractionStarted: null,
/**
* A hook which is called when an interaction is finished.
*
* @param {object} oFinishedInteraction The finished interaction
* @private
*/
onInteractionFinished: null,
/**
* This method sets the component name for an interaction once. This respects the case, where a new
* component is created in an interaction step while for example navigating to a new page. Differs
* from the actual owner component of the trigger control, which is still the previous component.
*
* @static
* @private
*/
setStepComponent : function(sComponentName) {
if (bInteractionActive && oPendingInteraction && sComponentName && !oPendingInteraction.stepComponent) {
oPendingInteraction.stepComponent = sComponentName;
}
},
/**
* @param {float} iDuration Increase busy duration of pending interaction by this value
* @static
* @private
*/
addBusyDuration : function (iDuration) {
if (bInteractionActive && oPendingInteraction) {
if (!oPendingInteraction.busyDuration) {
oPendingInteraction.busyDuration = 0;
}
oPendingInteraction.busyDuration += iDuration;
}
}
};
return Interaction;
});