Bug 1776056 - Switch to the tab an animation is running and make sure the animation...
[gecko.git] / editor / libeditor / EditorUtils.cpp
blob1bfb94df02f6251b47f891068b72e2d49e673e93
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "EditorUtils.h"
8 #include "EditorDOMPoint.h" // for EditorDOMPoint, EditorDOMRange, etc
9 #include "HTMLEditHelpers.h" // for MoveNodeResult
10 #include "HTMLEditUtils.h" // for HTMLEditUtils
11 #include "TextEditor.h" // for TextEditor
13 #include "mozilla/ComputedStyle.h" // for ComputedStyle
14 #include "mozilla/IntegerRange.h" // for IntegerRange
15 #include "mozilla/dom/Document.h" // for dom::Document
16 #include "mozilla/dom/Selection.h" // for dom::Selection
17 #include "mozilla/dom/Text.h" // for dom::Text
19 #include "nsComponentManagerUtils.h" // for do_CreateInstance
20 #include "nsContentUtils.h" // for nsContentUtils
21 #include "nsComputedDOMStyle.h" // for nsComputedDOMStyle
22 #include "nsError.h" // for NS_SUCCESS_* and NS_ERROR_*
23 #include "nsFrameSelection.h" // for nsFrameSelection
24 #include "nsIContent.h" // for nsIContent
25 #include "nsINode.h" // for nsINode
26 #include "nsITransferable.h" // for nsITransferable
27 #include "nsRange.h" // for nsRange
28 #include "nsStyleConsts.h" // for StyleWhiteSpace
29 #include "nsStyleStruct.h" // for nsStyleText, etc
31 namespace mozilla {
33 using namespace dom;
35 /******************************************************************************
36 * mozilla::EditActionResult
37 *****************************************************************************/
39 EditActionResult& EditActionResult::operator|=(
40 const MoveNodeResult& aMoveNodeResult) {
41 mHandled |= aMoveNodeResult.Handled();
42 return *this;
45 /******************************************************************************
46 * some general purpose editor utils
47 *****************************************************************************/
49 bool EditorUtils::IsDescendantOf(const nsINode& aNode, const nsINode& aParent,
50 EditorRawDOMPoint* aOutPoint /* = nullptr */) {
51 if (aOutPoint) {
52 aOutPoint->Clear();
55 if (&aNode == &aParent) {
56 return false;
59 for (const nsINode* node = &aNode; node; node = node->GetParentNode()) {
60 if (node->GetParentNode() == &aParent) {
61 if (aOutPoint) {
62 MOZ_ASSERT(node->IsContent());
63 aOutPoint->Set(node->AsContent());
65 return true;
69 return false;
72 bool EditorUtils::IsDescendantOf(const nsINode& aNode, const nsINode& aParent,
73 EditorDOMPoint* aOutPoint) {
74 MOZ_ASSERT(aOutPoint);
75 aOutPoint->Clear();
76 if (&aNode == &aParent) {
77 return false;
80 for (const nsINode* node = &aNode; node; node = node->GetParentNode()) {
81 if (node->GetParentNode() == &aParent) {
82 MOZ_ASSERT(node->IsContent());
83 aOutPoint->Set(node->AsContent());
84 return true;
88 return false;
91 // static
92 Maybe<StyleWhiteSpace> EditorUtils::GetComputedWhiteSpaceStyle(
93 const nsIContent& aContent) {
94 if (MOZ_UNLIKELY(!aContent.IsElement() && !aContent.GetParentElement())) {
95 return Nothing();
97 RefPtr<const ComputedStyle> elementStyle =
98 nsComputedDOMStyle::GetComputedStyleNoFlush(
99 aContent.IsElement() ? aContent.AsElement()
100 : aContent.GetParentElement());
101 if (NS_WARN_IF(!elementStyle)) {
102 return Nothing();
104 return Some(elementStyle->StyleText()->mWhiteSpace);
107 // static
108 bool EditorUtils::IsWhiteSpacePreformatted(const nsIContent& aContent) {
109 // Look at the node (and its parent if it's not an element), and grab its
110 // ComputedStyle.
111 Element* element = aContent.GetAsElementOrParentElement();
112 if (!element) {
113 return false;
116 RefPtr<const ComputedStyle> elementStyle =
117 nsComputedDOMStyle::GetComputedStyleNoFlush(element);
118 if (!elementStyle) {
119 // Consider nodes without a ComputedStyle to be NOT preformatted:
120 // For instance, this is true of JS tags inside the body (which show
121 // up as #text nodes but have no ComputedStyle).
122 return false;
125 return elementStyle->StyleText()->WhiteSpaceIsSignificant();
128 // static
129 bool EditorUtils::IsNewLinePreformatted(const nsIContent& aContent) {
130 // Look at the node (and its parent if it's not an element), and grab its
131 // ComputedStyle.
132 Element* element = aContent.GetAsElementOrParentElement();
133 if (!element) {
134 return false;
137 RefPtr<const ComputedStyle> elementStyle =
138 nsComputedDOMStyle::GetComputedStyleNoFlush(element);
139 if (!elementStyle) {
140 // Consider nodes without a ComputedStyle to be NOT preformatted:
141 // For instance, this is true of JS tags inside the body (which show
142 // up as #text nodes but have no ComputedStyle).
143 return false;
146 return elementStyle->StyleText()->NewlineIsSignificantStyle();
149 // static
150 bool EditorUtils::IsOnlyNewLinePreformatted(const nsIContent& aContent) {
151 // Look at the node (and its parent if it's not an element), and grab its
152 // ComputedStyle.
153 Element* element = aContent.GetAsElementOrParentElement();
154 if (!element) {
155 return false;
158 RefPtr<const ComputedStyle> elementStyle =
159 nsComputedDOMStyle::GetComputedStyleNoFlush(element);
160 if (!elementStyle) {
161 // Consider nodes without a ComputedStyle to be NOT preformatted:
162 // For instance, this is true of JS tags inside the body (which show
163 // up as #text nodes but have no ComputedStyle).
164 return false;
167 return elementStyle->StyleText()->mWhiteSpace == StyleWhiteSpace::PreLine;
170 bool EditorUtils::IsPointInSelection(const Selection& aSelection,
171 const nsINode& aParentNode,
172 uint32_t aOffset) {
173 if (aSelection.IsCollapsed()) {
174 return false;
177 const uint32_t rangeCount = aSelection.RangeCount();
178 for (const uint32_t i : IntegerRange(rangeCount)) {
179 MOZ_ASSERT(aSelection.RangeCount() == rangeCount);
180 RefPtr<const nsRange> range = aSelection.GetRangeAt(i);
181 if (MOZ_UNLIKELY(NS_WARN_IF(!range))) {
182 // Don't bail yet, iterate through them all
183 continue;
186 IgnoredErrorResult ignoredError;
187 bool nodeIsInSelection =
188 range->IsPointInRange(aParentNode, aOffset, ignoredError) &&
189 !ignoredError.Failed();
190 NS_WARNING_ASSERTION(!ignoredError.Failed(),
191 "nsRange::IsPointInRange() failed");
193 // Done when we find a range that we are in
194 if (nodeIsInSelection) {
195 return true;
199 return false;
202 // static
203 Result<nsCOMPtr<nsITransferable>, nsresult>
204 EditorUtils::CreateTransferableForPlainText(const Document& aDocument) {
205 // Create generic Transferable for getting the data
206 nsresult rv;
207 nsCOMPtr<nsITransferable> transferable =
208 do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
209 if (NS_FAILED(rv)) {
210 NS_WARNING("do_CreateInstance() failed to create nsITransferable instance");
211 return Err(rv);
214 if (!transferable) {
215 NS_WARNING("do_CreateInstance() returned nullptr, but ignored");
216 return nsCOMPtr<nsITransferable>();
219 DebugOnly<nsresult> rvIgnored =
220 transferable->Init(aDocument.GetLoadContext());
221 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
222 "nsITransferable::Init() failed, but ignored");
224 rvIgnored = transferable->AddDataFlavor(kUnicodeMime);
225 NS_WARNING_ASSERTION(
226 NS_SUCCEEDED(rvIgnored),
227 "nsITransferable::AddDataFlavor(kUnicodeMime) failed, but ignored");
228 rvIgnored = transferable->AddDataFlavor(kMozTextInternal);
229 NS_WARNING_ASSERTION(
230 NS_SUCCEEDED(rvIgnored),
231 "nsITransferable::AddDataFlavor(kMozTextInternal) failed, but ignored");
232 return transferable;
235 /******************************************************************************
236 * mozilla::EditorDOMPointBase
237 *****************************************************************************/
239 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, IsCharCollapsibleASCIISpace);
241 template <typename PT, typename CT>
242 bool EditorDOMPointBase<PT, CT>::IsCharCollapsibleASCIISpace() const {
243 if (IsCharNewLine()) {
244 return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
246 return IsCharASCIISpace() &&
247 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>());
250 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, IsCharCollapsibleNBSP);
252 template <typename PT, typename CT>
253 bool EditorDOMPointBase<PT, CT>::IsCharCollapsibleNBSP() const {
254 // TODO: Perhaps, we should return false if neither previous char nor
255 // next char is collapsible white-space or NBSP.
256 return IsCharNBSP() &&
257 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>());
260 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool,
261 IsCharCollapsibleASCIISpaceOrNBSP);
263 template <typename PT, typename CT>
264 bool EditorDOMPointBase<PT, CT>::IsCharCollapsibleASCIISpaceOrNBSP() const {
265 if (IsCharNewLine()) {
266 return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
268 return IsCharASCIISpaceOrNBSP() &&
269 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>());
272 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
273 bool, IsPreviousCharCollapsibleASCIISpace);
275 template <typename PT, typename CT>
276 bool EditorDOMPointBase<PT, CT>::IsPreviousCharCollapsibleASCIISpace() const {
277 if (IsPreviousCharNewLine()) {
278 return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
280 return IsPreviousCharASCIISpace() &&
281 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>());
284 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool,
285 IsPreviousCharCollapsibleNBSP);
287 template <typename PT, typename CT>
288 bool EditorDOMPointBase<PT, CT>::IsPreviousCharCollapsibleNBSP() const {
289 return IsPreviousCharNBSP() &&
290 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>());
293 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
294 bool, IsPreviousCharCollapsibleASCIISpaceOrNBSP);
296 template <typename PT, typename CT>
297 bool EditorDOMPointBase<PT, CT>::IsPreviousCharCollapsibleASCIISpaceOrNBSP()
298 const {
299 if (IsPreviousCharNewLine()) {
300 return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
302 return IsPreviousCharASCIISpaceOrNBSP() &&
303 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>());
306 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool,
307 IsNextCharCollapsibleASCIISpace);
309 template <typename PT, typename CT>
310 bool EditorDOMPointBase<PT, CT>::IsNextCharCollapsibleASCIISpace() const {
311 if (IsNextCharNewLine()) {
312 return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
314 return IsNextCharASCIISpace() &&
315 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>());
318 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, IsNextCharCollapsibleNBSP);
320 template <typename PT, typename CT>
321 bool EditorDOMPointBase<PT, CT>::IsNextCharCollapsibleNBSP() const {
322 return IsNextCharNBSP() &&
323 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>());
326 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
327 bool, IsNextCharCollapsibleASCIISpaceOrNBSP);
329 template <typename PT, typename CT>
330 bool EditorDOMPointBase<PT, CT>::IsNextCharCollapsibleASCIISpaceOrNBSP() const {
331 if (IsNextCharNewLine()) {
332 return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
334 return IsNextCharASCIISpaceOrNBSP() &&
335 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>());
338 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, IsCharPreformattedNewLine);
340 template <typename PT, typename CT>
341 bool EditorDOMPointBase<PT, CT>::IsCharPreformattedNewLine() const {
342 return IsCharNewLine() &&
343 EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
346 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
347 bool, IsCharPreformattedNewLineCollapsedWithWhiteSpaces);
349 template <typename PT, typename CT>
350 bool EditorDOMPointBase<
351 PT, CT>::IsCharPreformattedNewLineCollapsedWithWhiteSpaces() const {
352 return IsCharNewLine() &&
353 EditorUtils::IsOnlyNewLinePreformatted(*ContainerAs<Text>());
356 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool,
357 IsPreviousCharPreformattedNewLine);
359 template <typename PT, typename CT>
360 bool EditorDOMPointBase<PT, CT>::IsPreviousCharPreformattedNewLine() const {
361 return IsPreviousCharNewLine() &&
362 EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
365 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
366 bool, IsPreviousCharPreformattedNewLineCollapsedWithWhiteSpaces);
368 template <typename PT, typename CT>
369 bool EditorDOMPointBase<
370 PT, CT>::IsPreviousCharPreformattedNewLineCollapsedWithWhiteSpaces() const {
371 return IsPreviousCharNewLine() &&
372 EditorUtils::IsOnlyNewLinePreformatted(*ContainerAs<Text>());
375 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool,
376 IsNextCharPreformattedNewLine);
378 template <typename PT, typename CT>
379 bool EditorDOMPointBase<PT, CT>::IsNextCharPreformattedNewLine() const {
380 return IsNextCharNewLine() &&
381 EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>());
384 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
385 bool, IsNextCharPreformattedNewLineCollapsedWithWhiteSpaces);
387 template <typename PT, typename CT>
388 bool EditorDOMPointBase<
389 PT, CT>::IsNextCharPreformattedNewLineCollapsedWithWhiteSpaces() const {
390 return IsNextCharNewLine() &&
391 EditorUtils::IsOnlyNewLinePreformatted(*ContainerAs<Text>());
394 /******************************************************************************
395 * mozilla::EditorDOMRangeBase
396 *****************************************************************************/
398 NS_INSTANTIATE_EDITOR_DOM_RANGE_CONST_METHOD(nsINode*,
399 GetClosestCommonInclusiveAncestor);
401 template <typename EditorDOMPointType>
402 nsINode* EditorDOMRangeBase<
403 EditorDOMPointType>::GetClosestCommonInclusiveAncestor() const {
404 if (NS_WARN_IF(!IsPositioned())) {
405 return nullptr;
407 return nsContentUtils::GetClosestCommonInclusiveAncestor(
408 mStart.GetContainer(), mEnd.GetContainer());
411 /******************************************************************************
412 * mozilla::CaretPoint
413 *****************************************************************************/
415 nsresult CaretPoint::SuggestCaretPointTo(
416 const EditorBase& aEditorBase, const SuggestCaretOptions& aOptions) const {
417 mHandledCaretPoint = true;
418 if (!mCaretPoint.IsSet()) {
419 if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion)) {
420 return NS_OK;
422 NS_WARNING("There was no suggestion to put caret");
423 return NS_ERROR_FAILURE;
425 if (aOptions.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt) &&
426 !aEditorBase.AllowsTransactionsToChangeSelection()) {
427 return NS_OK;
429 nsresult rv = aEditorBase.CollapseSelectionTo(mCaretPoint);
430 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
431 NS_WARNING(
432 "EditorBase::CollapseSelectionTo() caused destroying the editor");
433 return NS_ERROR_EDITOR_DESTROYED;
435 return aOptions.contains(SuggestCaret::AndIgnoreTrivialError) && NS_FAILED(rv)
436 ? NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
437 : rv;
440 bool CaretPoint::CopyCaretPointTo(EditorDOMPoint& aPointToPutCaret,
441 const EditorBase& aEditorBase,
442 const SuggestCaretOptions& aOptions) const {
443 MOZ_ASSERT(!aOptions.contains(SuggestCaret::AndIgnoreTrivialError));
444 mHandledCaretPoint = true;
445 if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion) &&
446 !mCaretPoint.IsSet()) {
447 return false;
449 if (aOptions.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt) &&
450 !aEditorBase.AllowsTransactionsToChangeSelection()) {
451 return false;
453 aPointToPutCaret = mCaretPoint;
454 return true;
457 bool CaretPoint::MoveCaretPointTo(EditorDOMPoint& aPointToPutCaret,
458 const EditorBase& aEditorBase,
459 const SuggestCaretOptions& aOptions) {
460 MOZ_ASSERT(!aOptions.contains(SuggestCaret::AndIgnoreTrivialError));
461 mHandledCaretPoint = true;
462 if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion) &&
463 !mCaretPoint.IsSet()) {
464 return false;
466 if (aOptions.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt) &&
467 !aEditorBase.AllowsTransactionsToChangeSelection()) {
468 return false;
470 aPointToPutCaret = UnwrapCaretPoint();
471 return true;
474 } // namespace mozilla