blob: 901d742fee50d24f48f1b5e04541017794d7d935 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// 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/fetch/global_fetch.h"
#include "base/feature_list.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_deferred_request_init.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_request_init.h"
#include "third_party/blink/renderer/core/execution_context/navigator_base.h"
#include "third_party/blink/renderer/core/fetch/fetch_later_result.h"
#include "third_party/blink/renderer/core/fetch/fetch_manager.h"
#include "third_party/blink/renderer/core/fetch/request.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/workers/worker_global_scope.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/supplementable.h"
namespace blink {
namespace {
void MeasureFetchProperties(ExecutionContext* execution_context,
FetchRequestData* data) {
// 'redirect' measurement
if (data->Redirect() == network::mojom::RedirectMode::kError)
UseCounter::Count(execution_context, WebFeature::kFetchRedirectError);
else if (data->Redirect() == network::mojom::RedirectMode::kManual)
UseCounter::Count(execution_context, WebFeature::kFetchRedirectManual);
// 'cache' measurement: https://crbug.com/959789
if (data->CacheMode() == mojom::FetchCacheMode::kBypassCache)
UseCounter::Count(execution_context, WebFeature::kFetchCacheReload);
}
template <typename T>
class GlobalFetchImpl final : public GarbageCollected<GlobalFetchImpl<T>>,
public GlobalFetch::ScopedFetcher,
public Supplement<T> {
public:
static const char kSupplementName[];
static ScopedFetcher* From(T& supplementable,
ExecutionContext* execution_context) {
GlobalFetchImpl* supplement =
Supplement<T>::template From<GlobalFetchImpl>(supplementable);
if (!supplement) {
supplement = MakeGarbageCollected<GlobalFetchImpl>(supplementable,
execution_context);
Supplement<T>::ProvideTo(supplementable, supplement);
}
return supplement;
}
explicit GlobalFetchImpl(T& supplementable,
ExecutionContext* execution_context)
: Supplement<T>(supplementable),
fetch_manager_(MakeGarbageCollected<FetchManager>(execution_context)),
// TODO(crbug.com/1356128): FetchLater is only supported in Document.
fetch_later_manager_(
base::FeatureList::IsEnabled(blink::features::kFetchLaterAPI) &&
execution_context->IsWindow()
? MakeGarbageCollected<FetchLaterManager>(execution_context)
: nullptr) {}
ScriptPromise<Response> Fetch(ScriptState* script_state,
const V8RequestInfo* input,
const RequestInit* init,
ExceptionState& exception_state) override {
fetch_count_ += 1;
ExecutionContext* execution_context = fetch_manager_->GetExecutionContext();
if (!script_state->ContextIsValid() || !execution_context) {
// TODO(yhirano): Should this be moved to bindings?
exception_state.ThrowTypeError("The global scope is shutting down.");
return ScriptPromise<Response>();
}
// "Let |r| be the associated request of the result of invoking the
// initial value of Request as constructor with |input| and |init| as
// arguments. If this throws an exception, reject |p| with it."
Request* r = Request::Create(script_state, input, init, exception_state);
if (exception_state.HadException())
return ScriptPromise<Response>();
probe::WillSendXMLHttpOrFetchNetworkRequest(execution_context, r->url());
FetchRequestData* request_data =
r->PassRequestData(script_state, exception_state);
MeasureFetchProperties(execution_context, request_data);
// Even if this was checked at the beginning of the function, it might
// have been set to nullptr during Request::Create.
if (!fetch_manager_->GetExecutionContext()) {
exception_state.ThrowTypeError("The global scope is shutting down.");
return ScriptPromise<Response>();
}
auto promise = fetch_manager_->Fetch(script_state, request_data,
r->signal(), exception_state);
if (exception_state.HadException())
return ScriptPromise<Response>();
return promise;
}
FetchLaterResult* FetchLater(ScriptState* script_state,
const V8RequestInfo* input,
const DeferredRequestInit* init,
ExceptionState& exception_state) override {
if (!base::FeatureList::IsEnabled(blink::features::kFetchLaterAPI) ||
!fetch_later_manager_) {
exception_state.ThrowTypeError(
"FetchLater is not supported in this scope.");
return nullptr;
}
ExecutionContext* ec = fetch_later_manager_->GetExecutionContext();
if (!script_state->ContextIsValid() || !ec) {
exception_state.ThrowTypeError("The global scope is shutting down.");
return nullptr;
}
// https://whatpr.org/fetch/1647/9ca4bda...9994c1d.html#dom-global-fetch-later
// Run the fetchLater(input, init) method steps:
// 1. If the user-agent has determined that deferred fetching is not
// allowed in this context, then throw a NotAllowedError.
// TODO(crbug.com/1465781): Define Permissions-Policy to allow disabling
// this feature. It should be enabled by default:
// https://github.com/WICG/pending-beacon/issues/77.
// 2. Let `r` be the result of invoking the initial value of Request as
// constructor with `input` and `init` as arguments. This may throw an
// exception.
Request* r =
Request::Create(script_state, input,
static_cast<const RequestInit*>(init), exception_state);
if (exception_state.HadException()) {
return nullptr;
}
probe::WillSendXMLHttpOrFetchNetworkRequest(ec, r->url());
FetchRequestData* request_data =
r->PassRequestData(script_state, exception_state);
MeasureFetchProperties(ec, request_data);
// 6. If init is given and init ["activateAfter"] exists, then set
// `activate_after` to init ["activateAfter"].
std::optional<DOMHighResTimeStamp> activate_after =
(init->hasActivateAfter() ? std::make_optional(init->activateAfter())
: std::nullopt);
auto* result = fetch_later_manager_->FetchLater(script_state, request_data,
r->signal(), activate_after,
exception_state);
if (exception_state.HadException()) {
return nullptr;
}
return result;
}
uint32_t FetchCount() const override { return fetch_count_; }
void Trace(Visitor* visitor) const override {
visitor->Trace(fetch_manager_);
visitor->Trace(fetch_later_manager_);
ScopedFetcher::Trace(visitor);
Supplement<T>::Trace(visitor);
}
private:
Member<FetchManager> fetch_manager_;
Member<FetchLaterManager> fetch_later_manager_;
uint32_t fetch_count_ = 0;
};
// static
template <typename T>
const char GlobalFetchImpl<T>::kSupplementName[] = "GlobalFetchImpl";
} // namespace
GlobalFetch::ScopedFetcher::~ScopedFetcher() {}
FetchLaterResult* GlobalFetch::ScopedFetcher::FetchLater(
ScriptState* script_state,
const V8RequestInfo* input,
const DeferredRequestInit* init,
ExceptionState& exception_state) {
NOTREACHED_NORETURN();
}
GlobalFetch::ScopedFetcher* GlobalFetch::ScopedFetcher::From(
LocalDOMWindow& window) {
return GlobalFetchImpl<LocalDOMWindow>::From(window,
window.GetExecutionContext());
}
GlobalFetch::ScopedFetcher* GlobalFetch::ScopedFetcher::From(
WorkerGlobalScope& worker) {
return GlobalFetchImpl<WorkerGlobalScope>::From(worker,
worker.GetExecutionContext());
}
GlobalFetch::ScopedFetcher* GlobalFetch::ScopedFetcher::From(
NavigatorBase& navigator) {
return GlobalFetchImpl<NavigatorBase>::From(navigator,
navigator.GetExecutionContext());
}
void GlobalFetch::ScopedFetcher::Trace(Visitor* visitor) const {}
ScriptPromise<Response> GlobalFetch::fetch(ScriptState* script_state,
LocalDOMWindow& window,
const V8RequestInfo* input,
const RequestInit* init,
ExceptionState& exception_state) {
UseCounter::Count(window.GetExecutionContext(), WebFeature::kFetch);
if (!window.GetFrame()) {
exception_state.ThrowTypeError("The global scope is shutting down.");
return ScriptPromise<Response>();
}
return ScopedFetcher::From(window)->Fetch(script_state, input, init,
exception_state);
}
ScriptPromise<Response> GlobalFetch::fetch(ScriptState* script_state,
WorkerGlobalScope& worker,
const V8RequestInfo* input,
const RequestInit* init,
ExceptionState& exception_state) {
UseCounter::Count(worker.GetExecutionContext(), WebFeature::kFetch);
return ScopedFetcher::From(worker)->Fetch(script_state, input, init,
exception_state);
}
FetchLaterResult* GlobalFetch::fetchLater(ScriptState* script_state,
LocalDOMWindow& window,
const V8RequestInfo* input,
const DeferredRequestInit* init,
ExceptionState& exception_state) {
UseCounter::Count(window.GetExecutionContext(), WebFeature::kFetchLater);
if (!window.GetFrame()) {
exception_state.ThrowTypeError("The global scope is shutting down.");
return nullptr;
}
return ScopedFetcher::From(window)->FetchLater(script_state, input, init,
exception_state);
}
} // namespace blink