| // 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/web_contents_delegate.h" |
| #include "content/public/test/test_renderer_host.h" |
| #include "extensions/common/url_pattern.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 |