| // Copyright 2012 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/memory/raw_ptr.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/notreached.h" |
| #include "base/path_service.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/values_test_util.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/devtools/devtools_window.h" |
| #include "chrome/browser/devtools/devtools_window_testing.h" |
| #include "chrome/browser/extensions/api/developer_private/developer_private_functions.h" |
| #include "chrome/browser/extensions/extension_apitest.h" |
| #include "chrome/browser/extensions/extension_tab_util.h" |
| #include "chrome/browser/extensions/extension_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/tab_list/tab_list_interface.h" |
| #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/chrome_test_utils.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/service_worker_test_helpers.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "extensions/browser/api_test_utils.h" |
| #include "extensions/browser/browsertest_util.h" |
| #include "extensions/browser/extension_host_test_helper.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/browser/extension_registrar.h" |
| #include "extensions/browser/offscreen_document_host.h" |
| #include "extensions/browser/permissions/permissions_updater.h" |
| #include "extensions/buildflags/buildflags.h" |
| #include "extensions/common/extension_builder.h" |
| #include "extensions/common/extension_features.h" |
| #include "extensions/common/manifest_handlers/background_info.h" |
| #include "extensions/common/mojom/manifest.mojom-shared.h" |
| #include "extensions/common/mojom/view_type.mojom.h" |
| #include "extensions/test/result_catcher.h" |
| #include "extensions/test/test_extension_dir.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/views/widget/any_widget_observer.h" |
| #include "ui/views/widget/widget.h" |
| |
| #if BUILDFLAG(ENABLE_PLATFORM_APPS) |
| #include "extensions/browser/app_window/app_window.h" |
| #include "extensions/browser/app_window/app_window_registry.h" |
| #endif // BUILDFLAG(ENABLE_PLATFORM_APPS) |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "chrome/browser/flags/android/chrome_feature_list.h" |
| #else |
| #include "ui/views/widget/widget_delegate.h" |
| #include "ui/views/window/dialog_delegate.h" |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE)); |
| |
| namespace extensions { |
| |
| class DeveloperPrivateApiTest : public ExtensionApiTest { |
| protected: |
| std::optional<api::developer_private::ExtensionInfo> GetExtensionInfo( |
| const Extension& extension) { |
| auto get_info_function = |
| base::MakeRefCounted<api::DeveloperPrivateGetExtensionInfoFunction>(); |
| std::optional<base::Value> result = |
| api_test_utils::RunFunctionAndReturnSingleResult( |
| get_info_function.get(), |
| content::JsReplace(R"([$1])", extension.id()), profile()); |
| if (!result) { |
| ADD_FAILURE() << "No result back when getting extension info"; |
| return std::nullopt; |
| } |
| std::optional<api::developer_private::ExtensionInfo> info = |
| api::developer_private::ExtensionInfo::FromValue(*result); |
| if (!info) { |
| ADD_FAILURE() << "Problem creating ExtensionInfo from result data"; |
| } |
| return info; |
| } |
| }; |
| |
| #if BUILDFLAG(ENABLE_PLATFORM_APPS) |
| // Tests opening the developer tools for an app window. |
| IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest, InspectAppWindowView) { |
| base::FilePath dir; |
| base::PathService::Get(chrome::DIR_TEST_DATA, &dir); |
| dir = dir.AppendASCII("extensions") |
| .AppendASCII("platform_apps") |
| .AppendASCII("minimal"); |
| |
| // Load and launch a platform app. |
| const Extension* app = LoadAndLaunchApp(dir); |
| |
| // Get the info about the app, including the inspectable views. |
| auto info = GetExtensionInfo(*app); |
| |
| // There should be two inspectable views - the background page and the app |
| // window. Find the app window. |
| ASSERT_EQ(2u, info->views.size()); |
| const api::developer_private::ExtensionView* window_view = nullptr; |
| for (const auto& view : info->views) { |
| if (view.type == api::developer_private::ViewType::kAppWindow) { |
| window_view = &view; |
| break; |
| } |
| } |
| ASSERT_TRUE(window_view); |
| |
| // Inspect the app window. |
| auto function = |
| base::MakeRefCounted<api::DeveloperPrivateOpenDevToolsFunction>(); |
| api_test_utils::RunFunction( |
| function.get(), |
| base::StringPrintf("[{\"renderViewId\": %d, \"renderProcessId\": %d}]", |
| window_view->render_view_id, |
| window_view->render_process_id), |
| profile()); |
| |
| // Verify that dev tools opened. |
| std::list<raw_ptr<AppWindow, CtnExperimental>> app_windows = |
| AppWindowRegistry::Get(profile())->GetAppWindowsForApp(app->id()); |
| ASSERT_EQ(1u, app_windows.size()); |
| EXPECT_TRUE(DevToolsWindow::GetInstanceForInspectedWebContents( |
| (*app_windows.begin())->web_contents())); |
| } |
| #endif // BUILDFLAG(ENABLE_PLATFORM_APPS) |
| |
| IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest, InspectEmbeddedOptionsPage) { |
| base::FilePath dir; |
| base::PathService::Get(chrome::DIR_TEST_DATA, &dir); |
| // Load an extension that only has an embedded options_ui page. |
| const Extension* extension = LoadExtension(dir.AppendASCII("extensions") |
| .AppendASCII("delayed_install") |
| .AppendASCII("v1")); |
| ASSERT_TRUE(extension); |
| |
| // Open the embedded options page. |
| content::WebContents* web_contents = |
| chrome_test_utils::GetActiveWebContents(this); |
| ASSERT_TRUE(ExtensionTabUtil::OpenOptionsPageFromWebContents(extension, |
| web_contents)); |
| WaitForExtensionNotIdle(extension->id()); |
| |
| // On Android, the option page will be opened in a new tab as the guest view |
| // is not enabled on Android yet. |
| #if BUILDFLAG(IS_ANDROID) |
| web_contents = chrome_test_utils::GetActiveWebContents(this); |
| ASSERT_EQ("chrome-extension://" + extension->id() + "/popup.html", |
| web_contents->GetURL()); |
| #else |
| // Get the info about the extension, including the inspectable views. |
| auto info = GetExtensionInfo(*extension); |
| |
| // The embedded options page should show up. |
| ASSERT_EQ(1u, info->views.size()); |
| const api::developer_private::ExtensionView& view = info->views[0]; |
| ASSERT_EQ(api::developer_private::ViewType::kExtensionGuest, view.type); |
| |
| // Inspect the embedded options page. |
| auto function = |
| base::MakeRefCounted<api::DeveloperPrivateOpenDevToolsFunction>(); |
| api_test_utils::RunFunction( |
| function.get(), |
| base::StringPrintf("[{\"renderViewId\": %d, \"renderProcessId\": %d}]", |
| view.render_view_id, view.render_process_id), |
| profile()); |
| |
| // Verify that dev tools opened. |
| content::RenderFrameHost* render_frame_host = |
| content::RenderFrameHost::FromID(view.render_process_id, |
| view.render_view_id); |
| ASSERT_TRUE(render_frame_host); |
| content::WebContents* wc = |
| content::WebContents::FromRenderFrameHost(render_frame_host); |
| ASSERT_TRUE(wc); |
| EXPECT_TRUE(DevToolsWindow::GetInstanceForInspectedWebContents(wc)); |
| #endif |
| } |
| |
| // TODO(crbug.com/40273479): Test is flaky on MSan builders. |
| #if defined(MEMORY_SANITIZER) |
| #define MAYBE_InspectInactiveServiceWorkerBackground \ |
| DISABLED_InspectInactiveServiceWorkerBackground |
| #else |
| #define MAYBE_InspectInactiveServiceWorkerBackground \ |
| InspectInactiveServiceWorkerBackground |
| #endif |
| IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest, |
| MAYBE_InspectInactiveServiceWorkerBackground) { |
| ResultCatcher result_catcher; |
| // Load an extension that is service worker-based. |
| const Extension* extension = |
| LoadExtension(test_data_dir_.AppendASCII("service_worker") |
| .AppendASCII("worker_based_background") |
| .AppendASCII("inspect"), |
| // Wait for the registration to be stored since we'll stop |
| // the worker. |
| {.wait_for_registration_stored = true}); |
| ASSERT_TRUE(extension); |
| ASSERT_TRUE(result_catcher.GetNextResult()); |
| |
| // Stop the service worker. |
| browsertest_util::StopServiceWorkerForExtensionGlobalScope(profile(), |
| extension->id()); |
| |
| // Get the info about the extension, including the inspectable views. |
| auto info = GetExtensionInfo(*extension); |
| |
| // There should be a worker based background for the extension. |
| ASSERT_EQ(1u, info->views.size()); |
| const api::developer_private::ExtensionView& view = info->views[0]; |
| EXPECT_EQ(api::developer_private::ViewType::kExtensionServiceWorkerBackground, |
| view.type); |
| // The service worker should be inactive (indicated by -1 for |
| // the process id). |
| EXPECT_EQ(-1, view.render_process_id); |
| |
| // Inspect the inactive service worker background. |
| DevToolsWindowCreationObserver devtools_window_created_observer; |
| auto dev_tools_function = |
| base::MakeRefCounted<api::DeveloperPrivateOpenDevToolsFunction>(); |
| api_test_utils::RunFunction(dev_tools_function.get(), |
| base::StringPrintf( |
| R"([{"renderViewId": -1, |
| "renderProcessId": -1, |
| "isServiceWorker": true, |
| "extensionId": "%s" |
| }])", |
| extension->id().c_str()), |
| profile()); |
| devtools_window_created_observer.WaitForLoad(); |
| |
| // Verify that dev tool window opened. |
| scoped_refptr<content::DevToolsAgentHost> service_worker_host; |
| content::DevToolsAgentHost::List targets = |
| content::DevToolsAgentHost::GetOrCreateAll(); |
| for (const scoped_refptr<content::DevToolsAgentHost>& host : targets) { |
| if (host->GetType() == content::DevToolsAgentHost::kTypeServiceWorker && |
| host->GetURL() == |
| BackgroundInfo::GetBackgroundServiceWorkerScriptURL(extension)) { |
| EXPECT_FALSE(service_worker_host); |
| service_worker_host = host; |
| } |
| } |
| |
| ASSERT_TRUE(service_worker_host); |
| EXPECT_TRUE(DevToolsWindow::FindDevToolsWindow(service_worker_host.get())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest, |
| InspectActiveServiceWorkerBackground) { |
| ResultCatcher result_catcher; |
| // Load an extension that is service worker based. |
| const Extension* extension = |
| LoadExtension(test_data_dir_.AppendASCII("service_worker") |
| .AppendASCII("worker_based_background") |
| .AppendASCII("inspect")); |
| ASSERT_TRUE(extension); |
| ASSERT_TRUE(result_catcher.GetNextResult()); |
| |
| // Get the info about the extension, including the inspectable views. |
| auto info = GetExtensionInfo(*extension); |
| |
| // There should be a worker based background for the extension. |
| ASSERT_EQ(1u, info->views.size()); |
| const api::developer_private::ExtensionView& view = info->views[0]; |
| EXPECT_EQ(api::developer_private::ViewType::kExtensionServiceWorkerBackground, |
| view.type); |
| EXPECT_NE(-1, view.render_process_id); |
| |
| // Inspect the service worker page. |
| auto dev_tools_function = |
| base::MakeRefCounted<api::DeveloperPrivateOpenDevToolsFunction>(); |
| api_test_utils::RunFunction( |
| dev_tools_function.get(), |
| base::StringPrintf( |
| R"([{"renderViewId": -1, |
| "renderProcessId": %d, |
| "isServiceWorker": true, |
| "extensionId": "%s" |
| }])", |
| info->views[0].render_process_id, extension->id().c_str()), |
| profile()); |
| |
| // Find the service worker background host. |
| content::DevToolsAgentHost::List targets = |
| content::DevToolsAgentHost::GetOrCreateAll(); |
| scoped_refptr<content::DevToolsAgentHost> service_worker_host; |
| for (const scoped_refptr<content::DevToolsAgentHost>& host : targets) { |
| if (host->GetType() == content::DevToolsAgentHost::kTypeServiceWorker && |
| host->GetURL() == |
| BackgroundInfo::GetBackgroundServiceWorkerScriptURL(extension)) { |
| EXPECT_FALSE(service_worker_host); |
| service_worker_host = host; |
| } |
| } |
| |
| // Verify that dev tools opened. |
| ASSERT_TRUE(service_worker_host); |
| EXPECT_TRUE(DevToolsWindow::FindDevToolsWindow(service_worker_host.get())); |
| } |
| |
| // TODO(crbug.com/40882269): The test is flaky on MSAN and Linux. Re-enable it. |
| #if defined(MEMORY_SANITIZER) || BUILDFLAG(IS_LINUX) |
| #define MAYBE_InspectSplitModeServiceWorkerBackgrounds \ |
| DISABLED_InspectSplitModeServiceWorkerBackgrounds |
| #else |
| #define MAYBE_InspectSplitModeServiceWorkerBackgrounds \ |
| InspectSplitModeServiceWorkerBackgrounds |
| #endif |
| IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest, |
| MAYBE_InspectSplitModeServiceWorkerBackgrounds) { |
| ResultCatcher result_catcher; |
| // Load an extension that is service worker based, split mode and enabled in |
| // incognito. |
| static constexpr char kManifest[] = |
| R"({ |
| "name": "Split mode worker test", |
| "manifest_version": 3, |
| "version": "0.1", |
| "background": {"service_worker": "worker.js"}, |
| "incognito": "split" |
| })"; |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(kManifest); |
| test_dir.WriteFile(FILE_PATH_LITERAL("worker.js"), |
| "chrome.test.notifyPass();"); |
| const Extension* extension = |
| LoadExtension(test_dir.UnpackedPath(), {.allow_in_incognito = true}); |
| ASSERT_TRUE(extension); |
| ASSERT_TRUE(result_catcher.GetNextResult()); |
| |
| // Get the info about the extension, including the inspectable views. |
| auto info = GetExtensionInfo(*extension); |
| |
| // With no incognito window open, there should be a single worker based |
| // background page for the extension. |
| ASSERT_EQ(1u, info->views.size()); |
| int main_render_process_id = -1; |
| { |
| const api::developer_private::ExtensionView& view = info->views[0]; |
| EXPECT_EQ( |
| api::developer_private::ViewType::kExtensionServiceWorkerBackground, |
| view.type); |
| EXPECT_NE(-1, view.render_process_id); |
| main_render_process_id = view.render_process_id; |
| EXPECT_FALSE(view.incognito); |
| } |
| |
| // Now open up an incognito browser window page and check the inspectable |
| // views again. Waiting for the result catcher will wait for the incognito |
| // service worker to have become active. |
| content::WebContents* const incognito_window = |
| PlatformOpenURLOffTheRecord(profile(), GURL("about:blank")); |
| ASSERT_TRUE(incognito_window); |
| ASSERT_TRUE(result_catcher.GetNextResult()); |
| info = GetExtensionInfo(*extension); |
| // The views should now have 2 entries, one for the main worker which will be |
| // the same as before and a new one for the incognito worker. |
| ASSERT_EQ(2u, info->views.size()); |
| EXPECT_NE(info->views[0].incognito, info->views[1].incognito); |
| int incognito_render_process_id = -1; |
| for (auto& view : info->views) { |
| EXPECT_EQ( |
| api::developer_private::ViewType::kExtensionServiceWorkerBackground, |
| view.type); |
| EXPECT_NE(-1, view.render_process_id); |
| if (view.incognito) { |
| EXPECT_NE(main_render_process_id, view.render_process_id); |
| incognito_render_process_id = view.render_process_id; |
| } else { |
| EXPECT_EQ(main_render_process_id, view.render_process_id); |
| } |
| } |
| |
| // Open a devtools window for both the primary and incognito worker. |
| std::string kOpenDevToolsParams = |
| R"([{"renderViewId": -1, |
| "renderProcessId": $1, |
| "isServiceWorker": true, |
| "extensionId": $2, |
| "incognito": $3 |
| }])"; |
| DevToolsWindow* main_devtools_window = nullptr; |
| { |
| DevToolsWindowCreationObserver devtools_window_created_observer; |
| auto dev_tools_function = |
| base::MakeRefCounted<api::DeveloperPrivateOpenDevToolsFunction>(); |
| api_test_utils::RunFunction( |
| dev_tools_function.get(), |
| content::JsReplace(kOpenDevToolsParams, main_render_process_id, |
| extension->id().c_str(), /*incognito:*/ false), |
| profile()); |
| devtools_window_created_observer.WaitForLoad(); |
| main_devtools_window = devtools_window_created_observer.devtools_window(); |
| } |
| DevToolsWindow* incognito_devtools_window = nullptr; |
| { |
| DevToolsWindowCreationObserver devtools_window_created_observer; |
| auto dev_tools_function = |
| base::MakeRefCounted<api::DeveloperPrivateOpenDevToolsFunction>(); |
| api_test_utils::RunFunction( |
| dev_tools_function.get(), |
| content::JsReplace(kOpenDevToolsParams, incognito_render_process_id, |
| extension->id().c_str(), /*incognito:*/ true), |
| profile()); |
| devtools_window_created_observer.WaitForLoad(); |
| incognito_devtools_window = |
| devtools_window_created_observer.devtools_window(); |
| } |
| |
| // Both windows should have opened and should not point to the same window. |
| ASSERT_TRUE(main_devtools_window); |
| ASSERT_TRUE(incognito_devtools_window); |
| ASSERT_NE(main_devtools_window, incognito_devtools_window); |
| } |
| |
| // Test that offscreen documents show up in the list of inspectable views and |
| // can be inspected. |
| IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest, InspectOffscreenDocument) { |
| static constexpr char kManifest[] = |
| R"({ |
| "name": "Offscreen Document Test", |
| "manifest_version": 3, |
| "version": "0.1" |
| })"; |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(kManifest); |
| test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"), |
| "<html>offscreen</html>"); |
| |
| const Extension* extension = LoadExtension(test_dir.UnpackedPath()); |
| |
| // Create an offscreen document and wait for it to load. |
| std::unique_ptr<OffscreenDocumentHost> offscreen_document; |
| GURL offscreen_url = extension->GetResourceURL("offscreen.html"); |
| { |
| ExtensionHostTestHelper offscreen_waiter(profile(), extension->id()); |
| offscreen_waiter.RestrictToType(mojom::ViewType::kOffscreenDocument); |
| offscreen_document = std::make_unique<OffscreenDocumentHost>( |
| *extension, |
| profile(), offscreen_url); |
| offscreen_document->CreateRendererSoon(); |
| offscreen_waiter.WaitForHostCompletedFirstLoad(); |
| } |
| |
| // Get the list of inspectable views for the extension. |
| auto info = GetExtensionInfo(*extension); |
| |
| // The only inspectable view should be the offscreen document. Validate the |
| // metadata. |
| ASSERT_EQ(1u, info->views.size()); |
| const api::developer_private::ExtensionView& view = info->views[0]; |
| EXPECT_EQ(api::developer_private::ViewType::kOffscreenDocument, view.type); |
| content::WebContents* offscreen_contents = |
| offscreen_document->host_contents(); |
| EXPECT_EQ(offscreen_url.spec(), view.url); |
| EXPECT_EQ(offscreen_document->render_process_host()->GetDeprecatedID(), |
| view.render_process_id); |
| EXPECT_EQ(offscreen_contents->GetPrimaryMainFrame()->GetRoutingID(), |
| view.render_view_id); |
| EXPECT_FALSE(view.incognito); |
| EXPECT_FALSE(view.is_iframe); |
| |
| // The document shouldn't currently be under inspection. |
| EXPECT_FALSE( |
| DevToolsWindow::GetInstanceForInspectedWebContents(offscreen_contents)); |
| |
| // Call the API function to inspect the offscreen document. |
| auto dev_tools_function = |
| base::MakeRefCounted<api::DeveloperPrivateOpenDevToolsFunction>(); |
| api_test_utils::RunFunction( |
| dev_tools_function.get(), |
| content::JsReplace( |
| R"([{"renderViewId": $1, |
| "renderProcessId": $2, |
| "extensionId": $3 |
| }])", |
| view.render_view_id, view.render_process_id, extension->id()), |
| profile()); |
| |
| // Validate that the devtools window is now shown. |
| DevToolsWindow* dev_tools_window = |
| DevToolsWindow::GetInstanceForInspectedWebContents(offscreen_contents); |
| ASSERT_TRUE(dev_tools_window); |
| |
| // Tidy up. |
| DevToolsWindowTesting::CloseDevToolsWindowSync(dev_tools_window); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiTest, UninstallMultipleExtensions) { |
| // Load first extension. |
| static constexpr char kManifest_0[] = |
| R"({ |
| "name": "Multiple extensions uninstall test 0", |
| "manifest_version": 3, |
| "version": "0.1" |
| })"; |
| TestExtensionDir test_dir_0; |
| test_dir_0.WriteManifest(kManifest_0); |
| const Extension* extension_0 = LoadExtension(test_dir_0.UnpackedPath()); |
| ASSERT_TRUE(extension_0); |
| std::string extension_0_id = extension_0->id(); |
| |
| // Load second extension. |
| static constexpr char kManifest_1[] = |
| R"({ |
| "name": "Multiple extensions uninstall test 1", |
| "manifest_version": 3, |
| "version": "0.1" |
| })"; |
| TestExtensionDir test_dir_1; |
| test_dir_1.WriteManifest(kManifest_1); |
| const Extension* extension_1 = LoadExtension(test_dir_1.UnpackedPath()); |
| ASSERT_TRUE(extension_1); |
| std::string extension_1_id = extension_1->id(); |
| |
| auto function = base::MakeRefCounted< |
| api::DeveloperPrivateRemoveMultipleExtensionsFunction>(); |
| std::unique_ptr<ExtensionFunctionDispatcher> dispatcher = |
| std::make_unique<ExtensionFunctionDispatcher>(profile()); |
| function->SetDispatcher(dispatcher->AsWeakPtr()); |
| |
| std::string args = |
| base::StrCat({"[[\"", extension_0_id, "\", \"", extension_1_id, "\"]]"}); |
| api_test_utils::SendResponseHelper response_helper(function.get()); |
| function->SetArgs(base::test::ParseJsonList(args)); |
| function->accept_bubble_for_testing(true); |
| function->RunWithValidation().Execute(); |
| response_helper.WaitForResponse(); |
| |
| // Verify the extensions are uninstalled. |
| EXPECT_FALSE(extension_registry()->GetExtensionById( |
| extension_0_id, ExtensionRegistry::EVERYTHING)); |
| EXPECT_FALSE(extension_registry()->GetExtensionById( |
| extension_1_id, ExtensionRegistry::EVERYTHING)); |
| } |
| |
| class DeveloperPrivateApiRateExtensionTest : public DeveloperPrivateApiTest { |
| private: |
| base::test::ScopedFeatureList feature_list_{ |
| extensions_features::kCWSReviewPromptingNativeUI}; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiRateExtensionTest, |
| OpenReviewPage_NavigatesToCWS) { |
| scoped_refptr<const Extension> cws_extension = |
| ExtensionBuilder("CWS Extension") |
| .SetLocation(mojom::ManifestLocation::kInternal) |
| .AddFlags(Extension::FROM_WEBSTORE) |
| .Build(); |
| PermissionsUpdater updater(profile()); |
| updater.InitializePermissions(cws_extension.get()); |
| updater.GrantActivePermissions(cws_extension.get()); |
| extension_registrar()->AddExtension(cws_extension.get()); |
| |
| base::DictValue cws_info_dict; |
| cws_info_dict.Set("is-present", true); |
| cws_info_dict.Set("is-live", true); |
| cws_info_dict.Set("violation-type", 0); |
| ExtensionPrefs::Get(profile())->UpdateExtensionPref( |
| cws_extension->id(), "cws-info", base::Value(std::move(cws_info_dict))); |
| |
| content::WebContents* web_contents = GetActiveWebContents(); |
| ASSERT_TRUE(web_contents); |
| |
| TabListInterface* tab_list = |
| TabListInterface::From(browser_window_interface()); |
| ASSERT_TRUE(tab_list); |
| int initial_tab_count = tab_list->GetTabCount(); |
| |
| GURL expected_url = extensions::util::GetCWSWritingReviewUrl( |
| cws_extension->id(), extensions::util::CWSReviewSource::kExtensionsPage); |
| |
| content::TestNavigationObserver observer(expected_url); |
| observer.StartWatchingNewWebContents(); |
| |
| auto function = |
| base::MakeRefCounted<api::DeveloperPrivateOpenReviewPageFunction>(); |
| function->SetRenderFrameHost(web_contents->GetPrimaryMainFrame()); |
| |
| std::string args = |
| base::StringPrintf(R"(["%s"])", cws_extension->id().c_str()); |
| EXPECT_TRUE(api_test_utils::RunFunction(function.get(), args, profile())); |
| |
| observer.Wait(); |
| |
| EXPECT_EQ(initial_tab_count + 1, tab_list->GetTabCount()); |
| content::WebContents* new_tab = tab_list->GetActiveTab()->GetContents(); |
| EXPECT_NE(web_contents, new_tab); |
| |
| EXPECT_EQ(expected_url, new_tab->GetLastCommittedURL()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiRateExtensionTest, |
| OpenReviewPage_IncognitoIneligible) { |
| content::WebContents* incognito_contents = |
| PlatformOpenURLOffTheRecord(profile(), GURL("about:blank")); |
| ASSERT_TRUE(incognito_contents); |
| |
| Profile* incognito_profile = |
| Profile::FromBrowserContext(incognito_contents->GetBrowserContext()); |
| |
| scoped_refptr<const Extension> cws_extension = |
| ExtensionBuilder("CWS Extension") |
| .SetLocation(mojom::ManifestLocation::kInternal) |
| .AddFlags(Extension::FROM_WEBSTORE) |
| .Build(); |
| PermissionsUpdater updater(profile()); |
| updater.InitializePermissions(cws_extension.get()); |
| updater.GrantActivePermissions(cws_extension.get()); |
| extension_registrar()->AddExtension(cws_extension.get()); |
| |
| auto function = |
| base::MakeRefCounted<api::DeveloperPrivateOpenReviewPageFunction>(); |
| function->SetRenderFrameHost(incognito_contents->GetPrimaryMainFrame()); |
| |
| std::string args = |
| base::StringPrintf(R"(["%s"])", cws_extension->id().c_str()); |
| std::string error = api_test_utils::RunFunctionAndReturnError( |
| function.get(), args, incognito_profile); |
| EXPECT_EQ("The extension is ineligible for review prompts.", error); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiRateExtensionTest, |
| ExtensionsUI_CwsReviewPromptingEnabled) { |
| static constexpr char kScript[] = |
| "import('chrome://resources/js/load_time_data.js').then(m => " |
| "m.loadTimeData.getBoolean('cwsReviewPromptingEnabled'))"; |
| |
| content::WebContents* web_contents = GetActiveWebContents(); |
| ASSERT_TRUE(web_contents); |
| |
| // 1. Regular profile with policy allowed: cwsReviewPromptingEnabled is true. |
| ASSERT_TRUE( |
| content::NavigateToURL(web_contents, GURL("chrome://extensions"))); |
| EXPECT_EQ(true, content::EvalJs(web_contents, kScript)); |
| |
| // 2. Enterprise policy disabled: cwsReviewPromptingEnabled is false. |
| profile()->GetPrefs()->SetBoolean(prefs::kExtensionReviewPromptsAllowed, |
| false); |
| ASSERT_TRUE( |
| content::NavigateToURL(web_contents, GURL("chrome://extensions"))); |
| EXPECT_EQ(false, content::EvalJs(web_contents, kScript)); |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| // Desktop Android shows settings in a tab, where a page is addressed by URL |
| // only once SettingsInTabUrlNav is enabled. |
| class DeveloperPrivateApiSettingsUrlNavTest : public DeveloperPrivateApiTest { |
| public: |
| DeveloperPrivateApiSettingsUrlNavTest() { |
| feature_list_.InitAndEnableFeature(chrome::android::kSettingsInTabUrlNav); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // Tests that showSiteSettings opens the extension's site details page next to |
| // the chrome://extensions page that asked for it, rather than in a settings |
| // surface of its own. |
| IN_PROC_BROWSER_TEST_F(DeveloperPrivateApiSettingsUrlNavTest, |
| ShowSiteSettingsOpensSiteDetails) { |
| static constexpr char kExtensionId[] = "iodihamcpbpeioajjeobimgagajmlibd"; |
| |
| content::WebContents* web_contents = GetActiveWebContents(); |
| ASSERT_TRUE(web_contents); |
| ASSERT_TRUE( |
| content::NavigateToURL(web_contents, GURL("chrome://extensions"))); |
| |
| auto function = |
| base::MakeRefCounted<api::DeveloperPrivateShowSiteSettingsFunction>(); |
| function->SetRenderFrameHost(web_contents->GetPrimaryMainFrame()); |
| ASSERT_TRUE(api_test_utils::RunFunction( |
| function.get(), base::StringPrintf(R"(["%s"])", kExtensionId), |
| profile())); |
| |
| // The load is not awaited: settings is a native page, so what this test can |
| // meaningfully assert is the URL the new tab was sent to. |
| EXPECT_EQ(GetActiveWebContents()->GetVisibleURL(), |
| GURL(base::StrCat({"chrome://settings/siteDetails" |
| "?site=chrome-extension%3A%2F%2F", |
| kExtensionId}))); |
| } |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| } // namespace extensions |