// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/arc/intent_helper/arc_intent_helper_bridge.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "ash/components/arc/mojom/intent_helper.mojom-forward.h"
#include "ash/components/arc/mojom/intent_helper.mojom-shared.h"
#include "ash/components/arc/mojom/intent_helper.mojom.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
#include "components/arc/common/intent_helper/arc_intent_helper_package.h"
#include "components/arc/intent_helper/intent_constants.h"
#include "components/arc/intent_helper/open_url_delegate.h"
#include "mojo/public/cpp/bindings/clone_traits.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace arc {
class ArcIntentHelperTest : public testing::Test {
protected:
ArcIntentHelperTest() = default;
ArcIntentHelperTest(const ArcIntentHelperTest&) = delete;
ArcIntentHelperTest& operator=(const ArcIntentHelperTest&) = delete;
class TestOpenUrlDelegate : public OpenUrlDelegate {
public:
~TestOpenUrlDelegate() override = default;
// OpenUrlDelegate:
void OpenUrlFromArc(const GURL& url) override { last_opened_url_ = url; }
void OpenWebAppFromArc(const GURL& url) override { last_opened_url_ = url; }
void OpenArcCustomTab(
const GURL& url,
int32_t task_id,
mojom::IntentHelperHost::OnOpenCustomTabCallback callback) override {
std::move(callback).Run(mojo::NullRemote());
}
void OpenChromePageFromArc(mojom::ChromePage chrome_page) override {}
void OpenAppWithIntent(const GURL& url,
mojom::LaunchIntentPtr intent) override {
last_opened_url_ = url;
last_opened_intent_ = std::move(intent);
}
GURL TakeLastOpenedUrl() {
GURL result = std::move(last_opened_url_);
last_opened_url_ = GURL();
return result;
}
mojom::LaunchIntentPtr TakeLastOpenedIntent() {
auto result = std::move(last_opened_intent_);
last_opened_intent_.reset();
return result;
}
private:
GURL last_opened_url_;
mojom::LaunchIntentPtr last_opened_intent_;
};
std::unique_ptr<ArcBridgeService> arc_bridge_service_;
std::unique_ptr<TestOpenUrlDelegate> test_open_url_delegate_;
std::unique_ptr<ArcIntentHelperBridge> instance_;
private:
void SetUp() override {
arc_bridge_service_ = std::make_unique<ArcBridgeService>();
test_open_url_delegate_ = std::make_unique<TestOpenUrlDelegate>();
instance_ = std::make_unique<ArcIntentHelperBridge>(
nullptr /* context */, arc_bridge_service_.get());
ArcIntentHelperBridge::SetOpenUrlDelegate(test_open_url_delegate_.get());
}
void TearDown() override {
ArcIntentHelperBridge::SetOpenUrlDelegate(nullptr);
instance_.reset();
test_open_url_delegate_.reset();
arc_bridge_service_.reset();
}
};
// Tests if FilterOutIntentHelper removes handlers as expected.
TEST_F(ArcIntentHelperTest, TestFilterOutIntentHelper) {
{
std::vector<mojom::IntentHandlerInfoPtr> orig;
std::vector<mojom::IntentHandlerInfoPtr> filtered =
ArcIntentHelperBridge::FilterOutIntentHelper(std::move(orig));
EXPECT_EQ(0U, filtered.size());
}
{
std::vector<mojom::IntentHandlerInfoPtr> orig;
orig.push_back(mojom::IntentHandlerInfo::New());
orig[0]->name = "0";
orig[0]->package_name = "package_name0";
orig.push_back(mojom::IntentHandlerInfo::New());
orig[1]->name = "1";
orig[1]->package_name = "package_name1";
// FilterOutIntentHelper is no-op in this case.
std::vector<mojom::IntentHandlerInfoPtr> filtered =
ArcIntentHelperBridge::FilterOutIntentHelper(std::move(orig));
EXPECT_EQ(2U, filtered.size());
}
{
std::vector<mojom::IntentHandlerInfoPtr> orig;
orig.push_back(mojom::IntentHandlerInfo::New());
orig[0]->name = "0";
orig[0]->package_name = kArcIntentHelperPackageName;
orig.push_back(mojom::IntentHandlerInfo::New());
orig[1]->name = "1";
orig[1]->package_name = "package_name1";
// FilterOutIntentHelper should remove the first element.
std::vector<mojom::IntentHandlerInfoPtr> filtered =
ArcIntentHelperBridge::FilterOutIntentHelper(std::move(orig));
ASSERT_EQ(1U, filtered.size());
EXPECT_EQ("1", filtered[0]->name);
EXPECT_EQ("package_name1", filtered[0]->package_name);
}
{
std::vector<mojom::IntentHandlerInfoPtr> orig;
orig.push_back(mojom::IntentHandlerInfo::New());
orig[0]->name = "0";
orig[0]->package_name = kArcIntentHelperPackageName;
orig.push_back(mojom::IntentHandlerInfo::New());
orig[1]->name = "1";
orig[1]->package_name = "package_name1";
orig.push_back(mojom::IntentHandlerInfo::New());
orig[2]->name = "2";
orig[2]->package_name = kArcIntentHelperPackageName;
// FilterOutIntentHelper should remove two elements.
std::vector<mojom::IntentHandlerInfoPtr> filtered =
ArcIntentHelperBridge::FilterOutIntentHelper(std::move(orig));
ASSERT_EQ(1U, filtered.size());
EXPECT_EQ("1", filtered[0]->name);
EXPECT_EQ("package_name1", filtered[0]->package_name);
}
{
std::vector<mojom::IntentHandlerInfoPtr> orig;
orig.push_back(mojom::IntentHandlerInfo::New());
orig[0]->name = "0";
orig[0]->package_name = kArcIntentHelperPackageName;
orig.push_back(mojom::IntentHandlerInfo::New());
orig[1]->name = "1";
orig[1]->package_name = kArcIntentHelperPackageName;
// FilterOutIntentHelper should remove all elements.
std::vector<mojom::IntentHandlerInfoPtr> filtered =
ArcIntentHelperBridge::FilterOutIntentHelper(std::move(orig));
EXPECT_EQ(0U, filtered.size());
}
}
// Tests if observer works as expected.
TEST_F(ArcIntentHelperTest, TestObserver) {
class MockObserver : public ArcIntentHelperObserver {
public:
MOCK_METHOD(void,
OnIntentFiltersUpdated,
(const std::optional<std::string>& package_name),
(override));
MOCK_METHOD(void,
OnArcSupportedLinksChanged,
(const std::vector<arc::mojom::SupportedLinksPackagePtr>&
added_packages,
const std::vector<arc::mojom::SupportedLinksPackagePtr>&
removed_packages,
arc::mojom::SupportedLinkChangeSource source),
(override));
};
// Create and add observer.
testing::StrictMock<MockObserver> observer;
instance_->AddObserver(&observer);
{
// Observer should be called when an intent filter is updated.
EXPECT_CALL(observer, OnIntentFiltersUpdated(testing::Eq(std::nullopt)));
instance_->OnIntentFiltersUpdated(/*filters=*/std::vector<IntentFilter>());
testing::Mock::VerifyAndClearExpectations(&observer);
}
{
// Observer should be called when supported links change.
EXPECT_CALL(observer, OnArcSupportedLinksChanged);
instance_->OnSupportedLinksChanged(
/*added_packages=*/{},
/*removed_packages=*/{},
arc::mojom::SupportedLinkChangeSource::kArcSystem);
testing::Mock::VerifyAndClearExpectations(&observer);
}
// Observer should not be called after it's removed.
instance_->RemoveObserver(&observer);
instance_->OnIntentFiltersUpdated(/*filters=*/{});
instance_->OnSupportedLinksChanged(
/*added_packages=*/{},
/*removed_packages=*/{},
arc::mojom::SupportedLinkChangeSource::kArcSystem);
}
// Tests that OnOpenUrl opens the URL in Chrome browser.
TEST_F(ArcIntentHelperTest, TestOnOpenUrl) {
instance_->OnOpenUrl("http://google.com");
EXPECT_EQ(GURL("http://google.com"),
test_open_url_delegate_->TakeLastOpenedUrl());
instance_->OnOpenUrl("https://google.com");
EXPECT_EQ(GURL("https://google.com"),
test_open_url_delegate_->TakeLastOpenedUrl());
}
// Tests that OnOpenWebApp opens only HTTPS URLs or localhost.
TEST_F(ArcIntentHelperTest, TestOnOpenWebApp) {
instance_->OnOpenWebApp("http://google.com");
EXPECT_EQ(GURL(), test_open_url_delegate_->TakeLastOpenedUrl());
instance_->OnOpenWebApp("http://localhost/");
EXPECT_EQ(GURL("http://localhost/"),
test_open_url_delegate_->TakeLastOpenedUrl());
instance_->OnOpenWebApp("https://google.com");
EXPECT_EQ(GURL("https://google.com"),
test_open_url_delegate_->TakeLastOpenedUrl());
}
// Tests that OnOpenUrl does not open URLs with the 'chrome://' and equivalent
// schemes like 'about:'.
TEST_F(ArcIntentHelperTest, TestOnOpenUrl_ChromeScheme) {
instance_->OnOpenUrl("chrome://www.google.com");
EXPECT_FALSE(test_open_url_delegate_->TakeLastOpenedUrl().is_valid());
instance_->OnOpenUrl("chrome://settings");
EXPECT_FALSE(test_open_url_delegate_->TakeLastOpenedUrl().is_valid());
instance_->OnOpenUrl("about:");
EXPECT_FALSE(test_open_url_delegate_->TakeLastOpenedUrl().is_valid());
instance_->OnOpenUrl("about:settings");
EXPECT_FALSE(test_open_url_delegate_->TakeLastOpenedUrl().is_valid());
instance_->OnOpenUrl("about:blank");
EXPECT_FALSE(test_open_url_delegate_->TakeLastOpenedUrl().is_valid());
}
// Tests that OnOpenAppWithIntents opens only HTTPS URLs.
TEST_F(ArcIntentHelperTest, TestOnOpenAppWithIntent) {
auto intent = mojom::LaunchIntent::New();
intent->action = arc::kIntentActionSend;
intent->extra_text = "Foo";
instance_->OnOpenAppWithIntent(GURL("https://www.google.com"),
std::move(intent));
EXPECT_EQ(GURL("https://www.google.com"),
test_open_url_delegate_->TakeLastOpenedUrl());
EXPECT_EQ("Foo", test_open_url_delegate_->TakeLastOpenedIntent()->extra_text);
instance_->OnOpenAppWithIntent(GURL("http://www.google.com"),
mojom::LaunchIntent::New());
EXPECT_FALSE(test_open_url_delegate_->TakeLastOpenedUrl().is_valid());
EXPECT_TRUE(test_open_url_delegate_->TakeLastOpenedIntent().is_null());
instance_->OnOpenAppWithIntent(GURL("http://localhost:8000/foo"),
mojom::LaunchIntent::New());
EXPECT_TRUE(test_open_url_delegate_->TakeLastOpenedUrl().is_valid());
EXPECT_FALSE(test_open_url_delegate_->TakeLastOpenedIntent().is_null());
instance_->OnOpenAppWithIntent(GURL("chrome://settings"),
mojom::LaunchIntent::New());
EXPECT_FALSE(test_open_url_delegate_->TakeLastOpenedUrl().is_valid());
EXPECT_TRUE(test_open_url_delegate_->TakeLastOpenedIntent().is_null());
}
// Tests that AppendStringToIntentHelperPackageName works.
TEST_F(ArcIntentHelperTest, TestAppendStringToIntentHelperPackageName) {
std::string package_name = kArcIntentHelperPackageName;
std::string fake_activity = "this_is_a_fake_activity";
EXPECT_EQ(ArcIntentHelperBridge::AppendStringToIntentHelperPackageName(
fake_activity),
package_name + "." + fake_activity);
const std::string empty_string;
EXPECT_EQ(ArcIntentHelperBridge::AppendStringToIntentHelperPackageName(
empty_string),
package_name + ".");
}
} // namespace arc