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

js/background/inject/inject.js (14 issues)

1
/* global API */
2
var $j = jQuery.noConflict();
3
4
$j(document).ready(function () {
5
6
    $j(document).click(function (event) {
7
        var passwordPickerRef = '.passwordPickerIframe';
8
        if (!$j(event.target).closest(passwordPickerRef).length) {
9
            if ($j(passwordPickerRef).is(":visible")) {
10
                removePasswordPicker();
11
            }
12
        }
13
    });
14
15
    var _this = this;
16
    Array.prototype.findUrl = function (match) {
0 ignored issues
show
Compatibility Best Practice introduced by
You are extending the built-in type Array. This may have unintended consequences on other objects using this built-in type. Consider subclassing instead.
Loading history...
17
        return this.filter(function (item) {
18
            var matchParse = processURL(match, false, false, true, false);
19
            return typeof item === 'string' && item.indexOf(matchParse) > -1;
20
        });
21
    };
22
23
    function removePasswordPicker() {
24
        activeForm = undefined;
25
        $j('.passwordPickerIframe').remove();
26
    }
27
28
    _this.removePasswordPicker = removePasswordPicker;
29
30
    function enterLoginDetails(login, allowSubmit) {
31
        var username;
32
33
        if (login.hasOwnProperty('username')) {
34
            username = (login.username !== '' ) ? login.username : login.email;
35
        }
36
        if (!username) {
37
            username = null;
38
        }
39
40
        fillPassword(username, login.password);
41
42
        if (activeForm) {
43
            API.runtime.sendMessage(API.runtime.id, {method: 'isAutoSubmitEnabled'}).then(function (isEnabled) {
44
                if (isEnabled && allowSubmit) {
45
                    submitLoginForm(username);
46
                }
47
            });
48
        }
49
    }
50
51
    _this.enterLoginDetails = enterLoginDetails;
52
53
    function enterCustomFields(login, settings) {
54
        var customFieldPattern = /^\#(.*)$/;
55
        var elementId;
56
        var element = false;
0 ignored issues
show
The variable element seems to be never used. Consider removing it.
Loading history...
57
58
        /* parhaps wise to try / catch this as this is non essential and no reason to abort previous processing */
59
        try {
60
            /* do we have custom_fields for this entry */
61
            if (login.hasOwnProperty('custom_fields') && login.custom_fields.length) {
62
                /* yes we do, iterate over all the custom_fields values */
63
                for (var i = 0, len = login.custom_fields.length; i < len; i++) {
64
                    /* does this custom field label begin with a hash? */
65
                    if (customFieldPattern.test(login.custom_fields[i].label)) {
66
                        /* set variable elementid to whatever element we are trying to auto fill */
67
                        elementId = customFieldPattern.exec(login.custom_fields[i].label)[1];
68
                        enterCustomFieldElement(elementId, login.custom_fields[i].value);
69
                    }
70
                    else if ($j('label[for]:contains(' + login.custom_fields[i].label + ')').length) {
71
                        elementId = $j('label[for]:contains(' + login.custom_fields[i].label + ')').attr('for');
72
                        enterCustomFieldElement(elementId, login.custom_fields[i].value);
73
                    }
74
                }
75
            }
76
        }
77
        catch (e) {
78
            if (settings.debug) {
79
                console.log('While attempting to auto fill custom fields the following exception was thrown: ' + e);
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
80
            }
81
        }
82
    }
83
84
    function enterCustomFieldElement(elementId, value) {
85
        /* check to see if element id exist */
86
        if ($j('#' + elementId).length) {
87
            element = $j('#' + elementId);
0 ignored issues
show
The variable element seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.element.
Loading history...
88
        }
89
        else if ($j('input[name$="' + elementId + '"]').length) { /* maybe element name exist */
90
            element = $j('input[name$="' + elementId + '"]');
91
        }
92
        else { /* neither element id or name exist */
93
            element = false;
94
        }
95
        /* if we have an element and it is type text, number or password, lets auto fill it */
96
        if (element && (element[0].type === 'text' || element[0].type === 'number' || element[0].type === 'password')) {
97
            element.val(value);
98
        }
99
    }
100
101
    function submitLoginForm(username) {
102
        if (!activeForm) {
103
            // @TODO detect login form on the current page
104
            return;
105
        }
106
107
        var formEl = $j(activeForm).closest('form');
108
        var iframeUrl = API.extension.getURL('/html/inject/auto_login.html');
109
        $j('#loginPopupIframe').remove();
110
        var loginPopup = $j('<iframe class="loginPopupIframe" scrolling="no" frameborder="0" src="' + iframeUrl + '"></iframe>');
111
        var padding = parseInt($j(formEl).css('padding').replace('px', ''));
112
        var margin = parseInt($j(formEl).css('margin').replace('px', ''));
113
        var height = Math.round($j(formEl).height() + (padding * 2) + (margin * 2));
114
        var width = Math.round($j(formEl).width() + (padding * 2) + (margin * 2));
115
        loginPopup.attr('height', height);
116
        loginPopup.attr('width', width);
117
        loginPopup.css('position', 'absolute');
118
        loginPopup.css('z-index', getMaxZ() + 1);
119
        loginPopup.css('background-color', 'rgba(0, 0, 0, 0.73)');
120
        loginPopup.css('left', Math.floor($j(formEl).offset().left - padding - margin));
121
        loginPopup.css('top', Math.floor($j(formEl).offset().top - padding - margin));
122
        removePasswordPicker();
123
        $j(document.body).prepend(loginPopup);
124
        API.runtime.sendMessage(API.runtime.id, {'setIframeUsername': username}).then(function () {
125
            $j(formEl).submit();
126
            setTimeout(function () {
127
                loginPopup.remove();
128
            }, 2000);
129
        });
130
    }
131
132
    function getMaxZ() {
133
        return Math.max.apply(null,
134
            $j.map($j('body *'), function (e) {
135
                if ($j(e).css('position') !== 'static')
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if $j(e).css("position") !== "static" 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...
136
                    return parseInt($j(e).css('z-index')) || 1;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
137
            }));
138
    }
139
140
    var activeForm;
141
142
    function showPasswordPicker(form) {
143
        var jPasswordPicker = $j('.passwordPickerIframe');
144
        if (jPasswordPicker.length > 1) {
145
            return;
146
        }
147
        var loginField = $j(form[0]);
148
        var loginFieldPos = loginField.offset();
149
        var loginFieldVisible = loginField.is(':visible');
150
151
        var position = $j(form[1]).position();
0 ignored issues
show
The assignment to variable position seems to be never used. Consider removing it.
Loading history...
152
        var passwordField = $j(form[1]);
153
        var passwordFieldPos = passwordField.offset();
154
        var passwordFieldVisible = loginField.is(':visible');
0 ignored issues
show
The variable passwordFieldVisible seems to be never used. Consider removing it.
Loading history...
155
        var left = (loginFieldPos) ? loginFieldPos.left : passwordFieldPos.left;
156
        var top = (loginFieldPos) ? loginFieldPos.top : passwordFieldPos.top;
157
        var maxZ = getMaxZ();
158
159
        if (loginFieldPos && passwordFieldPos.top > loginFieldPos.top) {
160
            //console.log('login fields below each other')
161
            top = passwordFieldPos.top + passwordField.height() + 10;
162
        } else {
163
            // console.log('login fields next to each other')
164
            if (loginFieldPos) {
165
                top = top + loginField.height() + 10;
166
            } else {
167
                top = top + passwordField.height() + 10;
168
            }
169
        }
170
        if (!loginFieldVisible) {
171
            left = passwordFieldPos.left;
172
        }
173
174
        var pickerUrl = API.extension.getURL('/html/inject/password_picker.html');
175
176
        var picker = $j('<iframe class="passwordPickerIframe" scrolling="no" height="385" width="350" frameborder="0" src="' + pickerUrl + '"></iframe>');
177
        picker.css('position', 'absolute');
178
        picker.css('left', left);
179
        picker.css('z-index', maxZ + 10);
180
        picker.css('top', top);
181
        $j('body').prepend($j(picker));
182
        activeForm = form;
183
        // picker.css('width', $j(form).width());
184
        $j('.passwordPickerIframe:not(:last)').remove();
185
    }
186
187
    function onFormIconClick(e) {
188
        e.preventDefault();
189
        e.stopPropagation();
190
        var offsetX = e.offsetX;
191
        var offsetRight = (e.data.width - offsetX);
192
        if (offsetRight < e.data.height) {
193
            showPasswordPicker(e.data.form);
194
        }
195
    }
196
197
    function createFormIcon(el, form) {
198
        var offset = el.offset();
0 ignored issues
show
The assignment to variable offset seems to be never used. Consider removing it.
Loading history...
199
        var width = el.width();
200
        var height = el.height() * 1;
201
        var margin = (el.css('margin')) ? parseInt(el.css('margin').replace('px', '')) : 0;
0 ignored issues
show
The variable margin seems to be never used. Consider removing it.
Loading history...
202
        var padding = (el.css('padding')) ? parseInt(el.css('padding').replace('px', '')) : 0;
0 ignored issues
show
The variable padding seems to be never used. Consider removing it.
Loading history...
203
204
        var pickerIcon = API.extension.getURL('/icons/icon.svg');
205
        $j(el).css('background-image', 'url("' + pickerIcon + '")');
206
        $j(el).css('background-repeat', 'no-repeat');
207
        //$j(el).css('background-position', '');
208
        $j(el).css('cssText', el.attr('style') + ' background-position: right 3px center !important;');
209
210
        $j(el).unbind('click', onFormIconClick);
211
        $j(el).click({width: width, height: height, form: form}, onFormIconClick);
212
    }
213
214
    function createPasswordPicker(form) {
215
        for (var i = 0; i < form.length; i++) {
216
            var el = $j(form[i]);
217
            createFormIcon(el, form);
218
        }
219
    }
220
221
    function formSubmitted(fields) {
222
        var user = fields[0].value;
223
        var pass = fields[1].value;
224
        var params = {
225
            username: user,
226
            password: pass
227
        };
228
        //Disable password mining
229
        //$j(fields[1]).attr('type', 'hidden');
230
        API.runtime.sendMessage(API.runtime.id, {method: "minedForm", args: params});
231
232
    }
233
234
    function inIframe() {
235
        try {
236
            return window.self !== window.top;
237
        } catch (e) {
238
            return true;
239
        }
240
    }
241
242
    function showDoorhanger(data) {
243
        if (inIframe()) {
244
            return;
245
        }
246
        data.data.currentLocation = window.location.href;
247
        API.runtime.sendMessage(API.runtime.id, {method: "setDoorhangerData", args: data});
248
        var pickerUrl = API.extension.getURL('/html/inject/doorhanger.html');
249
250
        var doorhanger = $j('<iframe id="password-toolbarIframe" style="display: none;" scrolling="no" height="60" width="100%" frameborder="0" src="' + pickerUrl + '"></iframe>');
251
        $j('#password-toolbarIframe').remove();
252
        doorhanger.css('z-index', getMaxZ() + 1);
253
        $j('body').prepend(doorhanger);
254
        $j('#password-toolbarIframe').fadeIn();
255
    }
256
257
    _this.showDoorhanger = showDoorhanger;
258
259
    function showUrlUpdateDoorhanger(data) {
260
        var buttons = ['cancel', 'updateUrl'];
261
        showDoorhanger({
262
            data: data.data,
263
            buttons: buttons
264
        });
265
    }
266
267
    _this.showUrlUpdateDoorhanger = showUrlUpdateDoorhanger;
268
269
    function checkForMined() {
270
        if (inIframe()) {
271
            return;
272
        }
273
274
        API.runtime.sendMessage(API.runtime.id, {method: "getMinedData"}).then(function (data) {
275
            if (!data) {
276
                return;
277
            }
278
            if (data.hasOwnProperty('username') && data.hasOwnProperty('password') && data.hasOwnProperty('url')) {
279
                var buttons = ['cancel', 'ignore', 'save'];
280
                showDoorhanger({data: data, buttons: buttons});
281
            }
282
        });
283
    }
284
285
286
    function closeDoorhanger() {
287
        $j('#password-toolbarIframe').hide(400);
288
        $j('#password-toolbarIframe').remove();
289
    }
290
291
    _this.closeDoorhanger = closeDoorhanger;
292
293
    var flagFilledForm = false;
294
    function initForms() {
295
        API.runtime.sendMessage(API.runtime.id, {method: 'getRuntimeSettings'}).then(function (settings) {
296
            var enablePasswordPicker = settings.enablePasswordPicker;
297
            var url = window.location.href;
298
            var loginFields = getLoginFields();
299
            if (!settings.hasOwnProperty('ignored_sites') || settings.ignored_sites.findUrl(url).length !== 0) {
300
                return;
301
            }
302
303
            if (loginFields.length > 0) {
304
                for (var i = 0; i < loginFields.length; i++) {
305
                    var form = getFormFromElement(loginFields[i][0]);
306
                    if (enablePasswordPicker) {
307
                        createPasswordPicker(loginFields[i], form);
0 ignored issues
show
The call to createPasswordPicker seems to have too many arguments starting with form.
Loading history...
308
                    }
309
310
                    //Password miner
311
                    /* jshint ignore:start */
312
                    $j(form).submit((function (loginFields) {
313
                        return function () {
314
                            formSubmitted(loginFields);
315
                        };
316
                    })(loginFields[i]));
317
                    /* jshint ignore:end */
318
                }
319
320
                API.runtime.sendMessage(API.runtime.id, {
321
                    method: "getCredentialsByUrl",
322
                    args: url
323
                }).then(function (logins) {
324
                    console.log('Found ' + logins.length + ' logins for this site');
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
325
                    if (logins.length === 1) {
326
                        API.runtime.sendMessage(API.runtime.id, {method: 'isAutoFillEnabled'}).then(function (isEnabled) {
327
                            if (isEnabled && !flagFilledForm) {
328
                                enterLoginDetails(logins[0], false);
329
                                flagFilledForm = true;
330
                            }
331
                        });
332
                    }
333
                });
334
            }
335
336
            API.runtime.sendMessage(API.runtime.id, {
337
                method: "getCredentialsByUrl",
338
                args: url
339
            }).then(function (logins) {
340
                if (logins.length === 1) {
341
                    API.runtime.sendMessage(API.runtime.id, {method: 'isAutoFillEnabled'}).then(function (isEnabled) {
342
                        if (isEnabled) {
343
                            enterCustomFields(logins[0], settings);
344
                        }
345
                    });
346
                }
347
            });
348
349
        });
350
    }
351
352
    function minedLoginSaved(args) {
353
        // If the login added by the user then this is true
354
        if (args.selfAdded) {
355
            showDoorhanger({
356
                data: args,
357
                buttons: ['cancel']
358
            });
359
            enterLoginDetails(args.credential, false);
360
        }
361
    }
362
363
    _this.minedLoginSaved = minedLoginSaved;
364
365
    function resizeIframe(height) {
366
        $j('#password-toolbarIframe').height(60 + height);
367
    }
368
369
    _this.resizeIframe = resizeIframe;
370
371
    function copyText(text) {
372
        var txtToCopy = document.createElement('input');
373
        txtToCopy.style.left = '-300px';
374
        txtToCopy.style.position = 'absolute';
375
        txtToCopy.value = text;
376
        document.body.appendChild(txtToCopy);
377
        txtToCopy.select();
378
        document.execCommand('copy');
379
        txtToCopy.parentNode.removeChild(txtToCopy);
380
    }
381
382
    _this.copyText = copyText;
383
384
    function init() {
385
        checkForMined();
386
        initForms();
387
    }
388
389
    var readyStateCheckInterval = setInterval(function () {
390
        if (document.readyState === "complete") {
391
            clearInterval(readyStateCheckInterval);
392
            API.runtime.sendMessage(API.runtime.id, {method: 'getMasterPasswordSet'}).then(function (result) {
393
                if (result) {
394
                    init();
395
                    var body = document.getElementsByTagName('body')[0];
396
                    if (body) {
397
                        observeDOM(body, initForms);
398
                    }
399
                } else {
400
                    console.log('[Passman extension] Stopping, vault key not set');
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
401
                }
402
            });
403
        }
404
    }, 10);
405
406
    API.runtime.onMessage.addListener(function (msg, sender) {
407
        //console.log('Method call', msg.method);
408
        if (_this[msg.method]) {
409
            _this[msg.method](msg.args, sender);
410
        }
411
    });
412
});
413