1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 06:13:38 +00:00

Revert "[PS-1918] Make autofill doc-scanner traverse into ShadowRoot (#4119)" (#5147)

This reverts commit 208be8dfbf.
This commit is contained in:
Todd Martin
2023-04-04 08:07:48 -04:00
committed by GitHub
parent 0d6dfdd4a6
commit 8f9ce3dc8a

View File

@@ -31,115 +31,21 @@
/* /*
MODIFICATIONS FROM ORIGINAL MODIFICATIONS FROM ORIGINAL
1. Populate isFirefox 1. Populate isFirefox
2. Remove isChrome and isSafari since they are not used. 2. Remove isChrome and isSafari since they are not used.
3. Unminify and format to meet Mozilla review requirements. 3. Unminify and format to meet Mozilla review requirements.
4. Remove unnecessary input types from getFormElements query selector and limit number of elements returned. 4. Remove unnecessary input types from getFormElements query selector and limit number of elements returned.
5. Remove fakeTested prop. 5. Remove fakeTested prop.
6. Rename com.agilebits.* stuff to com.bitwarden.* 6. Rename com.agilebits.* stuff to com.bitwarden.*
7. Remove "some useful globals" on window 7. Remove "some useful globals" on window
8. Add ability to autofill span[data-bwautofill] elements 8. Add ability to autofill span[data-bwautofill] elements
9. Add new handler, for new command that responds with page details in response callback 9. Add new handler, for new command that responds with page details in response callback
10. Handle sandbox iframe and sandbox rule in CSP 10. Handle sandbox iframe and sandbox rule in CSP
11. Work on array of saved urls instead of just one to determine if we should autofill non-https sites 11. Work on array of saved urls instead of just one to determine if we should autofill non-https sites
12. Remove setting of attribute com.browser.browser.userEdited on user-inputs 12. Remove setting of attribute com.browser.browser.userEdited on user-inputs
13. Handle null value URLs in urlNotSecure 13. Handle null value URLs in urlNotSecure
14. Implement new HTML element query logic to be able to traverse into ShadowRoot
*/ */
/*
* `openOrClosedShadowRoot` is only available to WebExtensions.
* We need to use the correct implementation based on browser.
*/
// START MODIFICATION
var getShadowRoot;
if (chrome.dom && chrome.dom.openOrClosedShadowRoot) {
// Chromium 88+
// https://developer.chrome.com/docs/extensions/reference/dom/
getShadowRoot = function (element) {
if (!(element instanceof HTMLElement)) {
return null;
}
return chrome.dom.openOrClosedShadowRoot(element);
};
} else {
getShadowRoot = function (element) {
// `openOrClosedShadowRoot` is currently available for Firefox 63+
// https://developer.mozilla.org/en-US/docs/Web/API/Element/openOrClosedShadowRoot
// Fallback to usual shadowRoot if it doesn't exist, which will only find open ShadowRoots, not closed ones.
// https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot#browser_compatibility
return element.openOrClosedShadowRoot || element.shadowRoot;
};
}
/*
* Returns elements like Document.querySelectorAll does, but traverses the document and shadow
* roots, yielding a visited node only if it passes the predicate in filterCallback.
*/
function queryDocAll(doc, rootEl, filterCallback) {
var accumulatedNodes = [];
// mutates accumulatedNodes
accumulatingQueryDocAll(doc, rootEl, filterCallback, accumulatedNodes);
return accumulatedNodes;
}
function accumulatingQueryDocAll(doc, rootEl, filterCallback, accumulatedNodes) {
var treeWalker = doc.createTreeWalker(rootEl, NodeFilter.SHOW_ELEMENT);
var node;
while (node = treeWalker.nextNode()) {
if (filterCallback(node)) {
accumulatedNodes.push(node);
}
// If node contains a ShadowRoot we want to step into it and also traverse all child nodes inside.
var nodeShadowRoot = getShadowRoot(node);
if (!nodeShadowRoot) {
continue;
}
// recursively traverse into ShadowRoot
accumulatingQueryDocAll(doc, nodeShadowRoot, filterCallback, accumulatedNodes);
}
}
/*
* Returns an element like Document.querySelector does, but traverses the document and shadow
* roots, yielding a visited node only if it passes the predicate in filterCallback.
*/
function queryDoc(doc, rootEl, filterCallback) {
var treeWalker = doc.createTreeWalker(rootEl, NodeFilter.SHOW_ELEMENT);
var node;
while (node = treeWalker.nextNode()) {
if (filterCallback(node)) {
return node;
}
// If node contains a ShadowRoot we want to step into it and also traverse all child nodes inside.
var nodeShadowRoot = getShadowRoot(node);
if (!nodeShadowRoot) {
continue;
}
// recursively traverse into ShadowRoot
var subQueryResult = queryDoc(doc, nodeShadowRoot, filterCallback);
if (subQueryResult) {
return subQueryResult;
}
}
return null;
}
// END MODIFICATION
function collect(document, undefined) { function collect(document, undefined) {
// START MODIFICATION // START MODIFICATION
var isFirefox = navigator.userAgent.indexOf('Firefox') !== -1 || navigator.userAgent.indexOf('Gecko/') !== -1; var isFirefox = navigator.userAgent.indexOf('Firefox') !== -1 || navigator.userAgent.indexOf('Gecko/') !== -1;
@@ -292,22 +198,12 @@
theLabels = Array.prototype.slice.call(el.labels); theLabels = Array.prototype.slice.call(el.labels);
} else { } else {
if (el.id) { if (el.id) {
// START MODIFICATION theLabels = theLabels.concat(Array.prototype.slice.call(
var elId = JSON.stringify(el.id); queryDoc(theDoc, 'label[for=' + JSON.stringify(el.id) + ']')));
var labelsByReferencedId = queryDocAll(theDoc, theDoc.body, function (node) {
return node.nodeName === 'LABEL' && node.htmlFor === elId;
});
theLabels = theLabels.concat(labelsByReferencedId);
// END MODIFICATION
} }
if (el.name) { if (el.name) {
// START MODIFICATION docLabel = queryDoc(theDoc, 'label[for=' + JSON.stringify(el.name) + ']');
var elName = JSON.stringify(el.name);
docLabel = queryDocAll(theDoc, theDoc.body, function (node) {
return node.nodeName === 'LABEL' && node.htmlFor === elName;
});
// END MODIFICATION
for (var labelIndex = 0; labelIndex < docLabel.length; labelIndex++) { for (var labelIndex = 0; labelIndex < docLabel.length; labelIndex++) {
if (-1 === theLabels.indexOf(docLabel[labelIndex])) { if (-1 === theLabels.indexOf(docLabel[labelIndex])) {
@@ -364,21 +260,28 @@
function toLowerString(s) { function toLowerString(s) {
return 'string' === typeof s ? s.toLowerCase() : ('' + s).toLowerCase(); return 'string' === typeof s ? s.toLowerCase() : ('' + s).toLowerCase();
} }
// START MODIFICATION
// renamed queryDoc to queryDocAll and moved to top /**
// END MODIFICATION * 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) {
var els = [];
try {
els = doc.querySelectorAll(query);
} catch (e) { }
return els;
}
// end helpers // end helpers
var theView = theDoc.defaultView ? theDoc.defaultView : window, 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'); passwordRegEx = RegExp('((\\\\b|_|-)pin(\\\\b|_|-)|password|passwort|kennwort|(\\\\b|_|-)passe(\\\\b|_|-)|contraseña|senha|密码|adgangskode|hasło|wachtwoord)', 'i');
// get all the docs // get all the docs
// START MODIFICATION var theForms = Array.prototype.slice.call(queryDoc(theDoc, 'form')).map(function (formEl, elIndex) {
var formNodes = queryDocAll(theDoc, theDoc.body, function (el) {
return el.nodeName === 'FORM';
});
var theForms = formNodes.map(function (formEl, elIndex) {
// END MODIFICATION
var op = {}, var op = {},
formOpId = '__form__' + elIndex; formOpId = '__form__' + elIndex;
@@ -536,11 +439,7 @@
}; };
// get proper page title. maybe they are using the special meta tag? // get proper page title. maybe they are using the special meta tag?
// START MODIFICATION var theTitle = document.querySelector('[data-onepassword-title]')
var theTitle = queryDoc(theDoc, theDoc, function (node) {
return node.hasAttribute('data-onepassword-title');
});
// END MODIFICATION
if (theTitle && theTitle.dataset[DISPLAY_TITLE_ATTRIBUE]) { if (theTitle && theTitle.dataset[DISPLAY_TITLE_ATTRIBUE]) {
pageDetails.displayTitle = theTitle.dataset.onepasswordTitle; pageDetails.displayTitle = theTitle.dataset.onepasswordTitle;
} }
@@ -656,10 +555,7 @@
// walk the dom tree until we reach the top // 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 // Calculate the style of the element
// START MODIFICATION elStyle = el.getComputedStyle ? el.getComputedStyle(theEl, null) : theEl.style;
elStyle = el.getComputedStyle && theEl instanceof Element ? el.getComputedStyle(theEl, null) : theEl.style;
// END MODIFICATION
// If there's no computed style at all, we're done, as we know that it's not hidden // 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;
@@ -769,28 +665,6 @@
} }
} }
var ignoredInputTypes = {
hidden: true,
submit: true,
reset: true,
button: true,
image: true,
file: true,
};
/*
* inputEl MUST BE an instanceof HTMLInputElement, else inputEl.type.toLowerCase will throw an error
*/
function isRelevantInputField(inputEl) {
if (inputEl.hasAttribute('data-bwignore')) {
return false;
}
const isIgnoredInputType = ignoredInputTypes.hasOwnProperty(inputEl.type.toLowerCase());
return !isIgnoredInputType;
}
/** /**
* Query `theDoc` for form elements that we can use for autofill, ranked by importance and limited by `limit` * 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 {Document} theDoc The Document to query
@@ -799,19 +673,13 @@
*/ */
function getFormElements(theDoc, limit) { function getFormElements(theDoc, limit) {
// START MODIFICATION // START MODIFICATION
var els = [];
var els = queryDocAll(theDoc, theDoc.body, function (el) { try {
switch (el.nodeName) { var elsList = theDoc.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="reset"])' +
case 'SELECT': ':not([type="button"]):not([type="image"]):not([type="file"]):not([data-bwignore]), select, ' +
return true; 'span[data-bwautofill]');
case 'SPAN': els = Array.prototype.slice.call(elsList);
return el.hasAttribute('data-bwautofill'); } catch (e) { }
case 'INPUT':
return isRelevantInputField(el);
default:
return false;
}
});
if (!limit || els.length <= limit) { if (!limit || els.length <= limit) {
return els; return els;
@@ -841,8 +709,8 @@
} }
return returnEls; return returnEls;
// END MODIFICATION
} }
// END MODIFICATION
/** /**
* Focus the element `el` and optionally restore its original value * Focus the element `el` and optionally restore its original value
@@ -871,12 +739,6 @@
var markTheFilling = true, var markTheFilling = true,
animateTheFilling = true; animateTheFilling = true;
function queryPasswordInputs() {
return queryDocAll(document, document.body, function (el) {
return el.nodeName === 'INPUT' && el.type.toLowerCase() === 'password';
})
}
// Check if URL is not secure when the original saved one was // Check if URL is not secure when the original saved one was
function urlNotSecure(savedURLs) { function urlNotSecure(savedURLs) {
var passwordInputs = null; var passwordInputs = null;
@@ -884,7 +746,7 @@
return false; return false;
} }
return savedURLs.some(url => url?.indexOf('https://') === 0) && 'http:' === document.location.protocol && (passwordInputs = queryPasswordInputs(), return savedURLs.some(url => url?.indexOf('https://') === 0) && '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 < 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; 0 == confirmResult)) ? true : false;
} }
@@ -1236,12 +1098,9 @@
*/ */
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 queryDocAll(document, document.body, function (el) { return Array.prototype.slice.call(selectAllFromDoc("input[type='text']")).filter(function (el) {
return el.nodeName === 'INPUT' && return el.value && r.test(el.value);
el.type.toLowerCase() === 'text' && }, this);
el.value &&
r.test(el.value);
});
} }
/** /**
@@ -1266,9 +1125,7 @@
a: { a: {
currentEl = el; currentEl = el;
for (var owner = el.ownerDocument, owner = owner ? owner.defaultView : {}, theStyle; currentEl && currentEl !== document;) { for (var owner = el.ownerDocument, owner = owner ? owner.defaultView : {}, theStyle; currentEl && currentEl !== document;) {
// START MODIFICATION theStyle = owner.getComputedStyle ? owner.getComputedStyle(currentEl, null) : currentEl.style;
theStyle = owner.getComputedStyle && currentEl instanceof Element ? owner.getComputedStyle(currentEl, null) : currentEl.style;
// END MODIFICATION
if (!theStyle) { if (!theStyle) {
currentEl = true; currentEl = true;
break a; break a;
@@ -1302,19 +1159,12 @@
} }
try { try {
// START MODIFICATION // START MODIFICATION
var filteredElements = queryDocAll(document, document.body, function (el) { var elements = Array.prototype.slice.call(selectAllFromDoc('input, select, button, ' +
switch (el.nodeName) { 'span[data-bwautofill]'));
case 'INPUT':
case 'SELECT':
case 'BUTTON':
return el.opid === theOpId;
case 'SPAN':
return el.hasAttribute('data-bwautofill') && el.opid === theOpId;
}
return false;
});
// END MODIFICATION // END MODIFICATION
var filteredElements = elements.filter(function (o) {
return o.opid == theOpId;
});
if (0 < filteredElements.length) { if (0 < filteredElements.length) {
theElement = filteredElements[0], theElement = filteredElements[0],
1 < filteredElements.length && console.warn('More than one element found with opid ' + theOpId); 1 < filteredElements.length && console.warn('More than one element found with opid ' + theOpId);
@@ -1335,11 +1185,11 @@
* @returns * @returns
*/ */
function selectAllFromDoc(theSelector) { function selectAllFromDoc(theSelector) {
// START MODIFICATION var d = document, elements = [];
return queryDocAll(document, document, function(node) { try {
return node.matches(theSelector); elements = d.querySelectorAll(theSelector);
}); } catch (e) { }
// END MODIFICATION return elements;
} }
/** /**