Completed
Push — master ( adbed2...24ab4b )
by Sander
01:19
created

findForm.js ➔ fillPassword   C

Complexity

Conditions 11
Paths 28

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
cc 11
c 5
b 1
f 0
nc 28
nop 2
dl 0
loc 24
rs 5.3305

How to fix   Complexity   

Complexity

Complex classes like findForm.js ➔ fillPassword often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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
108
            // If we're not submitting a form (it's a page load), there are no
109
            // password field values for us to use for identifying fields. So,
110
            // just assume the first password field is the one to be filled in.
111
            if (!isSubmission || pwFields.length === 1){
112
                return [usernameField, pwFields[0].element, null];
113
            }
114
115
116
117
            // Try to figure out WTF is in the form based on the password values.
118
            var oldPasswordField, newPasswordField;
119
            var pw1 = pwFields[0].element.value;
120
            var pw2 = pwFields[1].element.value;
121
            var pw3 = (pwFields[2] ? pwFields[2].element.value : null);
122
123
            if (pwFields.length === 3) {
124
                // Look for two identical passwords, that's the new password
125
126
                if (pw1 === pw2 && pw2 === pw3) {
127
                    // All 3 passwords the same? Weird! Treat as if 1 pw field.
128
                    newPasswordField = pwFields[0].element;
129
                    oldPasswordField = null;
130
                } else if (pw1 === pw2) {
131
                    newPasswordField = pwFields[0].element;
132
                    oldPasswordField = pwFields[2].element;
133
                } else if (pw2 === pw3) {
134
                    oldPasswordField = pwFields[0].element;
135
                    newPasswordField = pwFields[2].element;
136
                } else if (pw1 === pw3) {
137
                    // A bit odd, but could make sense with the right page layout.
138
                    newPasswordField = pwFields[0].element;
139
                    oldPasswordField = pwFields[1].element;
140
                } else {
141
                    // We can't tell which of the 3 passwords should be saved.
142
                    this.log("(form ignored -- all 3 pw fields differ)");
143
                    return [null, null, null];
144
                }
145
            } else { // pwFields.length == 2
146
                if (pw1 === pw2) {
147
                    // Treat as if 1 pw field
148
                    newPasswordField = pwFields[0].element;
149
                    oldPasswordField = null;
150
                } else {
151
                    // Just assume that the 2nd password is the new password
152
                    oldPasswordField = pwFields[0].element;
153
                    newPasswordField = pwFields[1].element;
154
                }
155
            }
156
157
            return [usernameField, newPasswordField, oldPasswordField];
158
        },
159
        log: function (str) {
160
            if(settings.debug){
161
                console.log(str);
0 ignored issues
show
Debugging Code introduced by brantje
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
162
            }
163
        }
164
    };
165
}();
166
167
function getLoginFields(isSubmission) {
168
    var forms = document.forms;
169
    var loginForms = [];
170
171
    for (var i = 0; i < forms.length; i++) {
172
        var form = forms[i];
173
        var result = formManager.getFormFields(form, isSubmission);
174
        var usernameField = result[0];
175
        var passwordField = result[1];
176
        // Need a valid password field to do anything.
177
        if (passwordField === null){
178
            continue;
179
        }
180
        loginForms.push([usernameField, passwordField]);
181
    }
182
    return loginForms;
183
}
184
185
function getFormFromElement(elem) {
186
    if(elem) {
0 ignored issues
show
Complexity Best Practice introduced by brantje
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...
187
        while (elem.parentNode) {
188
            if (elem.parentNode.nodeName.toLowerCase() === "form") {
189
                return elem.parentNode;
190
            }
191
            elem = elem.parentNode;
192
        }
0 ignored issues
show
Best Practice introduced by brantje
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...
193
    }
194
}
195
196
function dispatchEvents(element){
197
    var eventNames = [ 'click', 'focus', 'keypress', 'keydown', 'keyup', 'input', 'blur', 'change' ];
198
    eventNames.forEach(function(eventName) {
199
        element.dispatchEvent(new Event(eventName, {"bubbles":true}));
0 ignored issues
show
Bug introduced by brantje
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...
200
    });
201
}
202
203
function fillPassword(user, password) {
204
    var loginFields = getLoginFields();
205
    for (var i = 0; i < loginFields.length; i++) {
206
        if(user && loginFields[i][0]){
207
            loginFields[i][0].value = user;
208
            if(loginFields[i][0].offsetParent) {
209
                dispatchEvents(loginFields[i][0]);
210
            }
211
        }
212
        if(password && loginFields[i][1]) {
213
            loginFields[i][1].value = password;
214
            if(loginFields[i][1].offsetParent) {
215
                dispatchEvents(loginFields[i][1]);
216
            }
217
        }
218
        if(password && loginFields[i][2]) {
219
            loginFields[i][2].value = password;
220
            if(loginFields[i][2].offsetParent) {
221
                dispatchEvents(loginFields[i][2]);
222
            }
223
        }
224
    }
225
226
}
227
formManager._init_();