This commit is contained in:
Chris Karlof 2013-01-07 18:19:25 -08:00
Родитель ed46aaa232
Коммит 90e96ba9d4
5 изменённых файлов: 127 добавлений и 33 удалений

3
TODO
Просмотреть файл

@ -1,5 +1,8 @@
get multistage working
HTTP auth capture/fill HTTP auth capture/fill
capturing and filling lone password fields capturing and filling lone password fields
tdameritrade may need some type like a human for lone password field on transfer page tdameritrade may need some type like a human for lone password field on transfer page
AJAX login: https://www.select-a-spot.com AJAX login: https://www.select-a-spot.com
bayareacurling has annoying change password page bayareacurling has annoying change password page
start capturing login url and title

Просмотреть файл

@ -169,7 +169,7 @@ var CommandHandler = function(Messaging, CapturedCredentialStorage) {
// //
Messaging.addContentMessageListener(function(request, sender, sendResponse) { Messaging.addContentMessageListener(function(request, sender, sendResponse) {
if (request.type && commandHandlers[request.type]) { if (request.type && commandHandlers[request.type]) {
console.log("Msg received", request, sender); //console.log("Msg received", request, sender);
return commandHandlers[request.type].call(commandHandlers,request.message,sender,sendResponse); return commandHandlers[request.type].call(commandHandlers,request.message,sender,sendResponse);
} }
}); });

Просмотреть файл

@ -24,3 +24,5 @@
# fields. The real fields appear after clicking on the specified element. # fields. The real fields appear after clicking on the specified element.
hulu.com: hulu.com:
clickOn: "input.inactive.dummy.user" clickOn: "input.inactive.dummy.user"

Просмотреть файл

@ -1,4 +1,5 @@
var PasswordForm = function($, DomMonitor) { var PasswordForm = function($, DomMonitor) {
const VALID_USERNAME_INPUT_TYPES = ['text','email','url','tel','number'];
function notifyObserver(fn) { function notifyObserver(fn) {
var args; var args;
@ -28,17 +29,122 @@ var PasswordForm = function($, DomMonitor) {
notifyObserver.call(this, "credentialsCaptured"); notifyObserver.call(this, "credentialsCaptured");
} }
var PasswordForm = function(id, usernameField, passwordField, containingEl, siteConfig) { function typeValueInElementHelper($el, value, currentIndex, callback) {
if (currentIndex >= value.length) {
if (callback) callback();
return;
}
var partialValue = value.substring(0, currentIndex+1);
setTimeout(function() {
$el.keydown();
$el.val(value);
setTimeout(function() {
$el.keyup();
setTimeout(function() {
typeValueInElementHelper($el, value, currentIndex+1, callback);
},0);
},0);
},0);
}
function fillField(el, value, callback) {
var $el = $(el);
$el.focus();
var blur = function() {
$el.blur();
setTimeout(callback, 0);
}
value = value || "";
// if typeValueInElementHelper is too slow, then use this code below.
// $el.val(value);
// blur();
setTimeout(function() {
// After focusing on the given element, the page's javascript may
// change the focus (for example, swap out a fake field for a real one)
// and we should then type the value into the newly focused field
var activeElement = document.activeElement;
if (inputIsPossibleUsernameField(activeElement) || inputIsPasswordField(activeElement)) {
$el = $(activeElement);
}
typeValueInElementHelper($el, value, 0, blur);
}, 0);
}
function inputIsPossibleUsernameField(input) {
return input.tagName.toLowerCase() === "input" &&
VALID_USERNAME_INPUT_TYPES.indexOf(input.type.toLowerCase()) !== -1;
}
function inputIsPasswordField(input) {
return input.tagName.toLowerCase() === "input" && input.type.toLowerCase() === "password";
}
function maybeGetConfiguredUsernameField() {
var $username = $(this.config.un);
if ($username.length > 0 && this.$containingForm.has($username)) {
return $username.get(0);
} else {
return null;
}
}
function findBestUsernameFieldCandidate() {
var inputsList = this.$containingEl.find('input').get();
var pwFieldIdx = inputsList.indexOf(this.passwordField.el);
for (var inputIdx = pwFieldIdx-1; inputIdx >= 0; inputIdx--) {
if (inputIsPossibleUsernameField(inputsList[inputIdx])) {
return inputsList[inputIdx];
}
}
// Couldn't find a valid username input field.
return null;
}
function findUsernameField() {
// If the username field is explicitly configured, then get it
var usernameEl = maybeGetConfiguredUsernameField.call(this);
// If we didn't find a configured username field for the form, then
// look for one
if (!usernameEl) {
usernameEl = findBestUsernameFieldCandidate.call(this);
}
return { el: usernameEl, $el: $(usernameEl) };
}
function handlePossibleUsernameFieldChange() {
var activeElement = document.activeElement;
// If the user focuses on a username element and the active element changes, then
// we should update what our notion of the username field is.
// See https://online.citibank.com for where this is needed.
if (activeElement !== this.usernameField.el &&
inputIsPossibleUsernameField(activeElement)) {
this.usernameField.$el.off(this.focusEvents);
this.usernameField = { el: activeElement, $el: $(activeElement) };
}
}
var PasswordForm = function(id, passwordEl, $containingEl, siteConfig) {
this.id = id; this.id = id;
this.usernameField = usernameField; this.passwordField = { el: passwordEl };
this.passwordField = passwordField; this.$containingEl = $containingEl;
this.containingEl = containingEl;
this.$containingEl = $(this.containingEl);
this.config = siteConfig || {}; this.config = siteConfig || {};
// "input" event will capture paste input and key by key input on modern browsers // "input" event will capture paste input and key by key input on modern browsers
// Note: this will not trigger when values are filled by javsacript or the browser // Note: this will not trigger when values are filled by javsacript or the browser
this.inputEvents = "input."+this.id; this.inputEvents = "input."+this.id;
this.submitEvents = "submit."+this.id; this.submitEvents = "submit."+this.id;
this.focusEvents = "focus.username"+this.id;
// Setting 'autocomplete' to 'off' will signal to the native
// password manager to ignore this login wrt filling and capturing.
// This solves the "double infobar" problem when linking.
this.passwordField.el.setAttribute('autocomplete', 'off');
// This must be called after passwordField and $containingEl are set
this.usernameField = findUsernameField.call(this);
this.usernameField.$el.on(this.focusEvents, handlePossibleUsernameFieldChange.bind(this));
// This is an external observer interested in events on the PasswordForm, // This is an external observer interested in events on the PasswordForm,
// most likely the PasswordFormInspector. // most likely the PasswordFormInspector.
this.observer = null; this.observer = null;
@ -58,6 +164,7 @@ var PasswordForm = function($, DomMonitor) {
PasswordForm.prototype.unobserve = function() { PasswordForm.prototype.unobserve = function() {
this.$containingEl.off(this.inputEvents); this.$containingEl.off(this.inputEvents);
this.$containingEl.off(this.submitEvents); this.$containingEl.off(this.submitEvents);
this.usernameField.$el.off(this.focusEvents);
this.observer = null; this.observer = null;
return this; return this;
}; };
@ -67,8 +174,9 @@ var PasswordForm = function($, DomMonitor) {
if (clickOn) { if (clickOn) {
$(clickOn).click(); $(clickOn).click();
} }
this.usernameField.el.value = credentials.username; fillField(this.usernameField.el, credentials.username, (function() {
this.passwordField.el.value = credentials.password; fillField(this.passwordField.el, credentials.password);
}).bind(this));
return this; return this;
}; };
@ -92,6 +200,7 @@ var PasswordForm = function($, DomMonitor) {
} }
PasswordForm.prototype.highlight = function() { PasswordForm.prototype.highlight = function() {
var containingEl = this.$containingEl.get(0);
if (this.usernameField.el) { if (this.usernameField.el) {
highlightEl(this.usernameField.el, "blue"); highlightEl(this.usernameField.el, "blue");
} else { } else {
@ -102,8 +211,8 @@ var PasswordForm = function($, DomMonitor) {
} else { } else {
console.log("No password field found for", this); console.log("No password field found for", this);
} }
if (this.containingEl) { if (containingEl) {
highlightEl(this.containingEl, "red"); highlightEl(containingEl, "red");
} else { } else {
console.log("No containing form field found for", this); console.log("No containing form field found for", this);
}; };

Просмотреть файл

@ -1,6 +1,4 @@
var PasswordFormInspector = function($, PasswordForm, DomMonitor) { var PasswordFormInspector = function($, PasswordForm, DomMonitor) {
const VALID_USERNAME_INPUT_TYPES = ['text','email','url','tel','number'];
var running = false; var running = false;
var observers = []; var observers = [];
@ -16,18 +14,6 @@ var PasswordFormInspector = function($, PasswordForm, DomMonitor) {
return idCounter; return idCounter;
} }
function getUsernameFieldForPasswordField(containerForm,passwordEl) {
var inputsList = containerForm.find('input').get();
var pwFieldIdx = inputsList.indexOf(passwordEl);
for (var inputIdx = pwFieldIdx-1; inputIdx >= 0; inputIdx--) {
if (VALID_USERNAME_INPUT_TYPES.indexOf(inputsList[inputIdx].type.toLowerCase()) != -1) {
return inputsList[inputIdx];
}
}
// Couldn't find a valid username input field.
return null;
}
function findForms() { function findForms() {
var $passwordInputs = $('input[type=password]'); var $passwordInputs = $('input[type=password]');
var oldPasswordForms = passwordForms; // TODO: clean these up var oldPasswordForms = passwordForms; // TODO: clean these up
@ -39,7 +25,7 @@ var PasswordFormInspector = function($, PasswordForm, DomMonitor) {
usernameFields = {}, usernameFields = {},
numPasswordInputs; numPasswordInputs;
if ($containingForm.length === 0) { if ($containingForm.length === 0) {
console.log("Could not find form element, passwordEl=", $passwordEl); console.log("Could not find form element, passwordEl=", passwordEl);
// Could not find an HTML form, so now just look for any element that // Could not find an HTML form, so now just look for any element that
// contains both the password field and some other input field with type=text. // contains both the password field and some other input field with type=text.
// Note: this will also find inputs with no type specified, which defaults to text. // Note: this will also find inputs with no type specified, which defaults to text.
@ -49,19 +35,13 @@ var PasswordFormInspector = function($, PasswordForm, DomMonitor) {
if ($containingForm.length === 0) { if ($containingForm.length === 0) {
return; return;
} }
// Setting 'autocomplete' to 'off' will signal to the native
// password manager to ignore this login wrt filling and capturing.
// This solves the "double infobar" problem when linking.
passwordEl.setAttribute('autocomplete', 'off');
numPasswordInputs = $containingForm.find('input[type=password]').length; numPasswordInputs = $containingForm.find('input[type=password]').length;
// If the containing form contains multiple password field, then ignore // If the containing form contains multiple password field, then ignore
// for now. This is probably a change password field. // for now. This is probably a change password field.
if (numPasswordInputs > 1) return; if (numPasswordInputs > 1) return;
usernameEl = getUsernameFieldForPasswordField($containingForm,passwordEl);
passwordForms.push(new PasswordForm(generateId(), passwordForms.push(new PasswordForm(generateId(),
{ el: usernameEl }, passwordEl,
{ el: passwordEl }, $containingForm,
$containingForm.get(0),
siteConfig)); siteConfig));
}); });
observeForms(); observeForms();