зеркало из https://github.com/mozilla/gecko-dev.git
2207 строки
83 KiB
C++
2207 строки
83 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "WSRunObject.h"
|
|
|
|
#include "HTMLEditUtils.h"
|
|
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/Casting.h"
|
|
#include "mozilla/EditorDOMPoint.h"
|
|
#include "mozilla/HTMLEditor.h"
|
|
#include "mozilla/mozalloc.h"
|
|
#include "mozilla/OwningNonNull.h"
|
|
#include "mozilla/RangeUtils.h"
|
|
#include "mozilla/SelectionState.h"
|
|
#include "mozilla/dom/AncestorIterator.h"
|
|
|
|
#include "nsAString.h"
|
|
#include "nsCRT.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsDebug.h"
|
|
#include "nsError.h"
|
|
#include "nsIContent.h"
|
|
#include "nsIContentInlines.h"
|
|
#include "nsISupportsImpl.h"
|
|
#include "nsRange.h"
|
|
#include "nsString.h"
|
|
#include "nsTextFragment.h"
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace dom;
|
|
|
|
const char16_t kNBSP = 160;
|
|
|
|
template WSRunScanner::WSRunScanner(const HTMLEditor* aHTMLEditor,
|
|
const EditorDOMPoint& aScanStartPoint,
|
|
const EditorDOMPoint& aScanEndPoint);
|
|
template WSRunScanner::WSRunScanner(const HTMLEditor* aHTMLEditor,
|
|
const EditorRawDOMPoint& aScanStartPoint,
|
|
const EditorRawDOMPoint& aScanEndPoint);
|
|
template WSRunObject::WSRunObject(HTMLEditor& aHTMLEditor,
|
|
const EditorDOMPoint& aScanStartPoint,
|
|
const EditorDOMPoint& aScanEndPoint);
|
|
template WSRunObject::WSRunObject(HTMLEditor& aHTMLEditor,
|
|
const EditorRawDOMPoint& aScanStartPoint,
|
|
const EditorRawDOMPoint& aScanEndPoint);
|
|
template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
|
|
const EditorDOMPoint& aPoint) const;
|
|
template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
|
|
const EditorRawDOMPoint& aPoint) const;
|
|
template WSScanResult WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
|
|
const EditorDOMPoint& aPoint) const;
|
|
template WSScanResult WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
|
|
const EditorRawDOMPoint& aPoint) const;
|
|
|
|
template <typename PT, typename CT>
|
|
WSRunScanner::WSRunScanner(const HTMLEditor* aHTMLEditor,
|
|
const EditorDOMPointBase<PT, CT>& aScanStartPoint,
|
|
const EditorDOMPointBase<PT, CT>& aScanEndPoint)
|
|
: mScanStartPoint(aScanStartPoint),
|
|
mScanEndPoint(aScanEndPoint),
|
|
mEditingHost(aHTMLEditor->GetActiveEditingHost()),
|
|
mPRE(false),
|
|
mStartOffset(0),
|
|
mEndOffset(0),
|
|
mFirstNBSPOffset(0),
|
|
mLastNBSPOffset(0),
|
|
mStartRun(nullptr),
|
|
mEndRun(nullptr),
|
|
mHTMLEditor(aHTMLEditor),
|
|
mStartReason(WSType::NotInitialized),
|
|
mEndReason(WSType::NotInitialized) {
|
|
MOZ_ASSERT(
|
|
*nsContentUtils::ComparePoints(aScanStartPoint.ToRawRangeBoundary(),
|
|
aScanEndPoint.ToRawRangeBoundary()) <= 0);
|
|
DebugOnly<nsresult> rvIgnored = GetWSNodes();
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
|
"WSRunScanner::GetWSNodes() failed, but ignored");
|
|
GetRuns();
|
|
}
|
|
|
|
WSRunScanner::~WSRunScanner() { ClearRuns(); }
|
|
|
|
template <typename PT, typename CT>
|
|
WSRunObject::WSRunObject(HTMLEditor& aHTMLEditor,
|
|
const EditorDOMPointBase<PT, CT>& aScanStartPoint,
|
|
const EditorDOMPointBase<PT, CT>& aScanEndPoint)
|
|
: WSRunScanner(&aHTMLEditor, aScanStartPoint, aScanEndPoint),
|
|
mHTMLEditor(aHTMLEditor) {}
|
|
|
|
// static
|
|
nsresult WSRunObject::Scrub(HTMLEditor& aHTMLEditor,
|
|
const EditorDOMPoint& aPoint) {
|
|
MOZ_ASSERT(aPoint.IsSet());
|
|
|
|
WSRunObject wsRunObject(aHTMLEditor, aPoint);
|
|
nsresult rv = wsRunObject.Scrub();
|
|
if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
|
|
return NS_ERROR_EDITOR_DESTROYED;
|
|
}
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WSRunObject::Scrub() failed");
|
|
return rv;
|
|
}
|
|
|
|
// static
|
|
nsresult WSRunObject::PrepareToJoinBlocks(HTMLEditor& aHTMLEditor,
|
|
Element& aLeftBlockElement,
|
|
Element& aRightBlockElement) {
|
|
WSRunObject leftWSObj(aHTMLEditor,
|
|
EditorRawDOMPoint::AtEndOf(aLeftBlockElement));
|
|
WSRunObject rightWSObj(aHTMLEditor,
|
|
EditorRawDOMPoint(&aRightBlockElement, 0));
|
|
|
|
nsresult rv = leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
|
|
if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
|
|
return NS_ERROR_EDITOR_DESTROYED;
|
|
}
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"WSRunObject::PrepareToDeleteRangePriv() failed");
|
|
return rv;
|
|
}
|
|
|
|
nsresult WSRunObject::PrepareToDeleteRange(HTMLEditor& aHTMLEditor,
|
|
EditorDOMPoint* aStartPoint,
|
|
EditorDOMPoint* aEndPoint) {
|
|
MOZ_ASSERT(aStartPoint);
|
|
MOZ_ASSERT(aEndPoint);
|
|
|
|
if (NS_WARN_IF(!aStartPoint->IsSet()) || NS_WARN_IF(!aEndPoint->IsSet())) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
AutoTrackDOMPoint trackerStart(aHTMLEditor.RangeUpdaterRef(), aStartPoint);
|
|
AutoTrackDOMPoint trackerEnd(aHTMLEditor.RangeUpdaterRef(), aEndPoint);
|
|
|
|
WSRunObject leftWSObj(aHTMLEditor, *aStartPoint);
|
|
WSRunObject rightWSObj(aHTMLEditor, *aEndPoint);
|
|
|
|
nsresult rv = leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"WSRunObject::PrepareToDeleteRangePriv() failed");
|
|
return rv;
|
|
}
|
|
|
|
nsresult WSRunObject::PrepareToDeleteNode(HTMLEditor& aHTMLEditor,
|
|
nsIContent* aContent) {
|
|
if (NS_WARN_IF(!aContent)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
EditorRawDOMPoint atContent(aContent);
|
|
if (!atContent.IsSet()) {
|
|
NS_WARNING("aContent was an orphan node");
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
WSRunObject leftWSObj(aHTMLEditor, atContent);
|
|
WSRunObject rightWSObj(aHTMLEditor, atContent.NextPoint());
|
|
|
|
nsresult rv = leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"WSRunObject::PrepareToDeleteRangePriv() failed");
|
|
return rv;
|
|
}
|
|
|
|
nsresult WSRunObject::PrepareToSplitAcrossBlocks(HTMLEditor& aHTMLEditor,
|
|
nsCOMPtr<nsINode>* aSplitNode,
|
|
int32_t* aSplitOffset) {
|
|
if (NS_WARN_IF(!aSplitNode) || NS_WARN_IF(!*aSplitNode) ||
|
|
NS_WARN_IF(!aSplitOffset)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
AutoTrackDOMPoint tracker(aHTMLEditor.RangeUpdaterRef(), aSplitNode,
|
|
aSplitOffset);
|
|
|
|
WSRunObject wsObj(aHTMLEditor, MOZ_KnownLive(*aSplitNode), *aSplitOffset);
|
|
|
|
nsresult rv = wsObj.PrepareToSplitAcrossBlocksPriv();
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"WSRunObject::PrepareToSplitAcrossBlocksPriv() failed");
|
|
return rv;
|
|
}
|
|
|
|
already_AddRefed<Element> WSRunObject::InsertBreak(
|
|
Selection& aSelection, const EditorDOMPoint& aPointToInsert,
|
|
nsIEditor::EDirection aSelect) {
|
|
if (NS_WARN_IF(!aPointToInsert.IsSet())) {
|
|
return nullptr;
|
|
}
|
|
|
|
// MOOSE: for now, we always assume non-PRE formatting. Fix this later.
|
|
// meanwhile, the pre case is handled in HandleInsertText() in
|
|
// HTMLEditSubActionHandler.cpp
|
|
|
|
WSFragment* beforeRun = FindNearestRun(aPointToInsert, false);
|
|
WSFragment* afterRun = FindNearestRun(aPointToInsert, true);
|
|
|
|
EditorDOMPoint pointToInsert(aPointToInsert);
|
|
{
|
|
// Some scoping for AutoTrackDOMPoint. This will track our insertion
|
|
// point while we tweak any surrounding whitespace
|
|
AutoTrackDOMPoint tracker(mHTMLEditor.RangeUpdaterRef(), &pointToInsert);
|
|
|
|
// Handle any changes needed to ws run after inserted br
|
|
if (!afterRun || afterRun->IsEndOfHardLine()) {
|
|
// Don't need to do anything. Just insert break. ws won't change.
|
|
} else if (afterRun->IsStartOfHardLine()) {
|
|
// Delete the leading ws that is after insertion point. We don't
|
|
// have to (it would still not be significant after br), but it's
|
|
// just more aesthetically pleasing to.
|
|
nsresult rv = DeleteRange(pointToInsert, afterRun->EndPoint());
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::DeleteRange() failed");
|
|
return nullptr;
|
|
}
|
|
} else if (afterRun->IsVisibleAndMiddleOfHardLine()) {
|
|
// Need to determine if break at front of non-nbsp run. If so, convert
|
|
// run to nbsp.
|
|
EditorDOMPointInText atNextCharOfInsertionPoint =
|
|
GetNextCharPoint(pointToInsert);
|
|
if (atNextCharOfInsertionPoint.IsSet() &&
|
|
!atNextCharOfInsertionPoint.IsEndOfContainer() &&
|
|
atNextCharOfInsertionPoint.IsCharASCIISpace()) {
|
|
EditorDOMPointInText atPreviousCharOfNextCharOfInsertionPoint =
|
|
GetPreviousCharPointFromPointInText(atNextCharOfInsertionPoint);
|
|
if (!atPreviousCharOfNextCharOfInsertionPoint.IsSet() ||
|
|
atPreviousCharOfNextCharOfInsertionPoint.IsEndOfContainer() ||
|
|
!atPreviousCharOfNextCharOfInsertionPoint.IsCharASCIISpace()) {
|
|
// We are at start of non-nbsps. Convert to a single nbsp.
|
|
nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(
|
|
atNextCharOfInsertionPoint);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING(
|
|
"WSRunObject::InsertNBSPAndRemoveFollowingASCIIWhitespaces() "
|
|
"failed");
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle any changes needed to ws run before inserted br
|
|
if (!beforeRun || beforeRun->IsStartOfHardLine()) {
|
|
// Don't need to do anything. Just insert break. ws won't change.
|
|
} else if (beforeRun->IsEndOfHardLine()) {
|
|
// Need to delete the trailing ws that is before insertion point, because
|
|
// it would become significant after break inserted.
|
|
nsresult rv = DeleteRange(beforeRun->StartPoint(), pointToInsert);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::DeleteRange() failed");
|
|
return nullptr;
|
|
}
|
|
} else if (beforeRun->IsVisibleAndMiddleOfHardLine()) {
|
|
// Try to change an nbsp to a space, just to prevent nbsp proliferation
|
|
nsresult rv = ReplacePreviousNBSPIfUnnecessary(beforeRun, pointToInsert);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::ReplacePreviousNBSPIfUnnecessary() failed");
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
RefPtr<Element> newBRElement =
|
|
MOZ_KnownLive(mHTMLEditor)
|
|
.InsertBRElementWithTransaction(pointToInsert, aSelect);
|
|
NS_WARNING_ASSERTION(newBRElement,
|
|
"HTMLEditor::InsertBRElementWithTransaction() failed");
|
|
return newBRElement.forget();
|
|
}
|
|
|
|
nsresult WSRunObject::InsertText(Document& aDocument,
|
|
const nsAString& aStringToInsert,
|
|
EditorRawDOMPoint* aPointAfterInsertedString) {
|
|
// MOOSE: for now, we always assume non-PRE formatting. Fix this later.
|
|
// meanwhile, the pre case is handled in HandleInsertText() in
|
|
// HTMLEditSubActionHandler.cpp
|
|
|
|
// MOOSE: for now, just getting the ws logic straight. This implementation
|
|
// is very slow. Will need to replace edit rules impl with a more efficient
|
|
// text sink here that does the minimal amount of searching/replacing/copying
|
|
|
|
if (aStringToInsert.IsEmpty()) {
|
|
if (aPointAfterInsertedString) {
|
|
*aPointAfterInsertedString = mScanStartPoint;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
WSFragment* beforeRun = FindNearestRun(mScanStartPoint, false);
|
|
// If mScanStartPoint isn't equal to mScanEndPoint, it will replace text (i.e.
|
|
// committing composition). And afterRun will be end point of replaced range.
|
|
// So we want to know this white space type (trailing whitespace etc) of
|
|
// this end point, not inserted (start) point, so we re-scan white space type.
|
|
WSRunObject afterRunObject(MOZ_KnownLive(mHTMLEditor), mScanEndPoint);
|
|
WSFragment* afterRun = afterRunObject.FindNearestRun(mScanEndPoint, true);
|
|
|
|
EditorDOMPoint pointToInsert(mScanStartPoint);
|
|
nsAutoString theString(aStringToInsert);
|
|
{
|
|
// Some scoping for AutoTrackDOMPoint. This will track our insertion
|
|
// point while we tweak any surrounding whitespace
|
|
AutoTrackDOMPoint tracker(mHTMLEditor.RangeUpdaterRef(), &pointToInsert);
|
|
|
|
// Handle any changes needed to ws run after inserted text
|
|
if (!afterRun || afterRun->IsEndOfHardLine()) {
|
|
// Don't need to do anything. Just insert text. ws won't change.
|
|
} else if (afterRun->IsStartOfHardLine()) {
|
|
// Delete the leading ws that is after insertion point, because it
|
|
// would become significant after text inserted.
|
|
nsresult rv = DeleteRange(pointToInsert, afterRun->EndPoint());
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::DeleteRange() failed");
|
|
return rv;
|
|
}
|
|
} else if (afterRun->IsVisibleAndMiddleOfHardLine()) {
|
|
// Try to change an nbsp to a space, if possible, just to prevent nbsp
|
|
// proliferation
|
|
nsresult rv = CheckLeadingNBSP(
|
|
afterRun, MOZ_KnownLive(pointToInsert.GetContainer()),
|
|
pointToInsert.Offset());
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::CheckLeadingNBSP() failed");
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// Handle any changes needed to ws run before inserted text
|
|
if (!beforeRun || beforeRun->IsStartOfHardLine()) {
|
|
// Don't need to do anything. Just insert text. ws won't change.
|
|
} else if (beforeRun->IsEndOfHardLine()) {
|
|
// Need to delete the trailing ws that is before insertion point, because
|
|
// it would become significant after text inserted.
|
|
nsresult rv = DeleteRange(beforeRun->StartPoint(), pointToInsert);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::DeleteRange() failed");
|
|
return rv;
|
|
}
|
|
} else if (beforeRun->IsVisibleAndMiddleOfHardLine()) {
|
|
// Try to change an nbsp to a space, if possible, just to prevent nbsp
|
|
// proliferation
|
|
nsresult rv = ReplacePreviousNBSPIfUnnecessary(beforeRun, pointToInsert);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::ReplacePreviousNBSPIfUnnecessary() failed");
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// After this block, pointToInsert is modified by AutoTrackDOMPoint.
|
|
}
|
|
|
|
// Next up, tweak head and tail of string as needed. First the head: there
|
|
// are a variety of circumstances that would require us to convert a leading
|
|
// ws char into an nbsp:
|
|
|
|
if (nsCRT::IsAsciiSpace(theString[0])) {
|
|
// We have a leading space
|
|
if (beforeRun) {
|
|
if (beforeRun->IsStartOfHardLine()) {
|
|
theString.SetCharAt(kNBSP, 0);
|
|
} else if (beforeRun->IsVisible()) {
|
|
EditorDOMPointInText atPreviousChar =
|
|
GetPreviousCharPoint(pointToInsert);
|
|
if (atPreviousChar.IsSet() && !atPreviousChar.IsEndOfContainer() &&
|
|
atPreviousChar.IsCharASCIISpace()) {
|
|
theString.SetCharAt(kNBSP, 0);
|
|
}
|
|
}
|
|
} else if (StartsFromHardLineBreak()) {
|
|
theString.SetCharAt(kNBSP, 0);
|
|
}
|
|
}
|
|
|
|
// Then the tail
|
|
uint32_t lastCharIndex = theString.Length() - 1;
|
|
|
|
if (nsCRT::IsAsciiSpace(theString[lastCharIndex])) {
|
|
// We have a leading space
|
|
if (afterRun) {
|
|
if (afterRun->IsEndOfHardLine()) {
|
|
theString.SetCharAt(kNBSP, lastCharIndex);
|
|
} else if (afterRun->IsVisible()) {
|
|
EditorDOMPointInText atNextChar = GetNextCharPoint(pointToInsert);
|
|
if (atNextChar.IsSet() && !atNextChar.IsEndOfContainer() &&
|
|
atNextChar.IsCharASCIISpace()) {
|
|
theString.SetCharAt(kNBSP, lastCharIndex);
|
|
}
|
|
}
|
|
} else if (afterRunObject.EndsByBlockBoundary()) {
|
|
// When afterRun is null, it means that mScanEndPoint is last point in
|
|
// editing host or editing block.
|
|
// If this text insertion replaces composition, this.mEndReason is
|
|
// start position of compositon. So we have to use afterRunObject's
|
|
// reason instead.
|
|
theString.SetCharAt(kNBSP, lastCharIndex);
|
|
}
|
|
}
|
|
|
|
// Next, scan string for adjacent ws and convert to nbsp/space combos
|
|
// MOOSE: don't need to convert tabs here since that is done by
|
|
// WillInsertText() before we are called. Eventually, all that logic will be
|
|
// pushed down into here and made more efficient.
|
|
bool prevWS = false;
|
|
for (uint32_t i = 0; i <= lastCharIndex; i++) {
|
|
if (nsCRT::IsAsciiSpace(theString[i])) {
|
|
if (prevWS) {
|
|
// i - 1 can't be negative because prevWS starts out false
|
|
theString.SetCharAt(kNBSP, i - 1);
|
|
} else {
|
|
prevWS = true;
|
|
}
|
|
} else {
|
|
prevWS = false;
|
|
}
|
|
}
|
|
|
|
// XXX If the point is not editable, InsertTextWithTransaction() returns
|
|
// error, but we keep handling it. But I think that it wastes the
|
|
// runtime cost. So, perhaps, we should return error code which couldn't
|
|
// modify it and make each caller of this method decide whether it should
|
|
// keep or stop handling the edit action.
|
|
nsresult rv =
|
|
MOZ_KnownLive(mHTMLEditor)
|
|
.InsertTextWithTransaction(aDocument, theString, pointToInsert,
|
|
aPointAfterInsertedString);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed, but ignored");
|
|
|
|
// XXX Temporarily, set new insertion point to the original point.
|
|
if (aPointAfterInsertedString) {
|
|
*aPointAfterInsertedString = pointToInsert;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult WSRunObject::DeleteWSBackward() {
|
|
EditorDOMPointInText atPreviousCharOfStart =
|
|
GetPreviousCharPoint(mScanStartPoint);
|
|
if (!atPreviousCharOfStart.IsSet() ||
|
|
atPreviousCharOfStart.IsEndOfContainer()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Easy case, preformatted ws.
|
|
if (mPRE) {
|
|
if (!atPreviousCharOfStart.IsCharASCIISpace() &&
|
|
!atPreviousCharOfStart.IsCharNBSP()) {
|
|
return NS_OK;
|
|
}
|
|
nsresult rv =
|
|
DeleteRange(atPreviousCharOfStart, atPreviousCharOfStart.NextPoint());
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WSRunObject::DeleteRange() failed");
|
|
return rv;
|
|
}
|
|
|
|
// Caller's job to ensure that previous char is really ws. If it is normal
|
|
// ws, we need to delete the whole run.
|
|
if (atPreviousCharOfStart.IsCharASCIISpace()) {
|
|
EditorDOMPointInText start, end;
|
|
Tie(start, end) =
|
|
GetASCIIWhitespacesBounds(eBoth, atPreviousCharOfStart.NextPoint());
|
|
NS_WARNING_ASSERTION(start.IsSet(),
|
|
"WSRunObject::GetASCIIWhitespacesBounds() didn't "
|
|
"return start position, but ignored");
|
|
NS_WARNING_ASSERTION(end.IsSet(),
|
|
"WSRunObject::GetASCIIWhitespacesBounds() didn't "
|
|
"return end position, but ignored");
|
|
|
|
// adjust surrounding ws
|
|
EditorDOMPoint startToDelete(start), endToDelete(end);
|
|
nsresult rv = WSRunObject::PrepareToDeleteRange(
|
|
MOZ_KnownLive(mHTMLEditor), &startToDelete, &endToDelete);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::PrepareToDeleteRange() failed");
|
|
return rv;
|
|
}
|
|
|
|
// finally, delete that ws
|
|
rv = DeleteRange(startToDelete, endToDelete);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WSRunObject::DeleteRange() failed");
|
|
return rv;
|
|
}
|
|
|
|
if (atPreviousCharOfStart.IsCharNBSP()) {
|
|
EditorDOMPoint startToDelete(atPreviousCharOfStart);
|
|
EditorDOMPoint endToDelete(startToDelete.NextPoint());
|
|
nsresult rv = WSRunObject::PrepareToDeleteRange(
|
|
MOZ_KnownLive(mHTMLEditor), &startToDelete, &endToDelete);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::PrepareToDeleteRange() failed");
|
|
return rv;
|
|
}
|
|
|
|
// finally, delete that ws
|
|
rv = DeleteRange(startToDelete, endToDelete);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WSRunObject::DeleteRange() failed");
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult WSRunObject::DeleteWSForward() {
|
|
EditorDOMPointInText atNextCharOfStart = GetNextCharPoint(mScanStartPoint);
|
|
if (!atNextCharOfStart.IsSet() || atNextCharOfStart.IsEndOfContainer()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Easy case, preformatted ws.
|
|
if (mPRE) {
|
|
if (!atNextCharOfStart.IsCharASCIISpace() &&
|
|
!atNextCharOfStart.IsCharNBSP()) {
|
|
return NS_OK;
|
|
}
|
|
nsresult rv = DeleteRange(atNextCharOfStart, atNextCharOfStart.NextPoint());
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WSRunObject::DeleteRange() failed");
|
|
return rv;
|
|
}
|
|
|
|
// Caller's job to ensure that next char is really ws. If it is normal ws,
|
|
// we need to delete the whole run.
|
|
if (atNextCharOfStart.IsCharASCIISpace()) {
|
|
EditorDOMPointInText start, end;
|
|
Tie(start, end) =
|
|
GetASCIIWhitespacesBounds(eBoth, atNextCharOfStart.NextPoint());
|
|
NS_WARNING_ASSERTION(start.IsSet(),
|
|
"WSRunObject::GetASCIIWhitespacesBounds() didn't "
|
|
"return start position, but ignored");
|
|
NS_WARNING_ASSERTION(end.IsSet(),
|
|
"WSRunObject::GetASCIIWhitespacesBounds() didn't "
|
|
"return end position, but ignored");
|
|
// Adjust surrounding ws
|
|
EditorDOMPoint startToDelete(start), endToDelete(end);
|
|
nsresult rv = WSRunObject::PrepareToDeleteRange(
|
|
MOZ_KnownLive(mHTMLEditor), &startToDelete, &endToDelete);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::PrepareToDeleteRange() failed");
|
|
return rv;
|
|
}
|
|
|
|
// Finally, delete that ws
|
|
rv = DeleteRange(startToDelete, endToDelete);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WSRunObject::DeleteRange() failed");
|
|
return rv;
|
|
}
|
|
|
|
if (atNextCharOfStart.IsCharNBSP()) {
|
|
EditorDOMPoint startToDelete(atNextCharOfStart);
|
|
EditorDOMPoint endToDelete(startToDelete.NextPoint());
|
|
nsresult rv = WSRunObject::PrepareToDeleteRange(
|
|
MOZ_KnownLive(mHTMLEditor), &startToDelete, &endToDelete);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::PrepareToDeleteRange() failed");
|
|
return rv;
|
|
}
|
|
|
|
// Finally, delete that ws
|
|
rv = DeleteRange(startToDelete, endToDelete);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WSRunObject::DeleteRange() failed");
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
template <typename PT, typename CT>
|
|
WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
|
|
const EditorDOMPointBase<PT, CT>& aPoint) const {
|
|
// Find first visible thing before the point. Position
|
|
// outVisNode/outVisOffset just _after_ that thing. If we don't find
|
|
// anything return start of ws.
|
|
MOZ_ASSERT(aPoint.IsSet());
|
|
|
|
WSFragment* run = FindNearestRun(aPoint, false);
|
|
|
|
// Is there a visible run there or earlier?
|
|
for (; run; run = run->mLeft) {
|
|
if (run->IsVisibleAndMiddleOfHardLine()) {
|
|
EditorDOMPointInText atPreviousChar = GetPreviousCharPoint(aPoint);
|
|
// When it's a non-empty text node, return it.
|
|
if (atPreviousChar.IsSet() && !atPreviousChar.IsContainerEmpty()) {
|
|
MOZ_ASSERT(!atPreviousChar.IsEndOfContainer());
|
|
return WSScanResult(
|
|
atPreviousChar.NextPoint(),
|
|
atPreviousChar.IsCharASCIISpace() || atPreviousChar.IsCharNBSP()
|
|
? WSType::NormalWhiteSpaces
|
|
: WSType::NormalText);
|
|
}
|
|
// If no text node, keep looking. We should eventually fall out of loop
|
|
}
|
|
}
|
|
|
|
if (mStartReasonContent != mStartNode) {
|
|
// In this case, mStartOffset is not meaningful.
|
|
return WSScanResult(mStartReasonContent, mStartReason);
|
|
}
|
|
return WSScanResult(EditorDOMPoint(mStartReasonContent, mStartOffset),
|
|
mStartReason);
|
|
}
|
|
|
|
template <typename PT, typename CT>
|
|
WSScanResult WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
|
|
const EditorDOMPointBase<PT, CT>& aPoint) const {
|
|
// Find first visible thing after the point. Position
|
|
// outVisNode/outVisOffset just _before_ that thing. If we don't find
|
|
// anything return end of ws.
|
|
MOZ_ASSERT(aPoint.IsSet());
|
|
|
|
WSFragment* run = FindNearestRun(aPoint, true);
|
|
|
|
// Is there a visible run there or later?
|
|
for (; run; run = run->mRight) {
|
|
if (run->IsVisibleAndMiddleOfHardLine()) {
|
|
EditorDOMPointInText atNextChar = GetNextCharPoint(aPoint);
|
|
// When it's a non-empty text node, return it.
|
|
if (atNextChar.IsSet() && !atNextChar.IsContainerEmpty()) {
|
|
return WSScanResult(
|
|
atNextChar,
|
|
!atNextChar.IsEndOfContainer() &&
|
|
(atNextChar.IsCharASCIISpace() || atNextChar.IsCharNBSP())
|
|
? WSType::NormalWhiteSpaces
|
|
: WSType::NormalText);
|
|
}
|
|
// If no text node, keep looking. We should eventually fall out of loop
|
|
}
|
|
}
|
|
|
|
if (mEndReasonContent != mEndNode) {
|
|
// In this case, mEndOffset is not meaningful.
|
|
return WSScanResult(mEndReasonContent, mEndReason);
|
|
}
|
|
return WSScanResult(EditorDOMPoint(mEndReasonContent, mEndOffset),
|
|
mEndReason);
|
|
}
|
|
|
|
nsresult WSRunObject::AdjustWhitespace() {
|
|
// this routine examines a run of ws and tries to get rid of some unneeded
|
|
// nbsp's, replacing them with regualr ascii space if possible. Keeping
|
|
// things simple for now and just trying to fix up the trailing ws in the run.
|
|
if (!mLastNBSPNode) {
|
|
// nothing to do!
|
|
return NS_OK;
|
|
}
|
|
for (WSFragment* run = mStartRun; run; run = run->mRight) {
|
|
if (!run->IsVisibleAndMiddleOfHardLine()) {
|
|
continue;
|
|
}
|
|
nsresult rv = CheckTrailingNBSPOfRun(run);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::CheckTrailingNBSPOfRun() failed");
|
|
return rv;
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
// protected methods
|
|
//--------------------------------------------------------------------------------------------
|
|
|
|
nsIContent* WSRunScanner::GetEditableBlockParentOrTopmotEditableInlineContent(
|
|
nsIContent* aContent) const {
|
|
if (NS_WARN_IF(!aContent)) {
|
|
return nullptr;
|
|
}
|
|
NS_ASSERTION(EditorUtils::IsEditableContent(*aContent, EditorType::HTML),
|
|
"Given content is not editable");
|
|
// XXX What should we do if scan range crosses block boundary? Currently,
|
|
// it's not collapsed only when inserting composition string so that
|
|
// it's possible but shouldn't occur actually.
|
|
nsIContent* editableBlockParentOrTopmotEditableInlineContent = nullptr;
|
|
for (nsIContent* content : InclusiveAncestorsOfType<nsIContent>(*aContent)) {
|
|
if (!EditorUtils::IsEditableContent(*content, EditorType::HTML)) {
|
|
break;
|
|
}
|
|
editableBlockParentOrTopmotEditableInlineContent = content;
|
|
if (HTMLEditUtils::IsBlockElement(
|
|
*editableBlockParentOrTopmotEditableInlineContent)) {
|
|
break;
|
|
}
|
|
}
|
|
return editableBlockParentOrTopmotEditableInlineContent;
|
|
}
|
|
|
|
nsresult WSRunScanner::GetWSNodes() {
|
|
// collect up an array of nodes that are contiguous with the insertion point
|
|
// and which contain only whitespace. Stop if you reach non-ws text or a new
|
|
// block boundary.
|
|
EditorDOMPoint start(mScanStartPoint), end(mScanStartPoint);
|
|
nsIContent* scanStartContent = mScanStartPoint.GetContainerAsContent();
|
|
if (NS_WARN_IF(!scanStartContent)) {
|
|
// Meaning container of mScanStartPoint is a Document or DocumentFragment.
|
|
// I.e., we're try to modify outside of root element. We don't need to
|
|
// support such odd case because web apps cannot append text nodes as
|
|
// direct child of Document node.
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
nsIContent* editableBlockParentOrTopmotEditableInlineContent =
|
|
GetEditableBlockParentOrTopmotEditableInlineContent(scanStartContent);
|
|
if (NS_WARN_IF(!editableBlockParentOrTopmotEditableInlineContent)) {
|
|
// Meaning that the container of `mScanStartPoint` is not editable.
|
|
editableBlockParentOrTopmotEditableInlineContent = scanStartContent;
|
|
}
|
|
|
|
// first look backwards to find preceding ws nodes
|
|
if (Text* textNode = mScanStartPoint.GetContainerAsText()) {
|
|
const nsTextFragment* textFrag = &textNode->TextFragment();
|
|
mNodeArray.InsertElementAt(0, textNode);
|
|
if (!mScanStartPoint.IsStartOfContainer()) {
|
|
for (uint32_t i = mScanStartPoint.Offset(); i; i--) {
|
|
// sanity bounds check the char position. bug 136165
|
|
if (i > textFrag->GetLength()) {
|
|
MOZ_ASSERT_UNREACHABLE("looking beyond end of text fragment");
|
|
continue;
|
|
}
|
|
char16_t theChar = textFrag->CharAt(i - 1);
|
|
if (!nsCRT::IsAsciiSpace(theChar)) {
|
|
if (theChar != kNBSP) {
|
|
mStartNode = textNode;
|
|
mStartOffset = i;
|
|
mStartReason = WSType::NormalText;
|
|
mStartReasonContent = textNode;
|
|
break;
|
|
}
|
|
// as we look backwards update our earliest found nbsp
|
|
mFirstNBSPNode = textNode;
|
|
mFirstNBSPOffset = i - 1;
|
|
// also keep track of latest nbsp so far
|
|
if (!mLastNBSPNode) {
|
|
mLastNBSPNode = textNode;
|
|
mLastNBSPOffset = i - 1;
|
|
}
|
|
}
|
|
start.Set(textNode, i - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (!mStartNode) {
|
|
// we haven't found the start of ws yet. Keep looking
|
|
nsCOMPtr<nsIContent> priorNode = GetPreviousWSNode(
|
|
start, editableBlockParentOrTopmotEditableInlineContent);
|
|
if (priorNode) {
|
|
if (HTMLEditUtils::IsBlockElement(*priorNode)) {
|
|
mStartNode = start.GetContainer();
|
|
mStartOffset = start.Offset();
|
|
mStartReason = WSType::OtherBlockBoundary;
|
|
mStartReasonContent = priorNode;
|
|
} else if (priorNode->IsText() && priorNode->IsEditable()) {
|
|
RefPtr<Text> textNode = priorNode->AsText();
|
|
mNodeArray.InsertElementAt(0, textNode);
|
|
const nsTextFragment* textFrag = &textNode->TextFragment();
|
|
uint32_t len = textNode->TextLength();
|
|
|
|
if (len < 1) {
|
|
// Zero length text node. Set start point to it
|
|
// so we can get past it!
|
|
start.Set(priorNode, 0);
|
|
} else {
|
|
for (int32_t pos = len - 1; pos >= 0; pos--) {
|
|
// sanity bounds check the char position. bug 136165
|
|
if (uint32_t(pos) >= textFrag->GetLength()) {
|
|
MOZ_ASSERT_UNREACHABLE("looking beyond end of text fragment");
|
|
continue;
|
|
}
|
|
char16_t theChar = textFrag->CharAt(pos);
|
|
if (!nsCRT::IsAsciiSpace(theChar)) {
|
|
if (theChar != kNBSP) {
|
|
mStartNode = textNode;
|
|
mStartOffset = pos + 1;
|
|
mStartReason = WSType::NormalText;
|
|
mStartReasonContent = textNode;
|
|
break;
|
|
}
|
|
// as we look backwards update our earliest found nbsp
|
|
mFirstNBSPNode = textNode;
|
|
mFirstNBSPOffset = pos;
|
|
// also keep track of latest nbsp so far
|
|
if (!mLastNBSPNode) {
|
|
mLastNBSPNode = textNode;
|
|
mLastNBSPOffset = pos;
|
|
}
|
|
}
|
|
start.Set(textNode, pos);
|
|
}
|
|
}
|
|
} else {
|
|
// it's a break or a special node, like <img>, that is not a block and
|
|
// not a break but still serves as a terminator to ws runs.
|
|
mStartNode = start.GetContainer();
|
|
mStartOffset = start.Offset();
|
|
if (priorNode->IsHTMLElement(nsGkAtoms::br)) {
|
|
mStartReason = WSType::BRElement;
|
|
} else {
|
|
mStartReason = WSType::SpecialContent;
|
|
}
|
|
mStartReasonContent = priorNode;
|
|
}
|
|
} else {
|
|
// no prior node means we exhausted
|
|
// editableBlockParentOrTopmotEditableInlineContent
|
|
mStartNode = start.GetContainer();
|
|
mStartOffset = start.Offset();
|
|
mStartReason = WSType::CurrentBlockBoundary;
|
|
// mStartReasonContent can be either a block element or any non-editable
|
|
// content in this case.
|
|
mStartReasonContent = editableBlockParentOrTopmotEditableInlineContent;
|
|
}
|
|
}
|
|
|
|
// then look ahead to find following ws nodes
|
|
if (Text* textNode = end.GetContainerAsText()) {
|
|
// don't need to put it on list. it already is from code above
|
|
const nsTextFragment* textFrag = &textNode->TextFragment();
|
|
if (!end.IsEndOfContainer()) {
|
|
for (uint32_t i = end.Offset(); i < textNode->TextLength(); i++) {
|
|
// sanity bounds check the char position. bug 136165
|
|
if (i >= textFrag->GetLength()) {
|
|
MOZ_ASSERT_UNREACHABLE("looking beyond end of text fragment");
|
|
continue;
|
|
}
|
|
char16_t theChar = textFrag->CharAt(i);
|
|
if (!nsCRT::IsAsciiSpace(theChar)) {
|
|
if (theChar != kNBSP) {
|
|
mEndNode = textNode;
|
|
mEndOffset = i;
|
|
mEndReason = WSType::NormalText;
|
|
mEndReasonContent = textNode;
|
|
break;
|
|
}
|
|
// as we look forwards update our latest found nbsp
|
|
mLastNBSPNode = textNode;
|
|
mLastNBSPOffset = i;
|
|
// also keep track of earliest nbsp so far
|
|
if (!mFirstNBSPNode) {
|
|
mFirstNBSPNode = textNode;
|
|
mFirstNBSPOffset = i;
|
|
}
|
|
}
|
|
end.Set(textNode, i + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (!mEndNode) {
|
|
// we haven't found the end of ws yet. Keep looking
|
|
nsCOMPtr<nsIContent> nextNode =
|
|
GetNextWSNode(end, editableBlockParentOrTopmotEditableInlineContent);
|
|
if (nextNode) {
|
|
if (HTMLEditUtils::IsBlockElement(*nextNode)) {
|
|
// we encountered a new block. therefore no more ws.
|
|
mEndNode = end.GetContainer();
|
|
mEndOffset = end.Offset();
|
|
mEndReason = WSType::OtherBlockBoundary;
|
|
mEndReasonContent = nextNode;
|
|
} else if (nextNode->IsText() && nextNode->IsEditable()) {
|
|
RefPtr<Text> textNode = nextNode->AsText();
|
|
mNodeArray.AppendElement(textNode);
|
|
const nsTextFragment* textFrag = &textNode->TextFragment();
|
|
uint32_t len = textNode->TextLength();
|
|
|
|
if (len < 1) {
|
|
// Zero length text node. Set end point to it
|
|
// so we can get past it!
|
|
end.Set(textNode, 0);
|
|
} else {
|
|
for (uint32_t pos = 0; pos < len; pos++) {
|
|
// sanity bounds check the char position. bug 136165
|
|
if (pos >= textFrag->GetLength()) {
|
|
MOZ_ASSERT_UNREACHABLE("looking beyond end of text fragment");
|
|
continue;
|
|
}
|
|
char16_t theChar = textFrag->CharAt(pos);
|
|
if (!nsCRT::IsAsciiSpace(theChar)) {
|
|
if (theChar != kNBSP) {
|
|
mEndNode = textNode;
|
|
mEndOffset = pos;
|
|
mEndReason = WSType::NormalText;
|
|
mEndReasonContent = textNode;
|
|
break;
|
|
}
|
|
// as we look forwards update our latest found nbsp
|
|
mLastNBSPNode = textNode;
|
|
mLastNBSPOffset = pos;
|
|
// also keep track of earliest nbsp so far
|
|
if (!mFirstNBSPNode) {
|
|
mFirstNBSPNode = textNode;
|
|
mFirstNBSPOffset = pos;
|
|
}
|
|
}
|
|
end.Set(textNode, pos + 1);
|
|
}
|
|
}
|
|
} else {
|
|
// we encountered a break or a special node, like <img>,
|
|
// that is not a block and not a break but still
|
|
// serves as a terminator to ws runs.
|
|
mEndNode = end.GetContainer();
|
|
mEndOffset = end.Offset();
|
|
if (nextNode->IsHTMLElement(nsGkAtoms::br)) {
|
|
mEndReason = WSType::BRElement;
|
|
} else {
|
|
mEndReason = WSType::SpecialContent;
|
|
}
|
|
mEndReasonContent = nextNode;
|
|
}
|
|
} else {
|
|
// no next node means we exhausted
|
|
// editableBlockParentOrTopmotEditableInlineContent
|
|
mEndNode = end.GetContainer();
|
|
mEndOffset = end.Offset();
|
|
mEndReason = WSType::CurrentBlockBoundary;
|
|
// mEndReasonContent can be either a block element or any non-editable
|
|
// content in this case.
|
|
mEndReasonContent = editableBlockParentOrTopmotEditableInlineContent;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void WSRunScanner::GetRuns() {
|
|
ClearRuns();
|
|
|
|
// Handle preformatted case first since it's simple. Note that if end of
|
|
// the scan range isn't in preformatted element, we need to check only the
|
|
// style at mScanStartPoint since the range would be replaced and the start
|
|
// style will be applied to all new string.
|
|
mPRE = EditorBase::IsPreformatted(mScanStartPoint.GetContainer());
|
|
// if it's preformatedd, or if we are surrounded by text or special, it's all
|
|
// one big normal ws run
|
|
if (mPRE ||
|
|
((StartsFromNormalText() || StartsFromSpecialContent()) &&
|
|
(EndsByNormalText() || EndsBySpecialContent() || EndsByBRElement()))) {
|
|
InitializeWithSingleFragment(WSFragment::Visible::Yes,
|
|
WSFragment::StartOfHardLine::No,
|
|
WSFragment::EndOfHardLine::No);
|
|
return;
|
|
}
|
|
|
|
// if we are before or after a block (or after a break), and there are no
|
|
// nbsp's, then it's all non-rendering ws.
|
|
if (!mFirstNBSPNode && !mLastNBSPNode &&
|
|
(StartsFromHardLineBreak() || EndsByBlockBoundary())) {
|
|
InitializeWithSingleFragment(
|
|
WSFragment::Visible::No,
|
|
StartsFromHardLineBreak() ? WSFragment::StartOfHardLine::Yes
|
|
: WSFragment::StartOfHardLine::No,
|
|
EndsByBlockBoundary() ? WSFragment::EndOfHardLine::Yes
|
|
: WSFragment::EndOfHardLine::No);
|
|
return;
|
|
}
|
|
|
|
// otherwise a little trickier. shucks.
|
|
mStartRun = new WSFragment();
|
|
mStartRun->mStartNode = mStartNode;
|
|
mStartRun->mStartOffset = mStartOffset;
|
|
|
|
if (StartsFromHardLineBreak()) {
|
|
// set up mStartRun
|
|
mStartRun->MarkAsStartOfHardLine();
|
|
mStartRun->mEndNode = mFirstNBSPNode;
|
|
mStartRun->mEndOffset = mFirstNBSPOffset;
|
|
mStartRun->SetStartFrom(mStartReason);
|
|
mStartRun->SetEndByNormalWiteSpaces();
|
|
|
|
// set up next run
|
|
WSFragment* normalRun = new WSFragment();
|
|
mStartRun->mRight = normalRun;
|
|
normalRun->MarkAsVisible();
|
|
normalRun->mStartNode = mFirstNBSPNode;
|
|
normalRun->mStartOffset = mFirstNBSPOffset;
|
|
normalRun->SetStartFromLeadingWhiteSpaces();
|
|
normalRun->mLeft = mStartRun;
|
|
if (!EndsByBlockBoundary()) {
|
|
// then no trailing ws. this normal run ends the overall ws run.
|
|
normalRun->SetEndBy(mEndReason);
|
|
normalRun->mEndNode = mEndNode;
|
|
normalRun->mEndOffset = mEndOffset;
|
|
mEndRun = normalRun;
|
|
} else {
|
|
// we might have trailing ws.
|
|
// it so happens that *if* there is an nbsp at end,
|
|
// {mEndNode,mEndOffset-1} will point to it, even though in general
|
|
// start/end points not guaranteed to be in text nodes.
|
|
if (mLastNBSPNode == mEndNode && mLastNBSPOffset == mEndOffset - 1) {
|
|
// normal ws runs right up to adjacent block (nbsp next to block)
|
|
normalRun->SetEndBy(mEndReason);
|
|
normalRun->mEndNode = mEndNode;
|
|
normalRun->mEndOffset = mEndOffset;
|
|
mEndRun = normalRun;
|
|
} else {
|
|
normalRun->mEndNode = mLastNBSPNode;
|
|
normalRun->mEndOffset = mLastNBSPOffset + 1;
|
|
normalRun->SetEndByTrailingWhiteSpaces();
|
|
|
|
// set up next run
|
|
WSFragment* lastRun = new WSFragment();
|
|
lastRun->MarkAsEndOfHardLine();
|
|
lastRun->mStartNode = mLastNBSPNode;
|
|
lastRun->mStartOffset = mLastNBSPOffset + 1;
|
|
lastRun->mEndNode = mEndNode;
|
|
lastRun->mEndOffset = mEndOffset;
|
|
lastRun->SetStartFromNormalWhiteSpaces();
|
|
lastRun->mLeft = normalRun;
|
|
lastRun->SetEndBy(mEndReason);
|
|
mEndRun = lastRun;
|
|
normalRun->mRight = lastRun;
|
|
}
|
|
}
|
|
} else {
|
|
MOZ_ASSERT(!StartsFromHardLineBreak());
|
|
mStartRun->MarkAsVisible();
|
|
mStartRun->mEndNode = mLastNBSPNode;
|
|
mStartRun->mEndOffset = mLastNBSPOffset + 1;
|
|
mStartRun->SetStartFrom(mStartReason);
|
|
|
|
// we might have trailing ws.
|
|
// it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1}
|
|
// will point to it, even though in general start/end points not
|
|
// guaranteed to be in text nodes.
|
|
if (mLastNBSPNode == mEndNode && mLastNBSPOffset == (mEndOffset - 1)) {
|
|
mStartRun->SetEndBy(mEndReason);
|
|
mStartRun->mEndNode = mEndNode;
|
|
mStartRun->mEndOffset = mEndOffset;
|
|
mEndRun = mStartRun;
|
|
} else {
|
|
// set up next run
|
|
WSFragment* lastRun = new WSFragment();
|
|
lastRun->MarkAsEndOfHardLine();
|
|
lastRun->mStartNode = mLastNBSPNode;
|
|
lastRun->mStartOffset = mLastNBSPOffset + 1;
|
|
lastRun->SetStartFromNormalWhiteSpaces();
|
|
lastRun->mLeft = mStartRun;
|
|
lastRun->SetEndBy(mEndReason);
|
|
mEndRun = lastRun;
|
|
mStartRun->mRight = lastRun;
|
|
mStartRun->SetEndByTrailingWhiteSpaces();
|
|
}
|
|
}
|
|
}
|
|
|
|
void WSRunScanner::ClearRuns() {
|
|
WSFragment *tmp, *run;
|
|
run = mStartRun;
|
|
while (run) {
|
|
tmp = run->mRight;
|
|
delete run;
|
|
run = tmp;
|
|
}
|
|
mStartRun = 0;
|
|
mEndRun = 0;
|
|
}
|
|
|
|
void WSRunScanner::InitializeWithSingleFragment(
|
|
WSFragment::Visible aIsVisible,
|
|
WSFragment::StartOfHardLine aIsStartOfHardLine,
|
|
WSFragment::EndOfHardLine aIsEndOfHardLine) {
|
|
MOZ_ASSERT(!mStartRun);
|
|
MOZ_ASSERT(!mEndRun);
|
|
|
|
mStartRun = new WSFragment();
|
|
|
|
mStartRun->mStartNode = mStartNode;
|
|
mStartRun->mStartOffset = mStartOffset;
|
|
if (aIsVisible == WSFragment::Visible::Yes) {
|
|
mStartRun->MarkAsVisible();
|
|
}
|
|
if (aIsStartOfHardLine == WSFragment::StartOfHardLine::Yes) {
|
|
mStartRun->MarkAsStartOfHardLine();
|
|
}
|
|
if (aIsEndOfHardLine == WSFragment::EndOfHardLine::Yes) {
|
|
mStartRun->MarkAsEndOfHardLine();
|
|
}
|
|
mStartRun->mEndNode = mEndNode;
|
|
mStartRun->mEndOffset = mEndOffset;
|
|
mStartRun->SetStartFrom(mStartReason);
|
|
mStartRun->SetEndBy(mEndReason);
|
|
|
|
mEndRun = mStartRun;
|
|
}
|
|
|
|
nsIContent* WSRunScanner::GetPreviousWSNodeInner(nsINode* aStartNode,
|
|
nsINode* aBlockParent) const {
|
|
// Can't really recycle various getnext/prior routines because we have
|
|
// special needs here. Need to step into inline containers but not block
|
|
// containers.
|
|
MOZ_ASSERT(aStartNode && aBlockParent);
|
|
|
|
if (aStartNode == mEditingHost) {
|
|
NS_WARNING(
|
|
"WSRunScanner::GetPreviousWSNodeInner() was called with editing host");
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIContent> previousContent = aStartNode->GetPreviousSibling();
|
|
OwningNonNull<nsINode> curNode = *aStartNode;
|
|
while (!previousContent) {
|
|
// We have exhausted nodes in parent of aStartNode.
|
|
nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
|
|
if (!curParent) {
|
|
NS_WARNING("Reached orphan node while climbing up the DOM tree");
|
|
return nullptr;
|
|
}
|
|
if (curParent == aBlockParent) {
|
|
// We have exhausted nodes in the block parent. The convention here is
|
|
// to return null.
|
|
return nullptr;
|
|
}
|
|
if (curParent == mEditingHost) {
|
|
NS_WARNING("Reached editing host while climbing up the DOM tree");
|
|
return nullptr;
|
|
}
|
|
// We have a parent: look for previous sibling
|
|
previousContent = curParent->GetPreviousSibling();
|
|
curNode = curParent;
|
|
}
|
|
|
|
if (!previousContent) {
|
|
return nullptr;
|
|
}
|
|
|
|
// We have a prior node. If it's a block, return it.
|
|
if (HTMLEditUtils::IsBlockElement(*previousContent)) {
|
|
return previousContent;
|
|
}
|
|
if (HTMLEditUtils::IsContainerNode(*previousContent)) {
|
|
// Else if it's a container, get deep rightmost child
|
|
nsCOMPtr<nsIContent> child =
|
|
mHTMLEditor->GetRightmostChild(previousContent);
|
|
if (child) {
|
|
return child;
|
|
}
|
|
}
|
|
// Else return the node itself
|
|
return previousContent;
|
|
}
|
|
|
|
nsIContent* WSRunScanner::GetPreviousWSNode(const EditorDOMPoint& aPoint,
|
|
nsINode* aBlockParent) const {
|
|
// Can't really recycle various getnext/prior routines because we
|
|
// have special needs here. Need to step into inline containers but
|
|
// not block containers.
|
|
MOZ_ASSERT(aPoint.IsSet() && aBlockParent);
|
|
|
|
if (aPoint.IsInTextNode()) {
|
|
return GetPreviousWSNodeInner(aPoint.GetContainer(), aBlockParent);
|
|
}
|
|
if (!aPoint.IsInContentNode() ||
|
|
!HTMLEditUtils::IsContainerNode(*aPoint.ContainerAsContent())) {
|
|
return GetPreviousWSNodeInner(aPoint.GetContainer(), aBlockParent);
|
|
}
|
|
|
|
if (!aPoint.Offset()) {
|
|
if (aPoint.GetContainer() == aBlockParent) {
|
|
// We are at start of the block.
|
|
return nullptr;
|
|
}
|
|
|
|
// We are at start of non-block container
|
|
return GetPreviousWSNodeInner(aPoint.GetContainer(), aBlockParent);
|
|
}
|
|
|
|
if (NS_WARN_IF(!aPoint.IsInContentNode())) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIContent> previousContent = aPoint.GetPreviousSiblingOfChild();
|
|
if (NS_WARN_IF(!previousContent)) {
|
|
return nullptr;
|
|
}
|
|
|
|
// We have a prior node. If it's a block, return it.
|
|
if (HTMLEditUtils::IsBlockElement(*previousContent)) {
|
|
return previousContent;
|
|
}
|
|
if (HTMLEditUtils::IsContainerNode(*previousContent)) {
|
|
// Else if it's a container, get deep rightmost child
|
|
nsCOMPtr<nsIContent> child =
|
|
mHTMLEditor->GetRightmostChild(previousContent);
|
|
if (child) {
|
|
return child;
|
|
}
|
|
}
|
|
// Else return the node itself
|
|
return previousContent;
|
|
}
|
|
|
|
nsIContent* WSRunScanner::GetNextWSNodeInner(nsINode* aStartNode,
|
|
nsINode* aBlockParent) const {
|
|
// Can't really recycle various getnext/prior routines because we have
|
|
// special needs here. Need to step into inline containers but not block
|
|
// containers.
|
|
MOZ_ASSERT(aStartNode && aBlockParent);
|
|
|
|
if (aStartNode == mEditingHost) {
|
|
NS_WARNING(
|
|
"WSRunScanner::GetNextWSNodeInner() was called with editing host");
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIContent> nextContent = aStartNode->GetNextSibling();
|
|
nsCOMPtr<nsINode> curNode = aStartNode;
|
|
while (!nextContent) {
|
|
// We have exhausted nodes in parent of aStartNode.
|
|
nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
|
|
if (!curParent) {
|
|
NS_WARNING("Reached orphan node while climbing up the DOM tree");
|
|
return nullptr;
|
|
}
|
|
if (curParent == aBlockParent) {
|
|
// We have exhausted nodes in the block parent. The convention here is
|
|
// to return null.
|
|
return nullptr;
|
|
}
|
|
if (curParent == mEditingHost) {
|
|
NS_WARNING("Reached editing host while climbing up the DOM tree");
|
|
return nullptr;
|
|
}
|
|
// We have a parent: look for next sibling
|
|
nextContent = curParent->GetNextSibling();
|
|
curNode = curParent;
|
|
}
|
|
|
|
if (!nextContent) {
|
|
return nullptr;
|
|
}
|
|
|
|
// We have a next node. If it's a block, return it.
|
|
if (HTMLEditUtils::IsBlockElement(*nextContent)) {
|
|
return nextContent;
|
|
}
|
|
if (HTMLEditUtils::IsContainerNode(*nextContent)) {
|
|
// Else if it's a container, get deep leftmost child
|
|
nsCOMPtr<nsIContent> child = mHTMLEditor->GetLeftmostChild(nextContent);
|
|
if (child) {
|
|
return child;
|
|
}
|
|
}
|
|
// Else return the node itself
|
|
return nextContent;
|
|
}
|
|
|
|
nsIContent* WSRunScanner::GetNextWSNode(const EditorDOMPoint& aPoint,
|
|
nsINode* aBlockParent) const {
|
|
// Can't really recycle various getnext/prior routines because we have
|
|
// special needs here. Need to step into inline containers but not block
|
|
// containers.
|
|
MOZ_ASSERT(aPoint.IsSet() && aBlockParent);
|
|
|
|
if (aPoint.IsInTextNode()) {
|
|
return GetNextWSNodeInner(aPoint.GetContainer(), aBlockParent);
|
|
}
|
|
if (!aPoint.IsInContentNode() ||
|
|
!HTMLEditUtils::IsContainerNode(*aPoint.ContainerAsContent())) {
|
|
return GetNextWSNodeInner(aPoint.GetContainer(), aBlockParent);
|
|
}
|
|
|
|
if (NS_WARN_IF(!aPoint.IsInContentNode())) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIContent> nextContent = aPoint.GetChild();
|
|
if (!nextContent) {
|
|
if (aPoint.GetContainer() == aBlockParent) {
|
|
// We are at end of the block.
|
|
return nullptr;
|
|
}
|
|
|
|
// We are at end of non-block container
|
|
return GetNextWSNodeInner(aPoint.GetContainer(), aBlockParent);
|
|
}
|
|
|
|
// We have a next node. If it's a block, return it.
|
|
if (HTMLEditUtils::IsBlockElement(*nextContent)) {
|
|
return nextContent;
|
|
}
|
|
if (HTMLEditUtils::IsContainerNode(*nextContent)) {
|
|
// else if it's a container, get deep leftmost child
|
|
nsCOMPtr<nsIContent> child = mHTMLEditor->GetLeftmostChild(nextContent);
|
|
if (child) {
|
|
return child;
|
|
}
|
|
}
|
|
// Else return the node itself
|
|
return nextContent;
|
|
}
|
|
|
|
nsresult WSRunObject::PrepareToDeleteRangePriv(WSRunObject* aEndObject) {
|
|
// this routine adjust whitespace before *this* and after aEndObject
|
|
// in preperation for the two areas to become adjacent after the
|
|
// intervening content is deleted. It's overly agressive right
|
|
// now. There might be a block boundary remaining between them after
|
|
// the deletion, in which case these adjstments are unneeded (though
|
|
// I don't think they can ever be harmful?)
|
|
|
|
if (NS_WARN_IF(!aEndObject)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
// get the runs before and after selection
|
|
WSFragment* beforeRun = FindNearestRun(mScanStartPoint, false);
|
|
WSFragment* afterRun =
|
|
aEndObject->FindNearestRun(aEndObject->mScanStartPoint, true);
|
|
|
|
if (!beforeRun && !afterRun) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (afterRun) {
|
|
// trim after run of any leading ws
|
|
if (afterRun->IsStartOfHardLine()) {
|
|
// mScanStartPoint will be referred bellow so that we need to keep
|
|
// it a valid point.
|
|
AutoEditorDOMPointChildInvalidator forgetChild(mScanStartPoint);
|
|
nsresult rv = aEndObject->DeleteRange(aEndObject->mScanStartPoint,
|
|
afterRun->EndPoint());
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::DeleteRange() failed");
|
|
return rv;
|
|
}
|
|
}
|
|
// adjust normal ws in afterRun if needed
|
|
else if (afterRun->IsVisibleAndMiddleOfHardLine() && !aEndObject->mPRE) {
|
|
if ((beforeRun && beforeRun->IsStartOfHardLine()) ||
|
|
(!beforeRun && StartsFromHardLineBreak())) {
|
|
// make sure leading char of following ws is an nbsp, so that it will
|
|
// show up
|
|
EditorDOMPointInText nextCharOfStartOfEnd =
|
|
aEndObject->GetNextCharPoint(aEndObject->mScanStartPoint);
|
|
if (nextCharOfStartOfEnd.IsSet() &&
|
|
!nextCharOfStartOfEnd.IsEndOfContainer() &&
|
|
nextCharOfStartOfEnd.IsCharASCIISpace()) {
|
|
// mScanStartPoint will be referred bellow so that we need to keep
|
|
// it a valid point.
|
|
AutoEditorDOMPointChildInvalidator forgetChild(mScanStartPoint);
|
|
nsresult rv =
|
|
aEndObject->InsertNBSPAndRemoveFollowingASCIIWhitespaces(
|
|
nextCharOfStartOfEnd);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING(
|
|
"WSRunObject::InsertNBSPAndRemoveFollowingASCIIWhitespaces() "
|
|
"failed");
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!beforeRun) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// trim before run of any trailing ws
|
|
if (beforeRun->IsEndOfHardLine()) {
|
|
nsresult rv = DeleteRange(beforeRun->StartPoint(), mScanStartPoint);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::DeleteRange() failed");
|
|
return rv;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
if (beforeRun->IsVisibleAndMiddleOfHardLine() && !mPRE) {
|
|
if ((afterRun && (afterRun->IsEndOfHardLine() || afterRun->IsVisible())) ||
|
|
(!afterRun && aEndObject->EndsByBlockBoundary())) {
|
|
// make sure trailing char of starting ws is an nbsp, so that it will show
|
|
// up
|
|
EditorDOMPointInText atPreviousCharOfStart =
|
|
GetPreviousCharPoint(mScanStartPoint);
|
|
if (atPreviousCharOfStart.IsSet() &&
|
|
!atPreviousCharOfStart.IsEndOfContainer() &&
|
|
atPreviousCharOfStart.IsCharASCIISpace()) {
|
|
EditorDOMPointInText start, end;
|
|
Tie(start, end) = GetASCIIWhitespacesBounds(eBoth, mScanStartPoint);
|
|
NS_WARNING_ASSERTION(start.IsSet(),
|
|
"WSRunObject::GetASCIIWhitespacesBounds() didn't "
|
|
"return start point, but ignored");
|
|
NS_WARNING_ASSERTION(end.IsSet(),
|
|
"WSRunObject::GetASCIIWhitespacesBounds() didn't "
|
|
"return end point, but ignored");
|
|
nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(start);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING(
|
|
"WSRunObject::InsertNBSPAndRemoveFollowingASCIIWhitespaces() "
|
|
"failed");
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult WSRunObject::PrepareToSplitAcrossBlocksPriv() {
|
|
// used to prepare ws to be split across two blocks. The main issue
|
|
// here is make sure normalWS doesn't end up becoming non-significant
|
|
// leading or trailing ws after the split.
|
|
|
|
// get the runs before and after selection
|
|
WSFragment* beforeRun = FindNearestRun(mScanStartPoint, false);
|
|
WSFragment* afterRun = FindNearestRun(mScanStartPoint, true);
|
|
|
|
// adjust normal ws in afterRun if needed
|
|
if (afterRun && afterRun->IsVisibleAndMiddleOfHardLine()) {
|
|
// make sure leading char of following ws is an nbsp, so that it will show
|
|
// up
|
|
EditorDOMPointInText atNextCharOfStart = GetNextCharPoint(mScanStartPoint);
|
|
if (atNextCharOfStart.IsSet() && !atNextCharOfStart.IsEndOfContainer() &&
|
|
atNextCharOfStart.IsCharASCIISpace()) {
|
|
// mScanStartPoint will be referred bellow so that we need to keep
|
|
// it a valid point.
|
|
AutoEditorDOMPointChildInvalidator forgetChild(mScanStartPoint);
|
|
nsresult rv =
|
|
InsertNBSPAndRemoveFollowingASCIIWhitespaces(atNextCharOfStart);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING(
|
|
"WSRunObject::InsertNBSPAndRemoveFollowingASCIIWhitespaces() "
|
|
"failed");
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
|
|
// adjust normal ws in beforeRun if needed
|
|
if (beforeRun && beforeRun->IsVisibleAndMiddleOfHardLine()) {
|
|
// make sure trailing char of starting ws is an nbsp, so that it will show
|
|
// up
|
|
EditorDOMPointInText atPreviousCharOfStart =
|
|
GetPreviousCharPoint(mScanStartPoint);
|
|
if (atPreviousCharOfStart.IsSet() &&
|
|
!atPreviousCharOfStart.IsEndOfContainer() &&
|
|
atPreviousCharOfStart.IsCharASCIISpace()) {
|
|
EditorDOMPointInText start, end;
|
|
Tie(start, end) = GetASCIIWhitespacesBounds(eBoth, mScanStartPoint);
|
|
NS_WARNING_ASSERTION(start.IsSet(),
|
|
"WSRunObject::GetASCIIWhitespacesBounds() didn't "
|
|
"return start point, but ignored");
|
|
NS_WARNING_ASSERTION(end.IsSet(),
|
|
"WSRunObject::GetASCIIWhitespacesBounds() didn't "
|
|
"return end point, but ignored");
|
|
nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(start);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING(
|
|
"WSRunObject::InsertNBSPAndRemoveFollowingASCIIWhitespaces() "
|
|
"failed");
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult WSRunObject::DeleteRange(const EditorDOMPoint& aStartPoint,
|
|
const EditorDOMPoint& aEndPoint) {
|
|
if (NS_WARN_IF(!aStartPoint.IsSet()) || NS_WARN_IF(!aEndPoint.IsSet())) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
MOZ_ASSERT(aStartPoint.IsSetAndValid());
|
|
MOZ_ASSERT(aEndPoint.IsSetAndValid());
|
|
|
|
// MOOSE: this routine needs to be modified to preserve the integrity of the
|
|
// wsFragment info.
|
|
|
|
if (aStartPoint == aEndPoint) {
|
|
// Nothing to delete
|
|
return NS_OK;
|
|
}
|
|
|
|
if (aStartPoint.GetContainer() == aEndPoint.GetContainer() &&
|
|
aStartPoint.IsInTextNode()) {
|
|
RefPtr<Text> textNode = aStartPoint.ContainerAsText();
|
|
nsresult rv = MOZ_KnownLive(mHTMLEditor)
|
|
.DeleteTextWithTransaction(
|
|
*textNode, aStartPoint.Offset(),
|
|
aEndPoint.Offset() - aStartPoint.Offset());
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"HTMLEditor::DeleteTextWithTransaction() failed");
|
|
return rv;
|
|
}
|
|
|
|
RefPtr<nsRange> range;
|
|
int32_t count = mNodeArray.Length();
|
|
int32_t idx = mNodeArray.IndexOf(aStartPoint.GetContainer());
|
|
if (idx == -1) {
|
|
// If our starting point wasn't one of our ws text nodes, then just go
|
|
// through them from the beginning.
|
|
idx = 0;
|
|
}
|
|
for (; idx < count; idx++) {
|
|
RefPtr<Text> node = mNodeArray[idx];
|
|
if (!node) {
|
|
// We ran out of ws nodes; must have been deleting to end
|
|
return NS_OK;
|
|
}
|
|
if (node == aStartPoint.GetContainer()) {
|
|
if (!aStartPoint.IsEndOfContainer()) {
|
|
nsresult rv = MOZ_KnownLive(mHTMLEditor)
|
|
.DeleteTextWithTransaction(
|
|
*node, aStartPoint.Offset(),
|
|
aStartPoint.GetContainer()->Length() -
|
|
aStartPoint.Offset());
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
|
|
return rv;
|
|
}
|
|
}
|
|
} else if (node == aEndPoint.GetContainer()) {
|
|
if (!aEndPoint.IsStartOfContainer()) {
|
|
nsresult rv =
|
|
MOZ_KnownLive(mHTMLEditor)
|
|
.DeleteTextWithTransaction(*node, 0, aEndPoint.Offset());
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
|
|
return rv;
|
|
}
|
|
}
|
|
break;
|
|
} else {
|
|
if (!range) {
|
|
ErrorResult error;
|
|
range = nsRange::Create(aStartPoint.ToRawRangeBoundary(),
|
|
aEndPoint.ToRawRangeBoundary(), error);
|
|
if (!range) {
|
|
NS_WARNING("nsRange::Create() failed");
|
|
return error.StealNSResult();
|
|
}
|
|
}
|
|
bool nodeBefore, nodeAfter;
|
|
nsresult rv =
|
|
RangeUtils::CompareNodeToRange(node, range, &nodeBefore, &nodeAfter);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("RangeUtils::CompareNodeToRange() failed");
|
|
return rv;
|
|
}
|
|
if (nodeAfter) {
|
|
break;
|
|
}
|
|
if (!nodeBefore) {
|
|
nsresult rv =
|
|
MOZ_KnownLive(mHTMLEditor).DeleteNodeWithTransaction(*node);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
|
|
return rv;
|
|
}
|
|
mNodeArray.RemoveElement(node);
|
|
--count;
|
|
--idx;
|
|
}
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
template <typename PT, typename CT>
|
|
EditorDOMPointInText WSRunScanner::GetNextCharPoint(
|
|
const EditorDOMPointBase<PT, CT>& aPoint) const {
|
|
MOZ_ASSERT(aPoint.IsSetAndValid());
|
|
|
|
size_t index = aPoint.IsInTextNode()
|
|
? mNodeArray.IndexOf(aPoint.GetContainer())
|
|
: decltype(mNodeArray)::NoIndex;
|
|
if (index == decltype(mNodeArray)::NoIndex) {
|
|
// Use range comparisons to get next text node which is in mNodeArray.
|
|
return LookForNextCharPointWithinAllTextNodes(aPoint);
|
|
}
|
|
return GetNextCharPointFromPointInText(
|
|
EditorDOMPointInText(mNodeArray[index], aPoint.Offset()));
|
|
}
|
|
|
|
template <typename PT, typename CT>
|
|
EditorDOMPointInText WSRunScanner::GetPreviousCharPoint(
|
|
const EditorDOMPointBase<PT, CT>& aPoint) const {
|
|
MOZ_ASSERT(aPoint.IsSetAndValid());
|
|
|
|
size_t index = aPoint.IsInTextNode()
|
|
? mNodeArray.IndexOf(aPoint.GetContainer())
|
|
: decltype(mNodeArray)::NoIndex;
|
|
if (index == decltype(mNodeArray)::NoIndex) {
|
|
// Use range comparisons to get previous text node which is in mNodeArray.
|
|
return LookForPreviousCharPointWithinAllTextNodes(aPoint);
|
|
}
|
|
return GetPreviousCharPointFromPointInText(
|
|
EditorDOMPointInText(mNodeArray[index], aPoint.Offset()));
|
|
}
|
|
|
|
EditorDOMPointInText WSRunScanner::GetNextCharPointFromPointInText(
|
|
const EditorDOMPointInText& aPoint) const {
|
|
MOZ_ASSERT(aPoint.IsSet());
|
|
|
|
size_t index = mNodeArray.IndexOf(aPoint.GetContainer());
|
|
if (index == decltype(mNodeArray)::NoIndex) {
|
|
// Can't find point, but it's not an error
|
|
return EditorDOMPointInText();
|
|
}
|
|
|
|
if (aPoint.IsSetAndValid() && !aPoint.IsEndOfContainer()) {
|
|
// XXX This may return empty text node.
|
|
return aPoint;
|
|
}
|
|
|
|
if (index + 1 == mNodeArray.Length()) {
|
|
return EditorDOMPointInText();
|
|
}
|
|
|
|
// XXX This may return empty text node.
|
|
return EditorDOMPointInText(mNodeArray[index + 1], 0);
|
|
}
|
|
|
|
EditorDOMPointInText WSRunScanner::GetPreviousCharPointFromPointInText(
|
|
const EditorDOMPointInText& aPoint) const {
|
|
MOZ_ASSERT(aPoint.IsSet());
|
|
|
|
size_t index = mNodeArray.IndexOf(aPoint.GetContainer());
|
|
if (index == decltype(mNodeArray)::NoIndex) {
|
|
// Can't find point, but it's not an error
|
|
return EditorDOMPointInText();
|
|
}
|
|
|
|
if (!aPoint.IsStartOfContainer()) {
|
|
return aPoint.PreviousPoint();
|
|
}
|
|
|
|
if (!index) {
|
|
return EditorDOMPointInText();
|
|
}
|
|
|
|
// XXX This may return empty text node.
|
|
return EditorDOMPointInText(mNodeArray[index - 1],
|
|
mNodeArray[index - 1]->TextLength()
|
|
? mNodeArray[index - 1]->TextLength() - 1
|
|
: 0);
|
|
}
|
|
|
|
nsresult WSRunObject::InsertNBSPAndRemoveFollowingASCIIWhitespaces(
|
|
const EditorDOMPointInText& aPoint) {
|
|
// MOOSE: this routine needs to be modified to preserve the integrity of the
|
|
// wsFragment info.
|
|
if (NS_WARN_IF(!aPoint.IsSet())) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
|
|
// First, insert an NBSP.
|
|
AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
|
|
nsresult rv = MOZ_KnownLive(mHTMLEditor)
|
|
.InsertTextIntoTextNodeWithTransaction(
|
|
nsDependentSubstring(&kNBSP, 1), aPoint, true);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
|
|
return rv;
|
|
}
|
|
|
|
// Now, the text node may have been modified by mutation observer.
|
|
// So, the NBSP may have gone.
|
|
if (!aPoint.IsSetAndValid() || aPoint.IsEndOfContainer() ||
|
|
!aPoint.IsCharNBSP()) {
|
|
// This is just preparation of an edit action. Let's return NS_OK.
|
|
// XXX Perhaps, we should return another success code which indicates
|
|
// mutation observer touched the DOM tree. However, that should
|
|
// be returned from each transaction's DoTransaction.
|
|
return NS_OK;
|
|
}
|
|
|
|
// Next, find range of whitespaces it will be replaced.
|
|
EditorDOMPointInText start, end;
|
|
Tie(start, end) = GetASCIIWhitespacesBounds(eAfter, aPoint.NextPoint());
|
|
if (!start.IsSet()) {
|
|
return NS_OK;
|
|
}
|
|
NS_WARNING_ASSERTION(end.IsSet(),
|
|
"WSRunObject::GetASCIIWhitespacesBounds() didn't return "
|
|
"end point, but ignored");
|
|
|
|
// Finally, delete that replaced ws, if any
|
|
rv = DeleteRange(start, end);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WSRunObject::DeleteRange() failed");
|
|
return rv;
|
|
}
|
|
|
|
template <typename PT, typename CT>
|
|
Tuple<EditorDOMPointInText, EditorDOMPointInText>
|
|
WSRunObject::GetASCIIWhitespacesBounds(
|
|
int16_t aDir, const EditorDOMPointBase<PT, CT>& aPoint) const {
|
|
MOZ_ASSERT(aPoint.IsSet());
|
|
|
|
EditorDOMPointInText start, end;
|
|
|
|
if (aDir & eAfter) {
|
|
EditorDOMPointInText atNextChar = GetNextCharPoint(aPoint);
|
|
if (atNextChar.IsSet()) {
|
|
// We found a text node, at least.
|
|
start = end = atNextChar;
|
|
// Scan ahead to end of ASCII whitespaces.
|
|
// XXX Looks like that this is too expensive in most cases. While we
|
|
// are scanning a text node, we should do it without
|
|
// GetNextCharPointInText().
|
|
// XXX This loop ends at end of a text node. Shouldn't we keep looking
|
|
// next text node?
|
|
for (; atNextChar.IsSet() && !atNextChar.IsEndOfContainer() &&
|
|
atNextChar.IsCharASCIISpace();
|
|
atNextChar = GetNextCharPointFromPointInText(atNextChar)) {
|
|
// End of the range should be after the whitespace.
|
|
end = atNextChar = atNextChar.NextPoint();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (aDir & eBefore) {
|
|
EditorDOMPointInText atPreviousChar = GetPreviousCharPoint(aPoint);
|
|
if (atPreviousChar.IsSet()) {
|
|
// We found a text node, at least.
|
|
start = atPreviousChar.NextPoint();
|
|
if (!end.IsSet()) {
|
|
end = start;
|
|
}
|
|
// Scan back to start of ASCII whitespaces.
|
|
// XXX Looks like that this is too expensive in most cases. While we
|
|
// are scanning a text node, we should do it without
|
|
// GetPreviousCharPointFromPointInText().
|
|
// XXX This loop ends at end of a text node. Shouldn't we keep looking
|
|
// the text node?
|
|
for (; atPreviousChar.IsSet() && !atPreviousChar.IsEndOfContainer() &&
|
|
atPreviousChar.IsCharASCIISpace();
|
|
atPreviousChar =
|
|
GetPreviousCharPointFromPointInText(atPreviousChar)) {
|
|
start = atPreviousChar;
|
|
}
|
|
}
|
|
}
|
|
|
|
return MakeTuple(start, end);
|
|
}
|
|
|
|
template <typename PT, typename CT>
|
|
WSRunScanner::WSFragment* WSRunScanner::FindNearestRun(
|
|
const EditorDOMPointBase<PT, CT>& aPoint, bool aForward) const {
|
|
MOZ_ASSERT(aPoint.IsSetAndValid());
|
|
|
|
for (WSFragment* run = mStartRun; run; run = run->mRight) {
|
|
int32_t comp = run->mStartNode ? *nsContentUtils::ComparePoints(
|
|
aPoint.ToRawRangeBoundary(),
|
|
run->StartPoint().ToRawRangeBoundary())
|
|
: -1;
|
|
if (comp <= 0) {
|
|
// aPoint equals or before start of the run. Return the run if we're
|
|
// scanning forward, otherwise, nullptr.
|
|
return aForward ? run : nullptr;
|
|
}
|
|
|
|
comp = run->mEndNode ? *nsContentUtils::ComparePoints(
|
|
aPoint.ToRawRangeBoundary(),
|
|
run->EndPoint().ToRawRangeBoundary())
|
|
: -1;
|
|
if (comp < 0) {
|
|
// If aPoint is in the run, return the run.
|
|
return run;
|
|
}
|
|
|
|
if (!comp) {
|
|
// If aPoint is at end of the run, return next run if we're scanning
|
|
// forward, otherwise, return the run.
|
|
return aForward ? run->mRight : run;
|
|
}
|
|
|
|
if (!run->mRight) {
|
|
// If the run is the last run and aPoint is after end of the last run,
|
|
// return nullptr if we're scanning forward, otherwise, return this
|
|
// last run.
|
|
return aForward ? nullptr : run;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
char16_t WSRunScanner::GetCharAt(Text* aTextNode, int32_t aOffset) const {
|
|
// return 0 if we can't get a char, for whatever reason
|
|
if (NS_WARN_IF(!aTextNode) || NS_WARN_IF(aOffset < 0) ||
|
|
NS_WARN_IF(aOffset >=
|
|
static_cast<int32_t>(aTextNode->TextDataLength()))) {
|
|
return 0;
|
|
}
|
|
return aTextNode->TextFragment().CharAt(aOffset);
|
|
}
|
|
|
|
template <typename PT, typename CT>
|
|
EditorDOMPointInText WSRunScanner::LookForNextCharPointWithinAllTextNodes(
|
|
const EditorDOMPointBase<PT, CT>& aPoint) const {
|
|
// Note: only to be called if aPoint.GetContainer() is not a ws node.
|
|
|
|
MOZ_ASSERT(aPoint.IsSetAndValid());
|
|
|
|
// Binary search on wsnodes
|
|
uint32_t numNodes = mNodeArray.Length();
|
|
|
|
if (!numNodes) {
|
|
// Do nothing if there are no nodes to search
|
|
return EditorDOMPointInText();
|
|
}
|
|
|
|
// Begin binary search. We do this because we need to minimize calls to
|
|
// ComparePoints(), which is expensive.
|
|
uint32_t firstNum = 0, curNum = numNodes / 2, lastNum = numNodes;
|
|
while (curNum != lastNum) {
|
|
Text* curNode = mNodeArray[curNum];
|
|
int16_t cmp = *nsContentUtils::ComparePoints(aPoint.ToRawRangeBoundary(),
|
|
RawRangeBoundary(curNode, 0u));
|
|
if (cmp < 0) {
|
|
lastNum = curNum;
|
|
} else {
|
|
firstNum = curNum + 1;
|
|
}
|
|
curNum = (lastNum - firstNum) / 2 + firstNum;
|
|
MOZ_ASSERT(firstNum <= curNum && curNum <= lastNum, "Bad binary search");
|
|
}
|
|
|
|
// When the binary search is complete, we always know that the current node
|
|
// is the same as the end node, which is always past our range. Therefore,
|
|
// we've found the node immediately after the point of interest.
|
|
if (curNum == mNodeArray.Length()) {
|
|
// hey asked for past our range (it's after the last node).
|
|
// GetNextCharPoint() will do the work for us when we pass it the last
|
|
// index of the last node.
|
|
return GetNextCharPointFromPointInText(
|
|
EditorDOMPointInText::AtEndOf(mNodeArray[curNum - 1]));
|
|
}
|
|
|
|
// The char after the point is the first character of our range.
|
|
return GetNextCharPointFromPointInText(
|
|
EditorDOMPointInText(mNodeArray[curNum], 0));
|
|
}
|
|
|
|
template <typename PT, typename CT>
|
|
EditorDOMPointInText WSRunScanner::LookForPreviousCharPointWithinAllTextNodes(
|
|
const EditorDOMPointBase<PT, CT>& aPoint) const {
|
|
// Note: only to be called if aNode is not a ws node.
|
|
|
|
MOZ_ASSERT(aPoint.IsSetAndValid());
|
|
|
|
// Binary search on wsnodes
|
|
uint32_t numNodes = mNodeArray.Length();
|
|
|
|
if (!numNodes) {
|
|
// Do nothing if there are no nodes to search
|
|
return EditorDOMPointInText();
|
|
}
|
|
|
|
uint32_t firstNum = 0, curNum = numNodes / 2, lastNum = numNodes;
|
|
int16_t cmp = 0;
|
|
|
|
// Begin binary search. We do this because we need to minimize calls to
|
|
// ComparePoints(), which is expensive.
|
|
while (curNum != lastNum) {
|
|
Text* curNode = mNodeArray[curNum];
|
|
cmp = *nsContentUtils::ComparePoints(aPoint.ToRawRangeBoundary(),
|
|
RawRangeBoundary(curNode, 0u));
|
|
if (cmp < 0) {
|
|
lastNum = curNum;
|
|
} else {
|
|
firstNum = curNum + 1;
|
|
}
|
|
curNum = (lastNum - firstNum) / 2 + firstNum;
|
|
MOZ_ASSERT(firstNum <= curNum && curNum <= lastNum, "Bad binary search");
|
|
}
|
|
|
|
// When the binary search is complete, we always know that the current node
|
|
// is the same as the end node, which is always past our range. Therefore,
|
|
// we've found the node immediately after the point of interest.
|
|
if (curNum == mNodeArray.Length()) {
|
|
// Get the point before the end of the last node, we can pass the length of
|
|
// the node into GetPreviousCharPoint(), and it will return the last
|
|
// character.
|
|
return GetPreviousCharPointFromPointInText(
|
|
EditorDOMPointInText::AtEndOf(mNodeArray[curNum - 1]));
|
|
}
|
|
|
|
// We can just ask the current node for the point immediately before it,
|
|
// it will handle moving to the previous node (if any) and returning the
|
|
// appropriate character
|
|
return GetPreviousCharPointFromPointInText(
|
|
EditorDOMPointInText(mNodeArray[curNum], 0));
|
|
}
|
|
|
|
nsresult WSRunObject::CheckTrailingNBSPOfRun(WSFragment* aRun) {
|
|
if (NS_WARN_IF(!aRun)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
// Try to change an nbsp to a space, if possible, just to prevent nbsp
|
|
// proliferation. Examine what is before and after the trailing nbsp, if
|
|
// any.
|
|
bool leftCheck = false;
|
|
bool spaceNBSP = false;
|
|
bool rightCheck = false;
|
|
|
|
// Check if it's a visible fragment in a hard line.
|
|
if (!aRun->IsVisibleAndMiddleOfHardLine()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// first check for trailing nbsp
|
|
EditorDOMPointInText atPreviousCharOfEndOfRun =
|
|
GetPreviousCharPoint(aRun->EndPoint());
|
|
if (atPreviousCharOfEndOfRun.IsSet() &&
|
|
!atPreviousCharOfEndOfRun.IsEndOfContainer() &&
|
|
atPreviousCharOfEndOfRun.IsCharNBSP()) {
|
|
// now check that what is to the left of it is compatible with replacing
|
|
// nbsp with space
|
|
EditorDOMPointInText atPreviousCharOfPreviousCharOfEndOfRun =
|
|
GetPreviousCharPointFromPointInText(atPreviousCharOfEndOfRun);
|
|
if (atPreviousCharOfPreviousCharOfEndOfRun.IsSet()) {
|
|
if (atPreviousCharOfPreviousCharOfEndOfRun.IsEndOfContainer() ||
|
|
!atPreviousCharOfPreviousCharOfEndOfRun.IsCharASCIISpace()) {
|
|
leftCheck = true;
|
|
} else {
|
|
spaceNBSP = true;
|
|
}
|
|
} else if (aRun->StartsFromNormalText() ||
|
|
aRun->StartsFromSpecialContent()) {
|
|
leftCheck = true;
|
|
}
|
|
if (leftCheck || spaceNBSP) {
|
|
// now check that what is to the right of it is compatible with replacing
|
|
// nbsp with space
|
|
if (aRun->EndsByNormalText() || aRun->EndsBySpecialContent() ||
|
|
aRun->EndsByBRElement()) {
|
|
rightCheck = true;
|
|
}
|
|
if (aRun->EndsByBlockBoundary() && mScanStartPoint.IsInContentNode()) {
|
|
bool insertBRElement = HTMLEditUtils::IsBlockElement(
|
|
*mScanStartPoint.ContainerAsContent());
|
|
if (!insertBRElement) {
|
|
nsIContent* blockParentOrTopmostEditableInlineContent =
|
|
GetEditableBlockParentOrTopmotEditableInlineContent(
|
|
mScanStartPoint.ContainerAsContent());
|
|
insertBRElement = blockParentOrTopmostEditableInlineContent &&
|
|
HTMLEditUtils::IsBlockElement(
|
|
*blockParentOrTopmostEditableInlineContent);
|
|
}
|
|
if (insertBRElement) {
|
|
// We are at a block boundary. Insert a <br>. Why? Well, first note
|
|
// that the br will have no visible effect since it is up against a
|
|
// block boundary. |foo<br><p>bar| renders like |foo<p>bar| and
|
|
// similarly |<p>foo<br></p>bar| renders like |<p>foo</p>bar|. What
|
|
// this <br> addition gets us is the ability to convert a trailing
|
|
// nbsp to a space. Consider: |<body>foo. '</body>|, where '
|
|
// represents selection. User types space attempting to put 2 spaces
|
|
// after the end of their sentence. We used to do this as:
|
|
// |<body>foo.  </body>| This caused problems with soft wrapping:
|
|
// the nbsp would wrap to the next line, which looked attrocious. If
|
|
// you try to do: |<body>foo.  </body>| instead, the trailing
|
|
// space is invisible because it is against a block boundary. If you
|
|
// do:
|
|
// |<body>foo.  </body>| then you get an even uglier soft
|
|
// wrapping problem, where foo is on one line until you type the final
|
|
// space, and then "foo " jumps down to the next line. Ugh. The
|
|
// best way I can find out of this is to throw in a harmless <br>
|
|
// here, which allows us to do: |<body>foo.  <br></body>|, which
|
|
// doesn't cause foo to jump lines, doesn't cause spaces to show up at
|
|
// the beginning of soft wrapped lines, and lets the user see 2 spaces
|
|
// when they type 2 spaces.
|
|
|
|
RefPtr<Element> brElement =
|
|
MOZ_KnownLive(mHTMLEditor)
|
|
.InsertBRElementWithTransaction(aRun->EndPoint());
|
|
if (!brElement) {
|
|
NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
atPreviousCharOfEndOfRun = GetPreviousCharPoint(aRun->EndPoint());
|
|
atPreviousCharOfPreviousCharOfEndOfRun =
|
|
GetPreviousCharPointFromPointInText(atPreviousCharOfEndOfRun);
|
|
rightCheck = true;
|
|
}
|
|
}
|
|
}
|
|
if (leftCheck && rightCheck) {
|
|
// Now replace nbsp with space. First, insert a space
|
|
AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
|
|
nsresult rv =
|
|
MOZ_KnownLive(mHTMLEditor)
|
|
.InsertTextIntoTextNodeWithTransaction(
|
|
NS_LITERAL_STRING(" "), atPreviousCharOfEndOfRun, true);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING(
|
|
"EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
|
|
return rv;
|
|
}
|
|
|
|
// Finally, delete that nbsp
|
|
NS_ASSERTION(!atPreviousCharOfEndOfRun.IsEndOfContainer() &&
|
|
!atPreviousCharOfEndOfRun.IsAtLastContent(),
|
|
"The text node was modified by mutation event listener");
|
|
if (!atPreviousCharOfEndOfRun.IsEndOfContainer() &&
|
|
!atPreviousCharOfEndOfRun.IsAtLastContent()) {
|
|
NS_ASSERTION(atPreviousCharOfEndOfRun.IsNextCharNBSP(),
|
|
"Trying to remove an NBSP, but it's gone from the "
|
|
"expected position");
|
|
EditorDOMPointInText atNextCharOfPreviousCharOfEndOfRun =
|
|
atPreviousCharOfEndOfRun.NextPoint();
|
|
nsresult rv =
|
|
DeleteRange(atNextCharOfPreviousCharOfEndOfRun,
|
|
atNextCharOfPreviousCharOfEndOfRun.NextPoint());
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::DeleteRange() failed");
|
|
return rv;
|
|
}
|
|
}
|
|
} else if (!mPRE && spaceNBSP && rightCheck) {
|
|
// Don't mess with this preformatted for now. We have a run of ASCII
|
|
// whitespace (which will render as one space) followed by an nbsp (which
|
|
// is at the end of the whitespace run). Let's switch their order. This
|
|
// will ensure that if someone types two spaces after a sentence, and the
|
|
// editor softwraps at this point, the spaces won't be split across lines,
|
|
// which looks ugly and is bad for the moose.
|
|
MOZ_ASSERT(!atPreviousCharOfPreviousCharOfEndOfRun.IsEndOfContainer());
|
|
EditorDOMPointInText start, end;
|
|
// XXX end won't be used, whey `eBoth`?
|
|
Tie(start, end) = GetASCIIWhitespacesBounds(
|
|
eBoth, atPreviousCharOfPreviousCharOfEndOfRun.NextPoint());
|
|
NS_WARNING_ASSERTION(
|
|
start.IsSet(),
|
|
"WSRunObject::GetASCIIWhitespacesBounds() didn't return start point");
|
|
NS_WARNING_ASSERTION(end.IsSet(),
|
|
"WSRunObject::GetASCIIWhitespacesBounds() didn't "
|
|
"return end point, but ignored");
|
|
|
|
// Delete that nbsp
|
|
NS_ASSERTION(!atPreviousCharOfEndOfRun.IsEndOfContainer(),
|
|
"The text node was modified by mutation event listener");
|
|
if (!atPreviousCharOfEndOfRun.IsEndOfContainer()) {
|
|
NS_ASSERTION(atPreviousCharOfEndOfRun.IsCharNBSP(),
|
|
"Trying to remove an NBSP, but it's gone from the "
|
|
"expected position");
|
|
nsresult rv = DeleteRange(atPreviousCharOfEndOfRun,
|
|
atPreviousCharOfEndOfRun.NextPoint());
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::DeleteRange() failed");
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// Finally, insert that nbsp before the ASCII ws run
|
|
NS_ASSERTION(start.IsSetAndValid(),
|
|
"The text node was modified by mutation event listener");
|
|
if (start.IsSetAndValid()) {
|
|
AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
|
|
nsresult rv = MOZ_KnownLive(mHTMLEditor)
|
|
.InsertTextIntoTextNodeWithTransaction(
|
|
nsDependentSubstring(&kNBSP, 1), start, true);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING(
|
|
"EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult WSRunObject::ReplacePreviousNBSPIfUnnecessary(
|
|
WSFragment* aRun, const EditorDOMPoint& aPoint) {
|
|
if (NS_WARN_IF(!aRun) || NS_WARN_IF(!aPoint.IsSet())) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
MOZ_ASSERT(aPoint.IsSetAndValid());
|
|
|
|
// Try to change an NBSP to a space, if possible, just to prevent NBSP
|
|
// proliferation. This routine is called when we are about to make this
|
|
// point in the ws abut an inserted break or text, so we don't have to worry
|
|
// about what is after it. What is after it now will end up after the
|
|
// inserted object.
|
|
bool canConvert = false;
|
|
EditorDOMPointInText atPreviousChar = GetPreviousCharPoint(aPoint);
|
|
if (atPreviousChar.IsSet() && !atPreviousChar.IsEndOfContainer() &&
|
|
atPreviousChar.IsCharNBSP()) {
|
|
EditorDOMPointInText atPreviousCharOfPreviousChar =
|
|
GetPreviousCharPointFromPointInText(atPreviousChar);
|
|
if (atPreviousCharOfPreviousChar.IsSet()) {
|
|
if (atPreviousCharOfPreviousChar.IsEndOfContainer() ||
|
|
!atPreviousCharOfPreviousChar.IsCharASCIISpace()) {
|
|
// If previous character is a NBSP and its previous character isn't
|
|
// ASCII space, we can replace the NBSP with ASCII space.
|
|
canConvert = true;
|
|
}
|
|
} else if (aRun->StartsFromNormalText() ||
|
|
aRun->StartsFromSpecialContent()) {
|
|
// If previous character is a NBSP and it's the first character of the
|
|
// text node, additionally, if its previous node is a text node including
|
|
// non-whitespace characters or <img> node or something inline
|
|
// non-container element node, we can replace the NBSP with ASCII space.
|
|
canConvert = true;
|
|
}
|
|
}
|
|
|
|
if (!canConvert) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// First, insert a space before the previous NBSP.
|
|
AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
|
|
nsresult rv = MOZ_KnownLive(mHTMLEditor)
|
|
.InsertTextIntoTextNodeWithTransaction(
|
|
NS_LITERAL_STRING(" "), atPreviousChar, true);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
|
|
return rv;
|
|
}
|
|
|
|
// Finally, delete the previous NBSP.
|
|
NS_ASSERTION(
|
|
!atPreviousChar.IsEndOfContainer() && !atPreviousChar.IsAtLastContent(),
|
|
"The text node was modified by mutation event listener");
|
|
if (!atPreviousChar.IsEndOfContainer() && !atPreviousChar.IsAtLastContent()) {
|
|
NS_ASSERTION(
|
|
atPreviousChar.IsNextCharNBSP(),
|
|
"Trying to remove an NBSP, but it's gone from the expected position");
|
|
EditorDOMPointInText atNextCharOfPreviousChar = atPreviousChar.NextPoint();
|
|
nsresult rv = DeleteRange(atNextCharOfPreviousChar,
|
|
atNextCharOfPreviousChar.NextPoint());
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WSRunObject::DeleteRange() failed");
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult WSRunObject::CheckLeadingNBSP(WSFragment* aRun, nsINode* aNode,
|
|
int32_t aOffset) {
|
|
// Try to change an nbsp to a space, if possible, just to prevent nbsp
|
|
// proliferation This routine is called when we are about to make this point
|
|
// in the ws abut an inserted text, so we don't have to worry about what is
|
|
// before it. What is before it now will end up before the inserted text.
|
|
bool canConvert = false;
|
|
EditorDOMPointInText atNextChar =
|
|
GetNextCharPoint(EditorRawDOMPoint(aNode, aOffset));
|
|
if (!atNextChar.IsSet() || NS_WARN_IF(atNextChar.IsEndOfContainer())) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (atNextChar.IsCharNBSP()) {
|
|
EditorDOMPointInText atNextCharOfNextCharOfNBSP =
|
|
GetNextCharPointFromPointInText(atNextChar.NextPoint());
|
|
if (atNextCharOfNextCharOfNBSP.IsSet()) {
|
|
if (atNextCharOfNextCharOfNBSP.IsEndOfContainer() ||
|
|
!atNextCharOfNextCharOfNBSP.IsCharASCIISpace()) {
|
|
canConvert = true;
|
|
}
|
|
} else if (aRun->EndsByNormalText() || aRun->EndsBySpecialContent() ||
|
|
aRun->EndsByBRElement()) {
|
|
canConvert = true;
|
|
}
|
|
}
|
|
if (canConvert) {
|
|
// First, insert a space
|
|
AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
|
|
nsresult rv = MOZ_KnownLive(mHTMLEditor)
|
|
.InsertTextIntoTextNodeWithTransaction(
|
|
NS_LITERAL_STRING(" "), atNextChar, true);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
|
|
return rv;
|
|
}
|
|
|
|
// Finally, delete that nbsp
|
|
NS_ASSERTION(
|
|
!atNextChar.IsEndOfContainer() && !atNextChar.IsAtLastContent(),
|
|
"The text node was modified by mutation event listener");
|
|
if (!atNextChar.IsEndOfContainer() && !atNextChar.IsAtLastContent()) {
|
|
NS_ASSERTION(
|
|
atNextChar.IsNextCharNBSP(),
|
|
"Trying to remove an NBSP, but it's gone from the expected position");
|
|
EditorDOMPointInText atNextCharOfNextChar = atNextChar.NextPoint();
|
|
rv = DeleteRange(atNextCharOfNextChar, atNextCharOfNextChar.NextPoint());
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"WSRunObject::DeleteRange() failed");
|
|
return rv;
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult WSRunObject::Scrub() {
|
|
for (WSFragment* run = mStartRun; run; run = run->mRight) {
|
|
if (run->IsMiddleOfHardLine()) {
|
|
continue;
|
|
}
|
|
nsresult rv = DeleteRange(run->StartPoint(), run->EndPoint());
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("WSRunObject::DeleteRange() failed");
|
|
return rv;
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace mozilla
|