(function () {
/**
* Use unique attributes and classes as a fallback
* - value uniqueness is checked individually
* - origin in href and src urls is removed
* @augments Help4.selector.methods.BaseSelector
*/
Help4.selector.methods.FallbackSelector = class extends Help4.selector.methods.BaseSelector {
/**
* block elements using CSS selectors
* @type {Object}
*/
static ELEMENTS = {block: ['[hidden], .hidden']}
/**
* don't use these attributes, they're language-based or otherwise not stable / unique
* across different sessions, stable ids would have been used by other selectors
* @type {Object}
*/
static ATTRIBUTES = {
block: [
'accessible-name', 'alt', 'aria-label', 'placeholder', 'title', 'value', // language-based
'style',
'active', 'collapsed', 'expanded', 'hidden', 'muted', 'selected', 'visible' // status-based
// potentially usable values (if not used by previous selectors)
//'aria-describedby', 'aria-labelledby', 'data-help-id', 'data-help-id2', 'data-testid', 'data-test-id', 'data-ui5-stable', 'id', // id-based
//'href', 'src', 'name'
],
blockWhitespaceValues: true // block attributes with values containing whitespace, assuming they are language-based or styles/scripts
}
/**
* don't use these classes, they're status-based or otherwise not stable / unique across different sessions
* @type {Object}
*/
static CLASSES = {
block: ['active', 'collapsed', 'expanded', 'hidden', 'muted', 'selected', 'visible'],
use: true
}
/**
* @param {string} selector
* @return {?HTMLElement}
*/
static getElement(selector) {
const {methods} = Help4.selector;
let {nodeName = '', attributes = [], classes = []} = Help4.JSON.parse(selector);
attributes = attributes.join('');
classes = classes.join('');
// check everything, then if something has changes, check individual attributes, then all classes
const queries = [
nodeName + attributes + classes,
nodeName + attributes,
nodeName + classes
];
for (const query of queries) {
const elements = methods.$(query);
if (elements.length === 1) return elements[0];
}
return null;
}
/**
* @param {HTMLElement} element
* @return {?Help4.selector.methods.Selector}
*/
static getSelector(element) {
const {methods, SELECTOR_QUALITY: {other: quality}} = Help4.selector;
const {ELEMENTS, ATTRIBUTES, CLASSES} = this;
if (ELEMENTS.block.some(s => element.matches(s))) return null;
const nodeName = element.nodeName.toUpperCase();
const blocked = new Set(ATTRIBUTES.block);
blocked.add('class');
const attributes = [];
for (const {name, value} of element.attributes) {
// reduce search time by removing already checked attributes from search set,
// block white space values as they are likely language-specific text
if (blocked.delete(name) || (ATTRIBUTES.blockWhitespaceValues && /\s/.test(value))) continue;
let s;
if (!value) {
s = `[${name}]`
} else if (Help4.includes(['href', 'src'], name)) {
s = `[${name}$="${methods.$E(_removeOrigin.call(this, value))}"]`;
} else {
s = `[${name}="${methods.$E(value)}"]`;
}
// check uniqueness and add to final selector,
// prioritize values without numbers
if (methods.$(nodeName + s).length === 1) {
/\d/.test(value) ? attributes.push(s) : attributes.unshift(s);
}
}
const classes = [];
if (CLASSES.use) {
const blocked = new Set(CLASSES.block);
let classSelector = '';
for (const c of element.classList) {
if (blocked.delete(c) || /^help4-/.test(c)) continue;
const value = '.' + methods.$E(c);
classSelector += value;
/\d/.test(value) ? classes.push(value) : classes.unshift(value);
}
if (methods.$(nodeName + classSelector).length !== 1) {
classes.splice(0, classes.length);
}
}
const value = attributes.length > 0 || classes.length > 0
? Help4.JSON.stringify({nodeName, attributes, classes})
: null;
return value ? {value, quality} : null;
}
}
/**
* @memberof Help4.selector.methods.FallbackSelector
* @private
* @param {string} value - a href or src value
* @returns {string} - if value is a valid url, returning the value without its origin, otherwise returning the original value
*/
function _removeOrigin(value) {
try {
let url = new URL(value);
return url.href.replace(url.origin + '/', '');
} catch {
return value;
}
}
})();