mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 10:44:56 +00:00
Bug 1617600 - Prototype :focus-visible behind a flag. r=smaug
The heuristic is that we show focus outlines for unknown or key focus, and not for mouse / touch. This is probably not the final heuristic we take, but this allows people to play with it and file bugs. Once this is mature enough we should remove :-moz-focusring in favor of :focus-visible. Differential Revision: https://phabricator.services.mozilla.com/D63861 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
cec1932e5b
commit
de54b68ce7
@ -872,7 +872,8 @@ nsresult nsFocusManager::ContentRemoved(Document* aDocument,
|
||||
}
|
||||
}
|
||||
|
||||
NotifyFocusStateChange(content, nullptr, shouldShowFocusRing, false);
|
||||
NotifyFocusStateChange(content, nullptr, shouldShowFocusRing, 0,
|
||||
/* aGettingFocus = */ false);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
@ -975,7 +976,7 @@ nsFocusManager::WindowHidden(mozIDOMWindowProxy* aWindow) {
|
||||
|
||||
if (oldFocusedElement && oldFocusedElement->IsInComposedDoc()) {
|
||||
NotifyFocusStateChange(oldFocusedElement, nullptr,
|
||||
mFocusedWindow->ShouldShowFocusRing(), false);
|
||||
mFocusedWindow->ShouldShowFocusRing(), 0, false);
|
||||
window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
|
||||
|
||||
if (presShell) {
|
||||
@ -1088,10 +1089,41 @@ nsFocusManager::ParentActivated(mozIDOMWindowProxy* aWindow, bool aActive) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
static bool ShouldMatchFocusVisible(const Element& aElement,
|
||||
int32_t aFocusFlags) {
|
||||
if (StaticPrefs::browser_display_show_focus_rings()) {
|
||||
// FIXME: Spec is ambiguous about whether we should take platform
|
||||
// conventions into account. This branch does make us account for them.
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (nsFocusManager::GetFocusMoveActionCause(aFocusFlags)) {
|
||||
case InputContextAction::CAUSE_UNKNOWN:
|
||||
case InputContextAction::CAUSE_KEY:
|
||||
return true;
|
||||
case InputContextAction::CAUSE_MOUSE:
|
||||
case InputContextAction::CAUSE_TOUCH:
|
||||
case InputContextAction::CAUSE_LONGPRESS:
|
||||
break;
|
||||
case InputContextAction::CAUSE_UNKNOWN_CHROME:
|
||||
case InputContextAction::CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT:
|
||||
case InputContextAction::CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT:
|
||||
// TODO(emilio): We could return some of these though, looking at
|
||||
// UserActivation. We may want to suppress focus rings for unknown /
|
||||
// programatic focus if the user is interacting with the page but not
|
||||
// during keyboard input, or such.
|
||||
MOZ_ASSERT_UNREACHABLE(
|
||||
"These don't get returned by GetFocusMoveActionCause");
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* static */
|
||||
void nsFocusManager::NotifyFocusStateChange(nsIContent* aContent,
|
||||
nsIContent* aContentToFocus,
|
||||
bool aWindowShouldShowFocusRing,
|
||||
int32_t aFlags,
|
||||
bool aGettingFocus) {
|
||||
MOZ_ASSERT_IF(aContentToFocus, !aGettingFocus);
|
||||
if (!aContent->IsElement()) {
|
||||
@ -1104,24 +1136,29 @@ void nsFocusManager::NotifyFocusStateChange(nsIContent* aContent,
|
||||
aContent, aContentToFocus);
|
||||
}
|
||||
|
||||
EventStates eventState = NS_EVENT_STATE_FOCUS;
|
||||
if (aWindowShouldShowFocusRing) {
|
||||
eventState |= NS_EVENT_STATE_FOCUSRING;
|
||||
}
|
||||
|
||||
if (aGettingFocus) {
|
||||
aContent->AsElement()->AddStates(eventState);
|
||||
EventStates eventStateToAdd = NS_EVENT_STATE_FOCUS;
|
||||
if (aWindowShouldShowFocusRing) {
|
||||
eventStateToAdd |= NS_EVENT_STATE_FOCUSRING;
|
||||
}
|
||||
if (ShouldMatchFocusVisible(*aContent->AsElement(), aFlags)) {
|
||||
eventStateToAdd |= NS_EVENT_STATE_FOCUS_VISIBLE;
|
||||
}
|
||||
aContent->AsElement()->AddStates(eventStateToAdd);
|
||||
} else {
|
||||
aContent->AsElement()->RemoveStates(eventState);
|
||||
EventStates eventStateToRemove = NS_EVENT_STATE_FOCUS |
|
||||
NS_EVENT_STATE_FOCUSRING |
|
||||
NS_EVENT_STATE_FOCUS_VISIBLE;
|
||||
aContent->AsElement()->RemoveStates(eventStateToRemove);
|
||||
}
|
||||
|
||||
for (nsIContent* content = aContent; content && content != commonAncestor;
|
||||
content = content->GetFlattenedTreeParent()) {
|
||||
if (!content->IsElement()) {
|
||||
Element* element = Element::FromNode(content);
|
||||
if (!element) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Element* element = content->AsElement();
|
||||
if (aGettingFocus) {
|
||||
if (element->State().HasState(NS_EVENT_STATE_FOCUS_WITHIN)) {
|
||||
break;
|
||||
@ -1950,7 +1987,7 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear,
|
||||
element && element->IsInComposedDoc() && !IsNonFocusableRoot(element);
|
||||
if (element) {
|
||||
if (sendBlurEvent) {
|
||||
NotifyFocusStateChange(element, aContentToFocus, shouldShowFocusRing,
|
||||
NotifyFocusStateChange(element, aContentToFocus, shouldShowFocusRing, 0,
|
||||
false);
|
||||
}
|
||||
|
||||
@ -2231,7 +2268,7 @@ void nsFocusManager::Focus(nsPIDOMWindowOuter* aWindow, Element* aElement,
|
||||
nsPresContext* presContext = presShell->GetPresContext();
|
||||
if (sendFocusEvent) {
|
||||
NotifyFocusStateChange(aElement, nullptr, aWindow->ShouldShowFocusRing(),
|
||||
true);
|
||||
aFlags, /* aGettingFocus = */ true);
|
||||
|
||||
// if this is an object/plug-in/remote browser, focus its widget. Note
|
||||
// that we might no longer be in the same document, due to the events we
|
||||
|
@ -671,7 +671,7 @@ class nsFocusManager final : public nsIFocusManager,
|
||||
static void NotifyFocusStateChange(nsIContent* aContent,
|
||||
nsIContent* aContentToFocus,
|
||||
bool aWindowShouldShowFocusRing,
|
||||
bool aGettingFocus);
|
||||
int32_t aFlags, bool aGettingFocus);
|
||||
|
||||
void SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow);
|
||||
|
||||
|
@ -2914,8 +2914,7 @@ bool CanvasRenderingContext2D::DrawCustomFocusRing(
|
||||
return false;
|
||||
}
|
||||
|
||||
nsFocusManager* fm = nsFocusManager::GetFocusManager();
|
||||
if (fm) {
|
||||
if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
|
||||
// check that the element is focused
|
||||
if (&aElement == fm->GetFocusedElement()) {
|
||||
if (nsPIDOMWindowOuter* window = aElement.OwnerDoc()->GetWindow()) {
|
||||
|
@ -287,9 +287,10 @@ class EventStates {
|
||||
#define NS_EVENT_STATE_AUTOFILL NS_DEFINE_EVENT_STATE_MACRO(50)
|
||||
// Element is filled with preview data by Autofill feature.
|
||||
#define NS_EVENT_STATE_AUTOFILL_PREVIEW NS_DEFINE_EVENT_STATE_MACRO(51)
|
||||
|
||||
// Event state that is used for values that need to be parsed but do nothing.
|
||||
#define NS_EVENT_STATE_IGNORE NS_DEFINE_EVENT_STATE_MACRO(63)
|
||||
// Element matches the :focus-visible pseudo-class.
|
||||
//
|
||||
// TODO(emilio): We should eventually unify this and FOCUSRING.
|
||||
#define NS_EVENT_STATE_FOCUS_VISIBLE NS_DEFINE_EVENT_STATE_MACRO(52)
|
||||
|
||||
/**
|
||||
* NOTE: do not go over 63 without updating EventStates::InternalType!
|
||||
@ -324,7 +325,8 @@ class EventStates {
|
||||
REQUIRED_STATES | NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_DEFINED | \
|
||||
NS_EVENT_STATE_DRAGOVER | NS_EVENT_STATE_FOCUS | NS_EVENT_STATE_FOCUSRING | \
|
||||
NS_EVENT_STATE_FOCUS_WITHIN | NS_EVENT_STATE_FULLSCREEN | \
|
||||
NS_EVENT_STATE_HOVER | NS_EVENT_STATE_URLTARGET)
|
||||
NS_EVENT_STATE_HOVER | NS_EVENT_STATE_URLTARGET | \
|
||||
NS_EVENT_STATE_FOCUS_VISIBLE)
|
||||
|
||||
#define INTRINSIC_STATES (~EXTERNALLY_MANAGED_STATES)
|
||||
|
||||
|
@ -2172,7 +2172,8 @@ void HTMLInputElement::SetFocusState(bool aIsFocused) {
|
||||
return;
|
||||
}
|
||||
|
||||
EventStates focusStates = NS_EVENT_STATE_FOCUS | NS_EVENT_STATE_FOCUSRING;
|
||||
EventStates focusStates = NS_EVENT_STATE_FOCUS | NS_EVENT_STATE_FOCUSRING |
|
||||
NS_EVENT_STATE_FOCUS_VISIBLE;
|
||||
if (aIsFocused) {
|
||||
AddStates(focusStates);
|
||||
} else {
|
||||
|
@ -170,14 +170,8 @@ bool nsIConstraintValidation::ReportValidity() {
|
||||
}
|
||||
}
|
||||
|
||||
if (element->IsHTMLElement(nsGkAtoms::input) &&
|
||||
// We don't use nsContentUtils::IsFocusedContent here, because it doesn't
|
||||
// really do what we want for number controls: it's true for the
|
||||
// anonymous textnode inside, but not the number control itself. We can
|
||||
// use the focus state, though, because that gets synced to the number
|
||||
// control by the anonymous text control.
|
||||
element->State().HasState(NS_EVENT_STATE_FOCUS)) {
|
||||
HTMLInputElement* inputElement = HTMLInputElement::FromNode(element);
|
||||
auto* inputElement = HTMLInputElement::FromNode(element);
|
||||
if (inputElement && inputElement->State().HasState(NS_EVENT_STATE_FOCUS)) {
|
||||
inputElement->UpdateValidityUIBits(true);
|
||||
}
|
||||
|
||||
|
@ -5155,6 +5155,16 @@
|
||||
mirror: always
|
||||
rust: true
|
||||
|
||||
# Whether the `:focus-visible` pseudo-class is enabled.
|
||||
#
|
||||
# TODO: There are various ambiguities in the spec about this, so we shouldn't
|
||||
# ship this until those are sorted out.
|
||||
- name: layout.css.focus-visible.enabled
|
||||
type: RelaxedAtomicBool
|
||||
value: @IS_NIGHTLY_BUILD@
|
||||
mirror: always
|
||||
rust: true
|
||||
|
||||
# Allow <number> and <number>/<number> both for <aspect-ratio>
|
||||
# https://github.com/w3c/csswg-drafts/issues/3757
|
||||
- name: layout.css.aspect-ratio-number.enabled
|
||||
|
@ -137,6 +137,10 @@ bitflags! {
|
||||
const IN_AUTOFILL_STATE = 1 << 50;
|
||||
/// Non-standard & undocumented.
|
||||
const IN_AUTOFILL_PREVIEW_STATE = 1 << 51;
|
||||
/// :focus-visible
|
||||
///
|
||||
/// https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo
|
||||
const IN_FOCUS_VISIBLE_STATE = 1 << 52;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,7 @@ macro_rules! apply_non_ts_list {
|
||||
("enabled", Enabled, enabled, IN_ENABLED_STATE, _),
|
||||
("focus", Focus, focus, IN_FOCUS_STATE, _),
|
||||
("focus-within", FocusWithin, focusWithin, IN_FOCUS_WITHIN_STATE, _),
|
||||
("focus-visible", FocusVisible, focusVisible, IN_FOCUS_VISIBLE_STATE, _),
|
||||
("hover", Hover, hover, IN_HOVER_STATE, _),
|
||||
("-moz-drag-over", MozDragOver, mozDragOver, IN_DRAGOVER_STATE, _),
|
||||
("target", Target, target, IN_TARGET_STATE, _),
|
||||
|
@ -172,6 +172,9 @@ impl NonTSPseudoClass {
|
||||
/// Returns whether the pseudo-class is enabled in content sheets.
|
||||
#[inline]
|
||||
fn is_enabled_in_content(&self) -> bool {
|
||||
if matches!(*self, NonTSPseudoClass::FocusVisible) {
|
||||
return static_prefs::pref!("layout.css.focus-visible.enabled")
|
||||
}
|
||||
!self.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME)
|
||||
}
|
||||
|
||||
|
@ -2049,6 +2049,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
||||
NonTSPseudoClass::MozReadOnly |
|
||||
NonTSPseudoClass::MozReadWrite |
|
||||
NonTSPseudoClass::FocusWithin |
|
||||
NonTSPseudoClass::FocusVisible |
|
||||
NonTSPseudoClass::MozDragOver |
|
||||
NonTSPseudoClass::MozDevtoolsHighlighted |
|
||||
NonTSPseudoClass::MozStyleeditorTransitioning |
|
||||
|
1
testing/web-platform/meta/css/selectors/__dir__.ini
Normal file
1
testing/web-platform/meta/css/selectors/__dir__.ini
Normal file
@ -0,0 +1 @@
|
||||
prefs: [layout.css.focus-visible.enabled:true]
|
@ -1,7 +1,3 @@
|
||||
[focus-visible-005.html]
|
||||
[Programmatic focus after click should not match :focus-visible]
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
if os == "mac": FAIL
|
||||
if os == "linux": FAIL
|
||||
|
||||
expected: FAIL
|
||||
|
@ -1,4 +1,5 @@
|
||||
[focus-visible-009.html]
|
||||
[Autofocus should match :focus-visible]
|
||||
expected: FAIL
|
||||
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
bug: 1618195
|
||||
|
@ -1,4 +0,0 @@
|
||||
[focus-visible-010.html]
|
||||
[Programmatic focus on page load bshould match :focus-visible]
|
||||
expected: FAIL
|
||||
|
@ -1,4 +1,5 @@
|
||||
[focus-visible-011.html]
|
||||
[:focus-visible matches even if preventDefault() is called]
|
||||
expected: FAIL
|
||||
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
bug: 1618195
|
||||
|
@ -35,7 +35,7 @@
|
||||
assert_equals(getComputedStyle(el).outlineColor, "rgb(0, 100, 0)");
|
||||
t.done();
|
||||
}));
|
||||
}, "Programmatic focus on page load bshould match :focus-visible");
|
||||
}, "Programmatic focus on page load should match :focus-visible");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user