--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1222,16 +1222,18 @@ pref("security.mixed_content.block_activ
// Show degraded UI for http pages with password fields.
// Only for Nightly, Dev Edition and early beta, not for late beta or release.
#ifdef EARLY_BETA_OR_EARLIER
pref("security.insecure_password.ui.enabled", true);
#else
pref("security.insecure_password.ui.enabled", false);
#endif
+pref("security.insecure_field_warning.contextual.enabled", false);
+
// 1 = allow MITM for certificate pinning checks.
pref("security.cert_pinning.enforcement_level", 1);
// Override the Gecko-default value of false for Firefox.
pref("plain_text.wrap_long_lines", true);
// If this turns true, Moz*Gesture events are not called stopPropagation()
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -479,16 +479,36 @@ toolbar:not(#TabsToolbar) > #personal-bo
#PopupAutoComplete > richlistbox > richlistitem > .ac-type-icon,
#PopupAutoComplete > richlistbox > richlistitem > .ac-site-icon,
#PopupAutoComplete > richlistbox > richlistitem > .ac-tags,
#PopupAutoComplete > richlistbox > richlistitem > .ac-separator,
#PopupAutoComplete > richlistbox > richlistitem > .ac-url {
display: none;
}
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"] {
+ -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistitem-insecure-field");
+ height: auto;
+ background-color: #F6F6F6;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"] > .ac-site-icon {
+ display: initial;
+ list-style-image: url(chrome://browser/skin/connection-mixed-active-loaded.svg#icon);
+}
+
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"] > .ac-title > .ac-text-overflow-container > .ac-title-text {
+ text-overflow: initial;
+ white-space: initial;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"] > .ac-title > label {
+ margin-inline-start: 0;
+}
+
#PopupSearchAutoComplete {
-moz-binding: url("chrome://browser/content/search/search.xml#browser-search-autocomplete-result-popup");
}
/* Overlay a badge on top of the icon of additional open search providers
in the search panel. */
.addengine-item > .button-box > .button-icon {
-moz-binding: url("chrome://browser/content/search/search.xml#addengine-icon");
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -5,31 +5,34 @@
"use strict";
this.EXPORTED_SYMBOLS = [ "LoginManagerContent",
"LoginFormFactory",
"UserAutoCompleteResult" ];
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
const PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS = 1;
+const PREF_INSECURE_FIELD_WARNING_ENABLED = "security.insecure_field_warning.contextual.enabled";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/InsecurePasswordUtils.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
"resource://gre/modules/FormLikeFactory.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginRecipesContent",
"resource://gre/modules/LoginRecipes.jsm");
-
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
+ "resource://gre/modules/InsecurePasswordUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gNetUtil",
"@mozilla.org/network/util;1",
"nsINetUtil");
XPCOMUtils.defineLazyGetter(this, "log", () => {
let logger = LoginHelper.createLogger("LoginManagerContent");
return logger.log.bind(logger);
@@ -304,16 +307,17 @@ var LoginManagerContent = {
null;
let requestData = {};
let messageData = { formOrigin: formOrigin,
actionOrigin: actionOrigin,
searchString: aSearchString,
previousResult: previousResult,
rect: aRect,
+ isSecure: InsecurePasswordUtils.isFormSecure(form),
remote: remote };
return this._sendRequest(messageManager, requestData,
"RemoteLogins:autoCompleteLogins",
messageData);
},
setupProgressListener(window) {
@@ -1208,34 +1212,39 @@ var LoginUtils = {
if (uriString == "")
uriString = form.baseURI; // ala bug 297761
return this._getPasswordOrigin(uriString, true);
},
};
// nsIAutoCompleteResult implementation
-function UserAutoCompleteResult (aSearchString, matchingLogins, messageManager) {
+function UserAutoCompleteResult (aSearchString, matchingLogins, {isSecure, messageManager}) {
function loginSort(a, b) {
var userA = a.username.toLowerCase();
var userB = b.username.toLowerCase();
if (userA < userB)
return -1;
if (userA > userB)
return 1;
return 0;
}
+ let prefShowInsecureFieldWarning =
+ Preferences.get(PREF_INSECURE_FIELD_WARNING_ENABLED, false);
+
+ this._showInsecureFieldWarning = (!isSecure && prefShowInsecureFieldWarning) ? 1 : 0;
this.searchString = aSearchString;
this.logins = matchingLogins.sort(loginSort);
- this.matchCount = matchingLogins.length;
+ this.matchCount = matchingLogins.length + this._showInsecureFieldWarning;
this._messageManager = messageManager;
+ this._stringBundle = Services.strings.createBundle("chrome://passwordmgr/locale/passwordmgr.properties");
if (this.matchCount > 0) {
this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
this.defaultIndex = 0;
}
}
UserAutoCompleteResult.prototype = {
@@ -1254,46 +1263,68 @@ UserAutoCompleteResult.prototype = {
// Interfaces from idl...
searchString : null,
searchResult : Ci.nsIAutoCompleteResult.RESULT_NOMATCH,
defaultIndex : -1,
errorDescription : "",
matchCount : 0,
getValueAt(index) {
- if (index < 0 || index >= this.logins.length)
+ if (index < 0 || index >= this.matchCount)
throw new Error("Index out of range.");
- return this.logins[index].username;
+ if (this._showInsecureFieldWarning && index === 0) {
+ return "";
+ }
+
+ return this.logins[index - this._showInsecureFieldWarning].username;
},
getLabelAt(index) {
- return this.getValueAt(index);
+ if (index < 0 || index >= this.matchCount)
+ throw new Error("Index out of range.");
+
+ if (this._showInsecureFieldWarning && index === 0) {
+ return this._stringBundle.GetStringFromName("insecureFieldWarningDescription");
+ }
+
+ return this.logins[index - this._showInsecureFieldWarning].username;
},
getCommentAt(index) {
return "";
},
getStyleAt(index) {
+ if (index == 0 && this._showInsecureFieldWarning) {
+ return "insecureWarning";
+ }
return "";
},
getImageAt(index) {
return "";
},
getFinalCompleteValueAt(index) {
return this.getValueAt(index);
},
removeValueAt(index, removeFromDB) {
- if (index < 0 || index >= this.logins.length)
+ if (index < 0 || index >= this.matchCount)
throw new Error("Index out of range.");
+ if (this._showInsecureFieldWarning && index === 0) {
+ // Ignore the warning message item.
+ return;
+ }
+ if (this._showInsecureFieldWarning) {
+ index--;
+ }
+
var [removedLogin] = this.logins.splice(index, 1);
this.matchCount--;
if (this.defaultIndex > this.logins.length)
this.defaultIndex--;
if (removeFromDB) {
if (this._messageManager) {
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -222,17 +222,17 @@ var LoginManagerParent = {
requestId: requestId,
logins: jsLogins,
recipes,
});
}),
doAutocompleteSearch: function({ formOrigin, actionOrigin,
searchString, previousResult,
- rect, requestId, remote }, target) {
+ rect, requestId, isSecure, remote }, target) {
// Note: previousResult is a regular object, not an
// nsIAutoCompleteResult.
let searchStringLower = searchString.toLowerCase();
let logins;
if (previousResult &&
searchStringLower.startsWith(previousResult.searchString.toLowerCase())) {
log("Using previous autocomplete result");
@@ -265,17 +265,17 @@ var LoginManagerParent = {
});
// XXX In the E10S case, we're responsible for showing our own
// autocomplete popup here because the autocomplete protocol hasn't
// been e10s-ized yet. In the non-e10s case, our caller is responsible
// for showing the autocomplete popup (via the regular
// nsAutoCompleteController).
if (remote) {
- let results = new UserAutoCompleteResult(searchString, matchingLogins);
+ let results = new UserAutoCompleteResult(searchString, matchingLogins, {isSecure});
AutoCompletePopup.showPopupWithResults({ browser: target.ownerDocument.defaultView, rect, results });
}
// Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
// doesn't support structured cloning.
var jsLogins = LoginHelper.loginsToVanillaObjects(matchingLogins);
target.messageManager.sendAsyncMessage("RemoteLogins:loginsAutoCompleted", {
requestId: requestId,
--- a/toolkit/components/passwordmgr/nsLoginManager.js
+++ b/toolkit/components/passwordmgr/nsLoginManager.js
@@ -17,16 +17,20 @@ Cu.import("resource://gre/modules/LoginM
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginFormFactory",
+ "resource://gre/modules/LoginManagerContent.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
+ "resource://gre/modules/InsecurePasswordUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "log", () => {
let logger = LoginHelper.createLogger("nsLoginManager");
return logger;
});
const MS_PER_DAY = 24 * 60 * 60 * 1000;
@@ -473,19 +477,22 @@ LoginManager.prototype = {
* We really ought to have a simple way for code to register an
* auto-complete provider, and not have satchel calling pwmgr directly.
*/
autoCompleteSearchAsync(aSearchString, aPreviousResult,
aElement, aCallback) {
// aPreviousResult is an nsIAutoCompleteResult, aElement is
// nsIDOMHTMLInputElement
+ let form = LoginFormFactory.createFromField(aElement);
+ let isSecure = InsecurePasswordUtils.isFormSecure(form);
+
if (!this._remember) {
setTimeout(function() {
- aCallback.onSearchCompletion(new UserAutoCompleteResult(aSearchString, []));
+ aCallback.onSearchCompletion(new UserAutoCompleteResult(aSearchString, [], {isSecure}));
}, 0);
return;
}
log.debug("AutoCompleteSearch invoked. Search is:", aSearchString);
let previousResult;
if (aPreviousResult) {
@@ -503,17 +510,20 @@ LoginManager.prototype = {
// If the search was canceled before we got our
// results, don't bother reporting them.
if (this._autoCompleteLookupPromise !== autoCompleteLookupPromise) {
return;
}
this._autoCompleteLookupPromise = null;
let results =
- new UserAutoCompleteResult(aSearchString, logins, messageManager);
+ new UserAutoCompleteResult(aSearchString, logins, {
+ messageManager,
+ isSecure,
+ });
aCallback.onSearchCompletion(results);
})
.then(null, Cu.reportError);
},
stopSearch() {
this._autoCompleteLookupPromise = null;
},
--- a/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
+++ b/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
@@ -21,16 +21,18 @@ skip-if = toolkit == 'android' # Bug 125
[test_basic_form_0pw.html]
[test_basic_form_1pw.html]
[test_basic_form_1pw_2.html]
[test_basic_form_2pw_1.html]
[test_basic_form_2pw_2.html]
[test_basic_form_3pw_1.html]
[test_basic_form_autocomplete.html]
skip-if = toolkit == 'android' # android:autocomplete.
+[test_insecure_form_field_autocomplete.html]
+skip-if = toolkit == 'android' # android:autocomplete.
[test_basic_form_html5.html]
[test_basic_form_pwevent.html]
[test_basic_form_pwonly.html]
[test_bug_627616.html]
skip-if = toolkit == 'android' # Tests desktop prompts
[test_bug_776171.html]
[test_case_differences.html]
skip-if = toolkit == 'android' # autocomplete
--- a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
@@ -204,16 +204,17 @@ function sendFakeAutocompleteEvent(eleme
element.dispatchEvent(acEvent);
}
function spinEventLoop() {
return Promise.resolve();
}
add_task(function* setup() {
+ yield SpecialPowers.pushPrefEnv({"set": [["security.insecure_field_warning.contextual.enabled", false]]});
listenForUnexpectedPopupShown();
});
add_task(function* test_form1_initial_empty() {
yield SimpleTest.promiseFocus(window);
// Make sure initial form is empty.
checkACForm("", "");
copy from toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
copy to toolkit/components/passwordmgr/test/mochitest/test_insecure_form_field_autocomplete.html
--- a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_insecure_form_field_autocomplete.html
@@ -204,82 +204,105 @@ function sendFakeAutocompleteEvent(eleme
element.dispatchEvent(acEvent);
}
function spinEventLoop() {
return Promise.resolve();
}
add_task(function* setup() {
+ yield SpecialPowers.pushPrefEnv({"set": [["security.insecure_field_warning.contextual.enabled", true]]});
listenForUnexpectedPopupShown();
});
add_task(function* test_form1_initial_empty() {
yield SimpleTest.promiseFocus(window);
// Make sure initial form is empty.
checkACForm("", "");
let popupState = yield getPopupState();
is(popupState.open, false, "Check popup is initially closed");
});
+add_task(function* test_form1_warning_entry() {
+ yield SimpleTest.promiseFocus(window);
+ // Trigger autocomplete popup
+ restoreForm();
+ let shownPromise = promiseACShown();
+ doKey("down"); // open
+ yield shownPromise;
+
+ let popupState = yield getPopupState();
+ is(popupState.selectedIndex, -1, "Check no entries are selected upon opening");
+
+ doKey("down"); // select insecure warning
+ checkACForm("", ""); // value shouldn't update just by selecting
+ doKey("return"); // not "enter"!
+ yield spinEventLoop(); // let focus happen
+ checkACForm("", "");
+});
+
add_task(function* test_form1_first_entry() {
yield SimpleTest.promiseFocus(window);
// Trigger autocomplete popup
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
let popupState = yield getPopupState();
is(popupState.selectedIndex, -1, "Check no entries are selected upon opening");
+ doKey("down"); // skip insecure warning
doKey("down"); // first
checkACForm("", ""); // value shouldn't update just by selecting
doKey("return"); // not "enter"!
yield promiseFormsProcessed();
checkACForm("tempuser1", "temppass1");
});
add_task(function* test_form1_second_entry() {
// Trigger autocomplete popup
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
+ doKey("down"); // skip insecure warning
doKey("down"); // first
doKey("down"); // second
doKey("return"); // not "enter"!
yield promiseFormsProcessed();
checkACForm("testuser2", "testpass2");
});
add_task(function* test_form1_third_entry() {
// Trigger autocomplete popup
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
+ doKey("down"); // skip insecure warning
doKey("down"); // first
doKey("down"); // second
doKey("down"); // third
doKey("return");
yield promiseFormsProcessed();
checkACForm("testuser3", "testpass3");
});
add_task(function* test_form1_fourth_entry() {
// Trigger autocomplete popup
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
+ doKey("down"); // skip insecure warning
doKey("down"); // first
doKey("down"); // second
doKey("down"); // third
doKey("down"); // fourth
doKey("return");
yield promiseFormsProcessed();
checkACForm("zzzuser4", "zzzpass4");
});
@@ -287,21 +310,23 @@ add_task(function* test_form1_fourth_ent
add_task(function* test_form1_wraparound_first_entry() {
// Trigger autocomplete popup
restoreForm();
yield spinEventLoop(); // let focus happen
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
+ doKey("down"); // skip insecure warning
doKey("down"); // first
doKey("down"); // second
doKey("down"); // third
doKey("down"); // fourth
doKey("down"); // deselects
+ doKey("down"); // skip insecure warning
doKey("down"); // first
doKey("return");
yield promiseFormsProcessed();
checkACForm("tempuser1", "temppass1");
});
add_task(function* test_form1_wraparound_up_last_entry() {
// Trigger autocomplete popup
@@ -337,71 +362,75 @@ add_task(function* test_form1_wraparound
doKey("down"); // open
yield shownPromise;
doKey("down");
doKey("up"); // deselects
doKey("up"); // last entry
doKey("up");
doKey("up");
+ doKey("up"); // skip insecure warning
doKey("up"); // first entry
doKey("up"); // deselects
doKey("up"); // last entry
doKey("return");
yield promiseFormsProcessed();
checkACForm("zzzuser4", "zzzpass4");
});
add_task(function* test_form1_fill_username_without_autofill_right() {
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
// Set first entry w/o triggering autocomplete
+ doKey("down"); // skip insecure warning
doKey("down"); // first
doKey("right");
yield spinEventLoop();
checkACForm("tempuser1", ""); // empty password
});
add_task(function* test_form1_fill_username_without_autofill_left() {
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
// Set first entry w/o triggering autocomplete
+ doKey("down"); // skip insecure warning
doKey("down"); // first
doKey("left");
checkACForm("tempuser1", ""); // empty password
});
add_task(function* test_form1_pageup_first() {
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
// Check first entry (page up)
doKey("down"); // first
doKey("down"); // second
doKey("page_up"); // first
+ doKey("down"); // skip insecure warning
doKey("return");
yield promiseFormsProcessed();
checkACForm("tempuser1", "temppass1");
});
add_task(function* test_form1_pagedown_last() {
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
- /* test 13 */
+ // test 13
// Check last entry (page down)
doKey("down"); // first
doKey("page_down"); // last
doKey("return");
yield promiseFormsProcessed();
checkACForm("zzzuser4", "zzzpass4");
});
@@ -423,23 +452,24 @@ add_task(function* test_form1_delete() {
doKey("down"); // open
yield shownPromise;
// XXX tried sending character "t" before/during dropdown to test
// filtering, but had no luck. Seemed like the character was getting lost.
// Setting uname.value didn't seem to work either. This works with a human
// driver, so I'm not sure what's up.
+ doKey("down"); // skip insecure warning
// Delete the first entry (of 4), "tempuser1"
doKey("down");
var numLogins;
numLogins = LoginManager.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null);
is(numLogins, 5, "Correct number of logins before deleting one");
- let countChangedPromise = notifyMenuChanged(3);
+ let countChangedPromise = notifyMenuChanged(4);
var deletionPromise = promiseStorageChanged(["removeLogin"]);
// On OS X, shift-backspace and shift-delete work, just delete does not.
// On Win/Linux, shift-backspace does not work, delete and shift-delete do.
doKey("delete", shiftModifier);
yield deletionPromise;
checkACForm("", "");
numLogins = LoginManager.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null);
@@ -451,29 +481,31 @@ add_task(function* test_form1_delete() {
});
add_task(function* test_form1_first_after_deletion() {
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
+ doKey("down"); // skip insecure warning
// Check the new first entry (of 3)
doKey("down");
doKey("return");
yield promiseFormsProcessed();
checkACForm("testuser2", "testpass2");
});
add_task(function* test_form1_delete_second() {
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
+ doKey("down"); // skip insecure warning
// Delete the second entry (of 3), "testuser3"
doKey("down");
doKey("down");
doKey("delete", shiftModifier);
checkACForm("", "");
numLogins = LoginManager.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null);
is(numLogins, 3, "Correct number of logins after deleting one");
doKey("return");
@@ -482,30 +514,32 @@ add_task(function* test_form1_delete_sec
});
add_task(function* test_form1_first_after_deletion2() {
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
+ doKey("down"); // skip insecure warning
// Check the new first entry (of 2)
doKey("down");
doKey("return");
yield promiseFormsProcessed();
checkACForm("testuser2", "testpass2");
});
add_task(function* test_form1_delete_last() {
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
- /* test 54 */
+ doKey("down"); // skip insecure warning
+ // test 54
// Delete the last entry (of 2), "zzzuser4"
doKey("down");
doKey("down");
doKey("delete", shiftModifier);
checkACForm("", "");
numLogins = LoginManager.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null);
is(numLogins, 2, "Correct number of logins after deleting one");
doKey("return");
@@ -514,53 +548,56 @@ add_task(function* test_form1_delete_las
});
add_task(function* test_form1_first_after_3_deletions() {
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
+ doKey("down"); // skip insecure warning
// Check the only remaining entry
doKey("down");
doKey("return");
yield promiseFormsProcessed();
checkACForm("testuser2", "testpass2");
});
add_task(function* test_form1_check_only_entry_remaining() {
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
- /* test 56 */
+ doKey("down"); // skip insecure warning
+ // test 56
// Delete the only remaining entry, "testuser2"
doKey("down");
doKey("delete", shiftModifier);
checkACForm("", "");
numLogins = LoginManager.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null);
is(numLogins, 1, "Correct number of logins after deleting one");
// remove the login that's not shown in the list.
setupScript.sendSyncMessage("removeLogin", "login0");
});
-/* Tests for single-user forms for ignoring autocomplete=off */
+// Tests for single-user forms for ignoring autocomplete=off
add_task(function* test_form2() {
// Turn our attention to form2
uname = $_(2, "uname");
pword = $_(2, "pword");
checkACForm("singleuser5", "singlepass5");
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
+ doKey("down"); // skip insecure warning
// Check first entry
doKey("down");
checkACForm("", ""); // value shouldn't update
doKey("return"); // not "enter"!
yield promiseFormsProcessed();
checkACForm("singleuser5", "singlepass5");
});
@@ -568,16 +605,17 @@ add_task(function* test_form3() {
uname = $_(3, "uname");
pword = $_(3, "pword");
checkACForm("singleuser5", "singlepass5");
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
+ doKey("down"); // skip insecure warning
// Check first entry
doKey("down");
checkACForm("", ""); // value shouldn't update
doKey("return"); // not "enter"!
yield promiseFormsProcessed();
checkACForm("singleuser5", "singlepass5");
});
@@ -585,16 +623,17 @@ add_task(function* test_form4() {
uname = $_(4, "uname");
pword = $_(4, "pword");
checkACForm("singleuser5", "singlepass5");
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
+ doKey("down"); // skip insecure warning
// Check first entry
doKey("down");
checkACForm("", ""); // value shouldn't update
doKey("return"); // not "enter"!
yield promiseFormsProcessed();
checkACForm("singleuser5", "singlepass5");
});
@@ -602,16 +641,17 @@ add_task(function* test_form5() {
uname = $_(5, "uname");
pword = $_(5, "pword");
checkACForm("singleuser5", "singlepass5");
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
+ doKey("down"); // skip insecure warning
// Check first entry
doKey("down");
checkACForm("", ""); // value shouldn't update
doKey("return"); // not "enter"!
yield promiseFormsProcessed();
checkACForm("singleuser5", "singlepass5");
});
@@ -657,16 +697,17 @@ add_task(function* test_form7() {
});
add_task(function* test_form7_2() {
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
+ doKey("down"); // skip insecure warning
// Check first entry
doKey("down");
checkACForm("", ""); // value shouldn't update
doKey("return"); // not "enter"!
// The form changes, so we expect the old username field to get the
// selected autocomplete value, but neither the new username field nor
// the password field should have any values filled in.
yield spinEventLoop();
@@ -712,41 +753,45 @@ add_task(function* test_form9_filtering(
checkACForm("form9userAB", "");
uname.focus();
doKey("left");
shownPromise = promiseACShown();
sendChar("A");
let results = yield shownPromise;
checkACForm("form9userAAB", "");
- checkArrayValues(results, ["form9userAAB"], "Check dropdown is updated after inserting 'A'");
+ checkArrayValues(results, ["This connection is not secure. Logins entered here could be compromised.", "form9userAAB"],
+ "Check dropdown is updated after inserting 'A'");
+ doKey("down"); // skip insecure warning
doKey("down");
doKey("return");
yield promiseFormsProcessed();
checkACForm("form9userAAB", "form9pass");
});
add_task(function* test_form9_autocomplete_cache() {
// Note that this addLogin call will only be seen by the autocomplete
// attempt for the sendChar if we do not successfully cache the
// autocomplete results.
setupScript.sendSyncMessage("addLogin", "login8C");
uname.focus();
- let promise0 = notifyMenuChanged(0);
+ let promise0 = notifyMenuChanged(1);
+ let shownPromise = promiseACShown();
sendChar("z");
yield promise0;
+ yield shownPromise;
let popupState = yield getPopupState();
- is(popupState.open, false, "Check popup shouldn't open");
+ is(popupState.open, true, "Check popup should open");
// check that empty results are cached - bug 496466
- promise0 = notifyMenuChanged(0);
+ promise0 = notifyMenuChanged(1);
sendChar("z");
yield promise0;
popupState = yield getPopupState();
- is(popupState.open, false, "Check popup stays closed due to cached empty result");
+ is(popupState.open, true, "Check popup stays opened due to cached empty result");
});
add_task(function* test_form11_recipes() {
yield loadRecipes({
siteRecipes: [{
"hosts": ["mochi.test:8888"],
"usernameSelector": "input[name='1']",
"passwordSelector": "input[name='2']"
@@ -761,16 +806,17 @@ add_task(function* test_form11_recipes()
pword.type = "password";
yield promiseFormsProcessed();
restoreForm();
checkACForm("", "");
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
+ doKey("down"); // skip insecure warning
doKey("down");
checkACForm("", ""); // value shouldn't update
doKey("return"); // not "enter"!
yield promiseFormsProcessed();
checkACForm("testuser10", "testpass10");
// Now test recipes with blur on the username field.
restoreForm();
@@ -788,16 +834,17 @@ add_task(function* test_form12_formless(
uname = $_(12, "uname");
pword = $_(12, "pword");
restoreForm();
checkACForm("", "");
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
+ doKey("down"); // skip insecure warning
// Trigger autocomplete
doKey("down");
checkACForm("", ""); // value shouldn't update
let processedPromise = promiseFormsProcessed();
doKey("return"); // not "enter"!
yield processedPromise;
checkACForm("testuser", "testpass");
});
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -1445,16 +1445,89 @@ extends="chrome://global/content/binding
delete this._adjustHeightOnPopupShown;
this.adjustHeight();
}
]]>
</handler>
</handlers>
</binding>
+ <binding id="autocomplete-richlistitem-insecure-field" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistitem">
+ <content align="center"
+ onoverflow="this._onOverflow();"
+ onunderflow="this._onUnderflow();">
+ <xul:image anonid="type-icon"
+ class="ac-type-icon"
+ xbl:inherits="selected,current,type"/>
+ <xul:image anonid="site-icon"
+ class="ac-site-icon"
+ xbl:inherits="src=image,selected,type"/>
+ <xul:vbox class="ac-title"
+ align="left"
+ xbl:inherits="">
+ <xul:description class="ac-text-overflow-container">
+ <xul:description anonid="title-text"
+ class="ac-title-text"
+ xbl:inherits="selected"/>
+ </xul:description>
+ <xul:label id="learnMoreLink" align="left" class="text-link"/>
+ </xul:vbox>
+ <xul:hbox anonid="tags"
+ class="ac-tags"
+ align="center"
+ xbl:inherits="selected">
+ <xul:description class="ac-text-overflow-container">
+ <xul:description anonid="tags-text"
+ class="ac-tags-text"
+ xbl:inherits="selected"/>
+ </xul:description>
+ </xul:hbox>
+ <xul:hbox anonid="separator"
+ class="ac-separator"
+ align="center"
+ xbl:inherits="selected,actiontype,type">
+ <xul:description class="ac-separator-text">—</xul:description>
+ </xul:hbox>
+ <xul:hbox class="ac-url"
+ align="center"
+ xbl:inherits="selected,actiontype">
+ <xul:description class="ac-text-overflow-container">
+ <xul:description anonid="url-text"
+ class="ac-url-text"
+ xbl:inherits="selected"/>
+ </xul:description>
+ </xul:hbox>
+ <xul:hbox class="ac-action"
+ align="center"
+ xbl:inherits="selected,actiontype">
+ <xul:description class="ac-text-overflow-container">
+ <xul:description anonid="action-text"
+ class="ac-action-text"
+ xbl:inherits="selected"/>
+ </xul:description>
+ </xul:hbox>
+ </content>
+ <implementation>
+ <constructor><![CDATA[
+ let learnMoreLink = document.getElementById("learnMoreLink");
+ learnMoreLink.setAttribute("value", this._stringBundle.GetStringFromName("insecureFieldWarningLearnMore"));
+ learnMoreLink.setAttribute("href", Services.urlFormatter.formatURLPref("app.support.baseURL") + "insecure-form-field-warning");
+ ]]></constructor>
+
+ <property name="_stringBundle">
+ <getter><![CDATA[
+ if (!this.__stringBundle) {
+ this.__stringBundle = Services.strings.createBundle("chrome://passwordmgr/locale/passwordmgr.properties");
+ }
+ return this.__stringBundle;
+ ]]></getter>
+ </property>
+ </implementation>
+ </binding>
+
<binding id="autocomplete-richlistitem" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
<content align="center"
onoverflow="this._onOverflow();"
onunderflow="this._onUnderflow();">
<xul:image anonid="type-icon"
class="ac-type-icon"
xbl:inherits="selected,current,type"/>
@@ -1682,16 +1755,19 @@ extends="chrome://global/content/binding
<method name="_setUpDescription">
<parameter name="aDescriptionElement"/>
<parameter name="aText"/>
<parameter name="aNoEmphasis"/>
<body>
<![CDATA[
// Get rid of all previous text
+ if (!aDescriptionElement) {
+ return;
+ }
while (aDescriptionElement.hasChildNodes())
aDescriptionElement.removeChild(aDescriptionElement.firstChild);
// If aNoEmphasis is specified, don't add any emphasis
if (aNoEmphasis) {
aDescriptionElement.appendChild(document.createTextNode(aText));
return;
}
--- a/toolkit/locales/en-US/chrome/passwordmgr/passwordmgr.properties
+++ b/toolkit/locales/en-US/chrome/passwordmgr/passwordmgr.properties
@@ -60,8 +60,11 @@ loginsDescriptionFiltered=The following
# This is used to show the context menu login items with their age.
# 1st string is the username for the login, 2nd is the login's age.
loginHostAge=%1$S (%2$S)
# LOCALIZATION NOTE (noUsername):
# String is used on the context menu when a login doesn't have a username.
noUsername=No username
duplicateLoginTitle=Login already exists
duplicateLogin=A duplicate login already exists.
+
+insecureFieldWarningDescription = This connection is not secure. Logins entered here could be compromised.
+insecureFieldWarningLearnMore = Learn More