blob: b6a4b8822c1c28585748bc328a22d431478b36a2 [file] [log] [blame]
// Copyright 2020 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/html/link_web_bundle.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
#include "third_party/blink/public/mojom/web_feature/web_feature.mojom-blink.h"
#include "third_party/blink/renderer/core/html/html_link_element.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/loader/threadable_loader.h"
#include "third_party/blink/renderer/core/loader/threadable_loader_client.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/loader/cors/cors.h"
#include "third_party/blink/renderer/platform/loader/fetch/bytes_consumer.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/fetch/url_loader/web_bundle_subresource_loader.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
namespace blink {
// WebBundleLoader is responsible for loading a WebBundle resource.
class WebBundleLoader : public GarbageCollected<WebBundleLoader>,
public ThreadableLoaderClient {
public:
WebBundleLoader(LinkWebBundle& link_web_bundle,
ExecutionContext& execution_context,
const KURL& url)
: link_web_bundle_(&link_web_bundle),
pending_factory_receiver_(loader_factory_.BindNewPipeAndPassReceiver()),
url_(url),
security_origin_(SecurityOrigin::Create(url)) {
ResourceRequest request(url);
request.SetUseStreamOnResponse(true);
// TODO(crbug.com/1082020): Revisit these once the fetch and process the
// linked resource algorithm [1] for <link rel=webbundle> is defined.
// [1]
// https://html.spec.whatwg.org/multipage/semantics.html#fetch-and-process-the-linked-resource
request.SetRequestContext(mojom::blink::RequestContextType::SUBRESOURCE);
request.SetMode(network::mojom::blink::RequestMode::kCors);
request.SetCredentialsMode(network::mojom::blink::CredentialsMode::kOmit);
ResourceLoaderOptions resource_loader_options(
execution_context.GetCurrentWorld());
resource_loader_options.data_buffering_policy = kDoNotBufferData;
loader_ = MakeGarbageCollected<ThreadableLoader>(execution_context, this,
resource_loader_options);
loader_->Start(std::move(request));
}
void Trace(Visitor* visitor) const override {
visitor->Trace(link_web_bundle_);
visitor->Trace(loader_);
}
bool HasLoaded() const { return !failed_; }
mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>
GetURLLoaderFactory() {
mojo::PendingRemote<network::mojom::blink::URLLoaderFactory> factory_clone;
loader_factory_->Clone(factory_clone.InitWithNewPipeAndPassReceiver());
return factory_clone;
}
// ThreadableLoaderClient
void DidReceiveResponse(uint64_t, const ResourceResponse& response) override {
if (!cors::IsOkStatus(response.HttpStatusCode()))
failed_ = true;
// TODO(crbug.com/1082020): Check response headers, as spec'ed in
// https://wicg.github.io/webpackage/draft-yasskin-wpack-bundled-exchanges.html#name-serving-constraints.
}
void DidStartLoadingResponseBody(BytesConsumer& consumer) override {
DCHECK(pending_factory_receiver_);
CreateWebBundleSubresourceLoaderFactory(
std::move(pending_factory_receiver_), consumer.DrainAsDataPipe(),
ConvertToBaseRepeatingCallback(
CrossThreadBindRepeating(&WebBundleLoader::OnWebBundleError,
WrapCrossThreadWeakPersistent(this))));
}
void DidFinishLoading(uint64_t) override { link_web_bundle_->NotifyLoaded(); }
void DidFail(const ResourceError&) override { DidFailInternal(); }
void DidFailRedirectCheck() override { DidFailInternal(); }
const KURL& url() const { return url_; }
scoped_refptr<SecurityOrigin> GetSecurityOrigin() const {
return security_origin_;
}
private:
void DidFailInternal() {
if (pending_factory_receiver_) {
// If we haven't create a WebBundleSubresourceLoaderFactory, create it
// with an empty bundle body so that requests to
// |pending_factory_receiver_| are processed (and fail).
CreateWebBundleSubresourceLoaderFactory(
std::move(pending_factory_receiver_),
mojo::ScopedDataPipeConsumerHandle(), base::DoNothing());
}
failed_ = true;
link_web_bundle_->NotifyLoaded();
}
void OnWebBundleError(WebBundleErrorType type, const String& message) {
// TODO(crbug.com/1082020): Dispatch "error" event on metadata parse error.
// Simply setting |failed_| here does not work because DidFinishLoading()
// may already be called.
link_web_bundle_->OnWebBundleError(url_.ElidedString() + ": " + message);
}
Member<LinkWebBundle> link_web_bundle_;
Member<ThreadableLoader> loader_;
mojo::Remote<network::mojom::blink::URLLoaderFactory> loader_factory_;
mojo::PendingReceiver<network::mojom::blink::URLLoaderFactory>
pending_factory_receiver_;
bool failed_ = false;
KURL url_;
scoped_refptr<SecurityOrigin> security_origin_;
};
LinkWebBundle::LinkWebBundle(HTMLLinkElement* owner) : LinkResource(owner) {
UseCounter::Count(owner_->GetDocument().GetExecutionContext(),
WebFeature::kSubresourceWebBundles);
}
LinkWebBundle::~LinkWebBundle() = default;
void LinkWebBundle::Trace(Visitor* visitor) const {
visitor->Trace(bundle_loader_);
LinkResource::Trace(visitor);
SubresourceWebBundle::Trace(visitor);
}
void LinkWebBundle::NotifyLoaded() {
if (owner_)
owner_->ScheduleEvent();
}
void LinkWebBundle::OnWebBundleError(const String& message) const {
if (!owner_)
return;
ExecutionContext* context = owner_->GetDocument().GetExecutionContext();
if (!context)
return;
context->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther,
mojom::blink::ConsoleMessageLevel::kWarning, message));
}
void LinkWebBundle::Process() {
if (!owner_ || !owner_->GetDocument().GetFrame())
return;
if (!owner_->ShouldLoadLink())
return;
ResourceFetcher* resource_fetcher = owner_->GetDocument().Fetcher();
if (!resource_fetcher)
return;
if (!bundle_loader_ || bundle_loader_->url() != owner_->Href()) {
bundle_loader_ = MakeGarbageCollected<WebBundleLoader>(
*this, *owner_->GetDocument().GetExecutionContext(), owner_->Href());
}
resource_fetcher->AddSubresourceWebBundle(*this);
}
LinkResource::LinkResourceType LinkWebBundle::GetType() const {
return kOther;
}
bool LinkWebBundle::HasLoaded() const {
return bundle_loader_ && bundle_loader_->HasLoaded();
}
void LinkWebBundle::OwnerRemoved() {
if (!owner_)
return;
ResourceFetcher* resource_fetcher = owner_->GetDocument().Fetcher();
if (!resource_fetcher)
return;
resource_fetcher->RemoveSubresourceWebBundle(*this);
bundle_loader_ = nullptr;
}
bool LinkWebBundle::CanHandleRequest(const KURL& url) const {
if (!owner_ || !owner_->ValidResourceUrls().Contains(url))
return false;
DCHECK(bundle_loader_);
if (!bundle_loader_->GetSecurityOrigin()->IsSameOriginWith(
SecurityOrigin::Create(url).get())) {
OnWebBundleError(url.ElidedString() + " cannot be loaded from WebBundle " +
bundle_loader_->url().ElidedString() +
": bundled resource must be same origin with the bundle.");
return false;
}
return true;
}
mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>
LinkWebBundle::GetURLLoaderFactory() {
DCHECK(bundle_loader_);
return bundle_loader_->GetURLLoaderFactory();
}
String LinkWebBundle::GetCacheIdentifier() const {
DCHECK(bundle_loader_);
return bundle_loader_->url().GetString();
}
// static
KURL LinkWebBundle::ParseResourceUrl(const AtomicString& str) {
// The implementation is almost copy and paste from ParseExchangeURL() defined
// in services/data_decoder/web_bundle_parser.cc, replacing GURL with KURL.
// TODO(hayato): Consider to support a relative URL.
KURL url(str);
if (!url.IsValid())
return KURL();
// Exchange URL must not have a fragment or credentials.
if (url.HasFragmentIdentifier() || !url.User().IsEmpty() ||
!url.Pass().IsEmpty())
return KURL();
// For now, we allow only http: and https: schemes in Web Bundle URLs.
// TODO(crbug.com/966753): Revisit this once
// https://github.com/WICG/webpackage/issues/468 is resolved.
if (!url.ProtocolIsInHTTPFamily())
return KURL();
return url;
}
} // namespace blink