Add autofill field comments (#4568)
* Added comments. * More comments. * More function comments. * Changed comment to add missing words. Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * Added better comment on fill query function. Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * More comments. * Added additional documentation on viewable and visible * Undid changes to the logic to avoid any chance of breaking anything. --------- Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
This commit is contained in:
parent
aea7f5c896
commit
da963346db
|
@ -56,7 +56,12 @@
|
||||||
function getPageDetails(theDoc, oneShotId) {
|
function getPageDetails(theDoc, oneShotId) {
|
||||||
// start helpers
|
// start helpers
|
||||||
|
|
||||||
// get the value of a dom element's attribute
|
/**
|
||||||
|
* For a given element `el`, returns the value of the attribute `attrName`.
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @param {string} attrName
|
||||||
|
* @returns {string} The value of the attribute
|
||||||
|
*/
|
||||||
function getElementAttrValue(el, attrName) {
|
function getElementAttrValue(el, attrName) {
|
||||||
var attrVal = el[attrName];
|
var attrVal = el[attrName];
|
||||||
if ('string' == typeof attrVal) {
|
if ('string' == typeof attrVal) {
|
||||||
|
@ -89,7 +94,11 @@
|
||||||
return elType !== el.type;
|
return elType !== el.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the value of a dom element
|
/**
|
||||||
|
* Returns the value of the given element.
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @returns {any} Value of the element
|
||||||
|
*/
|
||||||
function getElementValue(el) {
|
function getElementValue(el) {
|
||||||
switch (toLowerString(el.type)) {
|
switch (toLowerString(el.type)) {
|
||||||
case 'checkbox':
|
case 'checkbox':
|
||||||
|
@ -113,7 +122,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all the options for a "select" element
|
/**
|
||||||
|
* If `el` is a `<select>` element, return an array of all of the options' `text` properties.
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @returns {string[]} An array of options for the given `<select>` element
|
||||||
|
*/
|
||||||
function getSelectElementOptions(el) {
|
function getSelectElementOptions(el) {
|
||||||
if (!el.options) {
|
if (!el.options) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -132,34 +145,51 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the top label
|
/**
|
||||||
|
* If `el` is in a data table, get the label in the row directly above it
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @returns {string} A string containing the label, or null if not found
|
||||||
|
*/
|
||||||
function getLabelTop(el) {
|
function getLabelTop(el) {
|
||||||
var parent;
|
var parent;
|
||||||
|
|
||||||
|
// Traverse up the DOM until we reach either the top or the table data element containing our field
|
||||||
for (el = el.parentElement || el.parentNode; el && 'td' != toLowerString(el.tagName);) {
|
for (el = el.parentElement || el.parentNode; el && 'td' != toLowerString(el.tagName);) {
|
||||||
el = el.parentElement || el.parentNode;
|
el = el.parentElement || el.parentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we reached the top, return null
|
||||||
if (!el || void 0 === el) {
|
if (!el || void 0 === el) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Establish the parent of the table and make sure it's a table row
|
||||||
parent = el.parentElement || el.parentNode;
|
parent = el.parentElement || el.parentNode;
|
||||||
if ('tr' != parent.tagName.toLowerCase()) {
|
if ('tr' != parent.tagName.toLowerCase()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the previous sibling of the table row and make sure it's a table row
|
||||||
parent = parent.previousElementSibling;
|
parent = parent.previousElementSibling;
|
||||||
if (!parent || 'tr' != (parent.tagName + '').toLowerCase() ||
|
if (!parent || 'tr' != (parent.tagName + '').toLowerCase() ||
|
||||||
parent.cells && el.cellIndex >= parent.cells.length) {
|
parent.cells && el.cellIndex >= parent.cells.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parent is established as the row above the table data element containing our field
|
||||||
|
// Now let's traverse over to the cell in the same column as our field
|
||||||
el = parent.cells[el.cellIndex];
|
el = parent.cells[el.cellIndex];
|
||||||
|
|
||||||
|
// Get the contents of this label
|
||||||
var elText = el.textContent || el.innerText;
|
var elText = el.textContent || el.innerText;
|
||||||
return elText = cleanText(elText);
|
return elText = cleanText(elText);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all the tags for a given label
|
/**
|
||||||
|
* Get the contents of the elements that are labels for `el`
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @returns {string} A string containing all of the `innerText` or `textContent` values for all elements that are labels for `el`
|
||||||
|
*/
|
||||||
function getLabelTag(el) {
|
function getLabelTag(el) {
|
||||||
var docLabel,
|
var docLabel,
|
||||||
theLabels = [];
|
theLabels = [];
|
||||||
|
@ -207,7 +237,13 @@
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// add property and value to the object if there is a value
|
/**
|
||||||
|
* Add property `prop` with value `val` to the object `obj`
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {string} prop
|
||||||
|
* @param {any} val
|
||||||
|
* @param {*} d
|
||||||
|
*/
|
||||||
function addProp(obj, prop, val, d) {
|
function addProp(obj, prop, val, d) {
|
||||||
if (0 !== d && d === val || null === val || void 0 === val) {
|
if (0 !== d && d === val || null === val || void 0 === val) {
|
||||||
return;
|
return;
|
||||||
|
@ -216,12 +252,21 @@
|
||||||
obj[prop] = val;
|
obj[prop] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
// lowercase helper
|
/**
|
||||||
|
* Converts the string `s` to lowercase
|
||||||
|
* @param {string} s
|
||||||
|
* @returns Lowercase string
|
||||||
|
*/
|
||||||
function toLowerString(s) {
|
function toLowerString(s) {
|
||||||
return 'string' === typeof s ? s.toLowerCase() : ('' + s).toLowerCase();
|
return 'string' === typeof s ? s.toLowerCase() : ('' + s).toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
// query the document helper
|
/**
|
||||||
|
* Query the document `doc` for elements matching the selector `selector`
|
||||||
|
* @param {Document} doc
|
||||||
|
* @param {string} query
|
||||||
|
* @returns {HTMLElement[]} An array of elements matching the selector
|
||||||
|
*/
|
||||||
function queryDoc(doc, query) {
|
function queryDoc(doc, query) {
|
||||||
var els = [];
|
var els = [];
|
||||||
try {
|
try {
|
||||||
|
@ -405,6 +450,12 @@
|
||||||
|
|
||||||
document.elementForOPID = getElementForOPID;
|
document.elementForOPID = getElementForOPID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do the event on the element.
|
||||||
|
* @param {HTMLElement} kedol The element to do the event on
|
||||||
|
* @param {string} fonor The event name
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function doEventOnElement(kedol, fonor) {
|
function doEventOnElement(kedol, fonor) {
|
||||||
var quebo;
|
var quebo;
|
||||||
isFirefox ? (quebo = document.createEvent('KeyboardEvent'), quebo.initKeyEvent(fonor, true, false, null, false, false, false, false, 0, 0)) : (quebo = kedol.ownerDocument.createEvent('Events'),
|
isFirefox ? (quebo = document.createEvent('KeyboardEvent'), quebo.initKeyEvent(fonor, true, false, null, false, false, false, false, 0, 0)) : (quebo = kedol.ownerDocument.createEvent('Events'),
|
||||||
|
@ -413,20 +464,35 @@
|
||||||
return quebo;
|
return quebo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean up the text
|
/**
|
||||||
|
* Clean up the string `s` to remove non-printable characters and whitespace.
|
||||||
|
* @param {string} s
|
||||||
|
* @returns {string} Clean text
|
||||||
|
*/
|
||||||
function cleanText(s) {
|
function cleanText(s) {
|
||||||
var sVal = null;
|
var sVal = null;
|
||||||
s && (sVal = s.replace(/^\\s+|\\s+$|\\r?\\n.*$/gm, ''), sVal = 0 < sVal.length ? sVal : null);
|
s && (sVal = s.replace(/^\\s+|\\s+$|\\r?\\n.*$/gm, ''), sVal = 0 < sVal.length ? sVal : null);
|
||||||
return sVal;
|
return sVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the node type and adjust the array accordingly
|
/**
|
||||||
|
* If `el` is a text node, add the node's text to `arr`.
|
||||||
|
* If `el` is an element node, add the element's `textContent or `innerText` to `arr`.
|
||||||
|
* @param {string[]} arr An array of `textContent` or `innerText` values
|
||||||
|
* @param {HTMLElement} el The element to push to the array
|
||||||
|
*/
|
||||||
function checkNodeType(arr, el) {
|
function checkNodeType(arr, el) {
|
||||||
var theText = '';
|
var theText = '';
|
||||||
3 === el.nodeType ? theText = el.nodeValue : 1 === el.nodeType && (theText = el.textContent || el.innerText);
|
3 === el.nodeType ? theText = el.nodeValue : 1 === el.nodeType && (theText = el.textContent || el.innerText);
|
||||||
(theText = cleanText(theText)) && arr.push(theText);
|
(theText = cleanText(theText)) && arr.push(theText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if `el` is a type that indicates the transition to a new section of the page.
|
||||||
|
* If so, this indicates that we should not use `el` or its children for getting autofill context for the previous element.
|
||||||
|
* @param {HTMLElement} el The element to check
|
||||||
|
* @returns {boolean} Returns `true` if `el` is an HTML element from a known set and `false` otherwise
|
||||||
|
*/
|
||||||
function isKnownTag(el) {
|
function isKnownTag(el) {
|
||||||
if (el && void 0 !== el) {
|
if (el && void 0 !== el) {
|
||||||
var tags = 'select option input form textarea button table iframe body head script'.split(' ');
|
var tags = 'select option input form textarea button table iframe body head script'.split(' ');
|
||||||
|
@ -444,6 +510,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively gather all of the text values from the elements preceding `el` in the DOM
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @param {string[]} arr An array of `textContent` or `innerText` values
|
||||||
|
* @param {number} steps The number of steps to take up the DOM tree
|
||||||
|
*/
|
||||||
function shiftForLeftLabel(el, arr, steps) {
|
function shiftForLeftLabel(el, arr, steps) {
|
||||||
var sib;
|
var sib;
|
||||||
for (steps || (steps = 0); el && el.previousSibling;) {
|
for (steps || (steps = 0); el && el.previousSibling;) {
|
||||||
|
@ -470,37 +542,51 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// is a dom element visible on screen?
|
/**
|
||||||
|
* Determine if the element is visible.
|
||||||
|
* Visible is define as not having `display: none` or `visibility: hidden`.
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @returns {boolean} Returns `true` if the element is visible and `false` otherwise
|
||||||
|
*/
|
||||||
function isElementVisible(el) {
|
function isElementVisible(el) {
|
||||||
var theEl = el;
|
var theEl = el;
|
||||||
|
// Get the top level document
|
||||||
el = (el = el.ownerDocument) ? el.defaultView : {};
|
el = (el = el.ownerDocument) ? el.defaultView : {};
|
||||||
|
|
||||||
// walk the dom tree
|
// walk the dom tree until we reach the top
|
||||||
for (var elStyle; theEl && theEl !== document;) {
|
for (var elStyle; theEl && theEl !== document;) {
|
||||||
|
// Calculate the style of the element
|
||||||
elStyle = el.getComputedStyle ? el.getComputedStyle(theEl, null) : theEl.style;
|
elStyle = el.getComputedStyle ? el.getComputedStyle(theEl, null) : theEl.style;
|
||||||
|
// If there's no computed style at all, we're done, as we know that it's not hidden
|
||||||
if (!elStyle) {
|
if (!elStyle) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the element's computed style includes `display: none` or `visibility: hidden`, we know it's hidden
|
||||||
if ('none' === elStyle.display || 'hidden' == elStyle.visibility) {
|
if ('none' === elStyle.display || 'hidden' == elStyle.visibility) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// walk up
|
// At this point, we aren't sure if the element is hidden or not, so we need to keep walking up the tree
|
||||||
theEl = theEl.parentNode;
|
theEl = theEl.parentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
return theEl === document;
|
return theEl === document;
|
||||||
}
|
}
|
||||||
|
|
||||||
// is a dom element "viewable" on screen?
|
/**
|
||||||
|
* Determine if the element is "viewable" on the screen.
|
||||||
|
* "Viewable" is defined as being visible in the DOM and being within the confines of the viewport.
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @returns {boolean} Returns `true` if the element is viewable and `false` otherwise
|
||||||
|
*/
|
||||||
function isElementViewable(el) {
|
function isElementViewable(el) {
|
||||||
var theDoc = el.ownerDocument.documentElement,
|
var theDoc = el.ownerDocument.documentElement,
|
||||||
rect = el.getBoundingClientRect(),
|
rect = el.getBoundingClientRect(), // getBoundingClientRect is relative to the viewport
|
||||||
docScrollWidth = theDoc.scrollWidth,
|
docScrollWidth = theDoc.scrollWidth, // scrollWidth is the width of the document including any overflow
|
||||||
docScrollHeight = theDoc.scrollHeight,
|
docScrollHeight = theDoc.scrollHeight, // scrollHeight is the height of the document including any overflow
|
||||||
leftOffset = rect.left - theDoc.clientLeft,
|
leftOffset = rect.left - theDoc.clientLeft, // How far from the left of the viewport is the element, minus the left border width?
|
||||||
topOffset = rect.top - theDoc.clientTop,
|
topOffset = rect.top - theDoc.clientTop, // How far from the top of the viewport is the element, minus the top border width?
|
||||||
theRect;
|
theRect;
|
||||||
|
|
||||||
if (!isElementVisible(el) || !el.offsetParent || 10 > el.clientWidth || 10 > el.clientHeight) {
|
if (!isElementVisible(el) || !el.offsetParent || 10 > el.clientWidth || 10 > el.clientHeight) {
|
||||||
|
@ -512,30 +598,49 @@
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If any of the rects have a left side that is further right than the document width or a right side that is
|
||||||
|
// further left than the origin (i.e. is negative), we consider the element to be not viewable
|
||||||
for (var i = 0; i < rects.length; i++) {
|
for (var i = 0; i < rects.length; i++) {
|
||||||
if (theRect = rects[i], theRect.left > docScrollWidth || 0 > theRect.right) {
|
if (theRect = rects[i], theRect.left > docScrollWidth || 0 > theRect.right) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the element is further left than the document width, or further down than the document height, we know that it's not viewable
|
||||||
if (0 > leftOffset || leftOffset > docScrollWidth || 0 > topOffset || topOffset > docScrollHeight) {
|
if (0 > leftOffset || leftOffset > docScrollWidth || 0 > topOffset || topOffset > docScrollHeight) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// walk the tree
|
// Our next check is going to get the center point of the element, and then use elementFromPoint to see if the element
|
||||||
|
// is actually returned from that point. If it is, we know that it's viewable. If it isn't, we know that it's not viewable.
|
||||||
|
// If the right side of the bounding rectangle is outside the viewport, the x coordinate of the center point is the window width (minus offset) divided by 2.
|
||||||
|
// If the right side of the bounding rectangle is inside the viewport, the x coordinate of the center point is the width of the bounding rectangle divided by 2.
|
||||||
|
// If the bottom of the bounding rectangle is outside the viewport, the y coordinate of the center point is the window height (minus offset) divided by 2.
|
||||||
|
// If the bottom side of the bounding rectangle is inside the viewport, the y coordinate of the center point is the height of the bounding rectangle divided by
|
||||||
|
// We then use elementFromPoint to find the element at that point.
|
||||||
for (var pointEl = el.ownerDocument.elementFromPoint(leftOffset + (rect.right > window.innerWidth ? (window.innerWidth - leftOffset) / 2 : rect.width / 2), topOffset + (rect.bottom > window.innerHeight ? (window.innerHeight - topOffset) / 2 : rect.height / 2)); pointEl && pointEl !== el && pointEl !== document;) {
|
for (var pointEl = el.ownerDocument.elementFromPoint(leftOffset + (rect.right > window.innerWidth ? (window.innerWidth - leftOffset) / 2 : rect.width / 2), topOffset + (rect.bottom > window.innerHeight ? (window.innerHeight - topOffset) / 2 : rect.height / 2)); pointEl && pointEl !== el && pointEl !== document;) {
|
||||||
if (pointEl.tagName && 'string' === typeof pointEl.tagName && 'label' === pointEl.tagName.toLowerCase()
|
// If the element we found is a label, and the element we're checking has labels
|
||||||
&& el.labels && 0 < el.labels.length) {
|
if (pointEl.tagName && 'string' === typeof pointEl.tagName && 'label' === pointEl.tagName.toLowerCase()
|
||||||
return 0 <= Array.prototype.slice.call(el.labels).indexOf(pointEl);
|
&& el.labels && 0 < el.labels.length) {
|
||||||
}
|
// Return true if the element we found is one of the labels for the element we're checking.
|
||||||
|
// This means that the element we're looking for is considered viewable
|
||||||
|
return 0 <= Array.prototype.slice.call(el.labels).indexOf(pointEl);
|
||||||
|
}
|
||||||
|
|
||||||
// walk up
|
// Walk up the DOM tree to check the parent element
|
||||||
pointEl = pointEl.parentNode;
|
pointEl = pointEl.parentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the for loop exited because we found the element we're looking for, return true, as it's viewable
|
||||||
|
// If the element that we found isn't the element we're looking for, it means the element we're looking for is not viewable
|
||||||
return pointEl === el;
|
return pointEl === el;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the element from the document with the specified `opid` property
|
||||||
|
* @param {number} opId
|
||||||
|
* @returns {HTMLElement} The element with the specified `opiId`, or `null` if no such element exists
|
||||||
|
*/
|
||||||
function getElementForOPID(opId) {
|
function getElementForOPID(opId) {
|
||||||
var theEl;
|
var theEl;
|
||||||
if (void 0 === opId || null === opId) {
|
if (void 0 === opId || null === opId) {
|
||||||
|
@ -561,7 +666,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all the form elements that we care about
|
/**
|
||||||
|
* Query `theDoc` for form elements that we can use for autofill, ranked by importance and limited by `limit`
|
||||||
|
* @param {Document} theDoc The Document to query
|
||||||
|
* @param {number} limit The maximum number of elements to return
|
||||||
|
* @returns An array of HTMLElements
|
||||||
|
*/
|
||||||
function getFormElements(theDoc, limit) {
|
function getFormElements(theDoc, limit) {
|
||||||
// START MODIFICATION
|
// START MODIFICATION
|
||||||
var els = [];
|
var els = [];
|
||||||
|
@ -603,7 +713,11 @@
|
||||||
// END MODIFICATION
|
// END MODIFICATION
|
||||||
}
|
}
|
||||||
|
|
||||||
// focus the element and optionally restore its original value
|
/**
|
||||||
|
* Focus the element `el` and optionally restore its original value
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @param {boolean} setVal Set the value of the element to its original value
|
||||||
|
*/
|
||||||
function focusElement(el, setVal) {
|
function focusElement(el, setVal) {
|
||||||
if (setVal) {
|
if (setVal) {
|
||||||
var initialValue = el.value;
|
var initialValue = el.value;
|
||||||
|
@ -755,7 +869,12 @@
|
||||||
return el ? (fillTheElement(el, op), [el]) : null;
|
return el ? (fillTheElement(el, op), [el]) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// do a fill by query operation
|
/**
|
||||||
|
* Find all elements matching `query` and fill them using the value `op` from the fill script
|
||||||
|
* @param {string} query
|
||||||
|
* @param {string} op
|
||||||
|
* @returns {HTMLElement}
|
||||||
|
*/
|
||||||
function doFillByQuery(query, op) {
|
function doFillByQuery(query, op) {
|
||||||
var elements = selectAllFromDoc(query);
|
var elements = selectAllFromDoc(query);
|
||||||
return Array.prototype.map.call(Array.prototype.slice.call(elements), function (el) {
|
return Array.prototype.map.call(Array.prototype.slice.call(elements), function (el) {
|
||||||
|
@ -764,7 +883,12 @@
|
||||||
}, this);
|
}, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// do a simple set value by query
|
/**
|
||||||
|
* Assign `valueToSet` to all elements in the DOM that match `query`.
|
||||||
|
* @param {string} query
|
||||||
|
* @param {string} valueToSet
|
||||||
|
* @returns {Array} Array of elements that were set.
|
||||||
|
*/
|
||||||
function doSimpleSetByQuery(query, valueToSet) {
|
function doSimpleSetByQuery(query, valueToSet) {
|
||||||
var elements = selectAllFromDoc(query),
|
var elements = selectAllFromDoc(query),
|
||||||
arr = [];
|
arr = [];
|
||||||
|
@ -774,7 +898,11 @@
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// focus by opid
|
/**
|
||||||
|
* Do a a click and focus on the element with the given `opId`.
|
||||||
|
* @param {number} opId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function doFocusByOpId(opId) {
|
function doFocusByOpId(opId) {
|
||||||
var el = getElementByOpId(opId)
|
var el = getElementByOpId(opId)
|
||||||
if (el) {
|
if (el) {
|
||||||
|
@ -785,13 +913,21 @@
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// do a click by opid operation
|
/**
|
||||||
|
* Do a click on the element with the given `opId`.
|
||||||
|
* @param {number} opId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function doClickByOpId(opId) {
|
function doClickByOpId(opId) {
|
||||||
var el = getElementByOpId(opId);
|
var el = getElementByOpId(opId);
|
||||||
return el ? clickElement(el) ? [el] : null : null;
|
return el ? clickElement(el) ? [el] : null : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// do a click by query operation
|
/**
|
||||||
|
* Do a `click` and `focus` on all elements that match the query.
|
||||||
|
* @param {string} query
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function doClickByQuery(query) {
|
function doClickByQuery(query) {
|
||||||
query = selectAllFromDoc(query);
|
query = selectAllFromDoc(query);
|
||||||
return Array.prototype.map.call(Array.prototype.slice.call(query), function (el) {
|
return Array.prototype.map.call(Array.prototype.slice.call(query), function (el) {
|
||||||
|
@ -811,7 +947,11 @@
|
||||||
},
|
},
|
||||||
styleTimeout = 200;
|
styleTimeout = 200;
|
||||||
|
|
||||||
// fill an element
|
/**
|
||||||
|
* Fll an element `el` using the value `op` from the fill script
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @param {string} op
|
||||||
|
*/
|
||||||
function fillTheElement(el, op) {
|
function fillTheElement(el, op) {
|
||||||
var shouldCheck;
|
var shouldCheck;
|
||||||
if (el && null !== op && void 0 !== op && !(el.disabled || el.a || el.readOnly)) {
|
if (el && null !== op && void 0 !== op && !(el.disabled || el.a || el.readOnly)) {
|
||||||
|
@ -840,7 +980,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// do all the full operations needed
|
/**
|
||||||
|
* Do all the fill operations needed on the element `el`.
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @param {*} afterValSetFunc The function to perform after the operations are complete.
|
||||||
|
*/
|
||||||
function doAllFillOperations(el, afterValSetFunc) {
|
function doAllFillOperations(el, afterValSetFunc) {
|
||||||
setValueForElement(el);
|
setValueForElement(el);
|
||||||
afterValSetFunc(el);
|
afterValSetFunc(el);
|
||||||
|
@ -860,7 +1004,12 @@
|
||||||
|
|
||||||
document.elementForOPID = getElementByOpId;
|
document.elementForOPID = getElementByOpId;
|
||||||
|
|
||||||
// normalize the event based on API support
|
/**
|
||||||
|
* Normalize the event based on API support
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @param {string} eventName
|
||||||
|
* @returns {Event} A normalized event
|
||||||
|
*/
|
||||||
function normalizeEvent(el, eventName) {
|
function normalizeEvent(el, eventName) {
|
||||||
var ev;
|
var ev;
|
||||||
if ('KeyboardEvent' in window) {
|
if ('KeyboardEvent' in window) {
|
||||||
|
@ -882,7 +1031,11 @@
|
||||||
return ev;
|
return ev;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set value of the given element
|
/**
|
||||||
|
* Simulate the entry of a value into an element.
|
||||||
|
* Clicks the element, focuses it, and then fires a keydown, keypress, and keyup event.
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
*/
|
||||||
function setValueForElement(el) {
|
function setValueForElement(el) {
|
||||||
var valueToSet = el.value;
|
var valueToSet = el.value;
|
||||||
clickElement(el);
|
clickElement(el);
|
||||||
|
@ -893,7 +1046,11 @@
|
||||||
el.value !== valueToSet && (el.value = valueToSet);
|
el.value !== valueToSet && (el.value = valueToSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// set value of the given element by using events
|
/**
|
||||||
|
* Simulate the entry of a value into an element by using events.
|
||||||
|
* Dispatches a keydown, keypress, and keyup event, then fires the `input` and `change` events before removing focus.
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
*/
|
||||||
function setValueForElementByEvent(el) {
|
function setValueForElementByEvent(el) {
|
||||||
var valueToSet = el.value,
|
var valueToSet = el.value,
|
||||||
ev1 = el.ownerDocument.createEvent('HTMLEvents'),
|
ev1 = el.ownerDocument.createEvent('HTMLEvents'),
|
||||||
|
@ -910,7 +1067,11 @@
|
||||||
el.value !== valueToSet && (el.value = valueToSet);
|
el.value !== valueToSet && (el.value = valueToSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// click on an element
|
/**
|
||||||
|
* Click on an element `el`
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @returns {boolean} Returns true if the element was clicked and false if it was not able to be clicked
|
||||||
|
*/
|
||||||
function clickElement(el) {
|
function clickElement(el) {
|
||||||
if (!el || el && 'function' !== typeof el.click) {
|
if (!el || el && 'function' !== typeof el.click) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -919,7 +1080,10 @@
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all fields we care about
|
/**
|
||||||
|
* Get all the elements on the DOM that are likely to be a password field
|
||||||
|
* @returns {Array} Array of elements
|
||||||
|
*/
|
||||||
function getAllFields() {
|
function getAllFields() {
|
||||||
var r = RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|passe|contraseña|senha|密码|adgangskode|hasło|wachtwoord)', 'i');
|
var r = RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|passe|contraseña|senha|密码|adgangskode|hasło|wachtwoord)', 'i');
|
||||||
return Array.prototype.slice.call(selectAllFromDoc("input[type='text']")).filter(function (el) {
|
return Array.prototype.slice.call(selectAllFromDoc("input[type='text']")).filter(function (el) {
|
||||||
|
@ -927,7 +1091,9 @@
|
||||||
}, this);
|
}, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// touch all the fields
|
/**
|
||||||
|
* Touch all the fields
|
||||||
|
*/
|
||||||
function touchAllFields() {
|
function touchAllFields() {
|
||||||
getAllFields().forEach(function (el) {
|
getAllFields().forEach(function (el) {
|
||||||
setValueForElement(el);
|
setValueForElement(el);
|
||||||
|
@ -936,7 +1102,11 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// can we see the element to apply some styling?
|
/**
|
||||||
|
* Determine if we can apply styling to `el` to indicate that it was filled.
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @returns {boolean} Returns true if we can see the element to apply styling.
|
||||||
|
*/
|
||||||
function canSeeElementToStyle(el) {
|
function canSeeElementToStyle(el) {
|
||||||
var currentEl;
|
var currentEl;
|
||||||
if (currentEl = animateTheFilling) {
|
if (currentEl = animateTheFilling) {
|
||||||
|
@ -965,7 +1135,11 @@
|
||||||
return currentEl ? -1 !== 'email text password number tel url'.split(' ').indexOf(el.type || '') : false;
|
return currentEl ? -1 !== 'email text password number tel url'.split(' ').indexOf(el.type || '') : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// find the element for this operation
|
/**
|
||||||
|
* Find the element for the given `opid`.
|
||||||
|
* @param {number} theOpId
|
||||||
|
* @returns {HTMLElement} The element for the given `opid`, or `null` if not found.
|
||||||
|
*/
|
||||||
function getElementByOpId(theOpId) {
|
function getElementByOpId(theOpId) {
|
||||||
var theElement;
|
var theElement;
|
||||||
if (void 0 === theOpId || null === theOpId) {
|
if (void 0 === theOpId || null === theOpId) {
|
||||||
|
@ -993,7 +1167,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper for doc.querySelectorAll
|
/**
|
||||||
|
* Helper for doc.querySelectorAll
|
||||||
|
* @param {string} theSelector
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function selectAllFromDoc(theSelector) {
|
function selectAllFromDoc(theSelector) {
|
||||||
var d = document, elements = [];
|
var d = document, elements = [];
|
||||||
try {
|
try {
|
||||||
|
@ -1002,7 +1180,11 @@
|
||||||
return elements;
|
return elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
// focus an element and optionally re-set its value after focusing
|
/**
|
||||||
|
* Focus an element and optionally re-set its value after focusing
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @param {boolean} setValue Re-set the value after focusing
|
||||||
|
*/
|
||||||
function doFocusElement(el, setValue) {
|
function doFocusElement(el, setValue) {
|
||||||
if (setValue) {
|
if (setValue) {
|
||||||
var existingValue = el.value;
|
var existingValue = el.value;
|
||||||
|
|
|
@ -1,26 +1,101 @@
|
||||||
|
/**
|
||||||
|
* Represents a single field that is collected from the page source and is potentially autofilled.
|
||||||
|
*/
|
||||||
export default class AutofillField {
|
export default class AutofillField {
|
||||||
|
/**
|
||||||
|
* The unique identifier assigned to this field during collection of the page details
|
||||||
|
*/
|
||||||
opid: string;
|
opid: string;
|
||||||
|
/**
|
||||||
|
* Sequential number assigned to each element collected, based on its position in the DOM.
|
||||||
|
* Used to do perform proximal checks for username and password fields on the DOM.
|
||||||
|
*/
|
||||||
elementNumber: number;
|
elementNumber: number;
|
||||||
|
/**
|
||||||
|
* Designates whether the field is visible, based on the element's style
|
||||||
|
*/
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
|
/**
|
||||||
|
* Designates whether the field is viewable on the current part of the DOM that the user can see
|
||||||
|
*/
|
||||||
viewable: boolean;
|
viewable: boolean;
|
||||||
|
/**
|
||||||
|
* The HTML `id` attribute of the field
|
||||||
|
*/
|
||||||
htmlID: string;
|
htmlID: string;
|
||||||
|
/**
|
||||||
|
* The HTML `name` attribute of the field
|
||||||
|
*/
|
||||||
htmlName: string;
|
htmlName: string;
|
||||||
|
/**
|
||||||
|
* The HTML `class` attribute of the field
|
||||||
|
*/
|
||||||
htmlClass: string;
|
htmlClass: string;
|
||||||
|
/**
|
||||||
|
* The concatenated `innerText` or `textContent` of all the elements that are to the "left" of the field in the DOM
|
||||||
|
*/
|
||||||
"label-left": string;
|
"label-left": string;
|
||||||
|
/**
|
||||||
|
* The concatenated `innerText` or `textContent` of all the elements that are to the "right" of the field in the DOM
|
||||||
|
*/
|
||||||
"label-right": string;
|
"label-right": string;
|
||||||
|
/**
|
||||||
|
* For fields in a data table, the contents of the table row immediately above the field
|
||||||
|
*/
|
||||||
"label-top": string;
|
"label-top": string;
|
||||||
|
/**
|
||||||
|
* The contatenated `innerText` or `textContent` of all elements that are HTML labels for the field
|
||||||
|
*/
|
||||||
"label-tag": string;
|
"label-tag": string;
|
||||||
|
/**
|
||||||
|
* The `aria-label` attribute for the field
|
||||||
|
*/
|
||||||
"label-aria": string;
|
"label-aria": string;
|
||||||
|
/**
|
||||||
|
* The HTML `placeholder` attribute for the field
|
||||||
|
*/
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
|
/**
|
||||||
|
* The HTML `type` attribute for the field
|
||||||
|
*/
|
||||||
type: string;
|
type: string;
|
||||||
|
/**
|
||||||
|
* The HTML `value` for the field
|
||||||
|
*/
|
||||||
value: string;
|
value: string;
|
||||||
|
/**
|
||||||
|
* The `disabled` status of the field
|
||||||
|
*/
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
/**
|
||||||
|
* The `readonly` status of the field
|
||||||
|
*/
|
||||||
readonly: boolean;
|
readonly: boolean;
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* The `onePasswordFieldType` from the `dataset` on the element.
|
||||||
|
* If empty it contains the HTML `type` attribute for the field.
|
||||||
|
*/
|
||||||
onePasswordFieldType: string;
|
onePasswordFieldType: string;
|
||||||
|
/**
|
||||||
|
* The `opid` attribute value of the form that contains the field
|
||||||
|
*/
|
||||||
form: string;
|
form: string;
|
||||||
|
/**
|
||||||
|
* The `x-autocompletetype`, `autocompletetype`, or `autocomplete` attribute for the field
|
||||||
|
*/
|
||||||
autoCompleteType: string;
|
autoCompleteType: string;
|
||||||
|
/**
|
||||||
|
* For `<select>` elements, an array of the element's option `text` values
|
||||||
|
*/
|
||||||
selectInfo: any;
|
selectInfo: any;
|
||||||
|
/**
|
||||||
|
* The `maxLength` attribute for the field
|
||||||
|
*/
|
||||||
maxLength: number;
|
maxLength: number;
|
||||||
|
/**
|
||||||
|
* The `tagName` for the field
|
||||||
|
*/
|
||||||
tagName: string;
|
tagName: string;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,25 @@
|
||||||
|
/**
|
||||||
|
* Represents an HTML form whose elements can be autofilled
|
||||||
|
*/
|
||||||
export default class AutofillForm {
|
export default class AutofillForm {
|
||||||
|
/**
|
||||||
|
* The unique identifier assigned to this field during collection of the page details
|
||||||
|
*/
|
||||||
opid: string;
|
opid: string;
|
||||||
|
/**
|
||||||
|
* The HTML `name` attribute of the form field
|
||||||
|
*/
|
||||||
htmlName: string;
|
htmlName: string;
|
||||||
|
/**
|
||||||
|
* The HTML `id` attribute of the form field
|
||||||
|
*/
|
||||||
htmlID: string;
|
htmlID: string;
|
||||||
|
/**
|
||||||
|
* The HTML `action` attribute of the form field
|
||||||
|
*/
|
||||||
htmlAction: string;
|
htmlAction: string;
|
||||||
|
/**
|
||||||
|
* The HTML `method` attribute of the form field
|
||||||
|
*/
|
||||||
htmlMethod: string;
|
htmlMethod: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,25 @@
|
||||||
import AutofillField from "./autofill-field";
|
import AutofillField from "./autofill-field";
|
||||||
import AutofillForm from "./autofill-form";
|
import AutofillForm from "./autofill-form";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The details of a page that have been collected and can be used for autofill
|
||||||
|
*/
|
||||||
export default class AutofillPageDetails {
|
export default class AutofillPageDetails {
|
||||||
|
/**
|
||||||
|
* A unique identifier for the page
|
||||||
|
*/
|
||||||
documentUUID: string;
|
documentUUID: string;
|
||||||
title: string;
|
title: string;
|
||||||
url: string;
|
url: string;
|
||||||
documentUrl: string;
|
documentUrl: string;
|
||||||
tabUrl: string;
|
tabUrl: string;
|
||||||
|
/**
|
||||||
|
* A collection of all of the forms in the page DOM, keyed by their `opid`
|
||||||
|
*/
|
||||||
forms: { [id: string]: AutofillForm };
|
forms: { [id: string]: AutofillForm };
|
||||||
|
/**
|
||||||
|
* A collection of all the fields in the page DOM, keyed by their `opid`
|
||||||
|
*/
|
||||||
fields: AutofillField[];
|
fields: AutofillField[];
|
||||||
collectedTimestamp: number;
|
collectedTimestamp: number;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue