From 9cf444c6d87e98ff93218aa3876f90bea6695a0c Mon Sep 17 00:00:00 2001 From: Simon Hong Date: Mon, 2 Jan 2023 11:30:56 +0900 Subject: [PATCH 01/10] Introduced copy text from image feature on macOS fix https://github.com/brave/brave-browser/issues/27513 "Copy Text From Image" entry is added to render view's context menu. It gets bitmap data from renderer via LocalFrame::GetImage() mojom interface, and then passed it to TextRecognitionDialog to extract and show text from that image. --- app/BUILD.gn | 2 + app/brave_command_ids.h | 1 + app/brave_generated_resources.grd | 19 +++ browser/ui/BUILD.gn | 10 ++ browser/ui/browser_dialogs.h | 9 ++ .../ui/views/text_recognition_dialog_view.cc | 126 ++++++++++++++++++ .../ui/views/text_recognition_dialog_view.h | 45 +++++++ chromium_src/chrome/browser/DEPS | 1 + .../render_view_context_menu.cc | 49 ++++++- .../render_view_context_menu.h | 12 +- chromium_src/chrome/browser/sources.gni | 2 + .../renderer_host/render_frame_host_impl.cc | 20 +++ .../renderer_host/render_frame_host_impl.h | 21 +++ .../public/browser/render_frame_host.h | 20 +++ .../blink/public/mojom/frame/frame.mojom | 15 +++ .../blink/renderer/core/frame/local_frame.cc | 79 +++++++++++ .../blink/renderer/core/frame/local_frame.h | 20 +++ .../core/frame/local_frame_mojo_handler.cc | 17 +++ .../core/frame/local_frame_mojo_handler.h | 20 +++ components/text_recognition/DEPS | 5 + components/text_recognition/browser/BUILD.gn | 28 ++++ .../browser/text_recognition.h | 17 +++ .../browser/text_recognition.mm | 75 +++++++++++ .../common/buildflags/BUILD.gn | 12 ++ .../common/buildflags/buildflags.gni | 8 ++ 25 files changed, 627 insertions(+), 6 deletions(-) create mode 100644 browser/ui/views/text_recognition_dialog_view.cc create mode 100644 browser/ui/views/text_recognition_dialog_view.h create mode 100644 chromium_src/content/browser/renderer_host/render_frame_host_impl.cc create mode 100644 chromium_src/content/browser/renderer_host/render_frame_host_impl.h create mode 100644 chromium_src/content/public/browser/render_frame_host.h create mode 100644 chromium_src/third_party/blink/public/mojom/frame/frame.mojom create mode 100644 chromium_src/third_party/blink/renderer/core/frame/local_frame.h create mode 100644 chromium_src/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc create mode 100644 chromium_src/third_party/blink/renderer/core/frame/local_frame_mojo_handler.h create mode 100644 components/text_recognition/DEPS create mode 100644 components/text_recognition/browser/BUILD.gn create mode 100644 components/text_recognition/browser/text_recognition.h create mode 100644 components/text_recognition/browser/text_recognition.mm create mode 100644 components/text_recognition/common/buildflags/BUILD.gn create mode 100644 components/text_recognition/common/buildflags/buildflags.gni diff --git a/app/BUILD.gn b/app/BUILD.gn index 9b569e65c42a..936e92f32230 100644 --- a/app/BUILD.gn +++ b/app/BUILD.gn @@ -6,6 +6,7 @@ import("//brave/browser/shell_integrations/buildflags/buildflags.gni") import("//brave/components/brave_vpn/common/buildflags/buildflags.gni") import("//brave/components/speedreader/common/buildflags/buildflags.gni") +import("//brave/components/text_recognition/common/buildflags/buildflags.gni") import("//brave/resources/brave_grit.gni") import("//build/config/features.gni") import("//build/config/locales.gni") @@ -19,6 +20,7 @@ brave_grit("brave_generated_resources_grit") { "enable_speedreader=$enable_speedreader", "enable_brave_vpn=$enable_brave_vpn", "enable_pin_shortcut=$enable_pin_shortcut", + "enable_text_recognition=$enable_text_recognition", ] source = "brave_generated_resources.grd" output_dir = "$root_gen_dir/brave" diff --git a/app/brave_command_ids.h b/app/brave_command_ids.h index 82cb09673e50..78e947e7d5d5 100644 --- a/app/brave_command_ids.h +++ b/app/brave_command_ids.h @@ -51,6 +51,7 @@ #define IDC_COPY_CLEAN_LINK 56040 #define IDC_TOGGLE_TAB_MUTE 56041 #define IDC_SIDEBAR_TOGGLE_POSITION 56042 +#define IDC_CONTENT_CONTEXT_COPY_TEXT_FROM_IMAGE 56043 #define IDC_CONTENT_CONTEXT_IMPORT_IPNS_KEYS_START 56100 #define IDC_CONTENT_CONTEXT_IMPORT_IPNS_KEYS_END 56199 diff --git a/app/brave_generated_resources.grd b/app/brave_generated_resources.grd index 8fc645b07260..2e7c1393fae6 100644 --- a/app/brave_generated_resources.grd +++ b/app/brave_generated_resources.grd @@ -896,6 +896,25 @@ Or change later at $2brave://settings/ext + + + + Copy Text From Image + + + Close + + + Copying text from image... + + + Text copied from image + + + Text copy failed + + + diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn index 46e9454e533a..64d47edbf4c1 100644 --- a/browser/ui/BUILD.gn +++ b/browser/ui/BUILD.gn @@ -12,6 +12,7 @@ import("//brave/components/ipfs/buildflags/buildflags.gni") import("//brave/components/ntp_background_images/buildflags/buildflags.gni") import("//brave/components/playlist/common/buildflags/buildflags.gni") import("//brave/components/speedreader/common/buildflags/buildflags.gni") +import("//brave/components/text_recognition/common/buildflags/buildflags.gni") import("//brave/components/tor/buildflags/buildflags.gni") import("//build/config/features.gni") import("//chrome/common/features.gni") @@ -443,6 +444,15 @@ source_set("ui") { ] } + if (enable_text_recognition) { + sources += [ + "views/text_recognition_dialog_view.cc", + "views/text_recognition_dialog_view.h", + ] + + deps += [ "//brave/components/text_recognition/browser" ] + } + # This is no longer compiled into Chromium on Android, but we still # need it if (is_android) { diff --git a/browser/ui/browser_dialogs.h b/browser/ui/browser_dialogs.h index 2eb4b26549cb..a4e3d6e408ce 100644 --- a/browser/ui/browser_dialogs.h +++ b/browser/ui/browser_dialogs.h @@ -9,6 +9,11 @@ #include "base/callback_forward.h" class Browser; +class SkBitmap; + +namespace content { +class WebContents; +} // namespace content namespace brave { @@ -18,6 +23,10 @@ void ShowCrashReportPermissionAskDialog(Browser* browser); // Run |callback| when dialog closed. void ShowObsoleteSystemConfirmDialog(base::OnceCallback callback); +// Show web modal dialog for showing text that recognized from |image|. +void ShowTextRecognitionDialog(content::WebContents* web_contents, + const SkBitmap& image); + } // namespace brave #endif // BRAVE_BROWSER_UI_BROWSER_DIALOGS_H_ diff --git a/browser/ui/views/text_recognition_dialog_view.cc b/browser/ui/views/text_recognition_dialog_view.cc new file mode 100644 index 000000000000..c47374c514b1 --- /dev/null +++ b/browser/ui/views/text_recognition_dialog_view.cc @@ -0,0 +1,126 @@ +/* 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_view.h" + +#include + +#include "base/bind.h" +#include "base/strings/utf_string_conversions.h" +#include "base/task/task_traits.h" +#include "base/task/thread_pool.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 "ui/base/clipboard/scoped_clipboard_writer.h" +#include "ui/base/metadata/metadata_impl_macros.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/views/controls/label.h" +#include "ui/views/controls/scroll_view.h" +#include "ui/views/layout/flex_layout.h" +#include "ui/views/widget/widget.h" +#include "ui/views/window/dialog_client_view.h" + +namespace brave { + +void ShowTextRecognitionDialog(content::WebContents* web_contents, + const SkBitmap& image) { + constrained_window::ShowWebModalDialogViews( + new TextRecognitionDialogView(image), web_contents) + ->Show(); +} + +} // namespace brave + +TextRecognitionDialogView::TextRecognitionDialogView(const SkBitmap& image) { + SetModalType(ui::MODAL_TYPE_CHILD); + SetButtons(ui::DIALOG_BUTTON_OK); + SetButtonLabel(ui::DIALOG_BUTTON_OK, + brave_l10n::GetLocalizedResourceUTF16String( + IDS_TEXT_RECOG_DIALOG_CLOSE_BUTTON)); + SetShowCloseButton(false); + + SetLayoutManager(std::make_unique()) + ->SetOrientation(views::LayoutOrientation::kVertical) + .SetMainAxisAlignment(views::LayoutAlignment::kStart) + .SetInteriorMargin(gfx::Insets::TLBR(24, 26, 0, 26)); + + header_label_ = AddChildView(std::make_unique()); + const int size_diff = 14 - views::Label::GetDefaultFontList().GetFontSize(); + header_label_->SetFontList( + views::Label::GetDefaultFontList() + .DeriveWithSizeDelta(size_diff) + .DeriveWithWeight(gfx::Font::Weight::SEMIBOLD)); + header_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + header_label_->SetProperty(views::kMarginsKey, + gfx::Insets::TLBR(0, 0, 10, 0)); + + if (image.empty()) { + UpdateContents({}); + return; + } + + StartExtractingText(image); +} + +TextRecognitionDialogView::~TextRecognitionDialogView() = default; + +void TextRecognitionDialogView::StartExtractingText(const SkBitmap& image) { + header_label_->SetText(brave_l10n::GetLocalizedResourceUTF16String( + IDS_TEXT_RECOG_DIALOG_HEADER_IN_PROGRESS)); + + base::ThreadPool::PostTaskAndReplyWithResult( + FROM_HERE, + {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, + base::BindOnce(&GetTextFromImage, image), + base::BindOnce(&TextRecognitionDialogView::OnGetTextFromImage, + weak_factory_.GetWeakPtr())); +} + +void TextRecognitionDialogView::OnGetTextFromImage( + const std::vector& text) { + UpdateContents(text); + AdjustWidgetSize(); +} + +void TextRecognitionDialogView::UpdateContents( + const std::vector& text) { + if (text.empty()) { + header_label_->SetText(brave_l10n::GetLocalizedResourceUTF16String( + IDS_TEXT_RECOG_DIALOG_HEADER_FAILED)); + return; + } + + header_label_->SetText(brave_l10n::GetLocalizedResourceUTF16String( + IDS_TEXT_RECOG_DIALOG_HEADER_COMPLETE)); + + // Treat each string in |text| as a separated line string. + std::string unified_string; + for (const auto& t : text) { + unified_string += t; + unified_string += '\n'; + } + + ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste) + .WriteText(base::UTF8ToUTF16(unified_string)); + + auto* scroll_view = AddChildView(std::make_unique()); + scroll_view->SetProperty(views::kMarginsKey, gfx::Insets::VH(0, 10)); + scroll_view->ClipHeightTo(0, 350); + auto* label = scroll_view->SetContents( + std::make_unique(base::UTF8ToUTF16(unified_string))); + + label->SetHorizontalAlignment(gfx::ALIGN_LEFT); + label->SetSelectable(true); + label->SetMultiLine(true); +} + +void TextRecognitionDialogView::AdjustWidgetSize() { + GetWidget()->SetSize(GetDialogClientView()->GetPreferredSize()); +} + +BEGIN_METADATA(TextRecognitionDialogView, views::DialogDelegateView) +END_METADATA diff --git a/browser/ui/views/text_recognition_dialog_view.h b/browser/ui/views/text_recognition_dialog_view.h new file mode 100644 index 000000000000..12d7151fd271 --- /dev/null +++ b/browser/ui/views/text_recognition_dialog_view.h @@ -0,0 +1,45 @@ +/* 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_VIEW_H_ +#define BRAVE_BROWSER_UI_VIEWS_TEXT_RECOGNITION_DIALOG_VIEW_H_ + +#include +#include + +#include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" +#include "ui/base/metadata/metadata_header_macros.h" +#include "ui/views/window/dialog_delegate.h" + +class SkBitmap; + +namespace views { +class Label; +} // namespace views + +class TextRecognitionDialogView : public views::DialogDelegateView { + public: + METADATA_HEADER(TextRecognitionDialogView); + + explicit TextRecognitionDialogView(const SkBitmap& image); + TextRecognitionDialogView(const TextRecognitionDialogView&) = delete; + TextRecognitionDialogView& operator=(const TextRecognitionDialogView&) = + delete; + ~TextRecognitionDialogView() override; + + private: + void StartExtractingText(const SkBitmap& image); + void OnGetTextFromImage(const std::vector& text); + + // Show |text| in this dialog and copy it to clipboard. + void UpdateContents(const std::vector& text); + void AdjustWidgetSize(); + + raw_ptr header_label_ = nullptr; + base::WeakPtrFactory weak_factory_{this}; +}; + +#endif // BRAVE_BROWSER_UI_VIEWS_TEXT_RECOGNITION_DIALOG_VIEW_H_ diff --git a/chromium_src/chrome/browser/DEPS b/chromium_src/chrome/browser/DEPS index 50541737a6c4..3cd1a4f6d5a0 100644 --- a/chromium_src/chrome/browser/DEPS +++ b/chromium_src/chrome/browser/DEPS @@ -32,6 +32,7 @@ include_rules = [ "+brave/components/privacy_sandbox", "+brave/components/sidebar", "+brave/components/sync", + "+brave/components/text_recognition", "+brave/components/tor", "+brave/components/translate", "+brave/components/url_sanitizer", 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 5e9ec53a00b6..0353940eac07 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 @@ -1,6 +1,7 @@ -// Copyright 2018 The Brave Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. +/* Copyright (c) 2018 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 "chrome/browser/renderer_context_menu/render_view_context_menu.h" @@ -9,6 +10,7 @@ #include "brave/browser/profiles/profile_util.h" #include "brave/browser/renderer_context_menu/brave_spelling_options_submenu_observer.h" #include "brave/browser/ui/browser_commands.h" +#include "brave/browser/ui/browser_dialogs.h" #include "brave/components/ipfs/buildflags/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" #include "brave/grit/brave_theme_resources.h" @@ -137,6 +139,16 @@ void OnTorProfileCreated(const GURL& link_url, #endif +#if BUILDFLAG(ENABLE_TEXT_RECOGNITION) +void OnGetImage(base::WeakPtr web_contents, + const SkBitmap& image) { + if (!web_contents) + return; + + brave::ShowTextRecognitionDialog(web_contents.get(), image); +} +#endif + } // namespace BraveRenderViewContextMenu::BraveRenderViewContextMenu( @@ -152,6 +164,10 @@ BraveRenderViewContextMenu::BraveRenderViewContextMenu( bool BraveRenderViewContextMenu::IsCommandIdEnabled(int id) const { switch (id) { +#if BUILDFLAG(ENABLE_TEXT_RECOGNITION) + case IDC_CONTENT_CONTEXT_COPY_TEXT_FROM_IMAGE: + return params_.has_image_contents; +#endif case IDC_COPY_CLEAN_LINK: return params_.link_url.is_valid(); case IDC_CONTENT_CONTEXT_FORCE_PASTE: @@ -249,12 +265,27 @@ void BraveRenderViewContextMenu::ExecuteCommand(int id, int event_flags) { base::BindRepeating(OnTorProfileCreated, params_.link_url, HasAlreadyOpenedTorWindow(GetProfile()))); break; +#endif +#if BUILDFLAG(ENABLE_TEXT_RECOGNITION) + case IDC_CONTENT_CONTEXT_COPY_TEXT_FROM_IMAGE: + CopyTextFromImage(); + break; #endif default: RenderViewContextMenu_Chromium::ExecuteCommand(id, event_flags); } } +#if BUILDFLAG(ENABLE_TEXT_RECOGNITION) +void BraveRenderViewContextMenu::CopyTextFromImage() { + RenderFrameHost* frame_host = GetRenderFrameHost(); + if (frame_host) + frame_host->GetImageAt( + params_.x, params_.y, + base::BindOnce(OnGetImage, source_web_contents_->GetWeakPtr())); +} +#endif + void BraveRenderViewContextMenu::AddSpellCheckServiceItem(bool is_checked) { // Call our implementation, not the one in the base class. // Assumption: @@ -382,6 +413,18 @@ void BraveRenderViewContextMenu::InitMenu() { IDC_CONTENT_CONTEXT_FORCE_PASTE, IDS_CONTENT_CONTEXT_FORCE_PASTE); } +#if BUILDFLAG(ENABLE_TEXT_RECOGNITION) + bool media_image = content_type_->SupportsGroup( + ContextMenuContentType::ITEM_GROUP_MEDIA_IMAGE); + if (media_image) { + index = + menu_model_.GetIndexOfCommandId(IDC_CONTENT_CONTEXT_COPYIMAGELOCATION); + DCHECK(index); + menu_model_.InsertItemWithStringIdAt( + index.value() + 1, IDC_CONTENT_CONTEXT_COPY_TEXT_FROM_IMAGE, + IDS_CONTENT_CONTEXT_COPY_TEXT_FROM_IMAGE); + } +#endif #if BUILDFLAG(ENABLE_TOR) // Add Open Link with Tor diff --git a/chromium_src/chrome/browser/renderer_context_menu/render_view_context_menu.h b/chromium_src/chrome/browser/renderer_context_menu/render_view_context_menu.h index aafab8fc6d05..7b5136edccb7 100644 --- a/chromium_src/chrome/browser/renderer_context_menu/render_view_context_menu.h +++ b/chromium_src/chrome/browser/renderer_context_menu/render_view_context_menu.h @@ -1,11 +1,13 @@ -// Copyright 2018 The Brave Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. +/* Copyright (c) 2018 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_CHROMIUM_SRC_CHROME_BROWSER_RENDERER_CONTEXT_MENU_RENDER_VIEW_CONTEXT_MENU_H_ #define BRAVE_CHROMIUM_SRC_CHROME_BROWSER_RENDERER_CONTEXT_MENU_RENDER_VIEW_CONTEXT_MENU_H_ #include "brave/components/ipfs/buildflags/buildflags.h" +#include "brave/components/text_recognition/common/buildflags/buildflags.h" #define BRAVE_RENDER_VIEW_CONTEXT_MENU_H_ \ private: \ @@ -53,6 +55,10 @@ class BraveRenderViewContextMenu : public RenderViewContextMenu_Chromium { ui::SimpleMenuModel ipfs_submenu_model_; #endif + +#if BUILDFLAG(ENABLE_TEXT_RECOGNITION) + void CopyTextFromImage(); +#endif }; // Use our own subclass as the real RenderViewContextMenu. diff --git a/chromium_src/chrome/browser/sources.gni b/chromium_src/chrome/browser/sources.gni index 021fc8e8cc15..2c68a4644304 100644 --- a/chromium_src/chrome/browser/sources.gni +++ b/chromium_src/chrome/browser/sources.gni @@ -4,12 +4,14 @@ # You can obtain one at http://mozilla.org/MPL/2.0/. import("//brave/components/brave_vpn/common/buildflags/buildflags.gni") +import("//brave/components/text_recognition/common/buildflags/buildflags.gni") import("//build/config/ui.gni") brave_chromium_src_chrome_browser_deps = [ "//base", "//brave/components/brave_vpn/common/buildflags", "//brave/components/playlist/common/buildflags", + "//brave/components/text_recognition/common/buildflags", "//chrome/common:channel_info", "//components/version_info", ] diff --git a/chromium_src/content/browser/renderer_host/render_frame_host_impl.cc b/chromium_src/content/browser/renderer_host/render_frame_host_impl.cc new file mode 100644 index 000000000000..e5ea308c39a5 --- /dev/null +++ b/chromium_src/content/browser/renderer_host/render_frame_host_impl.cc @@ -0,0 +1,20 @@ +/* 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 "src/content/browser/renderer_host/render_frame_host_impl.cc" + +namespace content { + +void RenderFrameHostImpl::GetImageAt( + int x, + int y, + base::OnceCallback callback) { + gfx::PointF point_in_view = + GetView()->TransformRootPointToViewCoordSpace(gfx::PointF(x, y)); + GetAssociatedLocalFrame()->GetImageAt( + gfx::Point(point_in_view.x(), point_in_view.y()), std::move(callback)); +} + +} // namespace content diff --git a/chromium_src/content/browser/renderer_host/render_frame_host_impl.h b/chromium_src/content/browser/renderer_host/render_frame_host_impl.h new file mode 100644 index 000000000000..e54337b8bbc8 --- /dev/null +++ b/chromium_src/content/browser/renderer_host/render_frame_host_impl.h @@ -0,0 +1,21 @@ +/* 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_CHROMIUM_SRC_CONTENT_BROWSER_RENDERER_HOST_RENDER_FRAME_HOST_IMPL_H_ +#define BRAVE_CHROMIUM_SRC_CONTENT_BROWSER_RENDERER_HOST_RENDER_FRAME_HOST_IMPL_H_ + +#include "content/public/browser/render_frame_host.h" +#include "third_party/blink/public/mojom/frame/frame.mojom.h" + +#define CopyImageAt \ + GetImageAt(int x, int y, base::OnceCallback callback) \ + override; \ + void CopyImageAt + +#include "src/content/browser/renderer_host/render_frame_host_impl.h" + +#undef CopyImageAt + +#endif // BRAVE_CHROMIUM_SRC_CONTENT_BROWSER_RENDERER_HOST_RENDER_FRAME_HOST_IMPL_H_ diff --git a/chromium_src/content/public/browser/render_frame_host.h b/chromium_src/content/public/browser/render_frame_host.h new file mode 100644 index 000000000000..61b78521c2e5 --- /dev/null +++ b/chromium_src/content/public/browser/render_frame_host.h @@ -0,0 +1,20 @@ +/* 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_CHROMIUM_SRC_CONTENT_PUBLIC_BROWSER_RENDER_FRAME_HOST_H_ +#define BRAVE_CHROMIUM_SRC_CONTENT_PUBLIC_BROWSER_RENDER_FRAME_HOST_H_ + +class SkBitmap; + +#define CopyImageAt \ + GetImageAt(int x, int y, \ + base::OnceCallback callback) = 0; \ + virtual void CopyImageAt + +#include "src/content/public/browser/render_frame_host.h" + +#undef CopyImageAt + +#endif // BRAVE_CHROMIUM_SRC_CONTENT_PUBLIC_BROWSER_RENDER_FRAME_HOST_H_ diff --git a/chromium_src/third_party/blink/public/mojom/frame/frame.mojom b/chromium_src/third_party/blink/public/mojom/frame/frame.mojom new file mode 100644 index 000000000000..62348b2032f7 --- /dev/null +++ b/chromium_src/third_party/blink/public/mojom/frame/frame.mojom @@ -0,0 +1,15 @@ +// 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/. + +module blink.mojom; + +import "skia/public/mojom/bitmap.mojom"; +import "ui/gfx/geometry/mojom/geometry.mojom"; + +[BraveExtend] +interface LocalFrame { + GetImageAt(gfx.mojom.Point window_point) + => (skia.mojom.BitmapN32? bitmap_image); +}; diff --git a/chromium_src/third_party/blink/renderer/core/frame/local_frame.cc b/chromium_src/third_party/blink/renderer/core/frame/local_frame.cc index b506dc4d9e5b..af3f5f263677 100644 --- a/chromium_src/third_party/blink/renderer/core/frame/local_frame.cc +++ b/chromium_src/third_party/blink/renderer/core/frame/local_frame.cc @@ -6,7 +6,11 @@ #include "third_party/blink/renderer/core/frame/local_frame.h" #include "brave/components/brave_page_graph/common/buildflags.h" +#include "skia/ext/skia_utils_base.h" +#include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h" +#include "third_party/blink/renderer/core/layout/layout_image.h" #include "third_party/blink/renderer/core/probe/core_probes.h" +#include "third_party/blink/renderer/platform/graphics/graphics_types_3d.h" #if BUILDFLAG(ENABLE_BRAVE_PAGE_GRAPH) #include "brave/third_party/blink/renderer/core/brave_page_graph/page_graph.h" @@ -23,4 +27,79 @@ #include "src/third_party/blink/renderer/core/frame/local_frame.cc" +namespace blink { + +namespace { + +static scoped_refptr ImageFromNode(const Node& node) { + DCHECK(!node.GetDocument().NeedsLayoutTreeUpdate()); + DocumentLifecycle::DisallowTransitionScope disallow_transition( + node.GetDocument().Lifecycle()); + + const LayoutObject* const layout_object = node.GetLayoutObject(); + if (!layout_object) + return nullptr; + + if (layout_object->IsCanvas()) { + return To(const_cast(node)) + .Snapshot(kFrontBuffer); + } + + if (!layout_object->IsImage()) + return nullptr; + + const auto& layout_image = To(*layout_object); + const ImageResourceContent* const cached_image = layout_image.CachedImage(); + if (!cached_image || cached_image->ErrorOccurred()) + return nullptr; + return cached_image->GetImage(); +} + +} // namespace + +SkBitmap LocalFrame::GetImageAtViewportPoint(const gfx::Point& viewport_point) { + HitTestResult result = HitTestResultForVisualViewportPos(viewport_point); + if (!IsA(result.InnerNodeOrImageMapImage()) && + result.AbsoluteImageURL().IsEmpty()) { + // There isn't actually an image at these coordinates. Might be because + // the window scrolled while the context menu was open or because the page + // changed itself between when we thought there was an image here and when + // we actually tried to retrieve the image. + // + // FIXME: implement a cache of the most recent HitTestResult to avoid having + // to do two hit tests. + return {}; + } + + const scoped_refptr image = + ImageFromNode(*result.InnerNodeOrImageMapImage()); + if (!image.get()) + return {}; + + PaintImage paint_image = image->PaintImageForCurrentFrame(); + // Orient the data. + if (!image->HasDefaultOrientation()) { + paint_image = Image::ResizeAndOrientImage( + paint_image, image->CurrentFrameOrientation(), gfx::Vector2dF(1, 1), 1, + kInterpolationNone); + } + SkBitmap bitmap; + if (sk_sp sk_image = paint_image.GetSwSkImage()) + sk_image->asLegacyBitmap(&bitmap); + + // The bitmap backing a canvas can be in non-native skia pixel order (aka + // RGBA when kN32_SkColorType is BGRA-ordered, or higher bit-depth color-types + // like F16. The IPC to the browser requires the bitmap to be in N32 format + // so we convert it here if needed. + SkBitmap n32_bitmap; + if (skia::SkBitmapToN32OpaqueOrPremul(bitmap, &n32_bitmap) && + !n32_bitmap.isNull()) { + return n32_bitmap; + } + + return {}; +} + +} // namespace blink + #undef FrameAttachedToParent diff --git a/chromium_src/third_party/blink/renderer/core/frame/local_frame.h b/chromium_src/third_party/blink/renderer/core/frame/local_frame.h new file mode 100644 index 000000000000..f2090d5d8591 --- /dev/null +++ b/chromium_src/third_party/blink/renderer/core/frame/local_frame.h @@ -0,0 +1,20 @@ +/* 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_CHROMIUM_SRC_THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_LOCAL_FRAME_H_ +#define BRAVE_CHROMIUM_SRC_THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_LOCAL_FRAME_H_ + +class SkBitmap; + +#define CopyImageAtViewportPoint \ + CopyImageAtViewportPoint_UnUsed() {} \ + SkBitmap GetImageAtViewportPoint(const gfx::Point& viewport_point); \ + void CopyImageAtViewportPoint + +#include "src/third_party/blink/renderer/core/frame/local_frame.h" + +#undef CopyImageAtViewportPoint + +#endif // BRAVE_CHROMIUM_SRC_THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_LOCAL_FRAME_H_ diff --git a/chromium_src/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc b/chromium_src/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc new file mode 100644 index 000000000000..78d2736888ac --- /dev/null +++ b/chromium_src/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc @@ -0,0 +1,17 @@ +/* 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 "src/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc" + +namespace blink { + +void LocalFrameMojoHandler::GetImageAt(const gfx::Point& window_point, + GetImageAtCallback callback) { + gfx::Point viewport_position = + frame_->GetWidgetForLocalRoot()->DIPsToRoundedBlinkSpace(window_point); + std::move(callback).Run(frame_->GetImageAtViewportPoint(viewport_position)); +} + +} // namespace blink diff --git a/chromium_src/third_party/blink/renderer/core/frame/local_frame_mojo_handler.h b/chromium_src/third_party/blink/renderer/core/frame/local_frame_mojo_handler.h new file mode 100644 index 000000000000..bd3314d45fd7 --- /dev/null +++ b/chromium_src/third_party/blink/renderer/core/frame/local_frame_mojo_handler.h @@ -0,0 +1,20 @@ +/* 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_CHROMIUM_SRC_THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_LOCAL_FRAME_MOJO_HANDLER_H_ +#define BRAVE_CHROMIUM_SRC_THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_LOCAL_FRAME_MOJO_HANDLER_H_ + +#include "third_party/blink/public/mojom/frame/frame.mojom-blink.h" + +#define CopyImageAt \ + GetImageAt(const gfx::Point& window_point, GetImageAtCallback callback) \ + final; \ + void CopyImageAt + +#include "src/third_party/blink/renderer/core/frame/local_frame_mojo_handler.h" + +#undef CopyImageAt + +#endif // BRAVE_CHROMIUM_SRC_THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_LOCAL_FRAME_MOJO_HANDLER_H_ diff --git a/components/text_recognition/DEPS b/components/text_recognition/DEPS new file mode 100644 index 000000000000..833ba2c614eb --- /dev/null +++ b/components/text_recognition/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+base", + "+skia", + "+third_party/skia", +] diff --git a/components/text_recognition/browser/BUILD.gn b/components/text_recognition/browser/BUILD.gn new file mode 100644 index 000000000000..8c7228dadc6a --- /dev/null +++ b/components/text_recognition/browser/BUILD.gn @@ -0,0 +1,28 @@ +# 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/. + +import("//brave/components/text_recognition/common/buildflags/buildflags.gni") + +assert(enable_text_recognition) + +source_set("browser") { + sources = [ "text_recognition.h" ] + + if (is_mac) { + sources += [ "text_recognition.mm" ] + + frameworks = [ + "Foundation.framework", + "Vision.framework", + ] + } + + deps = [ + "//base", + "//skia", + ] + + configs += [ "//build/config/compiler:enable_arc" ] +} diff --git a/components/text_recognition/browser/text_recognition.h b/components/text_recognition/browser/text_recognition.h new file mode 100644 index 000000000000..0b9f20b7244a --- /dev/null +++ b/components/text_recognition/browser/text_recognition.h @@ -0,0 +1,17 @@ +/* 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_COMPONENTS_TEXT_RECOGNITION_BROWSER_TEXT_RECOGNITION_H_ +#define BRAVE_COMPONENTS_TEXT_RECOGNITION_BROWSER_TEXT_RECOGNITION_H_ + +#include +#include + +class SkBitmap; + +// Returns recognized texts from |image|. +std::vector GetTextFromImage(const SkBitmap& image); + +#endif // BRAVE_COMPONENTS_TEXT_RECOGNITION_BROWSER_TEXT_RECOGNITION_H_ diff --git a/components/text_recognition/browser/text_recognition.mm b/components/text_recognition/browser/text_recognition.mm new file mode 100644 index 000000000000..5cb87b65a22f --- /dev/null +++ b/components/text_recognition/browser/text_recognition.mm @@ -0,0 +1,75 @@ +/* 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/components/text_recognition/browser/text_recognition.h" + +#import +#import + +#include "base/logging.h" +#include "base/mac/foundation_util.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/strings/sys_string_conversions.h" +#include "skia/ext/skia_utils_base.h" +#include "skia/ext/skia_utils_mac.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/utils/mac/SkCGUtils.h" + +std::vector GetTextFromImage(const SkBitmap& image) { + std::vector result; + if (@available(macOS 10.15, *)) { + // The bitmap type is sanitized to be N32 before we get here. The conversion + // to an NSImage would not explode if we got this wrong, so this is not a + // security CHECK. + DCHECK_EQ(image.colorType(), kN32_SkColorType); + + auto* p_result = &result; + VNRecognizeTextRequest* textRecognitionRequest = + [[VNRecognizeTextRequest alloc] initWithCompletionHandler:^( + VNRequest* _Nonnull request, + NSError* _Nullable error) { + NSArray* observations = request.results; + + [observations enumerateObjectsUsingBlock:^( + VNRecognizedTextObservation* _Nonnull obj, + NSUInteger idx, BOOL* _Nonnull stop) { + // Ask first top candidate for a recognized text string. + VNRecognizedText* recognizedText = + [obj topCandidates:1].firstObject; + + p_result->push_back( + base::SysNSStringToUTF8([recognizedText string])); + }]; + }]; + + textRecognitionRequest.recognitionLevel = + VNRequestTextRecognitionLevelAccurate; + textRecognitionRequest.usesLanguageCorrection = true; + // Copied FF's supported language list. + // See https://support.mozilla.org/en-US/kb/text-recognition + // English, Chinese, Portuguese, French, Italian, German, and Spanish + textRecognitionRequest.recognitionLanguages = @[ + @"en-US", @"zh-Hans", @"zh-Hant", @"pt-BR", @"fr-FR", @"it-IT", @"de-DE", + @"es-ES" + ]; + + if (@available(macOS 13.0, *)) { + textRecognitionRequest.automaticallyDetectsLanguage = true; + } + + NSError* error = nil; + base::ScopedCFTypeRef cg_image(SkCreateCGImageRef(image)); + VNImageRequestHandler* requestHandler = + [[VNImageRequestHandler alloc] initWithCGImage:cg_image.get() + options:@{}]; + [requestHandler performRequests:@[ textRecognitionRequest ] error:&error]; + if (error) { + LOG(ERROR) << base::SysNSStringToUTF8([error localizedDescription]); + } + } + + return result; +} diff --git a/components/text_recognition/common/buildflags/BUILD.gn b/components/text_recognition/common/buildflags/BUILD.gn new file mode 100644 index 000000000000..f2c61f4b526b --- /dev/null +++ b/components/text_recognition/common/buildflags/BUILD.gn @@ -0,0 +1,12 @@ +# 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/. + +import("//brave/components/text_recognition/common/buildflags/buildflags.gni") +import("//build/buildflag_header.gni") + +buildflag_header("buildflags") { + header = "buildflags.h" + flags = [ "ENABLE_TEXT_RECOGNITION=$enable_text_recognition" ] +} diff --git a/components/text_recognition/common/buildflags/buildflags.gni b/components/text_recognition/common/buildflags/buildflags.gni new file mode 100644 index 000000000000..3ea4cd80e398 --- /dev/null +++ b/components/text_recognition/common/buildflags/buildflags.gni @@ -0,0 +1,8 @@ +# 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/. + +declare_args() { + enable_text_recognition = is_mac +} From f3cccf647427ae1ef7130e745db19d8aba58d232 Mon Sep 17 00:00:00 2001 From: Brian Clifton Date: Mon, 9 Jan 2023 00:52:36 -0700 Subject: [PATCH 02/10] Updated using std::copy and stringstream --- browser/ui/views/text_recognition_dialog_view.cc | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/browser/ui/views/text_recognition_dialog_view.cc b/browser/ui/views/text_recognition_dialog_view.cc index c47374c514b1..b9a28e7b6787 100644 --- a/browser/ui/views/text_recognition_dialog_view.cc +++ b/browser/ui/views/text_recognition_dialog_view.cc @@ -5,6 +5,7 @@ #include "brave/browser/ui/views/text_recognition_dialog_view.h" +#include #include #include "base/bind.h" @@ -98,11 +99,11 @@ void TextRecognitionDialogView::UpdateContents( IDS_TEXT_RECOG_DIALOG_HEADER_COMPLETE)); // Treat each string in |text| as a separated line string. - std::string unified_string; - for (const auto& t : text) { - unified_string += t; - unified_string += '\n'; - } + const char* const delimiter = "\n"; + std::ostringstream unified; + std::copy(text.begin(), text.end(), + std::ostream_iterator(unified, delimiter)); + std::string unified_string = unified.str(); ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste) .WriteText(base::UTF8ToUTF16(unified_string)); From 9e7c13905ebf139b956aed65c40feba12cbade26 Mon Sep 17 00:00:00 2001 From: Simon Hong Date: Tue, 10 Jan 2023 08:11:51 +0900 Subject: [PATCH 03/10] Used base::JoinString() to make strings into one string --- browser/ui/views/text_recognition_dialog_view.cc | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/browser/ui/views/text_recognition_dialog_view.cc b/browser/ui/views/text_recognition_dialog_view.cc index b9a28e7b6787..76e0c57fcb95 100644 --- a/browser/ui/views/text_recognition_dialog_view.cc +++ b/browser/ui/views/text_recognition_dialog_view.cc @@ -5,10 +5,10 @@ #include "brave/browser/ui/views/text_recognition_dialog_view.h" -#include #include #include "base/bind.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" @@ -99,12 +99,7 @@ void TextRecognitionDialogView::UpdateContents( IDS_TEXT_RECOG_DIALOG_HEADER_COMPLETE)); // Treat each string in |text| as a separated line string. - const char* const delimiter = "\n"; - std::ostringstream unified; - std::copy(text.begin(), text.end(), - std::ostream_iterator(unified, delimiter)); - std::string unified_string = unified.str(); - + const std::string unified_string = base::JoinString(text, "\n"); ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste) .WriteText(base::UTF8ToUTF16(unified_string)); From 6d5d6050a7c7c3de4f3e4882ed1f567b0fdffa73 Mon Sep 17 00:00:00 2001 From: Simon Hong Date: Wed, 11 Jan 2023 11:55:16 +0900 Subject: [PATCH 04/10] Tracks text recognition dialog is already active or not for each tab If renderer is busy or on slow machine, copy image could take some time. In that situation, user could ask copy text menu multiple times. And then, browser would get multiple copied image from renderer. When it happens, use same dialog and show lastly recognized text in dialog instead of creating multiple dialogs. --- app/brave_generated_resources.grd | 8 +- browser/ui/BUILD.gn | 1 + browser/ui/browser_dialogs.h | 3 + .../ui/views/text_recognition_dialog_view.cc | 101 ++++++++++++++---- .../ui/views/text_recognition_dialog_view.h | 5 +- .../render_view_context_menu.cc | 12 +-- .../blink/renderer/core/frame/local_frame.cc | 7 +- .../text_recognition/{ => browser}/DEPS | 0 .../browser/text_recognition.mm | 3 + 9 files changed, 108 insertions(+), 32 deletions(-) rename components/text_recognition/{ => browser}/DEPS (100%) diff --git a/app/brave_generated_resources.grd b/app/brave_generated_resources.grd index 2e7c1393fae6..4a88c2e51e63 100644 --- a/app/brave_generated_resources.grd +++ b/app/brave_generated_resources.grd @@ -901,16 +901,16 @@ Or change later at $2brave://settings/ext Copy Text From Image - + Close - + Copying text from image... - + Text copied from image - + Text copy failed diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn index 64d47edbf4c1..fb1c311ed235 100644 --- a/browser/ui/BUILD.gn +++ b/browser/ui/BUILD.gn @@ -405,6 +405,7 @@ source_set("ui") { "//brave/components/p3a:buildflags", "//brave/components/playlist/common/buildflags", "//brave/components/resources:static_resources", + "//brave/components/text_recognition/common/buildflags", "//brave/components/time_period_storage", "//brave/components/tor/buildflags", "//brave/components/vector_icons", diff --git a/browser/ui/browser_dialogs.h b/browser/ui/browser_dialogs.h index a4e3d6e408ce..7127d61e7bee 100644 --- a/browser/ui/browser_dialogs.h +++ b/browser/ui/browser_dialogs.h @@ -7,6 +7,7 @@ #define BRAVE_BROWSER_UI_BROWSER_DIALOGS_H_ #include "base/callback_forward.h" +#include "brave/components/text_recognition/common/buildflags/buildflags.h" class Browser; class SkBitmap; @@ -23,9 +24,11 @@ void ShowCrashReportPermissionAskDialog(Browser* browser); // Run |callback| when dialog closed. void ShowObsoleteSystemConfirmDialog(base::OnceCallback callback); +#if BUILDFLAG(ENABLE_TEXT_RECOGNITION) // Show web modal dialog for showing text that recognized from |image|. void ShowTextRecognitionDialog(content::WebContents* web_contents, const SkBitmap& image); +#endif } // namespace brave diff --git a/browser/ui/views/text_recognition_dialog_view.cc b/browser/ui/views/text_recognition_dialog_view.cc index 76e0c57fcb95..1d06a17a42bd 100644 --- a/browser/ui/views/text_recognition_dialog_view.cc +++ b/browser/ui/views/text_recognition_dialog_view.cc @@ -16,6 +16,7 @@ #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" @@ -23,15 +24,74 @@ #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_); + active_dialog_ = widget; + active_dialog_->AddObserver(this); + } + + 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 OnWidgetClosing(views::Widget* widget) override { + DCHECK_EQ(active_dialog_, widget); + DCHECK(active_dialog_->HasObserver(this)); + active_dialog_->RemoveObserver(this); + active_dialog_ = nullptr; + } + + raw_ptr active_dialog_ = nullptr; + + WEB_CONTENTS_USER_DATA_KEY_DECL(); +}; + +WEB_CONTENTS_USER_DATA_KEY_IMPL(TextRecognitionDialogTracker); + +} // namespace + namespace brave { void ShowTextRecognitionDialog(content::WebContents* web_contents, const SkBitmap& image) { - constrained_window::ShowWebModalDialogViews( - new TextRecognitionDialogView(image), web_contents) - ->Show(); + // Use existing dialog instead of creating multiple dialog. + // Dialog will have lastly recognizied text from image from same tab. + TextRecognitionDialogTracker::CreateForWebContents(web_contents); + auto* dialog_tracker = + TextRecognitionDialogTracker::FromWebContents(web_contents); + + if (auto* active_dialog = dialog_tracker->active_dialog()) { + TextRecognitionDialogView* text_recognition_dialog = + static_cast( + active_dialog->widget_delegate()); + text_recognition_dialog->StartExtractingText(image); + return; + } + + auto* new_dialog = constrained_window::ShowWebModalDialogViews( + new TextRecognitionDialogView(image), web_contents); + dialog_tracker->SetActiveDialog(new_dialog); + new_dialog->Show(); } } // namespace brave @@ -41,7 +101,7 @@ TextRecognitionDialogView::TextRecognitionDialogView(const SkBitmap& image) { SetButtons(ui::DIALOG_BUTTON_OK); SetButtonLabel(ui::DIALOG_BUTTON_OK, brave_l10n::GetLocalizedResourceUTF16String( - IDS_TEXT_RECOG_DIALOG_CLOSE_BUTTON)); + IDS_TEXT_RECOGNITION_DIALOG_CLOSE_BUTTON)); SetShowCloseButton(false); SetLayoutManager(std::make_unique()) @@ -59,19 +119,19 @@ TextRecognitionDialogView::TextRecognitionDialogView(const SkBitmap& image) { header_label_->SetProperty(views::kMarginsKey, gfx::Insets::TLBR(0, 0, 10, 0)); - if (image.empty()) { - UpdateContents({}); - return; - } - StartExtractingText(image); } TextRecognitionDialogView::~TextRecognitionDialogView() = default; void TextRecognitionDialogView::StartExtractingText(const SkBitmap& image) { + if (image.empty()) { + UpdateContents({}); + return; + } + header_label_->SetText(brave_l10n::GetLocalizedResourceUTF16String( - IDS_TEXT_RECOG_DIALOG_HEADER_IN_PROGRESS)); + IDS_TEXT_RECOGNITION_DIALOG_HEADER_IN_PROGRESS)); base::ThreadPool::PostTaskAndReplyWithResult( FROM_HERE, @@ -91,24 +151,25 @@ void TextRecognitionDialogView::UpdateContents( const std::vector& text) { if (text.empty()) { header_label_->SetText(brave_l10n::GetLocalizedResourceUTF16String( - IDS_TEXT_RECOG_DIALOG_HEADER_FAILED)); + IDS_TEXT_RECOGNITION_DIALOG_HEADER_FAILED)); return; } header_label_->SetText(brave_l10n::GetLocalizedResourceUTF16String( - IDS_TEXT_RECOG_DIALOG_HEADER_COMPLETE)); + IDS_TEXT_RECOGNITION_DIALOG_HEADER_COMPLETE)); // Treat each string in |text| as a separated line string. - const std::string unified_string = base::JoinString(text, "\n"); + const auto unified_string = base::UTF8ToUTF16(base::JoinString(text, "\n")); ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste) - .WriteText(base::UTF8ToUTF16(unified_string)); - - auto* scroll_view = AddChildView(std::make_unique()); - scroll_view->SetProperty(views::kMarginsKey, gfx::Insets::VH(0, 10)); - scroll_view->ClipHeightTo(0, 350); - auto* label = scroll_view->SetContents( - std::make_unique(base::UTF8ToUTF16(unified_string))); + .WriteText(unified_string); + if (!scroll_view_) { + scroll_view_ = AddChildView(std::make_unique()); + scroll_view_->SetProperty(views::kMarginsKey, gfx::Insets::VH(0, 10)); + scroll_view_->ClipHeightTo(0, 350); + } + auto* label = + scroll_view_->SetContents(std::make_unique(unified_string)); label->SetHorizontalAlignment(gfx::ALIGN_LEFT); label->SetSelectable(true); label->SetMultiLine(true); diff --git a/browser/ui/views/text_recognition_dialog_view.h b/browser/ui/views/text_recognition_dialog_view.h index 12d7151fd271..764359e95b71 100644 --- a/browser/ui/views/text_recognition_dialog_view.h +++ b/browser/ui/views/text_recognition_dialog_view.h @@ -18,6 +18,7 @@ class SkBitmap; namespace views { class Label; +class ScrollView; } // namespace views class TextRecognitionDialogView : public views::DialogDelegateView { @@ -30,8 +31,9 @@ class TextRecognitionDialogView : public views::DialogDelegateView { delete; ~TextRecognitionDialogView() override; - private: void StartExtractingText(const SkBitmap& image); + + private: void OnGetTextFromImage(const std::vector& text); // Show |text| in this dialog and copy it to clipboard. @@ -39,6 +41,7 @@ class TextRecognitionDialogView : public views::DialogDelegateView { void AdjustWidgetSize(); raw_ptr header_label_ = nullptr; + raw_ptr scroll_view_ = nullptr; 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 0353940eac07..27dfaa24b771 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 @@ -140,8 +140,8 @@ void OnTorProfileCreated(const GURL& link_url, #endif #if BUILDFLAG(ENABLE_TEXT_RECOGNITION) -void OnGetImage(base::WeakPtr web_contents, - const SkBitmap& image) { +void OnGetImageForTextCopy(base::WeakPtr web_contents, + const SkBitmap& image) { if (!web_contents) return; @@ -280,9 +280,9 @@ void BraveRenderViewContextMenu::ExecuteCommand(int id, int event_flags) { void BraveRenderViewContextMenu::CopyTextFromImage() { RenderFrameHost* frame_host = GetRenderFrameHost(); if (frame_host) - frame_host->GetImageAt( - params_.x, params_.y, - base::BindOnce(OnGetImage, source_web_contents_->GetWeakPtr())); + frame_host->GetImageAt(params_.x, params_.y, + base::BindOnce(OnGetImageForTextCopy, + source_web_contents_->GetWeakPtr())); } #endif @@ -414,7 +414,7 @@ void BraveRenderViewContextMenu::InitMenu() { IDS_CONTENT_CONTEXT_FORCE_PASTE); } #if BUILDFLAG(ENABLE_TEXT_RECOGNITION) - bool media_image = content_type_->SupportsGroup( + const bool media_image = content_type_->SupportsGroup( ContextMenuContentType::ITEM_GROUP_MEDIA_IMAGE); if (media_image) { index = diff --git a/chromium_src/third_party/blink/renderer/core/frame/local_frame.cc b/chromium_src/third_party/blink/renderer/core/frame/local_frame.cc index af3f5f263677..adf54fc40497 100644 --- a/chromium_src/third_party/blink/renderer/core/frame/local_frame.cc +++ b/chromium_src/third_party/blink/renderer/core/frame/local_frame.cc @@ -31,7 +31,9 @@ namespace blink { namespace { -static scoped_refptr ImageFromNode(const Node& node) { +// Copied same method from +// third_party/blink/renderer/core/editing/editing_utilities.cc +scoped_refptr ImageFromNode(const Node& node) { DCHECK(!node.GetDocument().NeedsLayoutTreeUpdate()); DocumentLifecycle::DisallowTransitionScope disallow_transition( node.GetDocument().Lifecycle()); @@ -57,6 +59,7 @@ static scoped_refptr ImageFromNode(const Node& node) { } // namespace +// Referred LocalFrame::CopyImageAtViewportPoint(). SkBitmap LocalFrame::GetImageAtViewportPoint(const gfx::Point& viewport_point) { HitTestResult result = HitTestResultForVisualViewportPos(viewport_point); if (!IsA(result.InnerNodeOrImageMapImage()) && @@ -76,6 +79,8 @@ SkBitmap LocalFrame::GetImageAtViewportPoint(const gfx::Point& viewport_point) { if (!image.get()) return {}; + // Referred SystemClipboard::WriteImageWithTag() about how to get bitmap data + // from Image. PaintImage paint_image = image->PaintImageForCurrentFrame(); // Orient the data. if (!image->HasDefaultOrientation()) { diff --git a/components/text_recognition/DEPS b/components/text_recognition/browser/DEPS similarity index 100% rename from components/text_recognition/DEPS rename to components/text_recognition/browser/DEPS diff --git a/components/text_recognition/browser/text_recognition.mm b/components/text_recognition/browser/text_recognition.mm index 5cb87b65a22f..ad1396792077 100644 --- a/components/text_recognition/browser/text_recognition.mm +++ b/components/text_recognition/browser/text_recognition.mm @@ -13,12 +13,15 @@ #include "base/mac/mac_util.h" #include "base/mac/scoped_cftyperef.h" #include "base/strings/sys_string_conversions.h" +#include "base/threading/scoped_blocking_call.h" #include "skia/ext/skia_utils_base.h" #include "skia/ext/skia_utils_mac.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/utils/mac/SkCGUtils.h" std::vector GetTextFromImage(const SkBitmap& image) { + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::WILL_BLOCK); std::vector result; if (@available(macOS 10.15, *)) { // The bitmap type is sanitized to be N32 before we get here. The conversion From 7cdd8b1e3a420afbfbe3fe496e33815da563842b Mon Sep 17 00:00:00 2001 From: Simon Hong Date: Wed, 11 Jan 2023 18:42:52 +0900 Subject: [PATCH 05/10] Use text_recognition namespace for text_recognition components --- browser/ui/views/text_recognition_dialog_view.cc | 2 +- components/text_recognition/browser/text_recognition.h | 4 ++++ components/text_recognition/browser/text_recognition.mm | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/browser/ui/views/text_recognition_dialog_view.cc b/browser/ui/views/text_recognition_dialog_view.cc index 1d06a17a42bd..fde4c175b478 100644 --- a/browser/ui/views/text_recognition_dialog_view.cc +++ b/browser/ui/views/text_recognition_dialog_view.cc @@ -136,7 +136,7 @@ void TextRecognitionDialogView::StartExtractingText(const SkBitmap& image) { base::ThreadPool::PostTaskAndReplyWithResult( FROM_HERE, {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, - base::BindOnce(&GetTextFromImage, image), + base::BindOnce(&text_recognition::GetTextFromImage, image), base::BindOnce(&TextRecognitionDialogView::OnGetTextFromImage, weak_factory_.GetWeakPtr())); } diff --git a/components/text_recognition/browser/text_recognition.h b/components/text_recognition/browser/text_recognition.h index 0b9f20b7244a..de35e8f41bd8 100644 --- a/components/text_recognition/browser/text_recognition.h +++ b/components/text_recognition/browser/text_recognition.h @@ -11,7 +11,11 @@ class SkBitmap; +namespace text_recognition { + // Returns recognized texts from |image|. std::vector GetTextFromImage(const SkBitmap& image); +} // namespace text_recognition + #endif // BRAVE_COMPONENTS_TEXT_RECOGNITION_BROWSER_TEXT_RECOGNITION_H_ diff --git a/components/text_recognition/browser/text_recognition.mm b/components/text_recognition/browser/text_recognition.mm index ad1396792077..c6fd5bff151a 100644 --- a/components/text_recognition/browser/text_recognition.mm +++ b/components/text_recognition/browser/text_recognition.mm @@ -19,6 +19,8 @@ #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/utils/mac/SkCGUtils.h" +namespace text_recognition { + std::vector GetTextFromImage(const SkBitmap& image) { base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, base::BlockingType::WILL_BLOCK); @@ -76,3 +78,5 @@ return result; } + +} // namespace text_recognition From b4b3dd8a92ceb4f0f54b63cc756f232d8a4f6b64 Mon Sep 17 00:00:00 2001 From: Simon Hong Date: Wed, 11 Jan 2023 19:00:40 +0900 Subject: [PATCH 06/10] Moved compiler:enable_arc target into macOS only part --- components/text_recognition/browser/BUILD.gn | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/text_recognition/browser/BUILD.gn b/components/text_recognition/browser/BUILD.gn index 8c7228dadc6a..e29adf9ddb61 100644 --- a/components/text_recognition/browser/BUILD.gn +++ b/components/text_recognition/browser/BUILD.gn @@ -17,12 +17,12 @@ source_set("browser") { "Foundation.framework", "Vision.framework", ] + + configs += [ "//build/config/compiler:enable_arc" ] } deps = [ "//base", "//skia", ] - - configs += [ "//build/config/compiler:enable_arc" ] } From a055c91b02699cc8bbafc5a30608bcc89519cdf4 Mon Sep 17 00:00:00 2001 From: Simon Hong Date: Wed, 11 Jan 2023 20:27:25 +0900 Subject: [PATCH 07/10] Make TextRecognitionDialogTracker track widget status more accurately --- browser/ui/views/text_recognition_dialog_view.cc | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/browser/ui/views/text_recognition_dialog_view.cc b/browser/ui/views/text_recognition_dialog_view.cc index fde4c175b478..e47bc7a8cbd8 100644 --- a/browser/ui/views/text_recognition_dialog_view.cc +++ b/browser/ui/views/text_recognition_dialog_view.cc @@ -8,6 +8,7 @@ #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" @@ -40,9 +41,9 @@ class TextRecognitionDialogTracker ~TextRecognitionDialogTracker() override = default; void SetActiveDialog(views::Widget* widget) { - DCHECK(!active_dialog_); + DCHECK(!active_dialog_ && !observation_.IsObserving()); active_dialog_ = widget; - active_dialog_->AddObserver(this); + observation_.Observe(widget); } views::Widget* active_dialog() { return active_dialog_; } @@ -54,14 +55,16 @@ class TextRecognitionDialogTracker *web_contents) {} // views::WidgetObserver overrides - void OnWidgetClosing(views::Widget* widget) override { + void OnWidgetDestroying(views::Widget* widget) override { DCHECK_EQ(active_dialog_, widget); - DCHECK(active_dialog_->HasObserver(this)); - active_dialog_->RemoveObserver(this); + DCHECK(observation_.IsObservingSource(widget)); + observation_.Reset(); active_dialog_ = nullptr; } raw_ptr active_dialog_ = nullptr; + base::ScopedObservation observation_{ + this}; WEB_CONTENTS_USER_DATA_KEY_DECL(); }; From 58f54eaba4e5b5edbd310539855fc72f20ba77da Mon Sep 17 00:00:00 2001 From: Simon Hong Date: Thu, 12 Jan 2023 09:41:25 +0900 Subject: [PATCH 08/10] Make text_recognition/browser target as shared lib --- components/text_recognition/browser/BUILD.gn | 6 +++++- components/text_recognition/browser/text_recognition.h | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/components/text_recognition/browser/BUILD.gn b/components/text_recognition/browser/BUILD.gn index e29adf9ddb61..6dc80d301b94 100644 --- a/components/text_recognition/browser/BUILD.gn +++ b/components/text_recognition/browser/BUILD.gn @@ -7,7 +7,9 @@ import("//brave/components/text_recognition/common/buildflags/buildflags.gni") assert(enable_text_recognition) -source_set("browser") { +component("browser") { + output_name = "text_recognition_browser" + sources = [ "text_recognition.h" ] if (is_mac) { @@ -25,4 +27,6 @@ source_set("browser") { "//base", "//skia", ] + + defines = [ "IS_TEXT_RECOGNITION_BROWSER_IMPL" ] } diff --git a/components/text_recognition/browser/text_recognition.h b/components/text_recognition/browser/text_recognition.h index de35e8f41bd8..1870323c2f82 100644 --- a/components/text_recognition/browser/text_recognition.h +++ b/components/text_recognition/browser/text_recognition.h @@ -9,11 +9,14 @@ #include #include +#include "base/component_export.h" + class SkBitmap; namespace text_recognition { // Returns recognized texts from |image|. +COMPONENT_EXPORT(TEXT_RECOGNITION_BROWSER) std::vector GetTextFromImage(const SkBitmap& image); } // namespace text_recognition From 8e18d4a1d9b275332434805894c4fd0763a662ff Mon Sep 17 00:00:00 2001 From: Simon Hong Date: Thu, 12 Jan 2023 13:10:14 +0900 Subject: [PATCH 09/10] 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 Date: Fri, 13 Jan 2023 18:53:33 +0900 Subject: [PATCH 10/10] Reordering properties in text_recognition/browser target --- components/text_recognition/browser/BUILD.gn | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/components/text_recognition/browser/BUILD.gn b/components/text_recognition/browser/BUILD.gn index 6dc80d301b94..300b34d85910 100644 --- a/components/text_recognition/browser/BUILD.gn +++ b/components/text_recognition/browser/BUILD.gn @@ -12,6 +12,13 @@ component("browser") { sources = [ "text_recognition.h" ] + defines = [ "IS_TEXT_RECOGNITION_BROWSER_IMPL" ] + + deps = [ + "//base", + "//skia", + ] + if (is_mac) { sources += [ "text_recognition.mm" ] @@ -22,11 +29,4 @@ component("browser") { configs += [ "//build/config/compiler:enable_arc" ] } - - deps = [ - "//base", - "//skia", - ] - - defines = [ "IS_TEXT_RECOGNITION_BROWSER_IMPL" ] }