accessible/src/html/HTMLSelectAccessible.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "HTMLSelectAccessible.h"
     8 #include "Accessible-inl.h"
     9 #include "nsAccessibilityService.h"
    10 #include "nsAccUtils.h"
    11 #include "DocAccessible.h"
    12 #include "nsEventShell.h"
    13 #include "nsIAccessibleEvent.h"
    14 #include "nsTextEquivUtils.h"
    15 #include "Role.h"
    16 #include "States.h"
    18 #include "nsCOMPtr.h"
    19 #include "mozilla/dom/HTMLOptionElement.h"
    20 #include "nsIComboboxControlFrame.h"
    21 #include "nsIFrame.h"
    22 #include "nsIListControlFrame.h"
    24 using namespace mozilla::a11y;
    25 using namespace mozilla::dom;
    27 ////////////////////////////////////////////////////////////////////////////////
    28 // HTMLSelectListAccessible
    29 ////////////////////////////////////////////////////////////////////////////////
    31 HTMLSelectListAccessible::
    32   HTMLSelectListAccessible(nsIContent* aContent, DocAccessible* aDoc) :
    33   AccessibleWrap(aContent, aDoc)
    34 {
    35   mGenericTypes |= eListControl | eSelect;
    36 }
    38 ////////////////////////////////////////////////////////////////////////////////
    39 // HTMLSelectListAccessible: Accessible public
    41 uint64_t
    42 HTMLSelectListAccessible::NativeState()
    43 {
    44   uint64_t state = AccessibleWrap::NativeState();
    45   if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple))
    46     state |= states::MULTISELECTABLE | states::EXTSELECTABLE;
    48   return state;
    49 }
    51 role
    52 HTMLSelectListAccessible::NativeRole()
    53 {
    54   return roles::LISTBOX;
    55 }
    57 ////////////////////////////////////////////////////////////////////////////////
    58 // HTMLSelectListAccessible: SelectAccessible
    60 bool
    61 HTMLSelectListAccessible::SelectAll()
    62 {
    63   return mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple) ?
    64     AccessibleWrap::SelectAll() : false;
    65 }
    67 bool
    68 HTMLSelectListAccessible::UnselectAll()
    69 {
    70   return mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple) ?
    71     AccessibleWrap::UnselectAll() : false;
    72 }
    74 ////////////////////////////////////////////////////////////////////////////////
    75 // HTMLSelectListAccessible: Widgets
    77 bool
    78 HTMLSelectListAccessible::IsWidget() const
    79 {
    80   return true;
    81 }
    83 bool
    84 HTMLSelectListAccessible::IsActiveWidget() const
    85 {
    86   return FocusMgr()->HasDOMFocus(mContent);
    87 }
    89 bool
    90 HTMLSelectListAccessible::AreItemsOperable() const
    91 {
    92   return true;
    93 }
    95 Accessible*
    96 HTMLSelectListAccessible::CurrentItem()
    97 {
    98   nsIListControlFrame* listControlFrame = do_QueryFrame(GetFrame());
    99   if (listControlFrame) {
   100     nsCOMPtr<nsIContent> activeOptionNode = listControlFrame->GetCurrentOption();
   101     if (activeOptionNode) {
   102       DocAccessible* document = Document();
   103       if (document)
   104         return document->GetAccessible(activeOptionNode);
   105     }
   106   }
   107   return nullptr;
   108 }
   110 void
   111 HTMLSelectListAccessible::SetCurrentItem(Accessible* aItem)
   112 {
   113   aItem->GetContent()->SetAttr(kNameSpaceID_None,
   114                                nsGkAtoms::selected, NS_LITERAL_STRING("true"),
   115                                true);
   116 }
   118 ////////////////////////////////////////////////////////////////////////////////
   119 // HTMLSelectListAccessible: Accessible protected
   121 void
   122 HTMLSelectListAccessible::CacheChildren()
   123 {
   124   // Cache accessibles for <optgroup> and <option> DOM decendents as children,
   125   // as well as the accessibles for them. Avoid whitespace text nodes. We want
   126   // to count all the <optgroup>s and <option>s as children because we want
   127   // a flat tree under the Select List.
   128   for (nsIContent* childContent = mContent->GetFirstChild(); childContent;
   129        childContent = childContent->GetNextSibling()) {
   130     if (!childContent->IsHTML()) {
   131       continue;
   132     }
   134     nsIAtom* tag = childContent->Tag();
   135     if (tag == nsGkAtoms::option ||
   136         tag == nsGkAtoms::optgroup) {
   138       // Get an accessible for option or optgroup and cache it.
   139       nsRefPtr<Accessible> accessible =
   140         GetAccService()->GetOrCreateAccessible(childContent, this);
   141       if (accessible)
   142         AppendChild(accessible);
   143     }
   144   }
   145 }
   148 ////////////////////////////////////////////////////////////////////////////////
   149 // HTMLSelectOptionAccessible
   150 ////////////////////////////////////////////////////////////////////////////////
   152 HTMLSelectOptionAccessible::
   153   HTMLSelectOptionAccessible(nsIContent* aContent, DocAccessible* aDoc) :
   154   HyperTextAccessibleWrap(aContent, aDoc)
   155 {
   156 }
   158 ////////////////////////////////////////////////////////////////////////////////
   159 // HTMLSelectOptionAccessible: Accessible public
   161 role
   162 HTMLSelectOptionAccessible::NativeRole()
   163 {
   164   if (GetCombobox())
   165     return roles::COMBOBOX_OPTION;
   167   return roles::OPTION;
   168 }
   170 ENameValueFlag
   171 HTMLSelectOptionAccessible::NativeName(nsString& aName)
   172 {
   173   // CASE #1 -- great majority of the cases
   174   // find the label attribute - this is what the W3C says we should use
   175   mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName);
   176   if (!aName.IsEmpty())
   177     return eNameOK;
   179   // CASE #2 -- no label parameter, get the first child, 
   180   // use it if it is a text node
   181   nsIContent* text = mContent->GetFirstChild();
   182   if (text && text->IsNodeOfType(nsINode::eTEXT)) {
   183     nsTextEquivUtils::AppendTextEquivFromTextContent(text, &aName);
   184     aName.CompressWhitespace();
   185     return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
   186   }
   188   return eNameOK;
   189 }
   191 uint64_t
   192 HTMLSelectOptionAccessible::NativeState()
   193 {
   194   // As a HTMLSelectOptionAccessible we can have the following states:
   195   // SELECTABLE, SELECTED, FOCUSED, FOCUSABLE, OFFSCREEN
   196   // Upcall to Accessible, but skip HyperTextAccessible impl
   197   // because we don't want EDITABLE or SELECTABLE_TEXT
   198   uint64_t state = Accessible::NativeState();
   200   Accessible* select = GetSelect();
   201   if (!select)
   202     return state;
   204   uint64_t selectState = select->State();
   205   if (selectState & states::INVISIBLE)
   206     return state;
   208   // Are we selected?
   209   HTMLOptionElement* option = HTMLOptionElement::FromContent(mContent);
   210   bool selected = option && option->Selected();
   211   if (selected)
   212     state |= states::SELECTED;
   214   if (selectState & states::OFFSCREEN) {
   215     state |= states::OFFSCREEN;
   216   } else if (selectState & states::COLLAPSED) {
   217     // <select> is COLLAPSED: add OFFSCREEN, if not the currently
   218     // visible option
   219     if (!selected) {
   220       state |= states::OFFSCREEN;
   221       state ^= states::INVISIBLE;
   222     } else {
   223       // Clear offscreen and invisible for currently showing option
   224       state &= ~(states::OFFSCREEN | states::INVISIBLE);
   225       state |= selectState & states::OPAQUE1;
   226     }
   227   } else {
   228     // XXX list frames are weird, don't rely on Accessible's general
   229     // visibility implementation unless they get reimplemented in layout
   230     state &= ~states::OFFSCREEN;
   231     // <select> is not collapsed: compare bounds to calculate OFFSCREEN
   232     Accessible* listAcc = Parent();
   233     if (listAcc) {
   234       int32_t optionX, optionY, optionWidth, optionHeight;
   235       int32_t listX, listY, listWidth, listHeight;
   236       GetBounds(&optionX, &optionY, &optionWidth, &optionHeight);
   237       listAcc->GetBounds(&listX, &listY, &listWidth, &listHeight);
   238       if (optionY < listY || optionY + optionHeight > listY + listHeight) {
   239         state |= states::OFFSCREEN;
   240       }
   241     }
   242   }
   244   return state;
   245 }
   247 uint64_t
   248 HTMLSelectOptionAccessible::NativeInteractiveState() const
   249 {
   250   return NativelyUnavailable() ?
   251     states::UNAVAILABLE : states::FOCUSABLE | states::SELECTABLE;
   252 }
   254 int32_t
   255 HTMLSelectOptionAccessible::GetLevelInternal()
   256 {
   257   nsIContent* parentContent = mContent->GetParent();
   259   int32_t level =
   260     parentContent->NodeInfo()->Equals(nsGkAtoms::optgroup) ? 2 : 1;
   262   if (level == 1 && Role() != roles::HEADING)
   263     level = 0; // In a single level list, the level is irrelevant
   265   return level;
   266 }
   268 void
   269 HTMLSelectOptionAccessible::GetBoundsRect(nsRect& aTotalBounds,
   270                                           nsIFrame** aBoundingFrame)
   271 {
   272   Accessible* combobox = GetCombobox();
   273   if (combobox && (combobox->State() & states::COLLAPSED))
   274     combobox->GetBoundsRect(aTotalBounds, aBoundingFrame);
   275   else
   276     HyperTextAccessibleWrap::GetBoundsRect(aTotalBounds, aBoundingFrame);
   277 }
   279 NS_IMETHODIMP
   280 HTMLSelectOptionAccessible::GetActionName(uint8_t aIndex, nsAString& aName)
   281 {
   282   if (aIndex == eAction_Select) {
   283     aName.AssignLiteral("select"); 
   284     return NS_OK;
   285   }
   286   return NS_ERROR_INVALID_ARG;
   287 }
   289 uint8_t
   290 HTMLSelectOptionAccessible::ActionCount()
   291 {
   292   return 1;
   293 }
   295 NS_IMETHODIMP
   296 HTMLSelectOptionAccessible::DoAction(uint8_t aIndex)
   297 {
   298   if (aIndex != eAction_Select)
   299     return NS_ERROR_INVALID_ARG;
   301   if (IsDefunct())
   302     return NS_ERROR_FAILURE;
   304   DoCommand();
   305   return NS_OK;
   306 }
   308 NS_IMETHODIMP
   309 HTMLSelectOptionAccessible::SetSelected(bool aSelect)
   310 {
   311   if (IsDefunct())
   312     return NS_ERROR_FAILURE;
   314   HTMLOptionElement* option = HTMLOptionElement::FromContent(mContent);
   315   return option ? option->SetSelected(aSelect) : NS_ERROR_FAILURE;
   316 }
   318 ////////////////////////////////////////////////////////////////////////////////
   319 // HTMLSelectOptionAccessible: Widgets
   321 Accessible*
   322 HTMLSelectOptionAccessible::ContainerWidget() const
   323 {
   324   Accessible* parent = Parent();
   325   if (parent && parent->IsHTMLOptGroup())
   326     parent = parent->Parent();
   328   return parent && parent->IsListControl() ? parent : nullptr;
   329 }
   331 ////////////////////////////////////////////////////////////////////////////////
   332 // HTMLSelectOptGroupAccessible
   333 ////////////////////////////////////////////////////////////////////////////////
   335 role
   336 HTMLSelectOptGroupAccessible::NativeRole()
   337 {
   338   return roles::GROUPING;
   339 }
   341 uint64_t
   342 HTMLSelectOptGroupAccessible::NativeInteractiveState() const
   343 {
   344   return NativelyUnavailable() ? states::UNAVAILABLE : 0;
   345 }
   347 NS_IMETHODIMP
   348 HTMLSelectOptGroupAccessible::DoAction(uint8_t index)
   349 {
   350   return NS_ERROR_NOT_IMPLEMENTED;
   351 }
   353 NS_IMETHODIMP
   354 HTMLSelectOptGroupAccessible::GetActionName(uint8_t aIndex, nsAString& aName)
   355 {
   356   return NS_ERROR_NOT_IMPLEMENTED;
   357 }
   359 uint8_t
   360 HTMLSelectOptGroupAccessible::ActionCount()
   361 {
   362   return 0;
   363 }
   365 ////////////////////////////////////////////////////////////////////////////////
   366 // HTMLComboboxAccessible
   367 ////////////////////////////////////////////////////////////////////////////////
   369 HTMLComboboxAccessible::
   370   HTMLComboboxAccessible(nsIContent* aContent, DocAccessible* aDoc) :
   371   AccessibleWrap(aContent, aDoc)
   372 {
   373   mGenericTypes |= eCombobox;
   374 }
   376 ////////////////////////////////////////////////////////////////////////////////
   377 // HTMLComboboxAccessible: Accessible
   379 role
   380 HTMLComboboxAccessible::NativeRole()
   381 {
   382   return roles::COMBOBOX;
   383 }
   385 void
   386 HTMLComboboxAccessible::InvalidateChildren()
   387 {
   388   AccessibleWrap::InvalidateChildren();
   390   if (mListAccessible)
   391     mListAccessible->InvalidateChildren();
   392 }
   394 void
   395 HTMLComboboxAccessible::CacheChildren()
   396 {
   397   nsIComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
   398   if (!comboFrame)
   399     return;
   401   nsIFrame* listFrame = comboFrame->GetDropDown();
   402   if (!listFrame)
   403     return;
   405   if (!mListAccessible) {
   406     mListAccessible = 
   407       new HTMLComboboxListAccessible(mParent, mContent, mDoc);
   409     // Initialize and put into cache.
   410     Document()->BindToDocument(mListAccessible, nullptr);
   411   }
   413   if (AppendChild(mListAccessible)) {
   414     // Cache combobox option accessibles so that we build complete accessible
   415     // tree for combobox.
   416     mListAccessible->EnsureChildren();
   417   }
   418 }
   420 void
   421 HTMLComboboxAccessible::Shutdown()
   422 {
   423   AccessibleWrap::Shutdown();
   425   if (mListAccessible) {
   426     mListAccessible->Shutdown();
   427     mListAccessible = nullptr;
   428   }
   429 }
   431 uint64_t
   432 HTMLComboboxAccessible::NativeState()
   433 {
   434   // As a HTMLComboboxAccessible we can have the following states:
   435   // FOCUSED, FOCUSABLE, HASPOPUP, EXPANDED, COLLAPSED
   436   // Get focus status from base class
   437   uint64_t state = Accessible::NativeState();
   439   nsIComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
   440   if (comboFrame && comboFrame->IsDroppedDown())
   441     state |= states::EXPANDED;
   442   else
   443     state |= states::COLLAPSED;
   445   state |= states::HASPOPUP;
   446   return state;
   447 }
   449 void
   450 HTMLComboboxAccessible::Description(nsString& aDescription)
   451 {
   452   aDescription.Truncate();
   453   // First check to see if combo box itself has a description, perhaps through
   454   // tooltip (title attribute) or via aria-describedby
   455   Accessible::Description(aDescription);
   456   if (!aDescription.IsEmpty())
   457     return;
   459   // Otherwise use description of selected option.
   460   Accessible* option = SelectedOption();
   461   if (option)
   462     option->Description(aDescription);
   463 }
   465 void
   466 HTMLComboboxAccessible::Value(nsString& aValue)
   467 {
   468   // Use accessible name of selected option.
   469   Accessible* option = SelectedOption();
   470   if (option)
   471     option->Name(aValue);
   472 }
   474 uint8_t
   475 HTMLComboboxAccessible::ActionCount()
   476 {
   477   return 1;
   478 }
   480 NS_IMETHODIMP
   481 HTMLComboboxAccessible::DoAction(uint8_t aIndex)
   482 {
   483   if (aIndex != eAction_Click)
   484     return NS_ERROR_INVALID_ARG;
   486   if (IsDefunct())
   487     return NS_ERROR_FAILURE;
   489   DoCommand();
   490   return NS_OK;
   491 }
   493 /**
   494   * Our action name is the reverse of our state: 
   495   *     if we are closed -> open is our name.
   496   *     if we are open -> closed is our name.
   497   * Uses the frame to get the state, updated on every click
   498   */
   499 NS_IMETHODIMP
   500 HTMLComboboxAccessible::GetActionName(uint8_t aIndex, nsAString& aName)
   501 {
   502   if (aIndex != HTMLComboboxAccessible::eAction_Click) {
   503     return NS_ERROR_INVALID_ARG;
   504   }
   505   nsIComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
   506   if (!comboFrame) {
   507     return NS_ERROR_FAILURE;
   508   }
   509   if (comboFrame->IsDroppedDown())
   510     aName.AssignLiteral("close"); 
   511   else
   512     aName.AssignLiteral("open"); 
   514   return NS_OK;
   515 }
   517 ////////////////////////////////////////////////////////////////////////////////
   518 // HTMLComboboxAccessible: Widgets
   520 bool
   521 HTMLComboboxAccessible::IsWidget() const
   522 {
   523   return true;
   524 }
   526 bool
   527 HTMLComboboxAccessible::IsActiveWidget() const
   528 {
   529   return FocusMgr()->HasDOMFocus(mContent);
   530 }
   532 bool
   533 HTMLComboboxAccessible::AreItemsOperable() const
   534 {
   535   nsIComboboxControlFrame* comboboxFrame = do_QueryFrame(GetFrame());
   536   return comboboxFrame && comboboxFrame->IsDroppedDown();
   537 }
   539 Accessible*
   540 HTMLComboboxAccessible::CurrentItem()
   541 {
   542   return AreItemsOperable() ? mListAccessible->CurrentItem() : nullptr;
   543 }
   545 void
   546 HTMLComboboxAccessible::SetCurrentItem(Accessible* aItem)
   547 {
   548   if (AreItemsOperable())
   549     mListAccessible->SetCurrentItem(aItem);
   550 }
   552 ////////////////////////////////////////////////////////////////////////////////
   553 // HTMLComboboxAccessible: protected
   555 Accessible*
   556 HTMLComboboxAccessible::SelectedOption() const
   557 {
   558   nsIFrame* frame = GetFrame();
   559   nsIComboboxControlFrame* comboboxFrame = do_QueryFrame(frame);
   560   if (!comboboxFrame)
   561     return nullptr;
   563   nsIListControlFrame* listControlFrame =
   564     do_QueryFrame(comboboxFrame->GetDropDown());
   565   if (listControlFrame) {
   566     nsCOMPtr<nsIContent> activeOptionNode = listControlFrame->GetCurrentOption();
   567     if (activeOptionNode) {
   568       DocAccessible* document = Document();
   569       if (document)
   570         return document->GetAccessible(activeOptionNode);
   571     }
   572   }
   574   return nullptr;
   575 }
   578 ////////////////////////////////////////////////////////////////////////////////
   579 // HTMLComboboxListAccessible
   580 ////////////////////////////////////////////////////////////////////////////////
   582 HTMLComboboxListAccessible::
   583   HTMLComboboxListAccessible(nsIAccessible* aParent, nsIContent* aContent,
   584                              DocAccessible* aDoc) :
   585   HTMLSelectListAccessible(aContent, aDoc)
   586 {
   587   mStateFlags |= eSharedNode;
   588 }
   590 ////////////////////////////////////////////////////////////////////////////////
   591 // HTMLComboboxAccessible: Accessible
   593 nsIFrame*
   594 HTMLComboboxListAccessible::GetFrame() const
   595 {
   596   nsIFrame* frame = HTMLSelectListAccessible::GetFrame();
   597   nsIComboboxControlFrame* comboBox = do_QueryFrame(frame);
   598   if (comboBox) {
   599     return comboBox->GetDropDown();
   600   }
   602   return nullptr;
   603 }
   605 role
   606 HTMLComboboxListAccessible::NativeRole()
   607 {
   608   return roles::COMBOBOX_LIST;
   609 }
   611 uint64_t
   612 HTMLComboboxListAccessible::NativeState()
   613 {
   614   // As a HTMLComboboxListAccessible we can have the following states:
   615   // FOCUSED, FOCUSABLE, FLOATING, INVISIBLE
   616   // Get focus status from base class
   617   uint64_t state = Accessible::NativeState();
   619   nsIComboboxControlFrame* comboFrame = do_QueryFrame(mParent->GetFrame());
   620   if (comboFrame && comboFrame->IsDroppedDown())
   621     state |= states::FLOATING;
   622   else
   623     state |= states::INVISIBLE;
   625   return state;
   626 }
   628 /**
   629   * Gets the bounds for the areaFrame.
   630   *     Walks the Frame tree and checks for proper frames.
   631   */
   632 void
   633 HTMLComboboxListAccessible::GetBoundsRect(nsRect& aBounds, nsIFrame** aBoundingFrame)
   634 {
   635   *aBoundingFrame = nullptr;
   637   Accessible* comboAcc = Parent();
   638   if (!comboAcc)
   639     return;
   641   if (0 == (comboAcc->State() & states::COLLAPSED)) {
   642     HTMLSelectListAccessible::GetBoundsRect(aBounds, aBoundingFrame);
   643     return;
   644   }
   646   // Get the first option.
   647   nsIContent* content = mContent->GetFirstChild();
   648   if (!content) {
   649     return;
   650   }
   651   nsIFrame* frame = content->GetPrimaryFrame();
   652   if (!frame) {
   653     *aBoundingFrame = nullptr;
   654     return;
   655   }
   657   *aBoundingFrame = frame->GetParent();
   658   aBounds = (*aBoundingFrame)->GetRect();
   659 }
   661 ////////////////////////////////////////////////////////////////////////////////
   662 // HTMLComboboxListAccessible: Widgets
   664 bool
   665 HTMLComboboxListAccessible::IsActiveWidget() const
   666 {
   667   return mParent && mParent->IsActiveWidget();
   668 }
   670 bool
   671 HTMLComboboxListAccessible::AreItemsOperable() const
   672 {
   673   return mParent && mParent->AreItemsOperable();
   674 }

mercurial