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

js/lib/findForm.js (4 issues)

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) {
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...
215
        while (elem.parentNode) {
216
            if (elem.parentNode.nodeName.toLowerCase() === "form") {
217
                return elem.parentNode;
218
            }
219
            elem = elem.parentNode;
220
        }
0 ignored issues
show
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...
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}));
0 ignored issues
show
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...
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_();