Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduced copy text from image feature on macOS #16508

Merged
merged 10 commits into from
Jan 13, 2023
Merged
2 changes: 2 additions & 0 deletions app/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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"
Expand Down
1 change: 1 addition & 0 deletions app/brave_command_ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions app/brave_generated_resources.grd
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,25 @@ Or change later at <ph name="SETTINGS_EXTENIONS_LINK">$2<ex>brave://settings/ext
</message>
</if>

<!-- Text recognition dialog-->
<if expr="enable_text_recognition">
<message name="IDS_CONTENT_CONTEXT_COPY_TEXT_FROM_IMAGE" desc="The name of the context menu item to get text from image">
Copy Text From Image
</message>
<message name="IDS_TEXT_RECOGNITION_DIALOG_CLOSE_BUTTON" desc="The text for the close button of text recognition dialog">
Close
</message>
<message name="IDS_TEXT_RECOGNITION_DIALOG_HEADER_IN_PROGRESS" desc="The text for the header of text recognition dialog">
Copying text from image...
</message>
<message name="IDS_TEXT_RECOGNITION_DIALOG_HEADER_COMPLETE" desc="The text for the header of text recognition dialog">
Text copied from image
</message>
<message name="IDS_TEXT_RECOGNITION_DIALOG_HEADER_FAILED" desc="The text for the header of text recognition dialog">
Text copy failed
bsclifton marked this conversation as resolved.
Show resolved Hide resolved
</message>
</if>

<!-- Brave VPN -->
<if expr="enable_brave_vpn">
<message name="IDS_SETTINGS_SHOW_VPN_BUTTON" desc="The label for showing vpn button in the toolbar">
Expand Down
19 changes: 19 additions & 0 deletions browser/ui/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -404,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",
Expand Down Expand Up @@ -443,6 +445,17 @@ 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",
]

deps += [ "//brave/components/text_recognition/browser" ]
}

# This is no longer compiled into Chromium on Android, but we still
# need it
if (is_android) {
Expand Down Expand Up @@ -790,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" ]
}
}
}
12 changes: 12 additions & 0 deletions browser/ui/browser_dialogs.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@
#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;

namespace content {
class WebContents;
} // namespace content

namespace brave {

Expand All @@ -18,6 +24,12 @@ void ShowCrashReportPermissionAskDialog(Browser* browser);
// Run |callback| when dialog closed.
void ShowObsoleteSystemConfirmDialog(base::OnceCallback<void(bool)> callback);

#if BUILDFLAG(ENABLE_TEXT_RECOGNITION)
// Show web modal dialog for showing text that recognized from |image|.
void ShowTextRecognitionDialog(content::WebContents* web_contents,
goodov marked this conversation as resolved.
Show resolved Hide resolved
const SkBitmap& image);
#endif

} // namespace brave

#endif // BRAVE_BROWSER_UI_BROWSER_DIALOGS_H_
150 changes: 150 additions & 0 deletions browser/ui/text_recognition_browsertest.cc
Original file line number Diff line number Diff line change
@@ -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 <memory>

#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<std::string>& text) {
// Test image has "brave" text.
EXPECT_EQ("brave", text[0]);
run_loop_->Quit();
}

void OnGetImageForTextCopy(base::WeakPtr<content::WebContents> web_contents,
const SkBitmap& image) {
if (!web_contents)
return;

brave::ShowTextRecognitionDialog(web_contents.get(), image);
}

void WaitUntil(base::RepeatingCallback<bool()> 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<base::RunLoop>();
run_loop()->Run();
}

base::RunLoop* run_loop() const { return run_loop_.get(); }

GURL image_html_url_;
std::unique_ptr<base::RunLoop> 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<TextRecognitionDialogView*>(
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();
}
29 changes: 29 additions & 0 deletions browser/ui/views/text_recognition_dialog_tracker.cc
Original file line number Diff line number Diff line change
@@ -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<TextRecognitionDialogTracker>(
*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);
43 changes: 43 additions & 0 deletions browser/ui/views/text_recognition_dialog_tracker.h
Original file line number Diff line number Diff line change
@@ -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<TextRecognitionDialogTracker>,
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<TextRecognitionDialogTracker>;
explicit TextRecognitionDialogTracker(content::WebContents* web_contents);

// views::WidgetObserver overrides
void OnWidgetDestroying(views::Widget* widget) override;

raw_ptr<views::Widget> active_dialog_ = nullptr;
base::ScopedObservation<views::Widget, views::WidgetObserver> observation_{
this};

WEB_CONTENTS_USER_DATA_KEY_DECL();
};

#endif // BRAVE_BROWSER_UI_VIEWS_TEXT_RECOGNITION_DIALOG_TRACKER_H_
Loading