1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927 |
- /* -*- 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 "TextEditUtils.h"
- #include "mozilla/Assertions.h"
- #include "mozilla/Casting.h"
- #include "mozilla/EditorUtils.h"
- #include "mozilla/HTMLEditor.h"
- #include "mozilla/mozalloc.h"
- #include "mozilla/OwningNonNull.h"
- #include "mozilla/SelectionState.h"
- #include "nsAString.h"
- #include "nsCRT.h"
- #include "nsContentUtils.h"
- #include "nsDebug.h"
- #include "nsError.h"
- #include "nsIContent.h"
- #include "nsIDOMDocument.h"
- #include "nsIDOMNode.h"
- #include "nsISupportsImpl.h"
- #include "nsRange.h"
- #include "nsString.h"
- #include "nsTextFragment.h"
- namespace mozilla {
- using namespace dom;
- const char16_t nbsp = 160;
- WSRunObject::WSRunObject(HTMLEditor* aHTMLEditor,
- nsINode* aNode,
- int32_t aOffset)
- : mNode(aNode)
- , mOffset(aOffset)
- , mPRE(false)
- , mStartOffset(0)
- , mEndOffset(0)
- , mFirstNBSPOffset(0)
- , mLastNBSPOffset(0)
- , mStartRun(nullptr)
- , mEndRun(nullptr)
- , mHTMLEditor(aHTMLEditor)
- {
- GetWSNodes();
- GetRuns();
- }
- WSRunObject::WSRunObject(HTMLEditor* aHTMLEditor,
- nsIDOMNode* aNode,
- int32_t aOffset)
- : mNode(do_QueryInterface(aNode))
- , mOffset(aOffset)
- , mPRE(false)
- , mStartOffset(0)
- , mEndOffset(0)
- , mFirstNBSPOffset(0)
- , mLastNBSPOffset(0)
- , mStartRun(nullptr)
- , mEndRun(nullptr)
- , mHTMLEditor(aHTMLEditor)
- {
- GetWSNodes();
- GetRuns();
- }
- WSRunObject::~WSRunObject()
- {
- ClearRuns();
- }
- nsresult
- WSRunObject::ScrubBlockBoundary(HTMLEditor* aHTMLEditor,
- BlockBoundary aBoundary,
- nsINode* aBlock,
- int32_t aOffset)
- {
- NS_ENSURE_TRUE(aHTMLEditor && aBlock, NS_ERROR_NULL_POINTER);
- int32_t offset;
- if (aBoundary == kBlockStart) {
- offset = 0;
- } else if (aBoundary == kBlockEnd) {
- offset = aBlock->Length();
- } else {
- // Else we are scrubbing an outer boundary - just before or after a block
- // element.
- NS_ENSURE_STATE(aOffset >= 0);
- offset = aOffset;
- }
- WSRunObject theWSObj(aHTMLEditor, aBlock, offset);
- return theWSObj.Scrub();
- }
- nsresult
- WSRunObject::PrepareToJoinBlocks(HTMLEditor* aHTMLEditor,
- Element* aLeftBlock,
- Element* aRightBlock)
- {
- NS_ENSURE_TRUE(aLeftBlock && aRightBlock && aHTMLEditor,
- NS_ERROR_NULL_POINTER);
- WSRunObject leftWSObj(aHTMLEditor, aLeftBlock, aLeftBlock->Length());
- WSRunObject rightWSObj(aHTMLEditor, aRightBlock, 0);
- return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
- }
- nsresult
- WSRunObject::PrepareToDeleteRange(HTMLEditor* aHTMLEditor,
- nsCOMPtr<nsINode>* aStartNode,
- int32_t* aStartOffset,
- nsCOMPtr<nsINode>* aEndNode,
- int32_t* aEndOffset)
- {
- NS_ENSURE_TRUE(aHTMLEditor && aStartNode && *aStartNode && aStartOffset &&
- aEndNode && *aEndNode && aEndOffset, NS_ERROR_NULL_POINTER);
- AutoTrackDOMPoint trackerStart(aHTMLEditor->mRangeUpdater,
- aStartNode, aStartOffset);
- AutoTrackDOMPoint trackerEnd(aHTMLEditor->mRangeUpdater,
- aEndNode, aEndOffset);
- WSRunObject leftWSObj(aHTMLEditor, *aStartNode, *aStartOffset);
- WSRunObject rightWSObj(aHTMLEditor, *aEndNode, *aEndOffset);
- return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
- }
- nsresult
- WSRunObject::PrepareToDeleteNode(HTMLEditor* aHTMLEditor,
- nsIContent* aContent)
- {
- NS_ENSURE_TRUE(aContent && aHTMLEditor, NS_ERROR_NULL_POINTER);
- nsCOMPtr<nsINode> parent = aContent->GetParentNode();
- NS_ENSURE_STATE(parent);
- int32_t offset = parent->IndexOf(aContent);
- WSRunObject leftWSObj(aHTMLEditor, parent, offset);
- WSRunObject rightWSObj(aHTMLEditor, parent, offset + 1);
- return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
- }
- nsresult
- WSRunObject::PrepareToSplitAcrossBlocks(HTMLEditor* aHTMLEditor,
- nsCOMPtr<nsINode>* aSplitNode,
- int32_t* aSplitOffset)
- {
- NS_ENSURE_TRUE(aHTMLEditor && aSplitNode && *aSplitNode && aSplitOffset,
- NS_ERROR_NULL_POINTER);
- AutoTrackDOMPoint tracker(aHTMLEditor->mRangeUpdater,
- aSplitNode, aSplitOffset);
- WSRunObject wsObj(aHTMLEditor, *aSplitNode, *aSplitOffset);
- return wsObj.PrepareToSplitAcrossBlocksPriv();
- }
- already_AddRefed<Element>
- WSRunObject::InsertBreak(nsCOMPtr<nsINode>* aInOutParent,
- int32_t* aInOutOffset,
- nsIEditor::EDirection aSelect)
- {
- // MOOSE: for now, we always assume non-PRE formatting. Fix this later.
- // meanwhile, the pre case is handled in WillInsertText in
- // HTMLEditRules.cpp
- NS_ENSURE_TRUE(aInOutParent && aInOutOffset, nullptr);
- WSFragment *beforeRun, *afterRun;
- FindRun(*aInOutParent, *aInOutOffset, &beforeRun, false);
- FindRun(*aInOutParent, *aInOutOffset, &afterRun, true);
- {
- // Some scoping for AutoTrackDOMPoint. This will track our insertion
- // point while we tweak any surrounding whitespace
- AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, aInOutParent,
- aInOutOffset);
- // Handle any changes needed to ws run after inserted br
- if (!afterRun || (afterRun->mType & WSType::trailingWS)) {
- // Don't need to do anything. Just insert break. ws won't change.
- } else if (afterRun->mType & WSType::leadingWS) {
- // 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 = DeleteChars(*aInOutParent, *aInOutOffset,
- afterRun->mEndNode, afterRun->mEndOffset,
- eOutsideUserSelectAll);
- NS_ENSURE_SUCCESS(rv, nullptr);
- } else if (afterRun->mType == WSType::normalWS) {
- // Need to determine if break at front of non-nbsp run. If so, convert
- // run to nbsp.
- WSPoint thePoint = GetCharAfter(*aInOutParent, *aInOutOffset);
- if (thePoint.mTextNode && nsCRT::IsAsciiSpace(thePoint.mChar)) {
- WSPoint prevPoint = GetCharBefore(thePoint);
- if (prevPoint.mTextNode && !nsCRT::IsAsciiSpace(prevPoint.mChar)) {
- // We are at start of non-nbsps. Convert to a single nbsp.
- nsresult rv = ConvertToNBSP(thePoint);
- NS_ENSURE_SUCCESS(rv, nullptr);
- }
- }
- }
- // Handle any changes needed to ws run before inserted br
- if (!beforeRun || (beforeRun->mType & WSType::leadingWS)) {
- // Don't need to do anything. Just insert break. ws won't change.
- } else if (beforeRun->mType & WSType::trailingWS) {
- // Need to delete the trailing ws that is before insertion point, because it
- // would become significant after break inserted.
- nsresult rv = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset,
- *aInOutParent, *aInOutOffset,
- eOutsideUserSelectAll);
- NS_ENSURE_SUCCESS(rv, nullptr);
- } else if (beforeRun->mType == WSType::normalWS) {
- // Try to change an nbsp to a space, just to prevent nbsp proliferation
- nsresult rv = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
- NS_ENSURE_SUCCESS(rv, nullptr);
- }
- }
- // ready, aim, fire!
- return mHTMLEditor->CreateBRImpl(aInOutParent, aInOutOffset, aSelect);
- }
- nsresult
- WSRunObject::InsertText(const nsAString& aStringToInsert,
- nsCOMPtr<nsINode>* aInOutParent,
- int32_t* aInOutOffset,
- nsIDocument* aDoc)
- {
- // MOOSE: for now, we always assume non-PRE formatting. Fix this later.
- // meanwhile, the pre case is handled in WillInsertText in
- // HTMLEditRules.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
- NS_ENSURE_TRUE(aInOutParent && aInOutOffset && aDoc, NS_ERROR_NULL_POINTER);
- if (aStringToInsert.IsEmpty()) {
- return NS_OK;
- }
- nsAutoString theString(aStringToInsert);
- WSFragment *beforeRun, *afterRun;
- FindRun(*aInOutParent, *aInOutOffset, &beforeRun, false);
- FindRun(*aInOutParent, *aInOutOffset, &afterRun, true);
- {
- // Some scoping for AutoTrackDOMPoint. This will track our insertion
- // point while we tweak any surrounding whitespace
- AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, aInOutParent,
- aInOutOffset);
- // Handle any changes needed to ws run after inserted text
- if (!afterRun || afterRun->mType & WSType::trailingWS) {
- // Don't need to do anything. Just insert text. ws won't change.
- } else if (afterRun->mType & WSType::leadingWS) {
- // Delete the leading ws that is after insertion point, because it
- // would become significant after text inserted.
- nsresult rv =
- DeleteChars(*aInOutParent, *aInOutOffset, afterRun->mEndNode,
- afterRun->mEndOffset, eOutsideUserSelectAll);
- NS_ENSURE_SUCCESS(rv, rv);
- } else if (afterRun->mType == WSType::normalWS) {
- // Try to change an nbsp to a space, if possible, just to prevent nbsp
- // proliferation
- nsresult rv = CheckLeadingNBSP(afterRun, *aInOutParent, *aInOutOffset);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- // Handle any changes needed to ws run before inserted text
- if (!beforeRun || beforeRun->mType & WSType::leadingWS) {
- // Don't need to do anything. Just insert text. ws won't change.
- } else if (beforeRun->mType & WSType::trailingWS) {
- // Need to delete the trailing ws that is before insertion point, because
- // it would become significant after text inserted.
- nsresult rv =
- DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset,
- *aInOutParent, *aInOutOffset, eOutsideUserSelectAll);
- NS_ENSURE_SUCCESS(rv, rv);
- } else if (beforeRun->mType == WSType::normalWS) {
- // Try to change an nbsp to a space, if possible, just to prevent nbsp
- // proliferation
- nsresult rv = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- }
- // 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->mType & WSType::leadingWS) {
- theString.SetCharAt(nbsp, 0);
- } else if (beforeRun->mType & WSType::normalWS) {
- WSPoint wspoint = GetCharBefore(*aInOutParent, *aInOutOffset);
- if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) {
- theString.SetCharAt(nbsp, 0);
- }
- }
- } else if (mStartReason & WSType::block || mStartReason == WSType::br) {
- theString.SetCharAt(nbsp, 0);
- }
- }
- // Then the tail
- uint32_t lastCharIndex = theString.Length() - 1;
- if (nsCRT::IsAsciiSpace(theString[lastCharIndex])) {
- // We have a leading space
- if (afterRun) {
- if (afterRun->mType & WSType::trailingWS) {
- theString.SetCharAt(nbsp, lastCharIndex);
- } else if (afterRun->mType & WSType::normalWS) {
- WSPoint wspoint = GetCharAfter(*aInOutParent, *aInOutOffset);
- if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) {
- theString.SetCharAt(nbsp, lastCharIndex);
- }
- }
- } else if (mEndReason & WSType::block) {
- theString.SetCharAt(nbsp, 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(nbsp, i - 1);
- } else {
- prevWS = true;
- }
- } else {
- prevWS = false;
- }
- }
- // Ready, aim, fire!
- mHTMLEditor->InsertTextImpl(theString, aInOutParent, aInOutOffset, aDoc);
- return NS_OK;
- }
- nsresult
- WSRunObject::DeleteWSBackward()
- {
- WSPoint point = GetCharBefore(mNode, mOffset);
- NS_ENSURE_TRUE(point.mTextNode, NS_OK); // nothing to delete
- // Easy case, preformatted ws.
- if (mPRE && (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp)) {
- return DeleteChars(point.mTextNode, point.mOffset,
- point.mTextNode, point.mOffset + 1);
- }
- // Caller's job to ensure that previous char is really ws. If it is normal
- // ws, we need to delete the whole run.
- if (nsCRT::IsAsciiSpace(point.mChar)) {
- RefPtr<Text> startNodeText, endNodeText;
- int32_t startOffset, endOffset;
- GetAsciiWSBounds(eBoth, point.mTextNode, point.mOffset + 1,
- getter_AddRefs(startNodeText), &startOffset,
- getter_AddRefs(endNodeText), &endOffset);
- // adjust surrounding ws
- nsCOMPtr<nsINode> startNode = startNodeText.get();
- nsCOMPtr<nsINode> endNode = endNodeText.get();
- nsresult rv =
- WSRunObject::PrepareToDeleteRange(mHTMLEditor,
- address_of(startNode), &startOffset,
- address_of(endNode), &endOffset);
- NS_ENSURE_SUCCESS(rv, rv);
- // finally, delete that ws
- return DeleteChars(startNode, startOffset, endNode, endOffset);
- }
- if (point.mChar == nbsp) {
- nsCOMPtr<nsINode> node(point.mTextNode);
- // adjust surrounding ws
- int32_t startOffset = point.mOffset;
- int32_t endOffset = point.mOffset + 1;
- nsresult rv =
- WSRunObject::PrepareToDeleteRange(mHTMLEditor,
- address_of(node), &startOffset,
- address_of(node), &endOffset);
- NS_ENSURE_SUCCESS(rv, rv);
- // finally, delete that ws
- return DeleteChars(node, startOffset, node, endOffset);
- }
- return NS_OK;
- }
- nsresult
- WSRunObject::DeleteWSForward()
- {
- WSPoint point = GetCharAfter(mNode, mOffset);
- NS_ENSURE_TRUE(point.mTextNode, NS_OK); // nothing to delete
- // Easy case, preformatted ws.
- if (mPRE && (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp)) {
- return DeleteChars(point.mTextNode, point.mOffset,
- point.mTextNode, point.mOffset + 1);
- }
- // Caller's job to ensure that next char is really ws. If it is normal ws,
- // we need to delete the whole run.
- if (nsCRT::IsAsciiSpace(point.mChar)) {
- RefPtr<Text> startNodeText, endNodeText;
- int32_t startOffset, endOffset;
- GetAsciiWSBounds(eBoth, point.mTextNode, point.mOffset + 1,
- getter_AddRefs(startNodeText), &startOffset,
- getter_AddRefs(endNodeText), &endOffset);
- // Adjust surrounding ws
- nsCOMPtr<nsINode> startNode(startNodeText), endNode(endNodeText);
- nsresult rv =
- WSRunObject::PrepareToDeleteRange(mHTMLEditor,
- address_of(startNode), &startOffset,
- address_of(endNode), &endOffset);
- NS_ENSURE_SUCCESS(rv, rv);
- // Finally, delete that ws
- return DeleteChars(startNode, startOffset, endNode, endOffset);
- }
- if (point.mChar == nbsp) {
- nsCOMPtr<nsINode> node(point.mTextNode);
- // Adjust surrounding ws
- int32_t startOffset = point.mOffset;
- int32_t endOffset = point.mOffset+1;
- nsresult rv =
- WSRunObject::PrepareToDeleteRange(mHTMLEditor,
- address_of(node), &startOffset,
- address_of(node), &endOffset);
- NS_ENSURE_SUCCESS(rv, rv);
- // Finally, delete that ws
- return DeleteChars(node, startOffset, node, endOffset);
- }
- return NS_OK;
- }
- void
- WSRunObject::PriorVisibleNode(nsINode* aNode,
- int32_t aOffset,
- nsCOMPtr<nsINode>* outVisNode,
- int32_t* outVisOffset,
- WSType* outType)
- {
- // 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(aNode && outVisNode && outVisOffset && outType);
- WSFragment* run;
- FindRun(aNode, aOffset, &run, false);
- // Is there a visible run there or earlier?
- for (; run; run = run->mLeft) {
- if (run->mType == WSType::normalWS) {
- WSPoint point = GetCharBefore(aNode, aOffset);
- // When it's a non-empty text node, return it.
- if (point.mTextNode && point.mTextNode->Length()) {
- *outVisNode = point.mTextNode;
- *outVisOffset = point.mOffset + 1;
- if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp) {
- *outType = WSType::normalWS;
- } else {
- *outType = WSType::text;
- }
- return;
- }
- // If no text node, keep looking. We should eventually fall out of loop
- }
- }
- // If we get here, then nothing in ws data to find. Return start reason.
- *outVisNode = mStartReasonNode;
- // This really isn't meaningful if mStartReasonNode != mStartNode
- *outVisOffset = mStartOffset;
- *outType = mStartReason;
- }
- void
- WSRunObject::NextVisibleNode(nsINode* aNode,
- int32_t aOffset,
- nsCOMPtr<nsINode>* outVisNode,
- int32_t* outVisOffset,
- WSType* outType)
- {
- // 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(aNode && outVisNode && outVisOffset && outType);
- WSFragment* run;
- FindRun(aNode, aOffset, &run, true);
- // Is there a visible run there or later?
- for (; run; run = run->mRight) {
- if (run->mType == WSType::normalWS) {
- WSPoint point = GetCharAfter(aNode, aOffset);
- // When it's a non-empty text node, return it.
- if (point.mTextNode && point.mTextNode->Length()) {
- *outVisNode = point.mTextNode;
- *outVisOffset = point.mOffset;
- if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp) {
- *outType = WSType::normalWS;
- } else {
- *outType = WSType::text;
- }
- return;
- }
- // If no text node, keep looking. We should eventually fall out of loop
- }
- }
- // If we get here, then nothing in ws data to find. Return end reason
- *outVisNode = mEndReasonNode;
- // This really isn't meaningful if mEndReasonNode != mEndNode
- *outVisOffset = mEndOffset;
- *outType = 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;
- }
- WSFragment *curRun = mStartRun;
- while (curRun) {
- // look for normal ws run
- if (curRun->mType == WSType::normalWS) {
- nsresult rv = CheckTrailingNBSPOfRun(curRun);
- if (NS_FAILED(rv)) {
- return rv;
- }
- }
- curRun = curRun->mRight;
- }
- return NS_OK;
- }
- //--------------------------------------------------------------------------------------------
- // protected methods
- //--------------------------------------------------------------------------------------------
- nsINode*
- WSRunObject::GetWSBoundingParent()
- {
- NS_ENSURE_TRUE(mNode, nullptr);
- OwningNonNull<nsINode> wsBoundingParent = *mNode;
- while (!IsBlockNode(wsBoundingParent)) {
- nsCOMPtr<nsINode> parent = wsBoundingParent->GetParentNode();
- if (!parent || !mHTMLEditor->IsEditable(parent)) {
- break;
- }
- wsBoundingParent = parent;
- }
- return wsBoundingParent;
- }
- nsresult
- WSRunObject::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(mNode, mOffset), end(mNode, mOffset);
- nsCOMPtr<nsINode> wsBoundingParent = GetWSBoundingParent();
- // first look backwards to find preceding ws nodes
- if (RefPtr<Text> textNode = mNode->GetAsText()) {
- const nsTextFragment* textFrag = textNode->GetText();
- mNodeArray.InsertElementAt(0, textNode);
- if (mOffset) {
- for (int32_t pos = mOffset - 1; pos >= 0; pos--) {
- // sanity bounds check the char position. bug 136165
- if (uint32_t(pos) >= textFrag->GetLength()) {
- NS_NOTREACHED("looking beyond end of text fragment");
- continue;
- }
- char16_t theChar = textFrag->CharAt(pos);
- if (!nsCRT::IsAsciiSpace(theChar)) {
- if (theChar != nbsp) {
- mStartNode = textNode;
- mStartOffset = pos + 1;
- mStartReason = WSType::text;
- mStartReasonNode = 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.node = textNode;
- start.offset = pos;
- }
- }
- }
- while (!mStartNode) {
- // we haven't found the start of ws yet. Keep looking
- nsCOMPtr<nsIContent> priorNode = GetPreviousWSNode(start, wsBoundingParent);
- if (priorNode) {
- if (IsBlockNode(priorNode)) {
- mStartNode = start.node;
- mStartOffset = start.offset;
- mStartReason = WSType::otherBlock;
- mStartReasonNode = priorNode;
- } else if (RefPtr<Text> textNode = priorNode->GetAsText()) {
- mNodeArray.InsertElementAt(0, textNode);
- const nsTextFragment *textFrag;
- if (!textNode || !(textFrag = textNode->GetText())) {
- return NS_ERROR_NULL_POINTER;
- }
- uint32_t len = textNode->TextLength();
- if (len < 1) {
- // Zero length text node. Set start point to it
- // so we can get past it!
- start.SetPoint(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()) {
- NS_NOTREACHED("looking beyond end of text fragment");
- continue;
- }
- char16_t theChar = textFrag->CharAt(pos);
- if (!nsCRT::IsAsciiSpace(theChar)) {
- if (theChar != nbsp) {
- mStartNode = textNode;
- mStartOffset = pos + 1;
- mStartReason = WSType::text;
- mStartReasonNode = 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.SetPoint(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.node;
- mStartOffset = start.offset;
- if (TextEditUtils::IsBreak(priorNode)) {
- mStartReason = WSType::br;
- } else {
- mStartReason = WSType::special;
- }
- mStartReasonNode = priorNode;
- }
- } else {
- // no prior node means we exhausted wsBoundingParent
- mStartNode = start.node;
- mStartOffset = start.offset;
- mStartReason = WSType::thisBlock;
- mStartReasonNode = wsBoundingParent;
- }
- }
- // then look ahead to find following ws nodes
- if (RefPtr<Text> textNode = mNode->GetAsText()) {
- // don't need to put it on list. it already is from code above
- const nsTextFragment *textFrag = textNode->GetText();
- uint32_t len = textNode->TextLength();
- if (uint16_t(mOffset)<len) {
- for (uint32_t pos = mOffset; pos < len; pos++) {
- // sanity bounds check the char position. bug 136165
- if (pos >= textFrag->GetLength()) {
- NS_NOTREACHED("looking beyond end of text fragment");
- continue;
- }
- char16_t theChar = textFrag->CharAt(pos);
- if (!nsCRT::IsAsciiSpace(theChar)) {
- if (theChar != nbsp) {
- mEndNode = textNode;
- mEndOffset = pos;
- mEndReason = WSType::text;
- mEndReasonNode = 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.SetPoint(textNode, pos + 1);
- }
- }
- }
- while (!mEndNode) {
- // we haven't found the end of ws yet. Keep looking
- nsCOMPtr<nsIContent> nextNode = GetNextWSNode(end, wsBoundingParent);
- if (nextNode) {
- if (IsBlockNode(nextNode)) {
- // we encountered a new block. therefore no more ws.
- mEndNode = end.node;
- mEndOffset = end.offset;
- mEndReason = WSType::otherBlock;
- mEndReasonNode = nextNode;
- } else if (RefPtr<Text> textNode = nextNode->GetAsText()) {
- mNodeArray.AppendElement(textNode);
- const nsTextFragment *textFrag;
- if (!textNode || !(textFrag = textNode->GetText())) {
- return NS_ERROR_NULL_POINTER;
- }
- uint32_t len = textNode->TextLength();
- if (len < 1) {
- // Zero length text node. Set end point to it
- // so we can get past it!
- end.SetPoint(textNode, 0);
- } else {
- for (uint32_t pos = 0; pos < len; pos++) {
- // sanity bounds check the char position. bug 136165
- if (pos >= textFrag->GetLength()) {
- NS_NOTREACHED("looking beyond end of text fragment");
- continue;
- }
- char16_t theChar = textFrag->CharAt(pos);
- if (!nsCRT::IsAsciiSpace(theChar)) {
- if (theChar != nbsp) {
- mEndNode = textNode;
- mEndOffset = pos;
- mEndReason = WSType::text;
- mEndReasonNode = 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.SetPoint(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.node;
- mEndOffset = end.offset;
- if (TextEditUtils::IsBreak(nextNode)) {
- mEndReason = WSType::br;
- } else {
- mEndReason = WSType::special;
- }
- mEndReasonNode = nextNode;
- }
- } else {
- // no next node means we exhausted wsBoundingParent
- mEndNode = end.node;
- mEndOffset = end.offset;
- mEndReason = WSType::thisBlock;
- mEndReasonNode = wsBoundingParent;
- }
- }
- return NS_OK;
- }
- void
- WSRunObject::GetRuns()
- {
- ClearRuns();
- // handle some easy cases first
- mHTMLEditor->IsPreformatted(GetAsDOMNode(mNode), &mPRE);
- // if it's preformatedd, or if we are surrounded by text or special, it's all one
- // big normal ws run
- if (mPRE ||
- ((mStartReason == WSType::text || mStartReason == WSType::special) &&
- (mEndReason == WSType::text || mEndReason == WSType::special ||
- mEndReason == WSType::br))) {
- MakeSingleWSRun(WSType::normalWS);
- 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 &&
- ((mStartReason & WSType::block) || mStartReason == WSType::br ||
- (mEndReason & WSType::block))) {
- WSType wstype;
- if ((mStartReason & WSType::block) || mStartReason == WSType::br) {
- wstype = WSType::leadingWS;
- }
- if (mEndReason & WSType::block) {
- wstype |= WSType::trailingWS;
- }
- MakeSingleWSRun(wstype);
- return;
- }
- // otherwise a little trickier. shucks.
- mStartRun = new WSFragment();
- mStartRun->mStartNode = mStartNode;
- mStartRun->mStartOffset = mStartOffset;
- if (mStartReason & WSType::block || mStartReason == WSType::br) {
- // set up mStartRun
- mStartRun->mType = WSType::leadingWS;
- mStartRun->mEndNode = mFirstNBSPNode;
- mStartRun->mEndOffset = mFirstNBSPOffset;
- mStartRun->mLeftType = mStartReason;
- mStartRun->mRightType = WSType::normalWS;
- // set up next run
- WSFragment *normalRun = new WSFragment();
- mStartRun->mRight = normalRun;
- normalRun->mType = WSType::normalWS;
- normalRun->mStartNode = mFirstNBSPNode;
- normalRun->mStartOffset = mFirstNBSPOffset;
- normalRun->mLeftType = WSType::leadingWS;
- normalRun->mLeft = mStartRun;
- if (mEndReason != WSType::block) {
- // then no trailing ws. this normal run ends the overall ws run.
- normalRun->mRightType = 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->mRightType = mEndReason;
- normalRun->mEndNode = mEndNode;
- normalRun->mEndOffset = mEndOffset;
- mEndRun = normalRun;
- } else {
- normalRun->mEndNode = mLastNBSPNode;
- normalRun->mEndOffset = mLastNBSPOffset+1;
- normalRun->mRightType = WSType::trailingWS;
- // set up next run
- WSFragment *lastRun = new WSFragment();
- lastRun->mType = WSType::trailingWS;
- lastRun->mStartNode = mLastNBSPNode;
- lastRun->mStartOffset = mLastNBSPOffset+1;
- lastRun->mEndNode = mEndNode;
- lastRun->mEndOffset = mEndOffset;
- lastRun->mLeftType = WSType::normalWS;
- lastRun->mLeft = normalRun;
- lastRun->mRightType = mEndReason;
- mEndRun = lastRun;
- normalRun->mRight = lastRun;
- }
- }
- } else {
- // mStartReason is not WSType::block or WSType::br; set up mStartRun
- mStartRun->mType = WSType::normalWS;
- mStartRun->mEndNode = mLastNBSPNode;
- mStartRun->mEndOffset = mLastNBSPOffset+1;
- mStartRun->mLeftType = 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->mRightType = mEndReason;
- mStartRun->mEndNode = mEndNode;
- mStartRun->mEndOffset = mEndOffset;
- mEndRun = mStartRun;
- } else {
- // set up next run
- WSFragment *lastRun = new WSFragment();
- lastRun->mType = WSType::trailingWS;
- lastRun->mStartNode = mLastNBSPNode;
- lastRun->mStartOffset = mLastNBSPOffset+1;
- lastRun->mLeftType = WSType::normalWS;
- lastRun->mLeft = mStartRun;
- lastRun->mRightType = mEndReason;
- mEndRun = lastRun;
- mStartRun->mRight = lastRun;
- mStartRun->mRightType = WSType::trailingWS;
- }
- }
- }
- void
- WSRunObject::ClearRuns()
- {
- WSFragment *tmp, *run;
- run = mStartRun;
- while (run) {
- tmp = run->mRight;
- delete run;
- run = tmp;
- }
- mStartRun = 0;
- mEndRun = 0;
- }
- void
- WSRunObject::MakeSingleWSRun(WSType aType)
- {
- mStartRun = new WSFragment();
- mStartRun->mStartNode = mStartNode;
- mStartRun->mStartOffset = mStartOffset;
- mStartRun->mType = aType;
- mStartRun->mEndNode = mEndNode;
- mStartRun->mEndOffset = mEndOffset;
- mStartRun->mLeftType = mStartReason;
- mStartRun->mRightType = mEndReason;
- mEndRun = mStartRun;
- }
- nsIContent*
- WSRunObject::GetPreviousWSNodeInner(nsINode* aStartNode,
- nsINode* aBlockParent)
- {
- // 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);
- nsCOMPtr<nsIContent> priorNode = aStartNode->GetPreviousSibling();
- OwningNonNull<nsINode> curNode = *aStartNode;
- while (!priorNode) {
- // We have exhausted nodes in parent of aStartNode.
- nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
- NS_ENSURE_TRUE(curParent, nullptr);
- if (curParent == aBlockParent) {
- // We have exhausted nodes in the block parent. The convention here is
- // to return null.
- return nullptr;
- }
- // We have a parent: look for previous sibling
- priorNode = curParent->GetPreviousSibling();
- curNode = curParent;
- }
- // We have a prior node. If it's a block, return it.
- if (IsBlockNode(priorNode)) {
- return priorNode;
- }
- if (mHTMLEditor->IsContainer(priorNode)) {
- // Else if it's a container, get deep rightmost child
- nsCOMPtr<nsIContent> child = mHTMLEditor->GetRightmostChild(priorNode);
- if (child) {
- return child;
- }
- }
- // Else return the node itself
- return priorNode;
- }
- nsIContent*
- WSRunObject::GetPreviousWSNode(EditorDOMPoint aPoint,
- nsINode* aBlockParent)
- {
- // 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.node && aBlockParent);
- if (aPoint.node->NodeType() == nsIDOMNode::TEXT_NODE) {
- return GetPreviousWSNodeInner(aPoint.node, aBlockParent);
- }
- if (!mHTMLEditor->IsContainer(aPoint.node)) {
- return GetPreviousWSNodeInner(aPoint.node, aBlockParent);
- }
- if (!aPoint.offset) {
- if (aPoint.node == aBlockParent) {
- // We are at start of the block.
- return nullptr;
- }
- // We are at start of non-block container
- return GetPreviousWSNodeInner(aPoint.node, aBlockParent);
- }
- nsCOMPtr<nsIContent> startContent = do_QueryInterface(aPoint.node);
- NS_ENSURE_TRUE(startContent, nullptr);
- nsCOMPtr<nsIContent> priorNode = startContent->GetChildAt(aPoint.offset - 1);
- NS_ENSURE_TRUE(priorNode, nullptr);
- // We have a prior node. If it's a block, return it.
- if (IsBlockNode(priorNode)) {
- return priorNode;
- }
- if (mHTMLEditor->IsContainer(priorNode)) {
- // Else if it's a container, get deep rightmost child
- nsCOMPtr<nsIContent> child = mHTMLEditor->GetRightmostChild(priorNode);
- if (child) {
- return child;
- }
- }
- // Else return the node itself
- return priorNode;
- }
- nsIContent*
- WSRunObject::GetNextWSNodeInner(nsINode* aStartNode,
- nsINode* aBlockParent)
- {
- // 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);
- nsCOMPtr<nsIContent> nextNode = aStartNode->GetNextSibling();
- nsCOMPtr<nsINode> curNode = aStartNode;
- while (!nextNode) {
- // We have exhausted nodes in parent of aStartNode.
- nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
- NS_ENSURE_TRUE(curParent, nullptr);
- if (curParent == aBlockParent) {
- // We have exhausted nodes in the block parent. The convention here is
- // to return null.
- return nullptr;
- }
- // We have a parent: look for next sibling
- nextNode = curParent->GetNextSibling();
- curNode = curParent;
- }
- // We have a next node. If it's a block, return it.
- if (IsBlockNode(nextNode)) {
- return nextNode;
- }
- if (mHTMLEditor->IsContainer(nextNode)) {
- // Else if it's a container, get deep leftmost child
- nsCOMPtr<nsIContent> child = mHTMLEditor->GetLeftmostChild(nextNode);
- if (child) {
- return child;
- }
- }
- // Else return the node itself
- return nextNode;
- }
- nsIContent*
- WSRunObject::GetNextWSNode(EditorDOMPoint aPoint,
- nsINode* aBlockParent)
- {
- // 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.node && aBlockParent);
- if (aPoint.node->NodeType() == nsIDOMNode::TEXT_NODE) {
- return GetNextWSNodeInner(aPoint.node, aBlockParent);
- }
- if (!mHTMLEditor->IsContainer(aPoint.node)) {
- return GetNextWSNodeInner(aPoint.node, aBlockParent);
- }
- nsCOMPtr<nsIContent> startContent = do_QueryInterface(aPoint.node);
- NS_ENSURE_TRUE(startContent, nullptr);
- nsCOMPtr<nsIContent> nextNode = startContent->GetChildAt(aPoint.offset);
- if (!nextNode) {
- if (aPoint.node == aBlockParent) {
- // We are at end of the block.
- return nullptr;
- }
- // We are at end of non-block container
- return GetNextWSNodeInner(aPoint.node, aBlockParent);
- }
- // We have a next node. If it's a block, return it.
- if (IsBlockNode(nextNode)) {
- return nextNode;
- }
- if (mHTMLEditor->IsContainer(nextNode)) {
- // else if it's a container, get deep leftmost child
- nsCOMPtr<nsIContent> child = mHTMLEditor->GetLeftmostChild(nextNode);
- if (child) {
- return child;
- }
- }
- // Else return the node itself
- return nextNode;
- }
- 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?)
- NS_ENSURE_TRUE(aEndObject, NS_ERROR_NULL_POINTER);
- // get the runs before and after selection
- WSFragment *beforeRun, *afterRun;
- FindRun(mNode, mOffset, &beforeRun, false);
- aEndObject->FindRun(aEndObject->mNode, aEndObject->mOffset, &afterRun, true);
- // trim after run of any leading ws
- if (afterRun && (afterRun->mType & WSType::leadingWS)) {
- nsresult rv =
- aEndObject->DeleteChars(aEndObject->mNode, aEndObject->mOffset,
- afterRun->mEndNode, afterRun->mEndOffset,
- eOutsideUserSelectAll);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- // adjust normal ws in afterRun if needed
- if (afterRun && afterRun->mType == WSType::normalWS && !aEndObject->mPRE) {
- if ((beforeRun && (beforeRun->mType & WSType::leadingWS)) ||
- (!beforeRun && ((mStartReason & WSType::block) ||
- mStartReason == WSType::br))) {
- // make sure leading char of following ws is an nbsp, so that it will show up
- WSPoint point = aEndObject->GetCharAfter(aEndObject->mNode,
- aEndObject->mOffset);
- if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
- nsresult rv = aEndObject->ConvertToNBSP(point, eOutsideUserSelectAll);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- }
- }
- // trim before run of any trailing ws
- if (beforeRun && (beforeRun->mType & WSType::trailingWS)) {
- nsresult rv = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset,
- mNode, mOffset, eOutsideUserSelectAll);
- NS_ENSURE_SUCCESS(rv, rv);
- } else if (beforeRun && beforeRun->mType == WSType::normalWS && !mPRE) {
- if ((afterRun && (afterRun->mType & WSType::trailingWS)) ||
- (afterRun && afterRun->mType == WSType::normalWS) ||
- (!afterRun && (aEndObject->mEndReason & WSType::block))) {
- // make sure trailing char of starting ws is an nbsp, so that it will show up
- WSPoint point = GetCharBefore(mNode, mOffset);
- if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
- RefPtr<Text> wsStartNode, wsEndNode;
- int32_t wsStartOffset, wsEndOffset;
- GetAsciiWSBounds(eBoth, mNode, mOffset,
- getter_AddRefs(wsStartNode), &wsStartOffset,
- getter_AddRefs(wsEndNode), &wsEndOffset);
- point.mTextNode = wsStartNode;
- point.mOffset = wsStartOffset;
- nsresult rv = ConvertToNBSP(point, eOutsideUserSelectAll);
- NS_ENSURE_SUCCESS(rv, 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, *afterRun;
- FindRun(mNode, mOffset, &beforeRun, false);
- FindRun(mNode, mOffset, &afterRun, true);
- // adjust normal ws in afterRun if needed
- if (afterRun && afterRun->mType == WSType::normalWS) {
- // make sure leading char of following ws is an nbsp, so that it will show up
- WSPoint point = GetCharAfter(mNode, mOffset);
- if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
- nsresult rv = ConvertToNBSP(point);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- }
- // adjust normal ws in beforeRun if needed
- if (beforeRun && beforeRun->mType == WSType::normalWS) {
- // make sure trailing char of starting ws is an nbsp, so that it will show up
- WSPoint point = GetCharBefore(mNode, mOffset);
- if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
- RefPtr<Text> wsStartNode, wsEndNode;
- int32_t wsStartOffset, wsEndOffset;
- GetAsciiWSBounds(eBoth, mNode, mOffset,
- getter_AddRefs(wsStartNode), &wsStartOffset,
- getter_AddRefs(wsEndNode), &wsEndOffset);
- point.mTextNode = wsStartNode;
- point.mOffset = wsStartOffset;
- nsresult rv = ConvertToNBSP(point);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- }
- return NS_OK;
- }
- nsresult
- WSRunObject::DeleteChars(nsINode* aStartNode,
- int32_t aStartOffset,
- nsINode* aEndNode,
- int32_t aEndOffset,
- AreaRestriction aAR)
- {
- // MOOSE: this routine needs to be modified to preserve the integrity of the
- // wsFragment info.
- NS_ENSURE_TRUE(aStartNode && aEndNode, NS_ERROR_NULL_POINTER);
- if (aAR == eOutsideUserSelectAll) {
- nsCOMPtr<nsIDOMNode> san =
- mHTMLEditor->FindUserSelectAllNode(GetAsDOMNode(aStartNode));
- if (san) {
- return NS_OK;
- }
- if (aStartNode != aEndNode) {
- san = mHTMLEditor->FindUserSelectAllNode(GetAsDOMNode(aEndNode));
- if (san) {
- return NS_OK;
- }
- }
- }
- if (aStartNode == aEndNode && aStartOffset == aEndOffset) {
- // Nothing to delete
- return NS_OK;
- }
- int32_t idx = mNodeArray.IndexOf(aStartNode);
- if (idx == -1) {
- // If our strarting point wasn't one of our ws text nodes, then just go
- // through them from the beginning.
- idx = 0;
- }
- if (aStartNode == aEndNode && aStartNode->GetAsText()) {
- return mHTMLEditor->DeleteText(*aStartNode->GetAsText(),
- static_cast<uint32_t>(aStartOffset),
- static_cast<uint32_t>(aEndOffset - aStartOffset));
- }
- RefPtr<nsRange> range;
- int32_t count = mNodeArray.Length();
- 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 == aStartNode) {
- uint32_t len = node->Length();
- if (uint32_t(aStartOffset) < len) {
- nsresult rv =
- mHTMLEditor->DeleteText(*node, AssertedCast<uint32_t>(aStartOffset),
- len - aStartOffset);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- } else if (node == aEndNode) {
- if (aEndOffset) {
- nsresult rv =
- mHTMLEditor->DeleteText(*node, 0, AssertedCast<uint32_t>(aEndOffset));
- NS_ENSURE_SUCCESS(rv, rv);
- }
- break;
- } else {
- if (!range) {
- range = new nsRange(aStartNode);
- nsresult rv =
- range->SetStartAndEnd(aStartNode, aStartOffset, aEndNode, aEndOffset);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- bool nodeBefore, nodeAfter;
- nsresult rv =
- nsRange::CompareNodeToRange(node, range, &nodeBefore, &nodeAfter);
- NS_ENSURE_SUCCESS(rv, rv);
- if (nodeAfter) {
- break;
- }
- if (!nodeBefore) {
- rv = mHTMLEditor->DeleteNode(node);
- NS_ENSURE_SUCCESS(rv, rv);
- mNodeArray.RemoveElement(node);
- --count;
- --idx;
- }
- }
- }
- return NS_OK;
- }
- WSRunObject::WSPoint
- WSRunObject::GetCharAfter(nsINode* aNode,
- int32_t aOffset)
- {
- MOZ_ASSERT(aNode);
- int32_t idx = mNodeArray.IndexOf(aNode);
- if (idx == -1) {
- // Use range comparisons to get right ws node
- return GetWSPointAfter(aNode, aOffset);
- }
- // Use WSPoint version of GetCharAfter()
- return GetCharAfter(WSPoint(mNodeArray[idx], aOffset, 0));
- }
- WSRunObject::WSPoint
- WSRunObject::GetCharBefore(nsINode* aNode,
- int32_t aOffset)
- {
- MOZ_ASSERT(aNode);
- int32_t idx = mNodeArray.IndexOf(aNode);
- if (idx == -1) {
- // Use range comparisons to get right ws node
- return GetWSPointBefore(aNode, aOffset);
- }
- // Use WSPoint version of GetCharBefore()
- return GetCharBefore(WSPoint(mNodeArray[idx], aOffset, 0));
- }
- WSRunObject::WSPoint
- WSRunObject::GetCharAfter(const WSPoint &aPoint)
- {
- MOZ_ASSERT(aPoint.mTextNode);
- WSPoint outPoint;
- outPoint.mTextNode = nullptr;
- outPoint.mOffset = 0;
- outPoint.mChar = 0;
- int32_t idx = mNodeArray.IndexOf(aPoint.mTextNode);
- if (idx == -1) {
- // Can't find point, but it's not an error
- return outPoint;
- }
- if (static_cast<uint16_t>(aPoint.mOffset) < aPoint.mTextNode->TextLength()) {
- outPoint = aPoint;
- outPoint.mChar = GetCharAt(aPoint.mTextNode, aPoint.mOffset);
- return outPoint;
- }
- int32_t numNodes = mNodeArray.Length();
- if (idx + 1 < numNodes) {
- outPoint.mTextNode = mNodeArray[idx + 1];
- MOZ_ASSERT(outPoint.mTextNode);
- outPoint.mOffset = 0;
- outPoint.mChar = GetCharAt(outPoint.mTextNode, 0);
- }
- return outPoint;
- }
- WSRunObject::WSPoint
- WSRunObject::GetCharBefore(const WSPoint &aPoint)
- {
- MOZ_ASSERT(aPoint.mTextNode);
- WSPoint outPoint;
- outPoint.mTextNode = nullptr;
- outPoint.mOffset = 0;
- outPoint.mChar = 0;
- int32_t idx = mNodeArray.IndexOf(aPoint.mTextNode);
- if (idx == -1) {
- // Can't find point, but it's not an error
- return outPoint;
- }
- if (aPoint.mOffset) {
- outPoint = aPoint;
- outPoint.mOffset--;
- outPoint.mChar = GetCharAt(aPoint.mTextNode, aPoint.mOffset - 1);
- return outPoint;
- }
- if (idx) {
- outPoint.mTextNode = mNodeArray[idx - 1];
- uint32_t len = outPoint.mTextNode->TextLength();
- if (len) {
- outPoint.mOffset = len - 1;
- outPoint.mChar = GetCharAt(outPoint.mTextNode, len - 1);
- }
- }
- return outPoint;
- }
- nsresult
- WSRunObject::ConvertToNBSP(WSPoint aPoint, AreaRestriction aAR)
- {
- // MOOSE: this routine needs to be modified to preserve the integrity of the
- // wsFragment info.
- NS_ENSURE_TRUE(aPoint.mTextNode, NS_ERROR_NULL_POINTER);
- if (aAR == eOutsideUserSelectAll) {
- nsCOMPtr<nsIDOMNode> san =
- mHTMLEditor->FindUserSelectAllNode(GetAsDOMNode(aPoint.mTextNode));
- if (san) {
- return NS_OK;
- }
- }
- // First, insert an nbsp
- AutoTransactionsConserveSelection dontSpazMySelection(mHTMLEditor);
- nsAutoString nbspStr(nbsp);
- nsresult rv =
- mHTMLEditor->InsertTextIntoTextNodeImpl(nbspStr, *aPoint.mTextNode,
- aPoint.mOffset, true);
- NS_ENSURE_SUCCESS(rv, rv);
- // Next, find range of ws it will replace
- RefPtr<Text> startNode, endNode;
- int32_t startOffset = 0, endOffset = 0;
- GetAsciiWSBounds(eAfter, aPoint.mTextNode, aPoint.mOffset + 1,
- getter_AddRefs(startNode), &startOffset,
- getter_AddRefs(endNode), &endOffset);
- // Finally, delete that replaced ws, if any
- if (startNode) {
- rv = DeleteChars(startNode, startOffset, endNode, endOffset);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- return NS_OK;
- }
- void
- WSRunObject::GetAsciiWSBounds(int16_t aDir,
- nsINode* aNode,
- int32_t aOffset,
- Text** outStartNode,
- int32_t* outStartOffset,
- Text** outEndNode,
- int32_t* outEndOffset)
- {
- MOZ_ASSERT(aNode && outStartNode && outStartOffset && outEndNode &&
- outEndOffset);
- RefPtr<Text> startNode, endNode;
- int32_t startOffset = 0, endOffset = 0;
- if (aDir & eAfter) {
- WSPoint point = GetCharAfter(aNode, aOffset);
- if (point.mTextNode) {
- // We found a text node, at least
- startNode = endNode = point.mTextNode;
- startOffset = endOffset = point.mOffset;
- // Scan ahead to end of ASCII ws
- for (; nsCRT::IsAsciiSpace(point.mChar) && point.mTextNode;
- point = GetCharAfter(point)) {
- endNode = point.mTextNode;
- // endOffset is _after_ ws
- point.mOffset++;
- endOffset = point.mOffset;
- }
- }
- }
- if (aDir & eBefore) {
- WSPoint point = GetCharBefore(aNode, aOffset);
- if (point.mTextNode) {
- // We found a text node, at least
- startNode = point.mTextNode;
- startOffset = point.mOffset + 1;
- if (!endNode) {
- endNode = startNode;
- endOffset = startOffset;
- }
- // Scan back to start of ASCII ws
- for (; nsCRT::IsAsciiSpace(point.mChar) && point.mTextNode;
- point = GetCharBefore(point)) {
- startNode = point.mTextNode;
- startOffset = point.mOffset;
- }
- }
- }
- startNode.forget(outStartNode);
- *outStartOffset = startOffset;
- endNode.forget(outEndNode);
- *outEndOffset = endOffset;
- }
- /**
- * Given a dompoint, find the ws run that is before or after it, as caller
- * needs
- */
- void
- WSRunObject::FindRun(nsINode* aNode,
- int32_t aOffset,
- WSFragment** outRun,
- bool after)
- {
- MOZ_ASSERT(aNode && outRun);
- *outRun = nullptr;
- for (WSFragment* run = mStartRun; run; run = run->mRight) {
- int32_t comp = run->mStartNode ? nsContentUtils::ComparePoints(aNode,
- aOffset, run->mStartNode, run->mStartOffset) : -1;
- if (comp <= 0) {
- if (after) {
- *outRun = run;
- } else {
- // before
- *outRun = nullptr;
- }
- return;
- }
- comp = run->mEndNode ? nsContentUtils::ComparePoints(aNode, aOffset,
- run->mEndNode, run->mEndOffset) : -1;
- if (comp < 0) {
- *outRun = run;
- return;
- } else if (!comp) {
- if (after) {
- *outRun = run->mRight;
- } else {
- // before
- *outRun = run;
- }
- return;
- }
- if (!run->mRight) {
- if (after) {
- *outRun = nullptr;
- } else {
- // before
- *outRun = run;
- }
- return;
- }
- }
- }
- char16_t
- WSRunObject::GetCharAt(Text* aTextNode,
- int32_t aOffset)
- {
- // return 0 if we can't get a char, for whatever reason
- NS_ENSURE_TRUE(aTextNode, 0);
- int32_t len = int32_t(aTextNode->TextLength());
- if (aOffset < 0 || aOffset >= len) {
- return 0;
- }
- return aTextNode->GetText()->CharAt(aOffset);
- }
- WSRunObject::WSPoint
- WSRunObject::GetWSPointAfter(nsINode* aNode,
- int32_t aOffset)
- {
- // Note: only to be called if aNode is not a ws node.
- // Binary search on wsnodes
- uint32_t numNodes = mNodeArray.Length();
- if (!numNodes) {
- // Do nothing if there are no nodes to search
- WSPoint outPoint;
- return outPoint;
- }
- uint32_t firstNum = 0, curNum = numNodes/2, lastNum = numNodes;
- int16_t cmp = 0;
- RefPtr<Text> curNode;
- // Begin binary search. We do this because we need to minimize calls to
- // ComparePoints(), which is expensive.
- while (curNum != lastNum) {
- curNode = mNodeArray[curNum];
- cmp = nsContentUtils::ComparePoints(aNode, aOffset, curNode, 0);
- 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). GetCharAfter
- // will do the work for us when we pass it the last index of the last node.
- RefPtr<Text> textNode(mNodeArray[curNum - 1]);
- WSPoint point(textNode, textNode->TextLength(), 0);
- return GetCharAfter(point);
- } else {
- // The char after the point is the first character of our range.
- RefPtr<Text> textNode(mNodeArray[curNum]);
- WSPoint point(textNode, 0, 0);
- return GetCharAfter(point);
- }
- }
- WSRunObject::WSPoint
- WSRunObject::GetWSPointBefore(nsINode* aNode,
- int32_t aOffset)
- {
- // Note: only to be called if aNode is not a ws node.
- // Binary search on wsnodes
- uint32_t numNodes = mNodeArray.Length();
- if (!numNodes) {
- // Do nothing if there are no nodes to search
- WSPoint outPoint;
- return outPoint;
- }
- uint32_t firstNum = 0, curNum = numNodes/2, lastNum = numNodes;
- int16_t cmp = 0;
- RefPtr<Text> curNode;
- // Begin binary search. We do this because we need to minimize calls to
- // ComparePoints(), which is expensive.
- while (curNum != lastNum) {
- curNode = mNodeArray[curNum];
- cmp = nsContentUtils::ComparePoints(aNode, aOffset, curNode, 0);
- 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 GetCharBefore, and it will return the last character.
- RefPtr<Text> textNode(mNodeArray[curNum - 1]);
- WSPoint point(textNode, textNode->TextLength(), 0);
- return GetCharBefore(point);
- } else {
- // 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
- RefPtr<Text> textNode(mNodeArray[curNum]);
- WSPoint point(textNode, 0, 0);
- return GetCharBefore(point);
- }
- }
- nsresult
- WSRunObject::CheckTrailingNBSPOfRun(WSFragment *aRun)
- {
- // 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.
- NS_ENSURE_TRUE(aRun, NS_ERROR_NULL_POINTER);
- bool leftCheck = false;
- bool spaceNBSP = false;
- bool rightCheck = false;
- // confirm run is normalWS
- if (aRun->mType != WSType::normalWS) {
- return NS_ERROR_FAILURE;
- }
- // first check for trailing nbsp
- WSPoint thePoint = GetCharBefore(aRun->mEndNode, aRun->mEndOffset);
- if (thePoint.mTextNode && thePoint.mChar == nbsp) {
- // now check that what is to the left of it is compatible with replacing nbsp with space
- WSPoint prevPoint = GetCharBefore(thePoint);
- if (prevPoint.mTextNode) {
- if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) {
- leftCheck = true;
- } else {
- spaceNBSP = true;
- }
- } else if (aRun->mLeftType == WSType::text ||
- aRun->mLeftType == WSType::special) {
- leftCheck = true;
- }
- if (leftCheck || spaceNBSP) {
- // now check that what is to the right of it is compatible with replacing
- // nbsp with space
- if (aRun->mRightType == WSType::text ||
- aRun->mRightType == WSType::special ||
- aRun->mRightType == WSType::br) {
- rightCheck = true;
- }
- if ((aRun->mRightType & WSType::block) &&
- IsBlockNode(GetWSBoundingParent())) {
- // 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.
- nsCOMPtr<Element> brNode =
- mHTMLEditor->CreateBR(aRun->mEndNode, aRun->mEndOffset);
- NS_ENSURE_TRUE(brNode, NS_ERROR_FAILURE);
- // Refresh thePoint, prevPoint
- thePoint = GetCharBefore(aRun->mEndNode, aRun->mEndOffset);
- prevPoint = GetCharBefore(thePoint);
- rightCheck = true;
- }
- }
- if (leftCheck && rightCheck) {
- // Now replace nbsp with space. First, insert a space
- AutoTransactionsConserveSelection dontSpazMySelection(mHTMLEditor);
- nsAutoString spaceStr(char16_t(32));
- nsresult rv =
- mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, *thePoint.mTextNode,
- thePoint.mOffset, true);
- NS_ENSURE_SUCCESS(rv, rv);
- // Finally, delete that nbsp
- rv = DeleteChars(thePoint.mTextNode, thePoint.mOffset + 1,
- thePoint.mTextNode, thePoint.mOffset + 2);
- NS_ENSURE_SUCCESS(rv, 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.
- RefPtr<Text> startNode, endNode;
- int32_t startOffset, endOffset;
- GetAsciiWSBounds(eBoth, prevPoint.mTextNode, prevPoint.mOffset + 1,
- getter_AddRefs(startNode), &startOffset,
- getter_AddRefs(endNode), &endOffset);
- // Delete that nbsp
- nsresult rv = DeleteChars(thePoint.mTextNode, thePoint.mOffset,
- thePoint.mTextNode, thePoint.mOffset + 1);
- NS_ENSURE_SUCCESS(rv, rv);
- // Finally, insert that nbsp before the ASCII ws run
- AutoTransactionsConserveSelection dontSpazMySelection(mHTMLEditor);
- nsAutoString nbspStr(nbsp);
- rv = mHTMLEditor->InsertTextIntoTextNodeImpl(nbspStr, *startNode,
- startOffset, true);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- }
- return NS_OK;
- }
- nsresult
- WSRunObject::CheckTrailingNBSP(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 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.
- NS_ENSURE_TRUE(aRun && aNode, NS_ERROR_NULL_POINTER);
- bool canConvert = false;
- WSPoint thePoint = GetCharBefore(aNode, aOffset);
- if (thePoint.mTextNode && thePoint.mChar == nbsp) {
- WSPoint prevPoint = GetCharBefore(thePoint);
- if (prevPoint.mTextNode) {
- if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) {
- canConvert = true;
- }
- } else if (aRun->mLeftType == WSType::text ||
- aRun->mLeftType == WSType::special) {
- canConvert = true;
- }
- }
- if (canConvert) {
- // First, insert a space
- AutoTransactionsConserveSelection dontSpazMySelection(mHTMLEditor);
- nsAutoString spaceStr(char16_t(32));
- nsresult rv =
- mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, *thePoint.mTextNode,
- thePoint.mOffset, true);
- NS_ENSURE_SUCCESS(rv, rv);
- // Finally, delete that nbsp
- rv = DeleteChars(thePoint.mTextNode, thePoint.mOffset + 1,
- thePoint.mTextNode, thePoint.mOffset + 2);
- NS_ENSURE_SUCCESS(rv, 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;
- WSPoint thePoint = GetCharAfter(aNode, aOffset);
- if (thePoint.mChar == nbsp) {
- WSPoint tmp = thePoint;
- // we want to be after thePoint
- tmp.mOffset++;
- WSPoint nextPoint = GetCharAfter(tmp);
- if (nextPoint.mTextNode) {
- if (!nsCRT::IsAsciiSpace(nextPoint.mChar)) {
- canConvert = true;
- }
- } else if (aRun->mRightType == WSType::text ||
- aRun->mRightType == WSType::special ||
- aRun->mRightType == WSType::br) {
- canConvert = true;
- }
- }
- if (canConvert) {
- // First, insert a space
- AutoTransactionsConserveSelection dontSpazMySelection(mHTMLEditor);
- nsAutoString spaceStr(char16_t(32));
- nsresult rv =
- mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, *thePoint.mTextNode,
- thePoint.mOffset, true);
- NS_ENSURE_SUCCESS(rv, rv);
- // Finally, delete that nbsp
- rv = DeleteChars(thePoint.mTextNode, thePoint.mOffset + 1,
- thePoint.mTextNode, thePoint.mOffset + 2);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- return NS_OK;
- }
- nsresult
- WSRunObject::Scrub()
- {
- WSFragment *run = mStartRun;
- while (run) {
- if (run->mType & (WSType::leadingWS | WSType::trailingWS)) {
- nsresult rv = DeleteChars(run->mStartNode, run->mStartOffset,
- run->mEndNode, run->mEndOffset);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- run = run->mRight;
- }
- return NS_OK;
- }
- bool
- WSRunObject::IsBlockNode(nsINode* aNode)
- {
- return aNode && aNode->IsElement() &&
- HTMLEditor::NodeIsBlockStatic(aNode->AsElement());
- }
- } // namespace mozilla
|