blob: a920bad3e273799246fb3efdbb527c0a9c3999ce [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/web_applications/web_app_data_retriever.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/webapps/browser/installable/fake_installable_manager.h"
#include "components/webapps/browser/installable/installable_data.h"
#include "components/webapps/browser/installable/installable_manager.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "components/webapps/common/web_page_metadata.mojom.h"
#include "components/webapps/common/web_page_metadata_agent.mojom-test-utils.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/site_instance.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/web_contents_tester.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
namespace web_app {
namespace {
const char16_t kFooTitle[] = u"Foo Title";
} // namespace
class FakeWebPageMetadataAgent
: public webapps::mojom::WebPageMetadataAgentInterceptorForTesting {
public:
FakeWebPageMetadataAgent() = default;
~FakeWebPageMetadataAgent() override = default;
WebPageMetadataAgent* GetForwardingInterface() override {
NOTREACHED();
return nullptr;
}
void Bind(mojo::ScopedInterfaceEndpointHandle handle) {
receiver_.Bind(mojo::PendingAssociatedReceiver<WebPageMetadataAgent>(
std::move(handle)));
}
// Set |web_app_info| to respond on |GetWebAppInstallInfo|.
void SetWebAppInstallInfo(const WebAppInstallInfo& web_app_info) {
web_app_info_ = web_app_info;
}
void GetWebPageMetadata(GetWebPageMetadataCallback callback) override {
webapps::mojom::WebPageMetadataPtr web_page_metadata(
webapps::mojom::WebPageMetadata::New());
web_page_metadata->application_name = web_app_info_.title;
web_page_metadata->description = web_app_info_.description;
web_page_metadata->application_url = web_app_info_.start_url;
// Convert more fields as needed.
DCHECK(web_app_info_.manifest_icons.empty());
DCHECK(web_app_info_.mobile_capable ==
WebAppInstallInfo::MOBILE_CAPABLE_UNSPECIFIED);
std::move(callback).Run(std::move(web_page_metadata));
}
private:
WebAppInstallInfo web_app_info_;
mojo::AssociatedReceiver<webapps::mojom::WebPageMetadataAgent> receiver_{
this};
};
class WebAppDataRetrieverTest : public ChromeRenderViewHostTestHarness {
public:
WebAppDataRetrieverTest() = default;
WebAppDataRetrieverTest(const WebAppDataRetrieverTest&) = delete;
WebAppDataRetrieverTest& operator=(const WebAppDataRetrieverTest&) = delete;
~WebAppDataRetrieverTest() override = default;
// Set fake WebPageMetadataAgent to avoid mojo connection errors.
void SetFakeWebPageMetadataAgent() {
web_contents()
->GetMainFrame()
->GetRemoteAssociatedInterfaces()
->OverrideBinderForTesting(
webapps::mojom::WebPageMetadataAgent::Name_,
base::BindRepeating(&FakeWebPageMetadataAgent::Bind,
base::Unretained(&fake_chrome_render_frame_)));
// When ProactivelySwapBrowsingInstance or RenderDocument is enabled on
// same-site main-frame navigations, a same-site navigation might result in
// a change of RenderFrames, which WebAppDataRetriever does not track (it
// tracks the old RenderFrame where the navigation started in). So we
// should disable same-site proactive BrowsingInstance for the main frame.
// Note: this will not disable RenderDocument.
// TODO(crbug.com/936696): Make WebAppDataRetriever support a change of
// RenderFrames.
content::DisableProactiveBrowsingInstanceSwapFor(
web_contents()->GetMainFrame());
}
void SetRendererWebAppInstallInfo(const WebAppInstallInfo& web_app_info) {
fake_chrome_render_frame_.SetWebAppInstallInfo(web_app_info);
}
void GetWebAppInstallInfoCallback(
base::OnceClosure quit_closure,
std::unique_ptr<WebAppInstallInfo> web_app_info) {
web_app_info_ = std::move(web_app_info);
std::move(quit_closure).Run();
}
void GetIconsCallback(base::OnceClosure quit_closure,
std::vector<apps::IconInfo> icons) {
icons_ = std::move(icons);
std::move(quit_closure).Run();
}
protected:
content::WebContentsTester* web_contents_tester() {
return content::WebContentsTester::For(web_contents());
}
const std::unique_ptr<WebAppInstallInfo>& web_app_info() {
return web_app_info_.value();
}
const std::vector<apps::IconInfo>& icons() { return icons_; }
private:
FakeWebPageMetadataAgent fake_chrome_render_frame_;
absl::optional<std::unique_ptr<WebAppInstallInfo>> web_app_info_;
std::vector<apps::IconInfo> icons_;
};
TEST_F(WebAppDataRetrieverTest, GetWebAppInstallInfo_NoEntry) {
SetFakeWebPageMetadataAgent();
base::RunLoop run_loop;
WebAppDataRetriever retriever;
retriever.GetWebAppInstallInfo(
web_contents(),
base::BindOnce(&WebAppDataRetrieverTest::GetWebAppInstallInfoCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(nullptr, web_app_info());
}
TEST_F(WebAppDataRetrieverTest, GetWebAppInstallInfo_AppUrlAbsent) {
SetFakeWebPageMetadataAgent();
const GURL kFooUrl("https://foo.example");
web_contents_tester()->NavigateAndCommit(kFooUrl);
WebAppInstallInfo original_web_app_info;
original_web_app_info.start_url = GURL();
SetRendererWebAppInstallInfo(original_web_app_info);
base::RunLoop run_loop;
WebAppDataRetriever retriever;
retriever.GetWebAppInstallInfo(
web_contents(),
base::BindOnce(&WebAppDataRetrieverTest::GetWebAppInstallInfoCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
// If the WebAppInstallInfo has no URL, we fallback to the last committed
// URL.
EXPECT_EQ(kFooUrl, web_app_info()->start_url);
}
TEST_F(WebAppDataRetrieverTest, GetWebAppInstallInfo_AppUrlPresent) {
SetFakeWebPageMetadataAgent();
web_contents_tester()->NavigateAndCommit(GURL("https://foo.example"));
WebAppInstallInfo original_web_app_info;
original_web_app_info.start_url = GURL("https://bar.example");
SetRendererWebAppInstallInfo(original_web_app_info);
base::RunLoop run_loop;
WebAppDataRetriever retriever;
retriever.GetWebAppInstallInfo(
web_contents(),
base::BindOnce(&WebAppDataRetrieverTest::GetWebAppInstallInfoCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(original_web_app_info.start_url, web_app_info()->start_url);
}
TEST_F(WebAppDataRetrieverTest, GetWebAppInstallInfo_TitleAbsentFromRenderer) {
SetFakeWebPageMetadataAgent();
web_contents_tester()->NavigateAndCommit(GURL("https://foo.example"));
web_contents_tester()->SetTitle(kFooTitle);
WebAppInstallInfo original_web_app_info;
original_web_app_info.title = u"";
SetRendererWebAppInstallInfo(original_web_app_info);
base::RunLoop run_loop;
WebAppDataRetriever retriever;
retriever.GetWebAppInstallInfo(
web_contents(),
base::BindOnce(&WebAppDataRetrieverTest::GetWebAppInstallInfoCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
// If the WebAppInstallInfo has no title, we fallback to the WebContents
// title.
EXPECT_EQ(kFooTitle, web_app_info()->title);
}
TEST_F(WebAppDataRetrieverTest,
GetWebAppInstallInfo_TitleAbsentFromWebContents) {
SetFakeWebPageMetadataAgent();
web_contents_tester()->NavigateAndCommit(GURL("https://foo.example"));
web_contents_tester()->SetTitle(u"");
WebAppInstallInfo original_web_app_info;
original_web_app_info.title = u"";
SetRendererWebAppInstallInfo(original_web_app_info);
base::RunLoop run_loop;
WebAppDataRetriever retriever;
retriever.GetWebAppInstallInfo(
web_contents(),
base::BindOnce(&WebAppDataRetrieverTest::GetWebAppInstallInfoCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
// If the WebAppInstallInfo has no title and the WebContents has no title, we
// fallback to start_url.
EXPECT_EQ(base::UTF8ToUTF16(web_app_info()->start_url.spec()),
web_app_info()->title);
}
TEST_F(WebAppDataRetrieverTest, GetWebAppInstallInfo_ConnectionError) {
// Do not set fake WebPageMetadataAgent to simulate connection error.
web_contents_tester()->NavigateAndCommit(GURL("https://foo.example"));
base::RunLoop run_loop;
WebAppDataRetriever retriever;
retriever.GetWebAppInstallInfo(
web_contents(),
base::BindOnce(&WebAppDataRetrieverTest::GetWebAppInstallInfoCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(nullptr, web_app_info());
}
TEST_F(WebAppDataRetrieverTest, GetWebAppInstallInfo_WebContentsDestroyed) {
SetFakeWebPageMetadataAgent();
web_contents_tester()->NavigateAndCommit(GURL("https://foo.example"));
base::RunLoop run_loop;
WebAppDataRetriever retriever;
retriever.GetWebAppInstallInfo(
web_contents(),
base::BindOnce(&WebAppDataRetrieverTest::GetWebAppInstallInfoCallback,
base::Unretained(this), run_loop.QuitClosure()));
DeleteContents();
run_loop.Run();
EXPECT_EQ(nullptr, web_app_info());
}
TEST_F(WebAppDataRetrieverTest,
CheckInstallabilityAndRetrieveManifest_WebContentsDestroyed) {
SetFakeWebPageMetadataAgent();
web_contents_tester()->NavigateAndCommit(GURL("https://foo.example"));
{
webapps::FakeInstallableManager::CreateForWebContentsWithManifest(
web_contents(), webapps::NO_MANIFEST, GURL(),
blink::mojom::Manifest::New());
}
base::RunLoop run_loop;
WebAppDataRetriever retriever;
retriever.CheckInstallabilityAndRetrieveManifest(
web_contents(), /*bypass_service_worker_check=*/false,
base::BindLambdaForTesting(
[&](blink::mojom::ManifestPtr opt_manifest, const GURL& manifest_url,
bool valid_manifest_for_web_app, bool is_installable) {
EXPECT_FALSE(opt_manifest);
EXPECT_EQ(manifest_url, GURL());
EXPECT_FALSE(valid_manifest_for_web_app);
EXPECT_FALSE(is_installable);
run_loop.Quit();
}));
DeleteContents();
run_loop.Run();
}
TEST_F(WebAppDataRetrieverTest, GetIcons_WebContentsDestroyed) {
SetFakeWebPageMetadataAgent();
web_contents_tester()->NavigateAndCommit(GURL("https://foo.example"));
const std::vector<GURL> icon_urls;
bool skip_page_favicons = true;
base::RunLoop run_loop;
WebAppDataRetriever retriever;
retriever.GetIcons(web_contents(), icon_urls, skip_page_favicons,
base::BindLambdaForTesting(
[&](IconsDownloadedResult result, IconsMap icons_map,
DownloadedIconsHttpResults icons_http_results) {
EXPECT_TRUE(icons_map.empty());
run_loop.Quit();
}));
DeleteContents();
run_loop.Run();
}
TEST_F(WebAppDataRetrieverTest, GetWebAppInstallInfo_FrameNavigated) {
SetFakeWebPageMetadataAgent();
web_contents_tester()->SetTitle(kFooTitle);
const GURL kFooUrl("https://foo.example/bar");
web_contents_tester()->NavigateAndCommit(kFooUrl.DeprecatedGetOriginAsURL());
base::RunLoop run_loop;
WebAppDataRetriever retriever;
retriever.GetWebAppInstallInfo(
web_contents(),
base::BindOnce(&WebAppDataRetrieverTest::GetWebAppInstallInfoCallback,
base::Unretained(this), run_loop.QuitClosure()));
web_contents_tester()->NavigateAndCommit(kFooUrl);
run_loop.Run();
EXPECT_EQ(kFooUrl.DeprecatedGetOriginAsURL(), web_app_info()->start_url);
EXPECT_EQ(kFooTitle, web_app_info()->title);
}
TEST_F(WebAppDataRetrieverTest, CheckInstallabilityAndRetrieveManifest) {
SetFakeWebPageMetadataAgent();
const GURL manifest_start_url = GURL("https://example.com/start");
const std::u16string manifest_short_name = u"Short Name from Manifest";
const std::u16string manifest_name = u"Name from Manifest";
const GURL manifest_scope = GURL("https://example.com/scope");
const SkColor manifest_theme_color = 0xAABBCCDD;
{
auto manifest = blink::mojom::Manifest::New();
manifest->short_name = manifest_short_name;
manifest->name = manifest_name;
manifest->start_url = manifest_start_url;
manifest->scope = manifest_scope;
manifest->has_theme_color = true;
manifest->theme_color = manifest_theme_color;
webapps::FakeInstallableManager::CreateForWebContentsWithManifest(
web_contents(), webapps::NO_ERROR_DETECTED,
GURL("https://example.com/manifest"), std::move(manifest));
}
base::RunLoop run_loop;
bool callback_called = false;
WebAppDataRetriever retriever;
retriever.CheckInstallabilityAndRetrieveManifest(
web_contents(), /*bypass_service_worker_check=*/false,
base::BindLambdaForTesting(
[&](blink::mojom::ManifestPtr opt_manifest, const GURL& manifest_url,
bool valid_manifest_for_web_app, bool is_installable) {
EXPECT_TRUE(is_installable);
EXPECT_EQ(manifest_short_name, opt_manifest->short_name);
EXPECT_EQ(manifest_name, opt_manifest->name);
EXPECT_EQ(manifest_start_url, opt_manifest->start_url);
EXPECT_EQ(manifest_scope, opt_manifest->scope);
EXPECT_EQ(manifest_theme_color, opt_manifest->theme_color);
EXPECT_EQ(manifest_url, GURL("https://example.com/manifest"));
callback_called = true;
run_loop.Quit();
}));
run_loop.Run();
EXPECT_TRUE(callback_called);
}
TEST_F(WebAppDataRetrieverTest, CheckInstallabilityFails) {
SetFakeWebPageMetadataAgent();
{
webapps::FakeInstallableManager::CreateForWebContentsWithManifest(
web_contents(), webapps::NO_MANIFEST, GURL(),
blink::mojom::Manifest::New());
}
base::RunLoop run_loop;
bool callback_called = false;
WebAppDataRetriever retriever;
retriever.CheckInstallabilityAndRetrieveManifest(
web_contents(), /*bypass_service_worker_check=*/false,
base::BindLambdaForTesting(
[&](blink::mojom::ManifestPtr opt_manifest, const GURL& manifest_url,
bool valid_manifest_for_web_app, bool is_installable) {
EXPECT_FALSE(is_installable);
EXPECT_FALSE(valid_manifest_for_web_app);
EXPECT_EQ(manifest_url, GURL());
callback_called = true;
run_loop.Quit();
}));
run_loop.Run();
EXPECT_TRUE(callback_called);
}
} // namespace web_app