(function() {
/**
* @namespace selector
* @memberof Help4
*/
Help4.selector = {};
/**
* @namespace methods
* @memberof Help4.selector
*/
Help4.selector.methods = {};
/**
* @typedef {Object} Help4.selector.methods.Selector
* @property {string} value - the selector string
* @property {number} quality - the selector quality
*/
/**
* @typedef {Object} Help4.selector.SelectorData
* @property {string} rule - indicates which rule is used
* @property {string|number} value - indicates the string representation of the assigned hotspot
* @property {Help4.control2.PositionXY} [offset] - indicates the offset of the hotspot
* @property {Help4.selector.SelectorData} [iframe] - selector values for iframe
*/
/**
* deliver the DOM element from given selector
* @param {string} selector
* @returns {NodeList<HTMLElement>|Array<void>}
*/
Help4.selector.methods.$ = selector => {
try {
return Help4.selector.window.document.querySelectorAll(selector);
} catch(e) {
}
return [];
};
/**
* escape strings for usage with Help4.selector.methods.$
* @param {string} value
* @returns {string}
*/
Help4.selector.methods.$E = value => {
return window.CSS && CSS.escape
? CSS.escape(value)
: value.replace(/([!"#$%&'()*+,-./;<=>?@[\]^`{|}~])/g, '\\$1')
.replace(/:/g, '\\3A ')
.replace(/\r/g, '\\d')
.replace(/\n/g, '\\a');
};
/** @type {Window} */
Help4.selector.methods.window = window; // XRAY-708; for iframe recognition
/**
* @typedef {Object} Help4.selector.Rule
* @property {string[]} blacklist - possible static position
* @property {string[]} rules - do ignore the area of the container
*/
/**
* each object is of type: {@link Help4.selector.Rule}
* @enum {Object}
*/
Help4.selector.rules = {
Shell: {
blacklist: [],
rules: ['AppFrameSelector', 'DataAttrSelector', 'ClassSelector', 'IdSelector', 'TextSelector', 'GenericSelector']
},
UI5: {
blacklist: [],
rules: ['AppFrameSelector', 'DataAttrSelector', 'ClassSelector', 'IdSelectorUI5', 'TabStripItemSelectorUI5', 'SideNavigationSelectorUI5', 'DomSelectorUI5', 'TextSelector', 'GenericSelector:unstableId']
},
Fiori: {
blacklist: [],
//rules: [/*XRAY-1666: 'BlacklistFiori', */'#UI5', 'BindingInfoUI5']
rules: ['AppFrameSelector', 'DataAttrSelector', 'KnownElementSelector:Fiori', 'ClassSelector', 'IdSelectorUI5', 'TabStripItemSelectorUI5', 'SideNavigationSelectorUI5', 'DomSelectorUI5', 'BindingInfoUI5', 'TextSelector', 'GenericSelector:unstableId']
},
HTMLGUI_TOP: {
blacklist: [],
rules: ['AppFrameSelector', 'IframeSelector:application-', '#Fiori']
},
HTMLGUI_IFRAME: {
blacklist: [],
rules: ['DataAttrSelector', 'ClassSelector', 'HtmlGui_DialogControlSelector', 'HtmlGui_SidSelector', 'HtmlGui_IdSelector', 'NoNumbersIdSelector', 'TextSelector'/*, 'GenericSelector:unstableId'*/]
},
WDA_TOP: {
blacklist: [],
rules: ['AppFrameSelector', '#Fiori']
},
WDA_IFRAME: {
blacklist: [],
rules: ['IframeSelector', 'DataAttrSelector', 'ClassSelector', 'WDA_InputFieldSelector', 'TextSelector', 'GenericSelector:unstableId']
}
};
/** Base selector handler */
Help4.selector.Selector = class {
static CORE_VERSION = 2;
/**
* some selectors have been renamed while creating xRay 2.0
* old content still exists, need to be mapped
* @memberof Help4.selector.Selector
* @type {{BindingInfo: 'BindingInfoUI5', DomSelector: 'DomSelectorUI5', DataCatalogSelector: 'DataAttrSelector'}}
*/
static LEGACY = {
BindingInfo: 'BindingInfoUI5',
DomSelector: 'DomSelectorUI5',
DataCatalogSelector: 'DataAttrSelector'
};
/**
* deliver the recording engine
* @returns {Help4.engine.RecordingEngine}
*/
static getRecordingEngine() {
return Help4.getController().getEngine('recording');
}
/**
* start recording
* @param {Object} config
* @param {Function} callback
*/
static startRecording(config, callback) {
const r = this.getRecordingEngine();
if (r && !r.isStarted()) {
r.start(config, selector => {
Help4.selector.Selector.stopRecording();
callback(Help4.selector.Selector.utf8ToBase64(selector));
}, () => {
callback(null);
});
}
}
/** stop recording */
static stopRecording() {
const r = this.getRecordingEngine();
r?.isStarted() && r.stop();
}
/**
* deliver if recording engine is recording
* @returns {boolean}
*/
static isRecording() {
return this.getRecordingEngine()?.isStarted() || false;
}
/** toggles the recording info area */
static toggleRecordingInfo() {
this.getRecordingEngine()?.toggleInfoArea();
}
/** toggles the recording via recording engine */
static toggleRecording() {
this.getRecordingEngine()?.toggleRecording();
}
/**
* convert and deliver given base64 string to utf8
* @param {string} str
* @returns {?Object}
*/
static base64ToUtf8(str) {
if (typeof str === 'string') {
const {JSON, selector: {Selector: {CORE_VERSION}}} = Help4;
const x = str.match(/WA#(\d+)#(.*$)/);
const version = x ? Number(x[1]) : -1;
try {
return version > 0 && version <= CORE_VERSION
? JSON.parse(decodeURIComponent(window.atob(x[2]))) // new selector versions encode the data string
: JSON.parse(window.atob(str)); // XRAY-1826: version 1 did not encode; decode would destroy the data
} catch(e) {
}
return null;
}
return str;
}
/**
* convert and deliver given utf8 selector to base64 string
* @param {Help4.selector.SelectorData} selector
* @returns {?string}
*/
static utf8ToBase64(selector) {
try {
// encoding is needed as btoa will break on special chars
const str = window.btoa(encodeURIComponent(Help4.JSON.stringify(selector)));
return 'WA#' + Help4.selector.Selector.CORE_VERSION + '#' + str;
} catch(e) {
}
return null;
}
/**
* deliver the rule for a given selector
* @param {Help4.selector.SelectorData} selector
* @returns {?string}
*/
static getRule(selector) {
return selector?.rule
? Help4.selector.Selector.LEGACY[selector.rule] || selector.rule
: null;
}
};
})();