summaryrefslogtreecommitdiffstats
path: root/accessible/base/SelectionManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--accessible/base/SelectionManager.cpp250
1 files changed, 250 insertions, 0 deletions
diff --git a/accessible/base/SelectionManager.cpp b/accessible/base/SelectionManager.cpp
new file mode 100644
index 0000000000..f31746d6eb
--- /dev/null
+++ b/accessible/base/SelectionManager.cpp
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "mozilla/a11y/SelectionManager.h"
+
+#include "DocAccessible-inl.h"
+#include "HyperTextAccessible.h"
+#include "HyperTextAccessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "nsEventShell.h"
+#include "nsFrameSelection.h"
+#include "TextLeafRange.h"
+
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/StaticPrefs_accessibility.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using mozilla::dom::Selection;
+
+struct mozilla::a11y::SelData final {
+ SelData(Selection* aSel, int32_t aReason, int32_t aGranularity)
+ : mSel(aSel), mReason(aReason), mGranularity(aGranularity) {}
+
+ RefPtr<Selection> mSel;
+ int16_t mReason;
+ int32_t mGranularity;
+
+ NS_INLINE_DECL_REFCOUNTING(SelData)
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~SelData() {}
+};
+
+SelectionManager::SelectionManager()
+ : mCaretOffset(-1), mAccWithCaret(nullptr) {}
+
+void SelectionManager::ClearControlSelectionListener() {
+ // Remove 'this' registered as selection listener for the normal selection.
+ if (mCurrCtrlNormalSel) {
+ mCurrCtrlNormalSel->RemoveSelectionListener(this);
+ mCurrCtrlNormalSel = nullptr;
+ }
+
+ // Remove 'this' registered as selection listener for the spellcheck
+ // selection.
+ if (mCurrCtrlSpellSel) {
+ mCurrCtrlSpellSel->RemoveSelectionListener(this);
+ mCurrCtrlSpellSel = nullptr;
+ }
+}
+
+void SelectionManager::SetControlSelectionListener(dom::Element* aFocusedElm) {
+ // When focus moves such that the caret is part of a new frame selection
+ // this removes the old selection listener and attaches a new one for
+ // the current focus.
+ ClearControlSelectionListener();
+
+ nsIFrame* controlFrame = aFocusedElm->GetPrimaryFrame();
+ if (!controlFrame) return;
+
+ const nsFrameSelection* frameSel = controlFrame->GetConstFrameSelection();
+ NS_ASSERTION(frameSel, "No frame selection for focused element!");
+ if (!frameSel) return;
+
+ // Register 'this' as selection listener for the normal selection.
+ Selection* normalSel = frameSel->GetSelection(SelectionType::eNormal);
+ normalSel->AddSelectionListener(this);
+ mCurrCtrlNormalSel = normalSel;
+
+ // Register 'this' as selection listener for the spell check selection.
+ Selection* spellSel = frameSel->GetSelection(SelectionType::eSpellCheck);
+ spellSel->AddSelectionListener(this);
+ mCurrCtrlSpellSel = spellSel;
+}
+
+void SelectionManager::AddDocSelectionListener(PresShell* aPresShell) {
+ const nsFrameSelection* frameSel = aPresShell->ConstFrameSelection();
+
+ // Register 'this' as selection listener for the normal selection.
+ Selection* normalSel = frameSel->GetSelection(SelectionType::eNormal);
+ normalSel->AddSelectionListener(this);
+
+ // Register 'this' as selection listener for the spell check selection.
+ Selection* spellSel = frameSel->GetSelection(SelectionType::eSpellCheck);
+ spellSel->AddSelectionListener(this);
+}
+
+void SelectionManager::RemoveDocSelectionListener(PresShell* aPresShell) {
+ const nsFrameSelection* frameSel = aPresShell->ConstFrameSelection();
+
+ // Remove 'this' registered as selection listener for the normal selection.
+ Selection* normalSel = frameSel->GetSelection(SelectionType::eNormal);
+ normalSel->RemoveSelectionListener(this);
+
+ // Remove 'this' registered as selection listener for the spellcheck
+ // selection.
+ Selection* spellSel = frameSel->GetSelection(SelectionType::eSpellCheck);
+ spellSel->RemoveSelectionListener(this);
+
+ if (mCurrCtrlNormalSel) {
+ if (mCurrCtrlNormalSel->GetPresShell() == aPresShell) {
+ // Remove 'this' registered as selection listener for the normal selection
+ // if we are removing listeners for its PresShell.
+ mCurrCtrlNormalSel->RemoveSelectionListener(this);
+ mCurrCtrlNormalSel = nullptr;
+ }
+ }
+
+ if (mCurrCtrlSpellSel) {
+ if (mCurrCtrlSpellSel->GetPresShell() == aPresShell) {
+ // Remove 'this' registered as selection listener for the spellcheck
+ // selection if we are removing listeners for its PresShell.
+ mCurrCtrlSpellSel->RemoveSelectionListener(this);
+ mCurrCtrlSpellSel = nullptr;
+ }
+ }
+}
+
+void SelectionManager::ProcessTextSelChangeEvent(AccEvent* aEvent) {
+ // Fire selection change event if it's not pure caret-move selection change,
+ // i.e. the accessible has or had not collapsed selection. Also, it must not
+ // be a collapsed selection on the container of a focused text field, since
+ // the text field has an independent selection and will thus fire its own
+ // selection events.
+ AccTextSelChangeEvent* event = downcast_accEvent(aEvent);
+ if (!event->IsCaretMoveOnly() &&
+ !(event->mSel->IsCollapsed() && event->mSel != mCurrCtrlNormalSel &&
+ FocusMgr() && FocusMgr()->FocusedLocalAccessible() &&
+ FocusMgr()->FocusedLocalAccessible()->IsTextField())) {
+ nsEventShell::FireEvent(aEvent);
+ }
+
+ // Fire caret move event if there's a caret in the selection.
+ nsINode* caretCntrNode = nsCoreUtils::GetDOMNodeFromDOMPoint(
+ event->mSel->GetFocusNode(), event->mSel->FocusOffset());
+ if (!caretCntrNode) return;
+
+ HyperTextAccessible* caretCntr = nsAccUtils::GetTextContainer(caretCntrNode);
+ NS_ASSERTION(
+ caretCntr,
+ "No text container for focus while there's one for common ancestor?!");
+ if (!caretCntr) return;
+
+ Selection* selection = caretCntr->DOMSelection();
+
+ // XXX Sometimes we can't get a selection for caretCntr, in that case assume
+ // event->mSel is correct.
+ if (!selection) selection = event->mSel;
+
+ mCaretOffset = caretCntr->DOMPointToOffset(selection->GetFocusNode(),
+ selection->FocusOffset());
+ mAccWithCaret = caretCntr;
+ if (mCaretOffset != -1) {
+ RefPtr<AccCaretMoveEvent> caretMoveEvent =
+ new AccCaretMoveEvent(caretCntr, mCaretOffset, selection->IsCollapsed(),
+ caretCntr->IsCaretAtEndOfLine(),
+ event->GetGranularity(), aEvent->FromUserInput());
+ nsEventShell::FireEvent(caretMoveEvent);
+ }
+}
+
+NS_IMETHODIMP
+SelectionManager::NotifySelectionChanged(dom::Document* aDocument,
+ Selection* aSelection, int16_t aReason,
+ int32_t aAmount) {
+ if (NS_WARN_IF(!aDocument) || NS_WARN_IF(!aSelection)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ DocAccessible* document = GetAccService()->GetDocAccessible(aDocument);
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eSelection)) {
+ logging::SelChange(aSelection, document, aReason);
+ }
+#endif
+
+ if (document) {
+ // Selection manager has longer lifetime than any document accessible,
+ // so that we are guaranteed that the notification is processed before
+ // the selection manager is destroyed.
+ RefPtr<SelData> selData = new SelData(aSelection, aReason, aAmount);
+ document->HandleNotification<SelectionManager, SelData>(
+ this, &SelectionManager::ProcessSelectionChanged, selData);
+ }
+
+ return NS_OK;
+}
+
+void SelectionManager::ProcessSelectionChanged(SelData* aSelData) {
+ Selection* selection = aSelData->mSel;
+ if (!selection->GetPresShell()) return;
+
+ const nsRange* range = selection->GetAnchorFocusRange();
+ nsINode* cntrNode = nullptr;
+ if (range) {
+ cntrNode = range->GetClosestCommonInclusiveAncestor();
+ }
+
+ if (!cntrNode) {
+ cntrNode = selection->GetFrameSelection()->GetAncestorLimiter();
+ if (!cntrNode) {
+ cntrNode = selection->GetPresShell()->GetDocument();
+ NS_ASSERTION(aSelData->mSel->GetPresShell()->ConstFrameSelection() ==
+ selection->GetFrameSelection(),
+ "Wrong selection container was used!");
+ }
+ }
+
+ HyperTextAccessible* text = nsAccUtils::GetTextContainer(cntrNode);
+ if (!text) {
+ // FIXME bug 1126649
+ NS_ERROR("We must reach document accessible implementing text interface!");
+ return;
+ }
+
+ if (selection->GetType() == SelectionType::eNormal) {
+ RefPtr<AccEvent> event = new AccTextSelChangeEvent(
+ text, selection, aSelData->mReason, aSelData->mGranularity);
+ text->Document()->FireDelayedEvent(event);
+
+ } else if (selection->GetType() == SelectionType::eSpellCheck) {
+ // XXX: fire an event for container accessible of the focus/anchor range
+ // of the spelcheck selection.
+ text->Document()->FireDelayedEvent(
+ nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED, text);
+ }
+}
+
+void SelectionManager::SpellCheckRangeChanged(const nsRange& aRange) {
+ // Events are fired in SelectionManager::NotifySelectionChanged. This is only
+ // used to push cache updates.
+ if (StaticPrefs::accessibility_cache_enabled_AtStartup() &&
+ IPCAccessibilityActive()) {
+ dom::Document* doc = aRange.GetStartContainer()->OwnerDoc();
+ MOZ_ASSERT(doc);
+ TextLeafPoint::UpdateCachedSpellingError(doc, aRange);
+ }
+}
+
+SelectionManager::~SelectionManager() = default;