Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=2 sw=2 et tw=78: */
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/. */
6 :
7 : /*
8 : * Implementation of mozilla::dom::SelectionChangeListener
9 : */
10 :
11 : #include "SelectionChangeListener.h"
12 :
13 : #include "nsContentUtils.h"
14 : #include "nsFrameSelection.h"
15 : #include "nsRange.h"
16 : #include "Selection.h"
17 :
18 : using namespace mozilla;
19 : using namespace mozilla::dom;
20 :
21 15 : SelectionChangeListener::RawRangeData::RawRangeData(const nsRange* aRange)
22 : {
23 30 : mozilla::ErrorResult rv;
24 15 : mStartContainer = aRange->GetStartContainer(rv);
25 15 : rv.SuppressException();
26 15 : mEndContainer = aRange->GetEndContainer(rv);
27 15 : rv.SuppressException();
28 15 : mStartOffset = aRange->GetStartOffset(rv);
29 15 : rv.SuppressException();
30 15 : mEndOffset = aRange->GetEndOffset(rv);
31 15 : rv.SuppressException();
32 15 : }
33 :
34 : bool
35 3 : SelectionChangeListener::RawRangeData::Equals(const nsRange* aRange)
36 : {
37 6 : mozilla::ErrorResult rv;
38 3 : bool eq = mStartContainer == aRange->GetStartContainer(rv);
39 3 : rv.SuppressException();
40 3 : eq = eq && mEndContainer == aRange->GetEndContainer(rv);
41 3 : rv.SuppressException();
42 3 : eq = eq && mStartOffset == aRange->GetStartOffset(rv);
43 3 : rv.SuppressException();
44 3 : eq = eq && mEndOffset == aRange->GetEndOffset(rv);
45 3 : rv.SuppressException();
46 6 : return eq;
47 : }
48 :
49 : inline void
50 0 : ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
51 : SelectionChangeListener::RawRangeData& aField,
52 : const char* aName,
53 : uint32_t aFlags = 0)
54 : {
55 0 : ImplCycleCollectionTraverse(aCallback, aField.mStartContainer,
56 0 : "mStartContainer", aFlags);
57 0 : ImplCycleCollectionTraverse(aCallback, aField.mEndContainer,
58 0 : "mEndContainer", aFlags);
59 0 : }
60 :
61 : NS_IMPL_CYCLE_COLLECTION_CLASS(SelectionChangeListener)
62 :
63 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SelectionChangeListener)
64 0 : tmp->mOldRanges.Clear();
65 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
66 :
67 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SelectionChangeListener)
68 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOldRanges);
69 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
70 :
71 64 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SelectionChangeListener)
72 32 : NS_INTERFACE_MAP_ENTRY(nsISupports)
73 32 : NS_INTERFACE_MAP_ENTRY(nsISelectionListener)
74 0 : NS_INTERFACE_MAP_END
75 :
76 119 : NS_IMPL_CYCLE_COLLECTING_ADDREF(SelectionChangeListener)
77 87 : NS_IMPL_CYCLE_COLLECTING_RELEASE(SelectionChangeListener)
78 :
79 : NS_IMETHODIMP
80 23 : SelectionChangeListener::NotifySelectionChanged(nsIDOMDocument* aDoc,
81 : nsISelection* aSel, int16_t aReason)
82 : {
83 46 : RefPtr<Selection> sel = aSel->AsSelection();
84 :
85 23 : nsIDocument* doc = sel->GetParentObject();
86 29 : if (!(doc && nsContentUtils::IsSystemPrincipal(doc->NodePrincipal())) &&
87 6 : !nsFrameSelection::sSelectionEventsEnabled) {
88 0 : return NS_OK;
89 : }
90 :
91 : // Check if the ranges have actually changed
92 : // Don't bother checking this if we are hiding changes.
93 23 : if (mOldRanges.Length() == sel->RangeCount() && !sel->IsBlockingSelectionChangeEvents()) {
94 5 : bool changed = false;
95 :
96 5 : for (size_t i = 0; i < mOldRanges.Length(); i++) {
97 3 : if (!mOldRanges[i].Equals(sel->GetRangeAt(i))) {
98 3 : changed = true;
99 3 : break;
100 : }
101 : }
102 :
103 5 : if (!changed) {
104 2 : return NS_OK;
105 : }
106 : }
107 :
108 : // The ranges have actually changed, update the mOldRanges array
109 21 : mOldRanges.ClearAndRetainStorage();
110 36 : for (size_t i = 0; i < sel->RangeCount(); i++) {
111 15 : mOldRanges.AppendElement(RawRangeData(sel->GetRangeAt(i)));
112 : }
113 :
114 21 : if (doc) {
115 21 : nsPIDOMWindowInner* inner = doc->GetInnerWindow();
116 21 : if (inner && !inner->HasSelectionChangeEventListeners()) {
117 17 : return NS_OK;
118 : }
119 : }
120 :
121 : // If we are hiding changes, then don't do anything else. We do this after we
122 : // update mOldRanges so that changes after the changes stop being hidden don't
123 : // incorrectly trigger a change, even though they didn't change anything
124 4 : if (sel->IsBlockingSelectionChangeEvents()) {
125 0 : return NS_OK;
126 : }
127 :
128 : // The spec currently doesn't say that we should dispatch this event on text
129 : // controls, so for now we only support doing that under a pref, disabled by
130 : // default.
131 : // See https://github.com/w3c/selection-api/issues/53.
132 4 : if (nsFrameSelection::sSelectionEventsOnTextControlsEnabled) {
133 8 : nsCOMPtr<nsINode> target;
134 :
135 : // Check if we should be firing this event to a different node than the
136 : // document. The limiter of the nsFrameSelection will be within the native
137 : // anonymous subtree of the node we want to fire the event on. We need to
138 : // climb up the parent chain to escape the native anonymous subtree, and then
139 : // fire the event.
140 4 : if (const nsFrameSelection* fs = sel->GetFrameSelection()) {
141 8 : if (nsCOMPtr<nsIContent> root = fs->GetLimiter()) {
142 0 : while (root && root->IsInNativeAnonymousSubtree()) {
143 0 : root = root->GetParent();
144 : }
145 :
146 0 : target = root.forget();
147 : }
148 : }
149 :
150 : // If we didn't get a target before, we can instead fire the event at the document.
151 4 : if (!target) {
152 8 : nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc);
153 4 : target = doc.forget();
154 : }
155 :
156 4 : if (target) {
157 : RefPtr<AsyncEventDispatcher> asyncDispatcher =
158 12 : new AsyncEventDispatcher(target, eSelectionChange, false);
159 4 : asyncDispatcher->PostDOMEvent();
160 : }
161 : } else {
162 0 : if (const nsFrameSelection* fs = sel->GetFrameSelection()) {
163 0 : if (nsCOMPtr<nsIContent> root = fs->GetLimiter()) {
164 0 : if (root->IsInNativeAnonymousSubtree()) {
165 0 : return NS_OK;
166 : }
167 : }
168 : }
169 :
170 0 : nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc);
171 0 : if (doc) {
172 : RefPtr<AsyncEventDispatcher> asyncDispatcher =
173 0 : new AsyncEventDispatcher(doc, eSelectionChange, false);
174 0 : asyncDispatcher->PostDOMEvent();
175 : }
176 : }
177 :
178 4 : return NS_OK;
179 : }
|