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
35 /******************************************************************************
36 * mozilla::EditActionResult
37 *****************************************************************************/
39 EditActionResult
& EditActionResult::operator|=(
40 const MoveNodeResult
& aMoveNodeResult
) {
41 mHandled
|= aMoveNodeResult
.Handled();
45 /******************************************************************************
46 * some general purpose editor utils
47 *****************************************************************************/
49 bool EditorUtils::IsDescendantOf(const nsINode
& aNode
, const nsINode
& aParent
,
50 EditorRawDOMPoint
* aOutPoint
/* = nullptr */) {
55 if (&aNode
== &aParent
) {
59 for (const nsINode
* node
= &aNode
; node
; node
= node
->GetParentNode()) {
60 if (node
->GetParentNode() == &aParent
) {
62 MOZ_ASSERT(node
->IsContent());
63 aOutPoint
->Set(node
->AsContent());
72 bool EditorUtils::IsDescendantOf(const nsINode
& aNode
, const nsINode
& aParent
,
73 EditorDOMPoint
* aOutPoint
) {
74 MOZ_ASSERT(aOutPoint
);
76 if (&aNode
== &aParent
) {
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());
92 Maybe
<StyleWhiteSpace
> EditorUtils::GetComputedWhiteSpaceStyle(
93 const nsIContent
& aContent
) {
94 if (MOZ_UNLIKELY(!aContent
.IsElement() && !aContent
.GetParentElement())) {
97 RefPtr
<const ComputedStyle
> elementStyle
=
98 nsComputedDOMStyle::GetComputedStyleNoFlush(
99 aContent
.IsElement() ? aContent
.AsElement()
100 : aContent
.GetParentElement());
101 if (NS_WARN_IF(!elementStyle
)) {
104 return Some(elementStyle
->StyleText()->mWhiteSpace
);
108 bool EditorUtils::IsWhiteSpacePreformatted(const nsIContent
& aContent
) {
109 // Look at the node (and its parent if it's not an element), and grab its
111 Element
* element
= aContent
.GetAsElementOrParentElement();
116 RefPtr
<const ComputedStyle
> elementStyle
=
117 nsComputedDOMStyle::GetComputedStyleNoFlush(element
);
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).
125 return elementStyle
->StyleText()->WhiteSpaceIsSignificant();
129 bool EditorUtils::IsNewLinePreformatted(const nsIContent
& aContent
) {
130 // Look at the node (and its parent if it's not an element), and grab its
132 Element
* element
= aContent
.GetAsElementOrParentElement();
137 RefPtr
<const ComputedStyle
> elementStyle
=
138 nsComputedDOMStyle::GetComputedStyleNoFlush(element
);
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).
146 return elementStyle
->StyleText()->NewlineIsSignificantStyle();
150 bool EditorUtils::IsOnlyNewLinePreformatted(const nsIContent
& aContent
) {
151 // Look at the node (and its parent if it's not an element), and grab its
153 Element
* element
= aContent
.GetAsElementOrParentElement();
158 RefPtr
<const ComputedStyle
> elementStyle
=
159 nsComputedDOMStyle::GetComputedStyleNoFlush(element
);
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).
167 return elementStyle
->StyleText()->mWhiteSpace
== StyleWhiteSpace::PreLine
;
170 bool EditorUtils::IsPointInSelection(const Selection
& aSelection
,
171 const nsINode
& aParentNode
,
173 if (aSelection
.IsCollapsed()) {
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
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
) {
203 Result
<nsCOMPtr
<nsITransferable
>, nsresult
>
204 EditorUtils::CreateTransferableForPlainText(const Document
& aDocument
) {
205 // Create generic Transferable for getting the data
207 nsCOMPtr
<nsITransferable
> transferable
=
208 do_CreateInstance("@mozilla.org/widget/transferable;1", &rv
);
210 NS_WARNING("do_CreateInstance() failed to create nsITransferable instance");
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");
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()
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())) {
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
)) {
422 NS_WARNING("There was no suggestion to put caret");
423 return NS_ERROR_FAILURE
;
425 if (aOptions
.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt
) &&
426 !aEditorBase
.AllowsTransactionsToChangeSelection()) {
429 nsresult rv
= aEditorBase
.CollapseSelectionTo(mCaretPoint
);
430 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
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
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()) {
449 if (aOptions
.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt
) &&
450 !aEditorBase
.AllowsTransactionsToChangeSelection()) {
453 aPointToPutCaret
= mCaretPoint
;
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()) {
466 if (aOptions
.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt
) &&
467 !aEditorBase
.AllowsTransactionsToChangeSelection()) {
470 aPointToPutCaret
= UnwrapCaretPoint();
474 } // namespace mozilla