1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 22:03:36 +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

@@ -44,102 +44,8 @@
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;
} }
/** /**