bitwarden-estensione-browser/apps/browser/src/content/autofill.js

1043 lines
40 KiB
JavaScript

!(function () {
/*
1Password Extension
Lovingly handcrafted by Dave Teare, Michael Fey, Rad Azzouz, and Roustem Karimov.
Copyright (c) 2014 AgileBits. All rights reserved.
================================================================================
Copyright (c) 2014 AgileBits Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
MODIFICATIONS FROM ORIGINAL
1. Populate isFirefox
2. Remove isChrome and isSafari since they are not used.
3. Unminify and format to meet Mozilla review requirements.
4. Remove unnecessary input types from getFormElements query selector and limit number of elements returned.
5. Remove fakeTested prop.
6. Rename com.agilebits.* stuff to com.bitwarden.*
7. Remove "some useful globals" on window
8. Add ability to autofill span[data-bwautofill] elements
*/
function collect(document, undefined) {
// START MODIFICATION
var isFirefox = navigator.userAgent.indexOf('Firefox') !== -1 || navigator.userAgent.indexOf('Gecko/') !== -1;
// END MODIFICATION
document.elementsByOPID = {};
document.addEventListener('input', function (inputevent) {
inputevent.a !== false &&
inputevent.target.tagName.toLowerCase() === 'input' &&
(inputevent.target.dataset['com.bitwarden.browser.userEdited'] = 'yes');
}, true);
function getPageDetails(theDoc, oneShotId) {
// start helpers
// get the value of a dom element's attribute
function getElementAttrValue(el, attrName) {
var attrVal = el[attrName];
if ('string' == typeof attrVal) {
return attrVal;
}
attrVal = el.getAttribute(attrName);
return 'string' == typeof attrVal ? attrVal : null;
}
// has the element been fake tested?
function checkIfFakeTested(field, el) {
if (-1 === ['text', 'password'].indexOf(el.type.toLowerCase()) ||
!(passwordRegEx.test(field.value) ||
passwordRegEx.test(field.htmlID) || passwordRegEx.test(field.htmlName) ||
passwordRegEx.test(field.placeholder) || passwordRegEx.test(field['label-tag']) ||
passwordRegEx.test(field['label-data']) || passwordRegEx.test(field['label-aria']))) {
return false;
}
if (!field.visible) {
return true;
}
if ('password' == el.type.toLowerCase()) {
return false;
}
var elType = el.type;
focusElement(el, true);
return elType !== el.type;
}
// get the value of a dom element
function getElementValue(el) {
switch (toLowerString(el.type)) {
case 'checkbox':
return el.checked ? '✓' : '';
case 'hidden':
el = el.value;
if (!el || 'number' != typeof el.length) {
return '';
}
254 < el.length && (el = el.substr(0, 254) + '...SNIPPED');
return el;
default:
// START MODIFICATION
if (!el.type && el.tagName.toLowerCase() === 'span') {
return el.innerText;
}
// END MODIFICATION
return el.value;
}
}
// get all the options for a "select" element
function getSelectElementOptions(el) {
if (!el.options) {
return null;
}
var options = Array.prototype.slice.call(el.options).map(function (option) {
var optionText = option.text ?
toLowerString(option.text).replace(/\\s/gm, '').replace(/[~`!@$%^&*()\\-_+=:;'\"\\[\\]|\\\\,<.>\\?]/gm, '') :
null;
return [optionText ? optionText : null, option.value];
})
return {
options: options
};
}
// get the top label
function getLabelTop(el) {
var parent;
for (el = el.parentElement || el.parentNode; el && 'td' != toLowerString(el.tagName);) {
el = el.parentElement || el.parentNode;
}
if (!el || void 0 === el) {
return null;
}
parent = el.parentElement || el.parentNode;
if ('tr' != parent.tagName.toLowerCase()) {
return null;
}
parent = parent.previousElementSibling;
if (!parent || 'tr' != (parent.tagName + '').toLowerCase() ||
parent.cells && el.cellIndex >= parent.cells.length) {
return null;
}
el = parent.cells[el.cellIndex];
var elText = el.textContent || el.innerText;
return elText = cleanText(elText);
}
// get all the tags for a given label
function getLabelTag(el) {
var docLabel,
theLabels = [];
if (el.labels && el.labels.length && 0 < el.labels.length) {
theLabels = Array.prototype.slice.call(el.labels);
} else {
if (el.id) {
theLabels = theLabels.concat(Array.prototype.slice.call(
queryDoc(theDoc, 'label[for=' + JSON.stringify(el.id) + ']')));
}
if (el.name) {
docLabel = queryDoc(theDoc, 'label[for=' + JSON.stringify(el.name) + ']');
for (var labelIndex = 0; labelIndex < docLabel.length; labelIndex++) {
if (-1 === theLabels.indexOf(docLabel[labelIndex])) {
theLabels.push(docLabel[labelIndex])
}
}
}
for (var theEl = el; theEl && theEl != theDoc; theEl = theEl.parentNode) {
if ('label' === toLowerString(theEl.tagName) && -1 === theLabels.indexOf(theEl)) {
theLabels.push(theEl);
}
}
}
if (0 === theLabels.length) {
theEl = el.parentNode;
if ('dd' === theEl.tagName.toLowerCase() && null !== theEl.previousElementSibling
&& 'dt' === theEl.previousElementSibling.tagName.toLowerCase()) {
theLabels.push(theEl.previousElementSibling);
}
}
if (0 > theLabels.length) {
return null;
}
return theLabels.map(function (l) {
return (l.textContent || l.innerText)
.replace(/^\\s+/, '').replace(/\\s+$/, '').replace('\\n', '').replace(/\\s{2,}/, ' ');
}).join('');
}
// add property and value to the object if there is a value
function addProp(obj, prop, val, d) {
if (0 !== d && d === val || null === val || void 0 === val) {
return;
}
obj[prop] = val;
}
// lowercase helper
function toLowerString(s) {
return 'string' === typeof s ? s.toLowerCase() : ('' + s).toLowerCase();
}
// query the document helper
function queryDoc(doc, query) {
var els = [];
try {
els = doc.querySelectorAll(query);
} catch (e) { }
return els;
}
// end helpers
var theView = theDoc.defaultView ? theDoc.defaultView : window,
passwordRegEx = RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|(\\\\b|_|-)passe(\\\\b|_|-)|contraseña|senha|密码|adgangskode|hasło|wachtwoord)', 'i');
// get all the docs
var theForms = Array.prototype.slice.call(queryDoc(theDoc, 'form')).map(function (formEl, elIndex) {
var op = {},
formOpId = '__form__' + elIndex;
formEl.opid = formOpId;
op.opid = formOpId;
addProp(op, 'htmlName', getElementAttrValue(formEl, 'name'));
addProp(op, 'htmlID', getElementAttrValue(formEl, 'id'));
formOpId = getElementAttrValue(formEl, 'action');
formOpId = new URL(formOpId, window.location.href);
addProp(op, 'htmlAction', formOpId ? formOpId.href : null);
addProp(op, 'htmlMethod', getElementAttrValue(formEl, 'method'));
return op;
});
// get all the form fields
var theFields = Array.prototype.slice.call(getFormElements(theDoc, 50)).map(function (el, elIndex) {
var field = {},
opId = '__' + elIndex,
elMaxLen = -1 == el.maxLength ? 999 : el.maxLength;
if (!elMaxLen || 'number' === typeof elMaxLen && isNaN(elMaxLen)) {
elMaxLen = 999;
}
theDoc.elementsByOPID[opId] = el;
el.opid = opId;
field.opid = opId;
field.elementNumber = elIndex;
addProp(field, 'maxLength', Math.min(elMaxLen, 999), 999);
field.visible = isElementVisible(el);
field.viewable = isElementViewable(el);
addProp(field, 'htmlID', getElementAttrValue(el, 'id'));
addProp(field, 'htmlName', getElementAttrValue(el, 'name'));
addProp(field, 'htmlClass', getElementAttrValue(el, 'class'));
addProp(field, 'tabindex', getElementAttrValue(el, 'tabindex'));
addProp(field, 'title', getElementAttrValue(el, 'title'));
// START MODIFICATION
addProp(field, 'userEdited', !!el.dataset['com.browser.browser.userEdited']);
var elTagName = el.tagName.toLowerCase();
addProp(field, 'tagName', elTagName);
if (elTagName === 'span') {
return field;
}
// END MODIFICATION
if ('hidden' != toLowerString(el.type)) {
addProp(field, 'label-tag', getLabelTag(el));
addProp(field, 'label-data', getElementAttrValue(el, 'data-label'));
addProp(field, 'label-aria', getElementAttrValue(el, 'aria-label'));
addProp(field, 'label-top', getLabelTop(el));
var labelArr = [];
for (var sib = el; sib && sib.nextSibling;) {
sib = sib.nextSibling;
if (isKnownTag(sib)) {
break;
}
checkNodeType(labelArr, sib);
}
addProp(field, 'label-right', labelArr.join(''));
labelArr = [];
shiftForLeftLabel(el, labelArr);
labelArr = labelArr.reverse().join('');
addProp(field, 'label-left', labelArr);
addProp(field, 'placeholder', getElementAttrValue(el, 'placeholder'));
}
addProp(field, 'rel', getElementAttrValue(el, 'rel'));
addProp(field, 'type', toLowerString(getElementAttrValue(el, 'type')));
addProp(field, 'value', getElementValue(el));
addProp(field, 'checked', el.checked, false);
addProp(field, 'autoCompleteType', el.getAttribute('x-autocompletetype') || el.getAttribute('autocompletetype') || el.getAttribute('autocomplete'), 'off');
addProp(field, 'disabled', el.disabled);
addProp(field, 'readonly', el.b || el.readOnly);
addProp(field, 'selectInfo', getSelectElementOptions(el));
addProp(field, 'aria-hidden', 'true' == el.getAttribute('aria-hidden'), false);
addProp(field, 'aria-disabled', 'true' == el.getAttribute('aria-disabled'), false);
addProp(field, 'aria-haspopup', 'true' == el.getAttribute('aria-haspopup'), false);
addProp(field, 'data-unmasked', el.dataset.unmasked);
addProp(field, 'data-stripe', getElementAttrValue(el, 'data-stripe'));
addProp(field, 'onepasswordFieldType', el.dataset.onepasswordFieldType || el.type);
addProp(field, 'onepasswordDesignation', el.dataset.onepasswordDesignation);
addProp(field, 'onepasswordSignInUrl', el.dataset.onepasswordSignInUrl);
addProp(field, 'onepasswordSectionTitle', el.dataset.onepasswordSectionTitle);
addProp(field, 'onepasswordSectionFieldKind', el.dataset.onepasswordSectionFieldKind);
addProp(field, 'onepasswordSectionFieldTitle', el.dataset.onepasswordSectionFieldTitle);
addProp(field, 'onepasswordSectionFieldValue', el.dataset.onepasswordSectionFieldValue);
if (el.form) {
field.form = getElementAttrValue(el.form, 'opid');
}
// START MODIFICATION
//addProp(field, 'fakeTested', checkIfFakeTested(field, el), false);
// END MODIFICATION
return field;
});
// test form fields
theFields.filter(function (f) {
return f.fakeTested;
}).forEach(function (f) {
var el = theDoc.elementsByOPID[f.opid];
el.getBoundingClientRect();
var originalValue = el.value;
// click it
!el || el && 'function' !== typeof el.click || el.click();
focusElement(el, false);
el.dispatchEvent(doEventOnElement(el, 'keydown'));
el.dispatchEvent(doEventOnElement(el, 'keypress'));
el.dispatchEvent(doEventOnElement(el, 'keyup'));
el.value !== originalValue && (el.value = originalValue);
el.click && el.click();
f.postFakeTestVisible = isElementVisible(el);
f.postFakeTestViewable = isElementViewable(el);
f.postFakeTestType = el.type;
var elValue = el.value;
var event1 = el.ownerDocument.createEvent('HTMLEvents'),
event2 = el.ownerDocument.createEvent('HTMLEvents');
el.dispatchEvent(doEventOnElement(el, 'keydown'));
el.dispatchEvent(doEventOnElement(el, 'keypress'));
el.dispatchEvent(doEventOnElement(el, 'keyup'));
event2.initEvent('input', true, true);
el.dispatchEvent(event2);
event1.initEvent('change', true, true);
el.dispatchEvent(event1);
el.blur();
el.value !== elValue && (el.value = elValue);
});
// build out the page details object. this is the final result
var pageDetails = {
documentUUID: oneShotId,
title: theDoc.title,
url: theView.location.href,
documentUrl: theDoc.location.href,
tabUrl: theView.location.href,
forms: function (forms) {
var formObj = {};
forms.forEach(function (f) {
formObj[f.opid] = f;
});
return formObj;
}(theForms),
fields: theFields,
collectedTimestamp: new Date().getTime()
};
// get proper page title. maybe they are using the special meta tag?
var theTitle = document.querySelector('[data-onepassword-title]')
if (theTitle && theTitle.dataset[DISPLAY_TITLE_ATTRIBUE]) {
pageDetails.displayTitle = theTitle.dataset.onepasswordTitle;
}
return pageDetails;
}
document.elementForOPID = getElementForOPID;
function doEventOnElement(kedol, fonor) {
var quebo;
isFirefox ? (quebo = document.createEvent('KeyboardEvent'), quebo.initKeyEvent(fonor, true, false, null, false, false, false, false, 0, 0)) : (quebo = kedol.ownerDocument.createEvent('Events'),
quebo.initEvent(fonor, true, false), quebo.charCode = 0, quebo.keyCode = 0, quebo.which = 0,
quebo.srcElement = kedol, quebo.target = kedol);
return quebo;
}
// clean up the text
function cleanText(s) {
var sVal = null;
s && (sVal = s.replace(/^\\s+|\\s+$|\\r?\\n.*$/gm, ''), sVal = 0 < sVal.length ? sVal : null);
return sVal;
}
// check the node type and adjust the array accordingly
function checkNodeType(arr, el) {
var theText = '';
3 === el.nodeType ? theText = el.nodeValue : 1 === el.nodeType && (theText = el.textContent || el.innerText);
(theText = cleanText(theText)) && arr.push(theText);
}
function isKnownTag(el) {
if (el && void 0 !== el) {
var tags = 'select option input form textarea button table iframe body head script'.split(' ');
if (el) {
var elTag = el ? (el.tagName || '').toLowerCase() : '';
return tags.constructor == Array ? 0 <= tags.indexOf(elTag) : elTag === tags;
}
else {
return false;
}
}
else {
return true;
}
}
function shiftForLeftLabel(el, arr, steps) {
var sib;
for (steps || (steps = 0); el && el.previousSibling;) {
el = el.previousSibling;
if (isKnownTag(el)) {
return;
}
checkNodeType(arr, el);
}
if (el && 0 === arr.length) {
for (sib = null; !sib;) {
el = el.parentElement || el.parentNode;
if (!el) {
return;
}
for (sib = el.previousSibling; sib && !isKnownTag(sib) && sib.lastChild;) {
sib = sib.lastChild;
}
}
// base case and recurse
isKnownTag(sib) || (checkNodeType(arr, sib), 0 === arr.length && shiftForLeftLabel(sib, arr, steps + 1));
}
}
// is a dom element visible on screen?
function isElementVisible(el) {
var theEl = el;
el = (el = el.ownerDocument) ? el.defaultView : {};
// walk the dom tree
for (var elStyle; theEl && theEl !== document;) {
elStyle = el.getComputedStyle ? el.getComputedStyle(theEl, null) : theEl.style;
if (!elStyle) {
return true;
}
if ('none' === elStyle.display || 'hidden' == elStyle.visibility) {
return false;
}
// walk up
theEl = theEl.parentNode;
}
return theEl === document;
}
// is a dom element "viewable" on screen?
function isElementViewable(el) {
var theDoc = el.ownerDocument.documentElement,
rect = el.getBoundingClientRect(),
docScrollWidth = theDoc.scrollWidth,
docScrollHeight = theDoc.scrollHeight,
leftOffset = rect.left - theDoc.clientLeft,
topOffset = rect.top - theDoc.clientTop,
theRect;
if (!isElementVisible(el) || !el.offsetParent || 10 > el.clientWidth || 10 > el.clientHeight) {
return false;
}
var rects = el.getClientRects();
if (0 === rects.length) {
return false;
}
for (var i = 0; i < rects.length; i++) {
if (theRect = rects[i], theRect.left > docScrollWidth || 0 > theRect.right) {
return false;
}
}
if (0 > leftOffset || leftOffset > docScrollWidth || 0 > topOffset || topOffset > docScrollHeight) {
return false;
}
// walk the tree
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()
&& el.labels && 0 < el.labels.length) {
return 0 <= Array.prototype.slice.call(el.labels).indexOf(pointEl);
}
// walk up
pointEl = pointEl.parentNode;
}
return pointEl === el;
}
function getElementForOPID(opId) {
var theEl;
if (void 0 === opId || null === opId) {
return null;
}
try {
var formEls = Array.prototype.slice.call(getFormElements(document));
var filteredFormEls = formEls.filter(function (el) {
return el.opid == opId;
});
if (0 < filteredFormEls.length) {
theEl = filteredFormEls[0], 1 < filteredFormEls.length && console.warn('More than one element found with opid ' + opId);
} else {
var theIndex = parseInt(opId.split('__')[1], 10);
isNaN(theIndex) || (theEl = formEls[theIndex]);
}
} catch (e) {
console.error('An unexpected error occurred: ' + e);
} finally {
return theEl;
}
}
// get all the form elements that we care about
function getFormElements(theDoc, limit) {
// START MODIFICATION
var els = [];
try {
var elsList = theDoc.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="reset"])' +
':not([type="button"]):not([type="image"]):not([type="file"]):not([data-bwignore]), select, ' +
'span[data-bwautofill]');
els = Array.prototype.slice.call(elsList);
} catch (e) { }
if (!limit || els.length <= limit) {
return els;
}
// non-checkboxes/radios have higher priority
var returnEls = [];
var unimportantEls = [];
for (var i = 0; i < els.length; i++) {
if (returnEls.length >= limit) {
break;
}
var el = els[i];
var type = el.type ? el.type.toLowerCase() : el.type;
if (type === 'checkbox' || type === 'radio') {
unimportantEls.push(el);
}
else {
returnEls.push(el);
}
}
var unimportantElsToAdd = limit - returnEls.length;
if (unimportantElsToAdd > 0) {
returnEls = returnEls.concat(unimportantEls.slice(0, unimportantElsToAdd));
}
return returnEls;
// END MODIFICATION
}
// focus the element and optionally restore its original value
function focusElement(el, setVal) {
if (setVal) {
var initialValue = el.value;
el.focus();
if (el.value !== initialValue) {
el.value = initialValue;
}
} else {
el.focus();
}
}
return JSON.stringify(getPageDetails(document, 'oneshotUUID'));
}
function fill(document, fillScript, undefined) {
var isFirefox = navigator.userAgent.indexOf('Firefox') !== -1 || navigator.userAgent.indexOf('Gecko/') !== -1;
var markTheFilling = true,
animateTheFilling = true;
// Check if URL is not secure when the original saved one was
function urlNotSecure(savedURL) {
var passwordInputs = null;
if (!savedURL) {
return false;
}
return 0 === savedURL.indexOf('https://') && 'http:' === document.location.protocol && (passwordInputs = document.querySelectorAll('input[type=password]'),
0 < passwordInputs.length && (confirmResult = confirm('Warning: This is an unsecured HTTP page, and any information you submit can potentially be seen and changed by others. This Login was originally saved on a secure (HTTPS) page.\\n\\nDo you still wish to fill this login?'),
0 == confirmResult)) ? true : false;
}
function doFill(fillScript) {
var fillScriptOps,
theOpIds = [],
fillScriptProperties = fillScript.properties,
operationDelayMs = 1,
doOperation,
operationsToDo = [];
fillScriptProperties &&
fillScriptProperties.delay_between_operations &&
(operationDelayMs = fillScriptProperties.delay_between_operations);
if (urlNotSecure(fillScript.savedURL)) {
return;
}
doOperation = function (ops, theOperation) {
var op = ops[0];
if (void 0 === op) {
theOperation();
} else {
// should we delay?
if ('delay' === op.operation || 'delay' === op[0]) {
operationDelayMs = op.parameters ? op.parameters[0] : op[1];
} else {
if (op = normalizeOp(op)) {
for (var opIndex = 0; opIndex < op.length; opIndex++) {
-1 === operationsToDo.indexOf(op[opIndex]) && operationsToDo.push(op[opIndex]);
}
}
theOpIds = theOpIds.concat(operationsToDo.map(function (operationToDo) {
return operationToDo && operationToDo.hasOwnProperty('opid') ? operationToDo.opid : null;
}));
}
setTimeout(function () {
doOperation(ops.slice(1), theOperation);
}, operationDelayMs);
}
};
if (fillScriptOps = fillScript.options) {
fillScriptOps.hasOwnProperty('animate') && (animateTheFilling = fillScriptOps.animate),
fillScriptOps.hasOwnProperty('markFilling') && (markTheFilling = fillScriptOps.markFilling);
}
// don't mark a password filling
fillScript.itemType && 'fillPassword' === fillScript.itemType && (markTheFilling = false);
if (!fillScript.hasOwnProperty('script')) {
return;
}
// custom fill script
fillScriptOps = fillScript.script;
doOperation(fillScriptOps, function () {
// Done now
// Do we have anything to autosubmit?
if (fillScript.hasOwnProperty('autosubmit') && 'function' == typeof autosubmit) {
fillScript.itemType && 'fillLogin' !== fillScript.itemType || (0 < operationsToDo.length ? setTimeout(function () {
autosubmit(fillScript.autosubmit, fillScriptProperties.allow_clicky_autosubmit, operationsToDo);
}, AUTOSUBMIT_DELAY) : DEBUG_AUTOSUBMIT && console.log('[AUTOSUBMIT] Not attempting to submit since no fields were filled: ', operationsToDo))
}
// handle protectedGlobalPage
if ('object' == typeof protectedGlobalPage) {
protectedGlobalPage.b('fillItemResults', {
documentUUID: documentUUID,
fillContextIdentifier: fillScript.fillContextIdentifier,
usedOpids: theOpIds
}, function () {
fillingItemType = null;
})
}
});
}
// fill for reference
var thisFill = {
fill_by_opid: doFillByOpId,
fill_by_query: doFillByQuery,
click_on_opid: doClickByOpId,
click_on_query: doClickByQuery,
touch_all_fields: touchAllFields,
simple_set_value_by_query: doSimpleSetByQuery,
focus_by_opid: doFocusByOpId,
delay: null
};
// normalize the op versus the reference
function normalizeOp(op) {
var thisOperation;
if (op.hasOwnProperty('operation') && op.hasOwnProperty('parameters')) {
thisOperation = op.operation, op = op.parameters;
} else {
if ('[object Array]' === Object.prototype.toString.call(op)) {
thisOperation = op[0],
op = op.splice(1);
} else {
return null;
}
}
return thisFill.hasOwnProperty(thisOperation) ? thisFill[thisOperation].apply(this, op) : null;
}
// do a fill by opid operation
function doFillByOpId(opId, op) {
var el = getElementByOpId(opId);
return el ? (fillTheElement(el, op), [el]) : null;
}
// do a fill by query operation
function doFillByQuery(query, op) {
var elements = selectAllFromDoc(query);
return Array.prototype.map.call(Array.prototype.slice.call(elements), function (el) {
fillTheElement(el, op);
return el;
}, this);
}
// do a simple set value by query
function doSimpleSetByQuery(query, valueToSet) {
var elements = selectAllFromDoc(query),
arr = [];
Array.prototype.forEach.call(Array.prototype.slice.call(elements), function (el) {
el.disabled || el.a || el.readOnly || void 0 === el.value || (el.value = valueToSet, arr.push(el));
});
return arr;
}
// focus by opid
function doFocusByOpId(opId) {
var el = getElementByOpId(opId)
if (el) {
'function' === typeof el.click && el.click(),
'function' === typeof el.focus && doFocusElement(el, true);
}
return null;
}
// do a click by opid operation
function doClickByOpId(opId) {
var el = getElementByOpId(opId);
return el ? clickElement(el) ? [el] : null : null;
}
// do a click by query operation
function doClickByQuery(query) {
query = selectAllFromDoc(query);
return Array.prototype.map.call(Array.prototype.slice.call(query), function (el) {
clickElement(el);
'function' === typeof el.click && el.click();
'function' === typeof el.focus && doFocusElement(el, true);
return [el];
}, this);
}
var checkRadioTrueOps = {
'true': true,
y: true,
1: true,
yes: true,
'✓': true
},
styleTimeout = 200;
// fill an element
function fillTheElement(el, op) {
var shouldCheck;
if (el && null !== op && void 0 !== op && !(el.disabled || el.a || el.readOnly)) {
switch (markTheFilling && el.form && !el.form.opfilled && (el.form.opfilled = true),
el.type ? el.type.toLowerCase() : null) {
case 'checkbox':
shouldCheck = op && 1 <= op.length && checkRadioTrueOps.hasOwnProperty(op.toLowerCase()) && true === checkRadioTrueOps[op.toLowerCase()];
el.checked === shouldCheck || doAllFillOperations(el, function (theEl) {
theEl.checked = shouldCheck;
});
break;
case 'radio':
true === checkRadioTrueOps[op.toLowerCase()] && el.click();
break;
default:
el.value == op || doAllFillOperations(el, function (theEl) {
// START MODIFICATION
if (!theEl.type && theEl.tagName.toLowerCase() === 'span') {
theEl.innerText = op;
return;
}
// END MODIFICATION
theEl.value = op;
});
}
}
}
// do all the full operations needed
function doAllFillOperations(el, afterValSetFunc) {
setValueForElement(el);
afterValSetFunc(el);
setValueForElementByEvent(el);
// START MODIFICATION
if (canSeeElementToStyle(el)) {
el.classList.add('com-bitwarden-browser-animated-fill');
setTimeout(function () {
if (el) {
el.classList.remove('com-bitwarden-browser-animated-fill');
}
}, styleTimeout);
}
// END MODIFICATION
}
document.elementForOPID = getElementByOpId;
// normalize the event based on API support
function normalizeEvent(el, eventName) {
var ev;
if ('KeyboardEvent' in window) {
ev = new window.KeyboardEvent(eventName, {
bubbles: true,
cancelable: false,
});
}
else {
ev = el.ownerDocument.createEvent('Events');
ev.initEvent(eventName, true, false);
ev.charCode = 0;
ev.keyCode = 0;
ev.which = 0;
ev.srcElement = el;
ev.target = el;
}
return ev;
}
// set value of the given element
function setValueForElement(el) {
var valueToSet = el.value;
clickElement(el);
doFocusElement(el, false);
el.dispatchEvent(normalizeEvent(el, 'keydown'));
el.dispatchEvent(normalizeEvent(el, 'keypress'));
el.dispatchEvent(normalizeEvent(el, 'keyup'));
el.value !== valueToSet && (el.value = valueToSet);
}
// set value of the given element by using events
function setValueForElementByEvent(el) {
var valueToSet = el.value,
ev1 = el.ownerDocument.createEvent('HTMLEvents'),
ev2 = el.ownerDocument.createEvent('HTMLEvents');
el.dispatchEvent(normalizeEvent(el, 'keydown'));
el.dispatchEvent(normalizeEvent(el, 'keypress'));
el.dispatchEvent(normalizeEvent(el, 'keyup'));
ev2.initEvent('input', true, true);
el.dispatchEvent(ev2);
ev1.initEvent('change', true, true);
el.dispatchEvent(ev1);
el.blur();
el.value !== valueToSet && (el.value = valueToSet);
}
// click on an element
function clickElement(el) {
if (!el || el && 'function' !== typeof el.click) {
return false;
}
el.click();
return true;
}
// get all fields we care about
function getAllFields() {
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 el.value && r.test(el.value);
}, this);
}
// touch all the fields
function touchAllFields() {
getAllFields().forEach(function (el) {
setValueForElement(el);
el.click && el.click();
setValueForElementByEvent(el);
});
}
// can we see the element to apply some styling?
function canSeeElementToStyle(el) {
var currentEl;
if (currentEl = animateTheFilling) {
a: {
currentEl = el;
for (var owner = el.ownerDocument, owner = owner ? owner.defaultView : {}, theStyle; currentEl && currentEl !== document;) {
theStyle = owner.getComputedStyle ? owner.getComputedStyle(currentEl, null) : currentEl.style;
if (!theStyle) {
currentEl = true;
break a;
}
if ('none' === theStyle.display || 'hidden' == theStyle.visibility) {
currentEl = false;
break a;
}
currentEl = currentEl.parentNode;
}
currentEl = currentEl === document;
}
}
// START MODIFICATION
if (el && !el.type && el.tagName.toLowerCase() === 'span') {
return true;
}
// END MODIFICATION
return currentEl ? -1 !== 'email text password number tel url'.split(' ').indexOf(el.type || '') : false;
}
// find the element for this operation
function getElementByOpId(theOpId) {
var theElement;
if (void 0 === theOpId || null === theOpId) {
return null;
}
try {
// START MODIFICATION
var elements = Array.prototype.slice.call(selectAllFromDoc('input, select, button, ' +
'span[data-bwautofill]'));
// END MODIFICATION
var filteredElements = elements.filter(function (o) {
return o.opid == theOpId;
});
if (0 < filteredElements.length) {
theElement = filteredElements[0],
1 < filteredElements.length && console.warn('More than one element found with opid ' + theOpId);
} else {
var elIndex = parseInt(theOpId.split('__')[1], 10);
isNaN(elIndex) || (theElement = elements[elIndex]);
}
} catch (e) {
console.error('An unexpected error occurred: ' + e);
} finally {
return theElement;
}
}
// helper for doc.querySelectorAll
function selectAllFromDoc(theSelector) {
var d = document, elements = [];
try {
elements = d.querySelectorAll(theSelector);
} catch (e) { }
return elements;
}
// focus an element and optionally re-set its value after focusing
function doFocusElement(el, setValue) {
if (setValue) {
var existingValue = el.value;
el.focus();
el.value !== existingValue && (el.value = existingValue);
} else {
el.focus();
}
}
doFill(fillScript);
return JSON.stringify({
success: true
});
}
/*
End 1Password Extension
*/
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
if (msg.command === 'collectPageDetails') {
var pageDetails = collect(document);
var pageDetailsObj = JSON.parse(pageDetails);
chrome.runtime.sendMessage({
command: 'collectPageDetailsResponse',
tab: msg.tab,
details: pageDetailsObj,
sender: msg.sender
});
sendResponse();
return true;
}
else if (msg.command === 'fillForm') {
fill(document, msg.fillScript);
sendResponse();
return true;
}
});
})();