blob: 8821dcb1191b1b594f2bfac90367374424604e36 [file] [log] [blame]
// Copyright 2017 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/modules/locks/lock.h"
#include "base/task/single_thread_task_runner.h"
#include "third_party/blink/renderer/bindings/core/v8/script_function.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/modules/locks/lock_manager.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
namespace {
const char* kLockModeNameExclusive = "exclusive";
const char* kLockModeNameShared = "shared";
} // namespace
class Lock::ThenFunction final : public ScriptFunction::Callable {
public:
enum ResolveType {
kFulfilled,
kRejected,
};
ThenFunction(Lock* lock, ResolveType type)
: lock_(lock), resolve_type_(type) {}
void Trace(Visitor* visitor) const override {
visitor->Trace(lock_);
ScriptFunction::Callable::Trace(visitor);
}
ScriptValue Call(ScriptState*, ScriptValue value) override {
DCHECK(lock_);
DCHECK(resolve_type_ == kFulfilled || resolve_type_ == kRejected);
lock_->ReleaseIfHeld();
if (resolve_type_ == kFulfilled) {
lock_->resolver_->Resolve(value);
lock_ = nullptr;
return value;
} else {
lock_->resolver_->Reject(value);
lock_ = nullptr;
return ScriptValue();
}
}
private:
Member<Lock> lock_;
ResolveType resolve_type_;
};
Lock::Lock(ScriptState* script_state,
const String& name,
mojom::blink::LockMode mode,
mojo::PendingAssociatedRemote<mojom::blink::LockHandle> handle,
mojo::PendingRemote<mojom::blink::ObservedFeature> lock_lifetime,
LockManager* manager)
: ExecutionContextLifecycleObserver(ExecutionContext::From(script_state)),
name_(name),
mode_(mode),
handle_(ExecutionContext::From(script_state)),
lock_lifetime_(ExecutionContext::From(script_state)),
manager_(manager) {
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
ExecutionContext::From(script_state)->GetTaskRunner(TaskType::kWebLocks);
handle_.Bind(std::move(handle), task_runner);
lock_lifetime_.Bind(std::move(lock_lifetime), task_runner);
handle_.set_disconnect_handler(
WTF::BindOnce(&Lock::OnConnectionError, WrapWeakPersistent(this)));
}
Lock::~Lock() = default;
String Lock::mode() const {
return ModeToString(mode_);
}
void Lock::HoldUntil(ScriptPromise promise, ScriptPromiseResolver* resolver) {
DCHECK(!resolver_);
// Note that it is possible for the ExecutionContext that this Lock lives in
// to have already been destroyed by the time this method is called. In that
// case `handle_` will have been reset, and the lock would have already been
// released. This is harmless, as nothing in this class uses `handle_` without
// first making sure it is still bound.
ScriptState* script_state = resolver->GetScriptState();
resolver_ = resolver;
promise.Then(MakeGarbageCollected<ScriptFunction>(
script_state, MakeGarbageCollected<ThenFunction>(
this, ThenFunction::kFulfilled)),
MakeGarbageCollected<ScriptFunction>(
script_state, MakeGarbageCollected<ThenFunction>(
this, ThenFunction::kRejected)));
}
// static
mojom::blink::LockMode Lock::StringToMode(const String& string) {
if (string == kLockModeNameShared)
return mojom::blink::LockMode::SHARED;
if (string == kLockModeNameExclusive)
return mojom::blink::LockMode::EXCLUSIVE;
NOTREACHED();
return mojom::blink::LockMode::SHARED;
}
// static
String Lock::ModeToString(mojom::blink::LockMode mode) {
switch (mode) {
case mojom::blink::LockMode::SHARED:
return kLockModeNameShared;
case mojom::blink::LockMode::EXCLUSIVE:
return kLockModeNameExclusive;
}
NOTREACHED();
return g_empty_string;
}
void Lock::ContextDestroyed() {
// This is kind of redundant, as `handle_` will reset itself as well when the
// context is destroyed, thereby releasing the lock. Explicitly releasing here
// as well doesn't hurt though.
ReleaseIfHeld();
}
void Lock::Trace(Visitor* visitor) const {
ExecutionContextLifecycleObserver::Trace(visitor);
ScriptWrappable::Trace(visitor);
visitor->Trace(resolver_);
visitor->Trace(handle_);
visitor->Trace(lock_lifetime_);
visitor->Trace(manager_);
}
void Lock::ReleaseIfHeld() {
if (handle_.is_bound()) {
// Drop the mojo pipe; this releases the lock on the back end.
handle_.reset();
lock_lifetime_.reset();
// Let the lock manager know that this instance can be collected.
manager_->OnLockReleased(this);
}
}
void Lock::OnConnectionError() {
DCHECK(resolver_);
ReleaseIfHeld();
ScriptState* const script_state = resolver_->GetScriptState();
if (!IsInParallelAlgorithmRunnable(resolver_->GetExecutionContext(),
script_state)) {
return;
}
ScriptState::Scope script_state_scope(script_state);
resolver_->Reject(V8ThrowDOMException::CreateOrDie(
script_state->GetIsolate(), DOMExceptionCode::kAbortError,
"Lock broken by another request with the 'steal' option."));
}
} // namespace blink