capture_selfie_cam: Add a11y alert for snapping and resizing camera
- Trigger a11y alert when focusing the camera preview. - Trigger a11y alert when snapping the camera preview, either through keyboard, mouse or gesture. Alert will still be triggered even though the snap position does not change through the operations. - Trigger a11y alert when pressing the resize button to collapse/expand the camera preview. Bug: 1302690 Change-Id: I707424f67e428da833be093779d6a6521cd892ea Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3569627 Reviewed-by: Ahmed Fakhry <afakhry@chromium.org> Commit-Queue: Min Chen <minch@chromium.org> Cr-Commit-Position: refs/heads/main@{#994458}
This commit is contained in:
ash
ash_strings.grd
ash_strings_grd
IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_FOCUSED.png.sha1IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_LEFT.png.sha1IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_RIGHT.png.sha1IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_LEFT.png.sha1IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_RIGHT.png.sha1
capture_mode
@ -4025,6 +4025,21 @@ Here are some things you can try to get started.
|
||||
<message name="IDS_ASH_SCREEN_CAPTURE_SELECTED_CAMERA_CHANGED" desc="Alert spoken by screen readers when the selected camera gets changed.">
|
||||
Camera input set to <ph name="CAMERA_NAME">$1</ph>.
|
||||
</message>
|
||||
<message name="IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_FOCUSED" desc="Alert spoken by screen readers when camera preview is focused.">
|
||||
Camera preview, press control + arrow keys to move the preview to a different corner
|
||||
</message>
|
||||
<message name="IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_RIGHT" desc="Alert spoken by screen readers when camera preview being snapped to upper right corner">
|
||||
Camera snapped to upper right corner
|
||||
</message>
|
||||
<message name="IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_LEFT" desc="Alert spoken by screen readers when camera preview being snapped to upper left corner">
|
||||
Camera snapped to upper left corner
|
||||
</message>
|
||||
<message name="IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_RIGHT" desc="Alert spoken by screen readers when camera preview being snapped to lower right corner">
|
||||
Camera snapped to lower right corner
|
||||
</message>
|
||||
<message name="IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_LEFT" desc="Alert spoken by screen readers when camera preview being snapped to lower left corner">
|
||||
Camera snapped to lower left corner
|
||||
</message>
|
||||
<message name="IDS_ASH_SCREEN_CAPTURE_NAVIGATION_ALERT_RECORD_MICROPHONE" desc="Alert spoken by screen readers when user tabs to record microphone setting.">
|
||||
Record microphone <ph name="CURRENT_STATE">$1<ex>off</ex></ph>, Press enter to turn microphone recording <ph name="NEW_STATE">$2<ex>on</ex></ph>
|
||||
</message>
|
||||
|
@ -0,0 +1 @@
|
||||
640369267721cc21ec00973d27469617cd9d21e6
|
1
ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_LEFT.png.sha1
Normal file
1
ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_LEFT.png.sha1
Normal file
@ -0,0 +1 @@
|
||||
261a7e1172786d25c8032996168d7d217e62c578
|
1
ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_RIGHT.png.sha1
Normal file
1
ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_RIGHT.png.sha1
Normal file
@ -0,0 +1 @@
|
||||
69522bd01ed5159c1c327ce2e9e37fb5ce76004a
|
1
ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_LEFT.png.sha1
Normal file
1
ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_LEFT.png.sha1
Normal file
@ -0,0 +1 @@
|
||||
b6a84be1d7df191e70a35481868d40e6a3a046de
|
1
ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_RIGHT.png.sha1
Normal file
1
ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_RIGHT.png.sha1
Normal file
@ -0,0 +1 @@
|
||||
44eb3cbc3e27e79645ce0cee1ebe573b25b249d4
|
@ -49,6 +49,7 @@
|
||||
#include "ui/views/widget/widget.h"
|
||||
#include "ui/wm/core/coordinate_conversion.h"
|
||||
#include "ui/wm/core/window_properties.h"
|
||||
#include "ui/wm/core/window_util.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
@ -179,10 +180,6 @@ views::Widget::InitParams CreateWidgetParams(const gfx::Rect& bounds) {
|
||||
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
|
||||
params.parent = CaptureModeController::Get()->GetCameraPreviewParentWindow();
|
||||
params.bounds = bounds;
|
||||
// Need to set `params.child` to true here, otherwise camera preview widget
|
||||
// will be added as a transient child to `params.parent`. For more details,
|
||||
// please check `NativeWidgetAura::InitNativeWidget`.
|
||||
params.child = true;
|
||||
params.name = "CameraPreviewWidget";
|
||||
return params;
|
||||
}
|
||||
@ -278,6 +275,21 @@ gfx::Size CalculatePreviewInitialSize() {
|
||||
return gfx::Size(max_shorter_side, max_shorter_side);
|
||||
}
|
||||
|
||||
// Returns the appropriate `message_id` for ChromeVox alert on setting camera
|
||||
// preview snap position.
|
||||
int GetMessageIdForSnapPosition(CameraPreviewSnapPosition snap_position) {
|
||||
switch (snap_position) {
|
||||
case CameraPreviewSnapPosition::kTopRight:
|
||||
return IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_RIGHT;
|
||||
case CameraPreviewSnapPosition::kTopLeft:
|
||||
return IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_UPPER_LEFT;
|
||||
case CameraPreviewSnapPosition::kBottomRight:
|
||||
return IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_RIGHT;
|
||||
case CameraPreviewSnapPosition::kBottomLeft:
|
||||
return IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_SNAPPED_TO_LOWER_LEFT;
|
||||
}
|
||||
}
|
||||
|
||||
// Defines a window targeter that will be installed on the camera preview
|
||||
// widget's window so that we can allow located events outside of the camera
|
||||
// preview circle to go through and not be consumed by the camera preview. This
|
||||
@ -451,15 +463,21 @@ void CaptureModeCameraController::MaybeReparentPreviewWidget() {
|
||||
if (!camera_preview_widget_)
|
||||
return;
|
||||
|
||||
// Remove the camera preview from its transient parent if it has one. And
|
||||
// reparenting it to the corresponding parent again. This is done to keep
|
||||
// the camera preview in a correct state that is not a transient child of
|
||||
// its parent.
|
||||
auto* native_window = camera_preview_widget_->GetNativeWindow();
|
||||
if (auto* transient_parent = wm::GetTransientParent(native_window))
|
||||
wm::RemoveTransientChild(transient_parent, native_window);
|
||||
|
||||
const bool was_visible_before = camera_preview_widget_->IsVisible();
|
||||
auto* controller = CaptureModeController::Get();
|
||||
DCHECK(!controller->is_recording_in_progress());
|
||||
auto* parent = controller->GetCameraPreviewParentWindow();
|
||||
DCHECK(parent);
|
||||
auto* native_window = camera_preview_widget_->GetNativeWindow();
|
||||
|
||||
if (parent != native_window->parent())
|
||||
views::Widget::ReparentNativeView(native_window, parent);
|
||||
DCHECK(!wm::GetTransientParent(native_window));
|
||||
|
||||
MaybeUpdatePreviewWidget();
|
||||
if (was_visible_before != camera_preview_widget_->IsVisible()) {
|
||||
@ -470,12 +488,15 @@ void CaptureModeCameraController::MaybeReparentPreviewWidget() {
|
||||
}
|
||||
|
||||
void CaptureModeCameraController::SetCameraPreviewSnapPosition(
|
||||
CameraPreviewSnapPosition value) {
|
||||
if (camera_preview_snap_position_ == value)
|
||||
return;
|
||||
CameraPreviewSnapPosition value,
|
||||
bool animate) {
|
||||
// Trigger a11y alert on setting camera preview snap position even though the
|
||||
// snap position may actually not change.
|
||||
capture_mode_util::TriggerAccessibilityAlert(
|
||||
GetMessageIdForSnapPosition(value));
|
||||
|
||||
camera_preview_snap_position_ = value;
|
||||
MaybeUpdatePreviewWidget();
|
||||
MaybeUpdatePreviewWidget(animate);
|
||||
}
|
||||
|
||||
void CaptureModeCameraController::MaybeUpdatePreviewWidget(bool animate) {
|
||||
@ -550,9 +571,8 @@ void CaptureModeCameraController::EndDraggingPreview(
|
||||
const gfx::PointF& screen_location,
|
||||
bool is_touch) {
|
||||
ContinueDraggingPreview(screen_location);
|
||||
UpdateSnapPositionOnDragEnded();
|
||||
|
||||
MaybeUpdatePreviewWidget(/*animate=*/true);
|
||||
SetCameraPreviewSnapPosition(CalculateSnapPositionOnDragEnded(),
|
||||
/*animate=*/true);
|
||||
|
||||
is_drag_in_progress_ = false;
|
||||
camera_preview_view_->RefreshResizeButtonVisibility();
|
||||
@ -775,7 +795,7 @@ void CaptureModeCameraController::RefreshCameraPreview() {
|
||||
layer->SetFillsBoundsOpaquely(false);
|
||||
layer->SetMasksToBounds(true);
|
||||
|
||||
MaybeUpdatePreviewWidget(/*animate=*/false);
|
||||
MaybeReparentPreviewWidget();
|
||||
}
|
||||
|
||||
DCHECK(camera_preview_view_);
|
||||
@ -884,7 +904,8 @@ gfx::Rect CaptureModeCameraController::GetPreviewWidgetBoundsForSnapPosition(
|
||||
return gfx::Rect(origin, preview_size);
|
||||
}
|
||||
|
||||
void CaptureModeCameraController::UpdateSnapPositionOnDragEnded() {
|
||||
CameraPreviewSnapPosition
|
||||
CaptureModeCameraController::CalculateSnapPositionOnDragEnded() const {
|
||||
const gfx::Point center_point_of_preview_widget =
|
||||
GetCurrentBoundsMatchingConfineBoundsCoordinates().CenterPoint();
|
||||
const gfx::Point center_point_of_confine_bounds =
|
||||
@ -893,20 +914,20 @@ void CaptureModeCameraController::UpdateSnapPositionOnDragEnded() {
|
||||
.CenterPoint();
|
||||
|
||||
if (center_point_of_preview_widget.x() < center_point_of_confine_bounds.x()) {
|
||||
if (center_point_of_preview_widget.y() < center_point_of_confine_bounds.y())
|
||||
camera_preview_snap_position_ = CameraPreviewSnapPosition::kTopLeft;
|
||||
else
|
||||
camera_preview_snap_position_ = CameraPreviewSnapPosition::kBottomLeft;
|
||||
} else {
|
||||
if (center_point_of_preview_widget.y() < center_point_of_confine_bounds.y())
|
||||
camera_preview_snap_position_ = CameraPreviewSnapPosition::kTopRight;
|
||||
else
|
||||
camera_preview_snap_position_ = CameraPreviewSnapPosition::kBottomRight;
|
||||
return center_point_of_preview_widget.y() <
|
||||
center_point_of_confine_bounds.y()
|
||||
? CameraPreviewSnapPosition::kTopLeft
|
||||
: CameraPreviewSnapPosition::kBottomLeft;
|
||||
}
|
||||
|
||||
return center_point_of_preview_widget.y() < center_point_of_confine_bounds.y()
|
||||
? CameraPreviewSnapPosition::kTopRight
|
||||
: CameraPreviewSnapPosition::kBottomRight;
|
||||
}
|
||||
|
||||
gfx::Rect CaptureModeCameraController::
|
||||
GetCurrentBoundsMatchingConfineBoundsCoordinates() {
|
||||
gfx::Rect
|
||||
CaptureModeCameraController::GetCurrentBoundsMatchingConfineBoundsCoordinates()
|
||||
const {
|
||||
aura::Window* preview_window = camera_preview_widget_->GetNativeWindow();
|
||||
aura::Window* parent = preview_window->parent();
|
||||
if (parent->GetProperty(wm::kUsesScreenCoordinatesKey))
|
||||
|
@ -190,8 +190,10 @@ class ASH_EXPORT CaptureModeCameraController
|
||||
void MaybeReparentPreviewWidget();
|
||||
|
||||
// Sets `camera_preview_snap_position_` and updates the preview widget's
|
||||
// bounds accordingly.
|
||||
void SetCameraPreviewSnapPosition(CameraPreviewSnapPosition value);
|
||||
// bounds accordingly. If `animate` is set to true, the camera preview will
|
||||
// animate to its new snap position.
|
||||
void SetCameraPreviewSnapPosition(CameraPreviewSnapPosition value,
|
||||
bool animate = false);
|
||||
|
||||
// Updates the bounds and visibility of `camera_preview_widget_` according to
|
||||
// the current state of the capture surface within which the camera preview
|
||||
@ -286,13 +288,12 @@ class ASH_EXPORT CaptureModeCameraController
|
||||
const gfx::Size& preview_size,
|
||||
CameraPreviewSnapPosition snap_position) const;
|
||||
|
||||
// Called by `EndDraggingPreview`, updating `camera_preview_snap_position_`
|
||||
// according to the current position of the `camera_preview_view_`.
|
||||
void UpdateSnapPositionOnDragEnded();
|
||||
// Returns the new snap position of the camera preview on drag ended.
|
||||
CameraPreviewSnapPosition CalculateSnapPositionOnDragEnded() const;
|
||||
|
||||
// Returns the current bounds of camemra preview widget that match the
|
||||
// coordinate system of the confine bounds.
|
||||
gfx::Rect GetCurrentBoundsMatchingConfineBoundsCoordinates();
|
||||
gfx::Rect GetCurrentBoundsMatchingConfineBoundsCoordinates() const;
|
||||
|
||||
// Does post works for camera preview after RefreshCameraPreview(). It
|
||||
// triggers a11y alert based on `was_preview_visible_before` and the current
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "ui/events/event.h"
|
||||
#include "ui/gfx/geometry/point_f.h"
|
||||
#include "ui/gfx/paint_vector_icon.h"
|
||||
#include "ui/views/accessibility/view_accessibility.h"
|
||||
#include "ui/views/animation/animation_builder.h"
|
||||
#include "ui/views/background.h"
|
||||
#include "ui/views/controls/highlight_path_generator.h"
|
||||
@ -118,11 +119,11 @@ bool CameraPreviewView::MaybeHandleKeyEvent(const ui::KeyEvent* event) {
|
||||
current_snap_position, /*going_up=*/key_code == ui::VKEY_UP);
|
||||
}
|
||||
|
||||
if (new_snap_position == current_snap_position)
|
||||
return false;
|
||||
|
||||
camera_controller_->SetCameraPreviewSnapPosition(new_snap_position);
|
||||
return true;
|
||||
// Still call `SetCameraPreviewSnapPosition` even if the snap position does
|
||||
// not change. As we still want to trigger a11y alert in this case.
|
||||
camera_controller_->SetCameraPreviewSnapPosition(new_snap_position,
|
||||
/*animate=*/true);
|
||||
return new_snap_position == current_snap_position;
|
||||
}
|
||||
|
||||
void CameraPreviewView::RefreshResizeButtonVisibility() {
|
||||
@ -236,6 +237,13 @@ void CameraPreviewView::Layout() {
|
||||
gfx::RoundedCornersF(height() / 2.f));
|
||||
}
|
||||
|
||||
void CameraPreviewView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
|
||||
views::View::GetAccessibleNodeData(node_data);
|
||||
node_data->SetName(
|
||||
l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_FOCUSED));
|
||||
node_data->role = ax::mojom::Role::kVideo;
|
||||
}
|
||||
|
||||
views::View* CameraPreviewView::GetView() {
|
||||
return this;
|
||||
}
|
||||
|
@ -77,6 +77,7 @@ class CameraPreviewView
|
||||
void OnMouseEntered(const ui::MouseEvent& event) override;
|
||||
void OnMouseExited(const ui::MouseEvent& event) override;
|
||||
void Layout() override;
|
||||
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
|
||||
|
||||
// CaptureModeSessionFocusCycler::HighlightableView:
|
||||
views::View* GetView() override;
|
||||
|
@ -64,6 +64,7 @@
|
||||
#include "ui/views/view_observer.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
#include "ui/wm/core/coordinate_conversion.h"
|
||||
#include "ui/wm/core/window_util.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
@ -1230,13 +1231,15 @@ TEST_F(CaptureModeCameraTest, CameraPreviewWidgetAfterTypeSwitched) {
|
||||
const auto* camera_preview_widget =
|
||||
camera_controller->camera_preview_widget();
|
||||
EXPECT_TRUE(camera_preview_widget);
|
||||
auto* parent = camera_preview_widget->GetNativeWindow()->parent();
|
||||
auto* camera_preview_window = camera_preview_widget->GetNativeWindow();
|
||||
const auto* selected_window =
|
||||
controller->capture_mode_session()->GetSelectedWindow();
|
||||
ASSERT_EQ(parent, selected_window);
|
||||
ASSERT_EQ(camera_preview_window->parent(), selected_window);
|
||||
|
||||
// Verify that camera preview is at the bottom right corner of the window.
|
||||
VerifyPreviewAlignment(selected_window->GetBoundsInScreen());
|
||||
// `camera_preview_window` should not have a transient parent.
|
||||
EXPECT_FALSE(wm::GetTransientParent(camera_preview_window));
|
||||
}
|
||||
|
||||
// Tests that audio and camera menu groups should be hidden from the settings
|
||||
@ -1642,7 +1645,7 @@ TEST_F(CaptureModeCameraTest, FocusableCameraPreviewInFullscreen) {
|
||||
// Tests that the camera preview is focusable in fullscreen capture.
|
||||
auto* camera_preview_view = camera_controller->camera_preview_view();
|
||||
auto* resize_button = GetPreviewResizeButton();
|
||||
SendKey(ui::VKEY_TAB, event_generator, /*shift_down=*/false, /*count=*/6);
|
||||
SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6);
|
||||
EXPECT_EQ(FocusGroup::kCameraPreview, test_api.GetCurrentFocusGroup());
|
||||
EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
|
||||
EXPECT_TRUE(camera_preview_view->has_focus());
|
||||
@ -1731,7 +1734,7 @@ TEST_F(CaptureModeCameraTest, FocusableCameraPreviewInRegion) {
|
||||
// Tests that the camera preview is focusable in region capture.
|
||||
auto* camera_preview_view = camera_controller->camera_preview_view();
|
||||
auto* resize_button = GetPreviewResizeButton();
|
||||
SendKey(ui::VKEY_TAB, event_generator, /*shift_down=*/false, /*count=*/15);
|
||||
SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/15);
|
||||
EXPECT_EQ(FocusGroup::kCameraPreview, test_api.GetCurrentFocusGroup());
|
||||
EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
|
||||
EXPECT_TRUE(camera_preview_view->has_focus());
|
||||
@ -1797,7 +1800,7 @@ TEST_F(CaptureModeCameraTest, FocusableCameraPreviewInWindow) {
|
||||
// be shown inside it.
|
||||
auto* camera_preview_view = camera_controller->camera_preview_view();
|
||||
auto* resize_button = GetPreviewResizeButton();
|
||||
SendKey(ui::VKEY_TAB, event_generator, /*shift_down=*/false, /*count=*/6);
|
||||
SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/6);
|
||||
EXPECT_EQ(FocusGroup::kCaptureWindow, test_api.GetCurrentFocusGroup());
|
||||
EXPECT_EQ(0u, test_api.GetCurrentFocusIndex());
|
||||
EXPECT_TRUE(test_api.GetHighlightableWindow(window2.get())->has_focus());
|
||||
@ -2504,6 +2507,15 @@ TEST_P(CaptureModeCameraPreviewTest, CameraPreviewDragToSnap) {
|
||||
camera_controller->camera_preview_snap_position());
|
||||
VerifyPreviewAlignment(GetCaptureBoundsInScreen());
|
||||
|
||||
// Drag the camera preview for a small distance. Tests that even though the
|
||||
// snap position does not change, the preview should be snapped back to its
|
||||
// previous position.
|
||||
DragPreviewToPoint(preview_widget, {capture_bounds_center_point.x() + 20,
|
||||
capture_bounds_center_point.y() + 20});
|
||||
EXPECT_EQ(CameraPreviewSnapPosition::kBottomRight,
|
||||
camera_controller->camera_preview_snap_position());
|
||||
VerifyPreviewAlignment(GetCaptureBoundsInScreen());
|
||||
|
||||
// Drag and drop camera preview by mouse to the top right of the
|
||||
// `capture_bounds_center_point`, verify that camera preview is snapped to
|
||||
// the top right with correct position.
|
||||
|
@ -1035,6 +1035,9 @@ void CaptureModeSession::OnKeyEvent(ui::KeyEvent* event) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->type() != ui::ET_KEY_PRESSED)
|
||||
return;
|
||||
|
||||
auto* camera_controller = controller_->camera_controller();
|
||||
auto* camera_preview_view =
|
||||
camera_controller ? camera_controller->camera_preview_view() : nullptr;
|
||||
@ -1043,9 +1046,6 @@ void CaptureModeSession::OnKeyEvent(ui::KeyEvent* event) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->type() != ui::ET_KEY_PRESSED)
|
||||
return;
|
||||
|
||||
// We create an owned heap-allocated boolean to pass it to `deferred_runner`
|
||||
// and hold a pointer to the boolean to be able to change its value to control
|
||||
// whether the deferred runner should call `MaybeUpdateCaptureUisOpacity` or
|
||||
|
Reference in New Issue
Block a user