|  | // 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 "chrome/browser/extensions/context_menu_helpers.h" | 
|  |  | 
|  | #include "content/public/browser/context_menu_params.h" | 
|  | #include "content/public/browser/page_navigator.h" | 
|  | #include "content/public/browser/web_contents_delegate.h" | 
|  | #include "content/public/test/test_renderer_host.h" | 
|  | #include "extensions/common/url_pattern.h" | 
|  | #include "services/network/public/cpp/shared_url_loader_factory.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "third_party/blink/public/mojom/context_menu/context_menu.mojom.h" | 
|  | #include "url/gurl.h" | 
|  |  | 
|  | using extensions::context_menu_helpers::ExtensionContextAndPatternMatch; | 
|  |  | 
|  | namespace extensions { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Generates a ContextMenuParams that matches the specified contexts. | 
|  | content::ContextMenuParams CreateParams(int contexts) { | 
|  | content::ContextMenuParams rv; | 
|  | rv.is_editable = false; | 
|  | rv.media_type = blink::mojom::ContextMenuDataMediaType::kNone; | 
|  | rv.page_url = GURL("http://test.page/"); | 
|  | rv.frame_url = GURL("http://test.page/"); | 
|  | rv.frame_origin = url::Origin::Create(rv.frame_url); | 
|  |  | 
|  | static constexpr char16_t selected_text[] = u"sel"; | 
|  | if (contexts & MenuItem::SELECTION) { | 
|  | rv.selection_text = selected_text; | 
|  | } | 
|  |  | 
|  | if (contexts & MenuItem::LINK) { | 
|  | rv.link_url = GURL("http://test.link/"); | 
|  | } | 
|  |  | 
|  | if (contexts & MenuItem::EDITABLE) { | 
|  | rv.is_editable = true; | 
|  | } | 
|  |  | 
|  | if (contexts & MenuItem::IMAGE) { | 
|  | rv.src_url = GURL("http://test.image/"); | 
|  | rv.media_type = blink::mojom::ContextMenuDataMediaType::kImage; | 
|  | } | 
|  |  | 
|  | if (contexts & MenuItem::VIDEO) { | 
|  | rv.src_url = GURL("http://test.video/"); | 
|  | rv.media_type = blink::mojom::ContextMenuDataMediaType::kVideo; | 
|  | } | 
|  |  | 
|  | if (contexts & MenuItem::AUDIO) { | 
|  | rv.src_url = GURL("http://test.audio/"); | 
|  | rv.media_type = blink::mojom::ContextMenuDataMediaType::kAudio; | 
|  | } | 
|  |  | 
|  | if (contexts & MenuItem::FRAME) { | 
|  | rv.is_subframe = true; | 
|  | } | 
|  |  | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | // Generates a URLPatternSet with a single pattern | 
|  | static URLPatternSet CreatePatternSet(const std::string& pattern) { | 
|  | URLPattern target(URLPattern::SCHEME_HTTP); | 
|  | target.Parse(pattern); | 
|  |  | 
|  | URLPatternSet rv; | 
|  | rv.AddPattern(target); | 
|  |  | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | class TestNavigationDelegate : public content::WebContentsDelegate { | 
|  | public: | 
|  | TestNavigationDelegate() = default; | 
|  | ~TestNavigationDelegate() override = default; | 
|  |  | 
|  | content::WebContents* OpenURLFromTab( | 
|  | content::WebContents* source, | 
|  | const content::OpenURLParams& params, | 
|  | base::OnceCallback<void(content::NavigationHandle&)> | 
|  | navigation_handle_callback) override { | 
|  | last_navigation_params_ = params; | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | const std::optional<content::OpenURLParams>& last_navigation_params() { | 
|  | return last_navigation_params_; | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::optional<content::OpenURLParams> last_navigation_params_; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class ContextMenuHelpersTest : public testing::Test { | 
|  | protected: | 
|  | ContextMenuHelpersTest() = default; | 
|  | ContextMenuHelpersTest(const ContextMenuHelpersTest&) = delete; | 
|  | ContextMenuHelpersTest& operator=(const ContextMenuHelpersTest&) = delete; | 
|  |  | 
|  | private: | 
|  | content::RenderViewHostTestEnabler rvh_test_enabler_; | 
|  | }; | 
|  |  | 
|  | TEST_F(ContextMenuHelpersTest, TargetIgnoredForPage) { | 
|  | content::ContextMenuParams params = CreateParams(0); | 
|  |  | 
|  | MenuItem::ContextList contexts; | 
|  | contexts.Add(MenuItem::PAGE); | 
|  |  | 
|  | URLPatternSet patterns = CreatePatternSet("*://test.none/*"); | 
|  |  | 
|  | EXPECT_TRUE(ExtensionContextAndPatternMatch(params, contexts, patterns)); | 
|  | } | 
|  |  | 
|  | TEST_F(ContextMenuHelpersTest, TargetCheckedForLink) { | 
|  | content::ContextMenuParams params = CreateParams(MenuItem::LINK); | 
|  |  | 
|  | MenuItem::ContextList contexts; | 
|  | contexts.Add(MenuItem::PAGE); | 
|  | contexts.Add(MenuItem::LINK); | 
|  |  | 
|  | URLPatternSet patterns = CreatePatternSet("*://test.none/*"); | 
|  |  | 
|  | EXPECT_FALSE(ExtensionContextAndPatternMatch(params, contexts, patterns)); | 
|  | } | 
|  |  | 
|  | TEST_F(ContextMenuHelpersTest, TargetCheckedForImage) { | 
|  | content::ContextMenuParams params = CreateParams(MenuItem::IMAGE); | 
|  |  | 
|  | MenuItem::ContextList contexts; | 
|  | contexts.Add(MenuItem::PAGE); | 
|  | contexts.Add(MenuItem::IMAGE); | 
|  |  | 
|  | URLPatternSet patterns = CreatePatternSet("*://test.none/*"); | 
|  |  | 
|  | EXPECT_FALSE(ExtensionContextAndPatternMatch(params, contexts, patterns)); | 
|  | } | 
|  |  | 
|  | TEST_F(ContextMenuHelpersTest, TargetCheckedForVideo) { | 
|  | content::ContextMenuParams params = CreateParams(MenuItem::VIDEO); | 
|  |  | 
|  | MenuItem::ContextList contexts; | 
|  | contexts.Add(MenuItem::PAGE); | 
|  | contexts.Add(MenuItem::VIDEO); | 
|  |  | 
|  | URLPatternSet patterns = CreatePatternSet("*://test.none/*"); | 
|  |  | 
|  | EXPECT_FALSE(ExtensionContextAndPatternMatch(params, contexts, patterns)); | 
|  | } | 
|  |  | 
|  | TEST_F(ContextMenuHelpersTest, TargetCheckedForAudio) { | 
|  | content::ContextMenuParams params = CreateParams(MenuItem::AUDIO); | 
|  |  | 
|  | MenuItem::ContextList contexts; | 
|  | contexts.Add(MenuItem::PAGE); | 
|  | contexts.Add(MenuItem::AUDIO); | 
|  |  | 
|  | URLPatternSet patterns = CreatePatternSet("*://test.none/*"); | 
|  |  | 
|  | EXPECT_FALSE(ExtensionContextAndPatternMatch(params, contexts, patterns)); | 
|  | } | 
|  |  | 
|  | TEST_F(ContextMenuHelpersTest, MatchWhenLinkedImageMatchesTarget) { | 
|  | content::ContextMenuParams params = | 
|  | CreateParams(MenuItem::IMAGE | MenuItem::LINK); | 
|  |  | 
|  | MenuItem::ContextList contexts; | 
|  | contexts.Add(MenuItem::LINK); | 
|  | contexts.Add(MenuItem::IMAGE); | 
|  |  | 
|  | URLPatternSet patterns = CreatePatternSet("*://test.link/*"); | 
|  |  | 
|  | EXPECT_TRUE(ExtensionContextAndPatternMatch(params, contexts, patterns)); | 
|  | } | 
|  |  | 
|  | TEST_F(ContextMenuHelpersTest, MatchWhenLinkedImageMatchesSource) { | 
|  | content::ContextMenuParams params = | 
|  | CreateParams(MenuItem::IMAGE | MenuItem::LINK); | 
|  |  | 
|  | MenuItem::ContextList contexts; | 
|  | contexts.Add(MenuItem::LINK); | 
|  | contexts.Add(MenuItem::IMAGE); | 
|  |  | 
|  | URLPatternSet patterns = CreatePatternSet("*://test.image/*"); | 
|  |  | 
|  | EXPECT_TRUE(ExtensionContextAndPatternMatch(params, contexts, patterns)); | 
|  | } | 
|  |  | 
|  | TEST_F(ContextMenuHelpersTest, NoMatchWhenLinkedImageMatchesNeither) { | 
|  | content::ContextMenuParams params = | 
|  | CreateParams(MenuItem::IMAGE | MenuItem::LINK); | 
|  |  | 
|  | MenuItem::ContextList contexts; | 
|  | contexts.Add(MenuItem::LINK); | 
|  | contexts.Add(MenuItem::IMAGE); | 
|  |  | 
|  | URLPatternSet patterns = CreatePatternSet("*://test.none/*"); | 
|  |  | 
|  | EXPECT_FALSE(ExtensionContextAndPatternMatch(params, contexts, patterns)); | 
|  | } | 
|  |  | 
|  | TEST_F(ContextMenuHelpersTest, TargetIgnoredForFrame) { | 
|  | content::ContextMenuParams params = CreateParams(MenuItem::FRAME); | 
|  |  | 
|  | MenuItem::ContextList contexts; | 
|  | contexts.Add(MenuItem::FRAME); | 
|  |  | 
|  | URLPatternSet patterns = CreatePatternSet("*://test.none/*"); | 
|  |  | 
|  | EXPECT_TRUE(ExtensionContextAndPatternMatch(params, contexts, patterns)); | 
|  | } | 
|  |  | 
|  | TEST_F(ContextMenuHelpersTest, TargetIgnoredForEditable) { | 
|  | content::ContextMenuParams params = CreateParams(MenuItem::EDITABLE); | 
|  |  | 
|  | MenuItem::ContextList contexts; | 
|  | contexts.Add(MenuItem::EDITABLE); | 
|  |  | 
|  | URLPatternSet patterns = CreatePatternSet("*://test.none/*"); | 
|  |  | 
|  | EXPECT_TRUE(ExtensionContextAndPatternMatch(params, contexts, patterns)); | 
|  | } | 
|  |  | 
|  | TEST_F(ContextMenuHelpersTest, TargetIgnoredForSelection) { | 
|  | content::ContextMenuParams params = CreateParams(MenuItem::SELECTION); | 
|  |  | 
|  | MenuItem::ContextList contexts; | 
|  | contexts.Add(MenuItem::SELECTION); | 
|  |  | 
|  | URLPatternSet patterns = CreatePatternSet("*://test.none/*"); | 
|  |  | 
|  | EXPECT_TRUE(ExtensionContextAndPatternMatch(params, contexts, patterns)); | 
|  | } | 
|  |  | 
|  | TEST_F(ContextMenuHelpersTest, TargetIgnoredForSelectionOnLink) { | 
|  | content::ContextMenuParams params = | 
|  | CreateParams(MenuItem::SELECTION | MenuItem::LINK); | 
|  |  | 
|  | MenuItem::ContextList contexts; | 
|  | contexts.Add(MenuItem::SELECTION); | 
|  | contexts.Add(MenuItem::LINK); | 
|  |  | 
|  | URLPatternSet patterns = CreatePatternSet("*://test.none/*"); | 
|  |  | 
|  | EXPECT_TRUE(ExtensionContextAndPatternMatch(params, contexts, patterns)); | 
|  | } | 
|  |  | 
|  | TEST_F(ContextMenuHelpersTest, TargetIgnoredForSelectionOnImage) { | 
|  | content::ContextMenuParams params = | 
|  | CreateParams(MenuItem::SELECTION | MenuItem::IMAGE); | 
|  |  | 
|  | MenuItem::ContextList contexts; | 
|  | contexts.Add(MenuItem::SELECTION); | 
|  | contexts.Add(MenuItem::IMAGE); | 
|  |  | 
|  | URLPatternSet patterns = CreatePatternSet("*://test.none/*"); | 
|  |  | 
|  | EXPECT_TRUE(ExtensionContextAndPatternMatch(params, contexts, patterns)); | 
|  | } | 
|  |  | 
|  | }  // namespace extensions |