Completed
Pull Request — master (#243)
by Sander
39s
created

findForm.js ➔ ... ➔ document.forEach   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
c 1
b 0
f 1
nc 1
nop 1
dl 0
loc 4
rs 10
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
        _getUsernameFields: function (form) {
77
            var usernameFields = [];
78
            for (var i = 0; i < form.elements.length; i++) {
79
                var elem = form.elements[i];
80
                if(!this.isElementVisible(elem)){
81
                    continue;
82
                }
83
                if (elem.type.toLowerCase() !== "text" && elem.type.toLowerCase() !== "email") {
84
                    continue;
85
                }
86
87
                usernameFields[usernameFields.length] = {
88
                    index: i,
89
                    element: elem
90
                };
91
            }
92
            return usernameFields.pop();
93
        },
94
        /*
95
         * _getFormFields
96
         *
97
         * Returns the username and password fields found in the form.
98
         * Can handle complex forms by trying to figure out what the
99
         * relevant fields are.
100
         *
101
         * Returns: [usernameField, newPasswordField, oldPasswordField]
102
         *
103
         * usernameField may be null.
104
         * newPasswordField will always be non-null.
105
         * oldPasswordField may be null. If null, newPasswordField is just
106
         * "theLoginField". If not null, the form is apparently a
107
         * change-password field, with oldPasswordField containing the password
108
         * that is being changed.
109
         */
110
        getFormFields: function (form, isSubmission) {
111
            var usernameField = null;
112
113
            // Locate the password field(s) in the form. Up to 3 supported.
114
            // If there's no password field, there's nothing for us to do.
115
            var pwFields = this._getPasswordFields(form, isSubmission);
116
            if (!pwFields) {
117
                usernameField = this._getUsernameFields(form);
118
                if(usernameField && usernameField.hasOwnProperty('element')) {
119
                    return [usernameField.element, null, null];
120
                } else {
121
                    return [null, null, null];
122
                }
123
            }
124
125
126
            // Locate the username field in the form by searching backwards
127
            // from the first passwordfield, assume the first text field is the
128
            // username. We might not find a username field if the user is
129
            // already logged in to the site.
130
            for (var i = pwFields[0].index - 1; i >= 0; i--) {
131
                if (!this.isElementVisible(form.elements[i])) {
132
                    continue;
133
                }
134
                if (form.elements[i].type.toLowerCase() === "text" || form.elements[i].type.toLowerCase() === "email") {
135
                    usernameField = form.elements[i];
136
                    break;
137
                }
138
            }
139
140
            if (!usernameField) {
141
                this.log('(form (' + form.action + ') ignored -- no username field found)');
142
            }
143
144
145
            // If we're not submitting a form (it's a page load), there are no
146
            // password field values for us to use for identifying fields. So,
147
            // just assume the first password field is the one to be filled in.
148
            if (!isSubmission || pwFields.length === 1) {
149
                var res = [usernameField, pwFields[0].element];
150
                if (pwFields[1]) {
151
                    res.push(pwFields[1].element);
152
                } else {
153
                    res.push(null);
154
                }
155
                return res;
156
            }
157
158
159
            // Try to figure out WTF is in the form based on the password values.
160
            var oldPasswordField, newPasswordField;
161
            var pw1 = pwFields[0].element.value;
162
            var pw2 = pwFields[1].element.value;
163
            var pw3 = (pwFields[2] ? pwFields[2].element.value : null);
164
165
            if (pwFields.length === 3) {
166
                // Look for two identical passwords, that's the new password
167
168
                if (pw1 === pw2 && pw2 === pw3) {
169
                    // All 3 passwords the same? Weird! Treat as if 1 pw field.
170
                    newPasswordField = pwFields[0].element;
171
                    oldPasswordField = null;
172
                } else if (pw1 === pw2) {
173
                    newPasswordField = pwFields[0].element;
174
                    oldPasswordField = pwFields[2].element;
175
                } else if (pw2 === pw3) {
176
                    oldPasswordField = pwFields[0].element;
177
                    newPasswordField = pwFields[2].element;
178
                } else if (pw1 === pw3) {
179
                    // A bit odd, but could make sense with the right page layout.
180
                    newPasswordField = pwFields[0].element;
181
                    oldPasswordField = pwFields[1].element;
182
                } else {
183
                    // We can't tell which of the 3 passwords should be saved.
184
                    this.log("(form ignored -- all 3 pw fields differ)");
185
                    return [null, null, null];
186
                }
187
            } else { // pwFields.length == 2
188
                if (pw1 === pw2) {
189
                    // Treat as if 1 pw field
190
                    newPasswordField = pwFields[0].element;
191
                    oldPasswordField = null;
192
                } else {
193
                    // Just assume that the 2nd password is the new password
194
                    oldPasswordField = pwFields[0].element;
195
                    newPasswordField = pwFields[1].element;
196
                }
197
            }
198
199
            return [usernameField, newPasswordField, oldPasswordField];
200
        },
201
        log: function (str) {
202
            if (settings.debug) {
203
                console.log(str);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
204
            }
205
        }
206
    };
207
}();
208
209
function getLoginFields(isSubmission) {
210
    var forms = document.forms;
211
    var loginForms = [];
212
213
    for (var i = 0; i < forms.length; i++) {
214
        var form = forms[i];
215
        var result = formManager.getFormFields(form, isSubmission);
216
        var usernameField = result[0];
217
        var passwordField = result[1];
218
219
        var res = [usernameField, passwordField];
220
        if (result[2]) {
221
            res.push(result[2]);
222
        } else {
223
            res.push(null);
224
        }
225
        loginForms.push(res);
226
    }
227
    return loginForms;
228
}
229
230
function getFormFromElement(elem) {
231
    if (elem) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if elem is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
232
        while (elem.parentNode) {
233
            if (elem.parentNode.nodeName.toLowerCase() === "form") {
234
                return elem.parentNode;
235
            }
236
            elem = elem.parentNode;
237
        }
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
238
    }
239
}
240
241
function dispatchEvents(element) {
242
    var eventNames = ['click', 'focus', 'keypress', 'keydown', 'keyup', 'input', 'blur', 'change'];
243
    eventNames.forEach(function (eventName) {
244
        element.dispatchEvent(new Event(eventName, {"bubbles": true}));
0 ignored issues
show
Bug introduced by
The variable Event seems to be never declared. If this is a global, consider adding a /** global: Event */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
245
    });
246
}
247
248
function fillPassword(user, password) {
249
    // var loginFields = getLoginFields();
250
    // for (var i = 0; i < loginFields.length; i++) {
251
    //     if(user && loginFields[i][0]){
252
    //         loginFields[i][0].value = user;
253
    //         if(loginFields[i][0].offsetParent) {
254
    //             dispatchEvents(loginFields[i][0]);
255
    //         }
256
    //     }
257
    //     if(password && loginFields[i][1]) {
258
    //         loginFields[i][1].value = password;
259
    //         if(loginFields[i][1].offsetParent) {
260
    //             dispatchEvents(loginFields[i][1]);
261
    //         }
262
    //     }
263
    //     if(password && loginFields[i][2]) {
264
    //         loginFields[i][2].value = password;
265
    //         if(loginFields[i][2].offsetParent) {
266
    //             dispatchEvents(loginFields[i][2]);
267
    //         }
268
    //     }
269
    // }
270
    if (user) {
271
        document.querySelectorAll('input[type=text], input[type=email]').forEach(function (el) {
272
            el.value = user;
273
            dispatchEvents(el);
274
        });
275
    }
276
    if (password) {
277
        document.querySelectorAll('input[type=password]').forEach(function (el) {
278
            el.value = password;
279
            dispatchEvents(el);
280
        });
281
    }
282
}
283
284
formManager._init_();