monitoring password form removal

This commit is contained in:
Chris Karlof 2013-01-03 18:41:54 -08:00
Родитель 8e801daff3
Коммит 5df0ec63cf
6 изменённых файлов: 183 добавлений и 70 удалений

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

@ -0,0 +1,138 @@
var DomMonitor = function($, MutationObserver) {
var mutationObserver = null;
var subscribers = {
addedNodes: [],
isRemoved: []
};
function visitSubscribersForNodes(subscriberList, nodes) {
var $node;
if (subscriberList.length === 0) return;
nodes.forEach(function(node) {
$node = $(node);
subscriberList.forEach(function(subscriber) {
if (subscriber.arg) {
if ($node.has(subscriber.arg).length > 0) {
subscriber.notify = true;
}
} else {
subscriber.notify = true;
}
});
});
}
function notifySubscribers() {
var notifyList = [];
var types = Object.getOwnPropertyNames(subscribers);
types.forEach(function(type) {
subscribers[type].forEach(function(subscriber) {
if (subscriber.notify) {
notifyList.push(subscriber);
}
});
});
notifyList.forEach(function(subscriber) {
delete subscriber.notify;
subscriber.callback(self);
});
}
function mutationObserverCallback(mutations) {
mutations.forEach(function(mutation) {
var removedNodes = Array.prototype.slice.call(mutation.removedNodes || []),
addedNodes = Array.prototype.slice.call(mutation.addedNodes || []);
if (mutation.type === "childList") {
visitSubscribersForNodes(subscribers.isRemoved, removedNodes);
visitSubscribersForNodes(subscribers.addedNodes, addedNodes);
}
}, this);
notifySubscribers();
}
function addSubscriber() {
var args = Array.prototype.slice.call(arguments),
type = args.shift(),
id = args.shift(),
obj = {};
if (subscribers[type]) {
obj.id = id;
if (args.length == 2) {
obj.arg = args[0];
obj.callback = args[1];
} else if (args.length == 1) {
obj.callback = args[0];
}
subscribers[type].push(obj);
} else {
console.log("DomMonitor: unknown type given to on:", type);
}
}
// addedNodes([cssFilter,] callback)
// cssFilter: optional css expression to filter addedNodes
// callback: function
// isRemoved(element, callback)
// element: DOM element
// callback: function
function on() {
var args = Array.prototype.slice.call(arguments),
type = args.shift(),
a = type.split("."),
id = null;
if (a.length > 1) {
type = a.shift();
id = a.join(".");
}
args.unshift(id);
args.unshift(type);
addSubscriber.apply(null, args);
}
function off() {
var args = Array.prototype.slice.call(arguments),
type = args.shift(),
a = type.split("."),
callback = args.shift(),
id = null,
subs = null,
subsLength;
if (a.length > 1) {
type = a.shift();
id = a.join(".");
}
subs = subscribers[type];
if (!subs) {
console.log("DomMonitor.off illegal type", type);
return;
}
length = subs.length;
for (var i=0; i<length; i++) {
if (id && subs[i].id === id ||
callback && subs[i].callback === callback) {
console.log("DomMonitor.off successfully removed subscriber", subs[i]);
subs.splice(i,1);
return;
}
}
}
function start() {
this.mutationObserver = new MutationObserver(mutationObserverCallback);
this.mutationObserver.observe(document, { childList: true, subtree: true });
}
function stop() {
this.mutationObserver.disconnect();
}
var self = {
start: start,
stop: stop,
on: on,
off: off
};
return self;
};

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

@ -1,48 +0,0 @@
// ConditionMonitor creates objects that monitor the DOM for added input objects,
// and calls a callback function when new inputs are added to the DOM.
// Public functions:
// start: starts the monitor
// stop: stops the monitor
var InputMonitor = function(MutationObserver) {
function mutationContainsAddedInput(mutation) {
var addedNodes = Array.prototype.slice.call(mutation.addedNodes),
addedNodesLength = addedNodes.length,
i;
if (addedNodesLength === 0) {
return false;
}
for (i=0; i<addedNodesLength; i++) {
if (addedNodes[i].querySelectorAll &&
addedNodes[i].querySelectorAll("input").length > 0) {
return true;
}
}
return false;
}
var InputMonitor = function(callbackFunc) {
this.callback = callbackFunc || function() {};
// create an observer instance
this.observer = new MutationObserver((function(mutations) {
var mutationsLength = mutations.length, i;
for (i=0; i<mutationsLength; i++) {
if (mutationContainsAddedInput(mutations[i])) {
this.callback();
return;
}
}
}).bind(this));
};
InputMonitor.prototype.start = function() {
this.observer.observe(document, { childList: true, subtree: true })
};
InputMonitor.prototype.stop = function() {
this.observer.disconnect();
};
return InputMonitor;
};

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

@ -1,10 +1,12 @@
var Gombot = {};
Gombot.Messaging = ContentMessaging();
Gombot.PasswordForm = PasswordForm(jQuery);
Gombot.InputMonitor = InputMonitor(window.MutationObserver || window.WebKitMutationObserver);
Gombot.MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
Gombot.DomMonitor = DomMonitor(jQuery, Gombot.MutationObserver);
Gombot.PasswordForm = PasswordForm(jQuery, Gombot.DomMonitor);
//Gombot.InputMonitor = InputMonitor(Gombot.MutationObserver); not used, replaced by DomMonitor
Gombot.Linker = {};
Gombot.PasswordFormInspector = PasswordFormInspector(jQuery, Gombot.PasswordForm, Gombot.InputMonitor);
Gombot.PasswordFormInspector = PasswordFormInspector(jQuery, Gombot.PasswordForm, Gombot.DomMonitor);
function maybeGetAndFillCredentials(formInspector)
{
@ -62,7 +64,7 @@ var formInspectorObserver = {
function start() {
// Run on page load
maybePromptToSaveCapturedCredentials();
Gombot.PasswordFormInspector.start();
Gombot.DomMonitor.start();
Gombot.PasswordFormInspector.observe(formInspectorObserver);
}

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

@ -1,4 +1,4 @@
var PasswordForm = function($) {
var PasswordForm = function($, DomMonitor) {
function notifyObserver(fn) {
var args;
@ -9,6 +9,14 @@ var PasswordForm = function($) {
}
}
function passwordFieldRemovedCallback(domMonitor) {
DomMonitor.off("isRemoved.pwdEl"+this.id);
// Notify observers they should link if they have captured creds
// TODO: maybe should qualify this a bit, e.g., only notify if user
// actually entered credentials into this form
notifyObserver.call(this, "link");
}
var PasswordForm = function(id, usernameField, passwordField, containingEl) {
this.id = id;
this.usernameField = usernameField;
@ -19,7 +27,10 @@ var PasswordForm = function($) {
// Note: this will not trigger when values are filled by javsacript or the browser
this.inputEvents = "input."+this.id;
this.submitEvents = "submit."+this.id;
// This is an external observer interested in events on the PasswordForm,
// most likely the PasswordFormInspector.
this.observer = null;
DomMonitor.on("isRemoved.pwdEl"+this.id, this.passwordField.el, passwordFieldRemovedCallback.bind(this))
};
// TODO: consider whether to make this a multiple event thingy
@ -27,12 +38,11 @@ var PasswordForm = function($) {
// becomes visible, invisible, or removed.
PasswordForm.prototype.observe = function(observer) {
this.observer = observer;
var boundNotifyObserver = notifyObserver.bind(this);
var capturedCredentialsNotify = function(event) {
boundNotifyObserver("credentialsCaptured");
notifyObserver.call(this, "credentialsCaptured");
};
this.$containingEl.on(this.inputEvents, "input", capturedCredentialsNotify);
this.$containingEl.on(this.submitEvents, capturedCredentialsNotify);
this.$containingEl.on(this.inputEvents, "input", capturedCredentialsNotify.bind(this));
this.$containingEl.on(this.submitEvents, capturedCredentialsNotify.bind(this));
return this;
};

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

@ -1,9 +1,9 @@
var PasswordFormInspector = function($, PasswordForm, InputMonitor) {
var PasswordFormInspector = function($, PasswordForm, DomMonitor) {
const VALID_USERNAME_INPUT_TYPES = ['text','email','url','tel','number'];
var observers = [];
var running = false;
var inputMonitor = null;
var observers = [];
var idCounter = 0;
@ -72,7 +72,8 @@ var PasswordFormInspector = function($, PasswordForm, InputMonitor) {
}
var passwordFormObserver = {
credentialsCaptured: credentialsCaptured
credentialsCaptured: credentialsCaptured,
link: function() { visitObservers("link"); }
};
// internal function to start observing the form collection
@ -93,9 +94,17 @@ var PasswordFormInspector = function($, PasswordForm, InputMonitor) {
});
}
function domMonitorCallback() {
findForms();
visitObservers("formsFound", passwordForms);
}
// PUBLIC SECTION
function observe(observer) {
if (!running) {
start();
}
observers.push(observer);
// if we have password forms, then notify observer immediately
if (passwordForms.length > 0 && observer.formsFound) {
@ -117,16 +126,18 @@ var PasswordFormInspector = function($, PasswordForm, InputMonitor) {
}
function start() {
inputMonitor = new InputMonitor(function () {
if (!running) {
running = true;
findForms();
visitObservers("formsFound", passwordForms);
});
findForms();
inputMonitor.start();
DomMonitor.on("addedNodes", "input", domMonitorCallback);
}
}
function cleanup() {
function stop() {
if (running) {
DomMonitor.off("addedNodes", domMonitorCallback);
running = false;
}
}
var self = {
@ -134,7 +145,7 @@ var PasswordFormInspector = function($, PasswordForm, InputMonitor) {
observe: observe,
fillForms: fill,
highlightForms: highlight,
cleanup: cleanup
stop: stop
};
return self;
};

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

@ -24,7 +24,7 @@
"js": ["lib/jquery.js",
"lib/underscore.js",
"content_scripts/content_messaging.js",
"content_scripts/input_monitor.js",
"content_scripts/dom_monitor.js",
"content_scripts/password_form_inspector.js",
"content_scripts/password_form.js",
"content_scripts/main.js"]