Skip to content

Commit

Permalink
[EyeDropper API] Add optional AbortSignal parameter to EyeDropper.open
Browse files Browse the repository at this point in the history
Per TAG review[1], this CL adds an optional AbortSignal parameter to
EyeDropper.open to allow authors to dismiss the eyedropper with no
selection in case another high-priority event occurs.

[1]: w3ctag/design-reviews#587 (comment)

Bug: 1248286
Change-Id: I15580609ece85f947f019a724b23b5e4898e42f2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3152761
Commit-Queue: Ionel Popescu <iopopesc@microsoft.com>
Reviewed-by: Mason Freed <masonf@chromium.org>
Cr-Commit-Position: refs/heads/main@{#920997}
  • Loading branch information
ipopescu93 authored and Chromium LUCI CQ committed Sep 13, 2021
1 parent f80ddc8 commit 024aec1
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 7 deletions.
2 changes: 2 additions & 0 deletions third_party/blink/renderer/bindings/generated_in_modules.gni
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ generated_dictionary_sources_in_modules = [
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_close_event_init.h",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_collected_client_data.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_collected_client_data.h",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_color_selection_options.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_color_selection_options.h",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_color_selection_result.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_color_selection_result.h",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_compute_pressure_observer_options.cc",
Expand Down
1 change: 1 addition & 0 deletions third_party/blink/renderer/bindings/idl_in_modules.gni
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ static_idl_files_in_modules = get_path_info(
"//third_party/blink/renderer/modules/encryptedmedia/navigator_request_media_key_system_access.idl",
"//third_party/blink/renderer/modules/eventsource/event_source.idl",
"//third_party/blink/renderer/modules/eventsource/event_source_init.idl",
"//third_party/blink/renderer/modules/eyedropper/color_selection_options.idl",
"//third_party/blink/renderer/modules/eyedropper/color_selection_result.idl",
"//third_party/blink/renderer/modules/eyedropper/eye_dropper.idl",
"//third_party/blink/renderer/modules/file_system_access/data_transfer_item_file_system_access.idl",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright 2021 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.

// https://wicg.github.io/eyedropper-api/#colorselectionoptions-dictionary

dictionary ColorSelectionOptions {
AbortSignal signal;
};
36 changes: 31 additions & 5 deletions third_party/blink/renderer/modules/eyedropper/eye_dropper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@
#include "third_party/blink/public/common/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/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/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 {

constexpr char kAbortMessage[] = "Color selection aborted.";
constexpr char kNotAvailableMessage[] = "EyeDropper is not available.";

EyeDropper::EyeDropper(ExecutionContext* context)
: eye_dropper_chooser_(context) {}

Expand All @@ -26,6 +32,7 @@ EyeDropper* EyeDropper::Create(ExecutionContext* context) {
}

ScriptPromise EyeDropper::open(ScriptState* script_state,
const ColorSelectionOptions* options,
ExceptionState& exception_state) {
DCHECK(RuntimeEnabledFeatures::EyeDropperAPIEnabled());

Expand All @@ -46,7 +53,7 @@ ScriptPromise EyeDropper::open(ScriptState* script_state,

if (!features::IsEyeDropperEnabled()) {
exception_state.ThrowDOMException(DOMExceptionCode::kOperationError,
"EyeDropper is not available.");
kNotAvailableMessage);
return ScriptPromise();
}

Expand All @@ -56,6 +63,16 @@ ScriptPromise EyeDropper::open(ScriptState* script_state,
return ScriptPromise();
}

if (options->hasSignal()) {
if (options->signal()->aborted()) {
exception_state.ThrowDOMException(DOMExceptionCode::kAbortError,
kAbortMessage);
return ScriptPromise();
}
options->signal()->AddAlgorithm(
WTF::Bind(&EyeDropper::Abort, WrapWeakPersistent(this)));
}

resolver_ = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver_->Promise();

Expand All @@ -72,6 +89,10 @@ ScriptPromise EyeDropper::open(ScriptState* script_state,
return promise;
}

void EyeDropper::Abort() {
RejectPromiseHelper(DOMExceptionCode::kAbortError, kAbortMessage);
}

void EyeDropper::EyeDropperResponseHandler(ScriptPromiseResolver* resolver,
bool success,
uint32_t color) {
Expand All @@ -87,16 +108,21 @@ void EyeDropper::EyeDropperResponseHandler(ScriptPromiseResolver* resolver,
result->setSRGBHex(Color(color).Serialized());
resolver->Resolve(result);
} else {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kAbortError, "The user canceled the selection."));
RejectPromiseHelper(DOMExceptionCode::kAbortError,
"The user canceled the selection.");
}
}

void EyeDropper::EndChooser() {
RejectPromiseHelper(DOMExceptionCode::kOperationError, kNotAvailableMessage);
}

void EyeDropper::RejectPromiseHelper(DOMExceptionCode exception_code,
const WTF::String& message) {
eye_dropper_chooser_.reset();
if (resolver_) {
resolver_->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kOperationError, "EyeDropper is not available."));
resolver_->Reject(
MakeGarbageCollected<DOMException>(exception_code, message));
resolver_ = nullptr;
}
}
Expand Down
8 changes: 7 additions & 1 deletion third_party/blink/renderer/modules/eyedropper/eye_dropper.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

namespace blink {

class ColorSelectionOptions;
enum class DOMExceptionCode;
class ExceptionState;
class ScriptPromise;
class ScriptPromiseResolver;
Expand All @@ -35,13 +37,17 @@ class EyeDropper final : public ScriptWrappable {

// Opens the eyedropper and replaces the cursor with a browser-defined
// preview.
ScriptPromise open(ScriptState*, ExceptionState&);
ScriptPromise open(ScriptState*,
const ColorSelectionOptions*,
ExceptionState&);

void Trace(Visitor*) const override;

private:
void Abort();
void EyeDropperResponseHandler(ScriptPromiseResolver*, bool, uint32_t);
void EndChooser();
void RejectPromiseHelper(DOMExceptionCode, const WTF::String&);

HeapMojoRemote<mojom::blink::EyeDropperChooser> eye_dropper_chooser_;
Member<ScriptPromiseResolver> resolver_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
interface EyeDropper {
[CallWith=ExecutionContext] constructor();

[CallWith=ScriptState, RaisesException] Promise<ColorSelectionResult> open();
[CallWith=ScriptState, RaisesException] Promise<ColorSelectionResult> open(
optional ColorSelectionOptions options = {});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<title>EyeDropper Test: abort signal</title>
<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>

<button id="eyedropperbutton">Open eyedropper!</button>

<script>
function clickOn(element) {
const actions = new test_driver.Actions();
return actions.pointerMove(0, 0, {origin: element})
.pointerDown({button: actions.ButtonType.LEFT})
.pointerUp({button: actions.ButtonType.LEFT})
.send();
}

promise_test(async t => {
const eyeDropperButton = document.getElementById("eyedropperbutton");
eyeDropperButton.addEventListener("click", async () => {
let eyeDropper = new EyeDropper();
let controller = new AbortController();
controller.abort();
await promise_rejects_dom(t, "AbortError", eyeDropper.open({signal: controller.signal}));
});
await clickOn(eyeDropperButton);
}, "Calling EyeDropper.open with signal's aborted flag set should directly throw AbortError");

promise_test(async t => {
const eyeDropperButton = document.getElementById("eyedropperbutton");
eyeDropperButton.addEventListener("click", () => {
let eyeDropper = new EyeDropper();
let controller = new AbortController();
this.step_timeout(() => {
controller.abort();
}, 500);
promise_rejects_dom(t, "AbortError", eyeDropper.open({signal: controller.signal}));
});
await clickOn(eyeDropperButton);
}, "Calling abort should dismiss the eyedropper");

</script>

0 comments on commit 024aec1

Please sign in to comment.