blob: b41aca99e28a5ce09f660dcb523ce86330e658bf [file] [log] [blame]
// Copyright 2013 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 "content/renderer/appcache/web_application_cache_host_impl.h"
#include <stddef.h>
#include <vector>
#include "base/compiler_specific.h"
#include "base/containers/id_map.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "content/common/appcache_interfaces.h"
#include "content/public/common/service_names.mojom.h"
#include "content/public/renderer/render_thread.h"
#include "content/renderer/render_frame_impl.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "services/service_manager/public/cpp/connector.h"
#include "third_party/blink/public/mojom/appcache/appcache.mojom.h"
#include "third_party/blink/public/mojom/appcache/appcache_info.mojom.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/platform/web_url_response.h"
using blink::WebApplicationCacheHost;
using blink::WebApplicationCacheHostClient;
using blink::WebString;
using blink::WebURL;
using blink::WebURLResponse;
using blink::WebVector;
namespace content {
namespace {
// Note: the order of the elements in this array must match those
// of the EventID enum in appcache_interfaces.h.
const char* const kEventNames[] = {
"Checking", "Error", "NoUpdate", "Downloading", "Progress",
"UpdateReady", "Cached", "Obsolete"
};
using HostsMap = base::IDMap<WebApplicationCacheHostImpl*>;
HostsMap* all_hosts() {
static HostsMap* map = new HostsMap;
return map;
}
GURL ClearUrlRef(const GURL& url) {
if (!url.has_ref())
return url;
GURL::Replacements replacements;
replacements.ClearRef();
return url.ReplaceComponents(replacements);
}
} // anon namespace
WebApplicationCacheHostImpl* WebApplicationCacheHostImpl::FromId(int id) {
return all_hosts()->Lookup(id);
}
WebApplicationCacheHostImpl::WebApplicationCacheHostImpl(
WebApplicationCacheHostClient* client,
int appcache_host_id,
int render_frame_id,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: binding_(this),
client_(client),
status_(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED),
is_scheme_supported_(false),
is_get_method_(false),
is_new_master_entry_(MAYBE_NEW_ENTRY),
was_select_cache_called_(false) {
DCHECK(client);
// PlzNavigate: The browser passes the ID to be used.
if (appcache_host_id != blink::mojom::kAppCacheNoHostId) {
all_hosts()->AddWithID(this, appcache_host_id);
host_id_ = appcache_host_id;
} else {
host_id_ = all_hosts()->Add(this);
}
DCHECK(host_id_ != blink::mojom::kAppCacheNoHostId);
static const base::NoDestructor<blink::mojom::AppCacheBackendPtr> backend_ptr(
[] {
blink::mojom::AppCacheBackendPtr result;
RenderThread::Get()->GetConnector()->BindInterface(
mojom::kBrowserServiceName, mojo::MakeRequest(&result));
return result;
}());
backend_ = backend_ptr->get();
blink::mojom::AppCacheFrontendPtr frontend_ptr;
binding_.Bind(mojo::MakeRequest(&frontend_ptr, task_runner), task_runner);
backend_->RegisterHost(
mojo::MakeRequest(&backend_host_, std::move(task_runner)),
std::move(frontend_ptr), host_id_, render_frame_id);
}
WebApplicationCacheHostImpl::~WebApplicationCacheHostImpl() {
all_hosts()->Remove(host_id_);
}
void WebApplicationCacheHostImpl::CacheSelected(
blink::mojom::AppCacheInfoPtr info) {
cache_info_ = *info;
client_->DidChangeCacheAssociation();
}
void WebApplicationCacheHostImpl::EventRaised(
blink::mojom::AppCacheEventID event_id) {
DCHECK_NE(event_id,
blink::mojom::AppCacheEventID::
APPCACHE_PROGRESS_EVENT); // See OnProgressEventRaised.
DCHECK_NE(event_id,
blink::mojom::AppCacheEventID::
APPCACHE_ERROR_EVENT); // See OnErrorEventRaised.
// Emit logging output prior to calling out to script as we can get
// deleted within the script event handler.
const char kFormatString[] = "Application Cache %s event";
std::string message = base::StringPrintf(
kFormatString, kEventNames[static_cast<int>(event_id)]);
LogMessage(blink::mojom::ConsoleMessageLevel::kInfo, message);
switch (event_id) {
case blink::mojom::AppCacheEventID::APPCACHE_CHECKING_EVENT:
status_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_CHECKING;
break;
case blink::mojom::AppCacheEventID::APPCACHE_DOWNLOADING_EVENT:
status_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_DOWNLOADING;
break;
case blink::mojom::AppCacheEventID::APPCACHE_UPDATE_READY_EVENT:
status_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_UPDATE_READY;
break;
case blink::mojom::AppCacheEventID::APPCACHE_CACHED_EVENT:
case blink::mojom::AppCacheEventID::APPCACHE_NO_UPDATE_EVENT:
status_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_IDLE;
break;
case blink::mojom::AppCacheEventID::APPCACHE_OBSOLETE_EVENT:
status_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
break;
default:
NOTREACHED();
break;
}
client_->NotifyEventListener(event_id);
}
void WebApplicationCacheHostImpl::ProgressEventRaised(const GURL& url,
int num_total,
int num_complete) {
// Emit logging output prior to calling out to script as we can get
// deleted within the script event handler.
const char kFormatString[] = "Application Cache Progress event (%d of %d) %s";
std::string message = base::StringPrintf(kFormatString, num_complete,
num_total, url.spec().c_str());
LogMessage(blink::mojom::ConsoleMessageLevel::kInfo, message);
status_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_DOWNLOADING;
client_->NotifyProgressEventListener(url, num_total, num_complete);
}
void WebApplicationCacheHostImpl::ErrorEventRaised(
blink::mojom::AppCacheErrorDetailsPtr details) {
// Emit logging output prior to calling out to script as we can get
// deleted within the script event handler.
const char kFormatString[] = "Application Cache Error event: %s";
std::string full_message =
base::StringPrintf(kFormatString, details->message.c_str());
LogMessage(blink::mojom::ConsoleMessageLevel::kError, full_message);
status_ = cache_info_.is_complete
? blink::mojom::AppCacheStatus::APPCACHE_STATUS_IDLE
: blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED;
if (details->is_cross_origin) {
// Don't leak detailed information to script for cross-origin resources.
DCHECK_EQ(blink::mojom::AppCacheErrorReason::APPCACHE_RESOURCE_ERROR,
details->reason);
client_->NotifyErrorEventListener(details->reason, details->url, 0,
WebString());
} else {
client_->NotifyErrorEventListener(details->reason, details->url,
details->status,
WebString::FromUTF8(details->message));
}
}
void WebApplicationCacheHostImpl::WillStartMainResourceRequest(
const WebURL& url,
const WebString& method_webstring,
const WebApplicationCacheHost* spawning_host) {
original_main_resource_url_ = ClearUrlRef(url);
std::string method = method_webstring.Utf8();
is_get_method_ = (method == kHttpGETMethod);
DCHECK(method == base::ToUpperASCII(method));
const WebApplicationCacheHostImpl* spawning_host_impl =
static_cast<const WebApplicationCacheHostImpl*>(spawning_host);
if (spawning_host_impl && (spawning_host_impl != this) &&
(spawning_host_impl->status_ !=
blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED)) {
backend_host_->SetSpawningHostId(spawning_host_impl->host_id());
}
}
void WebApplicationCacheHostImpl::SelectCacheWithoutManifest() {
if (was_select_cache_called_)
return;
was_select_cache_called_ = true;
status_ =
(document_response_.AppCacheID() == blink::mojom::kAppCacheNoCacheId)
? blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED
: blink::mojom::AppCacheStatus::APPCACHE_STATUS_CHECKING;
is_new_master_entry_ = OLD_ENTRY;
backend_host_->SelectCache(document_url_, document_response_.AppCacheID(),
GURL());
}
bool WebApplicationCacheHostImpl::SelectCacheWithManifest(
const WebURL& manifest_url) {
if (was_select_cache_called_)
return true;
was_select_cache_called_ = true;
GURL manifest_gurl(ClearUrlRef(manifest_url));
// 6.9.6 The application cache selection algorithm
// Check for new 'master' entries.
if (document_response_.AppCacheID() == blink::mojom::kAppCacheNoCacheId) {
if (is_scheme_supported_ && is_get_method_ &&
(manifest_gurl.GetOrigin() == document_url_.GetOrigin())) {
status_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_CHECKING;
is_new_master_entry_ = NEW_ENTRY;
} else {
status_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED;
is_new_master_entry_ = OLD_ENTRY;
manifest_gurl = GURL();
}
backend_host_->SelectCache(document_url_, blink::mojom::kAppCacheNoCacheId,
manifest_gurl);
return true;
}
DCHECK_EQ(OLD_ENTRY, is_new_master_entry_);
// 6.9.6 The application cache selection algorithm
// Check for 'foreign' entries.
GURL document_manifest_gurl(document_response_.AppCacheManifestURL());
if (document_manifest_gurl != manifest_gurl) {
backend_host_->MarkAsForeignEntry(document_url_,
document_response_.AppCacheID());
status_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED;
return false; // the navigation will be restarted
}
status_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_CHECKING;
// Its a 'master' entry thats already in the cache.
backend_host_->SelectCache(document_url_, document_response_.AppCacheID(),
manifest_gurl);
return true;
}
void WebApplicationCacheHostImpl::DidReceiveResponseForMainResource(
const WebURLResponse& response) {
document_response_ = response;
document_url_ = ClearUrlRef(document_response_.CurrentRequestUrl());
if (document_url_ != original_main_resource_url_)
is_get_method_ = true; // A redirect was involved.
original_main_resource_url_ = GURL();
is_scheme_supported_ = IsSchemeSupportedForAppCache(document_url_);
if ((document_response_.AppCacheID() != blink::mojom::kAppCacheNoCacheId) ||
!is_scheme_supported_ || !is_get_method_)
is_new_master_entry_ = OLD_ENTRY;
}
blink::mojom::AppCacheStatus WebApplicationCacheHostImpl::GetStatus() {
return status_;
}
bool WebApplicationCacheHostImpl::StartUpdate() {
bool result = false;
backend_host_->StartUpdate(&result);
if (!result)
return false;
if (status_ == blink::mojom::AppCacheStatus::APPCACHE_STATUS_IDLE ||
status_ == blink::mojom::AppCacheStatus::APPCACHE_STATUS_UPDATE_READY) {
status_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_CHECKING;
} else {
status_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED;
backend_host_->GetStatus(&status_);
}
return true;
}
bool WebApplicationCacheHostImpl::SwapCache() {
bool result = false;
backend_host_->SwapCache(&result);
if (!result)
return false;
backend_host_->GetStatus(&status_);
return true;
}
void WebApplicationCacheHostImpl::GetAssociatedCacheInfo(
WebApplicationCacheHost::CacheInfo* info) {
info->manifest_url = cache_info_.manifest_url;
if (!cache_info_.is_complete)
return;
info->creation_time = cache_info_.creation_time.ToDoubleT();
info->update_time = cache_info_.last_update_time.ToDoubleT();
info->total_size = cache_info_.size;
}
int WebApplicationCacheHostImpl::GetHostID() const {
return host_id_;
}
void WebApplicationCacheHostImpl::GetResourceList(
WebVector<ResourceInfo>* resources) {
if (!cache_info_.is_complete)
return;
std::vector<blink::mojom::AppCacheResourceInfoPtr> boxed_infos;
backend_host_->GetResourceList(&boxed_infos);
std::vector<blink::mojom::AppCacheResourceInfo> resource_infos;
for (auto& b : boxed_infos) {
resource_infos.emplace_back(std::move(*b));
}
WebVector<ResourceInfo> web_resources(resource_infos.size());
for (size_t i = 0; i < resource_infos.size(); ++i) {
web_resources[i].size = resource_infos[i].size;
web_resources[i].is_master = resource_infos[i].is_master;
web_resources[i].is_explicit = resource_infos[i].is_explicit;
web_resources[i].is_manifest = resource_infos[i].is_manifest;
web_resources[i].is_foreign = resource_infos[i].is_foreign;
web_resources[i].is_fallback = resource_infos[i].is_fallback;
web_resources[i].url = resource_infos[i].url;
}
resources->Swap(web_resources);
}
void WebApplicationCacheHostImpl::SelectCacheForSharedWorker(
long long app_cache_id) {
backend_host_->SelectCacheForSharedWorker(app_cache_id);
}
} // namespace content