From 8e18d4a1d9b275332434805894c4fd0763a662ff Mon Sep 17 00:00:00 2001 From: Simon Hong Date: Thu, 12 Jan 2023 13:10:14 +0900 Subject: [PATCH] Added TextRecognitionBrowserTest browser test This test extract "brave" text from test image. --- browser/ui/BUILD.gn | 8 + browser/ui/text_recognition_browsertest.cc | 150 ++++++++++++++++++ .../views/text_recognition_dialog_tracker.cc | 29 ++++ .../views/text_recognition_dialog_tracker.h | 43 +++++ .../ui/views/text_recognition_dialog_view.cc | 54 +------ .../ui/views/text_recognition_dialog_view.h | 6 + .../render_view_context_menu.cc | 3 +- test/data/text_recognition/image.html | 6 + test/data/text_recognition/image.png | Bin 0 -> 18344 bytes 9 files changed, 250 insertions(+), 49 deletions(-) create mode 100644 browser/ui/text_recognition_browsertest.cc create mode 100644 browser/ui/views/text_recognition_dialog_tracker.cc create mode 100644 browser/ui/views/text_recognition_dialog_tracker.h create mode 100644 test/data/text_recognition/image.html create mode 100644 test/data/text_recognition/image.png diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn index fb1c311ed235..08fd4babe0d4 100644 --- a/browser/ui/BUILD.gn +++ b/browser/ui/BUILD.gn @@ -447,6 +447,8 @@ source_set("ui") { if (enable_text_recognition) { sources += [ + "views/text_recognition_dialog_tracker.cc", + "views/text_recognition_dialog_tracker.h", "views/text_recognition_dialog_view.cc", "views/text_recognition_dialog_view.h", ] @@ -801,16 +803,22 @@ source_set("browser_tests") { ] deps = [ + "//brave/app:command_ids", "//brave/browser/ui", "//brave/components/constants", "//chrome/browser", "//chrome/browser/devtools:test_support", "//chrome/browser/profiles:profile", "//chrome/browser/ui", + "//chrome/test:test_support", "//chrome/test:test_support_ui", "//components/javascript_dialogs", "//components/prefs", "//content/test:test_support", ] + + if (enable_text_recognition) { + sources += [ "text_recognition_browsertest.cc" ] + } } } diff --git a/browser/ui/text_recognition_browsertest.cc b/browser/ui/text_recognition_browsertest.cc new file mode 100644 index 000000000000..d3f98c84124a --- /dev/null +++ b/browser/ui/text_recognition_browsertest.cc @@ -0,0 +1,150 @@ +/* Copyright (c) 2023 The Brave Authors. All rights reserved. + * 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 https://mozilla.org/MPL/2.0/. */ + +#include + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/functional/callback_helpers.h" +#include "base/memory/weak_ptr.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/test/bind.h" +#include "brave/app/brave_command_ids.h" +#include "brave/browser/ui/browser_dialogs.h" +#include "brave/browser/ui/views/text_recognition_dialog_tracker.h" +#include "brave/browser/ui/views/text_recognition_dialog_view.h" +#include "brave/components/constants/brave_paths.h" +#include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/test/browser_test.h" +#include "net/dns/mock_host_resolver.h" + +namespace { + +constexpr char kEmbeddedTestServerDirectory[] = "text_recognition"; + +} // namespace + +class TextRecognitionBrowserTest : public InProcessBrowserTest { + public: + void SetUpOnMainThread() override { + host_resolver()->AddRule("*", "127.0.0.1"); + content::SetupCrossSiteRedirector(embedded_test_server()); + + brave::RegisterPathProvider(); + base::FilePath test_data_dir; + base::PathService::Get(brave::DIR_TEST_DATA, &test_data_dir); + test_data_dir = test_data_dir.AppendASCII(kEmbeddedTestServerDirectory); + embedded_test_server()->ServeFilesFromDirectory(test_data_dir); + + ASSERT_TRUE(embedded_test_server()->Start()); + image_html_url_ = embedded_test_server()->GetURL("a.com", "/image.html"); + } + + void OnGetTextFromImage(const std::vector& text) { + // Test image has "brave" text. + EXPECT_EQ("brave", text[0]); + run_loop_->Quit(); + } + + void OnGetImageForTextCopy(base::WeakPtr web_contents, + const SkBitmap& image) { + if (!web_contents) + return; + + brave::ShowTextRecognitionDialog(web_contents.get(), image); + } + + void WaitUntil(base::RepeatingCallback condition) { + if (condition.Run()) + return; + + base::RepeatingTimer scheduler; + scheduler.Start(FROM_HERE, base::Milliseconds(100), + base::BindLambdaForTesting([this, &condition]() { + if (condition.Run()) + run_loop_->Quit(); + })); + Run(); + } + + void Run() { + run_loop_ = std::make_unique(); + run_loop()->Run(); + } + + base::RunLoop* run_loop() const { return run_loop_.get(); } + + GURL image_html_url_; + std::unique_ptr run_loop_; +}; + +IN_PROC_BROWSER_TEST_F(TextRecognitionBrowserTest, TextRecognitionTest) { + content::ContextMenuParams params; + params.media_type = blink::mojom::ContextMenuDataMediaType::kImage; + + // kImage type can have copy text from image menu entry. + { + TestRenderViewContextMenu menu(*browser() + ->tab_strip_model() + ->GetActiveWebContents() + ->GetPrimaryMainFrame(), + params); + menu.Init(); + + EXPECT_TRUE(menu.IsItemPresent(IDC_CONTENT_CONTEXT_COPY_TEXT_FROM_IMAGE)); + } + + // Other type should not have. + params.media_type = blink::mojom::ContextMenuDataMediaType::kVideo; + { + TestRenderViewContextMenu menu(*browser() + ->tab_strip_model() + ->GetActiveWebContents() + ->GetPrimaryMainFrame(), + params); + menu.Init(); + + EXPECT_FALSE(menu.IsItemPresent(IDC_CONTENT_CONTEXT_COPY_TEXT_FROM_IMAGE)); + } + + content::WebContents* contents = + browser()->tab_strip_model()->GetActiveWebContents(); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), image_html_url_)); + ASSERT_TRUE(WaitForLoadStop(contents)); + + // Using (10, 10) position will be fine because test image is set at (0, 0). + browser() + ->tab_strip_model() + ->GetActiveWebContents() + ->GetPrimaryMainFrame() + ->GetImageAt( + 10, 10, + base::BindOnce(&TextRecognitionBrowserTest::OnGetImageForTextCopy, + base::Unretained(this), contents->GetWeakPtr())); + TextRecognitionDialogTracker::CreateForWebContents(contents); + auto* dialog_tracker = + TextRecognitionDialogTracker::FromWebContents(contents); + + // Wait till text recognition dialog is launched. + WaitUntil(base::BindLambdaForTesting( + [&]() { return !!dialog_tracker->active_dialog(); })); + + TextRecognitionDialogView* text_recognition_dialog = + static_cast( + dialog_tracker->active_dialog()->widget_delegate()); + + // OnGetTextFromImage() verifies extracted text from test image. + text_recognition_dialog->on_get_text_callback_for_test_ = base::BindOnce( + &TextRecognitionBrowserTest::OnGetTextFromImage, base::Unretained(this)); + + Run(); +} diff --git a/browser/ui/views/text_recognition_dialog_tracker.cc b/browser/ui/views/text_recognition_dialog_tracker.cc new file mode 100644 index 000000000000..6b0cb06f4168 --- /dev/null +++ b/browser/ui/views/text_recognition_dialog_tracker.cc @@ -0,0 +1,29 @@ +/* Copyright (c) 2023 The Brave Authors. All rights reserved. + * 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 https://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/ui/views/text_recognition_dialog_tracker.h" + +TextRecognitionDialogTracker::TextRecognitionDialogTracker( + content::WebContents* web_contents) + : content::WebContentsUserData( + *web_contents) {} + +TextRecognitionDialogTracker::~TextRecognitionDialogTracker() = default; + +void TextRecognitionDialogTracker::SetActiveDialog(views::Widget* widget) { + DCHECK(!active_dialog_ && !observation_.IsObserving()); + active_dialog_ = widget; + observation_.Observe(widget); +} + +void TextRecognitionDialogTracker::OnWidgetDestroying(views::Widget* widget) { + DCHECK_EQ(active_dialog_, widget); + DCHECK(observation_.IsObservingSource(widget)); + + observation_.Reset(); + active_dialog_ = nullptr; +} + +WEB_CONTENTS_USER_DATA_KEY_IMPL(TextRecognitionDialogTracker); diff --git a/browser/ui/views/text_recognition_dialog_tracker.h b/browser/ui/views/text_recognition_dialog_tracker.h new file mode 100644 index 000000000000..659d37d361e0 --- /dev/null +++ b/browser/ui/views/text_recognition_dialog_tracker.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2023 The Brave Authors. All rights reserved. + * 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_UI_VIEWS_TEXT_RECOGNITION_DIALOG_TRACKER_H_ +#define BRAVE_BROWSER_UI_VIEWS_TEXT_RECOGNITION_DIALOG_TRACKER_H_ + +#include "base/memory/raw_ptr.h" +#include "base/scoped_observation.h" +#include "content/public/browser/web_contents_user_data.h" +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_observer.h" + +// Tracks whether text recognition dialog is active or not for WebContents. +class TextRecognitionDialogTracker + : public content::WebContentsUserData, + public views::WidgetObserver { + public: + TextRecognitionDialogTracker(const TextRecognitionDialogTracker&) = delete; + TextRecognitionDialogTracker& operator=(const TextRecognitionDialogTracker&) = + delete; + ~TextRecognitionDialogTracker() override; + + void SetActiveDialog(views::Widget* widget); + + views::Widget* active_dialog() { return active_dialog_; } + + private: + friend class content::WebContentsUserData; + explicit TextRecognitionDialogTracker(content::WebContents* web_contents); + + // views::WidgetObserver overrides + void OnWidgetDestroying(views::Widget* widget) override; + + raw_ptr active_dialog_ = nullptr; + base::ScopedObservation observation_{ + this}; + + WEB_CONTENTS_USER_DATA_KEY_DECL(); +}; + +#endif // BRAVE_BROWSER_UI_VIEWS_TEXT_RECOGNITION_DIALOG_TRACKER_H_ diff --git a/browser/ui/views/text_recognition_dialog_view.cc b/browser/ui/views/text_recognition_dialog_view.cc index e47bc7a8cbd8..57a063dcc502 100644 --- a/browser/ui/views/text_recognition_dialog_view.cc +++ b/browser/ui/views/text_recognition_dialog_view.cc @@ -6,18 +6,18 @@ #include "brave/browser/ui/views/text_recognition_dialog_view.h" #include +#include #include "base/bind.h" -#include "base/scoped_observation.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/task/task_traits.h" #include "base/task/thread_pool.h" +#include "brave/browser/ui/views/text_recognition_dialog_tracker.h" #include "brave/components/l10n/common/localization_util.h" #include "brave/components/text_recognition/browser/text_recognition.h" #include "brave/grit/brave_generated_resources.h" #include "components/constrained_window/constrained_window_views.h" -#include "content/public/browser/web_contents_user_data.h" #include "ui/base/clipboard/scoped_clipboard_writer.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/gfx/geometry/insets.h" @@ -25,54 +25,8 @@ #include "ui/views/controls/scroll_view.h" #include "ui/views/layout/flex_layout.h" #include "ui/views/widget/widget.h" -#include "ui/views/widget/widget_observer.h" #include "ui/views/window/dialog_client_view.h" -namespace { - -// Tracks whether text recognition dialog is active or not for WebContents. -class TextRecognitionDialogTracker - : public content::WebContentsUserData, - public views::WidgetObserver { - public: - TextRecognitionDialogTracker(const TextRecognitionDialogTracker&) = delete; - TextRecognitionDialogTracker& operator=(const TextRecognitionDialogTracker&) = - delete; - ~TextRecognitionDialogTracker() override = default; - - void SetActiveDialog(views::Widget* widget) { - DCHECK(!active_dialog_ && !observation_.IsObserving()); - active_dialog_ = widget; - observation_.Observe(widget); - } - - views::Widget* active_dialog() { return active_dialog_; } - - private: - friend class content::WebContentsUserData; - explicit TextRecognitionDialogTracker(content::WebContents* web_contents) - : content::WebContentsUserData( - *web_contents) {} - - // views::WidgetObserver overrides - void OnWidgetDestroying(views::Widget* widget) override { - DCHECK_EQ(active_dialog_, widget); - DCHECK(observation_.IsObservingSource(widget)); - observation_.Reset(); - active_dialog_ = nullptr; - } - - raw_ptr active_dialog_ = nullptr; - base::ScopedObservation observation_{ - this}; - - WEB_CONTENTS_USER_DATA_KEY_DECL(); -}; - -WEB_CONTENTS_USER_DATA_KEY_IMPL(TextRecognitionDialogTracker); - -} // namespace - namespace brave { void ShowTextRecognitionDialog(content::WebContents* web_contents, @@ -148,6 +102,10 @@ void TextRecognitionDialogView::OnGetTextFromImage( const std::vector& text) { UpdateContents(text); AdjustWidgetSize(); + + if (on_get_text_callback_for_test_) { + std::move(on_get_text_callback_for_test_).Run(text); + } } void TextRecognitionDialogView::UpdateContents( diff --git a/browser/ui/views/text_recognition_dialog_view.h b/browser/ui/views/text_recognition_dialog_view.h index 764359e95b71..2394cc3d61a8 100644 --- a/browser/ui/views/text_recognition_dialog_view.h +++ b/browser/ui/views/text_recognition_dialog_view.h @@ -9,6 +9,7 @@ #include #include +#include "base/callback_forward.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "ui/base/metadata/metadata_header_macros.h" @@ -34,6 +35,8 @@ class TextRecognitionDialogView : public views::DialogDelegateView { void StartExtractingText(const SkBitmap& image); private: + FRIEND_TEST_ALL_PREFIXES(TextRecognitionBrowserTest, TextRecognitionTest); + void OnGetTextFromImage(const std::vector& text); // Show |text| in this dialog and copy it to clipboard. @@ -42,6 +45,9 @@ class TextRecognitionDialogView : public views::DialogDelegateView { raw_ptr header_label_ = nullptr; raw_ptr scroll_view_ = nullptr; + + base::OnceCallback&)> + on_get_text_callback_for_test_; base::WeakPtrFactory weak_factory_{this}; }; diff --git a/chromium_src/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chromium_src/chrome/browser/renderer_context_menu/render_view_context_menu.cc index 27dfaa24b771..838f2aee16c8 100644 --- a/chromium_src/chrome/browser/renderer_context_menu/render_view_context_menu.cc +++ b/chromium_src/chrome/browser/renderer_context_menu/render_view_context_menu.cc @@ -279,10 +279,11 @@ void BraveRenderViewContextMenu::ExecuteCommand(int id, int event_flags) { #if BUILDFLAG(ENABLE_TEXT_RECOGNITION) void BraveRenderViewContextMenu::CopyTextFromImage() { RenderFrameHost* frame_host = GetRenderFrameHost(); - if (frame_host) + if (frame_host) { frame_host->GetImageAt(params_.x, params_.y, base::BindOnce(OnGetImageForTextCopy, source_web_contents_->GetWeakPtr())); + } } #endif diff --git a/test/data/text_recognition/image.html b/test/data/text_recognition/image.html new file mode 100644 index 000000000000..189dcb9abde0 --- /dev/null +++ b/test/data/text_recognition/image.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/test/data/text_recognition/image.png b/test/data/text_recognition/image.png new file mode 100644 index 0000000000000000000000000000000000000000..5657746ba549b31312a6fa1036620fe26c986a51 GIT binary patch literal 18344 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

>;_J)2COcPiP-EK6 zuHtMzto&L<>G)rB>i7!z{w=@zR@weTk6*{06o6D@U+@Rr=8oSYk3lKtfqoW{y!pLh z7nS7$WbE-;$7`pG;1YE7>|FSKC8%sP!T!H0_j6Gl%zJTbom6IDUpQ;Gq$Bd8Jnxe> zpo2XE04AMD+q#an&}IB+t*7|%{`yD|yFIhR^lw;xv)`S`8=-3moGA~wG;P4h-h=LN z^Ls^h@zqtdrP;9$1ODv{7!M~Orgj{05_(Q7J8w&Iz4tnJ&nsnGPM7lv`xc_JrxIL~_JzgtH?tbRpL) zmvWajHrvd#6|)d>C*_iDQZtuPIjhn0KF|C2@A=&Y}x9DOXuFPRQDm$w3R*3{h_55#V+@d)mon4mbWG}SMr<-#<(CwQ(!=!<4b z2dgxrZJ8Sh8SMdwBLZ7}Y<5u0u?%JNMZvh%KQGMSQ(I+gB%M`Ms6Lzt4USOY%*Ix2 zJd&~>kE6}01s$wH|2Vyo;xMe-{hz^+wG5=W@38lWMaFNJ((~2;!sH|%au3}HGlmUO zf5jc8?Hp<$Pnt=hJ&!!Z-M(r-^ruP%9e|-?)H=LpEc&!ojT_g;Avvdp$s5}WBt8KuFKA z6n5Y+JX0qw^(aVn7?^_+^Ml&LL92x2lr2>J3nKmpMv1t59NmPweBSm^wjV0IF+_hu z?#SphcdW?I(J0R@w;-LQ1AN@N;ClNwXH=Kz?V-jhg#1bV8RzgCP;2^LWX7RXK>Fe3 z{k?SrToX(Fv~x$%V~>7k7f-3v&xrT1Z&n5=A*(dEV`a=bayqK|7f-%+q@BqfxoXV~ zLiFurvxR}Tz$|`P9^YziI^qJ75SF6R4aq@mw6DhoCzxyd^J6f6a&&(&K?o}CIM&N!2(VQJj9j2#qD1i%`B;59Q$qCAroqIc~UD&GX!Ei);x z^fvUzY>isxr8xgN`>hClUo~n3k<{IvN%zvjh$Msye)9}632(4n`!BRZD$kw1Cf^49n3X$9xxE zHC0cXm96t91Pn($(?-Xy8m!=2%03vOQ-gPd+u6F0Kw;(2|^$6b=5H zO_toO?XP8eFNhKITSgwby?I(JrJNHo8~5j7=$j!J)VCwBOeYDowZBhQ+7$g#K7xdi zml5MqA10Z1Okq6PvrEkTl^=1mtw&1trClSa_4MFEe4~|nJ`1lXZxFn=YDjZaLQZC_ zB`8qqfXm_p?u~${kS`8)KvY)FMQOtVUb^H|0aZGWMRk3e8W-b3W zaVD6wKuS0%$=d&wP_XwC>Gkz;(I$K0Z1i7@>@XNa;ovA?Ei9$O&)lF(Dnk-wE-*Q~ zeMXhXHtn++ZMgex(+%V40^$o7R+|t4TJy`dQPraQRlvv