blob: 551b8d5f573ce104594412fc9295b809e4ba5ccf [file] [log] [blame]
// Copyright 2012 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 "third_party/blink/renderer/core/loader/appcache/application_cache_host_for_frame.h"
#include "third_party/blink/public/common/loader/url_loader_factory_bundle.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/events/application_cache_error_event.h"
#include "third_party/blink/renderer/core/events/progress_event.h"
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/loader/appcache/application_cache.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/web_test_support.h"
namespace blink {
namespace {
const char kHttpGETMethod[] = "GET";
KURL ClearUrlRef(const KURL& input_url) {
KURL url(input_url);
if (!url.HasFragmentIdentifier())
return url;
url.RemoveFragmentIdentifier();
return url;
}
void RestartNavigation(LocalFrame* frame) {
Document* document = frame->GetDocument();
FrameLoadRequest request(document, ResourceRequest(document->Url()));
request.SetClientRedirectReason(ClientNavigationReason::kReload);
frame->Navigate(request, WebFrameLoadType::kReplaceCurrentItem);
}
} // namespace
ApplicationCacheHostForFrame::ApplicationCacheHostForFrame(
DocumentLoader* document_loader,
const BrowserInterfaceBrokerProxy& interface_broker_proxy,
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
const base::UnguessableToken& appcache_host_id)
: ApplicationCacheHost(interface_broker_proxy, std::move(task_runner)),
local_frame_(document_loader->GetFrame()),
document_loader_(document_loader) {
// PlzNavigate: The browser passes the ID to be used.
if (!appcache_host_id.is_empty())
SetHostID(appcache_host_id);
}
void ApplicationCacheHostForFrame::Detach() {
ApplicationCacheHost::Detach();
document_loader_ = nullptr;
SetApplicationCache(nullptr);
}
bool ApplicationCacheHostForFrame::Update() {
if (!backend_host_.is_bound())
return false;
bool result = false;
backend_host_->StartUpdate(&result);
if (!result)
return false;
if (status_ == mojom::blink::AppCacheStatus::APPCACHE_STATUS_IDLE ||
status_ == mojom::blink::AppCacheStatus::APPCACHE_STATUS_UPDATE_READY) {
status_ = mojom::blink::AppCacheStatus::APPCACHE_STATUS_CHECKING;
} else {
status_ = mojom::blink::AppCacheStatus::APPCACHE_STATUS_UNCACHED;
backend_host_->GetStatus(&status_);
}
return true;
}
bool ApplicationCacheHostForFrame::SwapCache() {
if (!backend_host_.is_bound())
return false;
bool success = false;
backend_host_->SwapCache(&success);
if (!success)
return false;
backend_host_->GetStatus(&status_);
probe::UpdateApplicationCacheStatus(document_loader_->GetFrame());
return true;
}
void ApplicationCacheHostForFrame::SetApplicationCache(
ApplicationCache* dom_application_cache) {
DCHECK(!dom_application_cache_ || !dom_application_cache);
dom_application_cache_ = dom_application_cache;
}
void ApplicationCacheHostForFrame::StopDeferringEvents() {
for (unsigned i = 0; i < deferred_events_.size(); ++i) {
const DeferredEvent& deferred = deferred_events_[i];
DispatchDOMEvent(deferred.event_id, deferred.progress_total,
deferred.progress_done, deferred.error_reason,
deferred.error_url, deferred.error_status,
deferred.error_message);
}
deferred_events_.clear();
defers_events_ = false;
}
void ApplicationCacheHostForFrame::LogMessage(
mojom::blink::ConsoleMessageLevel log_level,
const String& message) {
if (WebTestSupport::IsRunningWebTest())
return;
if (!local_frame_ || !local_frame_->IsMainFrame())
return;
Frame* main_frame = local_frame_->GetPage()->MainFrame();
if (!main_frame->IsLocalFrame())
return;
// TODO(michaeln): Make app cache host per-frame and correctly report to the
// involved frame.
auto* local_frame = DynamicTo<LocalFrame>(main_frame);
local_frame->GetDocument()->AddConsoleMessage(
MakeGarbageCollected<ConsoleMessage>(mojom::ConsoleMessageSource::kOther,
log_level, message));
}
void ApplicationCacheHostForFrame::SetSubresourceFactory(
mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>
url_loader_factory) {
auto pending_factories = std::make_unique<PendingURLLoaderFactoryBundle>();
// |PassPipe()| invalidates all state, so capture |version()| first.
uint32_t version = url_loader_factory.version();
pending_factories->pending_appcache_factory() =
mojo::PendingRemote<network::mojom::URLLoaderFactory>(
url_loader_factory.PassPipe(), version);
local_frame_->Client()->UpdateSubresourceFactory(
std::move(pending_factories));
}
void ApplicationCacheHostForFrame::WillStartLoadingMainResource(
DocumentLoader* loader,
const KURL& url,
const String& method) {
if (!IsApplicationCacheEnabled())
return;
if (GetHostID().is_empty()) {
// If we did not get host id from the browser, we postpone creation of a new
// one until this point, where we actually load non-empty document.
SetHostID(base::UnguessableToken::Create());
}
// We defer binding to backend to avoid unnecessary binding around creating
// empty documents. At this point, we're initiating a main resource load for
// the document, so its for real.
if (!BindBackend())
return;
original_main_resource_url_ = ClearUrlRef(url);
is_get_method_ = (method == kHttpGETMethod);
DCHECK(method == method.UpperASCII());
const ApplicationCacheHost* spawning_host = nullptr;
DCHECK(loader->GetFrame());
LocalFrame* frame = loader->GetFrame();
Frame* spawning_frame = frame->Tree().Parent();
if (!spawning_frame || !IsA<LocalFrame>(spawning_frame))
spawning_frame = frame->Loader().Opener();
if (!spawning_frame || !IsA<LocalFrame>(spawning_frame))
spawning_frame = frame;
if (DocumentLoader* spawning_doc_loader =
To<LocalFrame>(spawning_frame)->Loader().GetDocumentLoader()) {
spawning_host = spawning_doc_loader->GetApplicationCacheHost();
}
if (spawning_host && (spawning_host != this) &&
(spawning_host->GetStatus() !=
mojom::blink::AppCacheStatus::APPCACHE_STATUS_UNCACHED)) {
backend_host_->SetSpawningHostId(spawning_host->GetHostID());
}
}
void ApplicationCacheHostForFrame::SelectCacheWithoutManifest() {
if (!backend_host_.is_bound())
return;
if (was_select_cache_called_)
return;
was_select_cache_called_ = true;
status_ =
(document_response_.AppCacheID() == mojom::blink::kAppCacheNoCacheId)
? mojom::blink::AppCacheStatus::APPCACHE_STATUS_UNCACHED
: mojom::blink::AppCacheStatus::APPCACHE_STATUS_CHECKING;
is_new_master_entry_ = OLD_ENTRY;
backend_host_->SelectCache(document_url_, document_response_.AppCacheID(),
KURL());
}
void ApplicationCacheHostForFrame::SelectCacheWithManifest(
const KURL& manifest_url) {
LocalFrame* frame = document_loader_->GetFrame();
Document* document = frame->GetDocument();
if (document->IsSandboxed(mojom::blink::WebSandboxFlags::kOrigin)) {
// Prevent sandboxes from establishing application caches.
SelectCacheWithoutManifest();
return;
}
CHECK(document->IsSecureContext());
Deprecation::CountDeprecation(
document, WebFeature::kApplicationCacheManifestSelectSecureOrigin);
if (!backend_host_.is_bound())
return;
if (was_select_cache_called_)
return;
was_select_cache_called_ = true;
KURL manifest_kurl(ClearUrlRef(manifest_url));
// 6.9.6 The application cache selection algorithm
// Check for new 'master' entries.
if (document_response_.AppCacheID() == mojom::blink::kAppCacheNoCacheId) {
if (is_scheme_supported_ && is_get_method_ &&
SecurityOrigin::AreSameOrigin(manifest_kurl, document_url_)) {
status_ = mojom::blink::AppCacheStatus::APPCACHE_STATUS_CHECKING;
is_new_master_entry_ = NEW_ENTRY;
} else {
status_ = mojom::blink::AppCacheStatus::APPCACHE_STATUS_UNCACHED;
is_new_master_entry_ = OLD_ENTRY;
manifest_kurl = KURL();
}
backend_host_->SelectCache(document_url_, mojom::blink::kAppCacheNoCacheId,
manifest_kurl);
return;
}
DCHECK_EQ(OLD_ENTRY, is_new_master_entry_);
// 6.9.6 The application cache selection algorithm
// Check for 'foreign' entries.
KURL document_manifest_kurl(document_response_.AppCacheManifestURL());
if (document_manifest_kurl != manifest_kurl) {
backend_host_->MarkAsForeignEntry(document_url_,
document_response_.AppCacheID());
status_ = mojom::blink::AppCacheStatus::APPCACHE_STATUS_UNCACHED;
// It's a foreign entry, restart the current navigation from the top of the
// navigation algorithm. The navigation will not result in the same resource
// being loaded, because "foreign" entries are never picked during
// navigation. see ApplicationCacheGroup::selectCache()
RestartNavigation(local_frame_); // the navigation will be restarted
return;
}
status_ = mojom::blink::AppCacheStatus::APPCACHE_STATUS_CHECKING;
// It's a 'master' entry that's already in the cache.
backend_host_->SelectCache(document_url_, document_response_.AppCacheID(),
manifest_kurl);
}
void ApplicationCacheHostForFrame::DidReceiveResponseForMainResource(
const ResourceResponse& response) {
if (!backend_host_.is_bound())
return;
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_ = KURL();
is_scheme_supported_ =
Platform::Current()->IsURLSupportedForAppCache(document_url_);
if ((document_response_.AppCacheID() != mojom::blink::kAppCacheNoCacheId) ||
!is_scheme_supported_ || !is_get_method_)
is_new_master_entry_ = OLD_ENTRY;
}
void ApplicationCacheHostForFrame::Trace(Visitor* visitor) {
visitor->Trace(dom_application_cache_);
visitor->Trace(local_frame_);
visitor->Trace(document_loader_);
ApplicationCacheHost::Trace(visitor);
}
void ApplicationCacheHostForFrame::NotifyApplicationCache(
mojom::AppCacheEventID id,
int progress_total,
int progress_done,
mojom::AppCacheErrorReason error_reason,
const String& error_url,
int error_status,
const String& error_message) {
if (id != mojom::AppCacheEventID::APPCACHE_PROGRESS_EVENT) {
probe::UpdateApplicationCacheStatus(document_loader_->GetFrame());
}
if (defers_events_) {
// Event dispatching is deferred until document.onload has fired.
deferred_events_.push_back(DeferredEvent(id, progress_total, progress_done,
error_reason, error_url,
error_status, error_message));
return;
}
DispatchDOMEvent(id, progress_total, progress_done, error_reason, error_url,
error_status, error_message);
}
void ApplicationCacheHostForFrame::DispatchDOMEvent(
mojom::AppCacheEventID id,
int progress_total,
int progress_done,
mojom::AppCacheErrorReason error_reason,
const String& error_url,
int error_status,
const String& error_message) {
// Don't dispatch an event if the window is detached.
if (!dom_application_cache_ || !dom_application_cache_->DomWindow())
return;
const AtomicString& event_type = ApplicationCache::ToEventType(id);
if (event_type.IsEmpty())
return;
Event* event = nullptr;
if (id == mojom::AppCacheEventID::APPCACHE_PROGRESS_EVENT) {
event =
ProgressEvent::Create(event_type, true, progress_done, progress_total);
} else if (id == mojom::AppCacheEventID::APPCACHE_ERROR_EVENT) {
event = MakeGarbageCollected<ApplicationCacheErrorEvent>(
error_reason, error_url, error_status, error_message);
} else {
event = Event::Create(event_type);
}
dom_application_cache_->DispatchEvent(*event);
}
bool ApplicationCacheHostForFrame::IsApplicationCacheEnabled() {
DCHECK(document_loader_->GetFrame());
return document_loader_->GetFrame()->GetSettings() &&
document_loader_->GetFrame()
->GetSettings()
->GetOfflineWebApplicationCacheEnabled();
}
} // namespace blink