Port Maxim Baz's form-fill logic (#5)

* Port Maxim Baz's injected form-fill logic
* Add hotkey to manifest
This commit is contained in:
Erayd
2018-04-20 07:33:16 +12:00
committed by GitHub
parent 9ca48cea41
commit 9c6b7237f5
2 changed files with 287 additions and 4 deletions

View File

@@ -1,14 +1,290 @@
(function() {
const FORM_MARKERS = ["login", "log-in", "log_in", "signin", "sign-in", "sign_in"];
const USERNAME_FIELDS = {
selectors: [
"input[id*=openid i]",
"input[name*=openid i]",
"input[class*=openid i]",
"input[id*=user i]",
"input[name*=user i]",
"input[class*=user i]",
"input[id*=login i]",
"input[name*=login i]",
"input[class*=login i]",
"input[id*=email i]",
"input[name*=email i]",
"input[class*=email i]",
"input[type=email i]",
"input[type=text i]",
"input[type=tel i]"
],
types: ["email", "text", "tel"]
};
const PASSWORD_FIELDS = {
selectors: ["input[type=password i]"]
};
const INPUT_FIELDS = {
selectors: PASSWORD_FIELDS.selectors.concat(USERNAME_FIELDS.selectors)
};
const SUBMIT_FIELDS = {
selectors: [
"[type=submit i]",
"button[name*=login i]",
"button[name*=log-in i]",
"button[name*=log_in i]",
"button[name*=signin i]",
"button[name*=sign-in i]",
"button[name*=sign_in i]",
"button[id*=login i]",
"button[id*=log-in i]",
"button[id*=log_in i]",
"button[id*=signin i]",
"button[id*=sign-in i]",
"button[id*=sign_in i]",
"button[class*=login i]",
"button[class*=log-in i]",
"button[class*=log_in i]",
"button[class*=signin i]",
"button[class*=sign-in i]",
"button[class*=sign_in i]",
"input[type=button i][name*=login i]",
"input[type=button i][name*=log-in i]",
"input[type=button i][name*=log_in i]",
"input[type=button i][name*=signin i]",
"input[type=button i][name*=sign-in i]",
"input[type=button i][name*=sign_in i]",
"input[type=button i][id*=login i]",
"input[type=button i][id*=log-in i]",
"input[type=button i][id*=log_in i]",
"input[type=button i][id*=signin i]",
"input[type=button i][id*=sign-in i]",
"input[type=button i][id*=sign_in i]",
"input[type=button i][class*=login i]",
"input[type=button i][class*=log-in i]",
"input[type=button i][class*=log_in i]",
"input[type=button i][class*=signin i]",
"input[type=button i][class*=sign-in i]",
"input[type=button i][class*=sign_in i]"
]
};
/**
* Fill password
*
* @since 3.0.0
*
* @param object login Login fields
* @param object login Login fields
* @param bool autoSubmit Whether to autosubmit the login form
* @return void
*/
function fillLogin(login) {
alert("Fill login: " + JSON.stringify(login));
function fillLogin(login, autoSubmit = false) {
var loginForm = form();
update(USERNAME_FIELDS, login.login, loginForm);
update(PASSWORD_FIELDS, login.secret, loginForm);
var password_inputs = queryAllVisible(document, PASSWORD_FIELDS, loginForm);
if (password_inputs.length > 1) {
// There is likely a field asking for OTP code, so do not submit form just yet
password_inputs[1].select();
} else {
window.requestAnimationFrame(function() {
// Try to submit the form, or focus on the submit button (based on user settings)
var submit = find(SUBMIT_FIELDS, loginForm);
if (submit) {
if (autoSubmit) {
submit.click();
} else {
submit.focus();
}
} else {
// There is no submit button. Try to submit the form itself.
if (autoSubmit && loginForm) {
loginForm.submit();
}
// We need to keep focus somewhere within the form, so that Enter hopefully submits the form.
var password = find(PASSWORD_FIELDS, loginForm);
if (password) {
password.focus();
} else {
var username = find(USERNAME_FIELDS, loginForm);
if (username) {
username.focus();
}
}
}
});
}
}
/**
* Query all visible elements
*
* @since 3.0.0
*
* @param DOMElement parent Parent element to query
* @param object field Field to search for
* @param DOMElement form Search only within this form
* @return array List of search results
*/
function queryAllVisible(parent, field, form) {
var result = [];
for (var i = 0; i < field.selectors.length; i++) {
var elems = parent.querySelectorAll(field.selectors[i]);
for (var j = 0; j < elems.length; j++) {
var elem = elems[j];
// Select only elements from specified form
if (form && form != elem.form) {
continue;
}
// Ignore disabled fields
if (elem.disabled) {
continue;
}
// Elem or its parent has a style 'display: none',
// or it is just too narrow to be a real field (a trap for spammers?).
if (elem.offsetWidth < 30 || elem.offsetHeight < 10) {
continue;
}
// We may have a whitelist of acceptable field types. If so, skip elements of a different type.
if (field.types && field.types.indexOf(elem.type.toLowerCase()) < 0) {
continue;
}
// Elem takes space on the screen, but it or its parent is hidden with a visibility style.
var style = window.getComputedStyle(elem);
if (style.visibility == "hidden") {
continue;
}
// Elem is outside of the boundaries of the visible viewport.
var rect = elem.getBoundingClientRect();
if (
rect.x + rect.width < 0 ||
rect.y + rect.height < 0 ||
(rect.x > window.innerWidth || rect.y > window.innerHeight)
) {
continue;
}
// This element is visible, will use it.
result.push(elem);
}
}
return result;
}
/**
* Query first visible element
*
* @since 3.0.0
*
* @param DOMElement parent Parent element to query
* @param object field Field to search for
* @param DOMElement form Search only within this form
* @return array First search result
*/
function queryFirstVisible(parent, field, form) {
var elems = queryAllVisible(parent, field, form);
return elems.length > 0 ? elems[0] : undefined;
}
/**
* Detect the login form
*
* @since 3.0.0
*
* @return The login form
*/
function form() {
var elems = queryAllVisible(document, INPUT_FIELDS, undefined);
var forms = [];
for (var i = 0; i < elems.length; i++) {
var form = elems[i].form;
if (form && forms.indexOf(form) < 0) {
forms.push(form);
}
}
if (forms.length == 0) {
return undefined;
}
if (forms.length == 1) {
return forms[0];
}
// If there are multiple forms, try to detect which one is a login form
var formProps = [];
for (var i = 0; i < forms.length; i++) {
var form = forms[i];
var props = [form.id, form.name, form.className];
formProps.push(props);
for (var j = 0; j < FORM_MARKERS.length; j++) {
var marker = FORM_MARKERS[j];
for (var k = 0; k < props.length; k++) {
var prop = props[k];
if (prop.toLowerCase().indexOf(marker) > -1) {
return form;
}
}
}
}
console.error(
"Unable to detect which of the multiple available forms is the login form. Please submit an issue for browserpass on github, and provide the following list in the details: " +
JSON.stringify(formProps)
);
return forms[0];
}
/**
* Find a form field
*
* @since 3.0.0
*
* @param object field Field to search for
* @param DOMElement form Form to search in
* @return DOMElement First matching form field
*/
function find(field, form) {
return queryFirstVisible(document, field, form);
}
/**
* Update a form field value
*
* @since 3.0.0
*
* @param object field Field to update
* @param string value Value to set
* @param DOMElement form Form for which to set the given field
* @return bool Whether the update succeeded
*/
function update(field, value, form) {
if (!value.length) {
return false;
}
// Focus the input element first
var el = find(field, form);
if (!el) {
return false;
}
var eventNames = ["click", "focus"];
eventNames.forEach(function(eventName) {
el.dispatchEvent(new Event(eventName, { bubbles: true }));
});
// Focus may have triggered unvealing a true input, find it again
el = find(field, form);
if (!el) {
return false;
}
// Now set the value and unfocus
el.setAttribute("value", value);
el.value = value;
var eventNames = ["keypress", "keydown", "keyup", "input", "blur", "change"];
eventNames.forEach(function(eventName) {
el.dispatchEvent(new Event(eventName, { bubbles: true }));
});
return true;
}
// set window object

View File

@@ -22,5 +22,12 @@
"permissions": [
"activeTab",
"nativeMessaging"
]
],
"commands": {
"_execute_browser_action": {
"suggested_key": {
"default": "Ctrl+Shift+L"
}
}
}
}