Bug 1618314 - Remove import suggestions on user opt-out or automatically r=MattN

Allow 3 showing of import suggestions debouncing multiple impressions within 10 seconds. Also treat deleting a suggestion as opt-out.

Differential Revision: https://phabricator.services.mozilla.com/D87805
This commit is contained in:
Ed Lee 2020-08-31 16:33:14 +00:00
Родитель 6d2844352a
Коммит a193e2372f
5 изменённых файлов: 115 добавлений и 11 удалений

Просмотреть файл

@ -1852,6 +1852,7 @@ pref("signon.management.page.showPasswordSyncNotification", true);
pref("signon.passwordEditCapture.enabled", true);
pref("signon.showAutoCompleteFooter", true);
pref("signon.showAutoCompleteImport", "import");
pref("signon.suggestImportCount", 3);
// Enable the "Simplify Page" feature in Print Preview. This feature
// is disabled by default in toolkit.

Просмотреть файл

@ -240,14 +240,26 @@ class ImportableLearnMoreAutocompleteItem extends AutocompleteItem {
}
class ImportableLoginsAutocompleteItem extends AutocompleteItem {
constructor(browserId, hostname) {
constructor(browserId, hostname, actor) {
super("importableLogins");
this.label = browserId;
this.comment = hostname;
this._actor = actor;
// This is sent for every item (re)shown, but the parent will debounce to
// reduce the count by 1 total.
this._actor.sendAsyncMessage(
"PasswordManager:decreaseSuggestImportCount",
1
);
}
removeFromStorage() {
Services.telemetry.recordEvent("exp_import", "event", "delete", this.label);
this._actor.sendAsyncMessage(
"PasswordManager:decreaseSuggestImportCount",
100
);
}
}
@ -364,7 +376,8 @@ function LoginAutoCompleteResult(
if (!logins.length && importableBrowsers) {
this._rows.push(
...importableBrowsers.map(
browserId => new ImportableLoginsAutocompleteItem(browserId, hostname)
browserId =>
new ImportableLoginsAutocompleteItem(browserId, hostname, actor)
)
);
this._rows.push(new ImportableLearnMoreAutocompleteItem());

Просмотреть файл

@ -111,6 +111,11 @@ this.LoginHelper = {
"signon.storeWhenAutocompleteOff"
);
this.suggestImportCount = Services.prefs.getIntPref(
"signon.suggestImportCount",
0
);
if (
Services.prefs.getBoolPref(
"signon.testOnlyUserHasInteractedByPrefValue",

Просмотреть файл

@ -127,7 +127,8 @@ Services.ppmm.addMessageListener("PasswordManager:findRecipes", message => {
async function getImportableLogins(formOrigin) {
// Include the experiment state for data and UI decisions; otherwise skip
// importing if not supported or disabled.
const state = LoginHelper.showAutoCompleteImport;
const state =
LoginHelper.suggestImportCount > 0 && LoginHelper.showAutoCompleteImport;
return state
? {
browsers: await ChromeMigrationUtils.getImportableLogins(formOrigin),
@ -256,6 +257,11 @@ class LoginManagerParent extends JSWindowActorParent {
break;
}
case "PasswordManager:decreaseSuggestImportCount": {
this.decreaseSuggestImportCount(data);
break;
}
case "PasswordManager:findLogins": {
return this.sendLoginDataToChild(
context.origin,
@ -347,6 +353,36 @@ class LoginManagerParent extends JSWindowActorParent {
return undefined;
}
/**
* Update the remaining number of import suggestion impressions with debounce
* to allow multiple popups showing the "same" items to count as one.
*/
decreaseSuggestImportCount(count) {
// Delay an existing timer with a potentially larger count.
if (this._suggestImportTimer) {
this._suggestImportTimer.delay =
LoginManagerParent.SUGGEST_IMPORT_DEBOUNCE_MS;
this._suggestImportCount = Math.max(count, this._suggestImportCount);
return;
}
this._suggestImportTimer = Cc["@mozilla.org/timer;1"].createInstance(
Ci.nsITimer
);
this._suggestImportTimer.init(
() => {
this._suggestImportTimer = null;
Services.prefs.setIntPref(
"signon.suggestImportCount",
LoginHelper.suggestImportCount - this._suggestImportCount
);
},
LoginManagerParent.SUGGEST_IMPORT_DEBOUNCE_MS,
Ci.nsITimer.TYPE_ONE_SHOT
);
this._suggestImportCount = count;
}
/**
* Trigger a login form fill and send relevant data (e.g. logins and recipes)
* to the child process (LoginManagerChild).
@ -1317,6 +1353,8 @@ class LoginManagerParent extends JSWindowActorParent {
}
}
LoginManagerParent.SUGGEST_IMPORT_DEBOUNCE_MS = 10000;
XPCOMUtils.defineLazyPreferenceGetter(
LoginManagerParent,
"_repromptTimeout",

Просмотреть файл

@ -20,9 +20,26 @@ add_task(async function check_fluent_ids() {
}
});
add_task(async function import_suggestion_wizard() {
sinon.stub(ChromeMigrationUtils, "getImportableLogins").resolves(["chrome"]);
// Showing importables updates counts delayed, so adjust and cleanup.
add_task(async function test_initialize() {
const debounce = sinon
.stub(LoginManagerParent, "SUGGEST_IMPORT_DEBOUNCE_MS")
.value(0);
const importable = sinon
.stub(ChromeMigrationUtils, "getImportableLogins")
.resolves(["chrome"]);
// This makes the third autocomplete test *not* show import suggestions.
Services.prefs.setIntPref("signon.suggestImportCount", 2);
registerCleanupFunction(() => {
debounce.restore();
importable.restore();
Services.prefs.clearUserPref("signon.suggestImportCount");
});
});
add_task(async function import_suggestion_wizard() {
await BrowserTestUtils.withNewTab(
{
gBrowser,
@ -59,13 +76,9 @@ add_task(async function import_suggestion_wizard() {
await BrowserTestUtils.closeWindow(wizard);
}
);
ChromeMigrationUtils.getImportableLogins.restore();
});
add_task(async function import_suggestion_learn_more() {
sinon.stub(ChromeMigrationUtils, "getImportableLogins").resolves(["chrome"]);
await BrowserTestUtils.withNewTab(
{
gBrowser,
@ -98,6 +111,40 @@ add_task(async function import_suggestion_learn_more() {
BrowserTestUtils.removeTab(supportTab);
}
);
ChromeMigrationUtils.getImportableLogins.restore();
});
add_task(async function import_suggestion_not_shown() {
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "https://example.com" + DIRECTORY_PATH + "form_basic.html",
},
async function(browser) {
const popup = document.getElementById("PopupAutoComplete");
ok(popup, "Got popup");
let opened = false;
openACPopup(popup, browser, "#form-basic-password").then(
() => (opened = true)
);
await TestUtils.waitForCondition(() => {
EventUtils.synthesizeKey("KEY_ArrowDown");
return opened;
});
const footer = popup.querySelector(`[originaltype="loginsFooter"]`);
ok(footer, "Got footer richlistitem");
await TestUtils.waitForCondition(() => {
return !EventUtils.isHidden(footer);
}, "Waiting for footer to become visible");
ok(
!popup.querySelector(`[originaltype="importableLogins"]`),
"No importable suggestion shown"
);
await closePopup(popup);
}
);
});