Completed
Push — master ( e4d488...d7a2df )
by Sander
30s
created

js/lib/findForm.js (1 issue)

1
var formManager = function(){
2
    /**
3
     Code based on:
4
     @url https://dxr.mozilla.org/firefox/source/toolkit/components/passwordmgr/src/nsLoginManager.js#655
5
     */
6
    var settings = {};
7
8
    return {
9
        _init_: function () {
10
            API.runtime.sendMessage(API.runtime.id, {method: 'getSetting', args: 'debug'}).then(function (result) {
11
                settings.debug = (result);
12
            });
13
        },
14
        /**
15
         *
16
         * _isAutoCompleteDisabled
17
         *
18
         * Returns true if the page requests autocomplete be disabled for the
19
         * specified form input.
20
         */
21
        isAutocompleteDisabled: function (element) {
22
            return !!(element && element.hasAttribute("autocomplete") && element.getAttribute("autocomplete").toLowerCase() === "off");
23
        },
24
        /**
25
         * Check if if an element is visible
26
         * @param element
27
         * @returns {boolean}
28
         */
29
        isElementVisible: function (element) {
30
            return !!( element.offsetWidth || element.offsetHeight || element.getClientRects().length );
31
        },
32
        /**
33
         * _getPasswordFields
34
         *
35
         * Returns an array of password field elements for the specified form.
36
         * If no pw fields are found, or if more than 3 are found, then null
37
         * is returned.
38
         *
39
         * skipEmptyFields can be set to ignore password fields with no value.
40
         */
41
        _getPasswordFields: function (form, skipEmptyFields) {
42
            // Locate the password fields in the form.
43
            var pwFields = [];
44
            for (var i = 0; i < form.elements.length; i++) {
45
                var elem = form.elements[i];
46
                if (elem.type !== "password"){
47
                    continue;
48
                }
49
50
                if(!this.isElementVisible(elem)){
51
                    continue;
52
                }
53
54
                if (skipEmptyFields && !elem.value){
55
                    continue;
56
                }
57
58
                pwFields[pwFields.length] = {
59
                    index: i,
60
                    element: elem
61
                };
62
            }
63
64
            // If too few or too many fields, bail out.
65
            if (pwFields.length === 0) {
66
                this.log('(form ignored ('+ form.action +') -- no password fields.)');
67
                return null;
68
            } else if (pwFields.length > 3) {
69
                this.log('(form ignored -- too many password fields. [got ' +
70
                    pwFields.length + "])");
71
                return null;
72
            }
73
74
            return pwFields;
75
        },
76
        /*
77
         * _getFormFields
78
         *
79
         * Returns the username and password fields found in the form.
80
         * Can handle complex forms by trying to figure out what the
81
         * relevant fields are.
82
         *
83
         * Returns: [usernameField, newPasswordField, oldPasswordField]
84
         *
85
         * usernameField may be null.
86
         * newPasswordField will always be non-null.
87
         * oldPasswordField may be null. If null, newPasswordField is just
88
         * "theLoginField". If not null, the form is apparently a
89
         * change-password field, with oldPasswordField containing the password
90
         * that is being changed.
91
         */
92
        getFormFields: function (form, isSubmission) {
93
            var usernameField = null;
94
95
            // Locate the password field(s) in the form. Up to 3 supported.
96
            // If there's no password field, there's nothing for us to do.
97
            var pwFields = this._getPasswordFields(form, isSubmission);
98
            if (!pwFields){
99
                return [null, null, null];
100
            }
101
102
103
104
            // Locate the username field in the form by searching backwards
105
            // from the first passwordfield, assume the first text field is the
106
            // username. We might not find a username field if the user is
107
            // already logged in to the site.
108
            for (var i = pwFields[0].index - 1; i >= 0; i--) {
109
                if(!this.isElementVisible(form.elements[i])){
110
                    continue;
111
                }
112
                if (form.elements[i].type.toLowerCase() === "text" || form.elements[i].type.toLowerCase() === "email") {
113
                    usernameField = form.elements[i];
114
                    break;
115
                }
116
            }
117
118
            if (!usernameField){
119
                this.log('(form ('+ form.action +') ignored -- no username field found)');
120
            }
121
122
123
            // If we're not submitting a form (it's a page load), there are no
124
            // password field values for us to use for identifying fields. So,
125
            // just assume the first password field is the one to be filled in.
126
            if (!isSubmission || pwFields.length === 1){
127
                var res = [usernameField, pwFields[0].element];
128
                if(pwFields[1]){
129
                    res.push(pwFields[1].element);
130
                } else {
131
                    res.push(null);
132
                }
133
                return res;
134
            }
135
136
137
138
            // Try to figure out WTF is in the form based on the password values.
139
            var oldPasswordField, newPasswordField;
140
            var pw1 = pwFields[0].element.value;
141
            var pw2 = pwFields[1].element.value;
142
            var pw3 = (pwFields[2] ? pwFields[2].element.value : null);
143
144
            if (pwFields.length === 3) {
145
                // Look for two identical passwords, that's the new password
146
147
                if (pw1 === pw2 && pw2 === pw3) {
148
                    // All 3 passwords the same? Weird! Treat as if 1 pw field.
149
                    newPasswordField = pwFields[0].element;
150
                    oldPasswordField = null;
151
                } else if (pw1 === pw2) {
152
                    newPasswordField = pwFields[0].element;
153
                    oldPasswordField = pwFields[2].element;
154
                } else if (pw2 === pw3) {
155
                    oldPasswordField = pwFields[0].element;
156
                    newPasswordField = pwFields[2].element;
157
                } else if (pw1 === pw3) {
158
                    // A bit odd, but could make sense with the right page layout.
159
                    newPasswordField = pwFields[0].element;
160
                    oldPasswordField = pwFields[1].element;
161
                } else {
162
                    // We can't tell which of the 3 passwords should be saved.
163
                    this.log("(form ignored -- all 3 pw fields differ)");
164
                    return [null, null, null];
165
                }
166
            } else { // pwFields.length == 2
167
                if (pw1 === pw2) {
168
                    // Treat as if 1 pw field
169
                    newPasswordField = pwFields[0].element;
170
                    oldPasswordField = null;
171
                } else {
172
                    // Just assume that the 2nd password is the new password
173
                    oldPasswordField = pwFields[0].element;
174
                    newPasswordField = pwFields[1].element;
175
                }
176
            }
177
178
            return [usernameField, newPasswordField, oldPasswordField];
179
        },
180
        log: function (str) {
181
            if(settings.debug){
182
                console.log(str);
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
183
            }
184
        }
185
    };
186
}();
187
188
function getLoginFields(isSubmission) {
189
    var forms = document.forms;
190
    var loginForms = [];
191
192
    for (var i = 0; i < forms.length; i++) {
193
        var form = forms[i];
194
        var result = formManager.getFormFields(form, isSubmission);
195
        var usernameField = result[0];
196
        var passwordField = result[1];
197
        // Need a valid password field to do anything.
198
        if (passwordField === null){
199
            continue;
200
        }
201
202
        var res = [usernameField, passwordField];
203
        if(result[2]){
204
            res.push(result[2]);
205
        } else {
206
            res.push(null);
207
        }
208
        loginForms.push(res);
209
    }
210
    return loginForms;
211
}
212
213
function getFormFromElement(elem) {
214
    if(elem) {
215
        while (elem.parentNode) {
216
            if (elem.parentNode.nodeName.toLowerCase() === "form") {
217
                return elem.parentNode;
218
            }
219
            elem = elem.parentNode;
220
        }
221
    }
222
}
223
224
function dispatchEvents(element){
225
    var eventNames = [ 'click', 'focus', 'keypress', 'keydown', 'keyup', 'input', 'blur', 'change' ];
226
    eventNames.forEach(function(eventName) {
227
        element.dispatchEvent(new Event(eventName, {"bubbles":true}));
228
    });
229
}
230
231
function fillPassword(user, password) {
232
    var loginFields = getLoginFields();
233
    for (var i = 0; i < loginFields.length; i++) {
234
        if(user && loginFields[i][0]){
235
            loginFields[i][0].value = user;
236
            if(loginFields[i][0].offsetParent) {
237
                dispatchEvents(loginFields[i][0]);
238
            }
239
        }
240
        if(password && loginFields[i][1]) {
241
            loginFields[i][1].value = password;
242
            if(loginFields[i][1].offsetParent) {
243
                dispatchEvents(loginFields[i][1]);
244
            }
245
        }
246
        if(password && loginFields[i][2]) {
247
            loginFields[i][2].value = password;
248
            if(loginFields[i][2].offsetParent) {
249
                dispatchEvents(loginFields[i][2]);
250
            }
251
        }
252
    }
253
254
}
255
formManager._init_();