1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
|
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/renderer_host/indexed_db_client_state_checker_factory.h"
#include <memory>
#include "components/services/storage/privileged/cpp/bucket_client_info.h"
#include "components/services/storage/privileged/mojom/indexed_db_client_state_checker.mojom-shared.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/disallow_activation_reason.h"
#include "content/public/browser/document_user_data.h"
#include "content/public/browser/render_frame_host.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"
namespace content {
namespace {
using IndexedDBDisallowActivationReason =
storage::mojom::DisallowInactiveClientReason;
DisallowActivationReasonId ConvertToDisallowActivationReasonId(
IndexedDBDisallowActivationReason reason) {
switch (reason) {
case IndexedDBDisallowActivationReason::kVersionChangeEvent:
return DisallowActivationReasonId::kIndexedDBEvent;
case IndexedDBDisallowActivationReason::kTransactionIsAcquiringLocks:
return DisallowActivationReasonId::kIndexedDBTransactionIsAcquiringLocks;
case IndexedDBDisallowActivationReason::
kTransactionIsStartingWhileBlockingOthers:
return DisallowActivationReasonId::
kIndexedDBTransactionIsStartingWhileBlockingOthers;
case IndexedDBDisallowActivationReason::
kTransactionIsOngoingAndBlockingOthers:
return DisallowActivationReasonId::
kIndexedDBTransactionIsOngoingAndBlockingOthers;
}
}
// The class will only provide the default result and the client will be
// considered active. It should be used when the client doesn't have an
// associated document, as is the case for shared worker or service worker.
class NoDocumentIndexedDBClientStateChecker
: public storage::mojom::IndexedDBClientStateChecker {
public:
NoDocumentIndexedDBClientStateChecker() = default;
~NoDocumentIndexedDBClientStateChecker() override = default;
NoDocumentIndexedDBClientStateChecker(
const NoDocumentIndexedDBClientStateChecker&) = delete;
NoDocumentIndexedDBClientStateChecker& operator=(
const NoDocumentIndexedDBClientStateChecker&) = delete;
// storage::mojom::IndexedDBClientStateChecker overrides:
// Non-document clients are always active, since the inactive state such as
// back/forward cache is not applicable to them.
void DisallowInactiveClient(
storage::mojom::DisallowInactiveClientReason reason,
mojo::PendingReceiver<storage::mojom::IndexedDBClientKeepActive>
keep_active,
DisallowInactiveClientCallback callback) override {
std::move(callback).Run(/*was_active=*/true);
}
void MakeClone(
mojo::PendingReceiver<storage::mojom::IndexedDBClientStateChecker>
receiver) override {
receivers_.Add(this, std::move(receiver));
}
private:
mojo::ReceiverSet<storage::mojom::IndexedDBClientStateChecker> receivers_;
};
// This class should be used when the client has an associated document. The
// client checks are performed based on the document. This class extends
// `DocumentUserData` because a document has one client per IndexedDB connection
// to a database.
class DocumentIndexedDBClientStateChecker final
: public DocumentUserData<DocumentIndexedDBClientStateChecker>,
public storage::mojom::IndexedDBClientStateChecker,
public storage::mojom::IndexedDBClientKeepActive {
public:
~DocumentIndexedDBClientStateChecker() final = default;
void Bind(mojo::PendingReceiver<storage::mojom::IndexedDBClientStateChecker>
receiver) {
receivers_.Add(this, std::move(receiver));
}
bool CheckIfClientWasActive(
storage::mojom::DisallowInactiveClientReason reason) {
bool was_active = false;
if (render_frame_host().GetLifecycleState() ==
RenderFrameHost::LifecycleState::kPrerendering) {
// Page under prerendering is able to continue the JS execution so it
// won't block the IndexedDB events. It shouldn't be deemed inactive for
// the IndexedDB service.
was_active = true;
} else {
// Call `IsInactiveAndDisallowActivation` to obtain the client state, this
// also brings side effect like evicting the page if it's in back/forward
// cache.
was_active = !render_frame_host().IsInactiveAndDisallowActivation(
ConvertToDisallowActivationReasonId(reason));
}
return was_active;
}
// storage::mojom::IndexedDBClientStateChecker overrides:
void DisallowInactiveClient(
storage::mojom::DisallowInactiveClientReason reason,
mojo::PendingReceiver<storage::mojom::IndexedDBClientKeepActive>
keep_active,
DisallowInactiveClientCallback callback) override {
// This client is currently blocking another client, for example because it
// has a transaction that holds locks needed by the another client or
// because it has a connection that prevents a version change in another
// client. There are 2 situations that could prevent this client from
// continuing its work and unblocking the other client: freezing and
// back-forward cache. They are handled differently.
//
// In both cases, if the document is neither frozen nor in the back-forward
// cache, there is nothing to do. If either situations happen in the future,
// `DisallowInactiveClient()` will be called again for it and then take
// action, by either unfreezing or evicting the document from the
// back-forward cache.
//
// In the case of a frozen document, we register a
// HoldingBlockingIDBLockHandle that will unfreeze and prevent the document
// from being frozen for the lifetime of the handle.
//
// In the case the document is in the back-forward cache, the call to
// `CheckIfClientWasActive()` below will evict it.
//
// In addition, if `reason` is kVersionChangeEvent, then we register both
// a HoldingBlockingIDBLockHandle and a
// BackForwardCacheDisablingFeatureHandle to prevent the document from going
// into an inactive state until the IndexedDB connection is successfully
// closed and the context is automatically destroyed.
bool is_version_change_event =
reason ==
storage::mojom::DisallowInactiveClientReason::kVersionChangeEvent;
CHECK(keep_active.is_valid());
bool was_active = CheckIfClientWasActive(reason);
if (!was_active) {
std::move(callback).Run(was_active);
return;
}
RenderFrameHostImpl* render_frame_host_impl =
RenderFrameHostImpl::From(&render_frame_host());
// If the client was in the BFCache, it should have been evicted with the
// check above. Note that until crbug.com/40691610 is fixed, a
// RenderFrameHost that is frozen for any other reasons than BFCache is
// considered active.
CHECK_NE(render_frame_host_impl->GetLifecycleState(),
RenderFrameHost::LifecycleState::kInBackForwardCache);
RenderFrameHostImpl::HoldingBlockingIDBLockHandle
holding_blocking_idb_lock_handle;
if (render_frame_host_impl->IsFrozen() || is_version_change_event) {
holding_blocking_idb_lock_handle =
render_frame_host_impl->RegisterHoldingBlockingIDBLockHandle();
}
RenderFrameHostImpl::BackForwardCacheDisablingFeatureHandle
bfcache_feature_handle;
if (is_version_change_event) {
bfcache_feature_handle =
render_frame_host_impl
->RegisterBackForwardCacheDisablingNonStickyFeature(
blink::scheduler::WebSchedulerTrackedFeature::
kIndexedDBEvent);
}
if (bfcache_feature_handle.IsValid() ||
holding_blocking_idb_lock_handle.IsValid()) {
KeepActiveReceiverContext context(
std::move(bfcache_feature_handle),
std::move(holding_blocking_idb_lock_handle));
keep_active_receivers_.Add(this, std::move(keep_active),
std::move(context));
}
std::move(callback).Run(was_active);
}
void MakeClone(
mojo::PendingReceiver<storage::mojom::IndexedDBClientStateChecker>
receiver) override {
Bind(std::move(receiver));
}
private:
// Keep the association between the receiver and the feature handles it
// registered.
class KeepActiveReceiverContext {
public:
KeepActiveReceiverContext() = default;
KeepActiveReceiverContext(
RenderFrameHostImpl::BackForwardCacheDisablingFeatureHandle
bfcache_feature_handle,
RenderFrameHostImpl::HoldingBlockingIDBLockHandle
holding_blocking_idb_lock_handle)
: bfcache_feature_handle_(std::move(bfcache_feature_handle)),
holding_blocking_idb_lock_handle_(
std::move(holding_blocking_idb_lock_handle)) {}
KeepActiveReceiverContext(KeepActiveReceiverContext&& context) noexcept
: bfcache_feature_handle_(std::move(context.bfcache_feature_handle_)),
holding_blocking_idb_lock_handle_(
std::move(context.holding_blocking_idb_lock_handle_)) {}
~KeepActiveReceiverContext() = default;
private:
RenderFrameHostImpl::BackForwardCacheDisablingFeatureHandle
bfcache_feature_handle_;
RenderFrameHostImpl::HoldingBlockingIDBLockHandle
holding_blocking_idb_lock_handle_;
};
explicit DocumentIndexedDBClientStateChecker(RenderFrameHost* rfh)
: DocumentUserData(rfh) {}
friend DocumentUserData;
DOCUMENT_USER_DATA_KEY_DECL();
mojo::ReceiverSet<storage::mojom::IndexedDBClientStateChecker> receivers_;
mojo::ReceiverSet<storage::mojom::IndexedDBClientKeepActive,
KeepActiveReceiverContext>
keep_active_receivers_;
};
} // namespace
DOCUMENT_USER_DATA_KEY_IMPL(DocumentIndexedDBClientStateChecker);
// static
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
IndexedDBClientStateCheckerFactory::InitializePendingRemote(
const storage::BucketClientInfo& client_info) {
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker>
client_state_checker_remote;
if (client_info.document_token) {
RenderFrameHost* rfh = RenderFrameHostImpl::FromDocumentToken(
client_info.process_id, client_info.document_token.value());
CHECK(rfh);
DocumentIndexedDBClientStateChecker::GetOrCreateForCurrentDocument(rfh)
->Bind(client_state_checker_remote.InitWithNewPipeAndPassReceiver());
} else {
if (client_info.context_token.Is<blink::SharedWorkerToken>() ||
client_info.context_token.Is<blink::ServiceWorkerToken>()) {
// Use a default checker instance for valid clients that have no
// associated document. See comments on
// `NoDocumentIndexedDBClientStateChecker`.
mojo::MakeSelfOwnedReceiver(
std::make_unique<NoDocumentIndexedDBClientStateChecker>(),
client_state_checker_remote.InitWithNewPipeAndPassReceiver());
} else if (client_info.context_token.Is<blink::DedicatedWorkerToken>()) {
// The rare case of a dedicated worker not having an associated document
// can occur when the worker has outlived the parent RFH. See code comment
// on `DedicatedWorkerHost`. We will not bind the remote in this case.
} else {
// No other client type is expected.
NOTREACHED();
}
}
return client_state_checker_remote;
}
// static
storage::mojom::IndexedDBClientStateChecker*
IndexedDBClientStateCheckerFactory::
GetOrCreateIndexedDBClientStateCheckerForTesting(
const GlobalRenderFrameHostId& rfh_id) {
CHECK_NE(rfh_id.frame_routing_id, MSG_ROUTING_NONE)
<< "RFH id should be valid when testing";
return DocumentIndexedDBClientStateChecker::GetOrCreateForCurrentDocument(
RenderFrameHost::FromID(rfh_id));
}
} // namespace content
|