Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #ifndef WSRunObject_h
7 : #define WSRunObject_h
8 :
9 : #include "nsCOMPtr.h"
10 : #include "nsIEditor.h" // for EDirection
11 : #include "nsINode.h"
12 : #include "nscore.h"
13 : #include "mozilla/Attributes.h"
14 : #include "mozilla/dom/Text.h"
15 :
16 : class nsIDOMNode;
17 :
18 : namespace mozilla {
19 :
20 : class HTMLEditor;
21 : class HTMLEditRules;
22 : struct EditorDOMPoint;
23 :
24 : // class WSRunObject represents the entire whitespace situation
25 : // around a given point. It collects up a list of nodes that contain
26 : // whitespace and categorizes in up to 3 different WSFragments (detailed
27 : // below). Each WSFragment is a collection of whitespace that is
28 : // either all insignificant, or that is significant. A WSFragment could
29 : // consist of insignificant whitespace because it is after a block
30 : // boundary or after a break. Or it could be insignificant because it
31 : // is before a block. Or it could be significant because it is
32 : // surrounded by text, or starts and ends with nbsps, etc.
33 :
34 : // Throughout I refer to LeadingWS, NormalWS, TrailingWS. LeadingWS & TrailingWS
35 : // are runs of ascii ws that are insignificant (do not render) because they
36 : // are adjacent to block boundaries, or after a break. NormalWS is ws that
37 : // does cause soem rendering. Note that not all the ws in a NormalWS run need
38 : // render. For example, two ascii spaces surrounded by text on both sides
39 : // will only render as one space (in non-preformatted stlye html), yet both
40 : // spaces count as NormalWS. Together, they render as the one visible space.
41 :
42 : /**
43 : * A type-safe bitfield indicating various types of whitespace or other things.
44 : * Used as a member variable in WSRunObject and WSFragment.
45 : *
46 : * XXX: If this idea is useful in other places, we should generalize it using a
47 : * template.
48 : */
49 : class WSType
50 : {
51 : public:
52 : enum Enum
53 : {
54 : none = 0,
55 : leadingWS = 1, // leading insignificant ws, ie, after block or br
56 : trailingWS = 1 << 1, // trailing insignificant ws, ie, before block
57 : normalWS = 1 << 2, // normal significant ws, ie, after text, image, ...
58 : text = 1 << 3, // indicates regular (non-ws) text
59 : special = 1 << 4, // indicates an inline non-container, like image
60 : br = 1 << 5, // indicates a br node
61 : otherBlock = 1 << 6, // indicates a block other than one ws run is in
62 : thisBlock = 1 << 7, // indicates the block ws run is in
63 : block = otherBlock | thisBlock // block found
64 : };
65 :
66 : /**
67 : * Implicit constructor, because the enums are logically just WSTypes
68 : * themselves, and are only a separate type because there's no other obvious
69 : * way to name specific WSType values.
70 : */
71 0 : MOZ_IMPLICIT WSType(const Enum& aEnum = none)
72 0 : : mEnum(aEnum)
73 0 : {}
74 :
75 : // operator==, &, and | need to access mEnum
76 : friend bool operator==(const WSType& aLeft, const WSType& aRight);
77 : friend const WSType operator&(const WSType& aLeft, const WSType& aRight);
78 : friend const WSType operator|(const WSType& aLeft, const WSType& aRight);
79 0 : WSType& operator=(const WSType& aOther)
80 : {
81 : // This handles self-assignment fine
82 0 : mEnum = aOther.mEnum;
83 0 : return *this;
84 : }
85 : WSType& operator&=(const WSType& aOther)
86 : {
87 : mEnum &= aOther.mEnum;
88 : return *this;
89 : }
90 0 : WSType& operator|=(const WSType& aOther)
91 : {
92 0 : mEnum |= aOther.mEnum;
93 0 : return *this;
94 : }
95 :
96 : private:
97 : uint16_t mEnum;
98 0 : void bool_conversion_helper() {}
99 :
100 : public:
101 : // Allow boolean conversion with no numeric conversion
102 : typedef void (WSType::*bool_type)();
103 0 : operator bool_type() const
104 : {
105 0 : return mEnum ? &WSType::bool_conversion_helper : nullptr;
106 : }
107 : };
108 :
109 : /**
110 : * These are declared as global functions so "WSType::Enum == WSType" et al.
111 : * will work using the implicit constructor.
112 : */
113 0 : inline bool operator==(const WSType& aLeft, const WSType& aRight)
114 : {
115 0 : return aLeft.mEnum == aRight.mEnum;
116 : }
117 :
118 0 : inline bool operator!=(const WSType& aLeft, const WSType& aRight)
119 : {
120 0 : return !(aLeft == aRight);
121 : }
122 :
123 0 : inline const WSType operator&(const WSType& aLeft, const WSType& aRight)
124 : {
125 0 : WSType ret;
126 0 : ret.mEnum = aLeft.mEnum & aRight.mEnum;
127 0 : return ret;
128 : }
129 :
130 0 : inline const WSType operator|(const WSType& aLeft, const WSType& aRight)
131 : {
132 0 : WSType ret;
133 0 : ret.mEnum = aLeft.mEnum | aRight.mEnum;
134 0 : return ret;
135 : }
136 :
137 : /**
138 : * Make sure that & and | of WSType::Enum creates a WSType instead of an int,
139 : * because operators between WSType and int shouldn't work
140 : */
141 : inline const WSType operator&(const WSType::Enum& aLeft,
142 : const WSType::Enum& aRight)
143 : {
144 : return WSType(aLeft) & WSType(aRight);
145 : }
146 :
147 0 : inline const WSType operator|(const WSType::Enum& aLeft,
148 : const WSType::Enum& aRight)
149 : {
150 0 : return WSType(aLeft) | WSType(aRight);
151 : }
152 :
153 : class MOZ_STACK_CLASS WSRunObject final
154 : {
155 : public:
156 : enum BlockBoundary
157 : {
158 : kBeforeBlock,
159 : kBlockStart,
160 : kBlockEnd,
161 : kAfterBlock
162 : };
163 :
164 : enum {eBefore = 1};
165 : enum {eAfter = 1 << 1};
166 : enum {eBoth = eBefore | eAfter};
167 :
168 : WSRunObject(HTMLEditor* aHTMLEditor, nsINode* aNode, int32_t aOffset);
169 : WSRunObject(HTMLEditor* aHTMLEditor, nsIDOMNode* aNode, int32_t aOffset);
170 : ~WSRunObject();
171 :
172 : // ScrubBlockBoundary removes any non-visible whitespace at the specified
173 : // location relative to a block node.
174 : static nsresult ScrubBlockBoundary(HTMLEditor* aHTMLEditor,
175 : BlockBoundary aBoundary,
176 : nsINode* aBlock,
177 : int32_t aOffset = -1);
178 :
179 : // PrepareToJoinBlocks fixes up ws at the end of aLeftBlock and the
180 : // beginning of aRightBlock in preperation for them to be joined. Example
181 : // of fixup: trailingws in aLeftBlock needs to be removed.
182 : static nsresult PrepareToJoinBlocks(HTMLEditor* aHTMLEditor,
183 : dom::Element* aLeftBlock,
184 : dom::Element* aRightBlock);
185 :
186 : // PrepareToDeleteRange fixes up ws before {aStartNode,aStartOffset}
187 : // and after {aEndNode,aEndOffset} in preperation for content
188 : // in that range to be deleted. Note that the nodes and offsets
189 : // are adjusted in response to any dom changes we make while
190 : // adjusting ws.
191 : // example of fixup: trailingws before {aStartNode,aStartOffset}
192 : // needs to be removed.
193 : static nsresult PrepareToDeleteRange(HTMLEditor* aHTMLEditor,
194 : nsCOMPtr<nsINode>* aStartNode,
195 : int32_t* aStartOffset,
196 : nsCOMPtr<nsINode>* aEndNode,
197 : int32_t* aEndOffset);
198 :
199 : // PrepareToDeleteNode fixes up ws before and after aContent in preparation
200 : // for aContent to be deleted. Example of fixup: trailingws before
201 : // aContent needs to be removed.
202 : static nsresult PrepareToDeleteNode(HTMLEditor* aHTMLEditor,
203 : nsIContent* aContent);
204 :
205 : // PrepareToSplitAcrossBlocks fixes up ws before and after
206 : // {aSplitNode,aSplitOffset} in preparation for a block parent to be split.
207 : // Note that the aSplitNode and aSplitOffset are adjusted in response to
208 : // any DOM changes we make while adjusting ws. Example of fixup: normalws
209 : // before {aSplitNode,aSplitOffset} needs to end with nbsp.
210 : static nsresult PrepareToSplitAcrossBlocks(HTMLEditor* aHTMLEditor,
211 : nsCOMPtr<nsINode>* aSplitNode,
212 : int32_t* aSplitOffset);
213 :
214 : // InsertBreak inserts a br node at {aInOutParent,aInOutOffset}
215 : // and makes any needed adjustments to ws around that point.
216 : // example of fixup: normalws after {aInOutParent,aInOutOffset}
217 : // needs to begin with nbsp.
218 : already_AddRefed<dom::Element> InsertBreak(nsCOMPtr<nsINode>* aInOutParent,
219 : int32_t* aInOutOffset,
220 : nsIEditor::EDirection aSelect);
221 :
222 : // InsertText inserts a string at {aInOutParent,aInOutOffset} and makes any
223 : // needed adjustments to ws around that point. Example of fixup:
224 : // trailingws before {aInOutParent,aInOutOffset} needs to be removed.
225 : nsresult InsertText(const nsAString& aStringToInsert,
226 : nsCOMPtr<nsINode>* aInOutNode,
227 : int32_t* aInOutOffset,
228 : nsIDocument* aDoc);
229 :
230 : // DeleteWSBackward deletes a single visible piece of ws before the ws
231 : // point (the point to create the wsRunObject, passed to its constructor).
232 : // It makes any needed conversion to adjacent ws to retain its
233 : // significance.
234 : nsresult DeleteWSBackward();
235 :
236 : // DeleteWSForward deletes a single visible piece of ws after the ws point
237 : // (the point to create the wsRunObject, passed to its constructor). It
238 : // makes any needed conversion to adjacent ws to retain its significance.
239 : nsresult DeleteWSForward();
240 :
241 : // PriorVisibleNode returns the first piece of visible thing before
242 : // {aNode,aOffset}. If there is no visible ws qualifying it returns what
243 : // is before the ws run. Note that {outVisNode,outVisOffset} is set to
244 : // just AFTER the visible object.
245 : void PriorVisibleNode(nsINode* aNode,
246 : int32_t aOffset,
247 : nsCOMPtr<nsINode>* outVisNode,
248 : int32_t* outVisOffset,
249 : WSType* outType);
250 :
251 : // NextVisibleNode returns the first piece of visible thing after
252 : // {aNode,aOffset}. If there is no visible ws qualifying it returns what
253 : // is after the ws run. Note that {outVisNode,outVisOffset} is set to just
254 : // BEFORE the visible object.
255 : void NextVisibleNode(nsINode* aNode,
256 : int32_t aOffset,
257 : nsCOMPtr<nsINode>* outVisNode,
258 : int32_t* outVisOffset,
259 : WSType* outType);
260 :
261 : // AdjustWhitespace examines the ws object for nbsp's that can
262 : // be safely converted to regular ascii space and converts them.
263 : nsresult AdjustWhitespace();
264 :
265 : protected:
266 : // WSFragment represents a single run of ws (all leadingws, or all normalws,
267 : // or all trailingws, or all leading+trailingws). Note that this single run
268 : // may still span multiple nodes.
269 0 : struct WSFragment final
270 : {
271 : nsCOMPtr<nsINode> mStartNode; // node where ws run starts
272 : nsCOMPtr<nsINode> mEndNode; // node where ws run ends
273 : int32_t mStartOffset; // offset where ws run starts
274 : int32_t mEndOffset; // offset where ws run ends
275 : // type of ws, and what is to left and right of it
276 : WSType mType, mLeftType, mRightType;
277 : // other ws runs to left or right. may be null.
278 : WSFragment *mLeft, *mRight;
279 :
280 0 : WSFragment()
281 0 : : mStartOffset(0)
282 : , mEndOffset(0)
283 : , mLeft(nullptr)
284 0 : , mRight(nullptr)
285 0 : {}
286 : };
287 :
288 : // A WSPoint struct represents a unique location within the ws run. It is
289 : // always within a textnode that is one of the nodes stored in the list
290 : // in the wsRunObject. For convenience, the character at that point is also
291 : // stored in the struct.
292 0 : struct MOZ_STACK_CLASS WSPoint final
293 : {
294 : RefPtr<dom::Text> mTextNode;
295 : uint32_t mOffset;
296 : char16_t mChar;
297 :
298 0 : WSPoint()
299 0 : : mTextNode(nullptr)
300 : , mOffset(0)
301 0 : , mChar(0)
302 0 : {}
303 :
304 0 : WSPoint(dom::Text* aTextNode, int32_t aOffset, char16_t aChar)
305 0 : : mTextNode(aTextNode)
306 : , mOffset(aOffset)
307 0 : , mChar(aChar)
308 0 : {}
309 : };
310 :
311 : /**
312 : * Return the node which we will handle white-space under. This is the
313 : * closest block within the DOM subtree we're editing, or if none is
314 : * found, the (inline) root of the editable subtree.
315 : */
316 : nsINode* GetWSBoundingParent();
317 :
318 : nsresult GetWSNodes();
319 : void GetRuns();
320 : void ClearRuns();
321 : void MakeSingleWSRun(WSType aType);
322 : nsIContent* GetPreviousWSNodeInner(nsINode* aStartNode,
323 : nsINode* aBlockParent);
324 : nsIContent* GetPreviousWSNode(EditorDOMPoint aPoint, nsINode* aBlockParent);
325 : nsIContent* GetNextWSNodeInner(nsINode* aStartNode, nsINode* aBlockParent);
326 : nsIContent* GetNextWSNode(EditorDOMPoint aPoint, nsINode* aBlockParent);
327 : nsresult PrepareToDeleteRangePriv(WSRunObject* aEndObject);
328 : nsresult PrepareToSplitAcrossBlocksPriv();
329 : nsresult DeleteChars(nsINode* aStartNode, int32_t aStartOffset,
330 : nsINode* aEndNode, int32_t aEndOffset);
331 : WSPoint GetCharAfter(nsINode* aNode, int32_t aOffset);
332 : WSPoint GetCharBefore(nsINode* aNode, int32_t aOffset);
333 : WSPoint GetCharAfter(const WSPoint& aPoint);
334 : WSPoint GetCharBefore(const WSPoint& aPoint);
335 : nsresult ConvertToNBSP(WSPoint aPoint);
336 : void GetAsciiWSBounds(int16_t aDir, nsINode* aNode, int32_t aOffset,
337 : dom::Text** outStartNode, int32_t* outStartOffset,
338 : dom::Text** outEndNode, int32_t* outEndOffset);
339 : void FindRun(nsINode* aNode, int32_t aOffset, WSFragment** outRun,
340 : bool after);
341 : char16_t GetCharAt(dom::Text* aTextNode, int32_t aOffset);
342 : WSPoint GetWSPointAfter(nsINode* aNode, int32_t aOffset);
343 : WSPoint GetWSPointBefore(nsINode* aNode, int32_t aOffset);
344 : nsresult CheckTrailingNBSPOfRun(WSFragment *aRun);
345 : nsresult CheckTrailingNBSP(WSFragment* aRun, nsINode* aNode,
346 : int32_t aOffset);
347 : nsresult CheckLeadingNBSP(WSFragment* aRun, nsINode* aNode,
348 : int32_t aOffset);
349 :
350 : nsresult Scrub();
351 : bool IsBlockNode(nsINode* aNode);
352 :
353 : // The node passed to our constructor.
354 : nsCOMPtr<nsINode> mNode;
355 : // The offset passed to our contructor.
356 : int32_t mOffset;
357 : // Together, the above represent the point at which we are building up ws info.
358 :
359 : // true if we are in preformatted whitespace context.
360 : bool mPRE;
361 : // Node/offset where ws starts.
362 : nsCOMPtr<nsINode> mStartNode;
363 : int32_t mStartOffset;
364 : // Reason why ws starts (eText, eOtherBlock, etc.).
365 : WSType mStartReason;
366 : // The node that implicated by start reason.
367 : nsCOMPtr<nsINode> mStartReasonNode;
368 :
369 : // Node/offset where ws ends.
370 : nsCOMPtr<nsINode> mEndNode;
371 : int32_t mEndOffset;
372 : // Reason why ws ends (eText, eOtherBlock, etc.).
373 : WSType mEndReason;
374 : // The node that implicated by end reason.
375 : nsCOMPtr<nsINode> mEndReasonNode;
376 :
377 : // Location of first nbsp in ws run, if any.
378 : RefPtr<dom::Text> mFirstNBSPNode;
379 : int32_t mFirstNBSPOffset;
380 :
381 : // Location of last nbsp in ws run, if any.
382 : RefPtr<dom::Text> mLastNBSPNode;
383 : int32_t mLastNBSPOffset;
384 :
385 : // The list of nodes containing ws in this run.
386 : nsTArray<RefPtr<dom::Text>> mNodeArray;
387 :
388 : // The first WSFragment in the run.
389 : WSFragment* mStartRun;
390 : // The last WSFragment in the run, may be same as first.
391 : WSFragment* mEndRun;
392 :
393 : // Non-owning.
394 : HTMLEditor* mHTMLEditor;
395 :
396 : // Opening this class up for pillaging.
397 : friend class HTMLEditRules;
398 : // Opening this class up for more pillaging.
399 : friend class HTMLEditor;
400 : };
401 :
402 : } // namespace mozilla
403 :
404 : #endif // #ifndef WSRunObject_h
|