зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1243722 - Introduce notUsernameSelector in passwordmgr recipes. r=MattN
Additionally, add a recipe for Facebook. MozReview-Commit-ID: 2l60v7R0TPP --HG-- extra : transplant_source : %DFw%97d%E3%ECc%E6B%A3%8D%DB%8F%A2%C8%8C9%12%EE%3A
This commit is contained in:
Родитель
7543d4f06c
Коммит
e420e52db5
|
@ -663,10 +663,17 @@ var LoginManagerContent = {
|
|||
// already logged in to the site.
|
||||
for (var i = pwFields[0].index - 1; i >= 0; i--) {
|
||||
var element = form.elements[i];
|
||||
if (LoginHelper.isUsernameFieldType(element)) {
|
||||
usernameField = element;
|
||||
break;
|
||||
if (!LoginHelper.isUsernameFieldType(element)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fieldOverrideRecipe && fieldOverrideRecipe.notUsernameSelector &&
|
||||
element.matches(fieldOverrideRecipe.notUsernameSelector)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
usernameField = element;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ this.EXPORTED_SYMBOLS = ["LoginRecipesContent", "LoginRecipesParent"];
|
|||
|
||||
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
|
||||
const REQUIRED_KEYS = ["hosts"];
|
||||
const OPTIONAL_KEYS = ["description", "passwordSelector", "pathRegex", "usernameSelector"];
|
||||
const OPTIONAL_KEYS = ["description", "notUsernameSelector", "passwordSelector", "pathRegex", "usernameSelector"];
|
||||
const SUPPORTED_KEYS = REQUIRED_KEYS.concat(OPTIONAL_KEYS);
|
||||
|
||||
Cu.importGlobalProperties(["URL"]);
|
||||
|
@ -227,7 +227,8 @@ var LoginRecipesContent = {
|
|||
let chosenRecipe = null;
|
||||
// Find the first (most-specific recipe that involves field overrides).
|
||||
for (let recipe of recipes) {
|
||||
if (!recipe.usernameSelector && !recipe.passwordSelector) {
|
||||
if (!recipe.usernameSelector && !recipe.passwordSelector &&
|
||||
!recipe.notUsernameSelector) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,11 @@
|
|||
"usernameSelector": "#accountname, input[name='loginname']",
|
||||
"passwordSelector": "#password1, input[name='loginpassword']",
|
||||
"pathRegex": "^\/account\/"
|
||||
},
|
||||
{
|
||||
"description": "Username field will be incorrectly captured in the change password form (bug 1243722)",
|
||||
"hosts": ["www.facebook.com"],
|
||||
"notUsernameSelector": "#password_strength"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -4,21 +4,53 @@
|
|||
<meta charset="utf-8">
|
||||
<title>Test for recipes overriding login fields</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<script src="pwmgr_common.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script type="application/javascript;version=1.8">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
<script>
|
||||
var chromeScript = runChecksAfterCommonInit();
|
||||
|
||||
const PWMGR_COMMON_URL = SimpleTest.getTestFileURL("pwmgr_common.js");
|
||||
let pwmgrCommonScript = SpecialPowers.loadChromeScript(PWMGR_COMMON_URL);
|
||||
let fillPromiseResolvers = [];
|
||||
|
||||
// Tell the parent to setup test logins.
|
||||
pwmgrCommonScript.sendAsyncMessage("setupParent", { selfFilling: true });
|
||||
function waitForFills(fillCount) {
|
||||
let promises = [];
|
||||
while (fillCount--) {
|
||||
let promise = new Promise(resolve => fillPromiseResolvers.push(resolve));
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
// When the setup is done, load a recipe for this test.
|
||||
pwmgrCommonScript.addMessageListener("doneSetup", function doneSetup() {
|
||||
pwmgrCommonScript.sendAsyncMessage("loadRecipes", {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
add_task(function* setup() {
|
||||
if (document.readyState !== "complete") {
|
||||
yield new Promise((resolve) => {
|
||||
document.onreadystatechange = () => {
|
||||
if (document.readyState !== "complete") {
|
||||
return;
|
||||
}
|
||||
document.onreadystatechange = null;
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById("content")
|
||||
.addEventListener("input", function handleInputEvent(evt) {
|
||||
let resolve = fillPromiseResolvers.shift();
|
||||
if (!resolve) {
|
||||
ok(false, "Too many fills");
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(evt.target);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* loadUsernamePasswordSelectorRecipes() {
|
||||
yield loadRecipes({
|
||||
siteRecipes: [{
|
||||
hosts: ["mochi.test:8888"],
|
||||
usernameSelector: "input[name='uname1']",
|
||||
|
@ -27,39 +59,80 @@ pwmgrCommonScript.addMessageListener("doneSetup", function doneSetup() {
|
|||
});
|
||||
});
|
||||
|
||||
pwmgrCommonScript.addMessageListener("loadedRecipes", function loadedRecipes() {
|
||||
// Append the form dynamically so autofill is triggered after setup above.
|
||||
document.getElementById("content").innerHTML += `
|
||||
<!-- form with recipe for the username and password -->
|
||||
<form id="form1">
|
||||
<input type="text" name="uname1" oninput="reportFill(this, true)">
|
||||
<input type="text" name="uname2" oninput="reportFill(this, false)">
|
||||
<input type="password" name="pword1" oninput="reportFill(this, false)">
|
||||
<input type="password" name="pword2" oninput="reportFill(this, true)">
|
||||
</form>`;
|
||||
add_task(function* testOverriddingFields() {
|
||||
// Insert the form dynamically so autofill is triggered after setup above.
|
||||
document.getElementById("content").innerHTML = `
|
||||
<!-- form with recipe for the username and password -->
|
||||
<form id="form1">
|
||||
<input type="text" name="uname1" data-expected="true">
|
||||
<input type="text" name="uname2" data-expected="false">
|
||||
<input type="password" name="pword1" data-expected="false">
|
||||
<input type="password" name="pword2" data-expected="true">
|
||||
</form>`;
|
||||
|
||||
let elements = yield waitForFills(2);
|
||||
for (let element of elements) {
|
||||
is(element.dataset.expected, "true", `${element.name} was filled`);
|
||||
}
|
||||
});
|
||||
|
||||
const EXPECTED_FILLS = 4;
|
||||
let fillCount = 0;
|
||||
add_task(function* testDefaultHeuristics() {
|
||||
// Insert the form dynamically so autofill is triggered after setup above.
|
||||
document.getElementById("content").innerHTML = `
|
||||
<!-- Fallback to the default heuristics since the selectors don't match -->
|
||||
<form id="form2">
|
||||
<input type="text" name="uname3" data-expected="false">
|
||||
<input type="text" name="uname4" data-expected="true">
|
||||
<input type="password" name="pword3" data-expected="true">
|
||||
<input type="password" name="pword4" data-expected="false">
|
||||
</form>`;
|
||||
|
||||
function reportFill(element, expected) {
|
||||
ok(expected, `${element.name} was filled`);
|
||||
if (++fillCount == EXPECTED_FILLS) {
|
||||
pwmgrCommonScript.destroy();
|
||||
SimpleTest.finish();
|
||||
} else if (fillCount == 2) {
|
||||
document.getElementById("content").innerHTML = `
|
||||
<!-- Fallback to the default heuristics since the selectors don't match -->
|
||||
<form id="form2">
|
||||
<input type="text" name="uname3" oninput="reportFill(this, false)">
|
||||
<input type="text" name="uname4" oninput="reportFill(this, true)">
|
||||
<input type="password" name="pword3" oninput="reportFill(this, true)">
|
||||
<input type="password" name="pword4" oninput="reportFill(this, false)">
|
||||
</form>`;
|
||||
} else if (fillCount > EXPECTED_FILLS) {
|
||||
ok(false, "Too many fills");
|
||||
let elements = yield waitForFills(2);
|
||||
for (let element of elements) {
|
||||
is(element.dataset.expected, "true", `${element.name} was filled`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* loadNotUsernameSelectorRecipes() {
|
||||
yield resetRecipes();
|
||||
yield loadRecipes({
|
||||
siteRecipes: [{
|
||||
hosts: ["mochi.test:8888"],
|
||||
notUsernameSelector: "input[name='not_uname1']"
|
||||
}],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* testNotUsernameField() {
|
||||
document.getElementById("content").innerHTML = `
|
||||
<!-- The field matching notUsernameSelector should be skipped -->
|
||||
<form id="form3">
|
||||
<input type="text" name="uname5" data-expected="true">
|
||||
<input type="text" name="not_uname1" data-expected="false">
|
||||
<input type="password" name="pword5" data-expected="true">
|
||||
</form>`;
|
||||
|
||||
let elements = yield waitForFills(2);
|
||||
for (let element of elements) {
|
||||
is(element.dataset.expected, "true", `${element.name} was filled`);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* testNotUsernameFieldNoUsername() {
|
||||
document.getElementById("content").innerHTML = `
|
||||
<!-- The field matching notUsernameSelector should be skipped.
|
||||
No username field should be found and filled in this case -->
|
||||
<form id="form4">
|
||||
<input type="text" name="not_uname1" data-expected="false">
|
||||
<input type="password" name="pword6" data-expected="true">
|
||||
</form>`;
|
||||
|
||||
let elements = yield waitForFills(1);
|
||||
for (let element of elements) {
|
||||
is(element.dataset.expected, "true", `${element.name} was filled`);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<p id="display"></p>
|
||||
|
|
|
@ -282,6 +282,26 @@ function promiseFormsProcessed(expectedCount = 1) {
|
|||
});
|
||||
}
|
||||
|
||||
function loadRecipes(recipes) {
|
||||
return new Promise(resolve => {
|
||||
chromeScript.addMessageListener("loadedRecipes", function loaded() {
|
||||
chromeScript.removeMessageListener("loadedRecipes", loaded);
|
||||
resolve(recipes);
|
||||
});
|
||||
chromeScript.sendAsyncMessage("loadRecipes", recipes);
|
||||
});
|
||||
}
|
||||
|
||||
function resetRecipes() {
|
||||
return new Promise(resolve => {
|
||||
chromeScript.addMessageListener("recipesReset", function reset() {
|
||||
chromeScript.removeMessageListener("recipesReset", reset);
|
||||
resolve();
|
||||
});
|
||||
chromeScript.sendAsyncMessage("resetRecipes");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a function synchronously in the parent process and destroy it in the test cleanup function.
|
||||
* @param {Function|String} aFunctionOrURL - either a function that will be stringified and run
|
||||
|
@ -310,6 +330,7 @@ function runChecksAfterCommonInit(aFunction = null) {
|
|||
pwmgrCommonScript.addMessageListener("registerRunTests", () => registerRunTests());
|
||||
}
|
||||
pwmgrCommonScript.sendSyncMessage("setupParent");
|
||||
return pwmgrCommonScript;
|
||||
}
|
||||
|
||||
// Code to run when loaded as a chrome script in tests via loadChromeScript
|
||||
|
@ -334,6 +355,13 @@ if (this.addMessageListener) {
|
|||
sendAsyncMessage("loadedRecipes", recipes);
|
||||
}));
|
||||
|
||||
addMessageListener("resetRecipes", Task.async(function* resetRecipes() {
|
||||
let { LoginManagerParent } = Cu.import("resource://gre/modules/LoginManagerParent.jsm", {});
|
||||
let recipeParent = yield LoginManagerParent.recipeParentPromise;
|
||||
yield recipeParent.reset();
|
||||
sendAsyncMessage("recipesReset");
|
||||
}));
|
||||
|
||||
var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
|
||||
globalMM.addMessageListener("RemoteLogins:onFormSubmit", function onFormSubmit(message) {
|
||||
sendAsyncMessage("formSubmissionProcessed", message.data, message.objects);
|
||||
|
|
Загрузка…
Ссылка в новой задаче