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
<std::pair
<StyleWhiteSpaceCollapse
, StyleTextWrapMode
>>
93 EditorUtils::GetComputedWhiteSpaceStyles(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 const auto* styleText
= elementStyle
->StyleText();
106 std::pair(styleText
->mWhiteSpaceCollapse
, styleText
->mTextWrapMode
));
110 bool EditorUtils::IsWhiteSpacePreformatted(const nsIContent
& aContent
) {
111 // Look at the node (and its parent if it's not an element), and grab its
113 Element
* element
= aContent
.GetAsElementOrParentElement();
118 RefPtr
<const ComputedStyle
> elementStyle
=
119 nsComputedDOMStyle::GetComputedStyleNoFlush(element
);
121 // Consider nodes without a ComputedStyle to be NOT preformatted:
122 // For instance, this is true of JS tags inside the body (which show
123 // up as #text nodes but have no ComputedStyle).
127 return elementStyle
->StyleText()->WhiteSpaceIsSignificant();
131 bool EditorUtils::IsNewLinePreformatted(const nsIContent
& aContent
) {
132 // Look at the node (and its parent if it's not an element), and grab its
134 Element
* element
= aContent
.GetAsElementOrParentElement();
139 RefPtr
<const ComputedStyle
> elementStyle
=
140 nsComputedDOMStyle::GetComputedStyleNoFlush(element
);
142 // Consider nodes without a ComputedStyle to be NOT preformatted:
143 // For instance, this is true of JS tags inside the body (which show
144 // up as #text nodes but have no ComputedStyle).
148 return elementStyle
->StyleText()->NewlineIsSignificantStyle();
152 bool EditorUtils::IsOnlyNewLinePreformatted(const nsIContent
& aContent
) {
153 // Look at the node (and its parent if it's not an element), and grab its
155 Element
* element
= aContent
.GetAsElementOrParentElement();
160 RefPtr
<const ComputedStyle
> elementStyle
=
161 nsComputedDOMStyle::GetComputedStyleNoFlush(element
);
163 // Consider nodes without a ComputedStyle to be NOT preformatted:
164 // For instance, this is true of JS tags inside the body (which show
165 // up as #text nodes but have no ComputedStyle).
169 return elementStyle
->StyleText()->mWhiteSpaceCollapse
==
170 StyleWhiteSpaceCollapse::PreserveBreaks
;
174 Result
<nsCOMPtr
<nsITransferable
>, nsresult
>
175 EditorUtils::CreateTransferableForPlainText(const Document
& aDocument
) {
176 // Create generic Transferable for getting the data
178 nsCOMPtr
<nsITransferable
> transferable
=
179 do_CreateInstance("@mozilla.org/widget/transferable;1", &rv
);
181 NS_WARNING("do_CreateInstance() failed to create nsITransferable instance");
186 NS_WARNING("do_CreateInstance() returned nullptr, but ignored");
187 return nsCOMPtr
<nsITransferable
>();
190 DebugOnly
<nsresult
> rvIgnored
=
191 transferable
->Init(aDocument
.GetLoadContext());
192 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
193 "nsITransferable::Init() failed, but ignored");
195 rvIgnored
= transferable
->AddDataFlavor(kTextMime
);
196 NS_WARNING_ASSERTION(
197 NS_SUCCEEDED(rvIgnored
),
198 "nsITransferable::AddDataFlavor(kTextMime) failed, but ignored");
199 rvIgnored
= transferable
->AddDataFlavor(kMozTextInternal
);
200 NS_WARNING_ASSERTION(
201 NS_SUCCEEDED(rvIgnored
),
202 "nsITransferable::AddDataFlavor(kMozTextInternal) failed, but ignored");
206 /******************************************************************************
207 * mozilla::EditorDOMPointBase
208 *****************************************************************************/
210 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, IsCharCollapsibleASCIISpace
);
212 template <typename PT
, typename CT
>
213 bool EditorDOMPointBase
<PT
, CT
>::IsCharCollapsibleASCIISpace() const {
214 if (IsCharNewLine()) {
215 return !EditorUtils::IsNewLinePreformatted(*ContainerAs
<Text
>());
217 return IsCharASCIISpace() &&
218 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs
<Text
>());
221 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, IsCharCollapsibleNBSP
);
223 template <typename PT
, typename CT
>
224 bool EditorDOMPointBase
<PT
, CT
>::IsCharCollapsibleNBSP() const {
225 // TODO: Perhaps, we should return false if neither previous char nor
226 // next char is collapsible white-space or NBSP.
227 return IsCharNBSP() &&
228 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs
<Text
>());
231 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool,
232 IsCharCollapsibleASCIISpaceOrNBSP
);
234 template <typename PT
, typename CT
>
235 bool EditorDOMPointBase
<PT
, CT
>::IsCharCollapsibleASCIISpaceOrNBSP() const {
236 if (IsCharNewLine()) {
237 return !EditorUtils::IsNewLinePreformatted(*ContainerAs
<Text
>());
239 return IsCharASCIISpaceOrNBSP() &&
240 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs
<Text
>());
243 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
244 bool, IsPreviousCharCollapsibleASCIISpace
);
246 template <typename PT
, typename CT
>
247 bool EditorDOMPointBase
<PT
, CT
>::IsPreviousCharCollapsibleASCIISpace() const {
248 if (IsPreviousCharNewLine()) {
249 return !EditorUtils::IsNewLinePreformatted(*ContainerAs
<Text
>());
251 return IsPreviousCharASCIISpace() &&
252 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs
<Text
>());
255 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool,
256 IsPreviousCharCollapsibleNBSP
);
258 template <typename PT
, typename CT
>
259 bool EditorDOMPointBase
<PT
, CT
>::IsPreviousCharCollapsibleNBSP() const {
260 return IsPreviousCharNBSP() &&
261 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs
<Text
>());
264 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
265 bool, IsPreviousCharCollapsibleASCIISpaceOrNBSP
);
267 template <typename PT
, typename CT
>
268 bool EditorDOMPointBase
<PT
, CT
>::IsPreviousCharCollapsibleASCIISpaceOrNBSP()
270 if (IsPreviousCharNewLine()) {
271 return !EditorUtils::IsNewLinePreformatted(*ContainerAs
<Text
>());
273 return IsPreviousCharASCIISpaceOrNBSP() &&
274 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs
<Text
>());
277 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool,
278 IsNextCharCollapsibleASCIISpace
);
280 template <typename PT
, typename CT
>
281 bool EditorDOMPointBase
<PT
, CT
>::IsNextCharCollapsibleASCIISpace() const {
282 if (IsNextCharNewLine()) {
283 return !EditorUtils::IsNewLinePreformatted(*ContainerAs
<Text
>());
285 return IsNextCharASCIISpace() &&
286 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs
<Text
>());
289 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, IsNextCharCollapsibleNBSP
);
291 template <typename PT
, typename CT
>
292 bool EditorDOMPointBase
<PT
, CT
>::IsNextCharCollapsibleNBSP() const {
293 return IsNextCharNBSP() &&
294 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs
<Text
>());
297 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
298 bool, IsNextCharCollapsibleASCIISpaceOrNBSP
);
300 template <typename PT
, typename CT
>
301 bool EditorDOMPointBase
<PT
, CT
>::IsNextCharCollapsibleASCIISpaceOrNBSP() const {
302 if (IsNextCharNewLine()) {
303 return !EditorUtils::IsNewLinePreformatted(*ContainerAs
<Text
>());
305 return IsNextCharASCIISpaceOrNBSP() &&
306 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs
<Text
>());
309 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, IsCharPreformattedNewLine
);
311 template <typename PT
, typename CT
>
312 bool EditorDOMPointBase
<PT
, CT
>::IsCharPreformattedNewLine() const {
313 return IsCharNewLine() &&
314 EditorUtils::IsNewLinePreformatted(*ContainerAs
<Text
>());
317 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
318 bool, IsCharPreformattedNewLineCollapsedWithWhiteSpaces
);
320 template <typename PT
, typename CT
>
321 bool EditorDOMPointBase
<
322 PT
, CT
>::IsCharPreformattedNewLineCollapsedWithWhiteSpaces() const {
323 return IsCharNewLine() &&
324 EditorUtils::IsOnlyNewLinePreformatted(*ContainerAs
<Text
>());
327 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool,
328 IsPreviousCharPreformattedNewLine
);
330 template <typename PT
, typename CT
>
331 bool EditorDOMPointBase
<PT
, CT
>::IsPreviousCharPreformattedNewLine() const {
332 return IsPreviousCharNewLine() &&
333 EditorUtils::IsNewLinePreformatted(*ContainerAs
<Text
>());
336 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
337 bool, IsPreviousCharPreformattedNewLineCollapsedWithWhiteSpaces
);
339 template <typename PT
, typename CT
>
340 bool EditorDOMPointBase
<
341 PT
, CT
>::IsPreviousCharPreformattedNewLineCollapsedWithWhiteSpaces() const {
342 return IsPreviousCharNewLine() &&
343 EditorUtils::IsOnlyNewLinePreformatted(*ContainerAs
<Text
>());
346 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool,
347 IsNextCharPreformattedNewLine
);
349 template <typename PT
, typename CT
>
350 bool EditorDOMPointBase
<PT
, CT
>::IsNextCharPreformattedNewLine() const {
351 return IsNextCharNewLine() &&
352 EditorUtils::IsNewLinePreformatted(*ContainerAs
<Text
>());
355 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(
356 bool, IsNextCharPreformattedNewLineCollapsedWithWhiteSpaces
);
358 template <typename PT
, typename CT
>
359 bool EditorDOMPointBase
<
360 PT
, CT
>::IsNextCharPreformattedNewLineCollapsedWithWhiteSpaces() const {
361 return IsNextCharNewLine() &&
362 EditorUtils::IsOnlyNewLinePreformatted(*ContainerAs
<Text
>());
365 /******************************************************************************
366 * mozilla::EditorDOMRangeBase
367 *****************************************************************************/
369 NS_INSTANTIATE_EDITOR_DOM_RANGE_CONST_METHOD(nsINode
*,
370 GetClosestCommonInclusiveAncestor
);
372 template <typename EditorDOMPointType
>
373 nsINode
* EditorDOMRangeBase
<
374 EditorDOMPointType
>::GetClosestCommonInclusiveAncestor() const {
375 if (NS_WARN_IF(!IsPositioned())) {
378 return nsContentUtils::GetClosestCommonInclusiveAncestor(
379 mStart
.GetContainer(), mEnd
.GetContainer());
382 /******************************************************************************
383 * mozilla::CaretPoint
384 *****************************************************************************/
386 nsresult
CaretPoint::SuggestCaretPointTo(
387 EditorBase
& aEditorBase
, const SuggestCaretOptions
& aOptions
) const {
388 mHandledCaretPoint
= true;
389 if (!mCaretPoint
.IsSet()) {
390 if (aOptions
.contains(SuggestCaret::OnlyIfHasSuggestion
)) {
393 NS_WARNING("There was no suggestion to put caret");
394 return NS_ERROR_FAILURE
;
396 if (aOptions
.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt
) &&
397 !aEditorBase
.AllowsTransactionsToChangeSelection()) {
400 nsresult rv
= aEditorBase
.CollapseSelectionTo(mCaretPoint
);
401 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
403 "EditorBase::CollapseSelectionTo() caused destroying the editor");
404 return NS_ERROR_EDITOR_DESTROYED
;
406 return aOptions
.contains(SuggestCaret::AndIgnoreTrivialError
) && NS_FAILED(rv
)
407 ? NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
411 bool CaretPoint::CopyCaretPointTo(EditorDOMPoint
& aPointToPutCaret
,
412 const EditorBase
& aEditorBase
,
413 const SuggestCaretOptions
& aOptions
) const {
414 MOZ_ASSERT(!aOptions
.contains(SuggestCaret::AndIgnoreTrivialError
));
415 mHandledCaretPoint
= true;
416 if (aOptions
.contains(SuggestCaret::OnlyIfHasSuggestion
) &&
417 !mCaretPoint
.IsSet()) {
420 if (aOptions
.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt
) &&
421 !aEditorBase
.AllowsTransactionsToChangeSelection()) {
424 aPointToPutCaret
= mCaretPoint
;
428 bool CaretPoint::MoveCaretPointTo(EditorDOMPoint
& aPointToPutCaret
,
429 const EditorBase
& aEditorBase
,
430 const SuggestCaretOptions
& aOptions
) {
431 MOZ_ASSERT(!aOptions
.contains(SuggestCaret::AndIgnoreTrivialError
));
432 mHandledCaretPoint
= true;
433 if (aOptions
.contains(SuggestCaret::OnlyIfHasSuggestion
) &&
434 !mCaretPoint
.IsSet()) {
437 if (aOptions
.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt
) &&
438 !aEditorBase
.AllowsTransactionsToChangeSelection()) {
441 aPointToPutCaret
= UnwrapCaretPoint();
445 } // namespace mozilla