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 mozilla_TextServicesDocument_h
7 #define mozilla_TextServicesDocument_h
8 
9 #include "nsCOMPtr.h"
10 #include "nsCycleCollectionParticipant.h"
11 #include "nsIEditActionListener.h"
12 #include "nsISupportsImpl.h"
13 #include "nsStringFwd.h"
14 #include "nsTArray.h"
15 #include "nscore.h"
16 
17 class nsIContent;
18 class nsIContentIterator;
19 class nsIDOMCharacterData;
20 class nsIDOMDocument;
21 class nsIDOMNode;
22 class nsIEditor;
23 class nsINode;
24 class nsISelection;
25 class nsISelectionController;
26 class nsITextServicesFilter;
27 class nsRange;
28 
29 namespace mozilla {
30 
31 class OffsetEntry;
32 class TextEditor;
33 
34 /**
35  * The TextServicesDocument presents the document in as a bunch of flattened
36  * text blocks. Each text block can be retrieved as an nsString.
37  */
38 class TextServicesDocument final : public nsIEditActionListener {
39  private:
40   enum class IteratorStatus : uint8_t {
41     // No iterator (I), or iterator doesn't point to anything valid.
42     eDone = 0,
43     // I points to first text node (TN) in current block (CB).
44     eValid,
45     // No TN in CB, I points to first TN in prev block.
46     ePrev,
47     // No TN in CB, I points to first TN in next block.
48     eNext,
49   };
50 
51   nsCOMPtr<nsIDOMDocument> mDOMDocument;
52   nsCOMPtr<nsISelectionController> mSelCon;
53   RefPtr<TextEditor> mTextEditor;
54   nsCOMPtr<nsIContentIterator> mIterator;
55   nsCOMPtr<nsIContent> mPrevTextBlock;
56   nsCOMPtr<nsIContent> mNextTextBlock;
57   nsTArray<OffsetEntry*> mOffsetTable;
58   RefPtr<nsRange> mExtent;
59   nsCOMPtr<nsITextServicesFilter> mTxtSvcFilter;
60 
61   int32_t mSelStartIndex;
62   int32_t mSelStartOffset;
63   int32_t mSelEndIndex;
64   int32_t mSelEndOffset;
65 
66   IteratorStatus mIteratorStatus;
67 
68  protected:
69   virtual ~TextServicesDocument();
70 
71  public:
72   TextServicesDocument();
73 
74   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
75   NS_DECL_CYCLE_COLLECTION_CLASS(TextServicesDocument)
76 
77   /**
78    * Initializes the text services document to use a particular editor. The
79    * text services document will use the DOM document and presentation shell
80    * used by the editor.
81    *
82    * @param aEditor             The editor to use.
83    */
84   nsresult InitWithEditor(nsIEditor* aEditor);
85 
86   /**
87    * Get the DOM document for the document in use.
88    *
89    * @return aDOMDocument       The dom document.
90    */
91   nsresult GetDocument(nsIDOMDocument** aDOMDocument);
92 
93   /**
94    * Sets the range/extent over which the text services document will iterate.
95    * Note that InitWithEditor() should have been called prior to calling this
96    * method.  If this method is never called, the text services defaults to
97    * iterating over the entire document.
98    *
99    * @param aDOMRange           The range to use. aDOMRange must point to a
100    *                            valid range object.
101    */
102   nsresult SetExtent(nsRange* aRange);
103 
104   /**
105    * Expands the end points of the range so that it spans complete words.  This
106    * call does not change any internal state of the text services document.
107    *
108    * @param aDOMRange           The range to be expanded/adjusted.
109    */
110   nsresult ExpandRangeToWordBoundaries(nsRange* aRange);
111 
112   /**
113    * Sets the filter to be used while iterating over content.
114    *
115    * @param aFilter             The filter to be used while iterating over
116    *                            content.
117    */
118   nsresult SetFilter(nsITextServicesFilter* aFilter);
119 
120   /**
121    * Returns the text in the current text block.
122    *
123    * @param aStr                [OUT] This will contain the text.
124    */
125   nsresult GetCurrentTextBlock(nsString* aStr);
126 
127   /**
128    * Tells the document to point to the first text block in the document.  This
129    * method does not adjust the current cursor position or selection.
130    */
131   nsresult FirstBlock();
132 
133   enum class BlockSelectionStatus {
134     // There is no text block (TB) in or before the selection (S).
135     eBlockNotFound = 0,
136     // No TB in S, but found one before/after S.
137     eBlockOutside,
138     // S extends beyond the start and end of TB.
139     eBlockInside,
140     // TB contains entire S.
141     eBlockContains,
142     // S begins or ends in TB but extends outside of TB.
143     eBlockPartial,
144   };
145 
146   /**
147    * Tells the document to point to the last text block that contains the
148    * current selection or caret.
149    *
150    * @param aSelectionStatus    [OUT] This will contain the text block
151    *                            selection status.
152    * @param aSelectionOffset    [OUT] This will contain the offset into the
153    *                            string returned by GetCurrentTextBlock() where
154    *                            the selection begins.
155    * @param aLength             [OUT] This will contain the number of
156    *                            characters that are selected in the string.
157    */
158   nsresult LastSelectedBlock(BlockSelectionStatus* aSelStatus,
159                              int32_t* aSelOffset, int32_t* aSelLength);
160 
161   /**
162    * Tells the document to point to the text block before the current one.
163    * This method will return NS_OK, even if there is no previous block.
164    * Callers should call IsDone() to check if we have gone beyond the first
165    * text block in the document.
166    */
167   nsresult PrevBlock();
168 
169   /**
170    * Tells the document to point to the text block after the current one.
171    * This method will return NS_OK, even if there is no next block. Callers
172    * should call IsDone() to check if we have gone beyond the last text block
173    * in the document.
174    */
175   nsresult NextBlock();
176 
177   /**
178    * IsDone() will always set aIsDone == false unless the document contains
179    * no text, PrevBlock() was called while the document was already pointing
180    * to the first text block in the document, or NextBlock() was called while
181    * the document was already pointing to the last text block in the document.
182    *
183    * @param aIsDone             [OUT] This will contain the result.
184    */
185   nsresult IsDone(bool* aIsDone);
186 
187   /**
188    * SetSelection() allows the caller to set the selection based on an offset
189    * into the string returned by GetCurrentTextBlock().  A length of zero
190    * places the cursor at that offset. A positive non-zero length "n" selects
191    * n characters in the string.
192    *
193    * @param aOffset             Offset into string returned by
194    *                            GetCurrentTextBlock().
195    * @param aLength             Number of characters selected.
196    */
197   nsresult SetSelection(int32_t aOffset, int32_t aLength);
198 
199   /**
200    * Scrolls the document so that the current selection is visible.
201    */
202   nsresult ScrollSelectionIntoView();
203 
204   /**
205    * Deletes the text selected by SetSelection(). Calling DeleteSelection()
206    * with nothing selected, or with a collapsed selection (cursor) does
207    * nothing and returns NS_OK.
208    */
209   nsresult DeleteSelection();
210 
211   /**
212    * Inserts the given text at the current cursor position.  If there is a
213    * selection, it will be deleted before the text is inserted.
214    */
215   nsresult InsertText(const nsString* aText);
216 
217   /**
218    * nsIEditActionListener method implementations.
219    */
220   NS_DECL_NSIEDITACTIONLISTENER
221 
222   /**
223    * Actual edit action listeners.  When you add new method here for listening
224    * to new edit action, you need to make it called by EditorBase.
225    * Additionally, you need to call it from proper method of
226    * nsIEditActionListener too because if this is created not for inline
227    * spell checker of the editor, edit actions will be notified via
228    * nsIEditActionListener (slow path, though).
229    */
230   void DidDeleteNode(nsINode* aChild);
231   void DidJoinNodes(nsINode& aLeftNode, nsINode& aRightNode);
232 
233   static nsresult GetRangeEndPoints(nsRange* aRange, nsINode** aStartContainer,
234                                     int32_t* aStartOffset,
235                                     nsINode** aEndContainer,
236                                     int32_t* aEndOffset);
237 
238  private:
239   nsresult CreateContentIterator(nsRange* aRange,
240                                  nsIContentIterator** aIterator);
241 
242   already_AddRefed<nsINode> GetDocumentContentRootNode();
243   already_AddRefed<nsRange> CreateDocumentContentRange();
244   already_AddRefed<nsRange> CreateDocumentContentRootToNodeOffsetRange(
245       nsINode* aParent, uint32_t aOffset, bool aToStart);
246   nsresult CreateDocumentContentIterator(nsIContentIterator** aIterator);
247 
248   nsresult AdjustContentIterator();
249 
250   static nsresult FirstTextNode(nsIContentIterator* aIterator,
251                                 IteratorStatus* aIteratorStatus);
252   static nsresult LastTextNode(nsIContentIterator* aIterator,
253                                IteratorStatus* aIteratorStatus);
254 
255   static nsresult FirstTextNodeInCurrentBlock(nsIContentIterator* aIterator);
256   static nsresult FirstTextNodeInPrevBlock(nsIContentIterator* aIterator);
257   static nsresult FirstTextNodeInNextBlock(nsIContentIterator* aIterator);
258 
259   nsresult GetFirstTextNodeInPrevBlock(nsIContent** aContent);
260   nsresult GetFirstTextNodeInNextBlock(nsIContent** aContent);
261 
262   static bool IsBlockNode(nsIContent* aContent);
263   static bool IsTextNode(nsIContent* aContent);
264   static bool IsTextNode(nsIDOMNode* aNode);
265 
266   static bool DidSkip(nsIContentIterator* aFilteredIter);
267   static void ClearDidSkip(nsIContentIterator* aFilteredIter);
268 
269   static bool HasSameBlockNodeParent(nsIContent* aContent1,
270                                      nsIContent* aContent2);
271 
272   nsresult SetSelectionInternal(int32_t aOffset, int32_t aLength,
273                                 bool aDoUpdate);
274   nsresult GetSelection(BlockSelectionStatus* aSelStatus, int32_t* aSelOffset,
275                         int32_t* aSelLength);
276   nsresult GetCollapsedSelection(BlockSelectionStatus* aSelStatus,
277                                  int32_t* aSelOffset, int32_t* aSelLength);
278   nsresult GetUncollapsedSelection(BlockSelectionStatus* aSelStatus,
279                                    int32_t* aSelOffset, int32_t* aSelLength);
280 
281   bool SelectionIsCollapsed();
282   bool SelectionIsValid();
283 
284   static nsresult CreateOffsetTable(nsTArray<OffsetEntry*>* aOffsetTable,
285                                     nsIContentIterator* aIterator,
286                                     IteratorStatus* aIteratorStatus,
287                                     nsRange* aIterRange, nsString* aStr);
288   static nsresult ClearOffsetTable(nsTArray<OffsetEntry*>* aOffsetTable);
289 
290   static nsresult NodeHasOffsetEntry(nsTArray<OffsetEntry*>* aOffsetTable,
291                                      nsINode* aNode, bool* aHasEntry,
292                                      int32_t* aEntryIndex);
293 
294   nsresult RemoveInvalidOffsetEntries();
295   nsresult SplitOffsetEntry(int32_t aTableIndex, int32_t aOffsetIntoEntry);
296 
297   static nsresult FindWordBounds(nsTArray<OffsetEntry*>* aOffsetTable,
298                                  nsString* aBlockStr, nsINode* aNode,
299                                  int32_t aNodeOffset, nsINode** aWordStartNode,
300                                  int32_t* aWordStartOffset,
301                                  nsINode** aWordEndNode,
302                                  int32_t* aWordEndOffset);
303 };
304 
305 }  // namespace mozilla
306 
307 #endif  // #ifndef mozilla_TextServicesDocument_h
308