1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * Implementation of mozilla::SelectionChangeEventDispatcher
11 #include "SelectionChangeEventDispatcher.h"
13 #include "mozilla/AsyncEventDispatcher.h"
14 #include "mozilla/BasePrincipal.h"
15 #include "mozilla/IntegerRange.h"
16 #include "mozilla/StaticPrefs_dom.h"
17 #include "mozilla/dom/Document.h"
18 #include "mozilla/dom/Selection.h"
20 #include "nsContentUtils.h"
21 #include "nsFrameSelection.h"
28 SelectionChangeEventDispatcher::RawRangeData::RawRangeData(
29 const nsRange
* aRange
) {
30 if (aRange
->IsPositioned()) {
31 mStartContainer
= aRange
->GetStartContainer();
32 mEndContainer
= aRange
->GetEndContainer();
33 mStartOffset
= aRange
->StartOffset();
34 mEndOffset
= aRange
->EndOffset();
36 mStartContainer
= nullptr;
37 mEndContainer
= nullptr;
43 bool SelectionChangeEventDispatcher::RawRangeData::Equals(
44 const nsRange
* aRange
) {
45 if (!aRange
->IsPositioned()) {
46 return !mStartContainer
;
48 return mStartContainer
== aRange
->GetStartContainer() &&
49 mEndContainer
== aRange
->GetEndContainer() &&
50 mStartOffset
== aRange
->StartOffset() &&
51 mEndOffset
== aRange
->EndOffset();
54 inline void ImplCycleCollectionTraverse(
55 nsCycleCollectionTraversalCallback
& aCallback
,
56 SelectionChangeEventDispatcher::RawRangeData
& aField
, const char* aName
,
57 uint32_t aFlags
= 0) {
58 ImplCycleCollectionTraverse(aCallback
, aField
.mStartContainer
,
59 "mStartContainer", aFlags
);
60 ImplCycleCollectionTraverse(aCallback
, aField
.mEndContainer
, "mEndContainer",
64 NS_IMPL_CYCLE_COLLECTION_CLASS(SelectionChangeEventDispatcher
)
66 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SelectionChangeEventDispatcher
)
67 tmp
->mOldRanges
.Clear();
68 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
70 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SelectionChangeEventDispatcher
)
71 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOldRanges
);
72 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
74 void SelectionChangeEventDispatcher::OnSelectionChange(Document
* aDoc
,
77 // Check if the ranges have actually changed
78 // Don't bother checking this if we are hiding changes.
79 if (mOldRanges
.Length() == aSel
->RangeCount() &&
80 !aSel
->IsBlockingSelectionChangeEvents()) {
81 bool changed
= mOldDirection
!= aSel
->GetDirection();
83 for (const uint32_t i
: IntegerRange(mOldRanges
.Length())) {
84 if (!mOldRanges
[i
].Equals(aSel
->GetRangeAt(i
))) {
96 // The ranges have actually changed, update the mOldRanges array
97 mOldRanges
.ClearAndRetainStorage();
98 for (const uint32_t i
: IntegerRange(aSel
->RangeCount())) {
99 mOldRanges
.AppendElement(RawRangeData(aSel
->GetRangeAt(i
)));
101 mOldDirection
= aSel
->GetDirection();
103 // If we are hiding changes, then don't do anything else. We do this after we
104 // update mOldRanges so that changes after the changes stop being hidden don't
105 // incorrectly trigger a change, even though they didn't change anything
106 if (aSel
->IsBlockingSelectionChangeEvents()) {
110 const Document
* doc
= aSel
->GetParentObject();
111 if (MOZ_UNLIKELY(!doc
)) {
114 const nsPIDOMWindowInner
* inner
= doc
->GetInnerWindow();
115 if (MOZ_UNLIKELY(!inner
)) {
118 const bool maybeHasSelectionChangeEventListeners
=
119 !inner
|| inner
->HasSelectionChangeEventListeners();
120 const bool maybeHasFormSelectEventListeners
=
121 !inner
|| inner
->HasFormSelectEventListeners();
122 if (!maybeHasSelectionChangeEventListeners
&&
123 !maybeHasFormSelectEventListeners
) {
127 // Be aware, don't call GetTextControlFromSelectionLimiter once you might
128 // run script because selection limit may have already been changed by it.
129 nsCOMPtr
<nsIContent
> textControl
;
130 if ((maybeHasFormSelectEventListeners
&&
131 (aReason
& nsISelectionListener::JS_REASON
)) ||
132 maybeHasSelectionChangeEventListeners
) {
133 if (const nsFrameSelection
* fs
= aSel
->GetFrameSelection()) {
134 if (nsCOMPtr
<nsIContent
> root
= fs
->GetLimiter()) {
135 textControl
= root
->GetClosestNativeAnonymousSubtreeRootParentOrHost();
136 MOZ_ASSERT_IF(textControl
,
137 textControl
->IsTextControlElement() &&
138 !textControl
->IsInNativeAnonymousSubtree());
143 // Selection changes with non-JS reason only cares about whether the new
144 // selection is collapsed or not. See TextInputListener::OnSelectionChange.
145 if (textControl
&& maybeHasFormSelectEventListeners
&&
146 (aReason
& nsISelectionListener::JS_REASON
)) {
147 RefPtr
<AsyncEventDispatcher
> asyncDispatcher
=
148 new AsyncEventDispatcher(textControl
, eFormSelect
, CanBubble::eYes
);
149 asyncDispatcher
->PostDOMEvent();
152 if (!maybeHasSelectionChangeEventListeners
) {
156 // The spec currently doesn't say that we should dispatch this event on text
157 // controls, so for now we only support doing that under a pref, disabled by
159 // See https://github.com/w3c/selection-api/issues/53.
161 !StaticPrefs::dom_select_events_textcontrols_selectionchange_enabled()) {
166 textControl
? static_cast<nsINode
*>(textControl
.get()) : aDoc
;
171 if (target
->HasScheduledSelectionChangeEvent()) {
175 target
->SetHasScheduledSelectionChangeEvent();
177 CanBubble canBubble
= textControl
? CanBubble::eYes
: CanBubble::eNo
;
178 RefPtr
<AsyncEventDispatcher
> asyncDispatcher
=
179 new AsyncSelectionChangeEventDispatcher(target
, eSelectionChange
,
181 asyncDispatcher
->PostDOMEvent();
184 } // namespace mozilla