monitoring password form removal
This commit is contained in:
Родитель
8e801daff3
Коммит
5df0ec63cf
|
@ -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"]
|
||||
|
|
Загрузка…
Ссылка в новой задаче