UNPKG

assertions-recorder

Version:

a module for publishing all dom and track events to the assertions recorder application

1,599 lines (1,377 loc) 64.6 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory() : typeof define === 'function' && define.amd ? define(factory) : (factory()); }(this, (function () { 'use strict'; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /** postmate - A powerful, simple, promise-based postMessage library @version v1.4.2 @link https://github.com/dollarshaveclub/postmate @author Jacob Kelley <jakie8@gmail.com> @license MIT **/ /** * The type of messages our frames our sending * @type {String} */ var messsageType = 'application/x-postmate-v1+json'; /** * hasOwnProperty() * @type {Function} * @return {Boolean} */ var hasOwnProperty = Object.prototype.hasOwnProperty; /** * The maximum number of attempts to send a handshake request to the parent * @type {Number} */ var maxHandshakeRequests = 5; /** * A unique message ID that is used to ensure responses are sent to the correct requests * @type {Number} */ var _messageId = 0; /** * Increments and returns a message ID * @return {Number} A unique ID for a message */ var messageId = function messageId() { return ++_messageId; }; /** * Takes a URL and returns the origin * @param {String} url The full URL being requested * @return {String} The URLs origin */ var resolveOrigin = function resolveOrigin(url) { var a = document.createElement('a'); a.href = url; var protocol = a.protocol.length > 4 ? a.protocol : window.location.protocol; var host = a.host.length ? a.port === '80' || a.port === '443' ? a.hostname : a.host : window.location.host; return a.origin || protocol + "//" + host; }; /** * Ensures that a message is safe to interpret * @param {Object} message The postmate message being sent * @param {String} allowedOrigin The whitelisted origin * @return {Boolean} */ var sanitize = function sanitize(message, allowedOrigin) { if (message.origin !== allowedOrigin) return false; if (_typeof(message.data) !== 'object') return false; if (!('postmate' in message.data)) return false; if (message.data.type !== messsageType) return false; if (!{ 'handshake-reply': 1, call: 1, emit: 1, reply: 1, request: 1 }[message.data.postmate]) return false; return true; }; /** * Takes a model, and searches for a value by the property * @param {Object} model The dictionary to search against * @param {String} property A path within a dictionary (i.e. 'window.location.href') * @param {Object} data Additional information from the get request that is * passed to functions in the child model * @return {Promise} */ var resolveValue = function resolveValue(model, property) { var unwrappedContext = typeof model[property] === 'function' ? model[property]() : model[property]; return Postmate.Promise.resolve(unwrappedContext); }; /** * Composes an API to be used by the parent * @param {Object} info Information on the consumer */ var ParentAPI = /*#__PURE__*/ function () { function ParentAPI(info) { var _this = this; this.parent = info.parent; this.frame = info.frame; this.child = info.child; this.childOrigin = info.childOrigin; this.events = {}; this.listener = function (e) { var _ref = ((e || {}).data || {}).value || {}, data = _ref.data, name = _ref.name; if (e.data.postmate === 'emit') { if (name in _this.events) { _this.events[name].call(_this, data); } } }; this.parent.addEventListener('message', this.listener, false); } var _proto = ParentAPI.prototype; _proto.get = function get(property) { var _this2 = this; return new Postmate.Promise(function (resolve) { // Extract data from response and kill listeners var uid = messageId(); var transact = function transact(e) { if (e.data.uid === uid && e.data.postmate === 'reply') { _this2.parent.removeEventListener('message', transact, false); resolve(e.data.value); } }; // Prepare for response from Child... _this2.parent.addEventListener('message', transact, false); // Then ask child for information _this2.child.postMessage({ postmate: 'request', type: messsageType, property: property, uid: uid }, _this2.childOrigin); }); }; _proto.call = function call(property, data) { // Send information to the child this.child.postMessage({ postmate: 'call', type: messsageType, property: property, data: data }, this.childOrigin); }; _proto.on = function on(eventName, callback) { this.events[eventName] = callback; }; _proto.destroy = function destroy() { window.removeEventListener('message', this.listener, false); this.frame.parentNode.removeChild(this.frame); }; return ParentAPI; }(); /** * Composes an API to be used by the child * @param {Object} info Information on the consumer */ var ChildAPI = /*#__PURE__*/ function () { function ChildAPI(info) { var _this3 = this; this.model = info.model; this.parent = info.parent; this.parentOrigin = info.parentOrigin; this.child = info.child; this.child.addEventListener('message', function (e) { if (!sanitize(e, _this3.parentOrigin)) return; var _e$data = e.data, property = _e$data.property, uid = _e$data.uid, data = _e$data.data; if (e.data.postmate === 'call') { if (property in _this3.model && typeof _this3.model[property] === 'function') { _this3.model[property].call(_this3, data); } return; } // Reply to Parent resolveValue(_this3.model, property).then(function (value) { return e.source.postMessage({ property: property, postmate: 'reply', type: messsageType, uid: uid, value: value }, e.origin); }); }); } var _proto2 = ChildAPI.prototype; _proto2.emit = function emit(name, data) { this.parent.postMessage({ postmate: 'emit', type: messsageType, value: { name: name, data: data } }, this.parentOrigin); }; return ChildAPI; }(); /** * The entry point of the Parent. * @type {Class} */ var Postmate = /*#__PURE__*/ function () { // eslint-disable-line no-undef // Internet Explorer craps itself /** * Sets options related to the Parent * @param {Object} userOptions The element to inject the frame into, and the url * @return {Promise} */ function Postmate(_temp) { var _ref2 = _temp === void 0 ? userOptions : _temp, _ref2$container = _ref2.container, container = _ref2$container === void 0 ? typeof container !== 'undefined' ? container : document.body : _ref2$container, model = _ref2.model, url = _ref2.url; // eslint-disable-line no-undef this.parent = window; this.frame = document.createElement('iframe'); container.appendChild(this.frame); this.child = this.frame.contentWindow || this.frame.contentDocument.parentWindow; this.model = model || {}; return this.sendHandshake(url); } /** * Begins the handshake strategy * @param {String} url The URL to send a handshake request to * @return {Promise} Promise that resolves when the handshake is complete */ var _proto3 = Postmate.prototype; _proto3.sendHandshake = function sendHandshake(url) { var _this4 = this; var childOrigin = resolveOrigin(url); var attempt = 0; var responseInterval; return new Postmate.Promise(function (resolve, reject) { var reply = function reply(e) { if (!sanitize(e, childOrigin)) return false; if (e.data.postmate === 'handshake-reply') { clearInterval(responseInterval); _this4.parent.removeEventListener('message', reply, false); _this4.childOrigin = e.origin; return resolve(new ParentAPI(_this4)); } // Might need to remove since parent might be receiving different messages return reject('Failed handshake'); }; _this4.parent.addEventListener('message', reply, false); var doSend = function doSend() { attempt++; _this4.child.postMessage({ postmate: 'handshake', type: messsageType, model: _this4.model }, childOrigin); if (attempt === maxHandshakeRequests) { clearInterval(responseInterval); } }; var loaded = function loaded() { doSend(); responseInterval = setInterval(doSend, 500); }; if (_this4.frame.attachEvent) { _this4.frame.attachEvent('onload', loaded); } else { _this4.frame.onload = loaded; } _this4.frame.src = url; }); }; return Postmate; }(); /** * The entry point of the Child * @type {Class} */ Postmate.debug = false; Postmate.Promise = function () { try { return window ? window.Promise : Promise; } catch (e) { return null; } }(); Postmate.Model = /*#__PURE__*/ function () { /** * Initializes the child, model, parent, and responds to the Parents handshake * @param {Object} model Hash of values, functions, or promises * @return {Promise} The Promise that resolves when the handshake has been received */ function Model(model) { this.child = window; this.model = model; this.parent = this.child.parent; return this.sendHandshakeReply(); } /** * Responds to a handshake initiated by the Parent * @return {Promise} Resolves an object that exposes an API for the Child */ var _proto4 = Model.prototype; _proto4.sendHandshakeReply = function sendHandshakeReply() { var _this5 = this; return new Postmate.Promise(function (resolve, reject) { var shake = function shake(e) { if (!e.data.postmate) { return; } if (e.data.postmate === 'handshake') { _this5.child.removeEventListener('message', shake, false); e.source.postMessage({ postmate: 'handshake-reply', type: messsageType }, e.origin); _this5.parentOrigin = e.origin; // Extend model with the one provided by the parent var defaults = e.data.model; if (defaults) { var keys = Object.keys(defaults); for (var i = 0; i < keys.length; i++) { if (hasOwnProperty.call(defaults, keys[i])) { _this5.model[keys[i]] = defaults[keys[i]]; } } } return resolve(new ChildAPI(_this5)); } return reject('Handshake Reply Failed'); }; _this5.child.addEventListener('message', shake, false); }); }; return Model; }(); var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; function unwrapExports (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var adapt_1 = createCommonjsModule(function (module, exports) { var _typeof2 = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; Object.defineProperty(exports, "__esModule", { value: true }); var _typeof = typeof Symbol === "function" && _typeof2(Symbol.iterator) === "symbol" ? function (obj) { return typeof obj === "undefined" ? "undefined" : _typeof2(obj); } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj === "undefined" ? "undefined" : _typeof2(obj); }; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = [];var _n = true;var _d = false;var _e = undefined;try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value);if (i && _arr.length === i) break; } } catch (err) { _d = true;_e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } }return _arr; }return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); exports.default = adapt; /** * # Adapt * * Check and extend the environment for universal usage */ /** * Modify the context based on the environment * * @param {HTMLELement} element - [description] * @param {Object} options - [description] * @return {boolean} - [description] */ function adapt(element, options) { // detect environment setup if (commonjsGlobal.document) { return false; } var context = options.context; commonjsGlobal.document = context || function () { var root = element; while (root.parent) { root = root.parent; } return root; }(); // https://github.com/fb55/domhandler/blob/master/index.js#L75 var ElementPrototype = Object.getPrototypeOf(commonjsGlobal.document); // alternative descriptor to access elements with filtering invalid elements (e.g. textnodes) if (!Object.getOwnPropertyDescriptor(ElementPrototype, 'childTags')) { Object.defineProperty(ElementPrototype, 'childTags', { enumerable: true, get: function get() { return this.children.filter(function (node) { // https://github.com/fb55/domelementtype/blob/master/index.js#L12 return node.type === 'tag' || node.type === 'script' || node.type === 'style'; }); } }); } if (!Object.getOwnPropertyDescriptor(ElementPrototype, 'attributes')) { // https://developer.mozilla.org/en-US/docs/Web/API/Element/attributes // https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap Object.defineProperty(ElementPrototype, 'attributes', { enumerable: true, get: function get() { var attribs = this.attribs; var attributesNames = Object.keys(attribs); var NamedNodeMap = attributesNames.reduce(function (attributes, attributeName, index) { attributes[index] = { name: attributeName, value: attribs[attributeName] }; return attributes; }, {}); Object.defineProperty(NamedNodeMap, 'length', { enumerable: false, configurable: false, value: attributesNames.length }); return NamedNodeMap; } }); } if (!ElementPrototype.getAttribute) { // https://docs.webplatform.org/wiki/dom/Element/getAttribute // https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute ElementPrototype.getAttribute = function (name) { return this.attribs[name] || null; }; } if (!ElementPrototype.getElementsByTagName) { // https://docs.webplatform.org/wiki/dom/Document/getElementsByTagName // https://developer.mozilla.org/en-US/docs/Web/API/Element/getElementsByTagName ElementPrototype.getElementsByTagName = function (tagName) { var HTMLCollection = []; traverseDescendants(this.childTags, function (descendant) { if (descendant.name === tagName || tagName === '*') { HTMLCollection.push(descendant); } }); return HTMLCollection; }; } if (!ElementPrototype.getElementsByClassName) { // https://docs.webplatform.org/wiki/dom/Document/getElementsByClassName // https://developer.mozilla.org/en-US/docs/Web/API/Element/getElementsByClassName ElementPrototype.getElementsByClassName = function (className) { var names = className.trim().replace(/\s+/g, ' ').split(' '); var HTMLCollection = []; traverseDescendants([this], function (descendant) { var descendantClassName = descendant.attribs.class; if (descendantClassName && names.every(function (name) { return descendantClassName.indexOf(name) > -1; })) { HTMLCollection.push(descendant); } }); return HTMLCollection; }; } if (!ElementPrototype.querySelectorAll) { // https://docs.webplatform.org/wiki/css/selectors_api/querySelectorAll // https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll ElementPrototype.querySelectorAll = function (selectors) { var _this = this; selectors = selectors.replace(/(>)(\S)/g, '$1 $2').trim(); // add space for '>' selector // using right to left execution => https://github.com/fb55/css-select#how-does-it-work var instructions = getInstructions(selectors); var discover = instructions.shift(); var total = instructions.length; return discover(this).filter(function (node) { var step = 0; while (step < total) { node = instructions[step](node, _this); if (!node) { // hierarchy doesn't match return false; } step += 1; } return true; }); }; } if (!ElementPrototype.contains) { // https://developer.mozilla.org/en-US/docs/Web/API/Node/contains ElementPrototype.contains = function (element) { var inclusive = false; traverseDescendants([this], function (descendant, done) { if (descendant === element) { inclusive = true; done(); } }); return inclusive; }; } return true; } /** * Retrieve transformation steps * * @param {Array.<string>} selectors - [description] * @return {Array.<Function>} - [description] */ function getInstructions(selectors) { return selectors.split(' ').reverse().map(function (selector, step) { var discover = step === 0; var _selector$split = selector.split(':'); var _selector$split2 = _slicedToArray(_selector$split, 2); var type = _selector$split2[0]; var pseudo = _selector$split2[1]; var validate = null; var instruction = null; (function () { switch (true) { // child: '>' case />/.test(type): instruction = function checkParent(node) { return function (validate) { return validate(node.parent) && node.parent; }; }; break; // class: '.' case /^\./.test(type): var names = type.substr(1).split('.'); validate = function validate(node) { var nodeClassName = node.attribs.class; return nodeClassName && names.every(function (name) { return nodeClassName.indexOf(name) > -1; }); }; instruction = function checkClass(node, root) { if (discover) { return node.getElementsByClassName(names.join(' ')); } return typeof node === 'function' ? node(validate) : getAncestor(node, root, validate); }; break; // attribute: '[key="value"]' case /^\[/.test(type): var _type$replace$split = type.replace(/\[|\]|"/g, '').split('='); var _type$replace$split2 = _slicedToArray(_type$replace$split, 2); var attributeKey = _type$replace$split2[0]; var attributeValue = _type$replace$split2[1]; validate = function validate(node) { var hasAttribute = Object.keys(node.attribs).indexOf(attributeKey) > -1; if (hasAttribute) { // regard optional attributeValue if (!attributeValue || node.attribs[attributeKey] === attributeValue) { return true; } } return false; }; instruction = function checkAttribute(node, root) { if (discover) { var _ret2 = function () { var NodeList = []; traverseDescendants([node], function (descendant) { if (validate(descendant)) { NodeList.push(descendant); } }); return { v: NodeList }; }(); if ((typeof _ret2 === 'undefined' ? 'undefined' : _typeof(_ret2)) === "object") return _ret2.v; } return typeof node === 'function' ? node(validate) : getAncestor(node, root, validate); }; break; // id: '#' case /^#/.test(type): var id = type.substr(1); validate = function validate(node) { return node.attribs.id === id; }; instruction = function checkId(node, root) { if (discover) { var _ret3 = function () { var NodeList = []; traverseDescendants([node], function (descendant, done) { if (validate(descendant)) { NodeList.push(descendant); done(); } }); return { v: NodeList }; }(); if ((typeof _ret3 === 'undefined' ? 'undefined' : _typeof(_ret3)) === "object") return _ret3.v; } return typeof node === 'function' ? node(validate) : getAncestor(node, root, validate); }; break; // universal: '*' case /\*/.test(type): validate = function validate(node) { return true; }; instruction = function checkUniversal(node, root) { if (discover) { var _ret4 = function () { var NodeList = []; traverseDescendants([node], function (descendant) { return NodeList.push(descendant); }); return { v: NodeList }; }(); if ((typeof _ret4 === 'undefined' ? 'undefined' : _typeof(_ret4)) === "object") return _ret4.v; } return typeof node === 'function' ? node(validate) : getAncestor(node, root, validate); }; break; // tag: '...' default: validate = function validate(node) { return node.name === type; }; instruction = function checkTag(node, root) { if (discover) { var _ret5 = function () { var NodeList = []; traverseDescendants([node], function (descendant) { if (validate(descendant)) { NodeList.push(descendant); } }); return { v: NodeList }; }(); if ((typeof _ret5 === 'undefined' ? 'undefined' : _typeof(_ret5)) === "object") return _ret5.v; } return typeof node === 'function' ? node(validate) : getAncestor(node, root, validate); }; } })(); if (!pseudo) { return instruction; } var rule = pseudo.match(/-(child|type)\((\d+)\)$/); var kind = rule[1]; var index = parseInt(rule[2], 10) - 1; var validatePseudo = function validatePseudo(node) { if (node) { var compareSet = node.parent.childTags; if (kind === 'type') { compareSet = compareSet.filter(validate); } var nodeIndex = compareSet.findIndex(function (child) { return child === node; }); if (nodeIndex === index) { return true; } } return false; }; return function enhanceInstruction(node) { var match = instruction(node); if (discover) { return match.reduce(function (NodeList, matchedNode) { if (validatePseudo(matchedNode)) { NodeList.push(matchedNode); } return NodeList; }, []); } return validatePseudo(match) && match; }; }); } /** * Walking recursive to invoke callbacks * * @param {Array.<HTMLElement>} nodes - [description] * @param {Function} handler - [description] */ function traverseDescendants(nodes, handler) { nodes.forEach(function (node) { var progress = true; handler(node, function () { return progress = false; }); if (node.childTags && progress) { traverseDescendants(node.childTags, handler); } }); } /** * Bubble up from bottom to top * * @param {HTMLELement} node - [description] * @param {HTMLELement} root - [description] * @param {Function} validate - [description] * @return {HTMLELement} - [description] */ function getAncestor(node, root, validate) { while (node.parent) { node = node.parent; if (validate(node)) { return node; } if (node === root) { break; } } return null; } module.exports = exports['default']; }); var adapt = unwrapExports(adapt_1); var adapt$1 = /*#__PURE__*/Object.freeze({ default: adapt, __moduleExports: adapt_1 }); var utilities = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); exports.convertNodeList = convertNodeList; exports.escapeValue = escapeValue; /** * # Utilities * * Convenience helpers */ /** * Create an array with the DOM nodes of the list * * @param {NodeList} nodes - [description] * @return {Array.<HTMLElement>} - [description] */ function convertNodeList(nodes) { var length = nodes.length; var arr = new Array(length); for (var i = 0; i < length; i++) { arr[i] = nodes[i]; } return arr; } /** * Escape special characters like quotes and backslashes * * Description of valid characters: https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector#Notes * * @param {String?} value - [description] * @return {String} - [description] */ function escapeValue(value) { return value && value.replace(/['"`\\/:\?&!#$%^()[\]{|}*+;,.<=>@~]/g, '\\$&'); } }); var utilities$1 = unwrapExports(utilities); var utilities_1 = utilities.convertNodeList; var utilities_2 = utilities.escapeValue; var utilities$2 = /*#__PURE__*/Object.freeze({ default: utilities$1, __moduleExports: utilities, convertNodeList: utilities_1, escapeValue: utilities_2 }); var _utilities = ( utilities$2 && utilities$1 ) || utilities$2; var match_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); exports.default = match; var defaultIgnore = { attribute: function attribute(attributeName) { return ['style', 'data-reactid', 'data-react-checksum'].indexOf(attributeName) > -1; } }; /** * Get the path of the element * * @param {HTMLElement} node - [description] * @param {Object} options - [description] * @return {string} - [description] */ /** * # Match * * Retrieves selector */ function match(node, options) { var _options$root = options.root; var root = _options$root === undefined ? document : _options$root; var _options$skip = options.skip; var skip = _options$skip === undefined ? null : _options$skip; var _options$priority = options.priority; var // TODO: refactor the detection to customize the execution order based on the attribute names priority = _options$priority === undefined ? ['id', 'class', 'href', 'src'] : _options$priority; var _options$ignore = options.ignore; var ignore = _options$ignore === undefined ? {} : _options$ignore; var path = []; var element = node; var length = path.length; var skipCompare = skip && (Array.isArray(skip) ? skip : [skip]).map(function (entry) { if (typeof entry !== 'function') { return function (element) { return element === entry; }; } return entry; }); var skipChecks = function skipChecks(element) { return skip && skipCompare.some(function (compare) { return compare(element); }); }; var ignoreClass = false; Object.keys(ignore).forEach(function (type) { if (type === 'class') { ignoreClass = true; } var predicate = ignore[type]; if (typeof predicate === 'function') return; if (typeof predicate === 'number') { predicate = predicate.toString(); } if (typeof predicate === 'string') { predicate = new RegExp((0, _utilities.escapeValue)(predicate).replace(/\\/g, '\\\\')); } // check class-/attributename for regex ignore[type] = predicate.test.bind(predicate); }); if (ignoreClass) { (function () { var ignoreAttribute = ignore.attribute; ignore.attribute = function (name, value, defaultPredicate) { return ignore.class(value) || ignoreAttribute && ignoreAttribute(name, value, defaultPredicate); }; })(); } while (element !== root) { if (skipChecks(element) !== true) { // global if (checkId(element, path, ignore)) break; if (checkClassGlobal(element, path, ignore, root)) break; if (checkAttributeGlobal(element, path, ignore, root, priority)) break; if (checkTagGlobal(element, path, ignore, root)) break; // local checkClassLocal(element, path, ignore); // define only one selector each iteration if (path.length === length) { checkAttributeLocal(element, path, ignore, priority); } if (path.length === length) { checkTagLocal(element, path, ignore); } if (path.length === length) { checkClassChild(element, path, ignore); } if (path.length === length) { checkAttributeChild(element, path, ignore, priority); } if (path.length === length) { checkTagChild(element, path, ignore); } } element = element.parentNode; length = path.length; } if (element === root) { path.unshift('*'); } return path.join(' '); } /** * Preset 'checkClass' with global data * * @param {HTMLElement} element - [description] * @param {Array.<string>} path - [description] * @param {Object} ignore - [description] * @return {boolean} - [description] */ function checkClassGlobal(element, path, ignore, root) { return checkClass(element, path, ignore, root); } /** * Preset 'checkClass' with local data * * @param {HTMLElement} element - [description] * @param {Array.<string>} path - [description] * @param {Object} ignore - [description] * @return {boolean} - [description] */ function checkClassLocal(element, path, ignore) { return checkClass(element, path, ignore, element.parentNode); } /** * Preset 'checkChild' with class data * * @param {HTMLElement} element - [description] * @param {Array.<string>} path - [description] * @param {Object} ignore - [description] * @return {boolean} - [description] */ function checkClassChild(element, path, ignore) { var className = (0, _utilities.escapeValue)(element.getAttribute('class')); if (checkIgnore(ignore.class, className)) { return false; } return checkChild(element, path, '.' + className.trim().replace(/\s+/g, '.')); } /** * Preset 'checkAttribute' with global data * * @param {HTMLElement} element - [description] * @param {Array.<string>} path - [description] * @param {Object} ignore - [description] * @return {boolean} - [description] */ function checkAttributeGlobal(element, path, ignore, root, priority) { return checkAttribute(element, path, ignore, root, priority); } /** * Preset 'checkAttribute' with local data * * @param {HTMLElement} element - [description] * @param {Array.<string>} path - [description] * @param {Object} ignore - [description] * @return {boolean} - [description] */ function checkAttributeLocal(element, path, ignore, priority) { return checkAttribute(element, path, ignore, element.parentNode, priority); } /** * Preset 'checkChild' with attribute data * * @param {HTMLElement} element - [description] * @param {Array.<string>} path - [description] * @param {Object} ignore - [description] * @return {boolean} - [description] */ function checkAttributeChild(element, path, ignore, priority) { var attributes = element.attributes; return Object.keys(attributes).sort(orderByPriority(attributes, priority)).some(function (key) { var attribute = attributes[key]; var attributeName = attribute.name; var attributeValue = (0, _utilities.escapeValue)(attribute.value); if (checkIgnore(ignore.attribute, attributeName, attributeValue, defaultIgnore.attribute)) { return false; } var pattern = '[' + attributeName + '="' + attributeValue + '"]'; return checkChild(element, path, pattern); }); } /** * Preset 'checkTag' with global data * * @param {HTMLElement} element - [description] * @param {Array.<string>} path - [description] * @param {Object} ignore - [description] * @return {boolean} - [description] */ function checkTagGlobal(element, path, ignore, root) { return checkTag(element, path, ignore, root); } /** * Preset 'checkTag' with local data * * @param {HTMLElement} element - [description] * @param {Array.<string>} path - [description] * @param {Object} ignore - [description] * @return {boolean} - [description] */ function checkTagLocal(element, path, ignore) { return checkTag(element, path, ignore, element.parentNode); } /** * Preset 'checkChild' with tag data * * @param {HTMLElement} element - [description] * @param {Array.<string>} path - [description] * @param {Object} ignore - [description] * @return {boolean} - [description] */ function checkTagChild(element, path, ignore) { var tagName = element.tagName.toLowerCase(); if (checkIgnore(ignore.tag, tagName)) { return false; } return checkChild(element, path, tagName); } /** * Lookup unique identifier * * @param {HTMLElement} element - [description] * @param {Array.<string>} path - [description] * @param {Object} ignore - [description] * @return {boolean} - [description] */ function checkId(element, path, ignore) { var id = (0, _utilities.escapeValue)(element.getAttribute('id')); if (checkIgnore(ignore.id, id)) { return false; } path.unshift('#' + id); return true; } /** * Lookup class identifier * * @param {HTMLElement} element - [description] * @param {Array.<string>} path - [description] * @param {Object} ignore - [description] * @param {HTMLElement} parent - [description] * @return {boolean} - [description] */ function checkClass(element, path, ignore, parent) { var className = (0, _utilities.escapeValue)(element.getAttribute('class')); if (checkIgnore(ignore.class, className)) { return false; } var matches = parent.getElementsByClassName(className); if (matches.length === 1) { path.unshift('.' + className.trim().replace(/\s+/g, '.')); return true; } return false; } /** * Lookup attribute identifier * * @param {HTMLElement} element - [description] * @param {Array.<string>} path - [description] * @param {Object} ignore - [description] * @param {HTMLElement} parent - [description] * @return {boolean} - [description] */ function checkAttribute(element, path, ignore, parent, priority) { var attributes = element.attributes; return Object.keys(attributes).sort(orderByPriority(attributes, priority)).some(function (key) { var attribute = attributes[key]; var attributeName = attribute.name; var attributeValue = (0, _utilities.escapeValue)(attribute.value); if (checkIgnore(ignore.attribute, attributeName, attributeValue, defaultIgnore.attribute)) { return false; } var pattern = '[' + attributeName + '="' + attributeValue + '"]'; var matches = parent.querySelectorAll(pattern); if (matches.length === 1) { path.unshift(pattern); return true; } }); } /** * Lookup tag identifier * * @param {HTMLElement} element - [description] * @param {Array.<string>} path - [description] * @param {HTMLElement} parent - [description] * @param {Object} ignore - [description] * @return {boolean} - [description] */ function checkTag(element, path, ignore, parent) { var tagName = element.tagName.toLowerCase(); if (checkIgnore(ignore.tag, tagName)) { return false; } var matches = parent.getElementsByTagName(tagName); if (matches.length === 1) { path.unshift(tagName); return true; } return false; } /** * Lookup child identfier * * Note: childTags is a custom property to use a view filter for tags on for virutal elements * * @param {HTMLElement} element - [description] * @param {Array.<string>} path - [description] * @param {String} selector - [description] * @return {boolean} - [description] */ function checkChild(element, path, selector) { var parent = element.parentNode; var children = parent.childTags || parent.children; for (var i = 0, l = children.length; i < l; i++) { if (children[i] === element) { path.unshift('> ' + selector + ':nth-child(' + (i + 1) + ')'); return true; } } return false; } /** * Validate with custom and default functions * * @param {Function} predicate - [description] * @param {string} name - [description] * @param {string} value - [description] * @param {Function} defaultPredicate - [description] * @return {boolean} - [description] */ function checkIgnore(predicate, name, value, defaultPredicate) { if (!name) { return true; } var check = predicate || defaultPredicate; if (!check) { return false; } return check(name, value || name, defaultPredicate); } /** * Rank the attribute names by their general relevance for a website * * @param {Object} attributes - [description] * @param {Array} priority - [description] * @return {Function} - [description] */ function orderByPriority(attributes, priority) { return function (curr, next) { return priority.indexOf(attributes[curr].name) - priority.indexOf(attributes[next].name); }; } module.exports = exports['default']; }); var match = unwrapExports(match_1); var match$1 = /*#__PURE__*/Object.freeze({ default: match, __moduleExports: match_1 }); var _adapt = ( adapt$1 && adapt ) || adapt$1; var optimize_1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); exports.default = optimize; var _adapt2 = _interopRequireDefault(_adapt); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * Apply different optimization techniques * * @param {string} selector - [description] * @param {HTMLElement|Array.<HTMLElement>} element - [description] * @param {Object} options - [description] * @return {string} - [description] */ /** * # Optimize * * 1.) Improve efficiency through shorter selectors by removing redundancy * 2.) Improve robustness through selector transformation */ function optimize(selector, elements) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; // convert single entry and NodeList if (!Array.isArray(elements)) { elements = !elements.length ? [elements] : (0, _utilities.convertNodeList)(elements); } if (!elements.length || elements.some(function (element) { return element.nodeType !== 1; })) { throw new Error('Invalid input - to compare HTMLElements its necessary to provide a reference of the selected node(s)! (missing "elements")'); } var globalModified = (0, _adapt2.default)(elements[0], options); // chunk parts outside of quotes (http://stackoverflow.com/a/25663729) var path = selector.replace(/> /g, '>').split(/\s+(?=(?:(?:[^"]*"){2})*[^"]*$)/); if (path.length < 2) { return optimizePart('', selector, '', elements); } var shortened = [path.pop()]; while (path.length > 1) { var current = path.pop(); var prePart = path.join(' '); var postPart = shortened.join(' '); var pattern = prePart + ' ' + postPart; var matches = document.querySelectorAll(pattern); if (matches.length !== elements.length) { shortened.unshift(optimizePart(prePart, current, postPart, elements)); } } shortened.unshift(path[0]); path = shortened; // optimize start + end path[0] = optimizePart('', path[0], path.slice(1).join(' '), elements); path[path.length - 1] = optimizePart(path.slice(0, -1).join(' '), path[path.length - 1], '', elements); if (globalModified) { delete commonjsGlobal.document; } return path.join(' ').replace(/>/g, '> ').trim(); } /** * Improve a chunk of the selector * * @param {string} prePart - [description] * @param {string} current - [description] * @param {string} postPart - [description] * @param {Array.<HTMLElement>} elements - [description] * @return {string} - [description] */ function optimizePart(prePart, current, postPart, elements) { if (prePart.length) prePart = prePart + ' '; if (postPart.length) postPart = ' ' + postPart; // robustness: attribute without value (generalization) if (/\[*\]/.test(current)) { var key = current.replace(/=.*$/, ']'); var pattern = '' + prePart + key + postPart; var matches = document.querySelectorAll(pattern); if (compareResults(matches, elements)) { current = key; } else { // robustness: replace specific key-value with base tag (heuristic) var references = document.querySelectorAll('' + prePart + key); var _loop = function _loop() { var reference = references[i]; if (elements.some(function (element) { return reference.contains(element); })) { var description = reference.tagName.toLowerCase(); pattern = '' + prePart + description + postPart; matches = document.querySelectorAll(pattern); if (compareResults(matches, elements)) { current = description; } return 'break'; } }; for (var i = 0, l = references.length; i < l; i++) { var pattern; var matches; var _ret = _loop(); if (_ret === 'break') break; } } } // robustness: descendant instead child (heuristic) if (/>/.test(current)) { var descendant = current.replace(/>/, ''); var pattern = '' + prePart + descendant + postPart; var matches = document.querySelectorAll(pattern); if (compareResults(matches, elements)) { current = descendant; } } // robustness: 'nth-of-type' instead 'nth-child' (heuristic) if (/:nth-child/.test(current)) { // TODO: consider complete coverage of 'nth-of-type' replacement var type = current.replace(/nth-child/g, 'nth-of-type'); var pattern = '' + prePart + type + postPart; var matches = document.querySelectorAll(pattern); if (compareResults(matches, elements)) { current = type; } } // efficiency: combinations of classname (partial permutations) if (/\.\S+\.\S+/.test(current)) { var names = current.trim().split('.').slice(1).map(function (name) { return '.' + name; }).sort(function (curr, next) { return curr.length - next.length; }); while (names.length) { var partial = current.replace(names.shift(), '').trim(); var pattern = ('' + prePart + partial + postPart).trim(); if (!pattern.length || pattern.charAt(0) === '>') { break; } var matches = document.querySelectorAll(pattern); if (compareResults(matches, elements)) { current = partial; } } // robustness: degrade complex classname (heuristic) if (current && current.match(/\./g).length > 2) { var _references = document.querySelectorAll('' + prePart + current); var _loop2 = function _loop2() { var reference = _references[i]; if (elements.some(function (element) { return reference.contains(element); })) { // TODO: // - check using attributes + regard excludes var description = reference.tagName.toLowerCase(); pattern = '' + prePart + description + postPart; matches = document.querySelectorAll(pattern); if (compareResults(matches, elements)) { current = description; } return 'break'; } }; for (var i = 0, l = _references.length; i < l; i++) { var pattern; var matches; var _ret2 = _loop2(); if (_ret2 === 'break') break; } } } return current; } /** * Evaluate matches with expected elements * * @param {Array.<HTMLElement>} matches - [description] * @param {Array.<HTMLElement>} elements - [description] * @return {Boolean} - [description] */ function compareResults(matches, elements) { var length = matches.length; return length === elements.length && elements.every(function (element) { for (var i = 0; i < length; i++) { if (matches[i] === element) { return true; } } return false; }); } module.exports = exports['default']; }); var optimize = unwrapExports(optimize_1); var optimize$1 = /*#__PURE__*/Object.freeze({ default: optimize, __moduleExports: optimize_1 }); var common = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); exports.getCommonAncestor = getCommonAncestor; exports.getCommonProperties = getCommonProperties; /** * # Common * * Group similars */ /** * Find the last common ancestor of elements * * @param {Array.<HTMLElements>} elements - [description] * @return {HTMLElement} - [description] */ function getCommonAncestor(elements) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var _options$root = options.root; var root = _options$root === undefined ? document : _options$root; var _options$skip = options.skip;