michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/a11y/SelectionManager.h" michael@0: michael@0: #include "DocAccessible-inl.h" michael@0: #include "nsAccessibilityService.h" michael@0: #include "nsAccUtils.h" michael@0: #include "nsCoreUtils.h" michael@0: #include "nsEventShell.h" michael@0: michael@0: #include "nsIAccessibleTypes.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIPresShell.h" michael@0: #include "mozilla/dom/Selection.h" michael@0: #include "mozilla/dom/Element.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::a11y; michael@0: using mozilla::dom::Selection; michael@0: michael@0: struct mozilla::a11y::SelData MOZ_FINAL michael@0: { michael@0: SelData(Selection* aSel, int32_t aReason) : michael@0: mSel(aSel), mReason(aReason) {} michael@0: michael@0: nsRefPtr mSel; michael@0: int16_t mReason; michael@0: michael@0: NS_INLINE_DECL_REFCOUNTING(SelData) michael@0: michael@0: private: michael@0: // Private destructor, to discourage deletion outside of Release(): michael@0: ~SelData() {} michael@0: }; michael@0: michael@0: void michael@0: SelectionManager::ClearControlSelectionListener() michael@0: { michael@0: if (!mCurrCtrlFrame) michael@0: return; michael@0: michael@0: const nsFrameSelection* frameSel = mCurrCtrlFrame->GetConstFrameSelection(); michael@0: NS_ASSERTION(frameSel, "No frame selection for the element!"); michael@0: michael@0: mCurrCtrlFrame = nullptr; michael@0: if (!frameSel) michael@0: return; michael@0: michael@0: // Remove 'this' registered as selection listener for the normal selection. michael@0: Selection* normalSel = michael@0: frameSel->GetSelection(nsISelectionController::SELECTION_NORMAL); michael@0: normalSel->RemoveSelectionListener(this); michael@0: michael@0: // Remove 'this' registered as selection listener for the spellcheck michael@0: // selection. michael@0: Selection* spellSel = michael@0: frameSel->GetSelection(nsISelectionController::SELECTION_SPELLCHECK); michael@0: spellSel->RemoveSelectionListener(this); michael@0: } michael@0: michael@0: void michael@0: SelectionManager::SetControlSelectionListener(dom::Element* aFocusedElm) michael@0: { michael@0: // When focus moves such that the caret is part of a new frame selection michael@0: // this removes the old selection listener and attaches a new one for michael@0: // the current focus. michael@0: ClearControlSelectionListener(); michael@0: michael@0: mCurrCtrlFrame = aFocusedElm->GetPrimaryFrame(); michael@0: if (!mCurrCtrlFrame) michael@0: return; michael@0: michael@0: const nsFrameSelection* frameSel = mCurrCtrlFrame->GetConstFrameSelection(); michael@0: NS_ASSERTION(frameSel, "No frame selection for focused element!"); michael@0: if (!frameSel) michael@0: return; michael@0: michael@0: // Register 'this' as selection listener for the normal selection. michael@0: Selection* normalSel = michael@0: frameSel->GetSelection(nsISelectionController::SELECTION_NORMAL); michael@0: normalSel->AddSelectionListener(this); michael@0: michael@0: // Register 'this' as selection listener for the spell check selection. michael@0: Selection* spellSel = michael@0: frameSel->GetSelection(nsISelectionController::SELECTION_SPELLCHECK); michael@0: spellSel->AddSelectionListener(this); michael@0: } michael@0: michael@0: void michael@0: SelectionManager::AddDocSelectionListener(nsIPresShell* aPresShell) michael@0: { michael@0: const nsFrameSelection* frameSel = aPresShell->ConstFrameSelection(); michael@0: michael@0: // Register 'this' as selection listener for the normal selection. michael@0: Selection* normalSel = michael@0: frameSel->GetSelection(nsISelectionController::SELECTION_NORMAL); michael@0: normalSel->AddSelectionListener(this); michael@0: michael@0: // Register 'this' as selection listener for the spell check selection. michael@0: Selection* spellSel = michael@0: frameSel->GetSelection(nsISelectionController::SELECTION_SPELLCHECK); michael@0: spellSel->AddSelectionListener(this); michael@0: } michael@0: michael@0: void michael@0: SelectionManager::RemoveDocSelectionListener(nsIPresShell* aPresShell) michael@0: { michael@0: const nsFrameSelection* frameSel = aPresShell->ConstFrameSelection(); michael@0: michael@0: // Remove 'this' registered as selection listener for the normal selection. michael@0: Selection* normalSel = michael@0: frameSel->GetSelection(nsISelectionController::SELECTION_NORMAL); michael@0: normalSel->RemoveSelectionListener(this); michael@0: michael@0: // Remove 'this' registered as selection listener for the spellcheck michael@0: // selection. michael@0: Selection* spellSel = michael@0: frameSel->GetSelection(nsISelectionController::SELECTION_SPELLCHECK); michael@0: spellSel->RemoveSelectionListener(this); michael@0: } michael@0: michael@0: void michael@0: SelectionManager::ProcessTextSelChangeEvent(AccEvent* aEvent) michael@0: { michael@0: // Fire selection change event if it's not pure caret-move selection change, michael@0: // i.e. the accessible has or had not collapsed selection. michael@0: AccTextSelChangeEvent* event = downcast_accEvent(aEvent); michael@0: if (!event->IsCaretMoveOnly()) michael@0: nsEventShell::FireEvent(aEvent); michael@0: michael@0: // Fire caret move event if there's a caret in the selection. michael@0: nsINode* caretCntrNode = michael@0: nsCoreUtils::GetDOMNodeFromDOMPoint(event->mSel->GetFocusNode(), michael@0: event->mSel->FocusOffset()); michael@0: if (!caretCntrNode) michael@0: return; michael@0: michael@0: HyperTextAccessible* caretCntr = nsAccUtils::GetTextContainer(caretCntrNode); michael@0: NS_ASSERTION(caretCntr, michael@0: "No text container for focus while there's one for common ancestor?!"); michael@0: if (!caretCntr) michael@0: return; michael@0: michael@0: int32_t caretOffset = caretCntr->CaretOffset(); michael@0: if (caretOffset != -1) { michael@0: nsRefPtr caretMoveEvent = michael@0: new AccCaretMoveEvent(caretCntr, caretOffset, aEvent->FromUserInput()); michael@0: nsEventShell::FireEvent(caretMoveEvent); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: SelectionManager::NotifySelectionChanged(nsIDOMDocument* aDOMDocument, michael@0: nsISelection* aSelection, michael@0: int16_t aReason) michael@0: { michael@0: NS_ENSURE_ARG(aDOMDocument); michael@0: michael@0: nsCOMPtr documentNode(do_QueryInterface(aDOMDocument)); michael@0: DocAccessible* document = GetAccService()->GetDocAccessible(documentNode); michael@0: michael@0: #ifdef A11Y_LOG michael@0: if (logging::IsEnabled(logging::eSelection)) michael@0: logging::SelChange(aSelection, document, aReason); michael@0: #endif michael@0: michael@0: // Don't fire events until document is loaded. michael@0: if (document && document->IsContentLoaded()) { michael@0: // Selection manager has longer lifetime than any document accessible, michael@0: // so that we are guaranteed that the notification is processed before michael@0: // the selection manager is destroyed. michael@0: nsRefPtr selData = michael@0: new SelData(static_cast(aSelection), aReason); michael@0: document->HandleNotification michael@0: (this, &SelectionManager::ProcessSelectionChanged, selData); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: SelectionManager::ProcessSelectionChanged(SelData* aSelData) michael@0: { michael@0: Selection* selection = aSelData->mSel; michael@0: if (!selection->GetPresShell()) michael@0: return; michael@0: michael@0: const nsRange* range = selection->GetAnchorFocusRange(); michael@0: nsINode* cntrNode = nullptr; michael@0: if (range) michael@0: cntrNode = range->GetCommonAncestor(); michael@0: michael@0: if (!cntrNode) { michael@0: cntrNode = selection->GetFrameSelection()->GetAncestorLimiter(); michael@0: if (!cntrNode) { michael@0: cntrNode = selection->GetPresShell()->GetDocument(); michael@0: NS_ASSERTION(aSelData->mSel->GetPresShell()->ConstFrameSelection() == selection->GetFrameSelection(), michael@0: "Wrong selection container was used!"); michael@0: } michael@0: } michael@0: michael@0: HyperTextAccessible* text = nsAccUtils::GetTextContainer(cntrNode); michael@0: if (!text) { michael@0: NS_NOTREACHED("We must reach document accessible implementing text interface!"); michael@0: return; michael@0: } michael@0: michael@0: if (selection->GetType() == nsISelectionController::SELECTION_NORMAL) { michael@0: nsRefPtr event = michael@0: new AccTextSelChangeEvent(text, selection, aSelData->mReason); michael@0: text->Document()->FireDelayedEvent(event); michael@0: michael@0: } else if (selection->GetType() == nsISelectionController::SELECTION_SPELLCHECK) { michael@0: // XXX: fire an event for container accessible of the focus/anchor range michael@0: // of the spelcheck selection. michael@0: text->Document()->FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED, michael@0: text); michael@0: } michael@0: }