Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #include "mozilla/a11y/SelectionManager.h" |
michael@0 | 7 | |
michael@0 | 8 | #include "DocAccessible-inl.h" |
michael@0 | 9 | #include "nsAccessibilityService.h" |
michael@0 | 10 | #include "nsAccUtils.h" |
michael@0 | 11 | #include "nsCoreUtils.h" |
michael@0 | 12 | #include "nsEventShell.h" |
michael@0 | 13 | |
michael@0 | 14 | #include "nsIAccessibleTypes.h" |
michael@0 | 15 | #include "nsIDOMDocument.h" |
michael@0 | 16 | #include "nsIPresShell.h" |
michael@0 | 17 | #include "mozilla/dom/Selection.h" |
michael@0 | 18 | #include "mozilla/dom/Element.h" |
michael@0 | 19 | |
michael@0 | 20 | using namespace mozilla; |
michael@0 | 21 | using namespace mozilla::a11y; |
michael@0 | 22 | using mozilla::dom::Selection; |
michael@0 | 23 | |
michael@0 | 24 | struct mozilla::a11y::SelData MOZ_FINAL |
michael@0 | 25 | { |
michael@0 | 26 | SelData(Selection* aSel, int32_t aReason) : |
michael@0 | 27 | mSel(aSel), mReason(aReason) {} |
michael@0 | 28 | |
michael@0 | 29 | nsRefPtr<Selection> mSel; |
michael@0 | 30 | int16_t mReason; |
michael@0 | 31 | |
michael@0 | 32 | NS_INLINE_DECL_REFCOUNTING(SelData) |
michael@0 | 33 | |
michael@0 | 34 | private: |
michael@0 | 35 | // Private destructor, to discourage deletion outside of Release(): |
michael@0 | 36 | ~SelData() {} |
michael@0 | 37 | }; |
michael@0 | 38 | |
michael@0 | 39 | void |
michael@0 | 40 | SelectionManager::ClearControlSelectionListener() |
michael@0 | 41 | { |
michael@0 | 42 | if (!mCurrCtrlFrame) |
michael@0 | 43 | return; |
michael@0 | 44 | |
michael@0 | 45 | const nsFrameSelection* frameSel = mCurrCtrlFrame->GetConstFrameSelection(); |
michael@0 | 46 | NS_ASSERTION(frameSel, "No frame selection for the element!"); |
michael@0 | 47 | |
michael@0 | 48 | mCurrCtrlFrame = nullptr; |
michael@0 | 49 | if (!frameSel) |
michael@0 | 50 | return; |
michael@0 | 51 | |
michael@0 | 52 | // Remove 'this' registered as selection listener for the normal selection. |
michael@0 | 53 | Selection* normalSel = |
michael@0 | 54 | frameSel->GetSelection(nsISelectionController::SELECTION_NORMAL); |
michael@0 | 55 | normalSel->RemoveSelectionListener(this); |
michael@0 | 56 | |
michael@0 | 57 | // Remove 'this' registered as selection listener for the spellcheck |
michael@0 | 58 | // selection. |
michael@0 | 59 | Selection* spellSel = |
michael@0 | 60 | frameSel->GetSelection(nsISelectionController::SELECTION_SPELLCHECK); |
michael@0 | 61 | spellSel->RemoveSelectionListener(this); |
michael@0 | 62 | } |
michael@0 | 63 | |
michael@0 | 64 | void |
michael@0 | 65 | SelectionManager::SetControlSelectionListener(dom::Element* aFocusedElm) |
michael@0 | 66 | { |
michael@0 | 67 | // When focus moves such that the caret is part of a new frame selection |
michael@0 | 68 | // this removes the old selection listener and attaches a new one for |
michael@0 | 69 | // the current focus. |
michael@0 | 70 | ClearControlSelectionListener(); |
michael@0 | 71 | |
michael@0 | 72 | mCurrCtrlFrame = aFocusedElm->GetPrimaryFrame(); |
michael@0 | 73 | if (!mCurrCtrlFrame) |
michael@0 | 74 | return; |
michael@0 | 75 | |
michael@0 | 76 | const nsFrameSelection* frameSel = mCurrCtrlFrame->GetConstFrameSelection(); |
michael@0 | 77 | NS_ASSERTION(frameSel, "No frame selection for focused element!"); |
michael@0 | 78 | if (!frameSel) |
michael@0 | 79 | return; |
michael@0 | 80 | |
michael@0 | 81 | // Register 'this' as selection listener for the normal selection. |
michael@0 | 82 | Selection* normalSel = |
michael@0 | 83 | frameSel->GetSelection(nsISelectionController::SELECTION_NORMAL); |
michael@0 | 84 | normalSel->AddSelectionListener(this); |
michael@0 | 85 | |
michael@0 | 86 | // Register 'this' as selection listener for the spell check selection. |
michael@0 | 87 | Selection* spellSel = |
michael@0 | 88 | frameSel->GetSelection(nsISelectionController::SELECTION_SPELLCHECK); |
michael@0 | 89 | spellSel->AddSelectionListener(this); |
michael@0 | 90 | } |
michael@0 | 91 | |
michael@0 | 92 | void |
michael@0 | 93 | SelectionManager::AddDocSelectionListener(nsIPresShell* aPresShell) |
michael@0 | 94 | { |
michael@0 | 95 | const nsFrameSelection* frameSel = aPresShell->ConstFrameSelection(); |
michael@0 | 96 | |
michael@0 | 97 | // Register 'this' as selection listener for the normal selection. |
michael@0 | 98 | Selection* normalSel = |
michael@0 | 99 | frameSel->GetSelection(nsISelectionController::SELECTION_NORMAL); |
michael@0 | 100 | normalSel->AddSelectionListener(this); |
michael@0 | 101 | |
michael@0 | 102 | // Register 'this' as selection listener for the spell check selection. |
michael@0 | 103 | Selection* spellSel = |
michael@0 | 104 | frameSel->GetSelection(nsISelectionController::SELECTION_SPELLCHECK); |
michael@0 | 105 | spellSel->AddSelectionListener(this); |
michael@0 | 106 | } |
michael@0 | 107 | |
michael@0 | 108 | void |
michael@0 | 109 | SelectionManager::RemoveDocSelectionListener(nsIPresShell* aPresShell) |
michael@0 | 110 | { |
michael@0 | 111 | const nsFrameSelection* frameSel = aPresShell->ConstFrameSelection(); |
michael@0 | 112 | |
michael@0 | 113 | // Remove 'this' registered as selection listener for the normal selection. |
michael@0 | 114 | Selection* normalSel = |
michael@0 | 115 | frameSel->GetSelection(nsISelectionController::SELECTION_NORMAL); |
michael@0 | 116 | normalSel->RemoveSelectionListener(this); |
michael@0 | 117 | |
michael@0 | 118 | // Remove 'this' registered as selection listener for the spellcheck |
michael@0 | 119 | // selection. |
michael@0 | 120 | Selection* spellSel = |
michael@0 | 121 | frameSel->GetSelection(nsISelectionController::SELECTION_SPELLCHECK); |
michael@0 | 122 | spellSel->RemoveSelectionListener(this); |
michael@0 | 123 | } |
michael@0 | 124 | |
michael@0 | 125 | void |
michael@0 | 126 | SelectionManager::ProcessTextSelChangeEvent(AccEvent* aEvent) |
michael@0 | 127 | { |
michael@0 | 128 | // Fire selection change event if it's not pure caret-move selection change, |
michael@0 | 129 | // i.e. the accessible has or had not collapsed selection. |
michael@0 | 130 | AccTextSelChangeEvent* event = downcast_accEvent(aEvent); |
michael@0 | 131 | if (!event->IsCaretMoveOnly()) |
michael@0 | 132 | nsEventShell::FireEvent(aEvent); |
michael@0 | 133 | |
michael@0 | 134 | // Fire caret move event if there's a caret in the selection. |
michael@0 | 135 | nsINode* caretCntrNode = |
michael@0 | 136 | nsCoreUtils::GetDOMNodeFromDOMPoint(event->mSel->GetFocusNode(), |
michael@0 | 137 | event->mSel->FocusOffset()); |
michael@0 | 138 | if (!caretCntrNode) |
michael@0 | 139 | return; |
michael@0 | 140 | |
michael@0 | 141 | HyperTextAccessible* caretCntr = nsAccUtils::GetTextContainer(caretCntrNode); |
michael@0 | 142 | NS_ASSERTION(caretCntr, |
michael@0 | 143 | "No text container for focus while there's one for common ancestor?!"); |
michael@0 | 144 | if (!caretCntr) |
michael@0 | 145 | return; |
michael@0 | 146 | |
michael@0 | 147 | int32_t caretOffset = caretCntr->CaretOffset(); |
michael@0 | 148 | if (caretOffset != -1) { |
michael@0 | 149 | nsRefPtr<AccCaretMoveEvent> caretMoveEvent = |
michael@0 | 150 | new AccCaretMoveEvent(caretCntr, caretOffset, aEvent->FromUserInput()); |
michael@0 | 151 | nsEventShell::FireEvent(caretMoveEvent); |
michael@0 | 152 | } |
michael@0 | 153 | } |
michael@0 | 154 | |
michael@0 | 155 | NS_IMETHODIMP |
michael@0 | 156 | SelectionManager::NotifySelectionChanged(nsIDOMDocument* aDOMDocument, |
michael@0 | 157 | nsISelection* aSelection, |
michael@0 | 158 | int16_t aReason) |
michael@0 | 159 | { |
michael@0 | 160 | NS_ENSURE_ARG(aDOMDocument); |
michael@0 | 161 | |
michael@0 | 162 | nsCOMPtr<nsIDocument> documentNode(do_QueryInterface(aDOMDocument)); |
michael@0 | 163 | DocAccessible* document = GetAccService()->GetDocAccessible(documentNode); |
michael@0 | 164 | |
michael@0 | 165 | #ifdef A11Y_LOG |
michael@0 | 166 | if (logging::IsEnabled(logging::eSelection)) |
michael@0 | 167 | logging::SelChange(aSelection, document, aReason); |
michael@0 | 168 | #endif |
michael@0 | 169 | |
michael@0 | 170 | // Don't fire events until document is loaded. |
michael@0 | 171 | if (document && document->IsContentLoaded()) { |
michael@0 | 172 | // Selection manager has longer lifetime than any document accessible, |
michael@0 | 173 | // so that we are guaranteed that the notification is processed before |
michael@0 | 174 | // the selection manager is destroyed. |
michael@0 | 175 | nsRefPtr<SelData> selData = |
michael@0 | 176 | new SelData(static_cast<Selection*>(aSelection), aReason); |
michael@0 | 177 | document->HandleNotification<SelectionManager, SelData> |
michael@0 | 178 | (this, &SelectionManager::ProcessSelectionChanged, selData); |
michael@0 | 179 | } |
michael@0 | 180 | |
michael@0 | 181 | return NS_OK; |
michael@0 | 182 | } |
michael@0 | 183 | |
michael@0 | 184 | void |
michael@0 | 185 | SelectionManager::ProcessSelectionChanged(SelData* aSelData) |
michael@0 | 186 | { |
michael@0 | 187 | Selection* selection = aSelData->mSel; |
michael@0 | 188 | if (!selection->GetPresShell()) |
michael@0 | 189 | return; |
michael@0 | 190 | |
michael@0 | 191 | const nsRange* range = selection->GetAnchorFocusRange(); |
michael@0 | 192 | nsINode* cntrNode = nullptr; |
michael@0 | 193 | if (range) |
michael@0 | 194 | cntrNode = range->GetCommonAncestor(); |
michael@0 | 195 | |
michael@0 | 196 | if (!cntrNode) { |
michael@0 | 197 | cntrNode = selection->GetFrameSelection()->GetAncestorLimiter(); |
michael@0 | 198 | if (!cntrNode) { |
michael@0 | 199 | cntrNode = selection->GetPresShell()->GetDocument(); |
michael@0 | 200 | NS_ASSERTION(aSelData->mSel->GetPresShell()->ConstFrameSelection() == selection->GetFrameSelection(), |
michael@0 | 201 | "Wrong selection container was used!"); |
michael@0 | 202 | } |
michael@0 | 203 | } |
michael@0 | 204 | |
michael@0 | 205 | HyperTextAccessible* text = nsAccUtils::GetTextContainer(cntrNode); |
michael@0 | 206 | if (!text) { |
michael@0 | 207 | NS_NOTREACHED("We must reach document accessible implementing text interface!"); |
michael@0 | 208 | return; |
michael@0 | 209 | } |
michael@0 | 210 | |
michael@0 | 211 | if (selection->GetType() == nsISelectionController::SELECTION_NORMAL) { |
michael@0 | 212 | nsRefPtr<AccEvent> event = |
michael@0 | 213 | new AccTextSelChangeEvent(text, selection, aSelData->mReason); |
michael@0 | 214 | text->Document()->FireDelayedEvent(event); |
michael@0 | 215 | |
michael@0 | 216 | } else if (selection->GetType() == nsISelectionController::SELECTION_SPELLCHECK) { |
michael@0 | 217 | // XXX: fire an event for container accessible of the focus/anchor range |
michael@0 | 218 | // of the spelcheck selection. |
michael@0 | 219 | text->Document()->FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED, |
michael@0 | 220 | text); |
michael@0 | 221 | } |
michael@0 | 222 | } |