blob: 7d8c85a162c5144d30e399dffc04ef4986c64d12 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "chrome/browser/background_fetch/background_fetch_delegate_impl.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/download/background_download_service_factory.h"
#include "chrome/browser/download/download_request_limiter.h"
#include "chrome/browser/offline_items_collection/offline_content_aggregator_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_key.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 "components/background_fetch/job_details.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/download/public/background_service/background_download_service.h"
#include "components/download/public/background_service/logger.h"
#include "components/offline_items_collection/core/offline_content_aggregator.h"
#include "components/offline_items_collection/core/offline_content_provider.h"
#include "components/offline_items_collection/core/offline_item.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "url/origin.h"
using offline_items_collection::ContentId;
using offline_items_collection::OfflineContentProvider;
using GetVisualsOptions =
offline_items_collection::OfflineContentProvider::GetVisualsOptions;
using offline_items_collection::OfflineItem;
using offline_items_collection::OfflineItemFilter;
using offline_items_collection::OfflineItemProgressUnit;
using offline_items_collection::OfflineItemVisuals;
namespace {
using net::test_server::BasicHttpResponse;
using net::test_server::HttpRequest;
using net::test_server::HttpResponse;
// Scripts run by this test are defined in
// chrome/test/data/background_fetch/background_fetch.js.
// URL of the test helper page that helps drive these tests.
const char kHelperPage[] = "/background_fetch/background_fetch.html";
// Name of the Background Fetch client as known by the download service.
const char kBackgroundFetchClient[] = "BackgroundFetch";
// Stringified values of request services made to the download service.
const char kResultAccepted[] = "ACCEPTED";
// Title of a Background Fetch started by StartSingleFileDownload().
const char kSingleFileDownloadTitle[] = "Single-file Background Fetch";
// Size of the downloaded resource, used in BackgroundFetch tests.
const int kDownloadedResourceSizeInBytes = 82;
// Incorrect downloadTotal, when it's set too high in the JavaScript file
// loaded by this test.
const int kDownloadTotalBytesTooHigh = 1000;
// Incorrect downloadTotal, when it's set too low in the JavaScript file
// loaded by this test.
const int kDownloadTotalBytesTooLow = 80;
// Number of requests in the fetch() call from the JavaScript file loaded by
// this test.
const int kNumRequestsInFetch = 1;
// Number of icons in the fetch() call from the JavaScript file loaded by this
// test.
const int kNumIconsInFetch = 1;
// Exponential bucket spacing for UKM event data.
const double kUkmEventDataBucketSpacing = 2.0;
// Implementation of a download system logger that provides the ability to wait
// for certain events to happen, notably added and progressing downloads.
class WaitableDownloadLoggerObserver : public download::Logger::Observer {
public:
using DownloadAcceptedCallback =
base::OnceCallback<void(const std::string& guid)>;
WaitableDownloadLoggerObserver() = default;
WaitableDownloadLoggerObserver(const WaitableDownloadLoggerObserver&) =
delete;
WaitableDownloadLoggerObserver& operator=(
const WaitableDownloadLoggerObserver&) = delete;
~WaitableDownloadLoggerObserver() override = default;
// Sets the |callback| to be invoked when a download has been accepted.
void set_download_accepted_callback(DownloadAcceptedCallback callback) {
download_accepted_callback_ = std::move(callback);
}
// download::Logger::Observer implementation:
void OnServiceStatusChanged(
const base::Value::Dict& service_status) override {}
void OnServiceDownloadsAvailable(
const base::Value::List& service_downloads) override {}
void OnServiceDownloadChanged(
const base::Value::Dict& service_download) override {}
void OnServiceDownloadFailed(
const base::Value::Dict& service_download) override {}
void OnServiceRequestMade(const base::Value::Dict& service_request) override {
const std::string* client = service_request.FindString("client");
const std::string* guid = service_request.FindString("guid");
const std::string* result = service_request.FindString("result");
if (*client != kBackgroundFetchClient)
return; // This event is not targeted to us.
if (*result == kResultAccepted && download_accepted_callback_)
std::move(download_accepted_callback_).Run(*guid);
}
private:
DownloadAcceptedCallback download_accepted_callback_;
};
// Observes the offline item collection's content provider and then invokes the
// associated test callbacks when one has been provided.
class OfflineContentProviderObserver final
: public OfflineContentProvider::Observer {
public:
using ItemsAddedCallback =
base::OnceCallback<void(const std::vector<OfflineItem>&)>;
using ItemUpdatedCallback = base::OnceCallback<void(const OfflineItem&)>;
using FinishedProcessingItemCallback =
base::OnceCallback<void(const OfflineItem&)>;
OfflineContentProviderObserver() = default;
OfflineContentProviderObserver(const OfflineContentProviderObserver&) =
delete;
OfflineContentProviderObserver& operator=(
const OfflineContentProviderObserver&) = delete;
~OfflineContentProviderObserver() final = default;
void set_items_added_callback(ItemsAddedCallback callback) {
items_added_callback_ = std::move(callback);
}
void set_finished_processing_item_callback(
FinishedProcessingItemCallback callback) {
finished_processing_item_callback_ = std::move(callback);
}
void set_delegate(BackgroundFetchDelegateImpl* delegate) {
delegate_ = delegate;
}
void PauseOnNextUpdate() {
DCHECK(!resume_);
pause_ = true;
}
void ResumeOnNextUpdate() {
DCHECK(!pause_);
resume_ = true;
}
// OfflineContentProvider::Observer implementation:
void OnItemsAdded(
const OfflineContentProvider::OfflineItemList& items) override {
if (items_added_callback_)
std::move(items_added_callback_).Run(items);
}
void OnItemRemoved(const ContentId& id) override {}
void OnItemUpdated(const OfflineItem& item,
const std::optional<offline_items_collection::UpdateDelta>&
update_delta) override {
if (item.state != offline_items_collection::OfflineItemState::IN_PROGRESS &&
item.state != offline_items_collection::OfflineItemState::PENDING &&
item.state != offline_items_collection::OfflineItemState::PAUSED &&
finished_processing_item_callback_) {
std::move(finished_processing_item_callback_).Run(item);
}
if (pause_) {
if (item.state == offline_items_collection::OfflineItemState::PAUSED) {
Resume(item.id);
pause_ = false;
} else {
delegate_->PauseDownload(item.id);
}
}
if (resume_ &&
item.state == offline_items_collection::OfflineItemState::PAUSED) {
Resume(item.id);
resume_ = false;
}
// Check that the progress is always increasing and never resets.
DCHECK_GE(item.progress.value, latest_item_.progress.value);
latest_item_ = item;
}
void OnContentProviderGoingDown() override {}
const OfflineItem& latest_item() const { return latest_item_; }
private:
void Resume(const ContentId& id) { delegate_->ResumeDownload(id); }
ItemsAddedCallback items_added_callback_;
FinishedProcessingItemCallback finished_processing_item_callback_;
raw_ptr<BackgroundFetchDelegateImpl, AcrossTasksDanglingUntriaged> delegate_ =
nullptr;
bool pause_ = false;
bool resume_ = false;
OfflineItem latest_item_;
};
} // namespace
class BackgroundFetchBrowserTest : public InProcessBrowserTest {
public:
BackgroundFetchBrowserTest()
: offline_content_provider_observer_(
std::make_unique<OfflineContentProviderObserver>()) {}
BackgroundFetchBrowserTest(const BackgroundFetchBrowserTest&) = delete;
BackgroundFetchBrowserTest& operator=(const BackgroundFetchBrowserTest&) =
delete;
~BackgroundFetchBrowserTest() override = default;
void SetUpOnMainThread() override {
https_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
https_server_->RegisterRequestHandler(base::BindRepeating(
&BackgroundFetchBrowserTest::HandleRequest, base::Unretained(this)));
https_server_->AddDefaultHandlers(GetChromeTestDataDir());
ASSERT_TRUE(https_server_->Start());
Profile* profile = browser()->profile();
download_observer_ = std::make_unique<WaitableDownloadLoggerObserver>();
download_service_ =
BackgroundDownloadServiceFactory::GetForKey(profile->GetProfileKey());
download_service_->GetLogger()->AddObserver(download_observer_.get());
// Register our observer for the offline items collection.
OfflineContentAggregatorFactory::GetInstance()
->GetForKey(profile->GetProfileKey())
->AddObserver(offline_content_provider_observer_.get());
SetUpBrowser(browser());
delegate_ = static_cast<BackgroundFetchDelegateImpl*>(
active_browser_->profile()->GetBackgroundFetchDelegate());
DCHECK(delegate_);
offline_content_provider_observer_->set_delegate(delegate_);
}
virtual void SetUpBrowser(Browser* browser) {
set_active_browser(browser);
// Load the helper page that helps drive these tests.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser, https_server_->GetURL(kHelperPage)));
// Register the Service Worker that's required for Background Fetch. The
// behaviour without an activated worker is covered by layout tests.
ASSERT_EQ("ok - service worker registered",
RunScript("RegisterServiceWorker()"));
test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
}
void TearDownOnMainThread() override {
OfflineContentAggregatorFactory::GetInstance()
->GetForKey(active_browser_->profile()->GetProfileKey())
->RemoveObserver(offline_content_provider_observer_.get());
download_service_->GetLogger()->RemoveObserver(download_observer_.get());
download_service_ = nullptr;
}
// ---------------------------------------------------------------------------
// Test execution functions.
// Runs the |script| and waits for one or more items to have been added to the
// offline items collection.
void RunScriptAndWaitForOfflineItems(const std::string& script,
std::vector<OfflineItem>* items) {
DCHECK(items);
base::RunLoop run_loop;
offline_content_provider_observer_->set_items_added_callback(
base::BindOnce(&BackgroundFetchBrowserTest::DidAddItems,
base::Unretained(this), run_loop.QuitClosure(), items));
ASSERT_EQ("ok", RunScript(script));
run_loop.Run();
}
// Runs the |script| and checks the result.
void RunScriptAndCheckResultingMessage(const std::string& script,
const std::string& expected_message) {
ASSERT_EQ(expected_message, RunScript(script));
}
void GetVisualsForOfflineItemSync(
const ContentId& offline_item_id,
std::unique_ptr<OfflineItemVisuals>* out_visuals) {
base::RunLoop run_loop;
delegate_->GetVisualsForItem(
offline_item_id, GetVisualsOptions::IconOnly(),
base::BindOnce(&BackgroundFetchBrowserTest::DidGetVisuals,
base::Unretained(this), run_loop.QuitClosure(),
out_visuals));
run_loop.Run();
}
// ---------------------------------------------------------------------------
// Helper functions.
// Runs the |script| in the current tab and writes the output to |*result|.
content::EvalJsResult RunScript(const std::string& script) {
return content::EvalJs(active_browser_->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame(),
script);
}
// Runs the given |function| and asserts that it responds with "ok".
// Must be wrapped with ASSERT_NO_FATAL_FAILURE().
void RunScriptFunction(const std::string& function) {
ASSERT_EQ("ok", RunScript(function));
}
// Intercepts all requests.
std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request) {
if (request.GetURL().path() == "/background_fetch/upload") {
DCHECK(!request.content.empty());
DCHECK(request_body_.empty());
request_body_ = request.content;
auto response = std::make_unique<BasicHttpResponse>();
response->set_code(net::HTTP_OK);
return response;
}
if (request.GetURL().query() == "clickevent")
std::move(click_event_closure_).Run();
// The default handlers will take care of this request.
return nullptr;
}
// Gets the ideal display size.
gfx::Size GetIconDisplaySize() {
gfx::Size out_display_size;
base::RunLoop run_loop;
browser()->profile()->GetBackgroundFetchDelegate()->GetIconDisplaySize(
base::BindOnce(&BackgroundFetchBrowserTest::DidGetIconDisplaySize,
base::Unretained(this), run_loop.QuitClosure(),
&out_display_size));
run_loop.Run();
return out_display_size;
}
// Called when we've received the ideal icon display size from
// BackgroundFetchDelegate.
void DidGetIconDisplaySize(base::OnceClosure quit_closure,
gfx::Size* out_display_size,
const gfx::Size& display_size) {
DCHECK(out_display_size);
*out_display_size = display_size;
std::move(quit_closure).Run();
}
// Callback for WaitableDownloadLoggerObserver::DownloadAcceptedCallback().
void DidAcceptDownloadCallback(base::OnceClosure quit_closure,
std::string* out_guid,
const std::string& guid) {
DCHECK(out_guid);
*out_guid = guid;
std::move(quit_closure).Run();
}
// Called when the an offline item has been processed.
void DidFinishProcessingItem(base::OnceClosure quit_closure,
OfflineItem* out_item,
const OfflineItem& processed_item) {
DCHECK(out_item);
*out_item = processed_item;
std::move(quit_closure).Run();
}
void RevokeDownloadPermission() {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
DownloadRequestLimiter::TabDownloadState* tab_download_state =
g_browser_process->download_request_limiter()->GetDownloadState(
web_contents, true /* create */);
tab_download_state->set_download_seen();
tab_download_state->SetDownloadStatusAndNotify(
url::Origin::Create(web_contents->GetVisibleURL()),
DownloadRequestLimiter::DOWNLOADS_NOT_ALLOWED);
}
void SetPermission(ContentSettingsType content_type, ContentSetting setting) {
auto* settings_map =
HostContentSettingsMapFactory::GetForProfile(browser()->profile());
DCHECK(settings_map);
ContentSettingsPattern host_pattern =
ContentSettingsPattern::FromURL(https_server_->base_url());
settings_map->SetContentSettingCustomScope(
host_pattern, ContentSettingsPattern::Wildcard(), content_type,
setting);
}
void DidUpdateItem(base::OnceClosure quit_closure,
OfflineItem* out_item,
const OfflineItem& item) {
*out_item = item;
std::move(quit_closure).Run();
}
void set_active_browser(Browser* browser) { active_browser_ = browser; }
net::EmbeddedTestServer* https_server() { return https_server_.get(); }
protected:
raw_ptr<BackgroundFetchDelegateImpl, AcrossTasksDanglingUntriaged> delegate_ =
nullptr;
raw_ptr<download::BackgroundDownloadService, AcrossTasksDanglingUntriaged>
download_service_ = nullptr;
base::OnceClosure click_event_closure_;
std::unique_ptr<WaitableDownloadLoggerObserver> download_observer_;
std::unique_ptr<OfflineContentProviderObserver>
offline_content_provider_observer_;
std::unique_ptr<ukm::TestUkmRecorder> test_ukm_recorder_;
std::string request_body_;
private:
// Callback for RunScriptAndWaitForOfflineItems(), called when the |items|
// have been added to the offline items collection.
void DidAddItems(base::OnceClosure quit_closure,
std::vector<OfflineItem>* out_items,
const std::vector<OfflineItem>& items) {
*out_items = items;
std::move(quit_closure).Run();
}
void DidGetVisuals(base::OnceClosure quit_closure,
std::unique_ptr<OfflineItemVisuals>* out_visuals,
const ContentId& offline_item_id,
std::unique_ptr<OfflineItemVisuals> visuals) {
*out_visuals = std::move(visuals);
std::move(quit_closure).Run();
}
std::unique_ptr<net::EmbeddedTestServer> https_server_;
raw_ptr<Browser, AcrossTasksDanglingUntriaged> active_browser_ = nullptr;
};
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest, DownloadService_Acceptance) {
// Starts a Background Fetch for a single to-be-downloaded file and waits for
// that request to be scheduled with the Download Service.
std::string guid;
{
base::RunLoop run_loop;
download_observer_->set_download_accepted_callback(
base::BindOnce(&BackgroundFetchBrowserTest::DidAcceptDownloadCallback,
base::Unretained(this), run_loop.QuitClosure(), &guid));
ASSERT_NO_FATAL_FAILURE(RunScriptFunction("StartSingleFileDownload()"));
run_loop.Run();
}
EXPECT_FALSE(guid.empty());
}
// Flaky on linux: crbug.com/1182296
#if BUILDFLAG(IS_LINUX)
#define MAYBE_RecordBackgroundFetchUkmEvent \
DISABLED_RecordBackgroundFetchUkmEvent
#else
#define MAYBE_RecordBackgroundFetchUkmEvent RecordBackgroundFetchUkmEvent
#endif
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest,
MAYBE_RecordBackgroundFetchUkmEvent) {
// Start a Background Fetch for a single to-be-downloaded file and test that
// the expected UKM data for the BackgroundFetch UKM event has been recorded.
ASSERT_NO_FATAL_FAILURE(
RunScriptFunction("StartSingleFileDownloadWithCorrectDownloadTotal()"));
std::vector<raw_ptr<const ukm::mojom::UkmEntry, VectorExperimental>> entries =
test_ukm_recorder_->GetEntriesByName(
ukm::builders::BackgroundFetch::kEntryName);
ASSERT_EQ(1u, entries.size());
const auto* entry = entries[0].get();
test_ukm_recorder_->ExpectEntryMetric(
entry, ukm::builders::BackgroundFetch::kHasTitleName, 1);
test_ukm_recorder_->ExpectEntryMetric(
entry, ukm::builders::BackgroundFetch::kNumIconsName, kNumIconsInFetch);
test_ukm_recorder_->ExpectEntryMetric(
entry, ukm::builders::BackgroundFetch::kDownloadTotalName,
ukm::GetExponentialBucketMin(kDownloadedResourceSizeInBytes,
kUkmEventDataBucketSpacing));
test_ukm_recorder_->ExpectEntryMetric(
entry, ukm::builders::BackgroundFetch::kNumRequestsInFetchName,
ukm::GetExponentialBucketMin(kNumRequestsInFetch,
kUkmEventDataBucketSpacing));
test_ukm_recorder_->ExpectEntryMetric(
entry, ukm::builders::BackgroundFetch::kDeniedDueToPermissionsName, 0);
// There is currently no desktop UI for BackgroundFetch, hence the icon
// display size is set to 0,0. Once that's no longer the case, this ASSERT
// will start failing and the unit test will have to be updated.
ASSERT_TRUE(GetIconDisplaySize().IsEmpty());
test_ukm_recorder_->ExpectEntryMetric(
entry, ukm::builders::BackgroundFetch::kRatioOfIdealToChosenIconSizeName,
-1);
}
#if BUILDFLAG(IS_MAC)
// Flaky on Mac: https://crbug.com/1259680
#define MAYBE_OfflineItemCollection_SingleFileMetadata \
DISABLED_OfflineItemCollection_SingleFileMetadata
#else
#define MAYBE_OfflineItemCollection_SingleFileMetadata \
OfflineItemCollection_SingleFileMetadata
#endif
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest,
MAYBE_OfflineItemCollection_SingleFileMetadata) {
// Starts a Background Fetch for a single to-be-downloaded file and waits for
// the fetch to be registered with the offline items collection. We then
// verify that all the appropriate values have been set.
std::vector<OfflineItem> items;
ASSERT_NO_FATAL_FAILURE(
RunScriptAndWaitForOfflineItems("StartSingleFileDownload()", &items));
ASSERT_EQ(items.size(), 1u);
const OfflineItem& offline_item = items[0];
// Verify that the appropriate data is being set.
EXPECT_EQ(offline_item.title, kSingleFileDownloadTitle);
EXPECT_EQ(offline_item.filter, OfflineItemFilter::FILTER_OTHER);
EXPECT_TRUE(offline_item.is_transient);
EXPECT_TRUE(offline_item.is_resumable);
EXPECT_FALSE(offline_item.is_suggested);
EXPECT_FALSE(offline_item.is_off_the_record);
// When downloadTotal isn't specified, we report progress by parts.
EXPECT_EQ(offline_item.progress.value, 0);
EXPECT_EQ(offline_item.progress.max.value(), 1);
EXPECT_EQ(offline_item.progress.unit, OfflineItemProgressUnit::PERCENTAGE);
// Change-detector tests for values we might want to provide or change.
EXPECT_TRUE(offline_item.description.empty());
EXPECT_TRUE(offline_item.url.is_empty());
EXPECT_FALSE(offline_item.is_off_the_record);
}
// Flaky on multiple platforms (b/323879025)/
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest,
DISABLED_OfflineItemCollection_VerifyIconReceived) {
// Starts a Background Fetch for a single to-be-downloaded file and waits for
// the fetch to be registered with the offline items collection. We then
// verify that the expected icon is associated with the newly added offline
// item.
std::vector<OfflineItem> items;
ASSERT_NO_FATAL_FAILURE(
RunScriptAndWaitForOfflineItems("StartSingleFileDownload()", &items));
ASSERT_EQ(items.size(), 1u);
const OfflineItem& offline_item = items[0];
// Verify that the appropriate data is being set.
EXPECT_EQ(offline_item.progress.value, 0);
EXPECT_EQ(offline_item.progress.max.value(), 1);
EXPECT_EQ(offline_item.progress.unit, OfflineItemProgressUnit::PERCENTAGE);
EXPECT_FALSE(offline_item.creation_time.is_null());
// Get visuals associated with the newly added offline item.
std::unique_ptr<OfflineItemVisuals> out_visuals;
GetVisualsForOfflineItemSync(offline_item.id, &out_visuals);
#if BUILDFLAG(IS_ANDROID)
EXPECT_FALSE(out_visuals->icon.IsEmpty());
EXPECT_EQ(out_visuals->icon.Size().width(), 100);
EXPECT_EQ(out_visuals->icon.Size().height(), 100);
#else
EXPECT_TRUE(out_visuals->icon.IsEmpty());
#endif
}
IN_PROC_BROWSER_TEST_F(
BackgroundFetchBrowserTest,
DISABLED_OfflineItemCollection_VerifyResourceDownloadedWhenDownloadTotalLargerThanActualSize) {
// Starts a Background Fetch for a single to-be-downloaded file and waits for
// the fetch to be registered with the offline items collection.
std::vector<OfflineItem> items;
ASSERT_NO_FATAL_FAILURE(RunScriptAndWaitForOfflineItems(
"StartSingleFileDownloadWithBiggerThanActualDownloadTotal()", &items));
ASSERT_EQ(items.size(), 1u);
OfflineItem offline_item = items[0];
// Verify that the appropriate data is being set when we start downloading.
EXPECT_EQ(offline_item.progress.value, 0);
EXPECT_EQ(offline_item.progress.max.value(), kDownloadTotalBytesTooHigh);
EXPECT_EQ(offline_item.progress.unit, OfflineItemProgressUnit::PERCENTAGE);
// Wait for the download to be completed.
{
base::RunLoop run_loop;
offline_content_provider_observer_->set_finished_processing_item_callback(
base::BindOnce(&BackgroundFetchBrowserTest::DidFinishProcessingItem,
base::Unretained(this), run_loop.QuitClosure(),
&offline_item));
run_loop.Run();
}
// Download total is too high; check that we're still reporting by size,
// but have set the max value of the progress bar to the actual download size.
EXPECT_EQ(offline_item.state,
offline_items_collection::OfflineItemState::COMPLETE);
EXPECT_EQ(offline_item.progress.max.value(), offline_item.progress.value);
EXPECT_EQ(offline_item.progress.max.value(), kDownloadedResourceSizeInBytes);
}
// The test is flaky on all platforms. https://crbug.com/1161385.
IN_PROC_BROWSER_TEST_F(
BackgroundFetchBrowserTest,
DISABLED_OfflineItemCollection_VerifyResourceDownloadedWhenDownloadTotalSmallerThanActualSize) {
// Starts a Background Fetch for a single to-be-downloaded file and waits for
// the fetch to be registered with the offline items collection.
std::vector<OfflineItem> items;
ASSERT_NO_FATAL_FAILURE(RunScriptAndWaitForOfflineItems(
"StartSingleFileDownloadWithSmallerThanActualDownloadTotal()", &items));
ASSERT_EQ(items.size(), 1u);
OfflineItem offline_item = items[0];
// Verify that the appropriate data is being set when we start downloading.
EXPECT_EQ(offline_item.progress.value, 0);
EXPECT_EQ(offline_item.progress.max.value(), kDownloadTotalBytesTooLow);
EXPECT_EQ(offline_item.progress.unit, OfflineItemProgressUnit::PERCENTAGE);
// Wait for the offline_item to be processed.
{
base::RunLoop run_loop;
offline_content_provider_observer_->set_finished_processing_item_callback(
base::BindOnce(&BackgroundFetchBrowserTest::DidFinishProcessingItem,
base::Unretained(this), run_loop.QuitClosure(),
&offline_item));
run_loop.Run();
}
// Download total is too low; check that we cancel the download when the
// bytes downloaded exceed downloadTotal.
EXPECT_EQ(offline_item.state,
offline_items_collection::OfflineItemState::CANCELLED);
}
// TODO(crbug.com/40842751): Fix flaky timeouts and re-enable.
IN_PROC_BROWSER_TEST_F(
BackgroundFetchBrowserTest,
DISABLED_OfflineItemCollection_VerifyResourceDownloadedWhenCorrectDownloadTotalSpecified) {
// Starts a Background Fetch for a single to-be-downloaded file and waits for
// the fetch to be registered with the offline items collection.
std::vector<OfflineItem> items;
ASSERT_NO_FATAL_FAILURE(RunScriptAndWaitForOfflineItems(
"StartSingleFileDownloadWithCorrectDownloadTotal()", &items));
ASSERT_EQ(items.size(), 1u);
const OfflineItem& offline_item = items[0];
// Verify that the appropriate data is being set when downloadTotal is
// correctly set.
EXPECT_EQ(offline_item.progress.value, 0);
EXPECT_EQ(offline_item.progress.max.value(), kDownloadedResourceSizeInBytes);
EXPECT_EQ(offline_item.progress.unit, OfflineItemProgressUnit::PERCENTAGE);
}
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest,
OfflineItemCollection_IncognitoPropagated) {
// Starts a fetch from an incognito profile, and makes sure that the
// OfflineItem has the appropriate fields set.
SetUpBrowser(CreateIncognitoBrowser());
std::vector<OfflineItem> items;
ASSERT_NO_FATAL_FAILURE(
RunScriptAndWaitForOfflineItems("StartSingleFileDownload()", &items));
ASSERT_EQ(items.size(), 1u);
ASSERT_TRUE(items[0].is_off_the_record);
}
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest,
FetchesRunToCompletionAndUpdateTitle_Fetched) {
ASSERT_NO_FATAL_FAILURE(RunScriptAndCheckResultingMessage(
"RunFetchTillCompletion()", "backgroundfetchsuccess"));
EXPECT_EQ(offline_content_provider_observer_->latest_item().state,
offline_items_collection::OfflineItemState::COMPLETE);
base::RunLoop().RunUntilIdle(); // Give `updateUI` a chance to propagate.
EXPECT_TRUE(
base::StartsWith(offline_content_provider_observer_->latest_item().title,
"New Fetched Title!", base::CompareCase::SENSITIVE));
}
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest,
FetchesRunToCompletionAndUpdateTitle_Failed) {
ASSERT_NO_FATAL_FAILURE(RunScriptAndCheckResultingMessage(
"RunFetchTillCompletionWithMissingResource()", "backgroundfetchfail"));
EXPECT_EQ(offline_content_provider_observer_->latest_item().state,
offline_items_collection::OfflineItemState::COMPLETE);
base::RunLoop().RunUntilIdle(); // Give `updateUI` a chance to propagate.
EXPECT_TRUE(
base::StartsWith(offline_content_provider_observer_->latest_item().title,
"New Failed Title!", base::CompareCase::SENSITIVE));
}
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest,
FetchesRunToCompletion_Upload) {
ASSERT_NO_FATAL_FAILURE(RunScriptAndCheckResultingMessage(
"RunFetchTillCompletionWithUpload()", "backgroundfetchsuccess"));
EXPECT_EQ(request_body_, "upload!");
}
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest, ClickEventIsDispatched) {
ASSERT_NO_FATAL_FAILURE(RunScriptAndCheckResultingMessage(
"RunFetchTillCompletion()", "backgroundfetchsuccess"));
EXPECT_EQ(offline_content_provider_observer_->latest_item().state,
offline_items_collection::OfflineItemState::COMPLETE);
base::RunLoop().RunUntilIdle(); // Give updates a chance to propagate.
ASSERT_EQ(delegate_->ui_state_map_.size(), 1u);
auto entry = delegate_->ui_state_map_.begin();
std::string job_id = entry->first;
auto& offline_item = entry->second.offline_item;
EXPECT_EQ(offline_items_collection::OfflineItemState::COMPLETE,
offline_item.state);
background_fetch::JobDetails* job_details =
delegate_->GetJobDetails(job_id, /*allow_null=*/true);
ASSERT_TRUE(!!job_details);
EXPECT_EQ(job_details->job_state,
background_fetch::JobDetails::State::kJobComplete);
// Simulate notification click.
delegate_->OpenItem(
offline_items_collection::OpenParams(
offline_items_collection::LaunchLocation::NOTIFICATION),
offline_item.id);
// The offline item and JobDetails should both be deleted at this point.
EXPECT_TRUE(delegate_->ui_state_map_.empty());
EXPECT_FALSE(delegate_->GetJobDetails(job_id, /*allow_null=*/true));
// Wait for click event.
{
base::RunLoop run_loop;
click_event_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
}
// TODO(crbug.com/40120187): Re-enable this test.
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest, DISABLED_AbortFromUI) {
std::vector<OfflineItem> items;
// Creates a registration with more than one request.
ASSERT_NO_FATAL_FAILURE(
RunScriptAndWaitForOfflineItems("StartFetchWithMultipleFiles()", &items));
ASSERT_EQ(items.size(), 1u);
// Simulate an abort from the UI.
delegate_->CancelDownload(items[0].id);
// Wait for an abort event to be dispatched. This is safe because there are
// more requests to process than the scheduler will handle, and every request
// needs to contact the delegate. Since the abort originates from the
// delegate, this fetch will be aborted before the fetch completes.
// Pass in a no-op function since we only want to wait for a message at this
// point.
ASSERT_NO_FATAL_FAILURE(RunScriptAndCheckResultingMessage(
"(() => {})()", "backgroundfetchabort"));
}
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest, FetchCanBePausedAndResumed) {
offline_content_provider_observer_->PauseOnNextUpdate();
ASSERT_NO_FATAL_FAILURE(RunScriptAndCheckResultingMessage(
"RunFetchTillCompletion()", "backgroundfetchsuccess"));
}
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest,
FetchRejectedWithoutPermission) {
RevokeDownloadPermission();
ASSERT_NO_FATAL_FAILURE(RunScriptAndCheckResultingMessage(
"RunFetchAnExpectAnException()",
"This origin does not have permission to start a fetch."));
}
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest, FetchFromServiceWorker) {
auto* settings_map =
HostContentSettingsMapFactory::GetForProfile(browser()->profile());
DCHECK(settings_map);
// Give the needed permissions.
SetPermission(ContentSettingsType::AUTOMATIC_DOWNLOADS,
CONTENT_SETTING_ALLOW);
// The fetch should succeed.
offline_content_provider_observer_->ResumeOnNextUpdate();
ASSERT_NO_FATAL_FAILURE(RunScriptAndCheckResultingMessage(
"StartFetchFromServiceWorker()", "backgroundfetchsuccess"));
// Revoke Automatic Downloads permission.
SetPermission(ContentSettingsType::AUTOMATIC_DOWNLOADS,
CONTENT_SETTING_BLOCK);
// This should fail without the Automatic Downloads permission.
ASSERT_NO_FATAL_FAILURE(RunScriptAndCheckResultingMessage(
"StartFetchFromServiceWorker()", "permissionerror"));
}
// TODO(crbug.com/1271962): Flaky on many platforms.
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest,
DISABLED_FetchFromServiceWorkerWithAsk) {
auto* settings_map =
HostContentSettingsMapFactory::GetForProfile(browser()->profile());
DCHECK(settings_map);
SetPermission(ContentSettingsType::AUTOMATIC_DOWNLOADS, CONTENT_SETTING_ASK);
// The fetch starts in a paused state.
std::vector<OfflineItem> items;
ASSERT_NO_FATAL_FAILURE(RunScriptAndWaitForOfflineItems(
"StartFetchFromServiceWorkerNoWait()", &items));
ASSERT_EQ(items.size(), 1u);
EXPECT_EQ(items[0].state,
offline_items_collection::OfflineItemState::PAUSED);
}
// TODO(crbug.com/1271962): Flaky on many platforms.
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest,
DISABLED_FetchFromChildFrameWithPermissions) {
// Give the needed permissions. The fetch should still start in a paused
// state.
SetPermission(ContentSettingsType::AUTOMATIC_DOWNLOADS,
CONTENT_SETTING_ALLOW);
// The fetch starts in a paused state.
std::vector<OfflineItem> items;
ASSERT_NO_FATAL_FAILURE(
RunScriptAndWaitForOfflineItems("StartFetchFromIframeNoWait()", &items));
ASSERT_EQ(items.size(), 1u);
EXPECT_EQ(items[0].state,
offline_items_collection::OfflineItemState::PAUSED);
}
// TODO(crbug.com/1271962): Flaky on many platforms.
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest,
DISABLED_FetchFromChildFrameWithAsk) {
SetPermission(ContentSettingsType::AUTOMATIC_DOWNLOADS, CONTENT_SETTING_ASK);
// The fetch starts in a paused state.
std::vector<OfflineItem> items;
ASSERT_NO_FATAL_FAILURE(
RunScriptAndWaitForOfflineItems("StartFetchFromIframeNoWait()", &items));
ASSERT_EQ(items.size(), 1u);
EXPECT_EQ(items[0].state,
offline_items_collection::OfflineItemState::PAUSED);
}
IN_PROC_BROWSER_TEST_F(BackgroundFetchBrowserTest,
FetchFromChildFrameWithMissingPermissions) {
SetPermission(ContentSettingsType::AUTOMATIC_DOWNLOADS,
CONTENT_SETTING_BLOCK);
ASSERT_NO_FATAL_FAILURE(RunScriptAndCheckResultingMessage(
"StartFetchFromIframe()", "permissionerror"));
}
class BackgroundFetchFencedFrameBrowserTest
: public BackgroundFetchBrowserTest {
public:
BackgroundFetchFencedFrameBrowserTest() = default;
~BackgroundFetchFencedFrameBrowserTest() override = default;
void SetUpBrowser(Browser* browser) override {
set_active_browser(browser);
GURL url = https_server()->GetURL("/empty.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser, url));
test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
}
content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
return fenced_frame_test_helper_;
}
void RegisterServiceWorker(content::RenderFrameHost* render_frame_host) {
ASSERT_EQ("ok - service worker registered",
content::EvalJs(render_frame_host, "RegisterServiceWorker()"));
}
void StartSingleFileDownload(content::RenderFrameHost* render_frame_host,
std::string expected_result) {
ASSERT_EQ(expected_result,
content::EvalJs(render_frame_host, "StartSingleFileDownload()"));
}
private:
content::test::FencedFrameTestHelper fenced_frame_test_helper_;
};
// Tests that background fetch UKM is not recorded in a fenced frame. The
// renderer should have checked and disallowed the request for fenced frames.
IN_PROC_BROWSER_TEST_F(BackgroundFetchFencedFrameBrowserTest,
NoRecordBackgroundFetchUkmEvent) {
// Load a fenced frame.
GURL fenced_frame_url(https_server()->GetURL("/fenced_frames/title1.html"));
content::RenderFrameHost* fenced_frame =
fenced_frame_test_helper().CreateFencedFrame(browser()
->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame(),
fenced_frame_url);
GURL fenced_frame_test_url(
https_server()->GetURL("/fenced_frames/background_fetch.html"));
// Navigate the fenced frame again.
fenced_frame = fenced_frame_test_helper().NavigateFrameInFencedFrameTree(
fenced_frame, fenced_frame_test_url);
// Register the Service Worker that's required for Background Fetch.
RegisterServiceWorker(fenced_frame);
constexpr char kExpectedError[] =
"NotAllowedError - Failed to execute 'fetch' on "
"'BackgroundFetchManager': backgroundFetch is not allowed in fenced "
"frames.";
StartSingleFileDownload(fenced_frame, kExpectedError);
std::vector<raw_ptr<const ukm::mojom::UkmEntry, VectorExperimental>> entries =
test_ukm_recorder_->GetEntriesByName(
ukm::builders::BackgroundFetch::kEntryName);
ASSERT_EQ(0u, entries.size());
}
// Tests that UKM record works based on the outer most main frame. This test is
// to check non-same origin case, but actually the background fetch UKM is not
// recorded in a fenced frame regardless of origin difference because the
// renderer should have checked and disallowed the request for fenced frames.
IN_PROC_BROWSER_TEST_F(BackgroundFetchFencedFrameBrowserTest,
NoRecordBackgroundFetchUkmEventNotInSameOrigin) {
net::EmbeddedTestServer cross_origin_server(
net::EmbeddedTestServer::TYPE_HTTPS);
cross_origin_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
cross_origin_server.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(cross_origin_server.Start());
// Load a fenced frame.
GURL fenced_frame_url(
cross_origin_server.GetURL("/fenced_frames/title1.html"));
content::RenderFrameHost* fenced_frame =
fenced_frame_test_helper().CreateFencedFrame(browser()
->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame(),
fenced_frame_url);
GURL fenced_frame_test_url(
cross_origin_server.GetURL("/fenced_frames/background_fetch.html"));
// Navigate the fenced frame again.
fenced_frame = fenced_frame_test_helper().NavigateFrameInFencedFrameTree(
fenced_frame, fenced_frame_test_url);
// Register the Service Worker that's required for Background Fetch.
RegisterServiceWorker(fenced_frame);
constexpr char kExpectedError[] =
"NotAllowedError - Failed to execute 'fetch' on "
"'BackgroundFetchManager': backgroundFetch is not allowed in fenced "
"frames.";
StartSingleFileDownload(fenced_frame, kExpectedError);
std::vector<raw_ptr<const ukm::mojom::UkmEntry, VectorExperimental>> entries =
test_ukm_recorder_->GetEntriesByName(
ukm::builders::BackgroundFetch::kEntryName);
ASSERT_EQ(0u, entries.size());
}