2015-10-20 03:52:43 +03:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
2012-05-21 15:12:37 +04:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
2003-04-02 02:18:29 +04:00
|
|
|
|
2012-04-13 18:17:03 +04:00
|
|
|
#include "Accessible-inl.h"
|
2011-08-10 05:44:00 +04:00
|
|
|
#include "AccIterator.h"
|
2012-05-30 06:21:24 +04:00
|
|
|
#include "DocAccessible-inl.h"
|
2014-03-08 01:35:19 +04:00
|
|
|
#include "DocAccessibleChild.h"
|
2014-03-06 04:36:56 +04:00
|
|
|
#include "HTMLImageMapAccessible.h"
|
2010-04-27 10:52:03 +04:00
|
|
|
#include "nsAccCache.h"
|
2012-02-02 10:14:51 +04:00
|
|
|
#include "nsAccessiblePivot.h"
|
2010-04-27 10:52:03 +04:00
|
|
|
#include "nsAccUtils.h"
|
2020-05-01 07:28:35 +03:00
|
|
|
#include "nsDeckFrame.h"
|
2012-07-28 08:21:40 +04:00
|
|
|
#include "nsEventShell.h"
|
2010-04-27 10:52:03 +04:00
|
|
|
#include "nsTextEquivUtils.h"
|
2012-01-12 07:07:35 +04:00
|
|
|
#include "Role.h"
|
2012-05-04 10:09:22 +04:00
|
|
|
#include "RootAccessible.h"
|
2012-11-19 13:20:09 +04:00
|
|
|
#include "TreeWalker.h"
|
2014-10-22 04:49:28 +04:00
|
|
|
#include "xpcAccessibleDocument.h"
|
2010-04-27 10:52:03 +04:00
|
|
|
|
2019-04-03 15:51:38 +03:00
|
|
|
#include "nsCommandManager.h"
|
2018-09-19 05:15:55 +03:00
|
|
|
#include "nsContentUtils.h"
|
2003-05-15 12:37:38 +04:00
|
|
|
#include "nsIDocShell.h"
|
2019-01-02 16:05:23 +03:00
|
|
|
#include "mozilla/dom/Document.h"
|
2005-11-29 02:56:44 +03:00
|
|
|
#include "nsPIDOMWindow.h"
|
2003-05-15 12:37:38 +04:00
|
|
|
#include "nsIEditingSession.h"
|
|
|
|
#include "nsIFrame.h"
|
2003-04-02 02:18:29 +04:00
|
|
|
#include "nsIInterfaceRequestorUtils.h"
|
2014-03-06 04:36:56 +04:00
|
|
|
#include "nsImageFrame.h"
|
2013-09-11 02:18:59 +04:00
|
|
|
#include "nsIPersistentProperties2.h"
|
2013-01-05 07:12:24 +04:00
|
|
|
#include "nsViewManager.h"
|
2009-09-03 07:57:41 +04:00
|
|
|
#include "nsIScrollableFrame.h"
|
2005-06-24 23:16:45 +04:00
|
|
|
#include "nsUnicharUtils.h"
|
2003-05-15 12:37:38 +04:00
|
|
|
#include "nsIURI.h"
|
|
|
|
#include "nsIWebNavigation.h"
|
Bug 178324, refactor focus by moving all focus handling into one place and simplifying it, add many tests, fixes many other bugs too numerous to mention in this small checkin comment, r=josh,smichaud,ere,dbaron,marco,neil,gavin,smaug,sr=smaug (CLOSED TREE)
2009-06-10 22:00:39 +04:00
|
|
|
#include "nsFocusManager.h"
|
2014-01-30 22:26:54 +04:00
|
|
|
#include "mozilla/ArrayUtils.h"
|
2012-10-16 02:06:03 +04:00
|
|
|
#include "mozilla/Assertions.h"
|
2014-04-03 08:18:36 +04:00
|
|
|
#include "mozilla/EventStates.h"
|
2017-08-07 11:42:50 +03:00
|
|
|
#include "mozilla/HTMLEditor.h"
|
2019-03-29 18:12:47 +03:00
|
|
|
#include "mozilla/PresShell.h"
|
2017-08-07 11:42:50 +03:00
|
|
|
#include "mozilla/TextEditor.h"
|
2020-03-02 14:36:42 +03:00
|
|
|
#include "mozilla/dom/AncestorIterator.h"
|
2019-04-10 01:39:01 +03:00
|
|
|
#include "mozilla/dom/BrowserChild.h"
|
2014-01-21 01:52:04 +04:00
|
|
|
#include "mozilla/dom/DocumentType.h"
|
2010-08-24 11:05:56 +04:00
|
|
|
#include "mozilla/dom/Element.h"
|
2018-02-09 19:17:10 +03:00
|
|
|
#include "mozilla/dom/MutationEventBinding.h"
|
2019-09-20 23:51:25 +03:00
|
|
|
#include "mozilla/dom/UserActivation.h"
|
2019-09-10 19:16:53 +03:00
|
|
|
#include "HTMLElementAccessibles.h"
|
2012-05-23 13:21:40 +04:00
|
|
|
|
2011-10-11 09:50:08 +04:00
|
|
|
using namespace mozilla;
|
2011-07-27 16:43:01 +04:00
|
|
|
using namespace mozilla::a11y;
|
2010-08-24 11:05:56 +04:00
|
|
|
|
2009-12-10 22:12:19 +03:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Static member initialization
|
2003-04-02 02:18:29 +04:00
|
|
|
|
2018-04-03 15:15:30 +03:00
|
|
|
static nsStaticAtom* const kRelationAttrs[] = {nsGkAtoms::aria_labelledby,
|
|
|
|
nsGkAtoms::aria_describedby,
|
|
|
|
nsGkAtoms::aria_details,
|
|
|
|
nsGkAtoms::aria_owns,
|
|
|
|
nsGkAtoms::aria_controls,
|
|
|
|
nsGkAtoms::aria_flowto,
|
|
|
|
nsGkAtoms::aria_errormessage,
|
|
|
|
nsGkAtoms::_for,
|
|
|
|
nsGkAtoms::control};
|
2010-11-18 05:55:44 +03:00
|
|
|
|
2014-01-30 22:26:54 +04:00
|
|
|
static const uint32_t kRelationAttrsLen = ArrayLength(kRelationAttrs);
|
2009-12-10 22:12:19 +03:00
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Constructor/desctructor
|
|
|
|
|
2019-04-13 15:13:15 +03:00
|
|
|
DocAccessible::DocAccessible(dom::Document* aDocument,
|
|
|
|
PresShell* aPresShell)
|
2017-01-07 02:03:30 +03:00
|
|
|
: // XXX don't pass a document to the Accessible constructor so that we
|
|
|
|
// don't set mDoc until our vtable is fully setup. If we set mDoc before
|
|
|
|
// setting up the vtable we will call Accessible::AddRef() but not the
|
|
|
|
// overrides of it for subclasses. It is important to call those
|
|
|
|
// overrides to avoid confusing leak checking machinary.
|
|
|
|
HyperTextAccessibleWrap(nullptr, nullptr),
|
2013-09-02 12:41:57 +04:00
|
|
|
// XXX aaronl should we use an algorithm for the initial cache size?
|
2014-08-06 17:31:21 +04:00
|
|
|
mAccessibleCache(kDefaultCacheLength),
|
|
|
|
mNodeToAccessibleMap(kDefaultCacheLength),
|
2013-09-23 09:37:19 +04:00
|
|
|
mDocumentNode(aDocument),
|
2012-12-27 08:25:27 +04:00
|
|
|
mLoadState(eTreeConstructionPending),
|
|
|
|
mDocFlags(0),
|
|
|
|
mLoadEventType(0),
|
2017-10-31 14:09:01 +03:00
|
|
|
mARIAAttrOldValue{nullptr},
|
|
|
|
mVirtualCursor(nullptr),
|
2014-03-08 01:35:19 +04:00
|
|
|
mPresShell(aPresShell),
|
|
|
|
mIPCDoc(nullptr) {
|
2012-12-18 09:22:26 +04:00
|
|
|
mGenericTypes |= eDocument;
|
2012-12-11 07:31:42 +04:00
|
|
|
mStateFlags |= eNotNodeMapEntry;
|
2017-01-07 02:03:30 +03:00
|
|
|
mDoc = this;
|
2012-12-11 07:31:42 +04:00
|
|
|
|
2012-10-16 02:06:03 +04:00
|
|
|
MOZ_ASSERT(mPresShell, "should have been given a pres shell");
|
|
|
|
mPresShell->SetDocAccessible(this);
|
2003-04-02 02:18:29 +04:00
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
DocAccessible::~DocAccessible() {
|
2012-02-08 02:38:54 +04:00
|
|
|
NS_ASSERTION(!mPresShell, "LastRelease was never called!?!");
|
2003-04-02 02:18:29 +04:00
|
|
|
}
|
|
|
|
|
2008-08-06 16:19:56 +04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2009-12-10 22:12:19 +03:00
|
|
|
// nsISupports
|
2008-08-06 16:19:56 +04:00
|
|
|
|
2013-08-02 05:29:05 +04:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(DocAccessible)
|
|
|
|
|
2012-05-29 05:18:45 +04:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible, Accessible)
|
2012-11-15 11:32:40 +04:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationController)
|
2012-11-29 04:05:04 +04:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVirtualCursor)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildDocuments)
|
2018-10-30 03:17:04 +03:00
|
|
|
for (auto hashesIter = tmp->mDependentIDsHashes.Iter(); !hashesIter.Done();
|
|
|
|
hashesIter.Next()) {
|
|
|
|
auto dependentIDsHash = hashesIter.UserData();
|
|
|
|
for (auto providersIter = dependentIDsHash->Iter(); !providersIter.Done();
|
|
|
|
providersIter.Next()) {
|
|
|
|
AttrRelProviders* providers = providersIter.UserData();
|
|
|
|
for (int32_t provIdx = providers->Length() - 1; provIdx >= 0; provIdx--) {
|
|
|
|
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
|
|
|
|
cb, "content of dependent ids hash entry of document accessible");
|
|
|
|
|
2020-04-04 00:05:35 +03:00
|
|
|
const auto& provider = (*providers)[provIdx];
|
2018-10-30 03:17:04 +03:00
|
|
|
cb.NoteXPCOMChild(provider->mContent);
|
|
|
|
}
|
2015-10-20 03:52:43 +03:00
|
|
|
}
|
|
|
|
}
|
2012-11-29 04:05:04 +04:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache)
|
2013-03-05 13:08:37 +04:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm)
|
2016-08-25 05:16:45 +03:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInvalidationList)
|
2015-10-30 01:08:48 +03:00
|
|
|
for (auto it = tmp->mARIAOwnsHash.ConstIter(); !it.Done(); it.Next()) {
|
2019-09-10 19:16:53 +03:00
|
|
|
nsTArray<RefPtr<Accessible>>* ar = it.UserData();
|
2015-10-30 01:08:48 +03:00
|
|
|
for (uint32_t i = 0; i < ar->Length(); i++) {
|
|
|
|
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mARIAOwnsHash entry item");
|
|
|
|
cb.NoteXPCOMChild(ar->ElementAt(i));
|
|
|
|
}
|
2015-09-18 15:52:46 +03:00
|
|
|
}
|
2008-08-06 16:19:56 +04:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
|
2012-05-29 05:18:45 +04:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible, Accessible)
|
2012-11-15 11:32:40 +04:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationController)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mVirtualCursor)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildDocuments)
|
2018-10-30 03:17:04 +03:00
|
|
|
tmp->mDependentIDsHashes.Clear();
|
2010-10-21 08:16:10 +04:00
|
|
|
tmp->mNodeToAccessibleMap.Clear();
|
2012-11-29 04:05:04 +04:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache)
|
2013-03-05 13:08:37 +04:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm)
|
2016-08-25 05:16:45 +03:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mInvalidationList)
|
2020-02-25 22:44:39 +03:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
|
2015-10-30 01:08:48 +03:00
|
|
|
tmp->mARIAOwnsHash.Clear();
|
2008-08-06 16:19:56 +04:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
|
2017-08-30 02:02:48 +03:00
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocAccessible)
|
2005-06-10 17:57:27 +04:00
|
|
|
NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
|
2006-07-02 11:23:10 +04:00
|
|
|
NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
|
2003-05-15 12:37:38 +04:00
|
|
|
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
|
|
|
|
NS_INTERFACE_MAP_ENTRY(nsIObserver)
|
2012-02-02 10:14:51 +04:00
|
|
|
NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivotObserver)
|
2014-10-22 04:49:28 +04:00
|
|
|
NS_INTERFACE_MAP_END_INHERITING(HyperTextAccessible)
|
2003-05-15 12:37:38 +04:00
|
|
|
|
2012-05-31 12:04:41 +04:00
|
|
|
NS_IMPL_ADDREF_INHERITED(DocAccessible, HyperTextAccessible)
|
|
|
|
NS_IMPL_RELEASE_INHERITED(DocAccessible, HyperTextAccessible)
|
2003-04-02 02:18:29 +04:00
|
|
|
|
2009-12-10 22:12:19 +03:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// nsIAccessible
|
|
|
|
|
2018-05-15 19:13:02 +03:00
|
|
|
ENameValueFlag DocAccessible::Name(nsString& aName) const {
|
2005-06-01 18:03:38 +04:00
|
|
|
aName.Truncate();
|
2012-05-01 07:08:31 +04:00
|
|
|
|
2008-03-14 23:49:38 +03:00
|
|
|
if (mParent) {
|
2012-05-01 07:08:31 +04:00
|
|
|
mParent->Name(aName); // Allow owning iframe to override the name
|
2005-06-01 18:03:38 +04:00
|
|
|
}
|
|
|
|
if (aName.IsEmpty()) {
|
2008-10-10 16:26:55 +04:00
|
|
|
// Allow name via aria-labelledby or title attribute
|
2012-05-29 05:18:45 +04:00
|
|
|
Accessible::Name(aName);
|
2005-06-01 18:03:38 +04:00
|
|
|
}
|
2008-03-14 23:49:38 +03:00
|
|
|
if (aName.IsEmpty()) {
|
2014-09-23 16:23:02 +04:00
|
|
|
Title(aName); // Try title element
|
2008-04-15 19:17:59 +04:00
|
|
|
}
|
|
|
|
if (aName.IsEmpty()) { // Last resort: use URL
|
2014-09-23 16:23:02 +04:00
|
|
|
URL(aName);
|
2005-08-17 23:08:26 +04:00
|
|
|
}
|
2014-09-23 16:23:02 +04:00
|
|
|
|
2012-05-01 07:08:31 +04:00
|
|
|
return eNameOK;
|
2003-04-15 12:45:55 +04:00
|
|
|
}
|
|
|
|
|
2012-05-29 05:18:45 +04:00
|
|
|
// Accessible public method
|
2018-05-07 22:05:50 +03:00
|
|
|
role DocAccessible::NativeRole() const {
|
2013-02-13 02:02:51 +04:00
|
|
|
nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode);
|
|
|
|
if (docShell) {
|
2005-06-01 17:54:08 +04:00
|
|
|
nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
|
2019-07-26 19:48:31 +03:00
|
|
|
docShell->GetInProcessSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
|
2014-01-20 11:58:26 +04:00
|
|
|
int32_t itemType = docShell->ItemType();
|
2013-02-13 02:02:51 +04:00
|
|
|
if (sameTypeRoot == docShell) {
|
2005-06-01 17:54:08 +04:00
|
|
|
// Root of content or chrome tree
|
2010-09-05 06:14:01 +04:00
|
|
|
if (itemType == nsIDocShellTreeItem::typeChrome)
|
2012-01-12 07:07:35 +04:00
|
|
|
return roles::CHROME_WINDOW;
|
2010-09-05 06:14:01 +04:00
|
|
|
|
|
|
|
if (itemType == nsIDocShellTreeItem::typeContent) {
|
2012-01-12 07:07:35 +04:00
|
|
|
return roles::DOCUMENT;
|
2005-06-01 17:54:08 +04:00
|
|
|
}
|
2007-06-14 21:12:50 +04:00
|
|
|
} else if (itemType == nsIDocShellTreeItem::typeContent) {
|
2012-01-12 07:07:35 +04:00
|
|
|
return roles::DOCUMENT;
|
2007-06-14 21:12:50 +04:00
|
|
|
}
|
2005-06-01 17:54:08 +04:00
|
|
|
}
|
2006-04-10 18:25:14 +04:00
|
|
|
|
2012-01-12 07:07:35 +04:00
|
|
|
return roles::PANE; // Fall back;
|
2003-04-02 02:18:29 +04:00
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
void DocAccessible::Description(nsString& aDescription) {
|
2011-04-23 17:14:05 +04:00
|
|
|
if (mParent) mParent->Description(aDescription);
|
2008-03-14 23:49:38 +03:00
|
|
|
|
2012-10-13 10:34:21 +04:00
|
|
|
if (HasOwnContent() && aDescription.IsEmpty()) {
|
2011-06-04 01:35:17 +04:00
|
|
|
nsTextEquivUtils::GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
|
2011-04-23 17:14:05 +04:00
|
|
|
aDescription);
|
2012-10-13 10:34:21 +04:00
|
|
|
}
|
2007-07-02 10:14:11 +04:00
|
|
|
}
|
|
|
|
|
2012-05-29 05:18:45 +04:00
|
|
|
// Accessible public method
|
2018-05-15 21:04:50 +03:00
|
|
|
uint64_t DocAccessible::NativeState() const {
|
2011-09-28 05:46:11 +04:00
|
|
|
// Document is always focusable.
|
2012-10-31 06:25:17 +04:00
|
|
|
uint64_t state =
|
|
|
|
states::FOCUSABLE; // keep in sync with NativeInteractiveState() impl
|
2011-09-28 05:46:11 +04:00
|
|
|
if (FocusMgr()->IsFocused(this)) state |= states::FOCUSED;
|
2006-04-10 18:25:14 +04:00
|
|
|
|
2011-08-08 11:55:36 +04:00
|
|
|
// Expose stale state until the document is ready (DOM is loaded and tree is
|
|
|
|
// constructed).
|
|
|
|
if (!HasLoadState(eReady)) state |= states::STALE;
|
|
|
|
|
|
|
|
// Expose state busy until the document and all its subdocuments is completely
|
|
|
|
// loaded.
|
|
|
|
if (!HasLoadState(eCompletelyLoaded)) state |= states::BUSY;
|
|
|
|
|
2007-03-19 09:45:35 +03:00
|
|
|
nsIFrame* frame = GetFrame();
|
2011-10-27 03:57:55 +04:00
|
|
|
if (!frame || !frame->IsVisibleConsideringAncestors(
|
|
|
|
nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
|
2011-04-10 03:38:06 +04:00
|
|
|
state |= states::INVISIBLE | states::OFFSCREEN;
|
2004-05-26 16:31:43 +04:00
|
|
|
}
|
2003-05-15 12:37:38 +04:00
|
|
|
|
2017-08-07 11:42:50 +03:00
|
|
|
RefPtr<TextEditor> textEditor = GetEditor();
|
|
|
|
state |= textEditor ? states::EDITABLE : states::READONLY;
|
2006-06-21 17:29:10 +04:00
|
|
|
|
2011-04-10 03:38:06 +04:00
|
|
|
return state;
|
2003-04-02 02:18:29 +04:00
|
|
|
}
|
|
|
|
|
2012-06-04 09:41:06 +04:00
|
|
|
uint64_t DocAccessible::NativeInteractiveState() const {
|
|
|
|
// Document is always focusable.
|
|
|
|
return states::FOCUSABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DocAccessible::NativelyUnavailable() const { return false; }
|
|
|
|
|
2012-05-29 05:18:45 +04:00
|
|
|
// Accessible public method
|
2012-08-22 19:56:38 +04:00
|
|
|
void DocAccessible::ApplyARIAState(uint64_t* aState) const {
|
2012-10-31 06:25:17 +04:00
|
|
|
// Grab states from content element.
|
|
|
|
if (mContent) Accessible::ApplyARIAState(aState);
|
2008-03-14 23:49:38 +03:00
|
|
|
|
2012-10-31 06:25:17 +04:00
|
|
|
// Allow iframe/frame etc. to have final state override via ARIA.
|
2011-04-10 03:38:06 +04:00
|
|
|
if (mParent) mParent->ApplyARIAState(aState);
|
2008-03-14 23:49:38 +03:00
|
|
|
}
|
|
|
|
|
2012-10-19 11:15:23 +04:00
|
|
|
already_AddRefed<nsIPersistentProperties> DocAccessible::Attributes() {
|
|
|
|
nsCOMPtr<nsIPersistentProperties> attributes =
|
|
|
|
HyperTextAccessibleWrap::Attributes();
|
|
|
|
|
2012-10-22 19:54:41 +04:00
|
|
|
// No attributes if document is not attached to the tree or if it's a root
|
|
|
|
// document.
|
2012-10-19 11:15:23 +04:00
|
|
|
if (!mParent || IsRoot()) return attributes.forget();
|
|
|
|
|
|
|
|
// Override ARIA object attributes from outerdoc.
|
|
|
|
aria::AttrIterator attribIter(mParent->GetContent());
|
|
|
|
nsAutoString name, value, unused;
|
|
|
|
while (attribIter.Next(name, value))
|
|
|
|
attributes->SetStringProperty(NS_ConvertUTF16toUTF8(name), value, unused);
|
|
|
|
|
|
|
|
return attributes.forget();
|
2008-03-14 23:49:38 +03:00
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
Accessible* DocAccessible::FocusedChild() {
|
2005-09-09 06:30:16 +04:00
|
|
|
// Return an accessible for the current global focus, which does not have to
|
|
|
|
// be contained within the current document.
|
2011-09-28 05:46:11 +04:00
|
|
|
return FocusMgr()->FocusedAccessible();
|
2005-02-18 17:36:28 +03:00
|
|
|
}
|
|
|
|
|
2018-05-15 20:19:54 +03:00
|
|
|
void DocAccessible::TakeFocus() const {
|
2010-06-11 12:23:18 +04:00
|
|
|
// Focus the document.
|
2012-02-12 06:34:00 +04:00
|
|
|
nsFocusManager* fm = nsFocusManager::GetFocusManager();
|
2018-04-26 17:37:47 +03:00
|
|
|
RefPtr<dom::Element> newFocus;
|
2019-09-20 23:51:25 +03:00
|
|
|
dom::AutoHandlingUserInputStatePusher inputStatePusher(true);
|
2014-09-16 21:30:23 +04:00
|
|
|
fm->MoveFocus(mDocumentNode->GetWindow(), nullptr,
|
|
|
|
nsFocusManager::MOVEFOCUS_ROOT, 0, getter_AddRefs(newFocus));
|
2007-01-10 09:32:15 +03:00
|
|
|
}
|
|
|
|
|
2012-05-31 12:04:41 +04:00
|
|
|
// HyperTextAccessible method
|
2012-05-27 13:01:40 +04:00
|
|
|
already_AddRefed<TextEditor> DocAccessible::GetEditor() const {
|
2008-10-08 16:50:36 +04:00
|
|
|
// Check if document is editable (designMode="on" case). Otherwise check if
|
|
|
|
// the html:body (for HTML document case) or document element is editable.
|
2012-11-23 02:15:25 +04:00
|
|
|
if (!mDocumentNode->HasFlag(NODE_IS_EDITABLE) &&
|
2012-10-31 06:25:17 +04:00
|
|
|
(!mContent || !mContent->HasFlag(NODE_IS_EDITABLE)))
|
2012-07-30 18:20:58 +04:00
|
|
|
return nullptr;
|
2003-05-15 12:37:38 +04:00
|
|
|
|
2016-03-14 21:00:13 +03:00
|
|
|
nsCOMPtr<nsIDocShell> docShell = mDocumentNode->GetDocShell();
|
|
|
|
if (!docShell) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsCOMPtr<nsIEditingSession> editingSession;
|
|
|
|
docShell->GetEditingSession(getter_AddRefs(editingSession));
|
2012-07-30 18:20:58 +04:00
|
|
|
if (!editingSession) return nullptr; // No editing session interface
|
2003-05-15 12:37:38 +04:00
|
|
|
|
2017-08-07 11:42:50 +03:00
|
|
|
RefPtr<HTMLEditor> htmlEditor =
|
|
|
|
editingSession->GetHTMLEditorForWindow(mDocumentNode->GetWindow());
|
|
|
|
if (!htmlEditor) {
|
2012-07-30 18:20:58 +04:00
|
|
|
return nullptr;
|
2017-08-07 11:42:50 +03:00
|
|
|
}
|
2012-03-08 07:28:38 +04:00
|
|
|
|
|
|
|
bool isEditable = false;
|
2017-08-07 11:42:50 +03:00
|
|
|
htmlEditor->GetIsDocumentEditable(&isEditable);
|
|
|
|
if (isEditable) {
|
|
|
|
return htmlEditor.forget();
|
|
|
|
}
|
2012-03-08 07:28:38 +04:00
|
|
|
|
2012-07-30 18:20:58 +04:00
|
|
|
return nullptr;
|
2003-05-15 12:37:38 +04:00
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
// DocAccessible public method
|
2014-09-23 16:23:02 +04:00
|
|
|
|
|
|
|
void DocAccessible::URL(nsAString& aURL) const {
|
|
|
|
nsCOMPtr<nsISupports> container = mDocumentNode->GetContainer();
|
|
|
|
nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(container));
|
|
|
|
nsAutoCString theURL;
|
|
|
|
if (webNav) {
|
|
|
|
nsCOMPtr<nsIURI> pURI;
|
|
|
|
webNav->GetCurrentURI(getter_AddRefs(pURI));
|
|
|
|
if (pURI) pURI->GetSpec(theURL);
|
|
|
|
}
|
|
|
|
CopyUTF8toUTF16(theURL, aURL);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DocAccessible::DocType(nsAString& aType) const {
|
|
|
|
dom::DocumentType* docType = mDocumentNode->GetDoctype();
|
|
|
|
if (docType) docType->GetPublicId(aType);
|
|
|
|
}
|
|
|
|
|
2009-12-10 22:12:19 +03:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2013-10-29 07:30:55 +04:00
|
|
|
// Accessible
|
2005-08-10 05:39:43 +04:00
|
|
|
|
2012-11-20 07:54:41 +04:00
|
|
|
void DocAccessible::Init() {
|
|
|
|
#ifdef A11Y_LOG
|
|
|
|
if (logging::IsEnabled(logging::eDocCreate))
|
2012-11-23 02:15:25 +04:00
|
|
|
logging::DocCreate("document initialize", mDocumentNode, this);
|
2012-11-20 07:54:41 +04:00
|
|
|
#endif
|
|
|
|
|
|
|
|
// Initialize notification controller.
|
|
|
|
mNotificationController = new NotificationController(this, mPresShell);
|
|
|
|
|
|
|
|
// Mark the document accessible as loaded if its DOM document was loaded at
|
|
|
|
// this point (this can happen because a11y is started late or DOM document
|
|
|
|
// having no container was loaded.
|
2019-01-02 16:05:23 +03:00
|
|
|
if (mDocumentNode->GetReadyStateEnum() == dom::Document::READYSTATE_COMPLETE)
|
2012-11-20 07:54:41 +04:00
|
|
|
mLoadState |= eDOMLoaded;
|
|
|
|
|
|
|
|
AddEventListeners();
|
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
void DocAccessible::Shutdown() {
|
2019-04-13 15:13:15 +03:00
|
|
|
if (!mPresShell) { // already shutdown
|
2010-06-12 08:04:35 +04:00
|
|
|
return;
|
2019-04-13 15:13:15 +03:00
|
|
|
}
|
2003-04-15 12:45:55 +04:00
|
|
|
|
2012-10-04 13:57:09 +04:00
|
|
|
#ifdef A11Y_LOG
|
2012-05-23 13:21:40 +04:00
|
|
|
if (logging::IsEnabled(logging::eDocDestroy))
|
2012-11-23 02:15:25 +04:00
|
|
|
logging::DocDestroy("document shutdown", mDocumentNode, this);
|
2012-05-23 13:21:40 +04:00
|
|
|
#endif
|
2010-06-08 20:39:58 +04:00
|
|
|
|
2016-04-14 18:15:16 +03:00
|
|
|
// Mark the document as shutdown before AT is notified about the document
|
|
|
|
// removal from its container (valid for root documents on ATK and due to
|
|
|
|
// some reason for MSAA, refer to bug 757392 for details).
|
|
|
|
mStateFlags |= eIsDefunct;
|
|
|
|
|
2011-01-18 11:03:38 +03:00
|
|
|
if (mNotificationController) {
|
|
|
|
mNotificationController->Shutdown();
|
2012-07-30 18:20:58 +04:00
|
|
|
mNotificationController = nullptr;
|
2010-02-26 22:02:39 +03:00
|
|
|
}
|
2010-01-27 14:42:08 +03:00
|
|
|
|
2003-05-15 12:37:38 +04:00
|
|
|
RemoveEventListeners();
|
|
|
|
|
2010-09-09 18:44:56 +04:00
|
|
|
if (mParent) {
|
2012-05-27 13:01:40 +04:00
|
|
|
DocAccessible* parentDocument = mParent->Document();
|
2010-09-09 18:44:56 +04:00
|
|
|
if (parentDocument) parentDocument->RemoveChildDocument(this);
|
|
|
|
|
2010-06-08 20:39:58 +04:00
|
|
|
mParent->RemoveChild(this);
|
2018-03-12 23:03:19 +03:00
|
|
|
MOZ_ASSERT(!mParent, "Parent has to be null!");
|
2010-09-09 18:44:56 +04:00
|
|
|
}
|
|
|
|
|
2010-11-09 22:34:25 +03:00
|
|
|
// Walk the array backwards because child documents remove themselves from the
|
|
|
|
// array as they are shutdown.
|
2012-08-22 19:56:38 +04:00
|
|
|
int32_t childDocCount = mChildDocuments.Length();
|
|
|
|
for (int32_t idx = childDocCount - 1; idx >= 0; idx--)
|
2010-10-28 13:34:26 +04:00
|
|
|
mChildDocuments[idx]->Shutdown();
|
|
|
|
|
2010-09-09 18:44:56 +04:00
|
|
|
mChildDocuments.Clear();
|
2010-06-08 20:39:58 +04:00
|
|
|
|
2014-03-08 01:35:19 +04:00
|
|
|
// XXX thinking about ordering?
|
2015-06-08 18:38:40 +03:00
|
|
|
if (mIPCDoc) {
|
|
|
|
MOZ_ASSERT(IPCAccessibilityActive());
|
2015-06-02 17:30:51 +03:00
|
|
|
mIPCDoc->Shutdown();
|
2014-03-08 01:35:19 +04:00
|
|
|
MOZ_ASSERT(!mIPCDoc);
|
|
|
|
}
|
|
|
|
|
2012-02-02 10:14:51 +04:00
|
|
|
if (mVirtualCursor) {
|
|
|
|
mVirtualCursor->RemoveObserver(this);
|
2012-07-30 18:20:58 +04:00
|
|
|
mVirtualCursor = nullptr;
|
2012-02-02 10:14:51 +04:00
|
|
|
}
|
|
|
|
|
2012-11-03 03:57:58 +04:00
|
|
|
mPresShell->SetDocAccessible(nullptr);
|
2012-07-30 18:20:58 +04:00
|
|
|
mPresShell = nullptr; // Avoid reentrancy
|
2003-04-15 12:45:55 +04:00
|
|
|
|
2018-10-30 03:17:04 +03:00
|
|
|
mDependentIDsHashes.Clear();
|
2010-10-21 08:16:10 +04:00
|
|
|
mNodeToAccessibleMap.Clear();
|
2017-03-20 16:42:39 +03:00
|
|
|
|
2019-05-23 01:03:44 +03:00
|
|
|
mAnchorJumpElm = nullptr;
|
|
|
|
mInvalidationList.Clear();
|
|
|
|
|
2017-03-20 16:42:39 +03:00
|
|
|
for (auto iter = mAccessibleCache.Iter(); !iter.Done(); iter.Next()) {
|
|
|
|
Accessible* accessible = iter.Data();
|
|
|
|
MOZ_ASSERT(accessible);
|
|
|
|
if (accessible && !accessible->IsDefunct()) {
|
|
|
|
// Unlink parent to avoid its cleaning overhead in shutdown.
|
|
|
|
accessible->mParent = nullptr;
|
|
|
|
accessible->Shutdown();
|
|
|
|
}
|
|
|
|
iter.Remove();
|
|
|
|
}
|
2007-12-27 08:13:40 +03:00
|
|
|
|
2012-05-31 12:04:41 +04:00
|
|
|
HyperTextAccessibleWrap::Shutdown();
|
2010-10-28 13:34:26 +04:00
|
|
|
|
2018-03-08 20:21:31 +03:00
|
|
|
GetAccService()->NotifyOfDocumentShutdown(this, mDocumentNode);
|
|
|
|
mDocumentNode = nullptr;
|
2003-04-02 02:18:29 +04:00
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
nsIFrame* DocAccessible::GetFrame() const {
|
2012-07-30 18:20:58 +04:00
|
|
|
nsIFrame* root = nullptr;
|
2019-04-13 15:13:15 +03:00
|
|
|
if (mPresShell) {
|
|
|
|
root = mPresShell->GetRootFrame();
|
|
|
|
}
|
2003-04-02 02:18:29 +04:00
|
|
|
|
|
|
|
return root;
|
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
// DocAccessible protected member
|
2014-09-16 21:30:23 +04:00
|
|
|
nsRect DocAccessible::RelativeBounds(nsIFrame** aRelativeFrame) const {
|
2003-04-02 02:18:29 +04:00
|
|
|
*aRelativeFrame = GetFrame();
|
2003-05-19 13:07:41 +04:00
|
|
|
|
2019-01-02 16:05:23 +03:00
|
|
|
dom::Document* document = mDocumentNode;
|
|
|
|
dom::Document* parentDoc = nullptr;
|
2003-05-19 13:07:41 +04:00
|
|
|
|
2014-09-16 21:30:23 +04:00
|
|
|
nsRect bounds;
|
2003-05-19 13:07:41 +04:00
|
|
|
while (document) {
|
2019-04-13 15:13:15 +03:00
|
|
|
PresShell* presShell = document->GetPresShell();
|
2019-03-29 18:12:47 +03:00
|
|
|
if (!presShell) {
|
|
|
|
return nsRect();
|
|
|
|
}
|
2003-05-19 13:07:41 +04:00
|
|
|
|
2009-09-03 07:57:41 +04:00
|
|
|
nsRect scrollPort;
|
2017-09-09 02:36:32 +03:00
|
|
|
nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
|
2009-09-03 07:57:41 +04:00
|
|
|
if (sf) {
|
|
|
|
scrollPort = sf->GetScrollPortRect();
|
|
|
|
} else {
|
2010-01-21 04:07:35 +03:00
|
|
|
nsIFrame* rootFrame = presShell->GetRootFrame();
|
2014-09-16 21:30:23 +04:00
|
|
|
if (!rootFrame) return nsRect();
|
|
|
|
|
2010-01-21 04:07:35 +03:00
|
|
|
scrollPort = rootFrame->GetRect();
|
2003-05-19 13:07:41 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (parentDoc) { // After first time thru loop
|
2009-09-03 07:57:41 +04:00
|
|
|
// XXXroc bogus code! scrollPort is relative to the viewport of
|
|
|
|
// this document, but we're intersecting rectangles derived from
|
|
|
|
// multiple documents and assuming they're all in the same coordinate
|
|
|
|
// system. See bug 514117.
|
2014-09-16 21:30:23 +04:00
|
|
|
bounds.IntersectRect(scrollPort, bounds);
|
2003-05-19 13:07:41 +04:00
|
|
|
} else { // First time through loop
|
2014-09-16 21:30:23 +04:00
|
|
|
bounds = scrollPort;
|
2003-05-19 13:07:41 +04:00
|
|
|
}
|
|
|
|
|
2019-07-26 19:48:31 +03:00
|
|
|
document = parentDoc = document->GetInProcessParentDocument();
|
2003-05-19 13:07:41 +04:00
|
|
|
}
|
2014-09-16 21:30:23 +04:00
|
|
|
|
|
|
|
return bounds;
|
2003-04-02 02:18:29 +04:00
|
|
|
}
|
|
|
|
|
2012-11-20 07:54:41 +04:00
|
|
|
// DocAccessible protected member
|
|
|
|
nsresult DocAccessible::AddEventListeners() {
|
2016-04-12 11:09:41 +03:00
|
|
|
nsCOMPtr<nsIDocShell> docShell(mDocumentNode->GetDocShell());
|
2012-11-20 07:54:41 +04:00
|
|
|
|
2012-11-20 19:33:30 +04:00
|
|
|
// We want to add a command observer only if the document is content and has
|
|
|
|
// an editor.
|
2016-04-12 11:09:41 +03:00
|
|
|
if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
|
2019-04-03 15:51:38 +03:00
|
|
|
RefPtr<nsCommandManager> commandManager = docShell->GetCommandManager();
|
2012-11-20 19:33:30 +04:00
|
|
|
if (commandManager)
|
2012-11-20 07:54:41 +04:00
|
|
|
commandManager->AddCommandObserver(this, "obs_documentCreated");
|
|
|
|
}
|
|
|
|
|
2013-03-07 16:16:10 +04:00
|
|
|
SelectionMgr()->AddDocSelectionListener(mPresShell);
|
2012-11-20 07:54:41 +04:00
|
|
|
|
2012-11-20 19:33:30 +04:00
|
|
|
// Add document observer.
|
2012-11-23 02:15:25 +04:00
|
|
|
mDocumentNode->AddObserver(this);
|
2012-11-20 07:54:41 +04:00
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
// DocAccessible protected member
|
|
|
|
nsresult DocAccessible::RemoveEventListeners() {
|
2003-04-15 12:45:55 +04:00
|
|
|
// Remove listeners associated with content documents
|
2012-11-23 02:15:25 +04:00
|
|
|
NS_ASSERTION(mDocumentNode, "No document during removal of listeners.");
|
2009-06-28 21:45:04 +04:00
|
|
|
|
2012-11-23 02:15:25 +04:00
|
|
|
if (mDocumentNode) {
|
|
|
|
mDocumentNode->RemoveObserver(this);
|
2009-06-28 21:45:04 +04:00
|
|
|
|
2016-04-12 11:09:41 +03:00
|
|
|
nsCOMPtr<nsIDocShell> docShell(mDocumentNode->GetDocShell());
|
|
|
|
NS_ASSERTION(docShell, "doc should support nsIDocShellTreeItem.");
|
2009-06-28 21:45:04 +04:00
|
|
|
|
2016-04-12 11:09:41 +03:00
|
|
|
if (docShell) {
|
|
|
|
if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
|
2019-04-03 15:51:38 +03:00
|
|
|
RefPtr<nsCommandManager> commandManager = docShell->GetCommandManager();
|
2009-06-28 21:45:04 +04:00
|
|
|
if (commandManager) {
|
|
|
|
commandManager->RemoveCommandObserver(this, "obs_documentCreated");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2003-05-15 12:37:38 +04:00
|
|
|
|
2003-06-16 14:22:40 +04:00
|
|
|
if (mScrollWatchTimer) {
|
|
|
|
mScrollWatchTimer->Cancel();
|
2012-07-30 18:20:58 +04:00
|
|
|
mScrollWatchTimer = nullptr;
|
2008-01-18 23:36:44 +03:00
|
|
|
NS_RELEASE_THIS(); // Kung fu death grip
|
2003-06-16 14:22:40 +04:00
|
|
|
}
|
|
|
|
|
2013-03-07 16:16:10 +04:00
|
|
|
SelectionMgr()->RemoveDocSelectionListener(mPresShell);
|
2003-07-22 18:55:22 +04:00
|
|
|
return NS_OK;
|
2003-04-15 12:45:55 +04:00
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
void DocAccessible::ScrollTimerCallback(nsITimer* aTimer, void* aClosure) {
|
|
|
|
DocAccessible* docAcc = reinterpret_cast<DocAccessible*>(aClosure);
|
2003-04-15 12:45:55 +04:00
|
|
|
|
2018-08-15 20:07:00 +03:00
|
|
|
if (docAcc) {
|
2020-02-17 07:09:34 +03:00
|
|
|
// Dispatch a scroll-end for all entries in table. They have not
|
|
|
|
// been scrolled in at least `kScrollEventInterval`.
|
|
|
|
for (auto iter = docAcc->mLastScrollingDispatch.Iter(); !iter.Done();
|
|
|
|
iter.Next()) {
|
|
|
|
docAcc->DispatchScrollingEvent(iter.Key(),
|
|
|
|
nsIAccessibleEvent::EVENT_SCROLLING_END);
|
|
|
|
iter.Remove();
|
|
|
|
}
|
2018-08-15 20:07:00 +03:00
|
|
|
|
2003-05-01 14:25:45 +04:00
|
|
|
if (docAcc->mScrollWatchTimer) {
|
2012-07-30 18:20:58 +04:00
|
|
|
docAcc->mScrollWatchTimer = nullptr;
|
2008-01-18 23:36:44 +03:00
|
|
|
NS_RELEASE(docAcc); // Release kung fu death grip
|
2003-05-01 14:25:45 +04:00
|
|
|
}
|
2003-04-15 12:45:55 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-17 07:09:34 +03:00
|
|
|
void DocAccessible::HandleScroll(nsINode* aTarget) {
|
2018-08-15 20:07:00 +03:00
|
|
|
const uint32_t kScrollEventInterval = 100;
|
2020-02-17 07:09:34 +03:00
|
|
|
TimeStamp now = TimeStamp::Now();
|
|
|
|
TimeStamp lastDispatch;
|
|
|
|
// If we haven't dispatched a scrolling event for a target in at least
|
|
|
|
// kScrollEventInterval milliseconds, dispatch one now.
|
|
|
|
if (!mLastScrollingDispatch.Get(aTarget, &lastDispatch) ||
|
|
|
|
(now - lastDispatch).ToMilliseconds() >= kScrollEventInterval) {
|
|
|
|
DispatchScrollingEvent(aTarget, nsIAccessibleEvent::EVENT_SCROLLING);
|
|
|
|
mLastScrollingDispatch.Put(aTarget, now);
|
2018-08-15 20:07:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// If timer callback is still pending, push it 100ms into the future.
|
|
|
|
// When scrolling ends and we don't fire this callback anymore, the
|
|
|
|
// timer callback will fire and dispatch an EVENT_SCROLLING_END.
|
2003-04-15 12:45:55 +04:00
|
|
|
if (mScrollWatchTimer) {
|
2018-08-15 20:07:00 +03:00
|
|
|
mScrollWatchTimer->SetDelay(kScrollEventInterval);
|
2003-04-15 12:45:55 +04:00
|
|
|
} else {
|
2017-09-25 05:57:48 +03:00
|
|
|
NS_NewTimerWithFuncCallback(getter_AddRefs(mScrollWatchTimer),
|
2018-08-15 20:07:00 +03:00
|
|
|
ScrollTimerCallback, this, kScrollEventInterval,
|
|
|
|
nsITimer::TYPE_ONE_SHOT,
|
2017-09-25 05:57:48 +03:00
|
|
|
"a11y::DocAccessible::ScrollPositionDidChange");
|
2003-04-15 12:45:55 +04:00
|
|
|
if (mScrollWatchTimer) {
|
2008-01-18 23:36:44 +03:00
|
|
|
NS_ADDREF_THIS(); // Kung fu death grip
|
2003-04-15 12:45:55 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-12-10 22:12:19 +03:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// nsIObserver
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
NS_IMETHODIMP
|
|
|
|
DocAccessible::Observe(nsISupports* aSubject, const char* aTopic,
|
2014-01-04 19:02:17 +04:00
|
|
|
const char16_t* aData) {
|
2007-08-14 20:25:24 +04:00
|
|
|
if (!nsCRT::strcmp(aTopic, "obs_documentCreated")) {
|
|
|
|
// State editable will now be set, readonly is now clear
|
2010-06-02 16:30:08 +04:00
|
|
|
// Normally we only fire delayed events created from the node, not an
|
2010-08-25 06:08:28 +04:00
|
|
|
// accessible object. See the AccStateChangeEvent constructor for details
|
2010-06-02 16:30:08 +04:00
|
|
|
// about this exceptional case.
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> event =
|
2011-10-17 18:59:28 +04:00
|
|
|
new AccStateChangeEvent(this, states::EDITABLE, true);
|
2012-11-20 08:53:38 +04:00
|
|
|
FireDelayedEvent(event);
|
2003-05-15 12:37:38 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2012-02-02 10:14:51 +04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// nsIAccessiblePivotObserver
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2012-05-27 13:01:40 +04:00
|
|
|
DocAccessible::OnPivotChanged(nsIAccessiblePivot* aPivot,
|
|
|
|
nsIAccessible* aOldAccessible, int32_t aOldStart,
|
2018-06-21 14:47:00 +03:00
|
|
|
int32_t aOldEnd, nsIAccessible* aNewAccessible,
|
|
|
|
int32_t aNewStart, int32_t aNewEnd,
|
2014-08-15 05:44:59 +04:00
|
|
|
PivotMoveReason aReason,
|
2018-07-16 13:56:00 +03:00
|
|
|
TextBoundaryType aBoundaryType,
|
2014-08-15 05:44:59 +04:00
|
|
|
bool aIsFromUserInput) {
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> event = new AccVCChangeEvent(
|
2014-10-22 04:49:28 +04:00
|
|
|
this, (aOldAccessible ? aOldAccessible->ToInternalAccessible() : nullptr),
|
2018-06-21 14:47:00 +03:00
|
|
|
aOldStart, aOldEnd,
|
|
|
|
(aNewAccessible ? aNewAccessible->ToInternalAccessible() : nullptr),
|
2018-07-16 13:56:00 +03:00
|
|
|
aNewStart, aNewEnd, aReason, aBoundaryType,
|
|
|
|
aIsFromUserInput ? eFromUserInput : eNoUserInput);
|
2012-02-02 10:14:51 +04:00
|
|
|
nsEventShell::FireEvent(event);
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2009-12-10 22:12:19 +03:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2005-06-10 17:57:27 +04:00
|
|
|
// nsIDocumentObserver
|
2003-04-28 14:24:52 +04:00
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(DocAccessible)
|
|
|
|
NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(DocAccessible)
|
2003-04-28 14:24:52 +04:00
|
|
|
|
2018-03-01 14:36:58 +03:00
|
|
|
void DocAccessible::AttributeWillChange(dom::Element* aElement,
|
2012-08-22 19:56:38 +04:00
|
|
|
int32_t aNameSpaceID,
|
2019-01-29 18:27:02 +03:00
|
|
|
nsAtom* aAttribute, int32_t aModType) {
|
2012-05-29 05:18:45 +04:00
|
|
|
Accessible* accessible = GetAccessible(aElement);
|
2011-08-11 15:45:36 +04:00
|
|
|
if (!accessible) {
|
|
|
|
if (aElement != mContent) return;
|
|
|
|
|
|
|
|
accessible = this;
|
|
|
|
}
|
2010-11-18 05:55:44 +03:00
|
|
|
|
2010-12-16 22:29:51 +03:00
|
|
|
// Update dependent IDs cache. Take care of elements that are accessible
|
|
|
|
// because dependent IDs cache doesn't contain IDs from non accessible
|
|
|
|
// elements.
|
2018-06-26 00:20:54 +03:00
|
|
|
if (aModType != dom::MutationEvent_Binding::ADDITION)
|
2015-09-15 19:01:51 +03:00
|
|
|
RemoveDependentIDsFor(accessible, aAttribute);
|
2011-08-11 15:45:36 +04:00
|
|
|
|
2015-10-30 01:08:48 +03:00
|
|
|
if (aAttribute == nsGkAtoms::id) {
|
|
|
|
RelocateARIAOwnedIfNeeded(aElement);
|
|
|
|
}
|
|
|
|
|
2011-08-11 15:45:36 +04:00
|
|
|
// Store the ARIA attribute old value so that it can be used after
|
|
|
|
// attribute change. Note, we assume there's no nested ARIA attribute
|
|
|
|
// changes. If this happens then we should end up with keeping a stack of
|
|
|
|
// old values.
|
|
|
|
|
|
|
|
// XXX TODO: bugs 472142, 472143.
|
|
|
|
// Here we will want to cache whatever attribute values we are interested
|
|
|
|
// in, such as the existence of aria-pressed for button (so we know if we
|
|
|
|
// need to newly expose it as a toggle button) etc.
|
2011-06-04 01:35:17 +04:00
|
|
|
if (aAttribute == nsGkAtoms::aria_checked ||
|
|
|
|
aAttribute == nsGkAtoms::aria_pressed) {
|
2018-06-26 00:20:54 +03:00
|
|
|
mARIAAttrOldValue = (aModType != dom::MutationEvent_Binding::ADDITION)
|
2012-07-30 18:20:58 +04:00
|
|
|
? nsAccUtils::GetARIAToken(aElement, aAttribute)
|
|
|
|
: nullptr;
|
2013-11-20 22:24:30 +04:00
|
|
|
return;
|
2010-11-18 05:55:44 +03:00
|
|
|
}
|
2013-11-20 22:24:30 +04:00
|
|
|
|
2020-04-24 17:34:09 +03:00
|
|
|
if (aAttribute == nsGkAtoms::aria_disabled || aAttribute == nsGkAtoms::href ||
|
2020-04-16 01:33:48 +03:00
|
|
|
aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::tabindex ||
|
|
|
|
aAttribute == nsGkAtoms::contenteditable) {
|
|
|
|
mPrevStateBits = accessible->State();
|
|
|
|
}
|
2009-06-29 22:36:25 +04:00
|
|
|
}
|
|
|
|
|
2018-03-01 14:36:58 +03:00
|
|
|
void DocAccessible::NativeAnonymousChildListChange(nsIContent* aContent,
|
2019-08-15 20:30:45 +03:00
|
|
|
bool aIsRemove) {
|
|
|
|
if (aIsRemove) {
|
|
|
|
#ifdef A11Y_LOG
|
|
|
|
if (logging::IsEnabled(logging::eTree)) {
|
|
|
|
logging::MsgBegin("TREE", "Anonymous content removed; doc: %p", this);
|
|
|
|
logging::Node("node", aContent);
|
|
|
|
logging::MsgEnd();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
ContentRemoved(aContent);
|
|
|
|
}
|
|
|
|
}
|
2015-09-24 18:23:32 +03:00
|
|
|
|
2018-03-01 14:36:58 +03:00
|
|
|
void DocAccessible::AttributeChanged(dom::Element* aElement,
|
2017-10-03 01:05:19 +03:00
|
|
|
int32_t aNameSpaceID, nsAtom* aAttribute,
|
2015-07-25 09:01:19 +03:00
|
|
|
int32_t aModType,
|
|
|
|
const nsAttrValue* aOldValue) {
|
2011-01-18 07:15:05 +03:00
|
|
|
NS_ASSERTION(!IsDefunct(),
|
|
|
|
"Attribute changed called on defunct document accessible!");
|
|
|
|
|
2010-12-18 21:33:00 +03:00
|
|
|
// Proceed even if the element is not accessible because element may become
|
|
|
|
// accessible if it gets certain attribute.
|
|
|
|
if (UpdateAccessibleOnAttrChange(aElement, aAttribute)) return;
|
2007-09-19 01:36:41 +04:00
|
|
|
|
2018-07-04 17:18:43 +03:00
|
|
|
// Update the accessible tree on aria-hidden change. Make sure to not create
|
|
|
|
// a tree under aria-hidden='true'.
|
|
|
|
if (aAttribute == nsGkAtoms::aria_hidden) {
|
|
|
|
if (aria::HasDefinedARIAHidden(aElement)) {
|
|
|
|
ContentRemoved(aElement);
|
2018-10-18 11:02:51 +03:00
|
|
|
} else {
|
|
|
|
ContentInserted(aElement, aElement->GetNextSibling());
|
2018-07-04 17:18:43 +03:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-12-18 21:33:00 +03:00
|
|
|
// Ignore attribute change if the element doesn't have an accessible (at all
|
|
|
|
// or still) iff the element is not a root content of this document accessible
|
|
|
|
// (which is treated as attribute change on this document accessible).
|
2011-01-18 07:15:05 +03:00
|
|
|
// Note: we don't bail if all the content hasn't finished loading because
|
|
|
|
// these attributes are changing for a loaded part of the content.
|
2012-05-29 05:18:45 +04:00
|
|
|
Accessible* accessible = GetAccessible(aElement);
|
2011-02-22 04:56:57 +03:00
|
|
|
if (!accessible) {
|
|
|
|
if (mContent != aElement) return;
|
|
|
|
|
|
|
|
accessible = this;
|
|
|
|
}
|
2010-12-16 22:29:51 +03:00
|
|
|
|
2016-08-19 18:29:33 +03:00
|
|
|
MOZ_ASSERT(accessible->IsBoundToParent() || accessible->IsDoc(),
|
|
|
|
"DOM attribute change on an accessible detached from the tree");
|
2016-08-04 17:49:21 +03:00
|
|
|
|
2010-12-18 21:33:00 +03:00
|
|
|
// Fire accessible events iff there's an accessible, otherwise we consider
|
|
|
|
// the accessible state wasn't changed, i.e. its state is initial state.
|
2020-04-24 17:34:09 +03:00
|
|
|
AttributeChangedImpl(accessible, aNameSpaceID, aAttribute, aModType);
|
2010-12-18 21:33:00 +03:00
|
|
|
|
2010-12-16 22:29:51 +03:00
|
|
|
// Update dependent IDs cache. Take care of accessible elements because no
|
|
|
|
// accessible element means either the element is not accessible at all or
|
|
|
|
// its accessible will be created later. It doesn't make sense to keep
|
|
|
|
// dependent IDs for non accessible elements. For the second case we'll update
|
|
|
|
// dependent IDs cache when its accessible is created.
|
2018-06-26 00:20:54 +03:00
|
|
|
if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
|
|
|
|
aModType == dom::MutationEvent_Binding::ADDITION) {
|
2015-09-15 19:01:51 +03:00
|
|
|
AddDependentIDsFor(accessible, aAttribute);
|
2010-11-18 05:55:44 +03:00
|
|
|
}
|
2007-09-19 01:36:41 +04:00
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
// DocAccessible protected member
|
2012-11-20 08:53:38 +04:00
|
|
|
void DocAccessible::AttributeChangedImpl(Accessible* aAccessible,
|
2017-10-03 01:05:19 +03:00
|
|
|
int32_t aNameSpaceID,
|
2020-04-24 17:34:09 +03:00
|
|
|
nsAtom* aAttribute, int32_t aModType) {
|
2007-04-17 08:45:42 +04:00
|
|
|
// Fire accessible event after short timer, because we need to wait for
|
|
|
|
// DOM attribute & resulting layout to actually change. Otherwise,
|
|
|
|
// assistive technology will retrieve the wrong state/value/selection info.
|
|
|
|
|
2003-04-28 14:24:52 +04:00
|
|
|
// XXX todo
|
2005-01-28 05:35:26 +03:00
|
|
|
// We still need to handle special HTML cases here
|
2003-04-28 14:24:52 +04:00
|
|
|
// For example, if an <img>'s usemap attribute is modified
|
|
|
|
// Otherwise it may just be a state change, for example an object changing
|
2005-01-28 05:35:26 +03:00
|
|
|
// its visibility
|
2009-01-12 20:20:34 +03:00
|
|
|
//
|
|
|
|
// XXX todo: report aria state changes for "undefined" literal value changes
|
|
|
|
// filed as bug 472142
|
|
|
|
//
|
|
|
|
// XXX todo: invalidate accessible when aria state changes affect exposed
|
|
|
|
// role filed as bug 472143
|
2010-12-16 22:29:51 +03:00
|
|
|
|
2009-09-16 05:01:47 +04:00
|
|
|
// Universal boolean properties that don't require a role. Fire the state
|
|
|
|
// change when disabled or aria-disabled attribute is set.
|
2013-11-20 22:24:30 +04:00
|
|
|
// Note. Checking the XUL or HTML namespace would not seem to gain us
|
|
|
|
// anything, because disabled attribute really is going to mean the same
|
|
|
|
// thing in any namespace.
|
|
|
|
// Note. We use the attribute instead of the disabled state bit because
|
|
|
|
// ARIA's aria-disabled does not affect the disabled state bit.
|
2011-06-04 01:35:17 +04:00
|
|
|
if (aAttribute == nsGkAtoms::disabled ||
|
|
|
|
aAttribute == nsGkAtoms::aria_disabled) {
|
2020-04-16 01:33:48 +03:00
|
|
|
// disabled can affect focusable state
|
|
|
|
aAccessible->MaybeFireFocusableStateChange(
|
|
|
|
(mPrevStateBits & states::FOCUSABLE) != 0);
|
|
|
|
|
2013-11-20 22:24:30 +04:00
|
|
|
// Do nothing if state wasn't changed (like @aria-disabled was removed but
|
|
|
|
// @disabled is still presented).
|
2020-04-16 01:33:48 +03:00
|
|
|
uint64_t unavailableState = (aAccessible->State() & states::UNAVAILABLE);
|
|
|
|
if ((mPrevStateBits & states::UNAVAILABLE) == unavailableState) {
|
|
|
|
return;
|
|
|
|
}
|
2009-09-16 05:01:47 +04:00
|
|
|
|
2020-04-16 01:33:48 +03:00
|
|
|
RefPtr<AccEvent> enabledChangeEvent = new AccStateChangeEvent(
|
|
|
|
aAccessible, states::ENABLED, !unavailableState);
|
2012-11-20 08:53:38 +04:00
|
|
|
FireDelayedEvent(enabledChangeEvent);
|
2009-09-16 05:01:47 +04:00
|
|
|
|
2020-04-16 01:33:48 +03:00
|
|
|
RefPtr<AccEvent> sensitiveChangeEvent = new AccStateChangeEvent(
|
|
|
|
aAccessible, states::SENSITIVE, !unavailableState);
|
2012-11-20 08:53:38 +04:00
|
|
|
FireDelayedEvent(sensitiveChangeEvent);
|
2020-04-16 01:33:48 +03:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aAttribute == nsGkAtoms::tabindex) {
|
|
|
|
// Fire a focusable state change event if the previous state was different.
|
|
|
|
// It may be the same if tabindex is on a redundantly focusable element.
|
|
|
|
aAccessible->MaybeFireFocusableStateChange(
|
|
|
|
(mPrevStateBits & states::FOCUSABLE));
|
2007-04-19 21:49:13 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-09-10 19:16:53 +03:00
|
|
|
// When a details object has its open attribute changed
|
|
|
|
// we should fire a state-change event on the accessible of
|
|
|
|
// its main summary
|
|
|
|
if (aAttribute == nsGkAtoms::open) {
|
|
|
|
// FromDetails checks if the given accessible belongs to
|
|
|
|
// a details frame and also locates the accessible of its
|
|
|
|
// main summary.
|
|
|
|
if (HTMLSummaryAccessible* summaryAccessible =
|
|
|
|
HTMLSummaryAccessible::FromDetails(aAccessible)) {
|
|
|
|
RefPtr<AccEvent> expandedChangeEvent =
|
|
|
|
new AccStateChangeEvent(summaryAccessible, states::EXPANDED);
|
|
|
|
FireDelayedEvent(expandedChangeEvent);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-09-25 05:19:03 +04:00
|
|
|
// Check for namespaced ARIA attribute
|
2007-12-12 05:10:26 +03:00
|
|
|
if (aNameSpaceID == kNameSpaceID_None) {
|
2007-09-25 05:19:03 +04:00
|
|
|
// Check for hyphenated aria-foo property?
|
2010-03-08 18:45:00 +03:00
|
|
|
if (StringBeginsWith(nsDependentAtomString(aAttribute),
|
|
|
|
NS_LITERAL_STRING("aria-"))) {
|
2012-11-20 08:53:38 +04:00
|
|
|
ARIAAttributeChanged(aAccessible, aAttribute);
|
2007-09-25 05:19:03 +04:00
|
|
|
}
|
|
|
|
}
|
2007-04-19 21:49:13 +04:00
|
|
|
|
2014-04-12 08:03:22 +04:00
|
|
|
// Fire name change and description change events. XXX: it's not complete and
|
|
|
|
// dupes the code logic of accessible name and description calculation, we do
|
|
|
|
// that for performance reasons.
|
|
|
|
if (aAttribute == nsGkAtoms::aria_label) {
|
2012-11-20 08:53:38 +04:00
|
|
|
FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
|
2008-02-20 10:45:14 +03:00
|
|
|
return;
|
|
|
|
}
|
2005-08-10 05:51:39 +04:00
|
|
|
|
2014-04-12 08:03:22 +04:00
|
|
|
if (aAttribute == nsGkAtoms::aria_describedby) {
|
|
|
|
FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-12-07 21:13:50 +03:00
|
|
|
dom::Element* elm = aAccessible->GetContent()->AsElement();
|
2014-04-12 08:03:22 +04:00
|
|
|
if (aAttribute == nsGkAtoms::aria_labelledby &&
|
|
|
|
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label)) {
|
|
|
|
FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aAttribute == nsGkAtoms::alt &&
|
|
|
|
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
|
|
|
|
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby)) {
|
|
|
|
FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aAttribute == nsGkAtoms::title) {
|
|
|
|
if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
|
|
|
|
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) &&
|
|
|
|
!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) {
|
|
|
|
FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_describedby))
|
|
|
|
FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE,
|
|
|
|
aAccessible);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-06-04 01:35:17 +04:00
|
|
|
if (aAttribute == nsGkAtoms::aria_busy) {
|
2014-04-12 08:03:22 +04:00
|
|
|
bool isOn = elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
|
|
|
|
eCaseMatters);
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> event =
|
2012-11-20 08:53:38 +04:00
|
|
|
new AccStateChangeEvent(aAccessible, states::BUSY, isOn);
|
|
|
|
FireDelayedEvent(event);
|
2011-05-24 18:02:30 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-10-30 01:08:48 +03:00
|
|
|
if (aAttribute == nsGkAtoms::id) {
|
|
|
|
RelocateARIAOwnedIfNeeded(elm);
|
2018-09-05 07:43:18 +03:00
|
|
|
ARIAActiveDescendantIDMaybeMoved(elm);
|
2015-10-30 01:08:48 +03:00
|
|
|
}
|
|
|
|
|
2011-11-01 04:52:27 +04:00
|
|
|
// ARIA or XUL selection
|
2015-03-03 14:08:59 +03:00
|
|
|
if ((aAccessible->GetContent()->IsXULElement() &&
|
|
|
|
aAttribute == nsGkAtoms::selected) ||
|
2011-06-04 01:35:17 +04:00
|
|
|
aAttribute == nsGkAtoms::aria_selected) {
|
2012-05-29 05:18:45 +04:00
|
|
|
Accessible* widget =
|
2012-11-20 08:53:38 +04:00
|
|
|
nsAccUtils::GetSelectableContainer(aAccessible, aAccessible->State());
|
2011-11-01 04:52:27 +04:00
|
|
|
if (widget) {
|
|
|
|
AccSelChangeEvent::SelChangeType selChangeType =
|
2012-11-20 08:53:38 +04:00
|
|
|
elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
|
|
|
|
eCaseMatters)
|
2011-11-01 04:52:27 +04:00
|
|
|
? AccSelChangeEvent::eSelectionAdd
|
|
|
|
: AccSelChangeEvent::eSelectionRemove;
|
|
|
|
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> event =
|
2012-11-20 08:53:38 +04:00
|
|
|
new AccSelChangeEvent(widget, aAccessible, selChangeType);
|
|
|
|
FireDelayedEvent(event);
|
2005-07-14 18:20:21 +04:00
|
|
|
}
|
2012-11-20 08:53:38 +04:00
|
|
|
|
2011-11-01 04:52:27 +04:00
|
|
|
return;
|
2005-07-14 18:20:21 +04:00
|
|
|
}
|
2007-08-14 20:25:24 +04:00
|
|
|
|
2011-06-04 01:35:17 +04:00
|
|
|
if (aAttribute == nsGkAtoms::contenteditable) {
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> editableChangeEvent =
|
2012-11-20 08:53:38 +04:00
|
|
|
new AccStateChangeEvent(aAccessible, states::EDITABLE);
|
|
|
|
FireDelayedEvent(editableChangeEvent);
|
2020-04-16 01:33:48 +03:00
|
|
|
// Fire a focusable state change event if the previous state was different.
|
|
|
|
// It may be the same if contenteditable is set on a node that doesn't
|
|
|
|
// support it. Like an <input>.
|
|
|
|
aAccessible->MaybeFireFocusableStateChange(
|
|
|
|
(mPrevStateBits & states::FOCUSABLE));
|
2007-08-14 20:25:24 +04:00
|
|
|
return;
|
|
|
|
}
|
2012-10-16 07:05:20 +04:00
|
|
|
|
|
|
|
if (aAttribute == nsGkAtoms::value) {
|
2012-11-20 08:53:38 +04:00
|
|
|
if (aAccessible->IsProgress())
|
|
|
|
FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
|
2020-04-24 17:34:09 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aModType == dom::MutationEvent_Binding::REMOVAL ||
|
|
|
|
aModType == dom::MutationEvent_Binding::ADDITION) {
|
|
|
|
if (aAttribute == nsGkAtoms::href) {
|
|
|
|
if (aAccessible->IsHTMLLink() &&
|
|
|
|
!nsCoreUtils::HasClickListener(aAccessible->GetContent())) {
|
|
|
|
RefPtr<AccEvent> linkedChangeEvent =
|
|
|
|
new AccStateChangeEvent(aAccessible, states::LINKED);
|
|
|
|
FireDelayedEvent(linkedChangeEvent);
|
|
|
|
// Fire a focusable state change event if the previous state was
|
|
|
|
// different. It may be the same if there is tabindex on this link.
|
|
|
|
aAccessible->MaybeFireFocusableStateChange(
|
|
|
|
(mPrevStateBits & states::FOCUSABLE));
|
|
|
|
}
|
|
|
|
}
|
2012-10-16 07:05:20 +04:00
|
|
|
}
|
2007-04-17 08:45:42 +04:00
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
// DocAccessible protected member
|
2017-10-03 01:05:19 +03:00
|
|
|
void DocAccessible::ARIAAttributeChanged(Accessible* aAccessible,
|
|
|
|
nsAtom* aAttribute) {
|
2010-04-02 17:33:55 +04:00
|
|
|
// Note: For universal/global ARIA states and properties we don't care if
|
|
|
|
// there is an ARIA role present or not.
|
|
|
|
|
2011-06-04 01:35:17 +04:00
|
|
|
if (aAttribute == nsGkAtoms::aria_required) {
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> event =
|
2012-11-20 08:53:38 +04:00
|
|
|
new AccStateChangeEvent(aAccessible, states::REQUIRED);
|
|
|
|
FireDelayedEvent(event);
|
2007-04-17 08:45:42 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-06-04 01:35:17 +04:00
|
|
|
if (aAttribute == nsGkAtoms::aria_invalid) {
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> event =
|
2012-11-20 08:53:38 +04:00
|
|
|
new AccStateChangeEvent(aAccessible, states::INVALID);
|
|
|
|
FireDelayedEvent(event);
|
2007-04-17 08:45:42 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-09-28 05:46:11 +04:00
|
|
|
// The activedescendant universal property redirects accessible focus events
|
|
|
|
// to the element with the id that activedescendant points to. Make sure
|
2018-06-08 17:55:08 +03:00
|
|
|
// the tree up to date before processing. In other words, when a node has just
|
|
|
|
// been inserted, the tree won't be up to date yet, so we must always schedule
|
|
|
|
// an async notification so that a newly inserted node will be present in
|
|
|
|
// the tree.
|
2011-06-04 01:35:17 +04:00
|
|
|
if (aAttribute == nsGkAtoms::aria_activedescendant) {
|
2018-06-08 17:55:08 +03:00
|
|
|
mNotificationController->ScheduleNotification<DocAccessible, Accessible>(
|
2012-11-20 08:53:38 +04:00
|
|
|
this, &DocAccessible::ARIAActiveDescendantChanged, aAccessible);
|
2007-04-17 08:45:42 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-04-02 17:33:55 +04:00
|
|
|
// We treat aria-expanded as a global ARIA state for historical reasons
|
2011-06-04 01:35:17 +04:00
|
|
|
if (aAttribute == nsGkAtoms::aria_expanded) {
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> event =
|
2012-11-20 08:53:38 +04:00
|
|
|
new AccStateChangeEvent(aAccessible, states::EXPANDED);
|
|
|
|
FireDelayedEvent(event);
|
2010-04-02 17:33:55 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-04-24 21:39:03 +04:00
|
|
|
// For aria attributes like drag and drop changes we fire a generic attribute
|
|
|
|
// change event; at least until native API comes up with a more meaningful
|
|
|
|
// event.
|
2013-04-25 07:48:26 +04:00
|
|
|
uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute);
|
2014-08-28 16:42:06 +04:00
|
|
|
if (!(attrFlags & ATTR_BYPASSOBJ)) {
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> event =
|
2014-08-28 16:42:06 +04:00
|
|
|
new AccObjectAttrChangedEvent(aAccessible, aAttribute);
|
|
|
|
FireDelayedEvent(event);
|
|
|
|
}
|
2012-04-24 21:39:03 +04:00
|
|
|
|
2017-12-07 21:13:50 +03:00
|
|
|
dom::Element* elm = aAccessible->GetContent()->AsElement();
|
2007-04-17 08:45:42 +04:00
|
|
|
|
2011-06-04 01:35:17 +04:00
|
|
|
if (aAttribute == nsGkAtoms::aria_checked ||
|
2014-04-04 12:01:19 +04:00
|
|
|
(aAccessible->IsButton() && aAttribute == nsGkAtoms::aria_pressed)) {
|
2012-11-20 08:53:38 +04:00
|
|
|
const uint64_t kState = (aAttribute == nsGkAtoms::aria_checked)
|
2011-04-10 03:38:06 +04:00
|
|
|
? states::CHECKED
|
|
|
|
: states::PRESSED;
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> event = new AccStateChangeEvent(aAccessible, kState);
|
2012-11-20 08:53:38 +04:00
|
|
|
FireDelayedEvent(event);
|
|
|
|
|
|
|
|
bool wasMixed = (mARIAAttrOldValue == nsGkAtoms::mixed);
|
|
|
|
bool isMixed = elm->AttrValueIs(kNameSpaceID_None, aAttribute,
|
|
|
|
nsGkAtoms::mixed, eCaseMatters);
|
|
|
|
if (isMixed != wasMixed) {
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> event =
|
2012-11-20 08:53:38 +04:00
|
|
|
new AccStateChangeEvent(aAccessible, states::MIXED, isMixed);
|
|
|
|
FireDelayedEvent(event);
|
2007-09-19 01:36:41 +04:00
|
|
|
}
|
2007-08-14 20:15:12 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-06-04 01:35:17 +04:00
|
|
|
if (aAttribute == nsGkAtoms::aria_readonly) {
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> event =
|
2012-11-20 08:53:38 +04:00
|
|
|
new AccStateChangeEvent(aAccessible, states::READONLY);
|
|
|
|
FireDelayedEvent(event);
|
2007-04-17 08:45:42 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-02 23:34:51 +03:00
|
|
|
// Fire text value change event whenever aria-valuetext is changed.
|
|
|
|
if (aAttribute == nsGkAtoms::aria_valuetext) {
|
|
|
|
FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, aAccessible);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fire numeric value change event when aria-valuenow is changed and
|
|
|
|
// aria-valuetext is empty
|
|
|
|
if (aAttribute == nsGkAtoms::aria_valuenow &&
|
|
|
|
(!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext) ||
|
|
|
|
elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_valuetext,
|
|
|
|
nsGkAtoms::_empty, eCaseMatters))) {
|
2012-11-20 08:53:38 +04:00
|
|
|
FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
|
2007-04-17 08:45:42 +04:00
|
|
|
return;
|
2005-01-28 05:35:26 +03:00
|
|
|
}
|
2015-10-30 01:08:48 +03:00
|
|
|
|
2017-07-03 18:02:00 +03:00
|
|
|
if (aAttribute == nsGkAtoms::aria_current) {
|
|
|
|
RefPtr<AccEvent> event =
|
|
|
|
new AccStateChangeEvent(aAccessible, states::CURRENT);
|
|
|
|
FireDelayedEvent(event);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-05-01 00:38:33 +03:00
|
|
|
if (aAttribute == nsGkAtoms::aria_haspopup) {
|
|
|
|
RefPtr<AccEvent> event =
|
|
|
|
new AccStateChangeEvent(aAccessible, states::HASPOPUP);
|
|
|
|
FireDelayedEvent(event);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-10-30 01:08:48 +03:00
|
|
|
if (aAttribute == nsGkAtoms::aria_owns) {
|
|
|
|
mNotificationController->ScheduleRelocation(aAccessible);
|
|
|
|
}
|
2005-06-10 17:57:27 +04:00
|
|
|
}
|
|
|
|
|
2012-11-20 08:53:38 +04:00
|
|
|
void DocAccessible::ARIAActiveDescendantChanged(Accessible* aAccessible) {
|
|
|
|
nsIContent* elm = aAccessible->GetContent();
|
2017-12-07 21:13:50 +03:00
|
|
|
if (elm && elm->IsElement() && aAccessible->IsActiveWidget()) {
|
2011-09-28 05:46:11 +04:00
|
|
|
nsAutoString id;
|
2017-12-07 21:13:50 +03:00
|
|
|
if (elm->AsElement()->GetAttr(kNameSpaceID_None,
|
|
|
|
nsGkAtoms::aria_activedescendant, id)) {
|
2019-07-25 23:44:18 +03:00
|
|
|
dom::Element* activeDescendantElm = IDRefsIterator::GetElem(elm, id);
|
2011-09-28 05:46:11 +04:00
|
|
|
if (activeDescendantElm) {
|
2012-05-29 05:18:45 +04:00
|
|
|
Accessible* activeDescendant = GetAccessible(activeDescendantElm);
|
2011-09-28 05:46:11 +04:00
|
|
|
if (activeDescendant) {
|
|
|
|
FocusMgr()->ActiveItemChanged(activeDescendant, false);
|
2012-10-04 13:57:09 +04:00
|
|
|
#ifdef A11Y_LOG
|
2012-09-27 03:53:23 +04:00
|
|
|
if (logging::IsEnabled(logging::eFocus))
|
|
|
|
logging::ActiveItemChangeCausedBy("ARIA activedescedant changed",
|
|
|
|
activeDescendant);
|
|
|
|
#endif
|
2018-08-31 10:00:59 +03:00
|
|
|
return;
|
2011-09-28 05:46:11 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-08-31 10:00:59 +03:00
|
|
|
|
|
|
|
// aria-activedescendant was cleared or changed to a non-existent node.
|
|
|
|
// Move focus back to the element itself.
|
|
|
|
FocusMgr()->ActiveItemChanged(aAccessible, false);
|
|
|
|
#ifdef A11Y_LOG
|
|
|
|
if (logging::IsEnabled(logging::eFocus)) {
|
|
|
|
logging::ActiveItemChangeCausedBy("ARIA activedescedant cleared",
|
|
|
|
aAccessible);
|
|
|
|
}
|
|
|
|
#endif
|
2011-09-28 05:46:11 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-01 14:36:58 +03:00
|
|
|
void DocAccessible::ContentAppended(nsIContent* aFirstNewContent) {}
|
2005-06-10 17:57:27 +04:00
|
|
|
|
2019-01-02 16:05:23 +03:00
|
|
|
void DocAccessible::ContentStateChanged(dom::Document* aDocument,
|
2012-05-27 13:01:40 +04:00
|
|
|
nsIContent* aContent,
|
2014-04-03 08:18:36 +04:00
|
|
|
EventStates aStateMask) {
|
2012-11-20 08:53:38 +04:00
|
|
|
Accessible* accessible = GetAccessible(aContent);
|
|
|
|
if (!accessible) return;
|
|
|
|
|
2010-11-08 16:33:18 +03:00
|
|
|
if (aStateMask.HasState(NS_EVENT_STATE_CHECKED)) {
|
2012-11-20 08:53:38 +04:00
|
|
|
Accessible* widget = accessible->ContainerWidget();
|
|
|
|
if (widget && widget->IsSelect()) {
|
|
|
|
AccSelChangeEvent::SelChangeType selChangeType =
|
|
|
|
aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED)
|
|
|
|
? AccSelChangeEvent::eSelectionAdd
|
|
|
|
: AccSelChangeEvent::eSelectionRemove;
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> event =
|
2012-11-20 08:53:38 +04:00
|
|
|
new AccSelChangeEvent(widget, accessible, selChangeType);
|
|
|
|
FireDelayedEvent(event);
|
2013-08-02 20:42:36 +04:00
|
|
|
return;
|
2011-11-01 04:52:27 +04:00
|
|
|
}
|
2013-08-02 20:42:36 +04:00
|
|
|
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> event = new AccStateChangeEvent(
|
2013-08-02 20:42:36 +04:00
|
|
|
accessible, states::CHECKED,
|
|
|
|
aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED));
|
|
|
|
FireDelayedEvent(event);
|
2005-07-26 01:40:31 +04:00
|
|
|
}
|
|
|
|
|
2010-11-08 16:33:18 +03:00
|
|
|
if (aStateMask.HasState(NS_EVENT_STATE_INVALID)) {
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> event =
|
2012-11-20 08:53:38 +04:00
|
|
|
new AccStateChangeEvent(accessible, states::INVALID, true);
|
|
|
|
FireDelayedEvent(event);
|
2012-11-09 21:37:27 +04:00
|
|
|
}
|
|
|
|
|
2020-04-28 21:19:17 +03:00
|
|
|
if (aStateMask.HasState(NS_EVENT_STATE_REQUIRED)) {
|
|
|
|
RefPtr<AccEvent> event =
|
2020-04-28 21:19:39 +03:00
|
|
|
new AccStateChangeEvent(accessible, states::REQUIRED);
|
2020-04-28 21:19:17 +03:00
|
|
|
FireDelayedEvent(event);
|
|
|
|
}
|
|
|
|
|
2012-11-09 21:37:27 +04:00
|
|
|
if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) {
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> event =
|
2012-11-20 08:53:38 +04:00
|
|
|
new AccStateChangeEvent(accessible, states::TRAVERSED, true);
|
|
|
|
FireDelayedEvent(event);
|
2012-11-09 21:37:27 +04:00
|
|
|
}
|
2005-07-26 01:40:31 +04:00
|
|
|
}
|
|
|
|
|
2018-03-01 14:36:58 +03:00
|
|
|
void DocAccessible::CharacterDataWillChange(nsIContent* aContent,
|
2018-02-27 17:30:27 +03:00
|
|
|
const CharacterDataChangeInfo&) {}
|
2007-09-05 12:22:17 +04:00
|
|
|
|
2018-03-01 14:36:58 +03:00
|
|
|
void DocAccessible::CharacterDataChanged(nsIContent* aContent,
|
2018-02-27 17:30:27 +03:00
|
|
|
const CharacterDataChangeInfo&) {}
|
2005-06-10 17:57:27 +04:00
|
|
|
|
2018-03-01 14:36:58 +03:00
|
|
|
void DocAccessible::ContentInserted(nsIContent* aChild) {}
|
2005-06-10 17:57:27 +04:00
|
|
|
|
2018-03-01 14:36:58 +03:00
|
|
|
void DocAccessible::ContentRemoved(nsIContent* aChildNode,
|
2016-07-20 16:40:55 +03:00
|
|
|
nsIContent* aPreviousSiblingNode) {
|
|
|
|
#ifdef A11Y_LOG
|
|
|
|
if (logging::IsEnabled(logging::eTree)) {
|
|
|
|
logging::MsgBegin("TREE", "DOM content removed; doc: %p", this);
|
2018-03-01 14:36:58 +03:00
|
|
|
logging::Node("container node", aChildNode->GetParent());
|
2016-07-20 16:40:55 +03:00
|
|
|
logging::Node("content node", aChildNode);
|
|
|
|
logging::MsgEnd();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
// This one and content removal notification from layout may result in
|
|
|
|
// double processing of same subtrees. If it pops up in profiling, then
|
|
|
|
// consider reusing a document node cache to reject these notifications early.
|
2017-04-25 21:02:41 +03:00
|
|
|
ContentRemoved(aChildNode);
|
2005-06-10 17:57:27 +04:00
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
void DocAccessible::ParentChainChanged(nsIContent* aContent) {}
|
2009-12-10 22:12:19 +03:00
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2012-05-29 05:18:45 +04:00
|
|
|
// Accessible
|
2009-12-10 22:12:19 +03:00
|
|
|
|
2012-10-04 13:57:09 +04:00
|
|
|
#ifdef A11Y_LOG
|
2012-05-27 13:01:40 +04:00
|
|
|
nsresult DocAccessible::HandleAccEvent(AccEvent* aEvent) {
|
2012-05-23 13:21:40 +04:00
|
|
|
if (logging::IsEnabled(logging::eDocLoad))
|
|
|
|
logging::DocLoadEventHandled(aEvent);
|
2009-12-10 22:12:19 +03:00
|
|
|
|
2012-05-31 12:04:41 +04:00
|
|
|
return HyperTextAccessible::HandleAccEvent(aEvent);
|
2009-12-10 22:12:19 +03:00
|
|
|
}
|
2010-06-08 20:39:58 +04:00
|
|
|
#endif
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Public members
|
2009-12-10 22:12:19 +03:00
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
void* DocAccessible::GetNativeWindow() const {
|
2019-04-13 15:13:15 +03:00
|
|
|
if (!mPresShell) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2012-01-10 22:52:14 +04:00
|
|
|
|
2013-01-05 07:12:24 +04:00
|
|
|
nsViewManager* vm = mPresShell->GetViewManager();
|
2012-07-30 18:20:58 +04:00
|
|
|
if (!vm) return nullptr;
|
2012-01-10 22:52:14 +04:00
|
|
|
|
|
|
|
nsCOMPtr<nsIWidget> widget;
|
|
|
|
vm->GetRootWidget(getter_AddRefs(widget));
|
|
|
|
if (widget) return widget->GetNativeData(NS_NATIVE_WINDOW);
|
|
|
|
|
2012-07-30 18:20:58 +04:00
|
|
|
return nullptr;
|
2010-09-17 07:23:17 +04:00
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
Accessible* DocAccessible::GetAccessibleByUniqueIDInSubtree(void* aUniqueID) {
|
2012-05-29 05:18:45 +04:00
|
|
|
Accessible* child = GetAccessibleByUniqueID(aUniqueID);
|
2010-09-09 18:44:56 +04:00
|
|
|
if (child) return child;
|
|
|
|
|
2012-08-22 19:56:38 +04:00
|
|
|
uint32_t childDocCount = mChildDocuments.Length();
|
|
|
|
for (uint32_t childDocIdx = 0; childDocIdx < childDocCount; childDocIdx++) {
|
2012-05-27 13:01:40 +04:00
|
|
|
DocAccessible* childDocument = mChildDocuments.ElementAt(childDocIdx);
|
2011-01-28 07:37:08 +03:00
|
|
|
child = childDocument->GetAccessibleByUniqueIDInSubtree(aUniqueID);
|
2010-09-09 18:44:56 +04:00
|
|
|
if (child) return child;
|
|
|
|
}
|
|
|
|
|
2012-07-30 18:20:58 +04:00
|
|
|
return nullptr;
|
2010-09-09 18:44:56 +04:00
|
|
|
}
|
2009-12-10 22:12:19 +03:00
|
|
|
|
2019-05-20 18:54:56 +03:00
|
|
|
Accessible* DocAccessible::GetAccessibleOrContainer(
|
|
|
|
nsINode* aNode, bool aNoContainerIfPruned) const {
|
2019-03-25 08:04:29 +03:00
|
|
|
if (!aNode || !aNode->GetComposedDoc()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2020-03-02 14:36:42 +03:00
|
|
|
nsINode* start = aNode;
|
|
|
|
if (auto* shadowRoot = dom::ShadowRoot::FromNode(aNode)) {
|
2019-03-25 08:04:29 +03:00
|
|
|
// This can happen, for example, when called within
|
|
|
|
// SelectionManager::ProcessSelectionChanged due to focusing a direct
|
|
|
|
// child of a shadow root.
|
|
|
|
// GetFlattenedTreeParent works on children of a shadow root, but not the
|
|
|
|
// shadow root itself.
|
2020-03-02 14:36:42 +03:00
|
|
|
start = shadowRoot->GetHost();
|
|
|
|
if (!start) {
|
2019-03-25 08:04:29 +03:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
2011-01-28 07:37:38 +03:00
|
|
|
|
2020-03-02 14:36:42 +03:00
|
|
|
for (nsINode* currNode : dom::InclusiveFlatTreeAncestors(*start)) {
|
2018-07-04 17:18:43 +03:00
|
|
|
// No container if is inside of aria-hidden subtree.
|
2019-05-20 18:54:56 +03:00
|
|
|
if (aNoContainerIfPruned && currNode->IsElement() &&
|
2018-07-04 17:18:43 +03:00
|
|
|
aria::HasDefinedARIAHidden(currNode->AsElement())) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2019-05-20 18:54:56 +03:00
|
|
|
// Check if node is in an unselected deck panel
|
|
|
|
if (aNoContainerIfPruned && currNode->IsXULElement()) {
|
|
|
|
if (nsIFrame* frame = currNode->AsContent()->GetPrimaryFrame()) {
|
|
|
|
nsDeckFrame* deckFrame = do_QueryFrame(frame->GetParent());
|
|
|
|
if (deckFrame && deckFrame->GetSelectedBox() != frame) {
|
|
|
|
// If deck is not a <tabpanels>, return null
|
|
|
|
nsIContent* parentFrameContent = deckFrame->GetContent();
|
|
|
|
if (!parentFrameContent ||
|
|
|
|
!parentFrameContent->IsXULElement(nsGkAtoms::tabpanels)) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-26 04:51:45 +03:00
|
|
|
// Check if node is in zero-sized map
|
|
|
|
if (aNoContainerIfPruned && currNode->IsHTMLElement(nsGkAtoms::map)) {
|
|
|
|
if (nsIFrame* frame = currNode->AsContent()->GetPrimaryFrame()) {
|
|
|
|
if (nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame->GetParent())
|
|
|
|
.IsEmpty()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-03 21:59:16 +03:00
|
|
|
if (Accessible* accessible = GetAccessible(currNode)) {
|
|
|
|
return accessible;
|
|
|
|
}
|
2014-08-23 00:10:57 +04:00
|
|
|
}
|
2011-01-28 07:37:38 +03:00
|
|
|
|
2018-01-03 21:59:16 +03:00
|
|
|
return nullptr;
|
2011-01-28 07:37:38 +03:00
|
|
|
}
|
|
|
|
|
2013-07-22 19:58:19 +04:00
|
|
|
Accessible* DocAccessible::GetAccessibleOrDescendant(nsINode* aNode) const {
|
|
|
|
Accessible* acc = GetAccessible(aNode);
|
|
|
|
if (acc) return acc;
|
|
|
|
|
2019-10-03 03:31:13 +03:00
|
|
|
if (aNode == mContent || aNode == mDocumentNode->GetRootElement()) {
|
|
|
|
// If the node is the doc's body or root element, return the doc accessible.
|
|
|
|
return const_cast<DocAccessible*>(this);
|
2019-09-11 21:32:07 +03:00
|
|
|
}
|
|
|
|
|
2019-10-03 03:31:13 +03:00
|
|
|
acc = GetContainerAccessible(aNode);
|
2013-07-22 19:58:19 +04:00
|
|
|
if (acc) {
|
2019-10-03 03:30:06 +03:00
|
|
|
// We access the `mChildren` array directly so that we don't access
|
|
|
|
// lazily created children in places like `XULTreeAccessible` and
|
|
|
|
// `XULTreeGridAccessible`.
|
|
|
|
uint32_t childCnt = acc->mChildren.Length();
|
2013-07-22 19:58:19 +04:00
|
|
|
for (uint32_t idx = 0; idx < childCnt; idx++) {
|
2019-10-03 03:30:06 +03:00
|
|
|
Accessible* child = acc->mChildren.ElementAt(idx);
|
2013-07-22 19:58:19 +04:00
|
|
|
for (nsIContent* elm = child->GetContent();
|
|
|
|
elm && elm != acc->GetContent();
|
|
|
|
elm = elm->GetFlattenedTreeParent()) {
|
|
|
|
if (elm == aNode) return child;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2012-05-29 05:18:45 +04:00
|
|
|
void DocAccessible::BindToDocument(Accessible* aAccessible,
|
2016-03-11 05:13:01 +03:00
|
|
|
const nsRoleMapEntry* aRoleMapEntry) {
|
2010-11-12 22:00:55 +03:00
|
|
|
// Put into DOM node cache.
|
2012-10-13 10:34:21 +04:00
|
|
|
if (aAccessible->IsNodeMapEntry())
|
2012-05-18 21:30:49 +04:00
|
|
|
mNodeToAccessibleMap.Put(aAccessible->GetNode(), aAccessible);
|
2010-11-12 22:00:55 +03:00
|
|
|
|
|
|
|
// Put into unique ID cache.
|
2020-02-25 20:03:36 +03:00
|
|
|
mAccessibleCache.Put(aAccessible->UniqueID(), RefPtr{aAccessible});
|
2010-11-12 22:00:55 +03:00
|
|
|
|
|
|
|
aAccessible->SetRoleMapEntry(aRoleMapEntry);
|
2012-12-28 00:54:28 +04:00
|
|
|
|
2015-10-30 01:08:48 +03:00
|
|
|
if (aAccessible->HasOwnContent()) {
|
2017-09-13 21:04:00 +03:00
|
|
|
AddDependentIDsFor(aAccessible);
|
|
|
|
|
2017-12-07 21:13:50 +03:00
|
|
|
nsIContent* content = aAccessible->GetContent();
|
|
|
|
if (content->IsElement() && content->AsElement()->HasAttr(
|
|
|
|
kNameSpaceID_None, nsGkAtoms::aria_owns)) {
|
2015-10-30 01:08:48 +03:00
|
|
|
mNotificationController->ScheduleRelocation(aAccessible);
|
|
|
|
}
|
|
|
|
}
|
2010-11-12 22:00:55 +03:00
|
|
|
}
|
|
|
|
|
2012-05-29 05:18:45 +04:00
|
|
|
void DocAccessible::UnbindFromDocument(Accessible* aAccessible) {
|
2010-11-20 05:37:40 +03:00
|
|
|
NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()),
|
|
|
|
"Unbinding the unbound accessible!");
|
|
|
|
|
2019-05-15 03:31:16 +03:00
|
|
|
// Fire focus event on accessible having DOM focus if last focus was removed
|
2011-09-28 05:46:11 +04:00
|
|
|
// from the tree.
|
2019-05-15 03:31:16 +03:00
|
|
|
if (FocusMgr()->WasLastFocused(aAccessible)) {
|
2012-07-30 18:20:58 +04:00
|
|
|
FocusMgr()->ActiveItemChanged(nullptr);
|
2012-10-04 13:57:09 +04:00
|
|
|
#ifdef A11Y_LOG
|
2012-09-27 03:53:23 +04:00
|
|
|
if (logging::IsEnabled(logging::eFocus))
|
|
|
|
logging::ActiveItemChangeCausedBy("tree shutdown", aAccessible);
|
|
|
|
#endif
|
2011-09-28 05:46:11 +04:00
|
|
|
}
|
|
|
|
|
2010-11-12 22:01:04 +03:00
|
|
|
// Remove an accessible from node-to-accessible map if it exists there.
|
2012-10-13 10:34:21 +04:00
|
|
|
if (aAccessible->IsNodeMapEntry() &&
|
2010-11-12 22:00:55 +03:00
|
|
|
mNodeToAccessibleMap.Get(aAccessible->GetNode()) == aAccessible)
|
|
|
|
mNodeToAccessibleMap.Remove(aAccessible->GetNode());
|
|
|
|
|
2017-07-20 19:57:00 +03:00
|
|
|
aAccessible->mStateFlags |= eIsNotInDocument;
|
|
|
|
|
2014-10-22 04:49:28 +04:00
|
|
|
// Update XPCOM part.
|
|
|
|
xpcAccessibleDocument* xpcDoc = GetAccService()->GetCachedXPCDocument(this);
|
|
|
|
if (xpcDoc) xpcDoc->NotifyOfShutdown(aAccessible);
|
|
|
|
|
2010-11-12 22:00:55 +03:00
|
|
|
void* uniqueID = aAccessible->UniqueID();
|
2010-11-20 05:37:40 +03:00
|
|
|
|
|
|
|
NS_ASSERTION(!aAccessible->IsDefunct(), "Shutdown the shutdown accessible!");
|
2010-11-12 22:00:55 +03:00
|
|
|
aAccessible->Shutdown();
|
2010-11-20 05:37:40 +03:00
|
|
|
|
2010-11-12 22:00:55 +03:00
|
|
|
mAccessibleCache.Remove(uniqueID);
|
|
|
|
}
|
|
|
|
|
2018-10-18 11:02:51 +03:00
|
|
|
void DocAccessible::ContentInserted(nsIContent* aStartChildNode,
|
2012-05-27 13:01:40 +04:00
|
|
|
nsIContent* aEndChildNode) {
|
2011-08-08 11:55:36 +04:00
|
|
|
// Ignore content insertions until we constructed accessible tree. Otherwise
|
|
|
|
// schedule tree update on content insertion after layout.
|
2019-08-01 19:19:15 +03:00
|
|
|
if (!mNotificationController || !HasLoadState(eTreeConstructed)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The frame constructor guarantees that only ranges with the same parent
|
|
|
|
// arrive here in presence of dynamic changes to the page, see
|
|
|
|
// nsCSSFrameConstructor::IssueSingleInsertNotifications' callers.
|
|
|
|
nsINode* parent = aStartChildNode->GetFlattenedTreeParentNode();
|
|
|
|
if (!parent) {
|
|
|
|
return;
|
2010-10-21 08:16:10 +04:00
|
|
|
}
|
2019-08-01 19:19:15 +03:00
|
|
|
|
|
|
|
Accessible* container = AccessibleOrTrueContainer(parent);
|
|
|
|
if (!container) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
AutoTArray<nsCOMPtr<nsIContent>, 10> list;
|
|
|
|
for (nsIContent* node = aStartChildNode; node != aEndChildNode;
|
|
|
|
node = node->GetNextSibling()) {
|
|
|
|
MOZ_ASSERT(parent == node->GetFlattenedTreeParentNode());
|
2019-08-03 01:13:28 +03:00
|
|
|
if (PruneOrInsertSubtree(node)) {
|
2019-08-01 19:19:15 +03:00
|
|
|
list.AppendElement(node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mNotificationController->ScheduleContentInsertion(container, list);
|
2011-01-18 11:03:38 +03:00
|
|
|
}
|
2010-10-21 08:16:10 +04:00
|
|
|
|
2019-08-03 01:13:28 +03:00
|
|
|
bool DocAccessible::PruneOrInsertSubtree(nsIContent* aRoot) {
|
2019-08-08 19:47:14 +03:00
|
|
|
bool insert = false;
|
2019-08-29 19:14:48 +03:00
|
|
|
|
|
|
|
// In the case that we are, or are in, a shadow host, we need to assure
|
|
|
|
// some accessibles are removed if they are not rendered anymore.
|
|
|
|
nsIContent* shadowHost =
|
|
|
|
aRoot->GetShadowRoot() ? aRoot : aRoot->GetContainingShadowHost();
|
|
|
|
if (shadowHost) {
|
|
|
|
dom::ExplicitChildIterator iter(shadowHost);
|
|
|
|
|
|
|
|
// Check all explicit children in the host, if they are not slotted
|
|
|
|
// then remove their accessibles and subtrees.
|
|
|
|
while (nsIContent* childNode = iter.GetNextChild()) {
|
|
|
|
if (!childNode->GetPrimaryFrame() &&
|
|
|
|
!nsCoreUtils::IsDisplayContents(childNode)) {
|
|
|
|
ContentRemoved(childNode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this is a slot, check to see if its fallback content is rendered,
|
|
|
|
// if not - remove it.
|
|
|
|
if (aRoot->IsHTMLElement(nsGkAtoms::slot)) {
|
|
|
|
for (nsIContent* childNode = aRoot->GetFirstChild(); childNode;
|
|
|
|
childNode = childNode->GetNextSibling()) {
|
|
|
|
if (!childNode->GetPrimaryFrame() &&
|
|
|
|
!nsCoreUtils::IsDisplayContents(childNode)) {
|
|
|
|
ContentRemoved(childNode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-03 01:13:28 +03:00
|
|
|
// If we already have an accessible, check if we need to remove it, recreate
|
|
|
|
// it, or keep it in place.
|
|
|
|
Accessible* acc = GetAccessible(aRoot);
|
|
|
|
if (acc) {
|
|
|
|
MOZ_ASSERT(aRoot == acc->GetContent(), "Accessible has differing content!");
|
|
|
|
#ifdef A11Y_LOG
|
|
|
|
if (logging::IsEnabled(logging::eTree)) {
|
|
|
|
logging::MsgBegin(
|
|
|
|
"TREE", "inserted content already has accessible; doc: %p", this);
|
|
|
|
logging::Node("content node", aRoot);
|
|
|
|
logging::AccessibleInfo("accessible node", acc);
|
|
|
|
logging::MsgEnd();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
nsIFrame* frame = acc->GetFrame();
|
|
|
|
|
2019-08-09 04:21:54 +03:00
|
|
|
// Accessible has no frame and it's not display:contents. Remove it.
|
|
|
|
// As well as removing the a11y subtree, we must also remove Accessibles
|
|
|
|
// for DOM descendants, since some of these might be relocated Accessibles
|
|
|
|
// and their DOM nodes are now hidden as well.
|
2019-08-03 01:13:28 +03:00
|
|
|
if (!frame && !nsCoreUtils::IsDisplayContents(aRoot)) {
|
2019-08-09 04:21:54 +03:00
|
|
|
ContentRemoved(aRoot);
|
2019-08-03 01:13:28 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If it's a XULLabel it was probably reframed because a `value` attribute
|
|
|
|
// was added. The accessible creates its text leaf upon construction, so we
|
|
|
|
// need to recreate. Remove it, and schedule for reconstruction.
|
|
|
|
if (acc->IsXULLabel()) {
|
|
|
|
ContentRemoved(acc);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// It is a broken image that is being reframed because it either got
|
|
|
|
// or lost an `alt` tag that would rerender this node as text.
|
|
|
|
if (frame && (acc->IsImage() != (frame->AccessibleType() == eImageType))) {
|
|
|
|
ContentRemoved(aRoot);
|
|
|
|
return true;
|
|
|
|
}
|
2019-08-29 19:14:48 +03:00
|
|
|
|
2019-11-08 08:49:46 +03:00
|
|
|
// If the frame is an OuterDoc frame but this isn't an OuterDocAccessible,
|
|
|
|
// we need to recreate the Accessible. This can happen for embed or object
|
|
|
|
// elements if their embedded content changes to be web content.
|
|
|
|
if (frame && !acc->IsOuterDoc() &&
|
|
|
|
frame->AccessibleType() == eOuterDocType) {
|
|
|
|
ContentRemoved(aRoot);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-02-05 03:31:57 +03:00
|
|
|
// If the content is focused, and is being re-framed, reset the selection
|
|
|
|
// listener for the node because the previous selection listener is on the
|
|
|
|
// old frame.
|
|
|
|
if (aRoot->IsElement() && FocusMgr()->HasDOMFocus(aRoot)) {
|
|
|
|
SelectionMgr()->SetControlSelectionListener(aRoot->AsElement());
|
|
|
|
}
|
|
|
|
|
2019-08-29 19:14:48 +03:00
|
|
|
// The accessible can be reparented or reordered in its parent.
|
|
|
|
// We schedule it for reinsertion. For example, a slotted element
|
|
|
|
// can change its slot attribute to a different slot.
|
|
|
|
insert = true;
|
2019-08-03 01:13:28 +03:00
|
|
|
} else {
|
|
|
|
// If there is no current accessible, and the node has a frame, or is
|
|
|
|
// display:contents, schedule it for insertion.
|
|
|
|
if (aRoot->GetPrimaryFrame() || nsCoreUtils::IsDisplayContents(aRoot)) {
|
2019-08-08 19:47:14 +03:00
|
|
|
// This may be a new subtree, the insertion process will recurse through
|
|
|
|
// its descendants.
|
|
|
|
if (!GetAccessibleOrDescendant(aRoot)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Content is not an accessible, but has accessible descendants.
|
|
|
|
// We schedule this container for insertion strictly for the case where it
|
|
|
|
// itself now needs an accessible. We will still need to recurse into the
|
|
|
|
// descendant content to prune accessibles, and in all likelyness to
|
|
|
|
// insert accessibles since accessible insertions will likeley get missed
|
|
|
|
// in an existing subtree.
|
|
|
|
insert = true;
|
2019-08-03 01:13:28 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Accessible* container = AccessibleOrTrueContainer(aRoot)) {
|
|
|
|
AutoTArray<nsCOMPtr<nsIContent>, 10> list;
|
|
|
|
dom::AllChildrenIterator iter =
|
|
|
|
dom::AllChildrenIterator(aRoot, nsIContent::eAllChildren, true);
|
|
|
|
while (nsIContent* childNode = iter.GetNextChild()) {
|
|
|
|
if (PruneOrInsertSubtree(childNode)) {
|
|
|
|
list.AppendElement(childNode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!list.IsEmpty()) {
|
|
|
|
mNotificationController->ScheduleContentInsertion(container, list);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-08 19:47:14 +03:00
|
|
|
return insert;
|
2019-08-03 01:13:28 +03:00
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
void DocAccessible::RecreateAccessible(nsIContent* aContent) {
|
2012-10-10 04:14:44 +04:00
|
|
|
#ifdef A11Y_LOG
|
|
|
|
if (logging::IsEnabled(logging::eTree)) {
|
|
|
|
logging::MsgBegin("TREE", "accessible recreated");
|
|
|
|
logging::Node("content", aContent);
|
|
|
|
logging::MsgEnd();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2011-02-01 06:00:06 +03:00
|
|
|
// XXX: we shouldn't recreate whole accessible subtree, instead we should
|
|
|
|
// subclass hide and show events to handle them separately and implement their
|
|
|
|
// coalescence with normal hide and show events. Note, in this case they
|
|
|
|
// should be coalesced with normal show/hide events.
|
2017-04-25 21:02:41 +03:00
|
|
|
ContentRemoved(aContent);
|
2018-10-18 11:02:51 +03:00
|
|
|
ContentInserted(aContent, aContent->GetNextSibling());
|
2010-10-21 08:16:10 +04:00
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
void DocAccessible::ProcessInvalidationList() {
|
2011-07-25 07:15:37 +04:00
|
|
|
// Invalidate children of container accessible for each element in
|
|
|
|
// invalidation list. Allow invalidation list insertions while container
|
|
|
|
// children are recached.
|
2012-08-22 19:56:38 +04:00
|
|
|
for (uint32_t idx = 0; idx < mInvalidationList.Length(); idx++) {
|
2011-07-25 07:15:37 +04:00
|
|
|
nsIContent* content = mInvalidationList[idx];
|
2016-09-23 23:57:16 +03:00
|
|
|
if (!HasAccessible(content) && content->HasID()) {
|
2012-05-29 05:18:45 +04:00
|
|
|
Accessible* container = GetContainerAccessible(content);
|
2016-03-29 16:20:43 +03:00
|
|
|
if (container) {
|
2016-08-24 23:54:54 +03:00
|
|
|
// Check if the node is a target of aria-owns, and if so, don't process
|
|
|
|
// it here and let DoARIAOwnsRelocation process it.
|
2018-10-30 03:17:04 +03:00
|
|
|
AttrRelProviders* list = GetRelProviders(
|
|
|
|
content->AsElement(), nsDependentAtomString(content->GetID()));
|
2016-08-24 23:54:54 +03:00
|
|
|
bool shouldProcess = !!list;
|
|
|
|
if (shouldProcess) {
|
|
|
|
for (uint32_t idx = 0; idx < list->Length(); idx++) {
|
|
|
|
if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {
|
|
|
|
shouldProcess = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shouldProcess) {
|
|
|
|
ProcessContentInserted(container, content);
|
|
|
|
}
|
|
|
|
}
|
2016-03-29 16:20:43 +03:00
|
|
|
}
|
2014-09-02 22:54:04 +04:00
|
|
|
}
|
2010-11-19 08:44:47 +03:00
|
|
|
}
|
2011-07-25 07:15:37 +04:00
|
|
|
|
|
|
|
mInvalidationList.Clear();
|
2010-11-19 08:44:47 +03:00
|
|
|
}
|
|
|
|
|
2014-03-06 04:36:56 +04:00
|
|
|
Accessible* DocAccessible::GetAccessibleEvenIfNotInMap(nsINode* aNode) const {
|
2015-03-03 14:08:59 +03:00
|
|
|
if (!aNode->IsContent() ||
|
|
|
|
!aNode->AsContent()->IsHTMLElement(nsGkAtoms::area))
|
2014-03-06 04:36:56 +04:00
|
|
|
return GetAccessible(aNode);
|
|
|
|
|
|
|
|
// XXX Bug 135040, incorrect when multiple images use the same map.
|
|
|
|
nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
|
|
|
|
nsImageFrame* imageFrame = do_QueryFrame(frame);
|
|
|
|
if (imageFrame) {
|
|
|
|
Accessible* parent = GetAccessible(imageFrame->GetContent());
|
|
|
|
if (parent) {
|
|
|
|
Accessible* area = parent->AsImageMap()->GetChildAccessibleFor(aNode);
|
|
|
|
if (area) return area;
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return GetAccessible(aNode);
|
|
|
|
}
|
|
|
|
|
2009-12-10 22:12:19 +03:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Protected members
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
void DocAccessible::NotifyOfLoading(bool aIsReloading) {
|
2011-08-08 11:55:36 +04:00
|
|
|
// Mark the document accessible as loading, if it stays alive then we'll mark
|
|
|
|
// it as loaded when we receive proper notification.
|
|
|
|
mLoadState &= ~eDOMLoaded;
|
|
|
|
|
|
|
|
if (!IsLoadEventTarget()) return;
|
|
|
|
|
2017-03-14 21:36:06 +03:00
|
|
|
if (aIsReloading && !mLoadEventType) {
|
2011-08-08 11:55:36 +04:00
|
|
|
// Fire reload and state busy events on existing document accessible while
|
|
|
|
// event from user input flag can be calculated properly and accessible
|
|
|
|
// is alive. When new document gets loaded then this one is destroyed.
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> reloadEvent =
|
2011-08-08 11:55:36 +04:00
|
|
|
new AccEvent(nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD, this);
|
|
|
|
nsEventShell::FireEvent(reloadEvent);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fire state busy change event. Use delayed event since we don't care
|
|
|
|
// actually if event isn't delivered when the document goes away like a shot.
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> stateEvent =
|
2012-11-20 08:53:38 +04:00
|
|
|
new AccStateChangeEvent(this, states::BUSY, true);
|
|
|
|
FireDelayedEvent(stateEvent);
|
2011-08-08 11:55:36 +04:00
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
void DocAccessible::DoInitialUpdate() {
|
2017-01-17 03:59:06 +03:00
|
|
|
if (nsCoreUtils::IsTabDocument(mDocumentNode)) {
|
2012-12-29 20:33:59 +04:00
|
|
|
mDocFlags |= eTabDocument;
|
2017-01-17 03:59:06 +03:00
|
|
|
if (IPCAccessibilityActive()) {
|
|
|
|
nsIDocShell* docShell = mDocumentNode->GetDocShell();
|
2019-04-10 01:39:01 +03:00
|
|
|
if (RefPtr<dom::BrowserChild> browserChild =
|
|
|
|
dom::BrowserChild::GetFrom(docShell)) {
|
2019-02-26 20:05:47 +03:00
|
|
|
DocAccessibleChild* ipcDoc = IPCDoc();
|
|
|
|
if (!ipcDoc) {
|
2019-04-10 01:39:01 +03:00
|
|
|
ipcDoc = new DocAccessibleChild(this, browserChild);
|
2019-02-26 20:05:47 +03:00
|
|
|
SetIPCDoc(ipcDoc);
|
2017-01-17 03:59:06 +03:00
|
|
|
|
|
|
|
#if defined(XP_WIN)
|
2019-02-26 20:05:47 +03:00
|
|
|
IAccessibleHolder holder(
|
|
|
|
CreateHolderFromAccessible(WrapNotNull(this)));
|
|
|
|
MOZ_ASSERT(!holder.IsNull());
|
|
|
|
int32_t childID = AccessibleWrap::GetChildIDFor(this);
|
2017-01-17 03:59:06 +03:00
|
|
|
#else
|
2019-02-26 20:05:47 +03:00
|
|
|
int32_t holder = 0, childID = 0;
|
2017-01-17 03:59:06 +03:00
|
|
|
#endif
|
2019-04-10 01:39:01 +03:00
|
|
|
browserChild->SendPDocAccessibleConstructor(ipcDoc, nullptr, 0,
|
|
|
|
childID, holder);
|
2019-09-13 19:55:01 +03:00
|
|
|
#if !defined(XP_WIN)
|
|
|
|
ipcDoc->SendPDocAccessiblePlatformExtConstructor();
|
|
|
|
#endif
|
2019-02-26 20:05:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (IsRoot()) {
|
2019-04-10 01:39:01 +03:00
|
|
|
browserChild->SetTopLevelDocAccessibleChild(ipcDoc);
|
2019-02-26 20:05:47 +03:00
|
|
|
}
|
2017-01-17 03:59:06 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-12-29 20:33:59 +04:00
|
|
|
|
2011-08-08 11:55:36 +04:00
|
|
|
mLoadState |= eTreeConstructed;
|
|
|
|
|
2016-03-04 18:36:18 +03:00
|
|
|
// Set up a root element and ARIA role mapping.
|
|
|
|
UpdateRootElIfNeeded();
|
2011-03-03 09:41:46 +03:00
|
|
|
|
2016-04-01 17:53:52 +03:00
|
|
|
// Build initial tree.
|
2011-03-03 09:41:46 +03:00
|
|
|
CacheChildrenInSubtree(this);
|
2016-08-19 19:10:58 +03:00
|
|
|
#ifdef A11Y_LOG
|
|
|
|
if (logging::IsEnabled(logging::eVerbose)) {
|
|
|
|
logging::Tree("TREE", "Initial subtree", this);
|
|
|
|
}
|
|
|
|
#endif
|
2011-07-19 12:30:32 +04:00
|
|
|
|
|
|
|
// Fire reorder event after the document tree is constructed. Note, since
|
|
|
|
// this reorder event is processed by parent document then events targeted to
|
|
|
|
// this document may be fired prior to this reorder event. If this is
|
|
|
|
// a problem then consider to keep event processing per tab document.
|
|
|
|
if (!IsRoot()) {
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(Parent());
|
2012-11-20 08:53:38 +04:00
|
|
|
ParentDocument()->FireDelayedEvent(reorderEvent);
|
2011-07-19 12:30:32 +04:00
|
|
|
}
|
2014-03-08 01:35:19 +04:00
|
|
|
|
2017-10-03 17:58:27 +03:00
|
|
|
if (IPCAccessibilityActive()) {
|
|
|
|
DocAccessibleChild* ipcDoc = IPCDoc();
|
|
|
|
MOZ_ASSERT(ipcDoc);
|
|
|
|
if (ipcDoc) {
|
|
|
|
for (auto idx = 0U; idx < mChildren.Length(); idx++) {
|
|
|
|
ipcDoc->InsertIntoIpcTree(this, mChildren.ElementAt(idx), idx);
|
|
|
|
}
|
|
|
|
}
|
2014-03-08 01:35:19 +04:00
|
|
|
}
|
2011-03-02 17:41:42 +03:00
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
void DocAccessible::ProcessLoad() {
|
2011-08-08 11:55:36 +04:00
|
|
|
mLoadState |= eCompletelyLoaded;
|
|
|
|
|
2012-10-10 05:01:46 +04:00
|
|
|
#ifdef A11Y_LOG
|
|
|
|
if (logging::IsEnabled(logging::eDocLoad))
|
|
|
|
logging::DocCompleteLoad(this, IsLoadEventTarget());
|
|
|
|
#endif
|
|
|
|
|
2011-08-08 11:55:36 +04:00
|
|
|
// Do not fire document complete/stop events for root chrome document
|
|
|
|
// accessibles and for frame/iframe documents because
|
|
|
|
// a) screen readers start working on focus event in the case of root chrome
|
|
|
|
// documents
|
|
|
|
// b) document load event on sub documents causes screen readers to act is if
|
|
|
|
// entire page is reloaded.
|
|
|
|
if (!IsLoadEventTarget()) return;
|
|
|
|
|
|
|
|
// Fire complete/load stopped if the load event type is given.
|
|
|
|
if (mLoadEventType) {
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> loadEvent = new AccEvent(mLoadEventType, this);
|
2013-03-18 09:58:34 +04:00
|
|
|
FireDelayedEvent(loadEvent);
|
2011-08-08 11:55:36 +04:00
|
|
|
|
|
|
|
mLoadEventType = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fire busy state change event.
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AccEvent> stateEvent =
|
2011-10-17 18:59:28 +04:00
|
|
|
new AccStateChangeEvent(this, states::BUSY, false);
|
2013-03-18 09:58:34 +04:00
|
|
|
FireDelayedEvent(stateEvent);
|
2011-08-08 11:55:36 +04:00
|
|
|
}
|
|
|
|
|
2017-10-03 01:05:19 +03:00
|
|
|
void DocAccessible::AddDependentIDsFor(Accessible* aRelProvider,
|
|
|
|
nsAtom* aRelAttr) {
|
2015-09-15 19:01:51 +03:00
|
|
|
dom::Element* relProviderEl = aRelProvider->Elm();
|
|
|
|
if (!relProviderEl) return;
|
|
|
|
|
2012-08-22 19:56:38 +04:00
|
|
|
for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
|
2018-04-03 15:15:30 +03:00
|
|
|
nsStaticAtom* relAttr = kRelationAttrs[idx];
|
2010-11-18 05:55:44 +03:00
|
|
|
if (aRelAttr && aRelAttr != relAttr) continue;
|
|
|
|
|
2011-06-04 01:35:17 +04:00
|
|
|
if (relAttr == nsGkAtoms::_for) {
|
2015-09-15 19:01:51 +03:00
|
|
|
if (!relProviderEl->IsAnyOfHTMLElements(nsGkAtoms::label,
|
|
|
|
nsGkAtoms::output))
|
2010-11-20 05:37:18 +03:00
|
|
|
continue;
|
|
|
|
|
2011-06-04 01:35:17 +04:00
|
|
|
} else if (relAttr == nsGkAtoms::control) {
|
2015-09-15 19:01:51 +03:00
|
|
|
if (!relProviderEl->IsAnyOfXULElements(nsGkAtoms::label,
|
|
|
|
nsGkAtoms::description))
|
2010-11-20 05:37:18 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-09-15 19:01:51 +03:00
|
|
|
IDRefsIterator iter(this, relProviderEl, relAttr);
|
2010-11-18 05:55:44 +03:00
|
|
|
while (true) {
|
|
|
|
const nsDependentSubstring id = iter.NextID();
|
|
|
|
if (id.IsEmpty()) break;
|
|
|
|
|
2017-09-13 21:04:00 +03:00
|
|
|
nsIContent* dependentContent = iter.GetElem(id);
|
2018-10-30 03:17:04 +03:00
|
|
|
if (!dependentContent ||
|
|
|
|
(relAttr == nsGkAtoms::aria_owns &&
|
|
|
|
!aRelProvider->IsAcceptableChild(dependentContent)))
|
2017-09-13 21:04:00 +03:00
|
|
|
continue;
|
|
|
|
|
2018-10-30 03:17:04 +03:00
|
|
|
AttrRelProviders* providers =
|
|
|
|
GetOrCreateRelProviders(dependentContent->AsElement(), id);
|
2010-11-18 05:55:44 +03:00
|
|
|
if (providers) {
|
2018-10-30 03:17:04 +03:00
|
|
|
AttrRelProvider* provider = new AttrRelProvider(relAttr, relProviderEl);
|
2010-11-19 08:44:47 +03:00
|
|
|
if (provider) {
|
2010-11-18 05:55:44 +03:00
|
|
|
providers->AppendElement(provider);
|
2010-11-19 08:44:47 +03:00
|
|
|
|
|
|
|
// We've got here during the children caching. If the referenced
|
|
|
|
// content is not accessible then store it to pend its container
|
|
|
|
// children invalidation (this happens immediately after the caching
|
|
|
|
// is finished).
|
2015-09-15 19:01:51 +03:00
|
|
|
if (dependentContent) {
|
|
|
|
if (!HasAccessible(dependentContent)) {
|
|
|
|
mInvalidationList.AppendElement(dependentContent);
|
|
|
|
}
|
2010-11-19 08:44:47 +03:00
|
|
|
}
|
|
|
|
}
|
2010-11-18 05:55:44 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the relation attribute is given then we don't have anything else to
|
|
|
|
// check.
|
|
|
|
if (aRelAttr) break;
|
|
|
|
}
|
2015-09-15 19:01:51 +03:00
|
|
|
|
|
|
|
// Make sure to schedule the tree update if needed.
|
|
|
|
mNotificationController->ScheduleProcessing();
|
2010-11-18 05:55:44 +03:00
|
|
|
}
|
|
|
|
|
2015-09-15 19:01:51 +03:00
|
|
|
void DocAccessible::RemoveDependentIDsFor(Accessible* aRelProvider,
|
2017-10-03 01:05:19 +03:00
|
|
|
nsAtom* aRelAttr) {
|
2015-09-15 19:01:51 +03:00
|
|
|
dom::Element* relProviderElm = aRelProvider->Elm();
|
|
|
|
if (!relProviderElm) return;
|
|
|
|
|
2012-08-22 19:56:38 +04:00
|
|
|
for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
|
2018-04-03 15:15:30 +03:00
|
|
|
nsStaticAtom* relAttr = kRelationAttrs[idx];
|
|
|
|
if (aRelAttr && aRelAttr != kRelationAttrs[idx]) continue;
|
2010-11-18 05:55:44 +03:00
|
|
|
|
2015-09-15 19:01:51 +03:00
|
|
|
IDRefsIterator iter(this, relProviderElm, relAttr);
|
2010-11-18 05:55:44 +03:00
|
|
|
while (true) {
|
|
|
|
const nsDependentSubstring id = iter.NextID();
|
|
|
|
if (id.IsEmpty()) break;
|
|
|
|
|
2018-10-30 03:17:04 +03:00
|
|
|
AttrRelProviders* providers = GetRelProviders(relProviderElm, id);
|
2010-11-18 05:55:44 +03:00
|
|
|
if (providers) {
|
2012-08-22 19:56:38 +04:00
|
|
|
for (uint32_t jdx = 0; jdx < providers->Length();) {
|
2020-04-04 00:05:35 +03:00
|
|
|
const auto& provider = (*providers)[jdx];
|
2010-11-18 05:55:44 +03:00
|
|
|
if (provider->mRelAttr == relAttr &&
|
2015-09-15 19:01:51 +03:00
|
|
|
provider->mContent == relProviderElm)
|
2020-04-04 00:05:35 +03:00
|
|
|
providers->RemoveElementAt(jdx);
|
2010-11-18 05:55:44 +03:00
|
|
|
else
|
|
|
|
jdx++;
|
|
|
|
}
|
2018-10-30 03:17:04 +03:00
|
|
|
RemoveRelProvidersIfEmpty(relProviderElm, id);
|
2010-11-18 05:55:44 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the relation attribute is given then we don't have anything else to
|
|
|
|
// check.
|
|
|
|
if (aRelAttr) break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
bool DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
|
2017-10-03 01:05:19 +03:00
|
|
|
nsAtom* aAttribute) {
|
2011-06-04 01:35:17 +04:00
|
|
|
if (aAttribute == nsGkAtoms::role) {
|
2010-12-18 21:33:00 +03:00
|
|
|
// It is common for js libraries to set the role on the body element after
|
|
|
|
// the document has loaded. In this case we just update the role map entry.
|
|
|
|
if (mContent == aElement) {
|
2020-01-23 07:34:22 +03:00
|
|
|
SetRoleMapEntryForDoc(aElement);
|
2016-06-08 00:27:17 +03:00
|
|
|
if (mIPCDoc) {
|
|
|
|
mIPCDoc->SendRoleChangedEvent(Role());
|
|
|
|
}
|
|
|
|
|
2010-12-18 21:33:00 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Recreate the accessible when role is changed because we might require a
|
|
|
|
// different accessible class for the new role or the accessible may expose
|
|
|
|
// a different sets of interfaces (COM restriction).
|
2012-03-23 05:49:55 +04:00
|
|
|
RecreateAccessible(aElement);
|
2011-01-19 11:03:12 +03:00
|
|
|
|
2010-12-18 21:33:00 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-06-04 01:35:17 +04:00
|
|
|
if (aAttribute == nsGkAtoms::aria_multiselectable &&
|
|
|
|
aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) {
|
2010-12-18 21:33:00 +03:00
|
|
|
// This affects whether the accessible supports SelectAccessible.
|
|
|
|
// COM says we cannot change what interfaces are supported on-the-fly,
|
|
|
|
// so invalidate this object. A new one will be created on demand.
|
2012-03-23 05:49:55 +04:00
|
|
|
RecreateAccessible(aElement);
|
2011-01-19 11:03:12 +03:00
|
|
|
|
2010-12-18 21:33:00 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-08-03 01:13:28 +03:00
|
|
|
if (aAttribute == nsGkAtoms::type) {
|
|
|
|
// If the input[type] changes, we should recreate the accessible.
|
|
|
|
RecreateAccessible(aElement);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-12-18 21:33:00 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-06-08 00:27:17 +03:00
|
|
|
void DocAccessible::UpdateRootElIfNeeded() {
|
|
|
|
dom::Element* rootEl = mDocumentNode->GetBodyElement();
|
|
|
|
if (!rootEl) {
|
|
|
|
rootEl = mDocumentNode->GetRootElement();
|
|
|
|
}
|
|
|
|
if (rootEl != mContent) {
|
|
|
|
mContent = rootEl;
|
2020-01-23 07:34:22 +03:00
|
|
|
SetRoleMapEntryForDoc(rootEl);
|
2016-06-08 00:27:17 +03:00
|
|
|
if (mIPCDoc) {
|
|
|
|
mIPCDoc->SendRoleChangedEvent(Role());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-28 20:40:21 +03:00
|
|
|
/**
|
|
|
|
* Content insertion helper.
|
|
|
|
*/
|
|
|
|
class InsertIterator final {
|
|
|
|
public:
|
|
|
|
InsertIterator(Accessible* aContext,
|
2019-09-10 19:16:53 +03:00
|
|
|
const nsTArray<nsCOMPtr<nsIContent>>* aNodes)
|
2016-03-28 20:40:21 +03:00
|
|
|
: mChild(nullptr),
|
|
|
|
mChildBefore(nullptr),
|
|
|
|
mWalker(aContext),
|
2016-08-05 17:20:58 +03:00
|
|
|
mNodes(aNodes),
|
|
|
|
mNodesIdx(0) {
|
2016-03-28 20:40:21 +03:00
|
|
|
MOZ_ASSERT(aContext, "No context");
|
|
|
|
MOZ_ASSERT(aNodes, "No nodes to search for accessible elements");
|
|
|
|
MOZ_COUNT_CTOR(InsertIterator);
|
|
|
|
}
|
2020-02-20 14:40:14 +03:00
|
|
|
MOZ_COUNTED_DTOR(InsertIterator)
|
2016-03-28 20:40:21 +03:00
|
|
|
|
|
|
|
Accessible* Context() const { return mWalker.Context(); }
|
|
|
|
Accessible* Child() const { return mChild; }
|
|
|
|
Accessible* ChildBefore() const { return mChildBefore; }
|
|
|
|
DocAccessible* Document() const { return mWalker.Document(); }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Iterates to a next accessible within the inserted content.
|
|
|
|
*/
|
|
|
|
bool Next();
|
|
|
|
|
2016-07-13 06:15:22 +03:00
|
|
|
void Rejected() {
|
|
|
|
mChild = nullptr;
|
|
|
|
mChildBefore = nullptr;
|
|
|
|
}
|
|
|
|
|
2016-03-28 20:40:21 +03:00
|
|
|
private:
|
|
|
|
Accessible* mChild;
|
|
|
|
Accessible* mChildBefore;
|
|
|
|
TreeWalker mWalker;
|
|
|
|
|
2019-09-10 19:16:53 +03:00
|
|
|
const nsTArray<nsCOMPtr<nsIContent>>* mNodes;
|
2019-08-29 19:14:48 +03:00
|
|
|
nsTHashtable<nsPtrHashKey<const nsIContent>> mProcessedNodes;
|
2016-03-28 20:40:21 +03:00
|
|
|
uint32_t mNodesIdx;
|
|
|
|
};
|
|
|
|
|
|
|
|
bool InsertIterator::Next() {
|
|
|
|
if (mNodesIdx > 0) {
|
2016-08-05 17:20:58 +03:00
|
|
|
Accessible* nextChild = mWalker.Next();
|
2016-03-28 20:40:21 +03:00
|
|
|
if (nextChild) {
|
|
|
|
mChildBefore = mChild;
|
|
|
|
mChild = nextChild;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
while (mNodesIdx < mNodes->Length()) {
|
|
|
|
// Ignore nodes that are not contained by the container anymore.
|
2016-03-11 19:35:35 +03:00
|
|
|
|
2016-03-15 19:40:07 +03:00
|
|
|
// The container might be changed, for example, because of the subsequent
|
|
|
|
// overlapping content insertion (i.e. other content was inserted between
|
|
|
|
// this inserted content and its container or the content was reinserted
|
|
|
|
// into different container of unrelated part of tree). To avoid a double
|
|
|
|
// processing of the content insertion ignore this insertion notification.
|
2016-03-28 20:40:21 +03:00
|
|
|
// Note, the inserted content might be not in tree at all at this point
|
|
|
|
// what means there's no container. Ignore the insertion too.
|
|
|
|
nsIContent* prevNode = mNodes->SafeElementAt(mNodesIdx - 1);
|
|
|
|
nsIContent* node = mNodes->ElementAt(mNodesIdx++);
|
2019-08-29 19:14:48 +03:00
|
|
|
// Check to see if we already processed this node with this iterator.
|
|
|
|
// this can happen if we get two redundant insertions in the case of a
|
|
|
|
// text and frame insertion.
|
|
|
|
if (!mProcessedNodes.EnsureInserted(node)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-09-06 03:24:21 +03:00
|
|
|
Accessible* container = Document()->AccessibleOrTrueContainer(
|
|
|
|
node->GetFlattenedTreeParentNode(), true);
|
2016-03-28 20:40:21 +03:00
|
|
|
if (container != Context()) {
|
2012-11-13 10:29:22 +04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-03-18 23:22:17 +03:00
|
|
|
// HTML comboboxes have no-content list accessible as an intermediate
|
|
|
|
// containing all options.
|
2016-03-28 20:40:21 +03:00
|
|
|
if (container->IsHTMLCombobox()) {
|
2016-03-18 23:22:17 +03:00
|
|
|
container = container->FirstChild();
|
|
|
|
}
|
|
|
|
|
2016-03-28 20:40:21 +03:00
|
|
|
if (!container->IsAcceptableChild(node)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef A11Y_LOG
|
|
|
|
logging::TreeInfo("traversing an inserted node", logging::eVerbose,
|
|
|
|
"container", container, "node", node);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// If inserted nodes are siblings then just move the walker next.
|
2016-08-19 19:10:58 +03:00
|
|
|
if (mChild && prevNode && prevNode->GetNextSibling() == node) {
|
2016-08-05 17:20:58 +03:00
|
|
|
Accessible* nextChild = mWalker.Scope(node);
|
2016-03-28 20:40:21 +03:00
|
|
|
if (nextChild) {
|
|
|
|
mChildBefore = mChild;
|
|
|
|
mChild = nextChild;
|
|
|
|
return true;
|
|
|
|
}
|
2016-08-05 17:20:58 +03:00
|
|
|
} else {
|
|
|
|
TreeWalker finder(container);
|
|
|
|
if (finder.Seek(node)) {
|
|
|
|
mChild = mWalker.Scope(node);
|
|
|
|
if (mChild) {
|
2017-12-22 19:25:00 +03:00
|
|
|
MOZ_ASSERT(!mChild->IsRelocated(), "child cannot be aria owned");
|
2016-08-05 17:20:58 +03:00
|
|
|
mChildBefore = finder.Prev();
|
|
|
|
return true;
|
|
|
|
}
|
2016-03-28 20:40:21 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DocAccessible::ProcessContentInserted(
|
2019-09-10 19:16:53 +03:00
|
|
|
Accessible* aContainer, const nsTArray<nsCOMPtr<nsIContent>>* aNodes) {
|
2016-03-28 20:40:21 +03:00
|
|
|
// Process insertions if the container accessible is still in tree.
|
2016-04-06 03:48:30 +03:00
|
|
|
if (!aContainer->IsInDocument()) {
|
2016-03-28 20:40:21 +03:00
|
|
|
return;
|
2016-04-06 03:48:30 +03:00
|
|
|
}
|
2016-03-28 20:40:21 +03:00
|
|
|
|
|
|
|
// If new root content has been inserted then update it.
|
|
|
|
if (aContainer == this) {
|
|
|
|
UpdateRootElIfNeeded();
|
|
|
|
}
|
|
|
|
|
2016-04-07 16:30:22 +03:00
|
|
|
InsertIterator iter(aContainer, aNodes);
|
|
|
|
if (!iter.Next()) {
|
|
|
|
return;
|
|
|
|
}
|
2016-03-28 20:40:21 +03:00
|
|
|
|
|
|
|
#ifdef A11Y_LOG
|
|
|
|
logging::TreeInfo("children before insertion", logging::eVerbose, aContainer);
|
|
|
|
#endif
|
|
|
|
|
2016-04-07 16:30:22 +03:00
|
|
|
TreeMutation mt(aContainer);
|
|
|
|
do {
|
2016-03-28 20:40:21 +03:00
|
|
|
Accessible* parent = iter.Child()->Parent();
|
|
|
|
if (parent) {
|
2019-08-29 19:14:48 +03:00
|
|
|
Accessible* previousSibling = iter.ChildBefore();
|
|
|
|
if (parent != aContainer ||
|
|
|
|
iter.Child()->PrevSibling() != previousSibling) {
|
Bug 1585851: When processing a11y insertions, don't try to move an accessible if its new previous sibling hasn't been moved into its new container yet. r=MarcoZ
Sometimes, depending on how children were changed, children might be in the insertion list out of order; e.g. [child2, child1].
It's also possible that an earlier child (child1 in the above example) is being moved out of another container.
When processing the earlier insertion (child2), we'll determine we need to move it within its parent and will fetch its new previous sibling so we can move it into the right place.
However, in this case, the new previous sibling (child1) will be in the wrong container.
We can't move in that case; the new previous sibling's index in parent will obviously be wrong, since it's relative to the wrong container.
Therefore, we just skip the move.
Since the previous sibling (child1) is later in the insertion list, the ordering will be corrected when we process that insertion.
Differential Revision: https://phabricator.services.mozilla.com/D51037
--HG--
extra : moz-landing-system : lando
2019-10-30 09:32:50 +03:00
|
|
|
if (previousSibling && previousSibling->Parent() != aContainer) {
|
|
|
|
// previousSibling hasn't been moved into aContainer yet.
|
|
|
|
// previousSibling should be later in the insertion list, so the tree
|
|
|
|
// will get adjusted when we process it later.
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(parent == aContainer,
|
|
|
|
"Child moving to new parent, but previous "
|
|
|
|
"sibling in wrong parent");
|
|
|
|
continue;
|
|
|
|
}
|
2016-03-28 20:40:21 +03:00
|
|
|
#ifdef A11Y_LOG
|
2019-08-29 19:14:48 +03:00
|
|
|
logging::TreeInfo("relocating accessible", 0, "old parent", parent,
|
2016-03-28 20:40:21 +03:00
|
|
|
"new parent", aContainer, "child", iter.Child(),
|
|
|
|
nullptr);
|
|
|
|
#endif
|
2019-08-29 19:14:48 +03:00
|
|
|
MoveChild(iter.Child(), aContainer,
|
|
|
|
previousSibling ? previousSibling->IndexInParent() + 1 : 0);
|
2016-03-28 20:40:21 +03:00
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aContainer->InsertAfter(iter.Child(), iter.ChildBefore())) {
|
|
|
|
#ifdef A11Y_LOG
|
|
|
|
logging::TreeInfo("accessible was inserted", 0, "container", aContainer,
|
|
|
|
"child", iter.Child(), nullptr);
|
|
|
|
#endif
|
|
|
|
|
2016-11-17 03:16:13 +03:00
|
|
|
CreateSubtree(iter.Child());
|
2016-11-11 07:35:51 +03:00
|
|
|
mt.AfterInsertion(iter.Child());
|
2016-03-28 20:40:21 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
MOZ_ASSERT_UNREACHABLE("accessible was rejected");
|
2016-07-13 06:15:22 +03:00
|
|
|
iter.Rejected();
|
2016-04-07 16:30:22 +03:00
|
|
|
} while (iter.Next());
|
|
|
|
|
2016-04-01 17:53:52 +03:00
|
|
|
mt.Done();
|
2016-03-28 20:40:21 +03:00
|
|
|
|
|
|
|
#ifdef A11Y_LOG
|
|
|
|
logging::TreeInfo("children after insertion", logging::eVerbose, aContainer);
|
|
|
|
#endif
|
|
|
|
|
2016-04-25 16:10:41 +03:00
|
|
|
FireEventsOnInsertion(aContainer);
|
2016-03-29 16:20:43 +03:00
|
|
|
}
|
|
|
|
|
2016-04-06 03:48:30 +03:00
|
|
|
void DocAccessible::ProcessContentInserted(Accessible* aContainer,
|
|
|
|
nsIContent* aNode) {
|
|
|
|
if (!aContainer->IsInDocument()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-06-08 20:51:47 +03:00
|
|
|
#ifdef A11Y_LOG
|
|
|
|
logging::TreeInfo("children before insertion", logging::eVerbose, aContainer);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef A11Y_LOG
|
|
|
|
logging::TreeInfo("traversing an inserted node", logging::eVerbose,
|
|
|
|
"container", aContainer, "node", aNode);
|
|
|
|
#endif
|
|
|
|
|
2016-04-06 03:48:30 +03:00
|
|
|
TreeWalker walker(aContainer);
|
|
|
|
if (aContainer->IsAcceptableChild(aNode) && walker.Seek(aNode)) {
|
2016-04-06 14:23:41 +03:00
|
|
|
Accessible* child = GetAccessible(aNode);
|
|
|
|
if (!child) {
|
|
|
|
child = GetAccService()->CreateAccessible(aNode, aContainer);
|
|
|
|
}
|
2016-04-06 03:48:30 +03:00
|
|
|
|
|
|
|
if (child) {
|
2016-04-07 16:30:22 +03:00
|
|
|
TreeMutation mt(aContainer);
|
2016-07-13 06:15:22 +03:00
|
|
|
if (!aContainer->InsertAfter(child, walker.Prev())) {
|
|
|
|
return;
|
|
|
|
}
|
2016-11-11 07:35:51 +03:00
|
|
|
CreateSubtree(child);
|
2016-04-06 03:48:30 +03:00
|
|
|
mt.AfterInsertion(child);
|
|
|
|
mt.Done();
|
|
|
|
|
2016-04-25 16:10:41 +03:00
|
|
|
FireEventsOnInsertion(aContainer);
|
2016-04-06 03:48:30 +03:00
|
|
|
}
|
|
|
|
}
|
2016-06-08 20:51:47 +03:00
|
|
|
|
|
|
|
#ifdef A11Y_LOG
|
|
|
|
logging::TreeInfo("children after insertion", logging::eVerbose, aContainer);
|
|
|
|
#endif
|
2016-04-06 03:48:30 +03:00
|
|
|
}
|
|
|
|
|
2016-04-25 16:10:41 +03:00
|
|
|
void DocAccessible::FireEventsOnInsertion(Accessible* aContainer) {
|
2016-03-28 20:40:21 +03:00
|
|
|
// Check to see if change occurred inside an alert, and fire an EVENT_ALERT
|
|
|
|
// if it did.
|
2016-04-25 16:10:41 +03:00
|
|
|
if (aContainer->IsAlert() || aContainer->IsInsideAlert()) {
|
2016-03-28 20:40:21 +03:00
|
|
|
Accessible* ancestor = aContainer;
|
|
|
|
do {
|
|
|
|
if (ancestor->IsAlert()) {
|
|
|
|
FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} while ((ancestor = ancestor->Parent()));
|
2011-01-19 19:01:31 +03:00
|
|
|
}
|
2011-01-18 11:03:38 +03:00
|
|
|
}
|
|
|
|
|
2017-05-04 20:21:17 +03:00
|
|
|
void DocAccessible::ContentRemoved(Accessible* aChild) {
|
|
|
|
Accessible* parent = aChild->Parent();
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(parent, "Unattached accessible from tree");
|
2017-04-25 21:02:41 +03:00
|
|
|
|
2014-12-30 23:43:49 +03:00
|
|
|
#ifdef A11Y_LOG
|
2016-06-08 14:33:06 +03:00
|
|
|
logging::TreeInfo("process content removal", 0, "container", parent, "child",
|
2017-05-04 20:21:17 +03:00
|
|
|
aChild, nullptr);
|
2014-12-30 23:43:49 +03:00
|
|
|
#endif
|
2011-01-18 11:03:38 +03:00
|
|
|
|
2017-05-12 21:02:15 +03:00
|
|
|
// XXX: event coalescence may kill us
|
|
|
|
RefPtr<Accessible> kungFuDeathGripChild(aChild);
|
|
|
|
|
2017-05-04 20:21:17 +03:00
|
|
|
TreeMutation mt(parent);
|
|
|
|
mt.BeforeRemoval(aChild);
|
2017-05-12 21:02:15 +03:00
|
|
|
|
|
|
|
if (aChild->IsDefunct()) {
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Event coalescence killed the accessible");
|
|
|
|
mt.Done();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(aChild->Parent(), "Alive but unparented #1");
|
2017-05-04 20:21:17 +03:00
|
|
|
|
|
|
|
if (aChild->IsRelocated()) {
|
2019-09-10 19:16:53 +03:00
|
|
|
nsTArray<RefPtr<Accessible>>* owned = mARIAOwnsHash.Get(parent);
|
2017-05-04 20:21:17 +03:00
|
|
|
MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
|
|
|
|
owned->RemoveElement(aChild);
|
|
|
|
if (owned->Length() == 0) {
|
|
|
|
mARIAOwnsHash.Remove(parent);
|
|
|
|
}
|
|
|
|
}
|
2017-05-09 22:56:41 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(aChild->Parent(), "Unparented #2");
|
2017-05-04 20:21:17 +03:00
|
|
|
parent->RemoveChild(aChild);
|
|
|
|
UncacheChildrenInSubtree(aChild);
|
|
|
|
|
2017-04-25 21:02:41 +03:00
|
|
|
mt.Done();
|
|
|
|
}
|
2016-04-25 16:07:48 +03:00
|
|
|
|
2017-04-25 21:02:41 +03:00
|
|
|
void DocAccessible::ContentRemoved(nsIContent* aContentNode) {
|
|
|
|
// If child node is not accessible then look for its accessible children.
|
|
|
|
Accessible* acc = GetAccessible(aContentNode);
|
|
|
|
if (acc) {
|
|
|
|
ContentRemoved(acc);
|
|
|
|
}
|
2017-05-04 20:21:17 +03:00
|
|
|
|
|
|
|
dom::AllChildrenIterator iter =
|
|
|
|
dom::AllChildrenIterator(aContentNode, nsIContent::eAllChildren, true);
|
|
|
|
while (nsIContent* childNode = iter.GetNextChild()) {
|
|
|
|
ContentRemoved(childNode);
|
2014-12-30 23:43:49 +03:00
|
|
|
}
|
2019-08-29 02:02:19 +03:00
|
|
|
|
|
|
|
// If this node has a shadow root, remove its explicit children too.
|
|
|
|
// The host node may be removed after the shadow root was attached, and
|
|
|
|
// before we asynchronously prune the light DOM and construct the shadow DOM.
|
|
|
|
// If this is a case where the node does not have its own accessible, we will
|
|
|
|
// not recurse into its current children, so we need to use an
|
|
|
|
// ExplicitChildIterator in order to get its accessible children in the light
|
|
|
|
// DOM, since they are not accessible anymore via AllChildrenIterator.
|
|
|
|
if (aContentNode->GetShadowRoot()) {
|
|
|
|
dom::ExplicitChildIterator iter = dom::ExplicitChildIterator(aContentNode);
|
|
|
|
while (nsIContent* childNode = iter.GetNextChild()) {
|
|
|
|
ContentRemoved(childNode);
|
|
|
|
}
|
|
|
|
}
|
2011-01-18 11:03:38 +03:00
|
|
|
}
|
|
|
|
|
2015-10-30 01:08:48 +03:00
|
|
|
bool DocAccessible::RelocateARIAOwnedIfNeeded(nsIContent* aElement) {
|
|
|
|
if (!aElement->HasID()) return false;
|
|
|
|
|
2018-10-30 03:17:04 +03:00
|
|
|
AttrRelProviders* list = GetRelProviders(
|
|
|
|
aElement->AsElement(), nsDependentAtomString(aElement->GetID()));
|
2015-10-30 01:08:48 +03:00
|
|
|
if (list) {
|
|
|
|
for (uint32_t idx = 0; idx < list->Length(); idx++) {
|
|
|
|
if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {
|
|
|
|
Accessible* owner = GetAccessible(list->ElementAt(idx)->mContent);
|
|
|
|
if (owner) {
|
|
|
|
mNotificationController->ScheduleRelocation(owner);
|
2016-04-06 14:58:58 +03:00
|
|
|
return true;
|
2015-10-30 01:08:48 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-04-06 14:58:58 +03:00
|
|
|
|
|
|
|
return false;
|
2015-10-30 01:08:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner) {
|
2015-12-04 01:07:43 +03:00
|
|
|
MOZ_ASSERT(aOwner, "aOwner must be a valid pointer");
|
|
|
|
MOZ_ASSERT(aOwner->Elm(), "aOwner->Elm() must be a valid pointer");
|
|
|
|
|
2016-04-01 04:22:13 +03:00
|
|
|
#ifdef A11Y_LOG
|
|
|
|
logging::TreeInfo("aria owns relocation", logging::eVerbose, aOwner);
|
|
|
|
#endif
|
|
|
|
|
2019-09-10 19:16:53 +03:00
|
|
|
nsTArray<RefPtr<Accessible>>* owned = mARIAOwnsHash.LookupOrAdd(aOwner);
|
2017-06-29 21:30:58 +03:00
|
|
|
|
2015-10-30 01:08:48 +03:00
|
|
|
IDRefsIterator iter(this, aOwner->Elm(), nsGkAtoms::aria_owns);
|
2017-06-29 21:30:58 +03:00
|
|
|
uint32_t idx = 0;
|
2016-04-06 14:58:58 +03:00
|
|
|
while (nsIContent* childEl = iter.NextElem()) {
|
|
|
|
Accessible* child = GetAccessible(childEl);
|
2017-06-29 21:30:58 +03:00
|
|
|
auto insertIdx = aOwner->ChildCount() - owned->Length() + idx;
|
2016-04-06 14:58:58 +03:00
|
|
|
|
|
|
|
// Make an attempt to create an accessible if it wasn't created yet.
|
|
|
|
if (!child) {
|
2018-09-19 05:15:55 +03:00
|
|
|
// An owned child cannot be an ancestor of the owner.
|
2019-07-12 16:10:28 +03:00
|
|
|
if (aOwner->Elm()->IsInclusiveDescendantOf(childEl)) {
|
2018-09-19 05:15:55 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-04-06 14:58:58 +03:00
|
|
|
if (aOwner->IsAcceptableChild(childEl)) {
|
|
|
|
child = GetAccService()->CreateAccessible(childEl, aOwner);
|
|
|
|
if (child) {
|
2016-04-07 16:30:22 +03:00
|
|
|
TreeMutation imut(aOwner);
|
2016-04-06 14:58:58 +03:00
|
|
|
aOwner->InsertChildAt(insertIdx, child);
|
|
|
|
imut.AfterInsertion(child);
|
|
|
|
imut.Done();
|
|
|
|
|
|
|
|
child->SetRelocated(true);
|
2017-06-29 21:30:58 +03:00
|
|
|
owned->InsertElementAt(idx, child);
|
|
|
|
idx++;
|
2016-04-06 14:58:58 +03:00
|
|
|
|
2017-02-03 21:51:52 +03:00
|
|
|
// Create subtree before adjusting the insertion index, since subtree
|
|
|
|
// creation may alter children in the container.
|
2016-04-25 16:10:41 +03:00
|
|
|
CreateSubtree(child);
|
|
|
|
FireEventsOnInsertion(aOwner);
|
2016-04-06 14:58:58 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-04-01 04:22:13 +03:00
|
|
|
#ifdef A11Y_LOG
|
|
|
|
logging::TreeInfo("aria owns traversal", logging::eVerbose, "candidate",
|
|
|
|
child, nullptr);
|
|
|
|
#endif
|
|
|
|
|
2017-08-09 19:33:44 +03:00
|
|
|
if (owned->IndexOf(child) < idx) {
|
|
|
|
continue; // ignore second entry of same ID
|
|
|
|
}
|
|
|
|
|
2015-10-30 01:08:48 +03:00
|
|
|
// Same child on same position, no change.
|
2017-07-25 23:31:00 +03:00
|
|
|
if (child->Parent() == aOwner) {
|
|
|
|
int32_t indexInParent = child->IndexInParent();
|
|
|
|
|
|
|
|
// The child is being placed in its current index,
|
|
|
|
// eg. aria-owns='id1 id2 id3' is changed to aria-owns='id3 id2 id1'.
|
|
|
|
if (indexInParent == static_cast<int32_t>(insertIdx)) {
|
|
|
|
MOZ_ASSERT(child->IsRelocated(),
|
|
|
|
"A child, having an index in parent from aria ownded "
|
|
|
|
"indices range, has to be aria owned");
|
|
|
|
MOZ_ASSERT(owned->ElementAt(idx) == child,
|
|
|
|
"Unexpected child in ARIA owned array");
|
|
|
|
idx++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The child is being inserted directly after its current index,
|
|
|
|
// resulting in a no-move case. This will happen when a parent aria-owns
|
|
|
|
// its last ordinal child:
|
|
|
|
// <ul aria-owns='id2'><li id='id1'></li><li id='id2'></li></ul>
|
|
|
|
if (indexInParent == static_cast<int32_t>(insertIdx) - 1) {
|
|
|
|
MOZ_ASSERT(!child->IsRelocated(),
|
|
|
|
"Child should be in its ordinal position");
|
|
|
|
child->SetRelocated(true);
|
|
|
|
owned->InsertElementAt(idx, child);
|
|
|
|
idx++;
|
|
|
|
continue;
|
|
|
|
}
|
2015-10-30 01:08:48 +03:00
|
|
|
}
|
|
|
|
|
2017-06-29 21:30:58 +03:00
|
|
|
MOZ_ASSERT(owned->SafeElementAt(idx) != child, "Already in place!");
|
2015-10-30 01:08:48 +03:00
|
|
|
|
|
|
|
// A new child is found, check for loops.
|
|
|
|
if (child->Parent() != aOwner) {
|
2017-07-24 18:54:12 +03:00
|
|
|
// Child is aria-owned by another container, skip.
|
|
|
|
if (child->IsRelocated()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-10-30 01:08:48 +03:00
|
|
|
Accessible* parent = aOwner;
|
|
|
|
while (parent && parent != child && !parent->IsDoc()) {
|
|
|
|
parent = parent->Parent();
|
|
|
|
}
|
|
|
|
// A referred child cannot be a parent of the owner.
|
|
|
|
if (parent == child) {
|
|
|
|
continue;
|
2015-09-15 19:01:51 +03:00
|
|
|
}
|
|
|
|
}
|
2015-10-30 01:08:48 +03:00
|
|
|
|
2016-04-01 04:22:13 +03:00
|
|
|
if (MoveChild(child, aOwner, insertIdx)) {
|
|
|
|
child->SetRelocated(true);
|
2017-08-28 16:32:26 +03:00
|
|
|
MOZ_ASSERT(owned == mARIAOwnsHash.Get(aOwner));
|
|
|
|
owned = mARIAOwnsHash.LookupOrAdd(aOwner);
|
|
|
|
owned->InsertElementAt(idx, child);
|
2017-06-29 21:30:58 +03:00
|
|
|
idx++;
|
2015-10-30 01:08:48 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put back children that are not seized anymore.
|
2017-06-29 21:30:58 +03:00
|
|
|
PutChildrenBack(owned, idx);
|
|
|
|
if (owned->Length() == 0) {
|
2015-10-30 01:08:48 +03:00
|
|
|
mARIAOwnsHash.Remove(aOwner);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-10 19:16:53 +03:00
|
|
|
void DocAccessible::PutChildrenBack(nsTArray<RefPtr<Accessible>>* aChildren,
|
2016-04-01 04:22:13 +03:00
|
|
|
uint32_t aStartIdx) {
|
2017-06-29 21:30:58 +03:00
|
|
|
MOZ_ASSERT(aStartIdx <= aChildren->Length(), "Wrong removal index");
|
|
|
|
|
2016-04-01 04:22:13 +03:00
|
|
|
for (auto idx = aStartIdx; idx < aChildren->Length(); idx++) {
|
|
|
|
Accessible* child = aChildren->ElementAt(idx);
|
|
|
|
if (!child->IsInDocument()) {
|
|
|
|
continue;
|
|
|
|
}
|
2015-10-30 01:08:48 +03:00
|
|
|
|
2016-04-01 04:22:13 +03:00
|
|
|
// Remove the child from the owner
|
|
|
|
Accessible* owner = child->Parent();
|
|
|
|
if (!owner) {
|
|
|
|
NS_ERROR("Cannot put the child back. No parent, a broken tree.");
|
|
|
|
continue;
|
|
|
|
}
|
2015-10-30 01:08:48 +03:00
|
|
|
|
2016-03-01 22:35:01 +03:00
|
|
|
#ifdef A11Y_LOG
|
2016-04-01 04:22:13 +03:00
|
|
|
logging::TreeInfo("aria owns put child back", 0, "old parent", owner,
|
|
|
|
"child", child, nullptr);
|
2016-03-01 22:35:01 +03:00
|
|
|
#endif
|
|
|
|
|
2016-04-01 04:22:13 +03:00
|
|
|
// Unset relocated flag to find an insertion point for the child.
|
|
|
|
child->SetRelocated(false);
|
2015-10-30 01:08:48 +03:00
|
|
|
|
2017-08-01 17:05:00 +03:00
|
|
|
nsIContent* content = child->GetContent();
|
2016-04-01 04:22:13 +03:00
|
|
|
int32_t idxInParent = -1;
|
2018-01-03 21:59:16 +03:00
|
|
|
Accessible* origContainer =
|
|
|
|
AccessibleOrTrueContainer(content->GetFlattenedTreeParentNode());
|
2016-04-01 04:22:13 +03:00
|
|
|
if (origContainer) {
|
|
|
|
TreeWalker walker(origContainer);
|
2017-08-01 17:05:00 +03:00
|
|
|
if (walker.Seek(content)) {
|
2016-04-01 04:22:13 +03:00
|
|
|
Accessible* prevChild = walker.Prev();
|
2017-03-29 19:08:10 +03:00
|
|
|
if (prevChild) {
|
|
|
|
idxInParent = prevChild->IndexInParent() + 1;
|
2017-05-04 20:21:17 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(origContainer == prevChild->Parent(),
|
|
|
|
"Broken tree");
|
2017-03-29 19:08:10 +03:00
|
|
|
origContainer = prevChild->Parent();
|
|
|
|
} else {
|
|
|
|
idxInParent = 0;
|
|
|
|
}
|
2016-04-01 04:22:13 +03:00
|
|
|
}
|
|
|
|
}
|
2017-07-25 23:31:00 +03:00
|
|
|
|
|
|
|
// The child may have already be in its ordinal place for 2 reasons:
|
|
|
|
// 1. It was the last ordinal child, and the first aria-owned child.
|
|
|
|
// given: <ul id="list" aria-owns="b"><li id="a"></li><li
|
|
|
|
// id="b"></li></ul> after load: $("list").setAttribute("aria-owns", "");
|
|
|
|
// 2. The preceding adopted children were just reclaimed, eg:
|
|
|
|
// given: <ul id="list"><li id="b"></li></ul>
|
|
|
|
// after load: $("list").setAttribute("aria-owns", "a b");
|
|
|
|
// later: $("list").setAttribute("aria-owns", "");
|
|
|
|
if (origContainer != owner || child->IndexInParent() != idxInParent) {
|
2017-07-24 18:54:12 +03:00
|
|
|
DebugOnly<bool> moved = MoveChild(child, origContainer, idxInParent);
|
|
|
|
MOZ_ASSERT(moved, "Failed to put child back.");
|
2017-07-25 23:31:00 +03:00
|
|
|
} else {
|
|
|
|
MOZ_ASSERT(!child->PrevSibling() || !child->PrevSibling()->IsRelocated(),
|
|
|
|
"No relocated child should appear before this one");
|
|
|
|
MOZ_ASSERT(!child->NextSibling() || child->NextSibling()->IsRelocated(),
|
|
|
|
"No ordinal child should appear after this one");
|
|
|
|
}
|
2015-10-30 01:08:48 +03:00
|
|
|
}
|
|
|
|
|
2016-04-01 04:22:13 +03:00
|
|
|
aChildren->RemoveElementsAt(aStartIdx, aChildren->Length() - aStartIdx);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DocAccessible::MoveChild(Accessible* aChild, Accessible* aNewParent,
|
|
|
|
int32_t aIdxInParent) {
|
|
|
|
MOZ_ASSERT(aChild, "No child");
|
|
|
|
MOZ_ASSERT(aChild->Parent(), "No parent");
|
2019-04-18 12:39:59 +03:00
|
|
|
// We can't guarantee MoveChild works correctly for accessibilities storing
|
|
|
|
// children outside mChildren.
|
|
|
|
MOZ_ASSERT(
|
|
|
|
aIdxInParent <= static_cast<int32_t>(aNewParent->mChildren.Length()),
|
|
|
|
"Wrong insertion point for a moving child");
|
2016-04-01 04:22:13 +03:00
|
|
|
|
|
|
|
Accessible* curParent = aChild->Parent();
|
2015-10-30 01:08:48 +03:00
|
|
|
|
2017-07-24 18:54:12 +03:00
|
|
|
if (!aNewParent->IsAcceptableChild(aChild->GetContent())) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-03-01 22:35:01 +03:00
|
|
|
#ifdef A11Y_LOG
|
2016-04-01 04:22:13 +03:00
|
|
|
logging::TreeInfo("move child", 0, "old parent", curParent, "new parent",
|
|
|
|
aNewParent, "child", aChild, nullptr);
|
2016-03-01 22:35:01 +03:00
|
|
|
#endif
|
|
|
|
|
2017-06-27 03:20:40 +03:00
|
|
|
// Forget aria-owns info in case of ARIA owned element. The caller is expected
|
|
|
|
// to update it if needed.
|
2015-10-30 01:08:48 +03:00
|
|
|
if (aChild->IsRelocated()) {
|
2017-06-27 03:20:40 +03:00
|
|
|
aChild->SetRelocated(false);
|
2019-09-10 19:16:53 +03:00
|
|
|
nsTArray<RefPtr<Accessible>>* owned = mARIAOwnsHash.Get(curParent);
|
2017-05-04 20:21:17 +03:00
|
|
|
MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
|
|
|
|
owned->RemoveElement(aChild);
|
|
|
|
if (owned->Length() == 0) {
|
|
|
|
mARIAOwnsHash.Remove(curParent);
|
|
|
|
}
|
2015-10-30 01:08:48 +03:00
|
|
|
}
|
|
|
|
|
2016-09-29 22:44:18 +03:00
|
|
|
NotificationController::MoveGuard mguard(mNotificationController);
|
|
|
|
|
2016-04-01 04:22:13 +03:00
|
|
|
if (curParent == aNewParent) {
|
|
|
|
MOZ_ASSERT(aChild->IndexInParent() != aIdxInParent, "No move case");
|
2019-09-12 04:00:15 +03:00
|
|
|
curParent->RelocateChild(aIdxInParent, aChild);
|
2015-10-30 01:08:48 +03:00
|
|
|
|
2016-03-01 22:35:01 +03:00
|
|
|
#ifdef A11Y_LOG
|
2016-04-01 04:22:13 +03:00
|
|
|
logging::TreeInfo("move child: parent tree after", logging::eVerbose,
|
|
|
|
curParent);
|
2016-03-01 22:35:01 +03:00
|
|
|
#endif
|
2016-04-01 04:22:13 +03:00
|
|
|
return true;
|
|
|
|
}
|
2016-03-01 22:35:01 +03:00
|
|
|
|
2017-03-30 22:56:22 +03:00
|
|
|
// If the child cannot be re-inserted into the tree, then make sure to remove
|
|
|
|
// it from its present parent and then shutdown it.
|
|
|
|
bool hasInsertionPoint =
|
2019-04-18 12:39:59 +03:00
|
|
|
(aIdxInParent >= 0) &&
|
|
|
|
(aIdxInParent <= static_cast<int32_t>(aNewParent->mChildren.Length()));
|
2017-03-30 22:56:22 +03:00
|
|
|
|
2016-04-07 16:30:22 +03:00
|
|
|
TreeMutation rmut(curParent);
|
2017-03-30 22:56:22 +03:00
|
|
|
rmut.BeforeRemoval(aChild, hasInsertionPoint && TreeMutation::kNoShutdown);
|
2016-04-01 17:53:52 +03:00
|
|
|
curParent->RemoveChild(aChild);
|
|
|
|
rmut.Done();
|
2015-10-30 01:08:48 +03:00
|
|
|
|
2016-04-01 04:22:13 +03:00
|
|
|
// No insertion point for the child.
|
2017-03-30 22:56:22 +03:00
|
|
|
if (!hasInsertionPoint) {
|
2017-03-29 19:08:10 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-04-07 16:30:22 +03:00
|
|
|
TreeMutation imut(aNewParent);
|
2016-04-01 17:53:52 +03:00
|
|
|
aNewParent->InsertChildAt(aIdxInParent, aChild);
|
|
|
|
imut.AfterInsertion(aChild);
|
|
|
|
imut.Done();
|
2016-03-30 17:47:36 +03:00
|
|
|
|
|
|
|
#ifdef A11Y_LOG
|
2016-04-01 04:22:13 +03:00
|
|
|
logging::TreeInfo("move child: old parent tree after", logging::eVerbose,
|
|
|
|
curParent);
|
|
|
|
logging::TreeInfo("move child: new parent tree after", logging::eVerbose,
|
|
|
|
aNewParent);
|
2016-03-30 17:47:36 +03:00
|
|
|
#endif
|
2015-10-30 01:08:48 +03:00
|
|
|
|
2016-04-01 04:22:13 +03:00
|
|
|
return true;
|
2015-09-15 19:01:51 +03:00
|
|
|
}
|
|
|
|
|
2013-07-31 18:47:39 +04:00
|
|
|
void DocAccessible::CacheChildrenInSubtree(Accessible* aRoot,
|
|
|
|
Accessible** aFocusedAcc) {
|
|
|
|
// If the accessible is focused then report a focus event after all related
|
|
|
|
// mutation events.
|
|
|
|
if (aFocusedAcc && !*aFocusedAcc &&
|
|
|
|
FocusMgr()->HasDOMFocus(aRoot->GetContent()))
|
|
|
|
*aFocusedAcc = aRoot;
|
|
|
|
|
2016-04-01 18:07:57 +03:00
|
|
|
Accessible* root = aRoot->IsHTMLCombobox() ? aRoot->FirstChild() : aRoot;
|
|
|
|
if (root->KidsFromDOM()) {
|
2016-04-07 16:30:22 +03:00
|
|
|
TreeMutation mt(root, TreeMutation::kNoEvents);
|
2016-04-01 18:07:57 +03:00
|
|
|
TreeWalker walker(root);
|
|
|
|
while (Accessible* child = walker.Next()) {
|
|
|
|
if (child->IsBoundToParent()) {
|
2019-04-18 12:39:59 +03:00
|
|
|
MoveChild(child, root, root->mChildren.Length());
|
2016-04-01 18:07:57 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
root->AppendChild(child);
|
|
|
|
mt.AfterInsertion(child);
|
2011-01-20 11:02:00 +03:00
|
|
|
|
2013-07-31 18:47:39 +04:00
|
|
|
CacheChildrenInSubtree(child, aFocusedAcc);
|
2016-04-01 04:40:56 +03:00
|
|
|
}
|
2016-04-01 18:07:57 +03:00
|
|
|
mt.Done();
|
2011-01-20 11:02:00 +03:00
|
|
|
}
|
2012-06-08 17:36:41 +04:00
|
|
|
|
2016-04-25 16:10:41 +03:00
|
|
|
// Fire events for ARIA elements.
|
|
|
|
if (!aRoot->HasARIARole()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// XXX: we should delay document load complete event if the ARIA document
|
|
|
|
// has aria-busy.
|
2016-05-18 15:58:26 +03:00
|
|
|
roles::Role role = aRoot->ARIARole();
|
2018-02-06 13:13:00 +03:00
|
|
|
if (!aRoot->IsDoc() &&
|
|
|
|
(role == roles::DIALOG || role == roles::NON_NATIVE_DOCUMENT)) {
|
2016-04-25 16:10:41 +03:00
|
|
|
FireDelayedEvent(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE, aRoot);
|
2012-06-08 17:36:41 +04:00
|
|
|
}
|
2011-01-20 11:02:00 +03:00
|
|
|
}
|
|
|
|
|
2012-05-29 05:18:45 +04:00
|
|
|
void DocAccessible::UncacheChildrenInSubtree(Accessible* aRoot) {
|
2012-12-11 07:31:42 +04:00
|
|
|
aRoot->mStateFlags |= eIsNotInDocument;
|
2015-09-15 19:01:51 +03:00
|
|
|
RemoveDependentIDsFor(aRoot);
|
2011-02-11 18:05:38 +03:00
|
|
|
|
2019-09-10 19:16:53 +03:00
|
|
|
nsTArray<RefPtr<Accessible>>* owned = mARIAOwnsHash.Get(aRoot);
|
2012-08-22 19:56:38 +04:00
|
|
|
uint32_t count = aRoot->ContentChildCount();
|
2016-08-31 23:14:30 +03:00
|
|
|
for (uint32_t idx = 0; idx < count; idx++) {
|
|
|
|
Accessible* child = aRoot->ContentChildAt(idx);
|
|
|
|
|
2017-05-04 20:21:17 +03:00
|
|
|
if (child->IsRelocated()) {
|
|
|
|
MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
|
|
|
|
owned->RemoveElement(child);
|
|
|
|
if (owned->Length() == 0) {
|
|
|
|
mARIAOwnsHash.Remove(aRoot);
|
2017-06-27 03:20:40 +03:00
|
|
|
owned = nullptr;
|
2017-05-04 20:21:17 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-31 23:14:30 +03:00
|
|
|
// Removing this accessible from the document doesn't mean anything about
|
|
|
|
// accessibles for subdocuments, so skip removing those from the tree.
|
|
|
|
if (!child->IsDoc()) {
|
|
|
|
UncacheChildrenInSubtree(child);
|
|
|
|
}
|
|
|
|
}
|
2007-10-06 20:24:57 +04:00
|
|
|
|
2012-10-13 10:34:21 +04:00
|
|
|
if (aRoot->IsNodeMapEntry() &&
|
2010-10-21 08:16:10 +04:00
|
|
|
mNodeToAccessibleMap.Get(aRoot->GetNode()) == aRoot)
|
|
|
|
mNodeToAccessibleMap.Remove(aRoot->GetNode());
|
|
|
|
}
|
2007-10-06 20:24:57 +04:00
|
|
|
|
2012-05-29 05:18:45 +04:00
|
|
|
void DocAccessible::ShutdownChildrenInSubtree(Accessible* aAccessible) {
|
2010-10-21 08:16:10 +04:00
|
|
|
// Traverse through children and shutdown them before this accessible. When
|
|
|
|
// child gets shutdown then it removes itself from children array of its
|
|
|
|
// parent. Use jdx index to process the cases if child is not attached to the
|
|
|
|
// parent and as result doesn't remove itself from its children.
|
2012-08-22 19:56:38 +04:00
|
|
|
uint32_t count = aAccessible->ContentChildCount();
|
|
|
|
for (uint32_t idx = 0, jdx = 0; idx < count; idx++) {
|
2012-05-29 05:18:45 +04:00
|
|
|
Accessible* child = aAccessible->ContentChildAt(jdx);
|
2010-10-21 08:16:10 +04:00
|
|
|
if (!child->IsBoundToParent()) {
|
|
|
|
NS_ERROR("Parent refers to a child, child doesn't refer to parent!");
|
|
|
|
jdx++;
|
2010-07-02 05:49:42 +04:00
|
|
|
}
|
2009-09-09 13:03:14 +04:00
|
|
|
|
2013-05-04 15:06:22 +04:00
|
|
|
// Don't cross document boundaries. The outerdoc shutdown takes care about
|
|
|
|
// its subdocument.
|
|
|
|
if (!child->IsDoc()) ShutdownChildrenInSubtree(child);
|
2007-10-06 20:24:57 +04:00
|
|
|
}
|
|
|
|
|
2010-11-12 22:00:55 +03:00
|
|
|
UnbindFromDocument(aAccessible);
|
2007-10-06 20:24:57 +04:00
|
|
|
}
|
2011-08-08 11:55:36 +04:00
|
|
|
|
2012-05-27 13:01:40 +04:00
|
|
|
bool DocAccessible::IsLoadEventTarget() const {
|
2013-11-15 20:32:12 +04:00
|
|
|
nsCOMPtr<nsIDocShellTreeItem> treeItem = mDocumentNode->GetDocShell();
|
2012-10-10 05:01:46 +04:00
|
|
|
NS_ASSERTION(treeItem, "No document shell for document!");
|
2011-08-08 11:55:36 +04:00
|
|
|
|
|
|
|
nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
|
2019-07-26 19:48:31 +03:00
|
|
|
treeItem->GetInProcessParent(getter_AddRefs(parentTreeItem));
|
2011-08-08 11:55:36 +04:00
|
|
|
|
2012-10-10 05:01:46 +04:00
|
|
|
// Not a root document.
|
2012-06-02 09:14:42 +04:00
|
|
|
if (parentTreeItem) {
|
2012-10-10 05:01:46 +04:00
|
|
|
// Return true if it's either:
|
|
|
|
// a) tab document;
|
|
|
|
nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
|
2019-08-07 22:24:00 +03:00
|
|
|
treeItem->GetInProcessRootTreeItem(getter_AddRefs(rootTreeItem));
|
2012-10-10 05:01:46 +04:00
|
|
|
if (parentTreeItem == rootTreeItem) return true;
|
|
|
|
|
|
|
|
// b) frame/iframe document and its parent document is not in loading state
|
|
|
|
// Note: we can get notifications while document is loading (and thus
|
|
|
|
// while there's no parent document yet).
|
2012-06-02 09:14:42 +04:00
|
|
|
DocAccessible* parentDoc = ParentDocument();
|
|
|
|
return parentDoc && parentDoc->HasLoadState(eCompletelyLoaded);
|
|
|
|
}
|
2011-08-08 11:55:36 +04:00
|
|
|
|
2012-05-30 06:21:24 +04:00
|
|
|
// It's content (not chrome) root document.
|
2014-01-20 11:58:26 +04:00
|
|
|
return (treeItem->ItemType() == nsIDocShellTreeItem::typeContent);
|
2011-08-08 11:55:36 +04:00
|
|
|
}
|
2018-08-15 20:07:00 +03:00
|
|
|
|
2019-02-26 20:05:47 +03:00
|
|
|
void DocAccessible::SetIPCDoc(DocAccessibleChild* aIPCDoc) {
|
|
|
|
MOZ_ASSERT(!mIPCDoc || !aIPCDoc, "Clobbering an attached IPCDoc!");
|
|
|
|
mIPCDoc = aIPCDoc;
|
|
|
|
}
|
|
|
|
|
2020-02-17 07:09:34 +03:00
|
|
|
void DocAccessible::DispatchScrollingEvent(nsINode* aTarget,
|
|
|
|
uint32_t aEventType) {
|
|
|
|
Accessible* acc = GetAccessible(aTarget);
|
|
|
|
if (!acc) {
|
2018-08-21 17:24:00 +03:00
|
|
|
return;
|
|
|
|
}
|
2018-08-15 20:07:00 +03:00
|
|
|
|
2020-02-21 00:12:43 +03:00
|
|
|
nsIFrame* frame = acc->GetFrame();
|
|
|
|
if (!frame) {
|
|
|
|
// Although the accessible had a frame at scroll time, it may now be gone
|
|
|
|
// because of display: contents.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-02-17 07:09:34 +03:00
|
|
|
LayoutDevicePoint scrollPoint;
|
|
|
|
LayoutDeviceRect scrollRange;
|
|
|
|
nsIScrollableFrame* sf = acc == this
|
|
|
|
? mPresShell->GetRootScrollFrameAsScrollable()
|
2020-02-21 00:12:43 +03:00
|
|
|
: frame->GetScrollTargetFrame();
|
2018-08-15 20:07:00 +03:00
|
|
|
|
2020-02-17 07:09:34 +03:00
|
|
|
// If there is no scrollable frame, it's likely a scroll in a popup, like
|
|
|
|
// <select>. Just send an event with no scroll info. The scroll info
|
|
|
|
// is currently only used on Android, and popups are rendered natively
|
|
|
|
// there.
|
|
|
|
if (sf) {
|
|
|
|
int32_t appUnitsPerDevPixel =
|
|
|
|
mPresShell->GetPresContext()->AppUnitsPerDevPixel();
|
|
|
|
scrollPoint = LayoutDevicePoint::FromAppUnits(sf->GetScrollPosition(),
|
|
|
|
appUnitsPerDevPixel) *
|
|
|
|
mPresShell->GetResolution();
|
|
|
|
|
|
|
|
scrollRange = LayoutDeviceRect::FromAppUnits(sf->GetScrollRange(),
|
|
|
|
appUnitsPerDevPixel);
|
|
|
|
scrollRange.ScaleRoundOut(mPresShell->GetResolution());
|
|
|
|
}
|
2018-08-15 20:07:00 +03:00
|
|
|
|
|
|
|
RefPtr<AccEvent> event =
|
2020-02-17 07:09:34 +03:00
|
|
|
new AccScrollingEvent(aEventType, acc, scrollPoint.x, scrollPoint.y,
|
2018-08-15 20:07:00 +03:00
|
|
|
scrollRange.width, scrollRange.height);
|
|
|
|
nsEventShell::FireEvent(event);
|
|
|
|
}
|
2018-09-05 07:43:18 +03:00
|
|
|
|
|
|
|
void DocAccessible::ARIAActiveDescendantIDMaybeMoved(dom::Element* aElm) {
|
|
|
|
nsINode* focusNode = FocusMgr()->FocusedDOMNode();
|
|
|
|
// The focused element must be within this document.
|
|
|
|
if (!focusNode || focusNode->OwnerDoc() != mDocumentNode) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dom::Element* focusElm = nullptr;
|
|
|
|
if (focusNode == mDocumentNode) {
|
|
|
|
// The document is focused, so look for aria-activedescendant on the
|
|
|
|
// body/root.
|
|
|
|
focusElm = Elm();
|
|
|
|
if (!focusElm) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
MOZ_ASSERT(focusNode->IsElement());
|
|
|
|
focusElm = focusNode->AsElement();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the focus has aria-activedescendant and whether
|
|
|
|
// it refers to the id just set on aElm.
|
|
|
|
nsAutoString id;
|
|
|
|
aElm->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
|
|
|
|
if (!focusElm->AttrValueIs(kNameSpaceID_None,
|
|
|
|
nsGkAtoms::aria_activedescendant, id,
|
|
|
|
eCaseMatters)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The aria-activedescendant target has probably changed.
|
|
|
|
Accessible* acc = GetAccessibleEvenIfNotInMapOrContainer(focusNode);
|
|
|
|
if (!acc) {
|
|
|
|
return;
|
|
|
|
}
|
2018-09-07 17:47:51 +03:00
|
|
|
|
2018-09-05 07:43:18 +03:00
|
|
|
// The active descendant might have just been inserted and may not be in the
|
|
|
|
// tree yet. Therefore, schedule this async to ensure the tree is up to date.
|
|
|
|
mNotificationController->ScheduleNotification<DocAccessible, Accessible>(
|
|
|
|
this, &DocAccessible::ARIAActiveDescendantChanged, acc);
|
|
|
|
}
|
2020-01-23 07:34:22 +03:00
|
|
|
|
|
|
|
void DocAccessible::SetRoleMapEntryForDoc(dom::Element* aElement) {
|
|
|
|
const nsRoleMapEntry* entry = aria::GetRoleMap(aElement);
|
|
|
|
if (!entry || entry->role == roles::APPLICATION ||
|
2020-06-02 08:53:52 +03:00
|
|
|
entry->role == roles::DIALOG ||
|
|
|
|
// Role alert isn't valid on the body element according to the ARIA spec,
|
|
|
|
// but it's useful for our UI; e.g. the WebRTC sharing indicator.
|
|
|
|
(entry->role == roles::ALERT &&
|
|
|
|
!nsCoreUtils::IsContentDocument(mDocumentNode))) {
|
2020-01-23 07:34:22 +03:00
|
|
|
SetRoleMapEntry(entry);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// No other ARIA roles are valid on body elements.
|
|
|
|
SetRoleMapEntry(nullptr);
|
|
|
|
}
|