diff --git a/browser/components/firefoxview/firefoxview.html b/browser/components/firefoxview/firefoxview.html
index 3dd3dfeb0637..bae9d655927b 100644
--- a/browser/components/firefoxview/firefoxview.html
+++ b/browser/components/firefoxview/firefoxview.html
@@ -18,6 +18,7 @@
+
entry.result == "added").length;
+ MigrationUtils.notifyLoginsManuallyImported(quantity);
+
progress[
lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS
] = {
@@ -672,17 +671,18 @@ export class MigrationWizardParent extends JSWindowActorParent {
) {
for (let resourceType in MigrationUtils.resourceTypes) {
// Normally, we check each possible resourceType to see if we have one or
- // more corresponding resourceTypes in profileMigrationData. The exception
- // is for Safari, where the migrator does not expose a PASSWORDS resource
- // type, but we allow the user to express that they'd like to import
- // passwords from it anyways. This is because the Safari migration flow is
- // special, and allows the user to import passwords from a file exported
- // from Safari.
+ // more corresponding resourceTypes in profileMigrationData.
+ //
+ // The exception is for passwords for Safari, and for Chrome on Windows,
+ // where we cannot import passwords automatically, but we allow the user
+ // to express that they'd like to import passwords from it anyways. We
+ // use this to determine whether or not to show guidance on how to
+ // manually import a passwords CSV file.
if (
profileMigrationData & MigrationUtils.resourceTypes[resourceType] ||
- (migrator.constructor.key == lazy.SafariProfileMigrator?.key &&
- MigrationUtils.resourceTypes[resourceType] ==
- MigrationUtils.resourceTypes.PASSWORDS)
+ (MigrationUtils.resourceTypes[resourceType] ==
+ MigrationUtils.resourceTypes.PASSWORDS &&
+ migrator.showsManualPasswordImport)
) {
availableResourceTypes.push(resourceType);
}
diff --git a/browser/components/migration/MigratorBase.sys.mjs b/browser/components/migration/MigratorBase.sys.mjs
index 4c9a034132b0..14fdb9f966fe 100644
--- a/browser/components/migration/MigratorBase.sys.mjs
+++ b/browser/components/migration/MigratorBase.sys.mjs
@@ -238,6 +238,16 @@ export class MigratorBase {
return Promise.resolve(false);
}
+ /**
+ * Subclasses should override this and return true if the source browser
+ * cannot have its passwords imported directly, and if there is a specialized
+ * flow through the wizard to walk the user through importing from a CSV
+ * file manually.
+ */
+ get showsManualPasswordImport() {
+ return false;
+ }
+
/**
* This method returns a number that is the bitwise OR of all resource
* types that are available in aProfile. See MigrationUtils.resourceTypes
diff --git a/browser/components/migration/SafariProfileMigrator.sys.mjs b/browser/components/migration/SafariProfileMigrator.sys.mjs
index fa9f0338f4d2..4ea92d4739e1 100644
--- a/browser/components/migration/SafariProfileMigrator.sys.mjs
+++ b/browser/components/migration/SafariProfileMigrator.sys.mjs
@@ -655,6 +655,18 @@ export class SafariProfileMigrator extends MigratorBase {
return false;
}
+ /**
+ * For Safari on macOS, we show a specialized flow for importing passwords
+ * from a CSV file.
+ *
+ * @returns {boolean}
+ */
+ get showsManualPasswordImport() {
+ // Since this migrator will only ever be used on macOS, all conditions are
+ // met and we can always return true.
+ return true;
+ }
+
get mainPreferencesPropertyList() {
if (this._mainPreferencesPropertyList === undefined) {
let file = FileUtils.getDir("UsrPrfs", []);
diff --git a/browser/components/migration/content/migration-wizard-constants.mjs b/browser/components/migration/content/migration-wizard-constants.mjs
index 81d51ecd0a0f..8bf4aa2442d5 100644
--- a/browser/components/migration/content/migration-wizard-constants.mjs
+++ b/browser/components/migration/content/migration-wizard-constants.mjs
@@ -20,6 +20,7 @@ export const MigrationWizardConstants = Object.freeze({
FILE_IMPORT_PROGRESS: "file-import-progress",
SAFARI_PERMISSION: "safari-permission",
SAFARI_PASSWORD_PERMISSION: "safari-password-permission",
+ CHROME_WINDOWS_PASSWORD_PERMISSION: "chrome-windows-password-permission",
NO_BROWSERS_FOUND: "no-browsers-found",
}),
diff --git a/browser/components/migration/content/migration-wizard.mjs b/browser/components/migration/content/migration-wizard.mjs
index a7fbc2963652..dfd397b236fc 100644
--- a/browser/components/migration/content/migration-wizard.mjs
+++ b/browser/components/migration/content/migration-wizard.mjs
@@ -249,6 +249,24 @@ export class MigrationWizard extends HTMLElement {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -298,6 +316,9 @@ export class MigrationWizard extends HTMLElement {
if (window.MozXULElement) {
window.MozXULElement.insertFTLIfNeeded("branding/brand.ftl");
window.MozXULElement.insertFTLIfNeeded("browser/migrationWizard.ftl");
+ window.MozXULElement.insertFTLIfNeeded(
+ "preview/migrationWizardChromeWindows.ftl"
+ );
}
document.l10n.connectRoot(shadow);
diff --git a/browser/components/migration/metrics.yaml b/browser/components/migration/metrics.yaml
index a073c215cb9f..386a38d1e45b 100644
--- a/browser/components/migration/metrics.yaml
+++ b/browser/components/migration/metrics.yaml
@@ -232,6 +232,20 @@ browser.migration:
expires: never
telemetry_mirror: BrowserMigration_SafariPasswordFile_Wizard
+ chrome_password_file_wizard:
+ type: event
+ description: >
+ Recorded if the user is importing from Chrome, and was presented with
+ the page of the wizard requesting to import passwords from a file. This
+ currently only gets shown on Windows.
+ bugs:
+ - https://bugzil.la/1960560
+ data_reviews:
+ - https://bugzil.la/1960560
+ notification_emails:
+ - mconley@mozilla.com
+ expires: never
+
migration_started_wizard:
type: event
description: >
diff --git a/browser/components/migration/tests/browser/browser.toml b/browser/components/migration/tests/browser/browser.toml
index 40fbe11a1fd0..c086f1933596 100644
--- a/browser/components/migration/tests/browser/browser.toml
+++ b/browser/components/migration/tests/browser/browser.toml
@@ -8,6 +8,9 @@ tags = "os_integration"
["browser_aboutwelcome_behavior.js"]
+["browser_chrome_windows_passwords.js"]
+run-if = ["os == 'win'"]
+
["browser_dialog_cancel_close.js"]
["browser_dialog_open.js"]
diff --git a/browser/components/migration/tests/browser/browser_chrome_windows_passwords.js b/browser/components/migration/tests/browser/browser_chrome_windows_passwords.js
new file mode 100644
index 000000000000..e162060ac159
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_chrome_windows_passwords.js
@@ -0,0 +1,413 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { ChromeProfileMigrator } = ChromeUtils.importESModule(
+ "resource:///modules/ChromeProfileMigrator.sys.mjs"
+);
+const { LoginCSVImport } = ChromeUtils.importESModule(
+ "resource://gre/modules/LoginCSVImport.sys.mjs"
+);
+
+const TEST_FILE_PATH = getTestFilePath("dummy_file.csv");
+
+// We use MockFilePicker to simulate a native file picker, and prepare it
+// to return a dummy file pointed at TEST_FILE_PATH. The file at
+// TEST_FILE_PATH is not required (nor expected) to exist.
+const { MockFilePicker } = SpecialPowers;
+
+add_setup(async function () {
+ MockFilePicker.init(window.browsingContext);
+ registerCleanupFunction(() => {
+ MockFilePicker.cleanup();
+ });
+});
+
+/**
+ * A helper function that does most of the heavy lifting for the tests in
+ * this file. Specfically, it takes care of:
+ *
+ * 1. Stubbing out the various hunks of the ChromeProfileMigrator in order
+ * to simulate a migration without actually performing one, since the
+ * migrator itself isn't being tested here.
+ * 2. Stubbing out parts of MigrationUtils and LoginCSVImport to have a
+ * consistent reporting on how many things are imported.
+ * 3. Setting up the MockFilePicker if expectsFilePicker is true to return
+ * the TEST_FILE_PATH.
+ * 4. Opens up the migration wizard, and chooses to import both BOOKMARKS
+ * and PASSWORDS, and then clicks "Import".
+ * 5. Waits for the migration wizard to show the Chrome password import
+ * instructions.
+ * 6. Runs taskFn
+ * 7. Closes the migration dialog.
+ *
+ * @param {boolean} expectsFilePicker
+ * True if the MockFilePicker should be set up to return TEST_FILE_PATH.
+ * @param {boolean} migrateBookmarks
+ * True if bookmarks should be migrated alongside passwords. If not, only
+ * passwords will be migrated.
+ * @param {boolean} shouldPasswordImportFail
+ * True if importing from the CSV file should fail.
+ * @param {Function} taskFn
+ * An asynchronous function that takes the following parameters in this
+ * order:
+ *
+ * {Element} wizard
+ * The opened migration wizard
+ * {Promise} filePickerShownPromise
+ * A Promise that resolves once the MockFilePicker has closed. This is
+ * undefined if expectsFilePicker was false.
+ * {object} importFromCSVStub
+ * The Sinon stub object for LoginCSVImport.importFromCSV. This can be
+ * used to check to see whether it was called.
+ * {Promise} didMigration
+ * A Promise that resolves to true once the migration completes.
+ * {Promise} wizardDone
+ * A Promise that resolves once the migration wizard reports that a
+ * migration has completed.
+ * @returns {Promise}
+ */
+async function testChromePasswordHelper(
+ expectsFilePicker,
+ migrateBookmarks,
+ shouldPasswordImportFail,
+ taskFn
+) {
+ let sandbox = sinon.createSandbox();
+ registerCleanupFunction(() => {
+ sandbox.restore();
+ });
+
+ sandbox.stub(MigrationUtils, "availableMigratorKeys").get(() => {
+ return ["chrome"];
+ });
+
+ let migrator = new ChromeProfileMigrator();
+ sandbox.stub(MigrationUtils, "getMigrator").resolves(migrator);
+
+ sandbox
+ .stub(ChromeProfileMigrator.prototype, "getSourceProfiles")
+ .resolves([{ id: "chrome-test-1", name: "Chrome test profile 1" }]);
+
+ // Have the migrator claim that only BOOKMARKS are available.
+ sandbox
+ .stub(ChromeProfileMigrator.prototype, "getMigrateData")
+ .resolves(MigrationUtils.resourceTypes.BOOKMARKS);
+
+ let migrateStub;
+ let didMigration = new Promise(resolve => {
+ migrateStub = sandbox
+ .stub(ChromeProfileMigrator.prototype, "migrate")
+ .callsFake((aResourceTypes, aStartup, aProfile, aProgressCallback) => {
+ if (!migrateBookmarks) {
+ Assert.ok(
+ false,
+ "Should not have called migrate when only migrating Chrome passwords."
+ );
+ }
+
+ Assert.ok(
+ !aStartup,
+ "Migrator should not have been called as a startup migration."
+ );
+ Assert.ok(
+ aResourceTypes & MigrationUtils.resourceTypes.BOOKMARKS,
+ "Should have requested to migrate the BOOKMARKS resource."
+ );
+ Assert.ok(
+ !(aResourceTypes & MigrationUtils.resourceTypes.PASSWORDS),
+ "Should not have requested to migrate the PASSWORDS resource."
+ );
+
+ aProgressCallback(MigrationUtils.resourceTypes.BOOKMARKS, true);
+ Services.obs.notifyObservers(null, "Migration:Ended");
+ resolve();
+ });
+ });
+
+ // We'll pretend we added EXPECTED_QUANTITY passwords from the Chrome
+ // password file.
+ let results = [];
+ for (let i = 0; i < EXPECTED_QUANTITY; ++i) {
+ results.push({ result: "added" });
+ }
+ let importFromCSVStub = sandbox.stub(LoginCSVImport, "importFromCSV");
+
+ if (shouldPasswordImportFail) {
+ importFromCSVStub.rejects(new Error("Some error message"));
+ } else {
+ importFromCSVStub.resolves(results);
+ }
+
+ sandbox.stub(MigrationUtils, "_importQuantities").value({
+ bookmarks: EXPECTED_QUANTITY,
+ });
+
+ let filePickerShownPromise;
+
+ if (expectsFilePicker) {
+ MockFilePicker.reset();
+
+ let dummyFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dummyFile.initWithPath(TEST_FILE_PATH);
+ filePickerShownPromise = new Promise(resolve => {
+ MockFilePicker.showCallback = () => {
+ Assert.ok(true, "Filepicker shown.");
+ MockFilePicker.setFiles([dummyFile]);
+ resolve();
+ };
+ });
+ MockFilePicker.returnValue = MockFilePicker.returnOK;
+ }
+
+ await withMigrationWizardDialog(async prefsWin => {
+ let dialogBody = prefsWin.document.body;
+ let wizard = dialogBody.querySelector("migration-wizard");
+ let wizardDone = BrowserTestUtils.waitForEvent(
+ wizard,
+ "MigrationWizard:DoneMigration"
+ );
+
+ let shadow = wizard.openOrClosedShadowRoot;
+
+ info("Choosing Chrome");
+ let panelItem = shadow.querySelector(
+ `panel-item[key="${ChromeProfileMigrator.key}"]`
+ );
+ panelItem.click();
+
+ let resourceTypeList = shadow.querySelector("#resource-type-list");
+
+ // Let's choose whether to import BOOKMARKS first.
+ let bookmarksNode = resourceTypeList.querySelector(
+ `label[data-resource-type="${MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS}"]`
+ );
+ bookmarksNode.control.checked = migrateBookmarks;
+
+ // Let's make sure that PASSWORDS is displayed despite the migrator only
+ // (currently) returning BOOKMARKS as an available resource to migrate.
+ let passwordsNode = resourceTypeList.querySelector(
+ `label[data-resource-type="${MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS}"]`
+ );
+ Assert.ok(
+ !passwordsNode.hidden,
+ "PASSWORDS should be available to import from."
+ );
+ passwordsNode.control.checked = true;
+
+ let deck = shadow.querySelector("#wizard-deck");
+ let switchedToChromePermissionPage =
+ BrowserTestUtils.waitForMutationCondition(
+ deck,
+ { attributeFilter: ["selected-view"] },
+ () => {
+ return (
+ deck.getAttribute("selected-view") ==
+ "page-" +
+ MigrationWizardConstants.PAGES.CHROME_WINDOWS_PASSWORD_PERMISSION
+ );
+ }
+ );
+
+ let importButton = shadow.querySelector("#import");
+ importButton.click();
+ await switchedToChromePermissionPage;
+ Assert.ok(true, "Went to Chrome permission page after attempting import.");
+
+ await taskFn(
+ wizard,
+ filePickerShownPromise,
+ importFromCSVStub,
+ didMigration,
+ migrateStub,
+ wizardDone
+ );
+
+ let dialog = prefsWin.document.querySelector("#migrationWizardDialog");
+ let doneButton = shadow.querySelector(
+ "div[name='page-progress'] .done-button"
+ );
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "close");
+
+ doneButton.click();
+ await dialogClosed;
+ });
+
+ sandbox.restore();
+ MockFilePicker.reset();
+}
+
+/**
+ * Tests the flow of importing passwords from Chrome via an
+ * exported CSV file.
+ */
+add_task(async function test_chrome_password_do_import() {
+ await testChromePasswordHelper(
+ true,
+ true,
+ false,
+ async (
+ wizard,
+ filePickerShownPromise,
+ importFromCSVStub,
+ didMigration,
+ migrateStub,
+ wizardDone
+ ) => {
+ let shadow = wizard.openOrClosedShadowRoot;
+ let manualPasswordImportSelect = shadow.querySelector(
+ "div[name='page-chrome-windows-password-permission'] .manual-password-import-select"
+ );
+
+ manualPasswordImportSelect.click();
+ await filePickerShownPromise;
+ Assert.ok(true, "File picker was shown.");
+
+ await didMigration;
+ Assert.ok(importFromCSVStub.called, "Importing from CSV was called.");
+
+ await wizardDone;
+
+ assertQuantitiesShown(wizard, [
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS,
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS,
+ ]);
+ }
+ );
+});
+
+/**
+ * Tests that only passwords get imported if the user only opts
+ * to import passwords, and that nothing else gets imported.
+ */
+add_task(async function test_chrome_password_only_do_import() {
+ await testChromePasswordHelper(
+ true,
+ false,
+ false,
+ async (
+ wizard,
+ filePickerShownPromise,
+ importFromCSVStub,
+ didMigration,
+ migrateStub,
+ wizardDone
+ ) => {
+ let shadow = wizard.openOrClosedShadowRoot;
+ let manualPasswordImportSelect = shadow.querySelector(
+ "div[name='page-chrome-windows-password-permission'] .manual-password-import-select"
+ );
+ manualPasswordImportSelect.click();
+ await filePickerShownPromise;
+ Assert.ok(true, "File picker was shown.");
+
+ await wizardDone;
+
+ assertQuantitiesShown(wizard, [
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS,
+ ]);
+
+ Assert.ok(importFromCSVStub.called, "Importing from CSV was called.");
+ Assert.ok(
+ !migrateStub.called,
+ "ChromeProfileMigrator.migrate was not called."
+ );
+ }
+ );
+});
+
+/**
+ * Tests the flow of importing passwords from Chrome when the file
+ * import fails.
+ */
+add_task(async function test_chrome_password_empty_csv_file() {
+ await testChromePasswordHelper(
+ true,
+ true,
+ true,
+ async (
+ wizard,
+ filePickerShownPromise,
+ importFromCSVStub,
+ didMigration,
+ migrateStub,
+ wizardDone
+ ) => {
+ let shadow = wizard.openOrClosedShadowRoot;
+ let manualPasswordImportSelect = shadow.querySelector(
+ "div[name='page-chrome-windows-password-permission'] .manual-password-import-select"
+ );
+ manualPasswordImportSelect.click();
+ await filePickerShownPromise;
+ Assert.ok(true, "File picker was shown.");
+
+ await didMigration;
+ Assert.ok(importFromCSVStub.called, "Importing from CSV was called.");
+
+ await wizardDone;
+
+ let headerL10nID =
+ shadow.querySelector("#progress-header").dataset.l10nId;
+ Assert.equal(
+ headerL10nID,
+ "migration-wizard-progress-done-with-warnings-header"
+ );
+
+ let progressGroup = shadow.querySelector(
+ `.resource-progress-group[data-resource-type="${MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS}"`
+ );
+ let progressIcon = progressGroup.querySelector(".progress-icon");
+ let messageText =
+ progressGroup.querySelector(".message-text").textContent;
+
+ Assert.equal(
+ progressIcon.getAttribute("state"),
+ "warning",
+ "Icon should be in the warning state."
+ );
+ Assert.stringMatches(
+ messageText,
+ /file doesn’t include any valid password data/
+ );
+ }
+ );
+});
+
+/**
+ * Tests that the user can skip importing passwords from Chrome.
+ */
+add_task(async function test_chrome_password_skip() {
+ await testChromePasswordHelper(
+ false,
+ true,
+ false,
+ async (
+ wizard,
+ filePickerShownPromise,
+ importFromCSVStub,
+ didMigration,
+ migrateStub,
+ wizardDone
+ ) => {
+ let shadow = wizard.openOrClosedShadowRoot;
+ let manualPasswordImportSkip = shadow.querySelector(
+ "div[name='page-chrome-windows-password-permission'] .manual-password-import-skip"
+ );
+ manualPasswordImportSkip.click();
+
+ await didMigration;
+ Assert.ok(!MockFilePicker.shown, "Never showed the file picker.");
+ Assert.ok(
+ !importFromCSVStub.called,
+ "Importing from CSV was never called."
+ );
+
+ await wizardDone;
+
+ assertQuantitiesShown(wizard, [
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS,
+ ]);
+ }
+ );
+});
diff --git a/browser/components/storybook/stories/migration-wizard.stories.mjs b/browser/components/storybook/stories/migration-wizard.stories.mjs
index 5d3fbd3958c6..ab69de33eba1 100644
--- a/browser/components/storybook/stories/migration-wizard.stories.mjs
+++ b/browser/components/storybook/stories/migration-wizard.stories.mjs
@@ -11,6 +11,10 @@ import { MigrationWizardConstants } from "chrome://browser/content/migration/mig
// Imported for side-effects.
import "toolkit-widgets/named-deck.js";
+window.MozXULElement.insertFTLIfNeeded(
+ "locales-preview/migrationWizardChromeWindows.ftl"
+);
+
export default {
title: "Domain-specific UI Widgets/Migration Wizard",
component: "migration-wizard",
@@ -592,6 +596,14 @@ SafariPasswordPermissions.args = {
},
};
+export const ChromeWindowsPasswordPermissions = Template.bind({});
+ChromeWindowsPasswordPermissions.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.CHROME_WINDOWS_PASSWORD_PERMISSION,
+ },
+};
+
export const NoBrowsersFound = Template.bind({});
NoBrowsersFound.args = {
dialogMode: true,
diff --git a/browser/locales-preview/migrationWizardChromeWindows.ftl b/browser/locales-preview/migrationWizardChromeWindows.ftl
new file mode 100644
index 000000000000..f31d06e52a44
--- /dev/null
+++ b/browser/locales-preview/migrationWizardChromeWindows.ftl
@@ -0,0 +1,13 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+migration-chrome-windows-password-import-header = How to import passwords from Chrome
+migration-chrome-windows-password-import-steps-header = In Chrome:
+migration-chrome-windows-password-import-step1 = Open the main menu and go to Passwords and Autofill > Google Password Manager.
+migration-chrome-windows-password-import-step2 = Select “Settings” from the menu.
+migration-chrome-windows-password-import-step3 = Choose “Download file” and save it to your device.
+migration-chrome-windows-password-import-step4 = Return here and “Select File” to finish import.
+
+migration-manual-password-import-skip-button = Skip
+migration-manual-password-import-select-button = Select File
diff --git a/browser/locales/jar.mn b/browser/locales/jar.mn
index b00da64d18c6..4d1734cdd5de 100644
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -20,6 +20,7 @@
preview/linkPreview.ftl (../locales-preview/linkPreview.ftl)
preview/smartTabGroups.ftl (../locales-preview/smartTabGroups.ftl)
preview/taskbartabs.ftl (../locales-preview/taskbartabs.ftl)
+ preview/migrationWizardChromeWindows.ftl (../locales-preview/migrationWizardChromeWindows.ftl)
@AB_CD@.jar:
% locale browser @AB_CD@ %locale/browser/
diff --git a/browser/themes/shared/jar.inc.mn b/browser/themes/shared/jar.inc.mn
index c91878ad50f3..e1c7bf2ad2a7 100644
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -77,6 +77,7 @@
skin/classic/browser/migration/migration-wizard.css (../shared/migration/migration-wizard.css)
skin/classic/browser/migration/success.svg (../shared/migration/success.svg)
skin/classic/browser/migration/progress-mask.svg (../shared/migration/progress-mask.svg)
+ skin/classic/browser/migration/chrome-icon-3dots.svg (../shared/migration/chrome-icon-3dots.svg)
skin/classic/browser/migration/safari-icon-3dots.svg (../shared/migration/safari-icon-3dots.svg)
skin/classic/browser/notification-icons/autoplay-media.svg (../shared/notification-icons/autoplay-media.svg)
skin/classic/browser/notification-icons/autoplay-media-blocked.svg (../shared/notification-icons/autoplay-media-blocked.svg)
diff --git a/browser/themes/shared/migration/chrome-icon-3dots.svg b/browser/themes/shared/migration/chrome-icon-3dots.svg
new file mode 100644
index 000000000000..1974f916750a
--- /dev/null
+++ b/browser/themes/shared/migration/chrome-icon-3dots.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/browser/themes/shared/migration/migration-wizard.css b/browser/themes/shared/migration/migration-wizard.css
index f3019f751044..9c24eb8a37de 100644
--- a/browser/themes/shared/migration/migration-wizard.css
+++ b/browser/themes/shared/migration/migration-wizard.css
@@ -340,16 +340,30 @@ summary {
vertical-align: middle;
}
-.safari-icon-3dots {
+.safari-icon-3dots,
+.chrome-icon-3dots {
width: 16px;
height: 16px;
- vertical-align: middle;
- content: url("chrome://browser/skin/migration/safari-icon-3dots.svg");
-moz-context-properties: fill, stroke;
+ vertical-align: middle;
+}
+
+.safari-icon-3dots {
+ content: url("chrome://browser/skin/migration/safari-icon-3dots.svg");
fill: currentColor;
stroke: color-mix(in srgb, currentColor 10%, transparent 90%);
}
+.chrome-icon-3dots {
+ content: url("chrome://browser/skin/migration/chrome-icon-3dots.svg");
+ /**
+ * Stroke and fill colours were sampled from built-in dark and light theme
+ * from Chrome on Windows
+ */
+ fill: light-dark(#474747, #C7C7C7);
+ stroke: light-dark(#474747, #C7C7C7);
+}
+
.no-browsers-found-message {
display: flex;
}