blob: 87d607e9b41ac6b7ea56a2ea967c299dcf9d6509 [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/offline_pages/offline_page_auto_fetcher_service.h"
#include <string>
#include <utility>
#include "base/optional.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/offline_pages/request_coordinator_factory.h"
#include "components/offline_pages/core/auto_fetch.h"
#include "components/offline_pages/core/background/request_coordinator.h"
#include "components/offline_pages/core/background/save_page_request.h"
#include "components/offline_pages/core/client_id.h"
#include "components/offline_pages/core/client_namespace_constants.h"
#include "components/offline_pages/core/offline_page_model.h"
#include "components/offline_pages/core/offline_store_utils.h"
#include "url/gurl.h"
namespace offline_pages {
namespace {
constexpr int kMaximumInFlight = 3;
bool URLMatches(const GURL& a, const GURL& b) {
// We strip the fragment, because when loading an offline page, only the most
// recent page saved with the URL (fragment stripped) can be loaded.
GURL::Replacements remove_fragment;
remove_fragment.ClearRef();
return a.ReplaceComponents(remove_fragment) ==
b.ReplaceComponents(remove_fragment);
}
SavePageRequest* FindRequest(
const std::vector<std::unique_ptr<SavePageRequest>>& all_requests,
const GURL& url) {
for (const auto& request : all_requests) {
if (request->client_id().name_space == kAutoAsyncNamespace &&
URLMatches(request->url(), url))
return request.get();
}
return nullptr;
}
} // namespace
// This is an attempt to verify that a task callback eventually calls
// TaskComplete exactly once. If the token is never std::move'd, it will DCHECK
// when it is destroyed.
class OfflinePageAutoFetcherService::TaskToken {
public:
// The static methods should only be called by StartOrEnqueue or TaskComplete.
static TaskToken NewToken() { return TaskToken(); }
static void Finalize(TaskToken* token) { token->alive_ = false; }
TaskToken(TaskToken&& other) : alive_(other.alive_) {
DCHECK(other.alive_);
other.alive_ = false;
}
~TaskToken() { DCHECK(!alive_); }
private:
TaskToken() {}
bool alive_ = true;
DISALLOW_COPY_AND_ASSIGN(TaskToken);
};
OfflinePageAutoFetcherService::OfflinePageAutoFetcherService(
RequestCoordinator* request_coordinator)
: page_load_watcher_(request_coordinator),
request_coordinator_(request_coordinator) {}
OfflinePageAutoFetcherService::~OfflinePageAutoFetcherService() {}
void OfflinePageAutoFetcherService::TrySchedule(bool user_requested,
const GURL& url,
int android_tab_id,
TryScheduleCallback callback) {
StartOrEnqueue(base::BindOnce(
&OfflinePageAutoFetcherService::TryScheduleStep1, GetWeakPtr(),
user_requested, url, android_tab_id, std::move(callback)));
}
void OfflinePageAutoFetcherService::CancelSchedule(const GURL& url) {
StartOrEnqueue(base::BindOnce(
&OfflinePageAutoFetcherService::CancelScheduleStep1, GetWeakPtr(), url));
}
void OfflinePageAutoFetcherService::TryScheduleStep1(
bool user_requested,
const GURL& url,
int android_tab_id,
TryScheduleCallback callback,
TaskToken token) {
// Return an early failure if the URL is not suitable.
if (!OfflinePageModel::CanSaveURL(url)) {
std::move(callback).Run(OfflinePageAutoFetcherScheduleResult::kOtherError);
TaskComplete(std::move(token));
return;
}
// We need to do some checks on in-flight requests before scheduling the
// fetch. So first, get the list of all requests, and proceed to step 2.
request_coordinator_->GetAllRequests(
base::BindOnce(&OfflinePageAutoFetcherService::TryScheduleStep2,
GetWeakPtr(), std::move(token), user_requested, url,
android_tab_id, std::move(callback),
// Unretained is OK because coordinator is calling us back.
base::Unretained(request_coordinator_)));
}
void OfflinePageAutoFetcherService::TryScheduleStep2(
TaskToken token,
bool user_requested,
const GURL& url,
int android_tab_id,
TryScheduleCallback callback,
RequestCoordinator* coordinator,
std::vector<std::unique_ptr<SavePageRequest>> all_requests) {
// If a request for this page is already scheduled, report scheduling as
// successful without doing anything.
if (FindRequest(all_requests, url)) {
std::move(callback).Run(
OfflinePageAutoFetcherScheduleResult::kAlreadyScheduled);
TaskComplete(std::move(token));
return;
}
// Respect kMaximumInFlight.
if (!user_requested) {
int in_flight_count = 0;
for (const auto& request : all_requests) {
if (request->client_id().name_space == kAutoAsyncNamespace) {
++in_flight_count;
}
}
if (in_flight_count >= kMaximumInFlight) {
std::move(callback).Run(
OfflinePageAutoFetcherScheduleResult::kNotEnoughQuota);
TaskComplete(std::move(token));
return;
}
}
// Finally, schedule a new request, and proceed to step 3.
RequestCoordinator::SavePageLaterParams params;
params.url = url;
auto_fetch::ClientIdMetadata metadata(android_tab_id);
params.client_id = auto_fetch::MakeClientId(metadata);
params.user_requested = false;
params.availability =
RequestCoordinator::RequestAvailability::ENABLED_FOR_OFFLINER;
coordinator->SavePageLater(
params,
base::BindOnce(&OfflinePageAutoFetcherService::TryScheduleStep3,
GetWeakPtr(), std::move(token), std::move(callback)));
}
void OfflinePageAutoFetcherService::TryScheduleStep3(
TaskToken token,
TryScheduleCallback callback,
AddRequestResult result) {
// Just forward the response to the mojo caller.
std::move(callback).Run(
result == AddRequestResult::SUCCESS
? OfflinePageAutoFetcherScheduleResult::kScheduled
: OfflinePageAutoFetcherScheduleResult::kOtherError);
TaskComplete(std::move(token));
}
void OfflinePageAutoFetcherService::CancelScheduleStep1(const GURL& url,
TaskToken token) {
// Get all requests, and proceed to step 2.
request_coordinator_->GetAllRequests(base::BindOnce(
&OfflinePageAutoFetcherService::CancelScheduleStep2, GetWeakPtr(),
std::move(token), url, request_coordinator_));
}
void OfflinePageAutoFetcherService::CancelScheduleStep2(
TaskToken token,
const GURL& url,
RequestCoordinator* coordinator,
std::vector<std::unique_ptr<SavePageRequest>> requests) {
// Cancel the request if it's found in the list of all requests.
SavePageRequest* matching_request = FindRequest(requests, url);
if (matching_request) {
coordinator->RemoveRequests(
{matching_request->request_id()},
base::BindOnce(&OfflinePageAutoFetcherService::CancelScheduleStep3,
GetWeakPtr(), std::move(token)));
return;
}
TaskComplete(std::move(token));
}
void OfflinePageAutoFetcherService::CancelScheduleStep3(
TaskToken token,
const MultipleItemStatuses&) {
TaskComplete(std::move(token));
}
void OfflinePageAutoFetcherService::StartOrEnqueue(TaskCallback task) {
bool can_run = task_queue_.empty();
task_queue_.push(std::move(task));
if (can_run)
std::move(task_queue_.front()).Run(TaskToken::NewToken());
}
void OfflinePageAutoFetcherService::TaskComplete(TaskToken token) {
TaskToken::Finalize(&token);
DCHECK(!task_queue_.empty());
DCHECK(!task_queue_.front());
task_queue_.pop();
if (!task_queue_.empty())
std::move(task_queue_.front()).Run(TaskToken::NewToken());
}
bool OfflinePageAutoFetcherService::IsTaskQueueEmptyForTesting() {
return task_queue_.empty();
}
} // namespace offline_pages