Completed
Push — master ( 892ae5...58fe46 )
by Sander
17s
created

findForm.js ➔ getLoginFields   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

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