This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.

Bug 23475 - Web focus model refactoring
Summary: Web focus model refactoring
Status: RESOLVED FIXED
Alias: None
Product: WHATWG
Classification: Unclassified
Component: HTML (show other bugs)
Version: unspecified
Hardware: Other other
: P3 normal
Target Milestone: Unsorted
Assignee: Ian 'Hixie' Hickson
QA Contact: contributor
URL:
Whiteboard:
Keywords:
Depends on: 23366
Blocks: 23382 23405
  Show dependency treegraph
 
Reported: 2013-10-09 21:26 UTC by Ian 'Hixie' Hickson
Modified: 2014-02-19 22:55 UTC (History)
6 users (show)

See Also:


Attachments

Description Ian 'Hixie' Hickson 2013-10-09 21:26:23 UTC

    
Comment 1 Ian 'Hixie' Hickson 2013-10-11 22:49:02 UTC
I think it would make sense that if no focusable element is focused, the key events should go to the element with "window focus", too.

Probably need a way to change what element has window focus, too, in the case of open non-modal <dialog> elements.
Comment 2 Ian 'Hixie' Hickson 2013-12-10 19:11:13 UTC
Proposal:

"focusable": an object that matches one of the following descriptions.
Each focusable has an owner Node, described in the following
descriptions as well, and a manager, described below.

   - an element with the tabindex focus flag set, if it is not
     disabled, and is either being rendered, or is the descendant of a
     canvas element and the nearest such ancestor element represents
     embedded content and is itself being rendered; its owner is
     itself

   - an <area>'s shape in an image map associated with an <img>
     element that is being rendered; its owner is the <img> element

   - a user-agent-provided subwidget of an element (e.g. <video> play
     button) that is not disabled and is being rendered; its owner is
     the element in question

   - a scrollable region; its owner is the element for which the box
     that the scrollable region scrolls was created

   - a viewport; its owner is the Document associated with the
     viewport in question

"manager": an object that manages focusables. An object is a manager
if it is one of the following:

   - a Document object in a browsing context

   - a <dialog> object that is open="" and is being rendered

The manager of a focusable is the owner, if the owner is a manager, or
else the nearest ancestor of the owner that is a manager.

"focus cluster": a group of one or more focusables having the same
manager, of which at any time one is selected. All focusables with the
same manager are in the same cluster. The cluster is said to be the
manager's cluster.

"focus": the selected focusable in a focus cluster.

"superior": for an element that is a manager, the nearest ancestor
manager of the manager element. For Documents in child browsing
contexts, the browsing context container of the Document's browsing
context. Documents of top-level browsing contexts do not have
superiors. A superior is either a manager or a focusable (or both).

Managers all have a "subordinate focusables list", which is the list
of focusables in that manager's focus cluster. There is always exactly
one focusable in each subordinate focusables list (i.e. in the focus
cluster) that is selected, unless the list is empty. The selected
focusable is the one with the "focus".

Documents, <dialog> elements, and browsing context containers all have
a "subordinate managers list", which is a list of managers that have
this object as their superior. At any particular time, one manager in
the subordinate managers list is the selected manager. If the
subordinate managers list is empty, or the object has a subordinate
focusables list and it is not empty, then there can also be no
selected manager.

When a key event is to be routed in a top-level browsing centext, the
user agent must run the follow steps:

 1. Let /candidate/ be the Document of the top-level browsing context.

 2. If /candidate/ has a subordinate managers list and it has a
 selected manager, then let /candidate/ be the selected manager, and
 redo this step.

 Otherwise, if /candidate/ has a subordinate focusables list and it
 has a selected focusable that isn't /candidate/ itself, then let
 /candidate/ be that selected focusable, and redo this step.

 Note: the only way an element found in the subordinate focusables
 list can recurse into a subordinate managers list is via browsing
 context containers (like iframe)s, which cross Document boundaries.

 3. Fire an appropriate cancelable key event at /candidate/'s owner.

 4. If the event was not canceled, then have /candidate/ handle the
 key event. This might include running synthetic click activation
 steps.

The focusing steps for normal elements are like now, except they just
change the selected focusable of the focusable's manager's subordinate
focusables list, they don't necessarily make the element be itself
focused. For dialogs, though, they additionally (regardless of whether
the element is focusable) set the <dialog> as the superior's
subordinate focusables list's selected manager, sending a 'blur' event
to the previous selected manager if it was a dialog and isn't the same
dialog.

Unfocusing steps focus the viewport if it's in the focusable's
manager's subordinate focusables list, otherwise they leave the focus
as is and, if not called by the focusing steps, don't fire a blur
event.


Legacy APIs:

   document.activeElement:
     1. Let /candidate/ be the Document.
     2. If /candidate/ has a subordinate managers list and it has a
        selected manager, then let /candidate/ be that manager and
        redo this step.
     3. If /candidate/ has a subordinate focusables list and it has a
        selected focusable, then let /candidate/ be that selected
        focusable's owner. (We don't recurse, since that would mean
        crossing Document boundaries.)
     4. If /candidate/ is a Document, then let /candidate/ be the
        Document's body element, if it has one, or else null.
     5. Return /candidate/.

New APIs:

   document.focusedDialog:  subordinate managers list's selected
                            manager (a <dialog>) or null
   dialog.focusedDialog:    subordinate managers list's selected
                            manager (a <dialog>) or null
   document.focusedControl: subordinate focusables list's selected
                            focusable's owner or null
   dialog.focusedControl:   subordinate focusables list's selected
                            focusable's owner or null
   element.focusOwner:      returns the element's manager, if
                            focusable, or else null
   dialog.parentDialog:     returns the element's superior, if it's a
                            dialog, or else null

Events:

   focus, blur: fired by focusing an unfocusing steps as modified
   above. (Note that in the case of <dialog tabindex="...">, this
   means it can be ambiguous as to why you got a 'blur' event.)

Conformance:

   It's not critical to the proposal, but it's probably best to make
   tabindex="" non-conforming on <dialog> unless there's a compelling
   use case.

CSS:

   :focus matches the element returned by this algorithm (same as
   activeElement but without the final step to convert Document to
   body):
     1. Let /candidate/ be the Document.
     2. If /candidate/ has a subordinate managers list and it has a
        selected manager, then let /candidate/ be that manager and
        redo this step.
     3. If /candidate/ has a subordinate focusables list and it has a
        selected focusable, then let /candidate/ be that selected
        focusable's owner. (We don't recurse, since that would mean
        crossing Document boundaries.)
     4. Return /candidate/.

   :focus also matches any <dialog> that's the selected manager of its
   superior's subordinate managers list.

   :background-focus matches any element that is the selected manager
   of a subordinate managers list or the owner of a selected focusable
   in a subordinate focusables list, but that is not the element to
   which events would be routed in this top-level browsing context,
   or, that is in a top-level browsing context that itself does not
   have system focus.

   (Elements can match :focus:background-focus in some cases. We need
   :background-focus to handle controls outside of <dialog>s when
   there's a <dialog> that's the selected manager of the Document, at
   a minimum, but we can also use it here to do background styles when
   the whole window isn't focused, which we currently can't do.)
Comment 3 Ian 'Hixie' Hickson 2013-12-10 22:16:17 UTC
Oh, also, the routing algorithm and the focusing steps should avoid nodes that are inert. And we probably need to define algorithms for getting the next and previous nodes for tab order, which should also skip inert (that would be a good place to implement bug 23960).
Comment 4 Ian 'Hixie' Hickson 2013-12-11 21:33:52 UTC
Should probably also make "canceling dialogs" be something you can do to whichever dialog is the manager of the focusable to which events are currently being routed.
Comment 5 Hayato Ito 2013-12-12 05:17:47 UTC
Some of the concepts in the proposal sound similar to Shadow DOM's one:

- http://w3c.github.io/webcomponents/spec/shadow/#focus-navigation
- http://w3c.github.io/webcomponents/spec/shadow/#retargeting-focus-events
- http://w3c.github.io/webcomponents/spec/shadow/#active-element

There are some similarities:

- An owner / manager <---->  Shadow host.
- Focus cluster <---->  Focusable elements in a shadow tree.
- A subordinate fucusables list <----> shadow tree navigation order


I think both have to treat focusables as they are in a *scope*.
In Shadow DOM, each node tree, a document tree or a shadow tree, acts like a scope for fucusable elements.
Comment 6 Ian 'Hixie' Hickson 2013-12-12 17:22:37 UTC
Can you elaborate on that? I'm not sure I follow.

If you're talking about the impact on tab order, then that's somewhat orthogonal, in theory. See bug 23960 for tabindex scoping.
Comment 7 Matt Falkenhagen 2013-12-17 07:55:55 UTC
Sorry for the delay. I'm trying to understand the proposal. Can you walk through what happens if a modal dialog opens and a button in the document had focus.

Let's say we have:
<body>
  <button id=a></button>
  <button id=b></button>
  <dialog>
    <button id=c></button>
  </dialog>
</body>

And let's say button #b has focus and the dialog is not yet open. So the document's selected focusable is #b and it has no selected manager.

Then the modal dialog opens. We run the focusing steps for #c to get focus. Am I correct that after this, then:
document's selected focusable: #b
document's selected manager: dialog
dialog's selected focusable: #c
dialog's selected manager: none

I think we want this so when the modal closes, #b has focus. But did #b get a blur event? What if instead it's a non-modal dialog and the user clicks on an input field in it? In the current implementation, #b gets a blur event.

At a higher level, I wonder if the benefits of this proposal are worth the additional API and implementation complexity. I know it was argued extensively in bug 23666, but maybe some concrete use cases would help make clear what we gain here over a simpler solution like just making the <dialog> focusable. To me, the big benefits of adding window focus are:
1) We get better behavior when you close a dialog. Focus automatically returns to where it was when the dialog was opened.
2) Tabbing through controls in a document won't result in element focus on a dialog itself, which would (normally) be useless and weird since the dialog doesn't handle key events.
Comment 8 Hayato Ito 2013-12-17 08:16:26 UTC
(In reply to Ian 'Hixie' Hickson from comment #6)
> Can you elaborate on that? I'm not sure I follow.
> 
> If you're talking about the impact on tab order, then that's somewhat
> orthogonal, in theory. See bug 23960 for tabindex scoping.

I chatted with Matt. Yeah, this might be somewhat orthogonal.

Shadow DOM spec is trying to resolve sequential focus navigation order for nested Web Components and (retargeted) document.activeElement.

Looks like the proposal is trying to resolve focus issues which is related to only dialogs, thought I am not sure I understand fully.
Comment 9 Ian 'Hixie' Hickson 2014-01-03 22:26:14 UTC
(In reply to Matt Falkenhagen from comment #7)
> 
> Let's say we have:
> <body>
>   <button id=a></button>
>   <button id=b></button>
>   <dialog>
>     <button id=c></button>
>   </dialog>
> </body>
> 
> And let's say button #b has focus and the dialog is not yet open. So the
> document's selected focusable is #b and it has no selected manager.
> 
> Then the modal dialog opens. We run the focusing steps for #c to get focus.
> Am I correct that after this, then:
> document's selected focusable: #b
> document's selected manager: dialog
> dialog's selected focusable: #c
> dialog's selected manager: none
> 
> I think we want this so when the modal closes, #b has focus.

All right so far, I believe.


> But did #b get a blur event?

Not as proposed above. Should it? It's still focused, it's just that it's in a window that's no longer active. Do form fields get onblur when you switch tabs?


> What if instead it's a non-modal dialog and the user clicks on
> an input field in it?

I don't think the proposal above is affected by whether things are modal or not. #b wouldn't be blurred.


> In the current implementation, #b gets a blur event.

Right, because it loses focus. But in the proposal here it does not, relative to the elements in the same "window".


> At a higher level, I wonder if the benefits of this proposal are worth the
> additional API and implementation complexity.

Well the alternative is that the UI for subwindows in HTML just doesn't make any sense, e.g. tabbing between dialogs, windows not being focusable, etc. It seems pretty critical to me. Every platform has this. Not having it would be really weird.


(In reply to Hayato Ito from comment #8)
> Looks like the proposal is trying to resolve focus issues which is related
> to only dialogs, thought I am not sure I understand fully.

Yes, the goal here is to refactor the focusing mechanism to work for dialogs. (As a side-effect it also cleans up the focusing rules to handle CSS scrollable regions, which were previously undefined.)
Comment 10 Matt Falkenhagen 2014-01-07 11:31:02 UTC
(In reply to Ian 'Hixie' Hickson from comment #9)
> (In reply to Matt Falkenhagen from comment #7)
> > But did #b get a blur event?
> 
> Not as proposed above. Should it?

Not sure. Currently elements also get blur events when you change focus across iframes. This proposal would also change that. I don't know if the change would cause sites to break.

Some other unorganized thoughts:
- Do we want showModal() to always run autofocus steps? Or does it retain dialog.focusedControl if non-null?
- Should tab navigate across non-modal dialogs? I'm not sure what other platforms do.
- Would a non-modal get window focus on show()? It may be good for screen readers. On the other hand, it would be bad for something like suggestions that pop-up while you type or tooltips.
- Should dialog.focus() give the dialog window focus? (related to the above point, e.g., maybe you want to ensure a non-modal dialog is read by screen readers.)

> (In reply to Hayato Ito from comment #8)
> > Looks like the proposal is trying to resolve focus issues which is related
> > to only dialogs, thought I am not sure I understand fully.
> 
> Yes, the goal here is to refactor the focusing mechanism to work for
> dialogs. (As a side-effect it also cleans up the focusing rules to handle
> CSS scrollable regions, which were previously undefined.)

We should think about what the new API does across Shadow DOM boundaries. Do we need to do adjusting like with document.activeElement? For example, suppose the element with "real" focus (i.e., it gets key events) is in a dialog inside Shadow DOM. document.focusedDialog can't be the dialog, since it would break encapsulation. Should document.focusedDialog be the shadow host (which isn't a dialog)?
Comment 11 Ian 'Hixie' Hickson 2014-01-08 19:32:02 UTC
(In reply to Matt Falkenhagen from comment #10)
> (In reply to Ian 'Hixie' Hickson from comment #9)
> > (In reply to Matt Falkenhagen from comment #7)
> > > But did #b get a blur event?
> > 
> > Not as proposed above. Should it?
> 
> Not sure. Currently elements also get blur events when you change focus
> across iframes. This proposal would also change that. I don't know if the
> change would cause sites to break.

Hm, interesting. I shouldn't break that. I'll have to make the iframe handling more backwards-compatible.


> Some other unorganized thoughts:
> - Do we want showModal() to always run autofocus steps? Or does it retain
> dialog.focusedControl if non-null?

If you're reshowing the dialog, it should reset focus, as if it was a new instance.


> - Should tab navigate across non-modal dialogs? I'm not sure what other
> platforms do.

I'm not aware of any platform that tab-navigates across non-modal windows.


> - Would a non-modal get window focus on show()? It may be good for screen
> readers. On the other hand, it would be bad for something like suggestions
> that pop-up while you type or tooltips.

<dialog> isn't for suggestions popups; if we want to support those, we'll need a different mechanism than <dialog>.


> - Should dialog.focus() give the dialog window focus? (related to the above
> point, e.g., maybe you want to ensure a non-modal dialog is read by screen
> readers.)

The proposal above does. Otherwise, we'd need some new API to do that, which seems suboptimal.


> We should think about what the new API does across Shadow DOM boundaries. Do
> we need to do adjusting like with document.activeElement? For example,
> suppose the element with "real" focus (i.e., it gets key events) is in a
> dialog inside Shadow DOM. document.focusedDialog can't be the dialog, since
> it would break encapsulation. Should document.focusedDialog be the shadow
> host (which isn't a dialog)?

I think in that situation document.focusedDialog should probbaly be an opaque object that represents "focus is somewhere you can't get access to".
Comment 12 Ian 'Hixie' Hickson 2014-01-08 21:21:17 UTC
Looks like Window objects in iframes get 'blur' and 'focus' events also, need to make sure that's handled.
Comment 13 Matt Falkenhagen 2014-01-09 03:42:38 UTC
(In reply to Ian 'Hixie' Hickson from comment #11)
> (In reply to Matt Falkenhagen from comment #10)
> > - Would a non-modal get window focus on show()? It may be good for screen
> > readers. On the other hand, it would be bad for something like suggestions
> > that pop-up while you type or tooltips.
> 
> <dialog> isn't for suggestions popups; if we want to support those, we'll
> need a different mechanism than <dialog>.

What about tooltips? If you're in the middle of composing something in
a text area, and then mouse over something else that pops up a tooltip,
you probably don't want to lose focus.
Comment 14 Ian 'Hixie' Hickson 2014-01-09 18:53:20 UTC
Yeah, a tooltip isn't a <dialog> either. Those use cases seem interesting, though; if we want to support them, let's file a separate bug for them. It may be that <aside> is actually sufficient for tooltips, at the markup level, and that we just need some style (e.g. a 'display' type) to mean "show this as a tooltip. Maybe something similar for autocomplete drop-downs. (We've got something for popup menus already, but maybe something for custom popup menus also.)

I seem to recall we were looking at that with Web Controls 1.0 and XBL2, though I forget how far we got with it. Those are dead now, anyway.
Comment 15 Ian 'Hixie' Hickson 2014-01-14 22:32:40 UTC
BTW, the story with 'focus' and 'blur' events isn't clear in existing browsers. See this test, for example:

   http://software.hixie.ch/utilities/js/live-dom-viewer/saved/2743

Firefox, Safari, and Chrome all do different things.

I'm trying to spec something sane that's more similar to Firefox here.
Comment 16 Ian 'Hixie' Hickson 2014-01-15 00:38:39 UTC
Ok, new proposal:

-----8<--------------------------------------------------------------
At any particular time, keyboard input events are routed to a
specific part of the interface. That part of the interface is said
to be focused. This section describes how this process works.

focusable control: an object that, in some circumstances, might
receive user focus. This could be an element or another region.

Each focusable control has an anchor. An anchor is a Node that
represents the control in the DOM for the purposes of focus
management.

These are the possible focusable controls and their anchors:

   - an element with the tabindex focus flag set, if it is not
     disabled, and is either being rendered, or is the descendant of
     a canvas element and the nearest such ancestor element
     represents embedded content and is itself being rendered; its
     anchor is itself.

      Example: <iframe>, <input type=text>, sometimes <a href=>

   - an <area>'s shape in an image map associated with an <img>
     element that is being rendered; its anchor is the <img>
     element.

   - a user-agent-provided subwidget of an element (e.g. <video>
     play button) that is not disabled and is being rendered; its
     anchor is the element in question.

   - a scrollable region that is being rendered; its anchor is the
     element for which the box that the scrollable region scrolls
     was created.

   - a viewport that is being rendered; its anchor is the Document
     associated with the viewport in question.

Each focusable control belongs to a control group. Each control
group has an owner, which is an owner object. The following are
owner objects:

   - Document objects in browsing contexts

   - <dialog> objects that are open="" and are being rendered

Each owner object owns one control group.

If a focusable control's anchor is an owner object, then that
control belongs to that owner object's control group.

 Examples: A viewport's control group's owner is the viewport's
 Document (the viewport's anchor).

Otherwise, it is the anchor's nearest ancestor owner object.

 Examples: an <input> element's owner is the nearest ancestor
 <dialog> or Document; an <area>'s shape's owner is the <img>'s
 nearest ancestor <dialog> or Document.

At any one time, one control in each non-empty control group is
selected. If a control group is empty, then the control group has no
selected control. When a focusable control is added to an empty
control group, it becomes the selected control.

 Example: When a Document is first rendered, the first object to be
 added to the control group would be the viewport, and thus the
 viewport gets the initial focus.

<dialog> elements that are owner objects belong to a dialog group,
which is itself managed by an owner object. A <dialog> element's
dialog group's manager is the <dialog> element's nearest ancestor
owner object (either another <dialog>, or the Document).

If a dialog group's manager's control group is empty, then one
<dialog> in that dialog group is designated the selected <dialog> of
that group.

If a dialog group's manager's control group is not empty, then one
<dialog> in that dialog group can be designated the selected
<dialog> of that group, but that position can also be left
unclaimed.


The currently focused owner object of a top-level browsing context
at any particular time is the control returned by this algorithm:

 1. Let /candidate/ be the Document of the top-level browsing
 context.

 2. If /candidate/ has a dialog group, and that dialog group has a
 selected <dialog>, then let /candidate/ be the selected <dialog>,
 and redo this step.

 Otherwise, if /candidate/ has a non-empty control group, and that
 control group's selected control is a browsing context container,
 then let /candidate/ be the active document of that browsing
 context container's nested browsing context, and redo this step.

 3. Return /candidate/.


The currently focused control of a top-level browsing context at any
particular time is the control returned by this algorithm:

 1. Let /candidate/ be the currently focused owner object of the
 top-level browsing context.

 2. If /candidate/ has a non-empty control group, then let
 /candidate/ be that selected control.

 3. Return /candidate/.


When a key event is to be routed in a top-level browsing context, the
user agent must run the follow steps:

 1. Let /candidate/ be the currently focused control of the
 top-level browsing context.

 2. Fire an appropriate cancelable key event at /candidate/'s
 anchor.

 3. If the event was not canceled, then have /candidate/ handle the
 key event. This might include running synthetic click activation
 steps.


The currently focused node at any particular time is the currently
focused control's anchor.

The currently focused browsing context of a top-level browsing
context at any particular time is the browsing context of the
Document object of the currently focused node.


The focusing steps for an element /candidate/ are as follows:

 1. same as current step 1, but "if the element is not focusable"
    becomes "...a focusable control or an owner object", "if the
    element is already focused" becomes "...is the currently focused
    control", and we add "is not inert" as a condition that aborts.

 2. If /candidate/ is a <dialog> element that is not a focusable
    control, but does have a dialog group, and that dialog group has
    a selected <dialog>, then let /candidate/ be that selected
    <dialog>, and redo this step.

    If /candidate/ is a <dialog> element that is not a focusable
    control, and its control group is not empty, then let
    /candidate/ be the selected control in /candidate/'s control
    group, and redo this step.

    Note: an element can be both a <dialog> element and a focusable
    control, if it has both an open="" attribute and a tabindex=""
    attribute and is being rendered.

 3. If /candidate/ is a browsing context container, then let
    /candidate/ be the nested browsing context's viewport.

 4. Let /old chain/ be the focus chain of the currently focused
    control.

 5. Let /new chain/ be the focus chain of /candidate/.

 6. If the first entry in /old chain/ and the first entry in /new
    chain/ are the same, pop the first entry in /old chain/ and the
    first entry in /new chain/ and redo this step.

 7. For each entry /entry/ in /old chain/, in reverse order, run
    these substeps:

     1. [unfocusing steps current step 1, for /entry/]

     2. Let /target/ be null.

     3. If /entry/ is an element: let /target/ be /entry/.

        If /entry/ is a Document object: let /target/ be the
        Document object's Window object.

     4. If /target/ is not null: fire a 'focus' event at /target/.

        Note: If /entry/ is an <area>'s shape, a scrollable region,
        or a viewport, no event is fired.

 8. Apply any relevant platform-specific conventions for focusing
    /candidate/. (For example, some platforms select the contents of
    a text field when that field is focused.)

 9. For each entry /entry/ in /new chain/, in order, run these
    substeps:

     1. If /entry/ is a <dialog> element: Let the <dialog> element
        be the selected dialog of its dialog group.

     2. If /entry/ is a focusable control: Let the control be the
        selected control of its control group. If it's control
        group's owner is also the manager of a dialog group, then
        let there be no selected dialogs in that dialog group.

        Note: /entry/ can be both a <dialog> element and a focusable
        control. When it's a focusable control, it is its own owner.

     3. Let /target/ be null.

     4. If /entry/ is an element: let /target/ be /entry/.

        If /entry/ is a Document object: let /target/ be the
        Document object's Window object.

     5. If /target/ is not null: fire a 'focus' event at /target/.

        Note: If /entry/ is an <area>'s shape, a user-agent-provided
        subwidget of an element, a scrollable region, or a viewport,
        no event is fired.

The /unfocusing steps/ are changed to be a call to the focusing
steps to move focus to the viewport, if the control group's owner is
the Document, or else to the next control in the dialog. (Probably
also needs to go through every control group and select the first
thing in it if it's no longer got a selection, or something.)

Making the currently focused control inert should also call the
unfocusing steps.


The focus chain of a focusable control or <dialog> is constructed as
follows:

  1. Let /candidate/ be the focusable control or <dialog>.

  2. Let /output/ be an empty list.

  3. Loop: Append /candidate/ to /output/.

  4. If /candidate/ is an <area>'s shape, then append the <area>
     element to /output/.

     Otherwise, if /candidate/ is a focusable control whose anchor
     is an element that is not the same object as /candidate/, then
     append that element to /output/.

  5. Otherwise, if /candidate/ is a <dialog>, let /candidate/ be its
     dialog group's manager, and return to the step labeled loop.

     Otherwise, if /candidate/ is a focusable control, let
     /candidate/ be the focusable control's control group's owner,
     and return to the step labeled loop.

     Otherwise, if /candidate/ is a Document in a nested browsing
     context, let /candidate/ be the browsing context container, and
     return to the step labeled loop.

   6. Reverse /output/.

   7. Return /output/.


HTMLElement.focus() should handle <area> shapes itself, by calling
the focusing steps with the first such shape instead of the element.

Window.focus() on Window should call the focusing steps for the
Document's viewport.


Legacy APIs:

   document.activeElement:
     1. Let /candidate/ be the Document.
     2. If /candidate/ manages a dialog group and it has a selected
        dialog, then let /candidate/ be that selected dialog and
        redo this step.
     3. If /candidate/ has a non-empty control group and it has a
        selected control, then let /candidate/ be that selected
        control's anchor. (We don't recurse, since that would mean
        crossing Document boundaries.)
     4. If /candidate/ is a Document, then let /candidate/ be the
        Document's body element, if it has one, or else null.
     5. Return /candidate/.

New APIs:

   document.focusedDialog:  dialog group's selected dialog or null
   dialog.focusedDialog:    dialog group's selected dialog or null
   document.focusedControl: control group's selected control or null
   dialog.focusedControl:   control group's selected control or null
   element.focusOwner:      focusable control's owner or null
   dialog.parentDialog:     dialog group's manager or null

Conformance:

   It's not critical to the proposal, but it's probably best to make
   tabindex= non-conforming on <dialog> unless there's a compelling
   use case.

CSS:

   Within a top-level browsing context, :focus matches the elements
   returned by this algorithm:

     0. If the top-level browsing context isn't focused, return the
        empty list.

     1. Let /candidate/ be the currently focused control.

     2. Let /output/ be an empty list.

     3. If /candidate/ is an <area>'s shape, then append the <area>
        element to /output/.

        Otherwise, if /candidate/ is a focusable control whose anchor
        is an element that is not the same object as /candidate/, then
        append that element to /output/.

     5. If /candidate/ is a <dialog>, append /candidate/ to
        /output/, let /candidate/ be its dialog group's manager, and
        redo this step.

        Otherwise, if /candidate/ is a focusable control, let
        /candidate/ be the focusable control's control group's
        owner, and redo this step.

        Otherwise, if /candidate/ is a Document in a nested browsing
        context, let /candidate/ be the browsing context container,
        and redo this step.

     6. Remove any inert nodes from /output/.

     7. Return /output/.

   :background-focus matches any non-inert element that is the
   selected dialog of a dialog group or the anchor of a selected
   control in a control group, except if the element matches :focus.

Finally, we'd also define an algorithm for selecting the
next/previous control in tab order. (see also bug 23960)
-----8<--------------------------------------------------------------
Comment 17 Ian 'Hixie' Hickson 2014-01-15 22:45:00 UTC
Re: the tooltip thing, I propose to make show() focus the <dialog>, but to make setting the open="" attribute manually not do that. Or is that too obscure? Maybe d.show(); d.focus(); isn't too much to ask?
Comment 18 Ian 'Hixie' Hickson 2014-02-03 19:37:09 UTC
heycam in whatwg proposed changing this part of activeElement:

     4. If /candidate/ is a Document, then let /candidate/ be the
        Document's body element, if it has one, or else null.

...to:

     4. If /candidate/ is a Document, then let /candidate/ be the
        Document's body element, if it has one, or else the root element, if
        there is one, or else null.

...which seems fine to me (it's still basically a lie, in that it's not the root element that has focus, and it makes the case indistinguishable from the case where the root element _does_ have focus (e.g. through tabindex="", or if the root element is a link or something), but those are rare cases so it's probably ok).
Comment 19 James Craig 2014-02-03 19:44:29 UTC
(In reply to Ian 'Hixie' Hickson from comment #17)
> Re: the tooltip thing, I propose to make show() focus the <dialog>, but to
> make setting the open="" attribute manually not do that. Or is that too
> obscure? Maybe d.show(); d.focus(); isn't too much to ask?

History has shown that authors don't/won't know to do this, so it should be implicit. It's fine to allow an explicit override, but the author should not be required to call d.focus() in the default case.
Comment 20 Ian 'Hixie' Hickson 2014-02-04 20:03:02 UTC
Maybe a third way of opening a dialog? showWithoutFocus() ?
Comment 21 Ian 'Hixie' Hickson 2014-02-10 22:07:44 UTC
#include comment 16, comment 20.

Things to look for in the rewrite:
  "gains focus"
  "loses focus"
  "gaining focus"
  "losing focus"
  "is focused"
  "is not focused"
  "can still focus"
  autofocus attribute section
  "focusable"
  "focusing steps"
  draw*FocusRing()
  :focus
  Focus section

I think instead of "focusable control" I should use "focusable area".
Comment 22 Ian 'Hixie' Hickson 2014-02-19 02:05:07 UTC
Ok, I've done comment 16 and comment 18. Comment 20 is now bug 24718. I didn't add the new APIs, those are now bug 24720. Sequential focus navigation is now bug 24719. Conformance requirement on <dialog tabindex> is now bug 24714.

I still have to do comment 21.

I also need to consider esprehn's feedback regarding making the focus algorithms use a more generic way to decide what's an owner, rather than <dialog open>.
Comment 23 contributor 2014-02-19 22:39:21 UTC
Checked in as WHATWG revision r8478.
Check-in comment: Revamp how focus is defined, to actually handle things we've ignored before, like viewports and scrollable regions
http://html5.org/tools/web-apps-tracker?from=8477&to=8478
Comment 24 Ian 'Hixie' Hickson 2014-02-19 22:55:44 UTC
Ok, I went through comment 21. I'm marking this fixed. If you see problems, please file bugs!

I haven't added a generic way to make an element into a dialog-like focus-capturing things yet. I need more concrete use cases to determine what the best API to do that is. If you have any, please file a bug.