Completed
Push — master ( 83cbed...5cd6e6 )
by Sander
38s
created

js/background/inject/inject.js   F

Complexity

Total Complexity 92
Complexity/F 2.36

Size

Lines of Code 399
Function Count 39

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 11
Bugs 1 Features 0
Metric Value
cc 0
c 11
b 1
f 0
nc 24576
dl 0
loc 399
rs 3.12
wmc 92
mnd 6
bc 83
fnc 39
bpm 2.1282
cpm 2.3589
noi 13

1 Function

Rating   Name   Duplication   Size   Complexity  
B $j.ready 0 397 1

How to fix   Complexity   

Complexity

Complex classes like js/background/inject/inject.js 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
/* 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
Unused Code introduced by
The assignment to 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
						/* check to see if element id exist */
69
						if($j('#'+elementid).length){
70
							element=$j('#'+elementid);
71
						}
72
						else if($j('input[name$="'+elementid+'"]').length){ /* maybe element name exist */
73
							element=$j('input[name$="'+elementid+'"]');
74
						}
75
						else{ /* neither element id or name exist */
76
							element=false;
77
						}
78
						/* if we have an element and it is type text, number or password, lets auto fill it */
79
						if(element&&(element[0].type==='text'||element[0].type==='number'||element[0].type==='password')){
80
							element.val(login.custom_fields[i].value);
81
						}
82
					}
83
				}
84
			}
85
		}
86
		catch(e){
87
			if(settings.debug){
88
                console.log('While attempting to auto fill custom fields the following exception was thrown: '+e);
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...
89
			}
90
		}
91
	}
92
    
93
    function submitLoginForm(username) {
94
        if (!activeForm) {
95
            // @TODO detect login form on the current page
96
            return;
97
        }
98
99
        var formEl = $j(activeForm).closest('form');
100
        var iframeUrl = API.extension.getURL('/html/inject/auto_login.html');
101
        $j('#loginPopupIframe').remove();
102
        var loginPopup = $j('<iframe class="loginPopupIframe" scrolling="no" frameborder="0" src="' + iframeUrl + '"></iframe>');
103
        var padding = parseInt($j(formEl).css('padding').replace('px', ''));
104
        var margin = parseInt($j(formEl).css('margin').replace('px', ''));
105
        var height = Math.round($j(formEl).height() + (padding * 2) + (margin * 2));
106
        var width = Math.round($j(formEl).width() + (padding * 2) + (margin * 2));
107
        loginPopup.attr('height', height);
108
        loginPopup.attr('width', width);
109
        loginPopup.css('position', 'absolute');
110
        loginPopup.css('z-index', getMaxZ() + 1);
111
        loginPopup.css('background-color', 'rgba(0, 0, 0, 0.73)');
112
        loginPopup.css('left', Math.floor($j(formEl).offset().left - padding - margin));
113
        loginPopup.css('top', Math.floor($j(formEl).offset().top - padding - margin));
114
        removePasswordPicker();
115
        $j(document.body).prepend(loginPopup);
116
        API.runtime.sendMessage(API.runtime.id, {'setIframeUsername': username}).then(function () {
117
            $j(formEl).submit();
118
            setTimeout(function () {
119
                loginPopup.remove();
120
            }, 2000);
121
        });
122
    }
123
124
    function getMaxZ() {
125
        return Math.max.apply(null,
126
            $j.map($j('body *'), function (e) {
127
                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...
128
                    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...
129
            }));
130
    }
131
132
    var activeForm;
133
134
    function showPasswordPicker(form) {
135
        var jPasswordPicker = $j('.passwordPickerIframe');
136
        if (jPasswordPicker.length > 1) {
137
            return;
138
        }
139
        var loginField = $j(form[0]);
140
        var loginFieldPos = loginField.offset();
141
        var loginFieldVisible = loginField.is(':visible');
142
143
        var position = $j(form[1]).position();
0 ignored issues
show
Unused Code introduced by
The assignment to variable position seems to be never used. Consider removing it.
Loading history...
144
        var passwordField = $j(form[1]);
145
        var passwordFieldPos = passwordField.offset();
146
        var passwordFieldVisible = loginField.is(':visible');
0 ignored issues
show
Unused Code introduced by
The variable passwordFieldVisible seems to be never used. Consider removing it.
Loading history...
147
        var left = (loginFieldPos) ? loginFieldPos.left : passwordFieldPos.left;
148
        var top = (loginFieldPos) ? loginFieldPos.top : passwordFieldPos.top;
149
        var maxZ = getMaxZ();
150
151
        if (loginFieldPos && passwordFieldPos.top > loginFieldPos.top) {
152
            //console.log('login fields below each other')
153
            top = passwordFieldPos.top + passwordField.height() + 10;
154
        } else {
155
            // console.log('login fields next to each other')
156
            if (loginFieldPos) {
157
                top = top + loginField.height() + 10;
158
            } else {
159
                top = top + passwordField.height() + 10;
160
            }
161
        }
162
        if (!loginFieldVisible) {
163
            left = passwordFieldPos.left;
164
        }
165
166
        var pickerUrl = API.extension.getURL('/html/inject/password_picker.html');
167
168
        var picker = $j('<iframe class="passwordPickerIframe" scrolling="no" height="385" width="350" frameborder="0" src="' + pickerUrl + '"></iframe>');
169
        picker.css('position', 'absolute');
170
        picker.css('left', left);
171
        picker.css('z-index', maxZ + 10);
172
        picker.css('top', top);
173
        $j('body').prepend($j(picker));
174
        activeForm = form;
175
        // picker.css('width', $j(form).width());
176
        $j('.passwordPickerIframe:not(:last)').remove();
177
    }
178
179
    function onFormIconClick(e) {
180
        e.preventDefault();
181
        e.stopPropagation();
182
        var offsetX = e.offsetX;
183
        var offsetRight = (e.data.width - offsetX);
184
        if (offsetRight < e.data.height) {
185
            showPasswordPicker(e.data.form);
186
        }
187
    }
188
189
    function createFormIcon(el, form) {
190
        var offset = el.offset();
0 ignored issues
show
Unused Code introduced by
The assignment to variable offset seems to be never used. Consider removing it.
Loading history...
191
        var width = el.width();
192
        var height = el.height() * 1;
193
        var margin = (el.css('margin')) ? parseInt(el.css('margin').replace('px', '')) : 0;
0 ignored issues
show
Unused Code introduced by
The variable margin seems to be never used. Consider removing it.
Loading history...
194
        var padding = (el.css('padding')) ? parseInt(el.css('padding').replace('px', '')) : 0;
0 ignored issues
show
Unused Code introduced by
The variable padding seems to be never used. Consider removing it.
Loading history...
195
196
        var pickerIcon = API.extension.getURL('/icons/icon.svg');
197
        $j(el).css('background-image', 'url("' + pickerIcon + '")');
198
        $j(el).css('background-repeat', 'no-repeat');
199
        //$j(el).css('background-position', '');
200
        $j(el).css('cssText', el.attr('style') + ' background-position: right 3px center !important;');
201
202
        $j(el).unbind('click', onFormIconClick);
203
        $j(el).click({width: width, height: height, form: form}, onFormIconClick);
204
    }
205
206
    function createPasswordPicker(form) {
207
        for (var i = 0; i < form.length; i++) {
208
            var el = $j(form[i]);
209
            createFormIcon(el, form);
210
        }
211
    }
212
213
    function formSubmitted(fields) {
214
        var user = fields[0].value;
215
        var pass = fields[1].value;
216
        var params = {
217
            username: user,
218
            password: pass
219
        };
220
        //Disable password mining
221
        //$j(fields[1]).attr('type', 'hidden');
222
        API.runtime.sendMessage(API.runtime.id, {method: "minedForm", args: params});
223
224
    }
225
226
    function inIframe() {
227
        try {
228
            return window.self !== window.top;
229
        } catch (e) {
230
            return true;
231
        }
232
    }
233
234
    function showDoorhanger(data) {
235
        if (inIframe()) {
236
            return;
237
        }
238
        data.data.currentLocation = window.location.href;
239
        API.runtime.sendMessage(API.runtime.id, {method: "setDoorhangerData", args: data});
240
        var pickerUrl = API.extension.getURL('/html/inject/doorhanger.html');
241
242
        var doorhanger = $j('<iframe id="password-toolbarIframe" style="display: none;" scrolling="no" height="60" width="100%" frameborder="0" src="' + pickerUrl + '"></iframe>');
243
        $j('#password-toolbarIframe').remove();
244
        doorhanger.css('z-index', getMaxZ() + 1);
245
        $j('body').prepend(doorhanger);
246
        $j('#password-toolbarIframe').fadeIn();
247
    }
248
249
    _this.showDoorhanger = showDoorhanger;
250
251
    function showUrlUpdateDoorhanger(data) {
252
        var buttons = ['cancel', 'updateUrl'];
253
        showDoorhanger({
254
            data: data.data,
255
            buttons: buttons
256
        });
257
    }
258
259
    _this.showUrlUpdateDoorhanger = showUrlUpdateDoorhanger;
260
261
    function checkForMined() {
262
        if (inIframe()) {
263
            return;
264
        }
265
266
        API.runtime.sendMessage(API.runtime.id, {method: "getMinedData"}).then(function (data) {
267
            if (!data) {
268
                return;
269
            }
270
            if (data.hasOwnProperty('username') && data.hasOwnProperty('password') && data.hasOwnProperty('url')) {
271
                var buttons = ['cancel', 'ignore', 'save'];
272
                showDoorhanger({data: data, buttons: buttons});
273
            }
274
        });
275
    }
276
277
278
    function closeDoorhanger() {
279
        $j('#password-toolbarIframe').hide(400);
280
        $j('#password-toolbarIframe').remove();
281
    }
282
283
    _this.closeDoorhanger = closeDoorhanger;
284
285
    function initForms() {
286
        API.runtime.sendMessage(API.runtime.id, {method: 'getRuntimeSettings'}).then(function (settings) {
287
            var enablePasswordPicker = settings.enablePasswordPicker;
288
            var url = window.location.href;
289
            var loginFields = getLoginFields();
290
            if (!settings.hasOwnProperty('ignored_sites') || settings.ignored_sites.findUrl(url).length !== 0) {
291
                return;
292
            }
293
294
            if (loginFields.length > 0) {
295
                for (var i = 0; i < loginFields.length; i++) {
296
                    var form = getFormFromElement(loginFields[i][0]);
297
                        if (enablePasswordPicker) {
298
                            createPasswordPicker(loginFields[i], form);
0 ignored issues
show
Bug introduced by
The call to createPasswordPicker seems to have too many arguments starting with form.
Loading history...
299
                        }
300
301
                        //Password miner
302
                        /* jshint ignore:start */
303
                        $j(form).submit((function (loginFields) {
304
                            return function () {
305
                                formSubmitted(loginFields);
306
                            };
307
                        })(loginFields[i]));
308
                        /* jshint ignore:end */
309
                }
310
311
                API.runtime.sendMessage(API.runtime.id, {
312
                    method: "getCredentialsByUrl",
313
                    args: url
314
                }).then(function (logins) {
315
                    console.log('Found ' + logins.length + ' logins for this site');
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...
316
                    if (logins.length === 1) {
317
                        API.runtime.sendMessage(API.runtime.id, {method: 'isAutoFillEnabled'}).then(function (isEnabled) {
318
                            if (isEnabled) {
319
                                enterLoginDetails(logins[0], false);
320
                            }
321
                        });
322
                    }
323
                });
324
            }
325
326
            API.runtime.sendMessage(API.runtime.id, {
327
                method: "getCredentialsByUrl",
328
                args: url
329
            }).then(function (logins) {
330
                if (logins.length === 1) {
331
                    API.runtime.sendMessage(API.runtime.id, {method: 'isAutoFillEnabled'}).then(function (isEnabled) {
332
                        if (isEnabled) {
333
                            enterCustomFields(logins[0], settings);
334
                        }
335
                    });
336
                    }
337
            });
338
            
339
        });
340
    }
341
342
    function minedLoginSaved(args) {
343
        // If the login added by the user then this is true
344
        if (args.selfAdded) {
345
            showDoorhanger({
346
                data: args,
347
                buttons: ['cancel']
348
            });
349
            enterLoginDetails(args.credential, false);
350
        }
351
    }
352
353
    _this.minedLoginSaved = minedLoginSaved;
354
355
    function resizeIframe(height) {
356
        $j('#password-toolbarIframe').height(60 + height);
357
    }
358
359
    _this.resizeIframe = resizeIframe;
360
361
    function copyText(text) {
362
        var txtToCopy = document.createElement('input');
363
        txtToCopy.style.left = '-300px';
364
        txtToCopy.style.position = 'absolute';
365
        txtToCopy.value = text;
366
        document.body.appendChild(txtToCopy);
367
        txtToCopy.select();
368
        document.execCommand('copy');
369
        txtToCopy.parentNode.removeChild(txtToCopy);
370
    }
371
372
    _this.copyText = copyText;
373
374
    function init() {
375
        checkForMined();
376
        initForms();
377
    }
378
379
    var readyStateCheckInterval = setInterval(function () {
380
        if (document.readyState === "complete") {
381
            clearInterval(readyStateCheckInterval);
382
            API.runtime.sendMessage(API.runtime.id, {method: 'getMasterPasswordSet'}).then(function (result) {
383
                if (result) {
384
                    init();
385
                    var body = document.getElementsByTagName('body')[0];
386
                    observeDOM(body, initForms);
387
                } else {
388
                    console.log('[Passman extension] Stopping, vault key not set');
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...
389
                }
390
            });
391
        }
392
    }, 10);
393
394
    API.runtime.onMessage.addListener(function (msg, sender) {
395
        //console.log('Method call', msg.method);
396
        if (_this[msg.method]) {
397
            _this[msg.method](msg.args, sender);
398
        }
399
    });
400
});
401