Commit 0ec59742 authored by Alice Boxhall's avatar Alice Boxhall Committed by Commit Bot

Making the temporary focus highlight match UX mocks. See bug for screenshots.

AX-Relnotes: N/A
Bug: 1021939
Change-Id: I051ee8ff4deb08977a5319783bae5aced602d54e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2256942
Commit-Queue: Alice Boxhall <aboxhall@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Reviewed-by: default avatarChris Hall <chrishall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#781731}
parent 50c45a82
...@@ -25,42 +25,58 @@ class Compositor; ...@@ -25,42 +25,58 @@ class Compositor;
namespace { namespace {
// The number of pixels of padding between the outer edge of the focused
// element's bounding box and the inner edge of the inner focus ring.
constexpr int kPaddingDIPs = 8;
// The size of the border radius of the innermost focus highlight ring.
constexpr int kBorderRadiusDIPs = 4;
// The stroke width, in DIPs, of the innermost focus ring, and each line drawn
// as part of the focus ring gradient effect.
constexpr int kStrokeWidthDIPs = 2;
// The thickness, in DIPs, of the outer focus ring gradient.
constexpr int kGradientWidthDIPs = 9;
// The padding between the bounds of the layer and the bounds of the // The padding between the bounds of the layer and the bounds of the
// drawn focus ring, in DIPs. If it's zero the focus ring might be // drawn focus ring, in DIPs. If it's zero the focus ring might be
// clipped. // clipped.
constexpr int kLayerPaddingDIPs = 2; constexpr int kLayerPaddingDIPs = 2;
// The number of pixels the focus ring is outset from the object it outlines, // Total DIPs between the edge of the node and the edge of the layer.
// which also determines the border radius of the rounded corners. constexpr int kTotalLayerPaddingDIPs =
constexpr int kAccessibilityFocusHighlightMarginDIPs = 7; kPaddingDIPs + kStrokeWidthDIPs + kGradientWidthDIPs + kLayerPaddingDIPs;
// The stroke width, in DIPs, of each line drawn as part of the focus ring
// gradient effect.
constexpr int kStrokeWidthDIPs = 2;
// The thickness, in DIPs, of the focus ring gradient.
constexpr int kGradientWidthDIPs = 3;
// The amount of time it should take for the highlight to fade in. // The amount of time it should take for the highlight to fade in.
constexpr int kFadeInTimeMilliseconds = 100; constexpr int kFadeInTimeMilliseconds = 100;
// The amount of time the highlight should persist before beginning to fade.
constexpr int kHighlightPersistTimeMilliseconds = 1000;
// The amount of time it should take for the highlight to fade out. // The amount of time it should take for the highlight to fade out.
constexpr int kFadeOutTimeMilliseconds = 1600; constexpr int kFadeOutTimeMilliseconds = 600;
} // namespace } // namespace
// static // static
SkColor AccessibilityFocusHighlight::color_; SkColor AccessibilityFocusHighlight::default_color_;
// static // static
base::TimeDelta AccessibilityFocusHighlight::fade_in_time_; base::TimeDelta AccessibilityFocusHighlight::fade_in_time_;
// static
base::TimeDelta AccessibilityFocusHighlight::persist_time_;
// static // static
base::TimeDelta AccessibilityFocusHighlight::fade_out_time_; base::TimeDelta AccessibilityFocusHighlight::fade_out_time_;
// static // static
bool AccessibilityFocusHighlight::skip_activation_check_for_testing_ = false; bool AccessibilityFocusHighlight::skip_activation_check_for_testing_ = false;
// static
bool AccessibilityFocusHighlight::use_default_color_for_testing_ = false;
AccessibilityFocusHighlight::AccessibilityFocusHighlight( AccessibilityFocusHighlight::AccessibilityFocusHighlight(
BrowserView* browser_view) BrowserView* browser_view)
: browser_view_(browser_view), : browser_view_(browser_view),
...@@ -82,9 +98,11 @@ AccessibilityFocusHighlight::AccessibilityFocusHighlight( ...@@ -82,9 +98,11 @@ AccessibilityFocusHighlight::AccessibilityFocusHighlight(
// One-time initialization of statics the first time an instance is created. // One-time initialization of statics the first time an instance is created.
if (fade_in_time_.is_zero()) { if (fade_in_time_.is_zero()) {
fade_in_time_ = base::TimeDelta::FromMilliseconds(kFadeInTimeMilliseconds); fade_in_time_ = base::TimeDelta::FromMilliseconds(kFadeInTimeMilliseconds);
persist_time_ =
base::TimeDelta::FromMilliseconds(kHighlightPersistTimeMilliseconds);
fade_out_time_ = fade_out_time_ =
base::TimeDelta::FromMilliseconds(kFadeOutTimeMilliseconds); base::TimeDelta::FromMilliseconds(kFadeOutTimeMilliseconds);
color_ = SkColorSetRGB(247, 152, 58); default_color_ = SkColorSetRGB(16, 16, 16); // #101010
} }
} }
...@@ -96,7 +114,8 @@ AccessibilityFocusHighlight::~AccessibilityFocusHighlight() { ...@@ -96,7 +114,8 @@ AccessibilityFocusHighlight::~AccessibilityFocusHighlight() {
// static // static
void AccessibilityFocusHighlight::SetNoFadeForTesting() { void AccessibilityFocusHighlight::SetNoFadeForTesting() {
fade_in_time_ = base::TimeDelta(); fade_in_time_ = base::TimeDelta();
fade_out_time_ = base::TimeDelta::FromHours(1); persist_time_ = base::TimeDelta::FromHours(1);
fade_out_time_ = base::TimeDelta();
} }
// static // static
...@@ -105,11 +124,21 @@ void AccessibilityFocusHighlight::SkipActivationCheckForTesting() { ...@@ -105,11 +124,21 @@ void AccessibilityFocusHighlight::SkipActivationCheckForTesting() {
} }
// static // static
SkColor AccessibilityFocusHighlight::GetHighlightColorForTesting() { void AccessibilityFocusHighlight::UseDefaultColorForTesting() {
return color_; use_default_color_for_testing_ = true;
}
SkColor AccessibilityFocusHighlight::GetHighlightColor() {
SkColor theme_color = browser_view_->GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_FocusedBorderColor);
if (theme_color == SK_ColorTRANSPARENT || use_default_color_for_testing_)
return default_color_;
return theme_color;
} }
void AccessibilityFocusHighlight::CreateOrUpdateLayer() { void AccessibilityFocusHighlight::CreateOrUpdateLayer(gfx::Rect node_bounds) {
// Find the layer of our owning BrowserView. // Find the layer of our owning BrowserView.
views::Widget* widget = browser_view_->GetWidget(); views::Widget* widget = browser_view_->GetWidget();
DCHECK(widget); DCHECK(widget);
...@@ -132,18 +161,30 @@ void AccessibilityFocusHighlight::CreateOrUpdateLayer() { ...@@ -132,18 +161,30 @@ void AccessibilityFocusHighlight::CreateOrUpdateLayer() {
layer_->parent()->StackAtTop(layer_.get()); layer_->parent()->StackAtTop(layer_.get());
// Update the bounds. // Update the bounds.
layer_->SetBounds(bounds_); // Outset the bounds of the layer by the total width of the focus highlight,
// plus the extra padding to ensure the highlight isn't clipped.
gfx::Rect layer_bounds = node_bounds;
int padding = kTotalLayerPaddingDIPs * device_scale_factor_;
layer_bounds.Inset(-padding, -padding);
layer_->SetBounds(layer_bounds);
// Set node_bounds_ and make their position relative to the layer, instead of
// the page.
node_bounds_ = node_bounds;
node_bounds_.set_x(padding);
node_bounds_.set_y(padding);
// Update the timestamp of the last time the layer changed. // Update the timestamp of the last time the layer changed.
focus_last_changed_time_ = base::TimeTicks::Now(); focus_last_changed_time_ = base::TimeTicks::Now();
// Ensure it's repainted. // Ensure it's repainted.
gfx::Rect layer_bounds(0, 0, bounds_.width(), bounds_.height()); gfx::Rect bounds(0, 0, layer_bounds.width(), layer_bounds.height());
layer_->SchedulePaint(layer_bounds); layer_->SchedulePaint(bounds);
// Schedule the animation observer, or update it if needed. // Schedule the animation observer, or update it if needed.
display::Display display = display::Display display =
display::Screen::GetScreen()->GetDisplayMatching(bounds_); display::Screen::GetScreen()->GetDisplayMatching(layer_bounds);
ui::Compositor* compositor = root_layer->GetCompositor(); ui::Compositor* compositor = root_layer->GetCompositor();
if (compositor != compositor_) { if (compositor != compositor_) {
if (compositor_ && compositor_->HasAnimationObserver(this)) if (compositor_ && compositor_->HasAnimationObserver(this))
...@@ -199,58 +240,74 @@ void AccessibilityFocusHighlight::Observe( ...@@ -199,58 +240,74 @@ void AccessibilityFocusHighlight::Observe(
// given to us in screen DIPs. // given to us in screen DIPs.
content::FocusedNodeDetails* node_details = content::FocusedNodeDetails* node_details =
content::Details<content::FocusedNodeDetails>(details).ptr(); content::Details<content::FocusedNodeDetails>(details).ptr();
bounds_ = node_details->node_bounds_in_screen; gfx::Rect node_bounds = node_details->node_bounds_in_screen;
// Convert it to the local coordinates of this BrowserView's widget. // Convert it to the local coordinates of this BrowserView's widget.
bounds_.Offset(-gfx::ToFlooredVector2d(browser_view_->GetWidget() node_bounds.Offset(-gfx::ToFlooredVector2d(browser_view_->GetWidget()
->GetClientAreaBoundsInScreen() ->GetClientAreaBoundsInScreen()
.OffsetFromOrigin())); .OffsetFromOrigin()));
// Outset the bounds by the margin of the focus ring plus the layer padding.
int outset =
int{(kAccessibilityFocusHighlightMarginDIPs + kLayerPaddingDIPs) *
device_scale_factor_};
bounds_.Inset(-outset, -outset);
// Create the layer if needed, and move/resize it. // Create the layer if needed, and move/resize it.
CreateOrUpdateLayer(); CreateOrUpdateLayer(node_bounds);
} }
void AccessibilityFocusHighlight::OnPaintLayer( void AccessibilityFocusHighlight::OnPaintLayer(
const ui::PaintContext& context) { const ui::PaintContext& context) {
ui::PaintRecorder recorder(context, layer_->size()); ui::PaintRecorder recorder(context, layer_->size());
SkColor highlight_color = GetHighlightColor();
cc::PaintFlags flags; cc::PaintFlags flags;
flags.setAntiAlias(true); flags.setAntiAlias(true);
flags.setStyle(cc::PaintFlags::kStroke_Style); flags.setStyle(cc::PaintFlags::kStroke_Style);
flags.setColor(highlight_color);
float dsf = device_scale_factor_; gfx::RectF bounds(node_bounds_);
flags.setStrokeWidth(kStrokeWidthDIPs * dsf); // Draw gradient first, so other lines will be drawn over the top.
// Apply padding
int padding = kPaddingDIPs * device_scale_factor_;
bounds.Inset(-padding, -padding);
int padding = int{kLayerPaddingDIPs * dsf}; gfx::RectF gradient_bounds(bounds);
int width = bounds_.width() - 2 * padding; int border_radius = kBorderRadiusDIPs * device_scale_factor_;
int height = bounds_.height() - 2 * padding; int gradient_border_radius = border_radius;
int margin = int{kAccessibilityFocusHighlightMarginDIPs * dsf};
// Translate the coordinate space so that we can draw the rounded
// rect at (0, 0) and not need to take the padding into account.
recorder.canvas()->Translate(gfx::Vector2d(padding, padding));
// Create a gradient effect by drawing the path outline multiple // Create a gradient effect by drawing the path outline multiple
// times with increasing insets from 0 to kGradientWidthDIPs, and // times with increasing insets from 0 to kGradientWidthDIPs, and
// with increasing transparency. // with increasing transparency.
int w = int{kGradientWidthDIPs * dsf}; int gradient_width = kGradientWidthDIPs * device_scale_factor_;
for (int i = 0; i < w; ++i) { int stroke_width = kStrokeWidthDIPs * device_scale_factor_;
// Distance remaining within border gradient. flags.setStrokeWidth(stroke_width);
int dist = w - i; int original_alpha = std::min(SkColorGetA(highlight_color), 192u);
for (int remaining = gradient_width; remaining > 0; remaining -= 1) {
// Decrease alpha as distance remaining decreases. // Decrease alpha as distance remaining decreases.
int alpha = 255 * dist * dist / (w * w); int alpha = (original_alpha * remaining * remaining) /
flags.setColor(SkColorSetA(color_, alpha)); (gradient_width * gradient_width);
flags.setAlpha(alpha);
recorder.canvas()->DrawRoundRect(gradient_bounds, gradient_border_radius,
flags);
gfx::RectF rect(i, i, width - i, height - i); gradient_bounds.Inset(-1, -1);
recorder.canvas()->DrawRoundRect(rect, margin - i, flags); gradient_border_radius += 1;
} }
// Draw the white ring before the inner ring, so that the inner ring is
// partially over the top, rather than drawing a 1px white ring. A 1px ring
// would be antialiased to look semi-transparent, which is not what we want.
// Resize bounds and border radius around inner ring
gfx::RectF white_ring_bounds(bounds);
white_ring_bounds.Inset(-(stroke_width / 2), -(stroke_width / 2));
int white_ring_border_radius = border_radius + (stroke_width / 2);
flags.setColor(SK_ColorWHITE);
flags.setStrokeWidth(stroke_width);
recorder.canvas()->DrawRoundRect(white_ring_bounds, white_ring_border_radius,
flags);
// Draw the innermost solid ring
flags.setColor(highlight_color);
recorder.canvas()->DrawRoundRect(bounds, border_radius, flags);
} }
void AccessibilityFocusHighlight::OnDeviceScaleFactorChanged( void AccessibilityFocusHighlight::OnDeviceScaleFactorChanged(
...@@ -283,26 +340,29 @@ void AccessibilityFocusHighlight::OnAnimationStep(base::TimeTicks timestamp) { ...@@ -283,26 +340,29 @@ void AccessibilityFocusHighlight::OnAnimationStep(base::TimeTicks timestamp) {
// If the fade out has completed, remove the layer and remove the // If the fade out has completed, remove the layer and remove the
// animation observer. // animation observer.
if (time_since_focus_move > fade_out_time_) { if (time_since_focus_move > persist_time_ + fade_out_time_) {
RemoveLayer(); RemoveLayer();
return; return;
} }
// Compute the opacity based on the fade in and fade out times. // Compute the opacity based on the fade in and fade out times.
float opacity; // TODO(aboxhall): figure out how to use cubic beziers
float opacity = 1.0f;
if (time_since_layer_create < fade_in_time_) { if (time_since_layer_create < fade_in_time_) {
// We're fading in. // We're fading in.
opacity = time_since_layer_create.InSecondsF() / fade_in_time_.InSecondsF(); opacity = time_since_layer_create.InSecondsF() / fade_in_time_.InSecondsF();
} else { } else if (time_since_focus_move > persist_time_) {
// Fading out. Add fade_in_time_ and fade_out_time_ because we don't // Fading out.
// want to start the fade out until after the fade in has finished. float time_since_began_fading =
opacity = 1.0f - (time_since_focus_move.InSecondsF() / time_since_focus_move.InSecondsF() -
fade_out_time_.InSecondsF()); (fade_in_time_.InSecondsF() + persist_time_.InSecondsF());
float fade_out_time_float = fade_out_time_.InSecondsF();
opacity = 1.0f - (time_since_began_fading / fade_out_time_float);
} }
// Layer::SetOpacity will throw an error if we're not within 0...1. // Layer::SetOpacity will throw an error if we're not within 0...1.
opacity = base::ClampToRange(opacity, 0.0f, 1.0f); opacity = base::ClampToRange(opacity, 0.0f, 1.0f);
layer_->SetOpacity(opacity); layer_->SetOpacity(opacity);
} }
......
...@@ -45,10 +45,10 @@ class AccessibilityFocusHighlight : public ui::LayerDelegate, ...@@ -45,10 +45,10 @@ class AccessibilityFocusHighlight : public ui::LayerDelegate,
// For testing. // For testing.
static void SetNoFadeForTesting(); static void SetNoFadeForTesting();
static void SkipActivationCheckForTesting(); static void SkipActivationCheckForTesting();
static SkColor GetHighlightColorForTesting(); static void UseDefaultColorForTesting();
// Create the layer if needed, and update its bounds to match |bounds_|. // Create the layer if needed, and set node_bounds_
void CreateOrUpdateLayer(); void CreateOrUpdateLayer(gfx::Rect node_bounds);
// Get rid of the layer and stop animation. // Get rid of the layer and stop animation.
void RemoveLayer(); void RemoveLayer();
...@@ -70,15 +70,17 @@ class AccessibilityFocusHighlight : public ui::LayerDelegate, ...@@ -70,15 +70,17 @@ class AccessibilityFocusHighlight : public ui::LayerDelegate,
void OnAnimationStep(base::TimeTicks timestamp) override; void OnAnimationStep(base::TimeTicks timestamp) override;
void OnCompositingShuttingDown(ui::Compositor* compositor) override; void OnCompositingShuttingDown(ui::Compositor* compositor) override;
// Compute the highlight color based on theme colors and defaults.
SkColor GetHighlightColor();
// The layer, if visible. // The layer, if visible.
std::unique_ptr<ui::Layer> layer_; std::unique_ptr<ui::Layer> layer_;
// The compositor associated with this layer. // The compositor associated with this layer.
ui::Compositor* compositor_ = nullptr; ui::Compositor* compositor_ = nullptr;
// The bounding rectangle of the focused object, in the coordinate system // The bounding rectangle of the focused object, relative to the layer.
// of our owner BrowserView's layer. gfx::Rect node_bounds_;
gfx::Rect bounds_;
// Owns this. // Owns this.
BrowserView* browser_view_; BrowserView* browser_view_;
...@@ -92,18 +94,25 @@ class AccessibilityFocusHighlight : public ui::LayerDelegate, ...@@ -92,18 +94,25 @@ class AccessibilityFocusHighlight : public ui::LayerDelegate,
// The current scale factor between DIPs and pixels. // The current scale factor between DIPs and pixels.
float device_scale_factor_; float device_scale_factor_;
// The color used for the highlight. // The default color used for the highlight.
static SkColor color_; static SkColor default_color_;
// The amount of time it should take for the highlight to fade in. // The amount of time it should take for the highlight to fade in.
static base::TimeDelta fade_in_time_; static base::TimeDelta fade_in_time_;
// The amount of time the highlight should persist between fading in and
// fading out.
static base::TimeDelta persist_time_;
// The amount of time it should take for the highlight to fade out. // The amount of time it should take for the highlight to fade out.
static base::TimeDelta fade_out_time_; static base::TimeDelta fade_out_time_;
// If set, draws the highlight even if the widget is not active. // If set, draws the highlight even if the widget is not active.
static bool skip_activation_check_for_testing_; static bool skip_activation_check_for_testing_;
// If set, don't check the system theme color.
static bool use_default_color_for_testing_;
// For observing browser preference notifications. // For observing browser preference notifications.
PrefChangeRegistrar profile_pref_registrar_; PrefChangeRegistrar profile_pref_registrar_;
......
...@@ -127,11 +127,12 @@ IN_PROC_BROWSER_TEST_F(AccessibilityFocusHighlightBrowserTest, ...@@ -127,11 +127,12 @@ IN_PROC_BROWSER_TEST_F(AccessibilityFocusHighlightBrowserTest,
MAYBE_DrawsHighlight) { MAYBE_DrawsHighlight) {
ui_test_utils::NavigateToURL( ui_test_utils::NavigateToURL(
browser(), GURL("data:text/html," browser(), GURL("data:text/html,"
"<body style='background-color: rgb(204, 255, 255)'>" "<body style='background-color: rgb(204, 255, 255);'>"
"<input id='textfield' style='width: 100%'>")); "<div tabindex=0 id='div'>Focusable div</div>"));
AccessibilityFocusHighlight::SetNoFadeForTesting(); AccessibilityFocusHighlight::SetNoFadeForTesting();
AccessibilityFocusHighlight::SkipActivationCheckForTesting(); AccessibilityFocusHighlight::SkipActivationCheckForTesting();
AccessibilityFocusHighlight::UseDefaultColorForTesting();
browser()->profile()->GetPrefs()->SetBoolean( browser()->profile()->GetPrefs()->SetBoolean(
prefs::kAccessibilityFocusHighlightEnabled, true); prefs::kAccessibilityFocusHighlightEnabled, true);
...@@ -145,16 +146,16 @@ IN_PROC_BROWSER_TEST_F(AccessibilityFocusHighlightBrowserTest, ...@@ -145,16 +146,16 @@ IN_PROC_BROWSER_TEST_F(AccessibilityFocusHighlightBrowserTest,
} while (CountPercentPixelsWithColor(image, SkColorSetRGB(204, 255, 255)) < } while (CountPercentPixelsWithColor(image, SkColorSetRGB(204, 255, 255)) <
90.0f); 90.0f);
// Initially less than 0.01% of the image should be the focus ring's highlight SkColor highlight_color = AccessibilityFocusHighlight::default_color_;
// Initially less than 0.05% of the image should be the focus ring's highlight
// color. // color.
SkColor highlight_color = ASSERT_LT(CountPercentPixelsWithColor(image, highlight_color), 0.05f);
AccessibilityFocusHighlight::GetHighlightColorForTesting();
ASSERT_LT(CountPercentPixelsWithColor(image, highlight_color), 0.01f);
// Focus something. // Focus something.
content::WebContents* web_contents = content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents(); browser()->tab_strip_model()->GetActiveWebContents();
std::string script("document.getElementById('textfield').focus();"); std::string script("document.getElementById('div').focus();");
EXPECT_TRUE(content::ExecuteScript(web_contents, script)); EXPECT_TRUE(content::ExecuteScript(web_contents, script));
// Now wait until at least 0.1% of the image has the focus ring's highlight // Now wait until at least 0.1% of the image has the focus ring's highlight
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment