blob: 9ca918dd9d70456b50c794d04a63430e040d98a5 [file] [log] [blame]
// Copyright 2016 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/ui/webui/offline/offline_internals_ui_message_handler.h"
#include <stdint.h>
#include <stdlib.h>
#include <algorithm>
#include <utility>
#include "base/bind.h"
#include "base/guid.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/offline_pages/offline_page_model_factory.h"
#include "chrome/browser/offline_pages/prefetch/prefetch_service_factory.h"
#include "chrome/browser/offline_pages/prefetch/prefetched_pages_notifier.h"
#include "chrome/browser/offline_pages/request_coordinator_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_content_client.h"
#include "components/offline_pages/core/client_namespace_constants.h"
#include "components/offline_pages/core/offline_page_feature.h"
#include "components/offline_pages/core/prefetch/generate_page_bundle_request.h"
#include "components/offline_pages/core/prefetch/get_operation_request.h"
#include "components/offline_pages/core/prefetch/prefetch_background_task_handler.h"
#include "components/offline_pages/core/prefetch/prefetch_dispatcher.h"
#include "components/offline_pages/core/prefetch/prefetch_downloader.h"
#include "components/offline_pages/core/prefetch/prefetch_service.h"
#include "components/offline_pages/core/prefetch/prefetch_types.h"
#include "content/public/browser/web_ui.h"
#include "net/base/network_change_notifier.h"
namespace offline_internals {
namespace {
std::string GetStringFromDeletePageResult(
offline_pages::DeletePageResult value) {
switch (value) {
case offline_pages::DeletePageResult::SUCCESS:
return "Success";
case offline_pages::DeletePageResult::CANCELLED:
return "Cancelled";
case offline_pages::DeletePageResult::STORE_FAILURE:
return "Store failure";
case offline_pages::DeletePageResult::DEVICE_FAILURE:
return "Device failure";
case offline_pages::DeletePageResult::NOT_FOUND:
return "Not found";
case offline_pages::DeletePageResult::RESULT_COUNT:
break;
}
NOTREACHED();
return "Unknown";
}
std::string GetStringFromDeleteRequestResults(
const offline_pages::MultipleItemStatuses& results) {
// If any requests failed, return "failure", else "success".
for (const auto& result : results) {
if (result.second == offline_pages::ItemActionStatus::STORE_ERROR)
return "Store failure, could not delete one or more requests";
}
return "Success";
}
std::string GetStringFromSavePageStatus() {
return "Available";
}
} // namespace
OfflineInternalsUIMessageHandler::OfflineInternalsUIMessageHandler()
: offline_page_model_(nullptr),
request_coordinator_(nullptr),
prefetch_service_(nullptr),
weak_ptr_factory_(this) {}
OfflineInternalsUIMessageHandler::~OfflineInternalsUIMessageHandler() {}
void OfflineInternalsUIMessageHandler::HandleDeleteSelectedPages(
const base::ListValue* args) {
std::string callback_id;
CHECK(args->GetString(0, &callback_id));
std::vector<int64_t> offline_ids;
const base::ListValue* offline_ids_from_arg;
args->GetList(1, &offline_ids_from_arg);
for (size_t i = 0; i < offline_ids_from_arg->GetSize(); i++) {
std::string value;
offline_ids_from_arg->GetString(i, &value);
int64_t int_value;
base::StringToInt64(value, &int_value);
offline_ids.push_back(int_value);
}
offline_page_model_->DeletePagesByOfflineId(
offline_ids,
base::Bind(&OfflineInternalsUIMessageHandler::HandleDeletedPagesCallback,
weak_ptr_factory_.GetWeakPtr(), callback_id));
}
void OfflineInternalsUIMessageHandler::HandleDeleteSelectedRequests(
const base::ListValue* args) {
std::string callback_id;
CHECK(args->GetString(0, &callback_id));
std::vector<int64_t> offline_ids;
const base::ListValue* offline_ids_from_arg = nullptr;
args->GetList(1, &offline_ids_from_arg);
for (size_t i = 0; i < offline_ids_from_arg->GetSize(); i++) {
std::string value;
offline_ids_from_arg->GetString(i, &value);
int64_t int_value;
base::StringToInt64(value, &int_value);
offline_ids.push_back(int_value);
}
// Call RequestCoordinator to delete them
if (request_coordinator_) {
request_coordinator_->RemoveRequests(
offline_ids,
base::Bind(
&OfflineInternalsUIMessageHandler::HandleDeletedRequestsCallback,
weak_ptr_factory_.GetWeakPtr(), callback_id));
}
}
void OfflineInternalsUIMessageHandler::HandleDeletedPagesCallback(
std::string callback_id,
offline_pages::DeletePageResult result) {
ResolveJavascriptCallback(base::Value(callback_id),
base::Value(GetStringFromDeletePageResult(result)));
}
void OfflineInternalsUIMessageHandler::HandleDeletedRequestsCallback(
std::string callback_id,
const offline_pages::MultipleItemStatuses& results) {
ResolveJavascriptCallback(
base::Value(callback_id),
base::Value(GetStringFromDeleteRequestResults(results)));
}
void OfflineInternalsUIMessageHandler::HandleStoredPagesCallback(
std::string callback_id,
const offline_pages::MultipleOfflinePageItemResult& pages) {
base::ListValue results;
for (const auto& page : pages) {
auto offline_page = std::make_unique<base::DictionaryValue>();
offline_page->SetString("onlineUrl", page.url.spec());
offline_page->SetString("namespace", page.client_id.name_space);
offline_page->SetDouble("size", page.file_size);
offline_page->SetString("id", std::to_string(page.offline_id));
offline_page->SetString("filePath", page.file_path.MaybeAsASCII());
offline_page->SetDouble("creationTime", page.creation_time.ToJsTime());
offline_page->SetDouble("lastAccessTime", page.last_access_time.ToJsTime());
offline_page->SetInteger("accessCount", page.access_count);
offline_page->SetString("originalUrl", page.original_url.spec());
offline_page->SetString("requestOrigin", page.request_origin);
results.Append(std::move(offline_page));
}
// Sort by creation order.
std::sort(results.GetList().begin(), results.GetList().end(),
[](auto& a, auto& b) {
return a.FindKey({"creationTime"})->GetDouble() <
b.FindKey({"creationTime"})->GetDouble();
});
ResolveJavascriptCallback(base::Value(callback_id), results);
}
void OfflineInternalsUIMessageHandler::HandleRequestQueueCallback(
std::string callback_id,
offline_pages::GetRequestsResult result,
std::vector<std::unique_ptr<offline_pages::SavePageRequest>> requests) {
base::ListValue save_page_requests;
if (result == offline_pages::GetRequestsResult::SUCCESS) {
for (const auto& request : requests) {
auto save_page_request = std::make_unique<base::DictionaryValue>();
save_page_request->SetString("onlineUrl", request->url().spec());
save_page_request->SetDouble("creationTime",
request->creation_time().ToJsTime());
save_page_request->SetString("status", GetStringFromSavePageStatus());
save_page_request->SetString("namespace",
request->client_id().name_space);
save_page_request->SetDouble("lastAttemptTime",
request->last_attempt_time().ToJsTime());
save_page_request->SetString("id", std::to_string(request->request_id()));
save_page_request->SetString("originalUrl",
request->original_url().spec());
save_page_request->SetString("requestOrigin", request->request_origin());
save_page_requests.Append(std::move(save_page_request));
}
}
ResolveJavascriptCallback(base::Value(callback_id), save_page_requests);
}
void OfflineInternalsUIMessageHandler::HandleGetRequestQueue(
const base::ListValue* args) {
AllowJavascript();
std::string callback_id;
CHECK(args->GetString(0, &callback_id));
if (request_coordinator_) {
request_coordinator_->queue()->GetRequests(base::Bind(
&OfflineInternalsUIMessageHandler::HandleRequestQueueCallback,
weak_ptr_factory_.GetWeakPtr(), callback_id));
} else {
base::ListValue results;
ResolveJavascriptCallback(base::Value(callback_id), results);
}
}
void OfflineInternalsUIMessageHandler::HandleGetStoredPages(
const base::ListValue* args) {
AllowJavascript();
std::string callback_id;
CHECK(args->GetString(0, &callback_id));
if (offline_page_model_) {
offline_page_model_->GetAllPages(
base::Bind(&OfflineInternalsUIMessageHandler::HandleStoredPagesCallback,
weak_ptr_factory_.GetWeakPtr(), callback_id));
} else {
base::ListValue results;
ResolveJavascriptCallback(base::Value(callback_id), results);
}
}
void OfflineInternalsUIMessageHandler::HandleSetRecordPageModel(
const base::ListValue* args) {
AllowJavascript();
bool should_record;
CHECK(args->GetBoolean(0, &should_record));
if (offline_page_model_)
offline_page_model_->GetLogger()->SetIsLogging(should_record);
}
void OfflineInternalsUIMessageHandler::HandleGetNetworkStatus(
const base::ListValue* args) {
AllowJavascript();
const base::Value* callback_id;
CHECK(args->Get(0, &callback_id));
ResolveJavascriptCallback(
*callback_id,
base::Value(net::NetworkChangeNotifier::IsOffline() ? "Offline"
: "Online"));
}
void OfflineInternalsUIMessageHandler::HandleScheduleNwake(
const base::ListValue* args) {
AllowJavascript();
const base::Value* callback_id;
CHECK(args->Get(0, &callback_id));
if (prefetch_service_) {
prefetch_service_->GetPrefetchBackgroundTaskHandler()
->EnsureTaskScheduled();
ResolveJavascriptCallback(*callback_id, base::Value("Scheduled."));
} else {
RejectJavascriptCallback(*callback_id,
base::Value("No prefetch service available."));
}
}
void OfflineInternalsUIMessageHandler::HandleCancelNwake(
const base::ListValue* args) {
AllowJavascript();
const base::Value* callback_id;
CHECK(args->Get(0, &callback_id));
if (prefetch_service_) {
prefetch_service_->GetPrefetchBackgroundTaskHandler()
->CancelBackgroundTask();
ResolveJavascriptCallback(*callback_id, base::Value("Cancelled."));
} else {
RejectJavascriptCallback(*callback_id,
base::Value("No prefetch service available."));
}
}
void OfflineInternalsUIMessageHandler::HandleShowPrefetchNotification(
const base::ListValue* args) {
AllowJavascript();
const base::Value* callback_id;
CHECK(args->Get(0, &callback_id));
offline_pages::ShowPrefetchedContentNotification(
GURL("https://www.example.com"));
ResolveJavascriptCallback(*callback_id, base::Value("Scheduled."));
}
void OfflineInternalsUIMessageHandler::HandleGeneratePageBundle(
const base::ListValue* args) {
AllowJavascript();
std::string callback_id;
CHECK(args->GetString(0, &callback_id));
if (!prefetch_service_) {
RejectJavascriptCallback(base::Value(callback_id),
base::Value("No prefetch service available."));
return;
}
std::string data;
CHECK(args->GetString(1, &data));
std::vector<std::string> page_urls = base::SplitStringUsingSubstr(
data, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
std::vector<offline_pages::PrefetchURL> prefetch_urls;
for (auto& page_url : page_urls) {
// Creates a dummy prefetch URL with a bogus ID, and using the URL as the
// page title.
GURL url(page_url);
if (url.is_valid()) {
prefetch_urls.push_back(offline_pages::PrefetchURL(
"offline-internals", url, base::UTF8ToUTF16(page_url)));
}
}
prefetch_service_->GetPrefetchDispatcher()->AddCandidatePrefetchURLs(
offline_pages::kSuggestedArticlesNamespace, prefetch_urls);
// Note: Static casts are needed here so that both Windows and Android can
// compile these printf formats.
std::string message =
base::StringPrintf("Added %zu candidate URLs.", prefetch_urls.size());
if (prefetch_urls.size() < page_urls.size()) {
size_t invalid_urls_count = page_urls.size() - prefetch_urls.size();
message.append(
base::StringPrintf(" Ignored %zu invalid URLs.", invalid_urls_count));
}
message.append("\n");
// Construct a JSON array containing all the URLs. To guard against malicious
// URLs that might contain special characters, we create a ListValue and then
// serialize it into JSON, instead of doing direct string manipulation.
base::ListValue urls;
for (const auto& prefetch_url : prefetch_urls) {
urls.GetList().emplace_back(prefetch_url.url.spec());
}
std::string json;
base::JSONWriter::Write(urls, &json);
message.append(json);
ResolveJavascriptCallback(base::Value(callback_id), base::Value(message));
}
void OfflineInternalsUIMessageHandler::HandleGetOperation(
const base::ListValue* args) {
AllowJavascript();
std::string callback_id;
CHECK(args->GetString(0, &callback_id));
if (!prefetch_service_) {
RejectJavascriptCallback(base::Value(callback_id),
base::Value("No prefetch service available."));
return;
}
std::string name;
CHECK(args->GetString(1, &name));
base::TrimWhitespaceASCII(name, base::TRIM_ALL, &name);
prefetch_service_->GetPrefetchDispatcher()
->GCMOperationCompletedMessageReceived(name);
base::Value message("GetOperation will be attempted for any matching pages.");
ResolveJavascriptCallback(base::Value(callback_id), message);
}
void OfflineInternalsUIMessageHandler::HandleDownloadArchive(
const base::ListValue* args) {
AllowJavascript();
std::string name;
CHECK(args->GetString(0, &name));
base::TrimWhitespaceASCII(name, base::TRIM_ALL, &name);
if (prefetch_service_) {
prefetch_service_->GetPrefetchDownloader()->StartDownload(
base::GenerateGUID(), name);
}
}
void OfflineInternalsUIMessageHandler::HandleSetRecordRequestQueue(
const base::ListValue* args) {
AllowJavascript();
bool should_record;
CHECK(args->GetBoolean(0, &should_record));
if (request_coordinator_)
request_coordinator_->GetLogger()->SetIsLogging(should_record);
}
void OfflineInternalsUIMessageHandler::HandleSetRecordPrefetchService(
const base::ListValue* args) {
AllowJavascript();
bool should_record;
CHECK(args->GetBoolean(0, &should_record));
if (prefetch_service_)
prefetch_service_->GetLogger()->SetIsLogging(should_record);
}
void OfflineInternalsUIMessageHandler::HandleGetLoggingState(
const base::ListValue* args) {
AllowJavascript();
const base::Value* callback_id;
CHECK(args->Get(0, &callback_id));
base::DictionaryValue result;
result.SetBoolean("modelIsLogging",
offline_page_model_
? offline_page_model_->GetLogger()->GetIsLogging()
: false);
result.SetBoolean("queueIsLogging",
request_coordinator_
? request_coordinator_->GetLogger()->GetIsLogging()
: false);
bool prefetch_logging = false;
if (prefetch_service_) {
prefetch_logging = prefetch_service_->GetLogger()->GetIsLogging();
}
result.SetBoolean("prefetchIsLogging", prefetch_logging);
ResolveJavascriptCallback(*callback_id, result);
}
void OfflineInternalsUIMessageHandler::HandleGetEventLogs(
const base::ListValue* args) {
AllowJavascript();
const base::Value* callback_id;
CHECK(args->Get(0, &callback_id));
std::vector<std::string> logs;
if (offline_page_model_)
offline_page_model_->GetLogger()->GetLogs(&logs);
if (request_coordinator_)
request_coordinator_->GetLogger()->GetLogs(&logs);
if (prefetch_service_)
prefetch_service_->GetLogger()->GetLogs(&logs);
std::sort(logs.begin(), logs.end());
base::ListValue result;
result.AppendStrings(logs);
ResolveJavascriptCallback(*callback_id, result);
}
void OfflineInternalsUIMessageHandler::HandleAddToRequestQueue(
const base::ListValue* args) {
std::string callback_id;
CHECK(args->GetString(0, &callback_id));
if (request_coordinator_) {
std::string url;
CHECK(args->GetString(1, &url));
// To be visible in Downloads UI, these items need a well-formed GUID
// and AsyncNamespace in their ClientId.
std::ostringstream id_stream;
id_stream << base::GenerateGUID();
offline_pages::RequestCoordinator::SavePageLaterParams params;
params.url = GURL(url);
params.client_id = offline_pages::ClientId(offline_pages::kAsyncNamespace,
id_stream.str());
request_coordinator_->SavePageLater(
params,
base::Bind(
&OfflineInternalsUIMessageHandler::HandleSavePageLaterCallback,
weak_ptr_factory_.GetWeakPtr(), callback_id));
} else {
ResolveJavascriptCallback(base::Value(callback_id), base::Value(false));
}
}
void OfflineInternalsUIMessageHandler::HandleSavePageLaterCallback(
std::string callback_id,
offline_pages::AddRequestResult result) {
ResolveJavascriptCallback(
base::Value(callback_id),
base::Value(result == offline_pages::AddRequestResult::SUCCESS));
}
void OfflineInternalsUIMessageHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"deleteSelectedPages",
base::BindRepeating(
&OfflineInternalsUIMessageHandler::HandleDeleteSelectedPages,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"deleteSelectedRequests",
base::BindRepeating(
&OfflineInternalsUIMessageHandler::HandleDeleteSelectedRequests,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getRequestQueue",
base::BindRepeating(
&OfflineInternalsUIMessageHandler::HandleGetRequestQueue,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getStoredPages",
base::BindRepeating(
&OfflineInternalsUIMessageHandler::HandleGetStoredPages,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getEventLogs",
base::BindRepeating(&OfflineInternalsUIMessageHandler::HandleGetEventLogs,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"setRecordRequestQueue",
base::BindRepeating(
&OfflineInternalsUIMessageHandler::HandleSetRecordRequestQueue,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"setRecordPageModel",
base::BindRepeating(
&OfflineInternalsUIMessageHandler::HandleSetRecordPageModel,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"setRecordPrefetchService",
base::BindRepeating(
&OfflineInternalsUIMessageHandler::HandleSetRecordPrefetchService,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getLoggingState",
base::BindRepeating(
&OfflineInternalsUIMessageHandler::HandleGetLoggingState,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"addToRequestQueue",
base::BindRepeating(
&OfflineInternalsUIMessageHandler::HandleAddToRequestQueue,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getNetworkStatus",
base::BindRepeating(
&OfflineInternalsUIMessageHandler::HandleGetNetworkStatus,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"scheduleNwake",
base::BindRepeating(
&OfflineInternalsUIMessageHandler::HandleScheduleNwake,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"cancelNwake",
base::BindRepeating(&OfflineInternalsUIMessageHandler::HandleCancelNwake,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"showPrefetchNotification",
base::BindRepeating(
&OfflineInternalsUIMessageHandler::HandleShowPrefetchNotification,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"generatePageBundle",
base::BindRepeating(
&OfflineInternalsUIMessageHandler::HandleGeneratePageBundle,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getOperation",
base::BindRepeating(&OfflineInternalsUIMessageHandler::HandleGetOperation,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"downloadArchive",
base::BindRepeating(
&OfflineInternalsUIMessageHandler::HandleDownloadArchive,
base::Unretained(this)));
// Get the offline page model associated with this web ui.
Profile* profile = Profile::FromWebUI(web_ui());
offline_page_model_ =
offline_pages::OfflinePageModelFactory::GetForBrowserContext(profile);
request_coordinator_ =
offline_pages::RequestCoordinatorFactory::GetForBrowserContext(profile);
prefetch_service_ =
offline_pages::PrefetchServiceFactory::GetForBrowserContext(profile);
}
void OfflineInternalsUIMessageHandler::OnJavascriptDisallowed() {
weak_ptr_factory_.InvalidateWeakPtrs();
}
} // namespace offline_internals