Closed Bug 115462 Opened 23 years ago Closed 3 years ago

Widgets should use out-of-band state for NS_THEMEing and pseudo-class styling, not attributes

Categories

(Core :: DOM: Core & HTML, defect, P5)

defect

Tracking

()

RESOLVED WONTFIX

People

(Reporter: ian, Unassigned)

References

Details

(Whiteboard: [Hixie-P5] [Hixie-CSSUI2])

(edited from bug 112980:)

I have issues with the isDisabled, isChecked and isSelected functions in
nsNativeThemeWin.cpp.

Using attributes directly like this IMHO is not going to work on the long term.

I think what we should do is bite the bullet and implement the pseudo-class
setting stuff that we've been talking about. Make the XBL for the widgets
toggled the :checked, :indeterminate (tri-state), :disabled, :enabled, etc,
pseudo-classes, then use those in the theme API to determine status instead of
hardcoding knowledge for every widget set we want to support.

This would make it easier to style the control in CSS and would make this API a
lot more resilient when it comes to implementing other controls in XBL (e.g. a
widget set that uses japanese attribute names).

Doing this would require us to add several functions to all elements. (Well,
actually it would require us to add these functions to the view, like
getComputedStyle, but for convenience a shortcut to these functions on the
default view would be added to the elements, yada yada.)

   element.setPseudoClass('pseudo', bState)
   bState = element.getPseudoClass('pseudo')

...which know how to toggle and return the states of:

   :enabled, :disabled                                  [1]
   :checked, :indeterminate, :-moz-unchecked            [1]
   :-moz-open, :-moz-closed                             [1]
   :-moz-vertical, :-moz-horizontal                     [1]
   :focus                                               [2]
   :active                                              [3]
   :hover                                               [3]
   etc...

[1] these pseudo-classses are mutually exclusive. Something may be :-moz-open,
:-moz-closed, or neither, but not both.

[2] this may only be set on elements which take focus (see the XBL/UI2 spec to
see what takes focus). Only one element has focus at any time, although :focus
matches on any element that is a parent of the one with focus and which itself
takes focus.

[3] :active and :hover will never be set on more than one element at once,
although the pseudo-classes match all that elements' parents.

I'm unsure how to deal with progress bars, track bars and scrollbars. I really
would like them to use a "value" property on the element, rather than an
attribute. But that means the state of a checkbox depends on the view, and the
state of a progress bar depends on the element, which is inconsistent and thus
bad. Should the :checked/:indeterminate/:-moz-unchecked pseudos be a special
case that cannot be changed directly by the "setPseudoClass" API?

If we use that convention, that places the pseudo-classes in the following
categories:

   :root, :first-child, and the other structural pseudos -- pseudos that are
   intrinsic based on the DOM core.

   :-moz-open, :-moz-closed, :-moz-vertical, :-moz-horizontal -- per element
   togglable pseudos.

   :enabled, :disabled, :checked, :indeterminate, :-moz-unchecked, :link,
   :visited, -moz-any-link -- pseudos that are intrinsic based on other DOM
   properties and/or attributes of the element. (Should :enabled and :disabled
   be in this list or the previous one?)

   :focus, :active, :hover -- per document togglable pseudos.

The problem with this though is that it means we have to explicitly define magic
mappings between (a) the :checked/:indeterminate/:-moz-unchecked pseudos and the
relevant DOM properties, and (b) the value of progress bars, track bars and
scrollbars and some DOM property. (The mapping from the DOM to :link/:visited/
:-moz-any-link is a complicated and already implemented one, so let's ignore
that here.)

Any better ideas for how to handle boolean, tri-state and unbounded values? Some
other API?
*** Bug 115351 has been marked as a duplicate of this bug. ***
Status: NEW → ASSIGNED
Target Milestone: --- → mozilla0.9.8
How about (on the ElementUI interface):

  /* STATE */
  const unsigned short      STATE_GROUP_ENABLED            = 0;
  const unsigned short      STATE_GROUP_CHECKED            = 1;
  const unsigned short      STATE_GROUP_SELECTED           = 2;
  const unsigned short      STATE_GROUP_VALUE              = 3;
  
  const unsigned short      STATE_ENABLED_DISABLED         = 0; // false
  const unsigned short      STATE_ENABLED_ENABLED          = 1; // true

  const unsigned short      STATE_CHECKED_UNCHECKED        = 0; // false
  const unsigned short      STATE_CHECKED_CHECKED          = 1; // true
  const unsigned short      STATE_CHECKED_INDETERMINATE    = 2;
  
  const unsigned short      STATE_SELECTED_UNSELECTED      = 0; // false
  const unsigned short      STATE_SELECTED_SELECTED        = 1; // true

  void setMetadataState(in unsigned short group, in long value);
  long getMetadataState(in unsigned short group);


  /* DYNAMIC */
  const unsigned short      DYNAMIC_ACTIVE                 = 0;
  const unsigned short      DYNAMIC_HOVER                  = 1;
  const unsigned short      DYNAMIC_OPEN                   = 2;

  void setDynamicState(in unsighed short pseudo, in bool value);
  bool getDynamicState(in unsigned short group);


  /* FOCUS */
  const unsigned short      FOCUSSED_BY_UNKNOWN            = 0;
  const unsigned short      FOCUSSED_BY_KEYBOARD           = 1;
  const unsigned short      FOCUSSED_BY_POINTER            = 2;

  void focus();
  void blur();
  void focusByMethod(in unsigned short method);


Where the constants are defined as:

 STATE_GROUP_ENABLED
 STATE_ENABLED_DISABLED
 STATE_ENABLED_ENABLED
  These constants are used to enable and disable user interface
  elements. Every element is either STATE_ENABLED_ENABLED or
  STATE_ENABLED_DISABLED. See the :enabled and :disabled
  pseudo-classes. Elements all initially start with their
  STATE_GROUP_ENABLED state set to STATE_ENABLED_ENABLED.

 STATE_GROUP_CHECKED
 STATE_CHECKED_UNCHECKED
 STATE_CHECKED_CHECKED
 STATE_CHECKED_INDETERMINATE
  These constants are used to toggle the value of check box and radio
  button interface elements. Every element has one of these values.
  See the :checked, :unchecked and :indeterminate pseudo-classes.
  Elements all initially start with their STATE_GROUP_CHECKED state
  set to STATE_ENABLED_UNCHECKED.

 STATE_GROUP_SELECTED
 STATE_SELECTED_UNSELECTED
 STATE_SELECTED_SELECTED
  These constants are used to determine the selection state of entire
  elements. (Note: This is distinct from text selection and focus.)
  Every element is either STATE_SELECTED_SELECTED or
  STATE_SELECTED_UNSELECTED. See the :selected and :unselected
  pseudo-classes Elements all initially start with their
  STATE_GROUP_SELECTED state set to
  STATE_SELECTED_UNSELECTED.

 STATE_GROUP_VALUE
  This constant is used to access the generic integer value of user
  interface elements. Every element has such a value, although it is
  only typically used by scroll bars, track bars and progress bars.
  Elements all initially start with their STATE_GROUP_VALUE state set
  to zero.

 DYNAMIC_ACTIVE
  This constant is used to toggle the active state of elements. Only
  one element per view may be active at any one time. Setting an
  element to active automatically resets any other active element to
  its non-active state. See the :active pseudo-class.

 DYNAMIC_HOVER
  This constant is used to toggle the hover state of elements. Only
  one element per view may be in the hover state at any one time.
  Setting an element's hover state to true automatically resets any
  other element's hover state. See the :hover pseudo-class. User
  agents may, in addition to changes triggered using the
  setDynamicState() method, automatically set this state on elements
  in response to user events (such as moving the mouse).

 DYNAMIC_OPEN
  This constant is used to toggle the open/closed state of elements.
  Every element has an open/closed state for each view. Toggling the
  open/closed state does not affect other elements or other views.
  (Note: At the moment there is no way to change the open/closed state
  of elements in any view other than the default view.) See the :open
  and :closed pseudo-classes.


Then the pseudos are defined as follows:

  :active
  This pseudo-class matches the element that has its DYNAMIC_ACTIVE
  state set, if any, as well as any ancestors of that element.

  :hover
  This pseudo-class matches the element that has its DYNAMIC_HOVER
  state set, if any, as well as any ancestors of that element.

  :focus
  This pseudo-class matches the element which has focus, if any, as
  well as all its ancestors with user-can-focus set to 'takes-focus'.
  See the focus() and focusByMethod() methods.

  :enabled
  This pseudo-class matches all elements that have 'user-can-focus'
  set to 'takes-focus', which have their STATE_GROUP_ENABLED state set
  to STATE_ENABLED_ENABLED, and whose ancestors (including those that
  are not focusable) all have their STATE_GROUP_ENABLED state set to
  STATE_ENABLED_ENABLED.

  :disabled
  This pseudo-class matches all elements that have 'user-can-focus'
  set to 'takes-focus' and which have either their STATE_GROUP_ENABLED
  state set to STATE_ENABLED_DISABLED or which have an ancestor whose
  STATE_GROUP_ENABLED state set to STATE_ENABLED_DISABLED (including
  ancestors that cannot be focussed).

  :checked
  This element matches all elements which have 'user-can-focus' set to
  'takes-focus' and their STATE_GROUP_CHECKED state set to
  STATE_CHECKED_CHECKED.

  :unchecked
  This element matches all elements which have 'user-can-focus' set to
  'takes-focus' and their STATE_GROUP_CHECKED state set to
  STATE_CHECKED_UNCHECKED.

  :indeterminate
  This element matches all elements which have 'user-can-focus' set to
  'takes-focus' and their STATE_GROUP_CHECKED state set to
  STATE_CHECKED_INDETERMINATE.

  :selected
  This element matches all elements which have 'user-can-focus' set to
  'takes-focus' and their STATE_GROUP_SELECTED state set to
  STATE_CHECKED_SELECTED. This pseudo-class is unrelated to the
  similarly named ::selection pseudo-element, which applies to the
  text selection.

  :unselected
  This element matches all elements which have 'user-can-focus' set to
  'takes-focus' and their STATE_GROUP_SELECTED state set to
  STATE_CHECKED_UNSELECTED.

  :open
  This element matches all elements which have their DYNAMIC_OPEN
  state set.

  :closed
  This element matches all elements which have their DYNAMIC_OPEN
  state unset.


The appearance values would then look at the various dynamic and
metadata states to determine how to draw themselves. For example, a
inner progress indicator bar could look at its elements' value of
STATE_GROUP_VALUE and use it as a percentage, and a checkbox could
decide its checked state based on the STATE_GROUP_CHECKED state's
value.

Comments? I know it feels complex, but this is a complex problem. I think in
practice it would turn out to be a lot simpler than in looks.
Incidentally I'm not at all attached to the names. I just came up with them in a
hurry. It's the concept I'm concerned about.
Blocks: 5693, 65917
Depends on: 112980
Summary: Widgets should use out-of-band state for theming and styling, not attributes → Widgets should use out-of-band state for NS_THEMEing and pseudo-class styling, not attributes
Note. Text fields aren't handled by any of this because text editing and
selection is handled in a way orthogonal to these metadata flags. You set a node
to 'user-modify: text' or 'user-modify: all' (default is 'user-modify:
real-only'), and that makes a node editable. Theme stuff should use
'user-modify' to determine the readonlyness of a field with '-moz-appearance'
set to 'text-field' like it uses the out of band data mentioned above to
determine the other states.

One suggestion hyatt gave is to add :default to match default buttons. I like
that idea. I think it should work like this:

  const unsigned short      STATE_GROUP_DEFAULT            = 5;
  const unsigned short      STATE_DEFAULT_NORMAL           = 0; // false
  const unsigned short      STATE_DEFAULT_DEFAULT          = 1; // true

 STATE_DEFAULT_NORMAL
  This constant is used to toggle the default element of a document. Only
  one element per document may be the default at any one time. Setting an
  element to STATE_DEFAULT_DEFAULT automatically resets any other element 
  to the STATE_DEFAULT_NORMAL state. See the :default pseudo-class. Elements 
  all initially start with their STATE_GROUP_DEFAULT state set to
  STATE_DEFAULT_NORMAL.

 :default
  This pseudo-class matches all elements that have their STATE_GROUP_DEFAULT 
  state set to STATE_DEFAULT_DEFAULT.

Note that I think my proposal above should be modified to not take focus into
account for most of the pseudo classes. "Focusability" is set by a property, and
pseudo-classes musn't depend on properties.
We could also do with an API for getting the current default element in the
document, and the current focused element in the view.
Note: the .cheched, .selected, and other DOM attributes would be defined as
mapping to these metadata states.
Target Milestone: mozilla0.9.8 → mozilla0.9.9
bryner, jke iser: This is how you would implement the XBL widgets without using
attributes. The C++ DOM <=> XUL DOM glue code in the XBL would simply forward
any changes of the metadata state to the XUL element, and vice versa, the HTML
element's DOM would use this metadata to return its state, and the
pseudo-classes would use this state information to determine whether or not they
apply.
Blocks: 120834
Blocks: 84400
Blocks: 117584
this looks and sounds good, is there enough time for this before moz1.0?
Target Milestone: mozilla0.9.9 → mozilla1.2
Whiteboard: [Hixie-P5] [Hixie-CSSUI2]
Keywords: mozilla1.4
Assignee: hyatt → nobody
QA Contact: jrgmorrison → xbl
Target Milestone: mozilla1.2alpha → ---
Not an XBL issue, not at all.  Not sure whether it should be in DOM or Widget.
Component: XBL → DOM
QA Contact: xbl → general
This is a mass change. Every comment has "assigned-to-new" in it.

I didn't look through the bugs, so I'm sorry if I change a bug which shouldn't be changed. But I guess these bugs are just bugs that were once assigned and people forgot to change the Status back when unassigning.
Status: ASSIGNED → NEW
https://bugzilla.mozilla.org/show_bug.cgi?id=1472046

Move all DOM bugs that haven't been updated in more than 3 years and has no one currently assigned to P5.

If you have questions, please contact :mdaly.
Priority: -- → P5
Component: DOM → DOM: Core & HTML

Some of this might happen as part of Web Components work or Open UI I suppose, but this is no longer the place.

Status: NEW → RESOLVED
Closed: 3 years ago
Resolution: --- → WONTFIX
You need to log in before you can comment on or make changes to this bug.