Bug 1477106 - Use a pref to set default checkedness for "Save card to Firefox" and "Save address to Firefox" checkboxes. r?Jaws draft
authorSam Foster <sfoster@mozilla.com>
Thu, 26 Jul 2018 15:36:07 -0700
changeset 825573 1e4ddd47832652dedac2cd8fe312171f3e1a624e
parent 825566 3cb90f16402bc5e1203e2771dc93553b8377fa40
push id118136
push userbmo:sfoster@mozilla.com
push dateThu, 02 Aug 2018 00:31:39 +0000
reviewersJaws
bugs1477106
milestone63.0a1
Bug 1477106 - Use a pref to set default checkedness for "Save card to Firefox" and "Save address to Firefox" checkboxes. r?Jaws * Get default checkedness for the card persist checkbox from a new pref: dom.payments.defaults.saveCreditCard * Get default checkedness for the address persist checkbox from a new pref: dom.payments.defaults.saveAddress * Remember checked state from card page (only) so it doesnt change back when returning from add/edit address page * Fix up card form tests to verify behavior in private/not-private windows, pref value, user opt-in for persisting the card * Fix up address form tests to not conflate private/not-private windows with expected address persisting behaviour MozReview-Commit-ID: GXMjqStlnlu
browser/components/payments/content/paymentDialogFrameScript.js
browser/components/payments/res/containers/address-form.js
browser/components/payments/res/containers/basic-card-form.js
browser/components/payments/res/unprivileged-fallbacks.js
browser/components/payments/test/browser/browser_address_edit.js
browser/components/payments/test/browser/browser_card_edit.js
browser/components/payments/test/browser/head.js
browser/components/payments/test/mochitest/test_basic_card_form.html
modules/libpref/init/all.js
--- a/browser/components/payments/content/paymentDialogFrameScript.js
+++ b/browser/components/payments/content/paymentDialogFrameScript.js
@@ -13,24 +13,28 @@
  * converted into messages of the same name.
  *
  * Business logic should stay out of this shim.
  */
 
 "use strict";
 
 /* eslint-env mozilla/frame-script */
+/* global Services */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "FormAutofill",
                                "resource://formautofill/FormAutofill.jsm");
 ChromeUtils.defineModuleGetter(this, "FormAutofillUtils",
                                "resource://formautofill/FormAutofillUtils.jsm");
 
+const SAVE_CREDITCARD_DEFAULT_PREF = "dom.payments.defaults.saveCreditCard";
+const SAVE_ADDRESS_DEFAULT_PREF = "dom.payments.defaults.saveAddress";
+
 let PaymentFrameScript = {
   init() {
     XPCOMUtils.defineLazyGetter(this, "log", () => {
       let {ConsoleAPI} = ChromeUtils.import("resource://gre/modules/Console.jsm", {});
       return new ConsoleAPI({
         maxLogLevelPref: "dom.payments.loglevel",
         prefix: "paymentDialogFrameScript",
       });
@@ -79,16 +83,26 @@ let PaymentFrameScript = {
       isCCNumber(value) {
         return FormAutofillUtils.isCCNumber(value);
       },
 
       getFormFormat(country) {
         let format = FormAutofillUtils.getFormFormat(country);
         return Cu.cloneInto(format, waivedContent);
       },
+
+      getDefaultPreferences() {
+        let prefValues = {
+          saveCreditCardDefaultChecked:
+            Services.prefs.getBoolPref(SAVE_CREDITCARD_DEFAULT_PREF, false),
+          saveAddressDefaultChecked:
+            Services.prefs.getBoolPref(SAVE_ADDRESS_DEFAULT_PREF, false),
+        };
+        return prefValues;
+      },
     };
     waivedContent.PaymentDialogUtils = Cu.cloneInto(PaymentDialogUtils, waivedContent, {
       cloneFunctions: true,
     });
   },
 
   sendToChrome({detail}) {
     let {messageType} = detail;
--- a/browser/components/payments/res/containers/address-form.js
+++ b/browser/components/payments/res/containers/address-form.js
@@ -138,19 +138,21 @@ export default class AddressForm extends
     if (editing) {
       record = addresses[addressPage.guid];
       if (!record) {
         throw new Error("Trying to edit a non-existing address: " + addressPage.guid);
       }
       // When editing an existing record, prevent changes to persistence
       this.persistCheckbox.hidden = true;
     } else {
-      // Adding a new record: default persistence to checked when in a not-private session
+      let defaults = PaymentDialogUtils.getDefaultPreferences();
+      // Adding a new record: default persistence to the pref value when in a not-private session
       this.persistCheckbox.hidden = false;
-      this.persistCheckbox.checked = !state.isPrivate;
+      this.persistCheckbox.checked = state.isPrivate ? false :
+                                                       defaults.saveAddressDefaultChecked;
     }
 
     this.formHandler.loadRecord(record);
 
     // Add validation to some address fields
     for (let formElement of this.form.elements) {
       let container = formElement.closest(`#${formElement.id}-container`);
       if (formElement.localName == "button" || !container) {
--- a/browser/components/payments/res/containers/basic-card-form.js
+++ b/browser/components/payments/res/containers/basic-card-form.js
@@ -148,19 +148,27 @@ export default class BasicCardForm exten
       this.persistCheckbox.hidden = true;
     } else {
       this.pageTitleHeading.textContent = this.dataset.addBasicCardTitle;
       // Use a currently selected shipping address as the default billing address
       record.billingAddressGUID = basicCardPage.billingAddressGUID;
       if (!record.billingAddressGUID && selectedShippingAddress) {
         record.billingAddressGUID = selectedShippingAddress;
       }
-      // Adding a new record: default persistence to checked when in a not-private session
+
+      let defaults = PaymentDialogUtils.getDefaultPreferences();
+      // Adding a new record: default persistence to pref value when in a not-private session
       this.persistCheckbox.hidden = false;
-      this.persistCheckbox.checked = !state.isPrivate;
+      if (basicCardPage.hasOwnProperty("persistCheckboxValue")) {
+        // returning to this page, use previous checked state
+        this.persistCheckbox.checked = basicCardPage.persistCheckboxValue;
+      } else {
+        this.persistCheckbox.checked = state.isPrivate ? false :
+                                                         defaults.saveCreditCardDefaultChecked;
+      }
     }
 
     this.formHandler.loadRecord(record, addresses, basicCardPage.preserveFieldValues);
 
     this.form.querySelector(".billingAddressRow").hidden = false;
 
     let billingAddressSelect = this.form.querySelector("#billingAddressGUID");
     if (basicCardPage.billingAddressGUID) {
@@ -212,16 +220,17 @@ export default class BasicCardForm exten
           "address-page": {
             guid: null,
             selectedStateKey: ["basic-card-page", "billingAddressGUID"],
             title: this.dataset.billingAddressTitleAdd,
           },
           "basic-card-page": {
             preserveFieldValues: true,
             guid: basicCardPage.guid,
+            persistCheckboxValue: this.persistCheckbox.checked,
           },
         };
         let billingAddressGUID = this.form.querySelector("#billingAddressGUID");
         let selectedOption = billingAddressGUID.selectedOptions.length &&
                              billingAddressGUID.selectedOptions[0];
         if (evt.target == this.addressEditLink && selectedOption && selectedOption.value) {
           nextState["address-page"].title = this.dataset.billingAddressTitleEdit;
           nextState["address-page"].guid = selectedOption.value;
@@ -250,16 +259,17 @@ export default class BasicCardForm exten
           if (request.paymentOptions.requestShipping) {
             addressPageState = Object.assign({}, addressPage, {guid: selectedShippingAddress});
           } else {
             addressPageState =
               Object.assign({}, addressPage, {guid: basicCardPage.billingAddressGUID});
           }
 
           let basicCardPageState = Object.assign({}, basicCardPage, {preserveFieldValues: true});
+          delete basicCardPageState.persistCheckboxValue;
 
           Object.assign(nextState, {
             "address-page": addressPageState,
             "basic-card-page": basicCardPageState,
           });
         }
 
         this.requestStore.setState(nextState);
--- a/browser/components/payments/res/unprivileged-fallbacks.js
+++ b/browser/components/payments/res/unprivileged-fallbacks.js
@@ -42,9 +42,16 @@ var PaymentDialogUtils = {
         {fieldId: "address-level1"},
         {fieldId: "postal-code"},
       ],
       // The following values come from addressReferences.js and should not be changed.
       /* eslint-disable-next-line max-len */
       "postalCodePattern": country == "US" ? "(\\d{5})(?:[ \\-](\\d{4}))?" : "[ABCEGHJKLMNPRSTVXY]\\d[ABCEGHJ-NPRSTV-Z] ?\\d[ABCEGHJ-NPRSTV-Z]\\d",
     };
   },
+  getDefaultPreferences() {
+    let prefValues = {
+      saveCreditCardDefaultChecked: false,
+      saveAddressDefaultChecked: true,
+    };
+    return prefValues;
+  },
 };
--- a/browser/components/payments/test/browser/browser_address_edit.js
+++ b/browser/components/payments/test/browser/browser_address_edit.js
@@ -43,49 +43,49 @@ add_task(async function test_add_link() 
       } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
       let {tempAddresses, savedAddresses} = await PTU.DialogContentUtils.getCurrentState(content);
       is(Object.keys(tempAddresses).length, 0, "No temporary addresses at the start of test");
       is(Object.keys(savedAddresses).length, 1, "1 saved address at the start of test");
     });
 
     let testOptions = [
-      { isTemporary: false, expectPersist: true },
-      { isTemporary: true, expectPersist: false },
+      { setPersistCheckedValue: true, expectPersist: true },
+      { setPersistCheckedValue: false, expectPersist: false },
     ];
     let newAddress = PTU.Addresses.TimBL2;
 
     for (let options of testOptions) {
       let shippingAddressChangePromise = ContentTask.spawn(browser, {
         eventName: "shippingaddresschange",
       }, PTU.ContentTasks.awaitPaymentRequestEventPromise);
 
       await manuallyAddAddress(frame, newAddress, options);
       await shippingAddressChangePromise;
       info("got shippingaddresschange event");
 
-      await spawnPaymentDialogTask(frame, async ({address, isTemporary, prefilledGuids}) => {
+      await spawnPaymentDialogTask(frame, async ({address, options, prefilledGuids}) => {
         let {
           PaymentTestUtils: PTU,
         } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
         let newAddresses = await PTU.DialogContentUtils.waitForState(content, (state) => {
           return state.tempAddresses && state.savedAddresses;
         });
-        let colnName = isTemporary ? "tempAddresses" : "savedAddresses";
+        let colnName = options.expectPersist ? "savedAddresses" : "tempAddresses";
         // remove any pre-filled entries
         delete newAddresses[colnName][prefilledGuids.address1GUID];
 
         let addressGUIDs = Object.keys(newAddresses[colnName]);
         is(addressGUIDs.length, 1, "Check there is one address");
         let resultAddress = newAddresses[colnName][addressGUIDs[0]];
         for (let [key, val] of Object.entries(address)) {
           is(resultAddress[key], val, "Check " + key);
         }
-      }, {address: newAddress, isTemporary: options.isTemporary, prefilledGuids});
+      }, {address: newAddress, options, prefilledGuids});
     }
 
     spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
     await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
   });
   await cleanupFormAutofillStorage();
 });
 
--- a/browser/components/payments/test/browser/browser_card_edit.js
+++ b/browser/components/payments/test/browser/browser_card_edit.js
@@ -17,21 +17,23 @@ async function add_link(aOptions = {}) {
   }, async browser => {
     let {win, frame} =
       await setupPaymentDialog(browser, {
         methodData: [PTU.MethodData.basicCard],
         details: Object.assign({}, PTU.Details.total60USD),
         merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
       }
     );
-
+    info("add_link, aOptions: " + JSON.stringify(aOptions, null, 2));
     await navigateToAddCardPage(frame);
+    info(`add_link, from the add card page,
+          verifyPersistCheckbox with expectPersist: ${aOptions.expectDefaultCardPersist}`);
     await verifyPersistCheckbox(frame, {
       checkboxSelector: "basic-card-form .persist-checkbox",
-      expectPersist: !aOptions.isPrivate,
+      expectPersist: aOptions.expectDefaultCardPersist,
     });
     await spawnPaymentDialogTask(frame, async (testArgs = {}) => {
       let {
         PaymentTestUtils: PTU,
       } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
       let state = await PTU.DialogContentUtils.waitForState(content, (state) => {
         return Object.keys(state.savedBasicCards).length == 1 &&
@@ -40,20 +42,26 @@ async function add_link(aOptions = {}) {
 
       let title = content.document.querySelector("basic-card-form h2");
       is(title.textContent, "Add Credit Card", "Add title should be set");
 
       is(state.isPrivate, testArgs.isPrivate,
          "isPrivate flag has expected value when shown from a private/non-private session");
     }, aOptions);
 
-    await fillInCardForm(frame, PTU.BasicCards.JaneMasterCard, {
-      isTemporary: aOptions.isPrivate,
+    let cardOptions = Object.assign({}, {
       checkboxSelector: "basic-card-form .persist-checkbox",
+      expectPersist: aOptions.expectCardPersist,
     });
+    if (aOptions.hasOwnProperty("setCardPersistCheckedValue")) {
+      cardOptions.setPersistCheckedValue = aOptions.setCardPersistCheckedValue;
+    }
+    await fillInCardForm(frame, PTU.BasicCards.JaneMasterCard, cardOptions);
+
+    await verifyPersistCheckbox(frame, cardOptions);
 
     await spawnPaymentDialogTask(frame, async (testArgs = {}) => {
       let billingAddressSelect = content.document.querySelector("#billingAddressGUID");
       ok(content.isVisible(billingAddressSelect),
          "The billing address selector should always be visible");
       is(billingAddressSelect.childElementCount, 2,
          "Only 2 child options should exist by default");
       is(billingAddressSelect.children[0].value, "",
@@ -61,18 +69,22 @@ async function add_link(aOptions = {}) {
       ok(billingAddressSelect.children[1].value, "",
          "The 2nd option is the prefilled address and should be truthy");
     }, aOptions);
 
     let addressOptions = Object.assign({}, aOptions, {
       addLinkSelector: ".billingAddressRow .add-link",
       checkboxSelector: "#address-page .persist-checkbox",
       initialPageId: "basic-card-page",
-      expectPersist: !aOptions.isPrivate,
+      expectPersist: aOptions.expectDefaultAddressPersist,
     });
+    if (aOptions.hasOwnProperty("setAddressPersistCheckedValue")) {
+      addressOptions.setPersistCheckedValue = aOptions.setAddressPersistCheckedValue;
+    }
+
     await navigateToAddAddressPage(frame, addressOptions);
 
     await spawnPaymentDialogTask(frame, async (testArgs = {}) => {
       let {
         PaymentTestUtils: PTU,
       } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
       let title = content.document.querySelector("basic-card-form h2");
@@ -91,17 +103,17 @@ async function add_link(aOptions = {}) {
       let addressBackButton = content.document.querySelector("address-form .back-button");
       addressBackButton.click();
 
       await PTU.DialogContentUtils.waitForState(content, (state) => {
         let total = Object.keys(state.savedBasicCards).length +
                     Object.keys(state.tempBasicCards).length;
         return state.page.id == "basic-card-page" && !state["basic-card-page"].guid &&
                total == 1;
-      }, "Check basic-card page, but card should not be saved and no addresses present");
+      }, "Check basic-card page, but card should not be saved and no new addresses present");
 
       is(title.textContent, "Add Credit Card", "Add title should be still be on credit card page");
 
       for (let [key, val] of Object.entries(card)) {
         let field = content.document.getElementById(key);
         is(field.value, val, "Field should still have previous value entered");
         ok(!field.disabled, "Fields should still be enabled for editing");
       }
@@ -123,17 +135,17 @@ async function add_link(aOptions = {}) {
       let state = await PTU.DialogContentUtils.waitForState(content, (state) => {
         return state.page.id == "basic-card-page" && !state["basic-card-page"].guid;
       }, "Check address was added and we're back on basic-card page (add)");
 
       let addressCount = Object.keys(state.savedAddresses).length +
                          Object.keys(state.tempAddresses).length;
       is(addressCount, 2, "Check address was added");
 
-      let addressColn = testArgs.isPrivate ? state.tempAddresses : state.savedAddresses;
+      let addressColn = testArgs.expectAddressPersist ? state.savedAddresses : state.tempAddresses;
 
       ok(state["basic-card-page"].preserveFieldValues,
          "preserveFieldValues should be set when coming back from address-page");
 
       ok(state["basic-card-page"].billingAddressGUID,
          "billingAddressGUID should be set when coming back from address-page");
 
       let billingAddressSelect = content.document.querySelector("#billingAddressGUID");
@@ -142,60 +154,63 @@ async function add_link(aOptions = {}) {
          "Three options should exist in the billingAddressSelect");
       let selectedOption =
         billingAddressSelect.children[billingAddressSelect.selectedIndex];
       let selectedAddressGuid = selectedOption.value;
       let lastAddress = Object.values(addressColn)[Object.keys(addressColn).length - 1];
       is(selectedAddressGuid, lastAddress.guid, "The select should have the new address selected");
     }, aOptions);
 
-    await fillInCardForm(frame, PTU.BasicCards.JaneMasterCard, {
-      isTemporary: aOptions.isPrivate,
+    cardOptions = Object.assign({}, {
       checkboxSelector: "basic-card-form .persist-checkbox",
+      expectPersist: aOptions.expectCardPersist,
     });
 
+    await verifyPersistCheckbox(frame, cardOptions);
+
     await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.clickPrimaryButton);
 
     await spawnPaymentDialogTask(frame, async (testArgs = {}) => {
       let {
         PaymentTestUtils: PTU,
       } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
       await PTU.DialogContentUtils.waitForState(content, (state) => {
         return state.page.id == "payment-summary";
       }, "Check we are back on the summary page");
     });
 
-
     await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.setSecurityCode, {
       securityCode: "123",
     });
 
     await spawnPaymentDialogTask(frame, async (testArgs = {}) => {
       let {
         PaymentTestUtils: PTU,
       } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
       let {prefilledGuids} = testArgs;
       let card = Object.assign({}, PTU.BasicCards.JaneMasterCard);
       let state = await PTU.DialogContentUtils.getCurrentState(content);
 
       let cardCount = Object.keys(state.savedBasicCards).length +
                          Object.keys(state.tempBasicCards).length;
       is(cardCount, 2, "Card was added");
-      if (testArgs.isPrivate) {
+      if (testArgs.expectCardPersist) {
+        is(Object.keys(state.tempBasicCards).length, 0, "No temporary cards addded");
+        is(Object.keys(state.savedBasicCards).length, 2, "New card was saved");
+      } else {
         is(Object.keys(state.tempBasicCards).length, 1, "Card was added temporarily");
         is(Object.keys(state.savedBasicCards).length, 1, "No change to saved cards");
-      } else {
-        is(Object.keys(state.tempBasicCards).length, 0, "No temporary cards addded");
-        is(Object.keys(state.savedBasicCards).length, 2, "New card was saved");
       }
 
-      let cardCollection = testArgs.isPrivate ? state.tempBasicCards : state.savedBasicCards;
-      let addressCollection = testArgs.isPrivate ? state.tempAddresses : state.savedAddresses;
+      let cardCollection = testArgs.expectCardPersist ? state.savedBasicCards :
+                                                        state.tempBasicCards;
+      let addressCollection = testArgs.expectAddressPersist ? state.savedAddresses :
+                                                              state.tempAddresses;
       let savedCardGUID =
         Object.keys(cardCollection).find(key => key != prefilledGuids.card1GUID);
       let savedAddressGUID =
         Object.keys(addressCollection).find(key => key != prefilledGuids.address1GUID);
       let savedCard = savedCardGUID && cardCollection[savedCardGUID];
 
       // we should never have an un-masked cc-number in the state:
       ok(Object.values(cardCollection).every(card => card["cc-number"].startsWith("************")),
@@ -228,30 +243,130 @@ async function add_link(aOptions = {}) {
                                              "Check response billing address");
 
     await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
   });
 }
 
 add_task(async function test_add_link() {
   let prefilledGuids = await setup([PTU.Addresses.TimBL], [PTU.BasicCards.JohnDoe]);
+  let defaultPersist = Services.prefs.getBoolPref(SAVE_CREDITCARD_DEFAULT_PREF);
+
+  is(defaultPersist, false, `Expect ${SAVE_CREDITCARD_DEFAULT_PREF} to default to false`);
+  info("Calling add_link from test_add_link");
   await add_link({
     isPrivate: false,
+    expectDefaultCardPersist: false,
+    expectCardPersist: false,
+    expectDefaultAddressPersist: true,
+    expectAddressPersist: true,
     prefilledGuids,
   });
 });
 
 add_task(async function test_private_add_link() {
   let prefilledGuids = await setup([PTU.Addresses.TimBL], [PTU.BasicCards.JohnDoe]);
+  info("Calling add_link from test_private_add_link");
+  await add_link({
+    isPrivate: true,
+    expectDefaultCardPersist: false,
+    expectCardPersist: false,
+    expectDefaultAddressPersist: false,
+    expectAddressPersist: false,
+    prefilledGuids,
+  });
+});
+
+add_task(async function test_persist_prefd_on_add_link() {
+  let prefilledGuids = await setup([PTU.Addresses.TimBL], [PTU.BasicCards.JohnDoe]);
+  Services.prefs.setBoolPref(SAVE_CREDITCARD_DEFAULT_PREF, true);
+
+  info("Calling add_link from test_persist_prefd_on_add_link");
+  await add_link({
+    isPrivate: false,
+    expectDefaultCardPersist: true,
+    expectCardPersist: true,
+    expectDefaultAddressPersist: true,
+    expectAddressPersist: true,
+    prefilledGuids,
+  });
+  Services.prefs.clearUserPref(SAVE_CREDITCARD_DEFAULT_PREF);
+});
+
+add_task(async function test_private_persist_prefd_on_add_link() {
+  let prefilledGuids = await setup([PTU.Addresses.TimBL], [PTU.BasicCards.JohnDoe]);
+  Services.prefs.setBoolPref(SAVE_CREDITCARD_DEFAULT_PREF, true);
+
+  info("Calling add_link from test_private_persist_prefd_on_add_link");
+  // in private window, even when the pref is set true,
+  // we should still default to not saving credit-card info
   await add_link({
     isPrivate: true,
+    expectDefaultCardPersist: false,
+    expectCardPersist: false,
+    expectDefaultAddressPersist: false,
+    expectAddressPersist: false,
+    prefilledGuids,
+  });
+  Services.prefs.clearUserPref(SAVE_CREDITCARD_DEFAULT_PREF);
+});
+
+add_task(async function test_optin_persist_add_link() {
+  let prefilledGuids = await setup([PTU.Addresses.TimBL], [PTU.BasicCards.JohnDoe]);
+  let defaultPersist = Services.prefs.getBoolPref(SAVE_CREDITCARD_DEFAULT_PREF);
+
+  is(defaultPersist, false, `Expect ${SAVE_CREDITCARD_DEFAULT_PREF} to default to false`);
+  info("Calling add_link from test_add_link");
+  // verify that explicit opt-in by checking the box results in the record being saved
+  await add_link({
+    isPrivate: false,
+    expectDefaultCardPersist: false,
+    setCardPersistCheckedValue: true,
+    expectCardPersist: true,
+    expectDefaultAddressPersist: true,
+    expectAddressPersist: true,
     prefilledGuids,
   });
 });
 
+add_task(async function test_optin_private_persist_add_link() {
+  let prefilledGuids = await setup([PTU.Addresses.TimBL], [PTU.BasicCards.JohnDoe]);
+  let defaultPersist = Services.prefs.getBoolPref(SAVE_CREDITCARD_DEFAULT_PREF);
+
+  is(defaultPersist, false, `Expect ${SAVE_CREDITCARD_DEFAULT_PREF} to default to false`);
+  // verify that explicit opt-in for the card only from a private window results
+  // in the record being saved
+  await add_link({
+    isPrivate: true,
+    expectDefaultCardPersist: false,
+    setCardPersistCheckedValue: true,
+    expectCardPersist: true,
+    expectDefaultAddressPersist: false,
+    expectAddressPersist: false,
+    prefilledGuids,
+  });
+});
+
+add_task(async function test_opt_out_persist_prefd_on_add_link() {
+  let prefilledGuids = await setup([PTU.Addresses.TimBL], [PTU.BasicCards.JohnDoe]);
+  Services.prefs.setBoolPref(SAVE_CREDITCARD_DEFAULT_PREF, true);
+
+  // set the pref to default to persist creditcards, but manually uncheck the checkbox
+  await add_link({
+    isPrivate: false,
+    expectDefaultCardPersist: true,
+    setCardPersistCheckedValue: false,
+    expectCardPersist: false,
+    expectDefaultAddressPersist: true,
+    expectAddressPersist: true,
+    prefilledGuids,
+  });
+  Services.prefs.clearUserPref(SAVE_CREDITCARD_DEFAULT_PREF);
+});
+
 add_task(async function test_edit_link() {
   // add an address and card linked to this address
   let prefilledGuids = await setup([PTU.Addresses.TimBL]);
   {
     let card = Object.assign({}, PTU.BasicCards.JohnDoe,
                              { billingAddressGUID: prefilledGuids.address1GUID });
     await addCardRecord(card);
   }
--- a/browser/components/payments/test/browser/head.js
+++ b/browser/components/payments/test/browser/head.js
@@ -6,16 +6,18 @@
     args: "none",
   }],
 */
 
 
 const BLANK_PAGE_PATH = "/browser/browser/components/payments/test/browser/blank_page.html";
 const BLANK_PAGE_URL = "https://example.com" + BLANK_PAGE_PATH;
 const RESPONSE_TIMEOUT_PREF = "dom.payments.response.timeout";
+const SAVE_CREDITCARD_DEFAULT_PREF = "dom.payments.defaults.saveCreditCard";
+const SAVE_ADDRESS_DEFAULT_PREF = "dom.payments.defaults.saveAddress";
 
 const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"]
                      .getService(Ci.nsIPaymentRequestService);
 const paymentUISrv = Cc["@mozilla.org/dom/payments/payment-ui-service;1"]
                      .getService().wrappedJSObject;
 const {formAutofillStorage} = ChromeUtils.import(
   "resource://formautofill/FormAutofillStorage.jsm", {});
 const {PaymentTestUtils: PTU} = ChromeUtils.import(
@@ -328,16 +330,18 @@ add_task(async function setup_head() {
     }
     ok(false, msg.message || msg.errorMessage);
   });
   await setupFormAutofillStorage();
   registerCleanupFunction(function cleanup() {
     paymentSrv.cleanup();
     cleanupFormAutofillStorage();
     Services.prefs.clearUserPref(RESPONSE_TIMEOUT_PREF);
+    Services.prefs.clearUserPref(SAVE_CREDITCARD_DEFAULT_PREF);
+    Services.prefs.clearUserPref(SAVE_ADDRESS_DEFAULT_PREF);
     SpecialPowers.postConsoleSentinel();
   });
 });
 
 function deepClone(obj) {
   return JSON.parse(JSON.stringify(obj));
 }
 
@@ -385,38 +389,39 @@ async function fillInAddressForm(frame, 
       if (!field) {
         ok(false, `${key} field not found`);
       }
       field.value = val;
     }
     let persistCheckbox = Cu.waiveXrays(
         content.document.querySelector(options.checkboxSelector));
     // only touch the checked state if explicitly told to in the options
-    if (options.hasOwnProperty("isTemporary")) {
-      info(`fillInAddressForm: toggling persistCheckbox as isTemporary is: ${options.isTemporary}`);
-      persistCheckbox.checked = !options.isTemporary;
+    if (options.hasOwnProperty("setPersistCheckedValue")) {
+      info("fillInCardForm: Manually setting the persist checkbox checkedness to: " +
+            options.setPersistCheckedValue);
+      Cu.waiveXrays(persistCheckbox).checked = options.setPersistCheckedValue;
     }
     info(`fillInAddressForm, persistCheckbox.checked: ${persistCheckbox.checked}`);
   }, {address: aAddress, options: aOptions});
 }
 
 async function verifyPersistCheckbox(frame, aOptions = {}) {
   await spawnPaymentDialogTask(frame, async (args) => {
     let {options = {}} = args;
     // ensure card/address is persisted or not based on the temporary option given
-    info("verifyPersistCheckbox");
+    info("verifyPersistCheckbox, got options: " + JSON.stringify(options));
     let persistCheckbox = Cu.waiveXrays(
         content.document.querySelector(options.checkboxSelector));
 
     if (options.isEditing) {
-      ok(persistCheckbox.hidden, "checkbox should be hidden when editing an existing address");
+      ok(persistCheckbox.hidden, "checkbox should be hidden when editing a record");
     } else {
-      ok(!persistCheckbox.hidden, "checkbox should be visible when adding a new address");
+      ok(!persistCheckbox.hidden, "checkbox should be visible when adding a new record");
       is(persistCheckbox.checked, options.expectPersist,
-         "persist checkbox is in the expected state");
+         `persist checkbox state is expected to be ${options.expectPersist}`);
     }
   }, {options: aOptions});
 }
 
 async function submitAddressForm(frame, aAddress, aOptions = {}) {
   await spawnPaymentDialogTask(frame, async (args) => {
     let {options = {}} = args;
     let {
@@ -453,17 +458,19 @@ async function submitAddressForm(frame, 
 async function manuallyAddAddress(frame, aAddress, aOptions = {}) {
   let options = Object.assign({
     expectPersist: true,
     isEditing: false,
   }, aOptions, {
     checkboxSelector: "#address-page .persist-checkbox",
   });
   await navigateToAddAddressPage(frame);
+  info("manuallyAddAddress, fill in address form with options: " + JSON.stringify(options));
   await fillInAddressForm(frame, aAddress, options);
+  info("manuallyAddAddress, verifyPersistCheckbox with options: " + JSON.stringify(options));
   await verifyPersistCheckbox(frame, options);
   await submitAddressForm(frame, aAddress, options);
 }
 
 async function navigateToAddCardPage(frame, aOptions = {
   addLinkSelector: "payment-method-picker .add-link",
 }) {
   await spawnPaymentDialogTask(frame, async (options) => {
@@ -506,18 +513,20 @@ async function fillInCardForm(frame, aCa
       EventUtils.synthesizeKey(fillValue, {}, content.window);
       ok(field.value, fillValue, `${key} value is correct after synthesizeKey`);
     }
 
     info([...content.document.getElementById("cc-exp-year").options].map(op => op.label).join(","));
 
     let persistCheckbox = content.document.querySelector(options.checkboxSelector);
     // only touch the checked state if explicitly told to in the options
-    if (options.hasOwnProperty("isTemporary")) {
-      Cu.waiveXrays(persistCheckbox).checked = !options.isTemporary;
+    if (options.hasOwnProperty("setPersistCheckedValue")) {
+      info("fillInCardForm: Manually setting the persist checkbox checkedness to: " +
+            options.setPersistCheckedValue);
+      Cu.waiveXrays(persistCheckbox).checked = options.setPersistCheckedValue;
     }
   }, {card: aCard, options: aOptions});
 }
 
 // The JSDoc validator does not support @returns tags in abstract functions or
 // star functions without return statements.
 /* eslint-disable valid-jsdoc */
 /**
--- a/browser/components/payments/test/mochitest/test_basic_card_form.html
+++ b/browser/components/payments/test/mochitest/test_basic_card_form.html
@@ -125,16 +125,17 @@ add_task(async function test_saveButton(
     guid: undefined,
     messageType: "updateAutofillRecord",
     record: {
       "cc-exp-month": "11",
       "cc-exp-year": year,
       "cc-name": "J. Smith",
       "cc-number": "4111 1111-1111 1111",
       "billingAddressGUID": "",
+      "isTemporary": true,
     },
   }, "Check event details for the message to chrome");
   form.remove();
 });
 
 add_task(async function test_genericError() {
   let form = new BasicCardForm();
   await form.requestStore.setState({
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5793,16 +5793,18 @@ pref("dom.moduleScripts.enabled", true);
 
 // Maximum amount of time in milliseconds consecutive setTimeout()/setInterval()
 // callback are allowed to run before yielding the event loop.
 pref("dom.timeout.max_consecutive_callbacks_ms", 4);
 
 // Use this preference to house "Payment Request API" during development
 pref("dom.payments.request.enabled", false);
 pref("dom.payments.loglevel", "Warn");
+pref("dom.payments.defaults.saveCreditCard", false);
+pref("dom.payments.defaults.saveAddress", true);
 
 #ifdef FUZZING
 pref("fuzzing.enabled", false);
 #endif
 
 #ifdef MOZ_ASAN_REPORTER
 pref("asanreporter.apiurl", "https://anf1.fuzzing.mozilla.org/crashproxy/submit/");
 pref("asanreporter.clientid", "unknown");