Bug 1890277: part 2) Add `require-trusted-types-for` directive to CSP parser, guarded...
[gecko.git] / dom / base / SelectionChangeEventDispatcher.cpp
bloba669c45d53eb9d54e43fc21a57087620beb047f6
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/. */
7 /*
8 * Implementation of mozilla::SelectionChangeEventDispatcher
9 */
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"
19 #include "nsCOMPtr.h"
20 #include "nsContentUtils.h"
21 #include "nsFrameSelection.h"
22 #include "nsRange.h"
24 namespace mozilla {
26 using namespace dom;
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();
35 } else {
36 mStartContainer = nullptr;
37 mEndContainer = nullptr;
38 mStartOffset = 0;
39 mEndOffset = 0;
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",
61 aFlags);
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,
75 Selection* aSel,
76 int16_t aReason) {
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();
82 if (!changed) {
83 for (const uint32_t i : IntegerRange(mOldRanges.Length())) {
84 if (!mOldRanges[i].Equals(aSel->GetRangeAt(i))) {
85 changed = true;
86 break;
91 if (!changed) {
92 return;
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()) {
107 return;
110 const Document* doc = aSel->GetParentObject();
111 if (MOZ_UNLIKELY(!doc)) {
112 return;
114 const nsPIDOMWindowInner* inner = doc->GetInnerWindow();
115 if (MOZ_UNLIKELY(!inner)) {
116 return;
118 const bool maybeHasSelectionChangeEventListeners =
119 !inner || inner->HasSelectionChangeEventListeners();
120 const bool maybeHasFormSelectEventListeners =
121 !inner || inner->HasFormSelectEventListeners();
122 if (!maybeHasSelectionChangeEventListeners &&
123 !maybeHasFormSelectEventListeners) {
124 return;
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) {
153 return;
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
158 // default.
159 // See https://github.com/w3c/selection-api/issues/53.
160 if (textControl &&
161 !StaticPrefs::dom_select_events_textcontrols_selectionchange_enabled()) {
162 return;
165 nsINode* target =
166 textControl ? static_cast<nsINode*>(textControl.get()) : aDoc;
167 if (target) {
168 CanBubble canBubble = textControl ? CanBubble::eYes : CanBubble::eNo;
169 RefPtr<AsyncEventDispatcher> asyncDispatcher =
170 new AsyncEventDispatcher(target, eSelectionChange, canBubble);
171 asyncDispatcher->PostDOMEvent();
175 } // namespace mozilla