|  | // Copyright 2025 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "chrome/browser/extensions/extension_browsertest.h" | 
|  | #include "chrome/browser/profiles/profile.h" | 
|  | #include "content/public/test/browser_test.h" | 
|  | #include "extensions/browser/extension_registry.h" | 
|  | #include "extensions/test/extension_test_message_listener.h" | 
|  | #include "extensions/test/result_catcher.h" | 
|  | #include "extensions/test/test_extension_dir.h" | 
|  |  | 
|  | namespace extensions { | 
|  |  | 
|  | using ComponentExtensionBrowserTest = ExtensionBrowserTest; | 
|  |  | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | // Tests that MojoJS is enabled for component extensions that need it. | 
|  | // Note the test currently only runs for ChromeOS because the test extension | 
|  | // uses `mojoPrivate` to test and `mojoPrivate` is ChromeOS only. | 
|  | IN_PROC_BROWSER_TEST_F(ComponentExtensionBrowserTest, MojoJS) { | 
|  | ResultCatcher result_catcher; | 
|  |  | 
|  | auto* extension = | 
|  | LoadExtension(test_data_dir_.AppendASCII("service_worker/mojo"), | 
|  | {.load_as_component = true}); | 
|  | ASSERT_TRUE(extension); | 
|  |  | 
|  | ASSERT_TRUE(result_catcher.GetNextResult()); | 
|  | } | 
|  | #endif  // BUILDFLAG(IS_CHROMEOS) | 
|  |  | 
|  | const ExtensionId kExtensionId = "iegclhlplifhodhkoafiokenjoapiobj"; | 
|  | constexpr char kExtensionKey[] = | 
|  | "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjzv7dI7Ygyh67VHE1DdidudpYf8P" | 
|  | "Ffv8iucWvzO+3xpF/Dm5xNo7aQhPNiEaNfHwJQ7lsp4gc+C+4bbaVewBFspTruoSJhZc5uEf" | 
|  | "qxwovJwN+v1/SUFXTXQmQBv6gs0qZB4gBbl4caNQBlqrFwAMNisnu1V6UROna8rOJQ90D7Nv" | 
|  | "7TCwoVPKBfVshpFjdDOTeBg4iLctO3S/06QYqaTDrwVceSyHkVkvzBY6tc6mnYX0RZu78J9i" | 
|  | "L8bdqwfllOhs69cqoHHgrLdI6JdOyiuh6pBP6vxMlzSKWJ3YTNjaQTPwfOYaLMuzdl0v+Ydz" | 
|  | "afIzV9zwe4Xiskk+5JNGt8b2rQIDAQAB"; | 
|  |  | 
|  | // Tests updating a Service Worker-based component extension across a restart. | 
|  | // This simulates a browser update where a component extension might change. | 
|  | class ComponentExtensionServiceWorkerUpdateBrowserTest | 
|  | : public ComponentExtensionBrowserTest { | 
|  | public: | 
|  | void WriteExtension(TestExtensionDir* dir, int version) { | 
|  | constexpr char kManifestTemplate[] = | 
|  | R"({ | 
|  | "name": "Component SW Update Test", | 
|  | "manifest_version": 3, | 
|  | "version": "%d", | 
|  | "background": {"service_worker": "sw.js"}, | 
|  | "key": "%s" | 
|  | })"; | 
|  | dir->WriteManifest( | 
|  | base::StringPrintf(kManifestTemplate, version, kExtensionKey)); | 
|  |  | 
|  | constexpr char kBackgroundScriptTemplate[] = | 
|  | R"(self.version = %d; | 
|  | chrome.test.sendMessage(`v${self.version} ready`);)"; | 
|  | dir->WriteFile(FILE_PATH_LITERAL("sw.js"), | 
|  | base::StringPrintf(kBackgroundScriptTemplate, version)); | 
|  | } | 
|  |  | 
|  | int GetWorkerVersion(const ExtensionId& id) { | 
|  | constexpr char kGetVersionScript[] = | 
|  | "chrome.test.sendScriptResult(self.version);"; | 
|  | base::Value version = ExecuteScriptInBackgroundPage(id, kGetVersionScript); | 
|  | if (!version.is_int()) { | 
|  | ADD_FAILURE() << "Script did not return an integer. Value: " << version; | 
|  | return -1; | 
|  | } | 
|  | return version.GetInt(); | 
|  | } | 
|  |  | 
|  | TestExtensionDir test_dir_v1_; | 
|  | TestExtensionDir test_dir_v2_; | 
|  | }; | 
|  |  | 
|  | // PRE_ test: Installs V1 of the component extension. Verifies it runs. | 
|  | IN_PROC_BROWSER_TEST_F(ComponentExtensionServiceWorkerUpdateBrowserTest, | 
|  | PRE_Update) { | 
|  | WriteExtension(&test_dir_v1_, 1); | 
|  |  | 
|  | // Load V1 of the component extension. | 
|  | ExtensionTestMessageListener v1_ready("v1 ready"); | 
|  | const Extension* extension_v1 = | 
|  | LoadExtension(test_dir_v1_.UnpackedPath(), {.load_as_component = true}); | 
|  | ASSERT_TRUE(extension_v1); | 
|  |  | 
|  | // Ensure it has the correct ID and wait for it to start. | 
|  | const ExtensionId id = extension_v1->id(); | 
|  | ASSERT_EQ(kExtensionId, id); | 
|  | ASSERT_TRUE(v1_ready.WaitUntilSatisfied()); | 
|  |  | 
|  | // Check service worker version. | 
|  | EXPECT_EQ("1", extension_v1->version().GetString()); | 
|  | EXPECT_EQ(1, GetWorkerVersion(id)); | 
|  | } | 
|  |  | 
|  | // Main test: Installs V2 of the component extension. Verifies V2 runs. | 
|  | IN_PROC_BROWSER_TEST_F(ComponentExtensionServiceWorkerUpdateBrowserTest, | 
|  | Update) { | 
|  | ASSERT_FALSE( | 
|  | extension_registry()->enabled_extensions().GetByID(kExtensionId)); | 
|  | WriteExtension(&test_dir_v2_, 2); | 
|  |  | 
|  | // Load V2 of the component extension. | 
|  | ExtensionTestMessageListener v2_ready("v2 ready"); | 
|  | const Extension* extension_v2 = | 
|  | LoadExtension(test_dir_v2_.UnpackedPath(), {.load_as_component = true}); | 
|  | ASSERT_TRUE(extension_v2); | 
|  |  | 
|  | // Ensure it has the correct ID (same as V1) and wait for it to start. | 
|  | const ExtensionId id = extension_v2->id(); | 
|  | EXPECT_EQ(kExtensionId, id); | 
|  | ASSERT_TRUE(v2_ready.WaitUntilSatisfied()); | 
|  |  | 
|  | // Check service worker version. | 
|  | EXPECT_EQ("2", extension_v2->version().GetString()); | 
|  | EXPECT_EQ(2, GetWorkerVersion(id)); | 
|  | } | 
|  |  | 
|  | }  // namespace extensions |