blob: 63ab6200aa9b3c00f33b6dbc248312a58ff25ee9 [file] [log] [blame]
// Copyright 2021 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/eyedropper/eye_dropper.h"
#include "third_party/blink/public/platform/browser_interface_broker_proxy.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_throw_dom_exception.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_color_selection_options.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_color_selection_result.h"
#include "third_party/blink/renderer/core/dom/abort_signal.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/dom/scoped_abort_state.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/platform/bindings/exception_code.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/graphics/color.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "ui/base/ui_base_features.h"
namespace blink {
class EyeDropper::OpenAbortAlgorithm final : public AbortSignal::Algorithm {
public:
OpenAbortAlgorithm(EyeDropper* eyedropper, AbortSignal* signal)
: eyedropper_(eyedropper), abortsignal_(signal) {}
~OpenAbortAlgorithm() override = default;
void Run() override { eyedropper_->AbortCallback(abortsignal_); }
void Trace(Visitor* visitor) const override {
visitor->Trace(eyedropper_);
visitor->Trace(abortsignal_);
Algorithm::Trace(visitor);
}
private:
Member<EyeDropper> eyedropper_;
Member<AbortSignal> abortsignal_;
};
constexpr char kNotAvailableMessage[] = "EyeDropper is not available.";
EyeDropper::EyeDropper(ExecutionContext* context)
: eye_dropper_chooser_(context) {}
EyeDropper* EyeDropper::Create(ExecutionContext* context) {
return MakeGarbageCollected<EyeDropper>(context);
}
ScriptPromise<ColorSelectionResult> EyeDropper::open(
ScriptState* script_state,
const ColorSelectionOptions* options,
ExceptionState& exception_state) {
DCHECK(RuntimeEnabledFeatures::EyeDropperAPIEnabled());
if (!script_state->ContextIsValid()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"The object is no longer associated with a window.");
return EmptyPromise();
}
LocalDOMWindow* window = LocalDOMWindow::From(script_state);
if (!LocalFrame::HasTransientUserActivation(window->GetFrame())) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotAllowedError,
"EyeDropper::open() requires user gesture.");
return EmptyPromise();
}
if (!::features::IsEyeDropperEnabled()) {
exception_state.ThrowDOMException(DOMExceptionCode::kOperationError,
kNotAvailableMessage);
return EmptyPromise();
}
if (eye_dropper_chooser_.is_bound()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"EyeDropper is already open.");
return EmptyPromise();
}
std::unique_ptr<ScopedAbortState> end_chooser_abort_state = nullptr;
std::unique_ptr<ScopedAbortState> response_handler_abort_state = nullptr;
if (auto* signal = options->getSignalOr(nullptr)) {
if (signal->aborted()) {
return ScriptPromise<ColorSelectionResult>::Reject(
script_state, signal->reason(script_state));
}
auto* handle = signal->AddAlgorithm(
MakeGarbageCollected<OpenAbortAlgorithm>(this, signal));
end_chooser_abort_state =
std::make_unique<ScopedAbortState>(signal, handle);
response_handler_abort_state =
std::make_unique<ScopedAbortState>(signal, handle);
}
resolver_ = MakeGarbageCollected<ScriptPromiseResolver<ColorSelectionResult>>(
script_state, exception_state.GetContext());
auto promise = resolver_->Promise();
auto* frame = window->GetFrame();
frame->GetBrowserInterfaceBroker().GetInterface(
eye_dropper_chooser_.BindNewPipeAndPassReceiver(
frame->GetTaskRunner(TaskType::kUserInteraction)));
eye_dropper_chooser_.set_disconnect_handler(
BindOnce(&EyeDropper::EndChooser, WrapWeakPersistent(this),
std::move(end_chooser_abort_state)));
eye_dropper_chooser_->Choose(resolver_->WrapCallbackInScriptScope(
BindOnce(&EyeDropper::EyeDropperResponseHandler, WrapPersistent(this),
std::move(response_handler_abort_state))));
return promise;
}
void EyeDropper::AbortCallback(AbortSignal* signal) {
if (resolver_) {
ScriptState* script_state = resolver_->GetScriptState();
if (IsInParallelAlgorithmRunnable(resolver_->GetExecutionContext(),
script_state)) {
ScriptState::Scope script_state_scope(script_state);
resolver_->Reject(signal->reason(script_state));
}
}
eye_dropper_chooser_.reset();
resolver_ = nullptr;
}
void EyeDropper::EyeDropperResponseHandler(
std::unique_ptr<ScopedAbortState> scoped_abort_state,
ScriptPromiseResolver<ColorSelectionResult>* resolver,
bool success,
uint32_t color) {
eye_dropper_chooser_.reset();
// The abort callback resets the Mojo remote if an abort is signalled,
// so by receiving a reply, the eye dropper operation must *not* have
// been aborted by the abort signal. Thus, the promise is not yet resolved,
// so resolver_ must be non-null.
DCHECK_EQ(resolver_, resolver);
if (success) {
ColorSelectionResult* result = ColorSelectionResult::Create();
// TODO(https://1351544): The EyeDropper should return a Color or an
// SkColor4f, instead of an SkColor.
result->setSRGBHex(Color::FromRGBA32(color).SerializeAsCanvasColor());
resolver->Resolve(result);
resolver_ = nullptr;
} else {
RejectPromiseHelper(DOMExceptionCode::kAbortError,
"The user canceled the selection.");
}
}
void EyeDropper::EndChooser(
std::unique_ptr<ScopedAbortState> scoped_abort_state) {
eye_dropper_chooser_.reset();
if (!resolver_ ||
!IsInParallelAlgorithmRunnable(resolver_->GetExecutionContext(),
resolver_->GetScriptState())) {
return;
}
ScriptState::Scope script_state_scope(resolver_->GetScriptState());
RejectPromiseHelper(DOMExceptionCode::kOperationError, kNotAvailableMessage);
}
void EyeDropper::RejectPromiseHelper(DOMExceptionCode exception_code,
const String& message) {
resolver_->RejectWithDOMException(exception_code, message);
resolver_ = nullptr;
}
void EyeDropper::Trace(Visitor* visitor) const {
visitor->Trace(eye_dropper_chooser_);
visitor->Trace(resolver_);
ScriptWrappable::Trace(visitor);
}
} // namespace blink