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

Open Leo from search #26082

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5c130ec
Add new permission for opening Leo from Brave Search
yrliou Sep 5, 2024
324a77e
Patch for supporting Open Leo permission in permission settings UI
yrliou Sep 11, 2024
f8a8680
Permission settings UI for Open Leo permission
yrliou Sep 12, 2024
82d933d
Add a NavigationThrottle to intercept specific URL requests and open Leo
yrliou Sep 5, 2024
8578c9c
Use TabInterface to show sidebar from WebContent
yrliou Oct 21, 2024
78eea97
Address review comments (@cdesouza-chromium)
yrliou Oct 22, 2024
65e3da2
address review comments
yrliou Oct 25, 2024
278ace5
Override PermissionContextBase::GetPermissionStatusInternal instead o…
yrliou Oct 28, 2024
041c5f7
Add feature flag for open Leo from Brave Search
yrliou Oct 28, 2024
066267a
Add missing !is_android guard for browser/ui/ai_chat target dep
yrliou Oct 28, 2024
c366be1
Add back content setting registration for Android
yrliou Oct 29, 2024
0a9c5a2
Revise permission string
yrliou Oct 29, 2024
35c4751
Add RequestType::kBraveMinValue and RequestType::kBraveMaxValue
yrliou Oct 29, 2024
762e961
Move the nonce validation to AIChatSearchThrottle
yrliou Oct 29, 2024
0e5edde
Fix method typo
yrliou Oct 29, 2024
0db3a85
Add comment for BRAVE_AI_CHAT content setting
yrliou Oct 29, 2024
be1b396
More unit tests
yrliou Oct 30, 2024
ab4ea07
Rename OpenLeo -> OpenAIChat
yrliou Oct 30, 2024
ab1e047
Rename permission/content setting to OpenAIChat
yrliou Oct 30, 2024
c07dcff
fixup! Rename OpenLeo -> OpenAIChat
yrliou Oct 30, 2024
4b7d3d4
update patch after content setting type name change
yrliou Oct 30, 2024
e882251
fixup! Rename permission/content setting to OpenAIChat
yrliou Oct 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/brave_settings_strings.grdp
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,17 @@
Allowed to access localhost resources
</message>

<!-- AI chat setting -->
<message name="IDS_SETTINGS_SITE_SETTINGS_BRAVE_AI_CHAT" desc="Label for Leo AI chat site settings.">
Leo AI chat
</message>
<message name="IDS_SETTINGS_SITE_SETTINGS_BRAVE_AI_CHAT_ASK" desc="Label for the enabled option of Leo AI chat site settings.">
Sites can ask to open Leo AI chat
</message>
<message name="IDS_SETTINGS_SITE_SETTINGS_BRAVE_AI_CHAT_BLOCK" desc="Label for the disabled option of Leo AI chat site settings.">
Don't allow site to open Leo AI chat
</message>

<!-- Settings / Privacy and security / Safety Check -->
<message name="IDS_SETTINGS_BRAVE_SAFETY_CHECK_SAFE_BROWSING_ENABLED_STANDARD_AVAILABLE_ENHANCED" desc="This text points out that Safe Browsing is enabled as standard protection.">
Standard protection is on.
Expand Down
82 changes: 41 additions & 41 deletions browser/about_flags.cc
Original file line number Diff line number Diff line change
Expand Up @@ -361,44 +361,47 @@
#endif

#if BUILDFLAG(ENABLE_AI_CHAT)
#define BRAVE_AI_CHAT \
EXPAND_FEATURE_ENTRIES({ \
"brave-ai-chat", \
"Brave AI Chat", \
"Summarize articles and engage in conversation with AI", \
kOsWin | kOsMac | kOsLinux | kOsAndroid, \
FEATURE_VALUE_TYPE(ai_chat::features::kAIChat), \
})
#define BRAVE_AI_CHAT_HISTORY \
EXPAND_FEATURE_ENTRIES({ \
"brave-ai-chat-history", \
"Brave AI Chat History", \
"Enables AI Chat History persistence and management", \
kOsWin | kOsMac | kOsLinux, \
FEATURE_VALUE_TYPE(ai_chat::features::kAIChatHistory), \
})
#define BRAVE_AI_CHAT_CONTEXT_MENU_REWRITE_IN_PLACE \
EXPAND_FEATURE_ENTRIES({ \
"brave-ai-chat-context-menu-rewrite-in-place", \
"Brave AI Chat Rewrite In Place From Context Menu", \
"Enables AI Chat rewrite in place feature from the context menu", \
kOsDesktop, \
FEATURE_VALUE_TYPE(ai_chat::features::kContextMenuRewriteInPlace), \
})
#define BRAVE_AI_CHAT_PAGE_CONTENT_REFINE \
EXPAND_FEATURE_ENTRIES({ \
"brave-ai-chat-page-content-refine", \
"Brave AI Chat Page Content Refine", \
"Enable local text embedding for long page content in order to find " \
"most relevant parts to the prompt within context limit.", \
kOsDesktop | kOsAndroid, \
FEATURE_VALUE_TYPE(ai_chat::features::kPageContentRefine), \
})
#define BRAVE_AI_CHAT_FEATURE_ENTRIES \
bridiver marked this conversation as resolved.
Show resolved Hide resolved
EXPAND_FEATURE_ENTRIES( \
{ \
"brave-ai-chat", \
"Brave AI Chat", \
"Summarize articles and engage in conversation with AI", \
kOsWin | kOsMac | kOsLinux | kOsAndroid, \
FEATURE_VALUE_TYPE(ai_chat::features::kAIChat), \
}, \
{ \
"brave-ai-chat-history", \
"Brave AI Chat History", \
"Enables AI Chat History persistence and management", \
kOsWin | kOsMac | kOsLinux, \
FEATURE_VALUE_TYPE(ai_chat::features::kAIChatHistory), \
}, \
{ \
"brave-ai-chat-context-menu-rewrite-in-place", \
"Brave AI Chat Rewrite In Place From Context Menu", \
"Enables AI Chat rewrite in place feature from the context menu", \
kOsDesktop, \
FEATURE_VALUE_TYPE(ai_chat::features::kContextMenuRewriteInPlace), \
}, \
{ \
"brave-ai-chat-page-content-refine", \
"Brave AI Chat Page Content Refine", \
"Enable local text embedding for long page content in order to " \
"find " \
"most relevant parts to the prompt within context limit.", \
kOsDesktop | kOsAndroid, \
FEATURE_VALUE_TYPE(ai_chat::features::kPageContentRefine), \
}, \
{ \
"brave-ai-chat-open-leo-from-brave-search", \
"Open Leo AI Chat from Brave Search", \
"Enables opening Leo AI Chat from Brave Search", \
kOsDesktop | kOsAndroid, \
FEATURE_VALUE_TYPE(ai_chat::features::kOpenLeoFromBraveSearch), \
})
#else
#define BRAVE_AI_CHAT
#define BRAVE_AI_CHAT_HISTORY
#define BRAVE_AI_CHAT_CONTEXT_MENU_REWRITE_IN_PLACE
#define BRAVE_AI_CHAT_PAGE_CONTENT_REFINE
#define BRAVE_AI_CHAT_FEATURE_ENTRIES
#endif
#if BUILDFLAG(ENABLE_AI_REWRITER)
#define BRAVE_AI_REWRITER \
Expand Down Expand Up @@ -980,10 +983,7 @@
BRAVE_SAFE_BROWSING_ANDROID \
BRAVE_CHANGE_ACTIVE_TAB_ON_SCROLL_EVENT_FEATURE_ENTRIES \
BRAVE_TABS_FEATURE_ENTRIES \
BRAVE_AI_CHAT \
BRAVE_AI_CHAT_HISTORY \
BRAVE_AI_CHAT_CONTEXT_MENU_REWRITE_IN_PLACE \
BRAVE_AI_CHAT_PAGE_CONTENT_REFINE \
BRAVE_AI_CHAT_FEATURE_ENTRIES \
BRAVE_AI_REWRITER \
BRAVE_OMNIBOX_FEATURES \
BRAVE_MIDDLE_CLICK_AUTOSCROLL_FEATURE_ENTRY \
Expand Down
18 changes: 11 additions & 7 deletions browser/ai_chat/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ assert(enable_ai_chat)

static_library("ai_chat") {
sources = [
"//brave/browser/ai_chat/ai_chat_service_factory.cc",
"//brave/browser/ai_chat/ai_chat_service_factory.h",
"//brave/browser/ai_chat/ai_chat_settings_helper.cc",
"//brave/browser/ai_chat/ai_chat_settings_helper.h",
"//brave/browser/ai_chat/ai_chat_utils.cc",
"//brave/browser/ai_chat/ai_chat_utils.h",
"ai_chat_service_factory.cc",
"ai_chat_service_factory.h",
"ai_chat_settings_helper.cc",
"ai_chat_settings_helper.h",
"ai_chat_utils.cc",
"ai_chat_utils.h",
]

deps = [
Expand Down Expand Up @@ -46,7 +46,10 @@ static_library("ai_chat") {

source_set("unit_tests") {
testonly = true
sources = [ "ai_chat_throttle_unittest.cc" ]
sources = [
"ai_chat_throttle_unittest.cc",
"brave_ai_chat_permission_context_unittest.cc",
]

deps = [
"//base",
Expand All @@ -70,6 +73,7 @@ source_set("browser_tests") {
sources = [
"//chrome/browser/renderer_context_menu/render_view_context_menu_browsertest_util.cc",
"//chrome/browser/renderer_context_menu/render_view_context_menu_browsertest_util.h",
"ai_chat_brave_search_throttle_browsertest.cc",
yrliou marked this conversation as resolved.
Show resolved Hide resolved
"ai_chat_browsertests.cc",
"ai_chat_metrics_browsertest.cc",
"ai_chat_policy_browsertest.cc",
Expand Down
241 changes: 241 additions & 0 deletions browser/ai_chat/ai_chat_brave_search_throttle_browsertest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
/* Copyright (c) 2024 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/ai_chat/content/browser/ai_chat_brave_search_throttle.h"

#include <memory>
#include <string>

#include "base/files/file_path.h"
#include "base/location.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "brave/browser/ui/brave_browser.h"
#include "brave/browser/ui/sidebar/sidebar_controller.h"
#include "brave/browser/ui/sidebar/sidebar_model.h"
#include "brave/components/constants/brave_paths.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/permissions/permission_request_manager.h"
#include "components/permissions/test/mock_permission_prompt_factory.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "net/base/net_errors.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

constexpr char kBraveSearchHost[] = "search.brave.com";
constexpr char kLeoPath[] = "/leo";
constexpr char kOpenLeoButtonValidPath[] = "/open_leo_button_valid.html";
constexpr char kOpenLeoButtonInvalidPath[] = "/open_leo_button_invalid.html";

} // namespace

// TODO(jocelyn): This should be changed to PlatformBrowserTest when we support
// Android. https://github.com/brave/brave-browser/issues/41905
class AIChatBraveSearchThrottleBrowserTest : public InProcessBrowserTest {
bridiver marked this conversation as resolved.
Show resolved Hide resolved
public:
AIChatBraveSearchThrottleBrowserTest()
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}

void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();

mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
host_resolver()->AddRule("*", "127.0.0.1");
content::SetupCrossSiteRedirector(&https_server_);

base::FilePath test_data_dir =
base::PathService::CheckedGet(brave::DIR_TEST_DATA);
test_data_dir = test_data_dir.AppendASCII("leo");
https_server_.ServeFilesFromDirectory(test_data_dir);
ASSERT_TRUE(https_server_.Start());

permissions::PermissionRequestManager* manager =
permissions::PermissionRequestManager::FromWebContents(
GetActiveWebContents());
prompt_factory_ =
std::make_unique<permissions::MockPermissionPromptFactory>(manager);
}

void TearDownOnMainThread() override {
prompt_factory_.reset();
InProcessBrowserTest::TearDownOnMainThread();
}

void SetUpCommandLine(base::CommandLine* command_line) override {
InProcessBrowserTest::SetUpCommandLine(command_line);
mock_cert_verifier_.SetUpCommandLine(command_line);
}

void SetUpInProcessBrowserTestFixture() override {
InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
}

void TearDownInProcessBrowserTestFixture() override {
mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
InProcessBrowserTest::TearDownInProcessBrowserTestFixture();
}

content::WebContents* GetActiveWebContents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}

void ClickOpenLeoButton() {
// Modify the href to have test server port and click it.
ASSERT_TRUE(content::ExecJs(GetActiveWebContents()->GetPrimaryMainFrame(),
content::JsReplace(R"(
const link = document.getElementById('continue-with-leo')
const url = new URL(link.href)
url.port = $1
link.href = url.href
link.click())",
https_server_.port())));
}

bool IsLeoOpened() {
sidebar::SidebarController* controller =
static_cast<BraveBrowser*>(browser())->sidebar_controller();
auto index = controller->model()->GetIndexOf(
sidebar::SidebarItem::BuiltInItemType::kChatUI);
return index.has_value() && controller->IsActiveIndex(index);
}

void CloseLeoPanel(const base::Location& location) {
SCOPED_TRACE(testing::Message() << location.ToString());
sidebar::SidebarController* controller =
static_cast<BraveBrowser*>(browser())->sidebar_controller();
controller->DeactivateCurrentPanel();
ASSERT_FALSE(IsLeoOpened());
}

void NavigateToTestPage(const base::Location& location,
const std::string& host,
const std::string& path,
int expected_prompt_count) {
SCOPED_TRACE(testing::Message() << location.ToString());
ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(),
https_server_.GetURL(host, path)));
EXPECT_FALSE(IsLeoOpened());
EXPECT_EQ(expected_prompt_count, prompt_factory_->show_count());
}

void ClickOpenLeoAndCheckLeoOpenedAndNavigationCancelled(
const base::Location& location,
int expected_prompt_count,
bool expected_leo_opened,
const std::string& expected_last_committed_path =
kOpenLeoButtonValidPath) {
SCOPED_TRACE(testing::Message() << location.ToString());
content::TestNavigationObserver observer(
GetActiveWebContents(), net::ERR_ABORTED,
content::MessageLoopRunner::QuitMode::IMMEDIATE,
false /* ignore_uncommitted_navigations */);
ClickOpenLeoButton();
observer.Wait();

EXPECT_EQ(IsLeoOpened(), expected_leo_opened);
EXPECT_EQ(expected_prompt_count, prompt_factory_->show_count());
EXPECT_EQ(observer.last_navigation_url().path_piece(), kLeoPath);
EXPECT_EQ(GetActiveWebContents()->GetLastCommittedURL().path_piece(),
expected_last_committed_path);
}

protected:
net::test_server::EmbeddedTestServer https_server_;
std::unique_ptr<permissions::MockPermissionPromptFactory> prompt_factory_;

private:
content::ContentMockCertVerifier mock_cert_verifier_;
};

IN_PROC_BROWSER_TEST_F(AIChatBraveSearchThrottleBrowserTest,
OpenLeo_AskAndAccept) {
int cur_prompt_count = 0;
prompt_factory_->set_response_type(
permissions::PermissionRequestManager::ACCEPT_ALL);
NavigateToTestPage(FROM_HERE, kBraveSearchHost, kOpenLeoButtonValidPath,
cur_prompt_count);
ClickOpenLeoAndCheckLeoOpenedAndNavigationCancelled(
FROM_HERE, ++cur_prompt_count,
/*expected_leo_opened=*/true);

CloseLeoPanel(FROM_HERE);
ClickOpenLeoAndCheckLeoOpenedAndNavigationCancelled(
FROM_HERE, cur_prompt_count,
/*expected_leo_opened=*/true);
}

IN_PROC_BROWSER_TEST_F(AIChatBraveSearchThrottleBrowserTest,
OpenLeo_AskAndDeny) {
int cur_prompt_count = 0;
prompt_factory_->set_response_type(
permissions::PermissionRequestManager::DENY_ALL);
NavigateToTestPage(FROM_HERE, kBraveSearchHost, kOpenLeoButtonValidPath,
cur_prompt_count);
ClickOpenLeoAndCheckLeoOpenedAndNavigationCancelled(
FROM_HERE, ++cur_prompt_count, /*expected_leo_opened=*/false);

// Clicking a button again to test no new permission prompt should be shown
// when the permission setting is denied.
ClickOpenLeoAndCheckLeoOpenedAndNavigationCancelled(
FROM_HERE, cur_prompt_count,
/*expected_leo_opened=*/false);
}

IN_PROC_BROWSER_TEST_F(AIChatBraveSearchThrottleBrowserTest,
OpenLeo_AskAndDismiss) {
int cur_prompt_count = 0;
prompt_factory_->set_response_type(
permissions::PermissionRequestManager::DISMISS);
NavigateToTestPage(FROM_HERE, kBraveSearchHost, kOpenLeoButtonValidPath,
cur_prompt_count);
ClickOpenLeoAndCheckLeoOpenedAndNavigationCancelled(
FROM_HERE, ++cur_prompt_count, /*expected_leo_opened=*/false);

// Click a button again after dismissing the permission, permission prompt
// should be shown again.
prompt_factory_->set_response_type(
permissions::PermissionRequestManager::ACCEPT_ALL);
NavigateToTestPage(FROM_HERE, kBraveSearchHost, kOpenLeoButtonValidPath,
cur_prompt_count);
ClickOpenLeoAndCheckLeoOpenedAndNavigationCancelled(
FROM_HERE, ++cur_prompt_count,
/*expected_leo_opened=*/true);
}

IN_PROC_BROWSER_TEST_F(AIChatBraveSearchThrottleBrowserTest,
OpenLeo_MismatchedNonce) {
int cur_prompt_count = 0;
NavigateToTestPage(FROM_HERE, kBraveSearchHost, kOpenLeoButtonInvalidPath,
cur_prompt_count);
// No permission prompt should be shown.
ClickOpenLeoAndCheckLeoOpenedAndNavigationCancelled(
FROM_HERE, cur_prompt_count, /*expected_leo_opened=*/false,
kOpenLeoButtonInvalidPath);
}

IN_PROC_BROWSER_TEST_F(AIChatBraveSearchThrottleBrowserTest,
OpenLeo_NotBraveSearchURL) {
// The behavior should be the same as without the throttle.
NavigateToTestPage(FROM_HERE, "brave.com", kOpenLeoButtonValidPath, 0);
content::TestNavigationObserver observer(GetActiveWebContents());
ClickOpenLeoButton();
observer.Wait();

EXPECT_FALSE(IsLeoOpened());
EXPECT_EQ(0, prompt_factory_->show_count());
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(observer.last_navigation_url().path_piece(), kLeoPath);
EXPECT_EQ(GetActiveWebContents()->GetLastCommittedURL().path_piece(),
kLeoPath);
}
Loading
Loading