|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 sw=2 et tw=79: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include <stdlib.h> |
|
8 |
|
9 #include "mozilla/Assertions.h" |
|
10 #include "mozilla/MathAlgorithms.h" |
|
11 #include "mozilla/Preferences.h" |
|
12 #include "mozilla/dom/Selection.h" |
|
13 #include "mozilla/dom/Element.h" |
|
14 #include "mozilla/mozalloc.h" |
|
15 #include "nsAString.h" |
|
16 #include "nsAlgorithm.h" |
|
17 #include "nsCOMArray.h" |
|
18 #include "nsCRT.h" |
|
19 #include "nsCRTGlue.h" |
|
20 #include "nsComponentManagerUtils.h" |
|
21 #include "nsContentUtils.h" |
|
22 #include "nsDebug.h" |
|
23 #include "nsEditProperty.h" |
|
24 #include "nsEditor.h" |
|
25 #include "nsEditorUtils.h" |
|
26 #include "nsError.h" |
|
27 #include "nsGkAtoms.h" |
|
28 #include "nsHTMLCSSUtils.h" |
|
29 #include "nsHTMLEditRules.h" |
|
30 #include "nsHTMLEditUtils.h" |
|
31 #include "nsHTMLEditor.h" |
|
32 #include "nsIAtom.h" |
|
33 #include "nsIContent.h" |
|
34 #include "nsIContentIterator.h" |
|
35 #include "nsID.h" |
|
36 #include "nsIDOMCharacterData.h" |
|
37 #include "nsIDOMDocument.h" |
|
38 #include "nsIDOMElement.h" |
|
39 #include "nsIDOMNode.h" |
|
40 #include "nsIDOMRange.h" |
|
41 #include "nsIDOMText.h" |
|
42 #include "nsIHTMLAbsPosEditor.h" |
|
43 #include "nsIHTMLDocument.h" |
|
44 #include "nsINode.h" |
|
45 #include "nsISelection.h" |
|
46 #include "nsISelectionPrivate.h" |
|
47 #include "nsLiteralString.h" |
|
48 #include "nsPlaintextEditor.h" |
|
49 #include "nsRange.h" |
|
50 #include "nsReadableUtils.h" |
|
51 #include "nsString.h" |
|
52 #include "nsStringFwd.h" |
|
53 #include "nsTArray.h" |
|
54 #include "nsTextEditUtils.h" |
|
55 #include "nsThreadUtils.h" |
|
56 #include "nsUnicharUtils.h" |
|
57 #include "nsWSRunObject.h" |
|
58 #include <algorithm> |
|
59 |
|
60 class nsISupports; |
|
61 class nsRulesInfo; |
|
62 |
|
63 using namespace mozilla; |
|
64 using namespace mozilla::dom; |
|
65 |
|
66 //const static char* kMOZEditorBogusNodeAttr="MOZ_EDITOR_BOGUS_NODE"; |
|
67 //const static char* kMOZEditorBogusNodeValue="TRUE"; |
|
68 |
|
69 enum |
|
70 { |
|
71 kLonely = 0, |
|
72 kPrevSib = 1, |
|
73 kNextSib = 2, |
|
74 kBothSibs = 3 |
|
75 }; |
|
76 |
|
77 /******************************************************** |
|
78 * first some helpful functors we will use |
|
79 ********************************************************/ |
|
80 |
|
81 static bool IsBlockNode(nsIDOMNode* node) |
|
82 { |
|
83 bool isBlock (false); |
|
84 nsHTMLEditor::NodeIsBlockStatic(node, &isBlock); |
|
85 return isBlock; |
|
86 } |
|
87 |
|
88 static bool IsInlineNode(nsIDOMNode* node) |
|
89 { |
|
90 return !IsBlockNode(node); |
|
91 } |
|
92 |
|
93 static bool |
|
94 IsStyleCachePreservingAction(EditAction action) |
|
95 { |
|
96 return action == EditAction::deleteSelection || |
|
97 action == EditAction::insertBreak || |
|
98 action == EditAction::makeList || |
|
99 action == EditAction::indent || |
|
100 action == EditAction::outdent || |
|
101 action == EditAction::align || |
|
102 action == EditAction::makeBasicBlock || |
|
103 action == EditAction::removeList || |
|
104 action == EditAction::makeDefListItem || |
|
105 action == EditAction::insertElement || |
|
106 action == EditAction::insertQuotation; |
|
107 } |
|
108 |
|
109 class nsTableCellAndListItemFunctor : public nsBoolDomIterFunctor |
|
110 { |
|
111 public: |
|
112 virtual bool operator()(nsIDOMNode* aNode) // used to build list of all li's, td's & th's iterator covers |
|
113 { |
|
114 if (nsHTMLEditUtils::IsTableCell(aNode)) return true; |
|
115 if (nsHTMLEditUtils::IsListItem(aNode)) return true; |
|
116 return false; |
|
117 } |
|
118 }; |
|
119 |
|
120 class nsBRNodeFunctor : public nsBoolDomIterFunctor |
|
121 { |
|
122 public: |
|
123 virtual bool operator()(nsIDOMNode* aNode) |
|
124 { |
|
125 if (nsTextEditUtils::IsBreak(aNode)) return true; |
|
126 return false; |
|
127 } |
|
128 }; |
|
129 |
|
130 class nsEmptyEditableFunctor : public nsBoolDomIterFunctor |
|
131 { |
|
132 public: |
|
133 nsEmptyEditableFunctor(nsHTMLEditor* editor) : mHTMLEditor(editor) {} |
|
134 virtual bool operator()(nsIDOMNode* aNode) |
|
135 { |
|
136 if (mHTMLEditor->IsEditable(aNode) && |
|
137 (nsHTMLEditUtils::IsListItem(aNode) || |
|
138 nsHTMLEditUtils::IsTableCellOrCaption(aNode))) |
|
139 { |
|
140 bool bIsEmptyNode; |
|
141 nsresult res = mHTMLEditor->IsEmptyNode(aNode, &bIsEmptyNode, false, false); |
|
142 NS_ENSURE_SUCCESS(res, false); |
|
143 if (bIsEmptyNode) |
|
144 return true; |
|
145 } |
|
146 return false; |
|
147 } |
|
148 protected: |
|
149 nsHTMLEditor* mHTMLEditor; |
|
150 }; |
|
151 |
|
152 class nsEditableTextFunctor : public nsBoolDomIterFunctor |
|
153 { |
|
154 public: |
|
155 nsEditableTextFunctor(nsHTMLEditor* editor) : mHTMLEditor(editor) {} |
|
156 virtual bool operator()(nsIDOMNode* aNode) |
|
157 { |
|
158 if (nsEditor::IsTextNode(aNode) && mHTMLEditor->IsEditable(aNode)) |
|
159 { |
|
160 return true; |
|
161 } |
|
162 return false; |
|
163 } |
|
164 protected: |
|
165 nsHTMLEditor* mHTMLEditor; |
|
166 }; |
|
167 |
|
168 |
|
169 /******************************************************** |
|
170 * Constructor/Destructor |
|
171 ********************************************************/ |
|
172 |
|
173 nsHTMLEditRules::nsHTMLEditRules() |
|
174 { |
|
175 InitFields(); |
|
176 } |
|
177 |
|
178 void |
|
179 nsHTMLEditRules::InitFields() |
|
180 { |
|
181 mHTMLEditor = nullptr; |
|
182 mDocChangeRange = nullptr; |
|
183 mListenerEnabled = true; |
|
184 mReturnInEmptyLIKillsList = true; |
|
185 mDidDeleteSelection = false; |
|
186 mDidRangedDelete = false; |
|
187 mRestoreContentEditableCount = false; |
|
188 mUtilRange = nullptr; |
|
189 mJoinOffset = 0; |
|
190 mNewBlock = nullptr; |
|
191 mRangeItem = new nsRangeStore(); |
|
192 // populate mCachedStyles |
|
193 mCachedStyles[0] = StyleCache(nsEditProperty::b, EmptyString(), EmptyString()); |
|
194 mCachedStyles[1] = StyleCache(nsEditProperty::i, EmptyString(), EmptyString()); |
|
195 mCachedStyles[2] = StyleCache(nsEditProperty::u, EmptyString(), EmptyString()); |
|
196 mCachedStyles[3] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("face"), EmptyString()); |
|
197 mCachedStyles[4] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("size"), EmptyString()); |
|
198 mCachedStyles[5] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("color"), EmptyString()); |
|
199 mCachedStyles[6] = StyleCache(nsEditProperty::tt, EmptyString(), EmptyString()); |
|
200 mCachedStyles[7] = StyleCache(nsEditProperty::em, EmptyString(), EmptyString()); |
|
201 mCachedStyles[8] = StyleCache(nsEditProperty::strong, EmptyString(), EmptyString()); |
|
202 mCachedStyles[9] = StyleCache(nsEditProperty::dfn, EmptyString(), EmptyString()); |
|
203 mCachedStyles[10] = StyleCache(nsEditProperty::code, EmptyString(), EmptyString()); |
|
204 mCachedStyles[11] = StyleCache(nsEditProperty::samp, EmptyString(), EmptyString()); |
|
205 mCachedStyles[12] = StyleCache(nsEditProperty::var, EmptyString(), EmptyString()); |
|
206 mCachedStyles[13] = StyleCache(nsEditProperty::cite, EmptyString(), EmptyString()); |
|
207 mCachedStyles[14] = StyleCache(nsEditProperty::abbr, EmptyString(), EmptyString()); |
|
208 mCachedStyles[15] = StyleCache(nsEditProperty::acronym, EmptyString(), EmptyString()); |
|
209 mCachedStyles[16] = StyleCache(nsEditProperty::cssBackgroundColor, EmptyString(), EmptyString()); |
|
210 mCachedStyles[17] = StyleCache(nsEditProperty::sub, EmptyString(), EmptyString()); |
|
211 mCachedStyles[18] = StyleCache(nsEditProperty::sup, EmptyString(), EmptyString()); |
|
212 } |
|
213 |
|
214 nsHTMLEditRules::~nsHTMLEditRules() |
|
215 { |
|
216 // remove ourselves as a listener to edit actions |
|
217 // In some cases, we have already been removed by |
|
218 // ~nsHTMLEditor, in which case we will get a null pointer here |
|
219 // which we ignore. But this allows us to add the ability to |
|
220 // switch rule sets on the fly if we want. |
|
221 if (mHTMLEditor) |
|
222 mHTMLEditor->RemoveEditActionListener(this); |
|
223 } |
|
224 |
|
225 /******************************************************** |
|
226 * XPCOM Cruft |
|
227 ********************************************************/ |
|
228 |
|
229 NS_IMPL_ADDREF_INHERITED(nsHTMLEditRules, nsTextEditRules) |
|
230 NS_IMPL_RELEASE_INHERITED(nsHTMLEditRules, nsTextEditRules) |
|
231 NS_IMPL_QUERY_INTERFACE_INHERITED(nsHTMLEditRules, nsTextEditRules, nsIEditActionListener) |
|
232 |
|
233 |
|
234 /******************************************************** |
|
235 * Public methods |
|
236 ********************************************************/ |
|
237 |
|
238 NS_IMETHODIMP |
|
239 nsHTMLEditRules::Init(nsPlaintextEditor *aEditor) |
|
240 { |
|
241 InitFields(); |
|
242 |
|
243 mHTMLEditor = static_cast<nsHTMLEditor*>(aEditor); |
|
244 nsresult res; |
|
245 |
|
246 // call through to base class Init |
|
247 res = nsTextEditRules::Init(aEditor); |
|
248 NS_ENSURE_SUCCESS(res, res); |
|
249 |
|
250 // cache any prefs we care about |
|
251 static const char kPrefName[] = |
|
252 "editor.html.typing.returnInEmptyListItemClosesList"; |
|
253 nsAdoptingCString returnInEmptyLIKillsList = |
|
254 Preferences::GetCString(kPrefName); |
|
255 |
|
256 // only when "false", becomes FALSE. Otherwise (including empty), TRUE. |
|
257 // XXX Why was this pref designed as a string and not bool? |
|
258 mReturnInEmptyLIKillsList = !returnInEmptyLIKillsList.EqualsLiteral("false"); |
|
259 |
|
260 // make a utility range for use by the listenter |
|
261 nsCOMPtr<nsINode> node = mHTMLEditor->GetRoot(); |
|
262 if (!node) { |
|
263 node = mHTMLEditor->GetDocument(); |
|
264 } |
|
265 |
|
266 NS_ENSURE_STATE(node); |
|
267 |
|
268 mUtilRange = new nsRange(node); |
|
269 |
|
270 // set up mDocChangeRange to be whole doc |
|
271 // temporarily turn off rules sniffing |
|
272 nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this); |
|
273 if (!mDocChangeRange) { |
|
274 mDocChangeRange = new nsRange(node); |
|
275 } |
|
276 |
|
277 if (node->IsElement()) { |
|
278 ErrorResult rv; |
|
279 mDocChangeRange->SelectNode(*node, rv); |
|
280 res = AdjustSpecialBreaks(node); |
|
281 NS_ENSURE_SUCCESS(res, res); |
|
282 } |
|
283 |
|
284 // add ourselves as a listener to edit actions |
|
285 res = mHTMLEditor->AddEditActionListener(this); |
|
286 |
|
287 return res; |
|
288 } |
|
289 |
|
290 NS_IMETHODIMP |
|
291 nsHTMLEditRules::DetachEditor() |
|
292 { |
|
293 if (mHTMLEditor) { |
|
294 mHTMLEditor->RemoveEditActionListener(this); |
|
295 } |
|
296 mHTMLEditor = nullptr; |
|
297 return nsTextEditRules::DetachEditor(); |
|
298 } |
|
299 |
|
300 NS_IMETHODIMP |
|
301 nsHTMLEditRules::BeforeEdit(EditAction action, |
|
302 nsIEditor::EDirection aDirection) |
|
303 { |
|
304 if (mLockRulesSniffing) return NS_OK; |
|
305 |
|
306 nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this); |
|
307 mDidExplicitlySetInterline = false; |
|
308 |
|
309 if (!mActionNesting++) |
|
310 { |
|
311 // clear our flag about if just deleted a range |
|
312 mDidRangedDelete = false; |
|
313 |
|
314 // remember where our selection was before edit action took place: |
|
315 |
|
316 // get selection |
|
317 nsCOMPtr<nsISelection> selection; |
|
318 NS_ENSURE_STATE(mHTMLEditor); |
|
319 nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); |
|
320 NS_ENSURE_SUCCESS(res, res); |
|
321 |
|
322 // get the selection start location |
|
323 nsCOMPtr<nsIDOMNode> selStartNode, selEndNode; |
|
324 int32_t selOffset; |
|
325 NS_ENSURE_STATE(mHTMLEditor); |
|
326 res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selStartNode), &selOffset); |
|
327 NS_ENSURE_SUCCESS(res, res); |
|
328 mRangeItem->startNode = selStartNode; |
|
329 mRangeItem->startOffset = selOffset; |
|
330 |
|
331 // get the selection end location |
|
332 NS_ENSURE_STATE(mHTMLEditor); |
|
333 res = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(selEndNode), &selOffset); |
|
334 NS_ENSURE_SUCCESS(res, res); |
|
335 mRangeItem->endNode = selEndNode; |
|
336 mRangeItem->endOffset = selOffset; |
|
337 |
|
338 // register this range with range updater to track this as we perturb the doc |
|
339 NS_ENSURE_STATE(mHTMLEditor); |
|
340 (mHTMLEditor->mRangeUpdater).RegisterRangeItem(mRangeItem); |
|
341 |
|
342 // clear deletion state bool |
|
343 mDidDeleteSelection = false; |
|
344 |
|
345 // clear out mDocChangeRange and mUtilRange |
|
346 if(mDocChangeRange) |
|
347 { |
|
348 // clear out our accounting of what changed |
|
349 mDocChangeRange->Reset(); |
|
350 } |
|
351 if(mUtilRange) |
|
352 { |
|
353 // ditto for mUtilRange. |
|
354 mUtilRange->Reset(); |
|
355 } |
|
356 |
|
357 // remember current inline styles for deletion and normal insertion operations |
|
358 if (action == EditAction::insertText || |
|
359 action == EditAction::insertIMEText || |
|
360 action == EditAction::deleteSelection || |
|
361 IsStyleCachePreservingAction(action)) { |
|
362 nsCOMPtr<nsIDOMNode> selNode = selStartNode; |
|
363 if (aDirection == nsIEditor::eNext) |
|
364 selNode = selEndNode; |
|
365 res = CacheInlineStyles(selNode); |
|
366 NS_ENSURE_SUCCESS(res, res); |
|
367 } |
|
368 |
|
369 // Stabilize the document against contenteditable count changes |
|
370 NS_ENSURE_STATE(mHTMLEditor); |
|
371 nsCOMPtr<nsIDOMDocument> doc = mHTMLEditor->GetDOMDocument(); |
|
372 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); |
|
373 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc); |
|
374 NS_ENSURE_TRUE(htmlDoc, NS_ERROR_FAILURE); |
|
375 if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) { |
|
376 htmlDoc->ChangeContentEditableCount(nullptr, +1); |
|
377 mRestoreContentEditableCount = true; |
|
378 } |
|
379 |
|
380 // check that selection is in subtree defined by body node |
|
381 ConfirmSelectionInBody(); |
|
382 // let rules remember the top level action |
|
383 mTheAction = action; |
|
384 } |
|
385 return NS_OK; |
|
386 } |
|
387 |
|
388 |
|
389 NS_IMETHODIMP |
|
390 nsHTMLEditRules::AfterEdit(EditAction action, |
|
391 nsIEditor::EDirection aDirection) |
|
392 { |
|
393 if (mLockRulesSniffing) return NS_OK; |
|
394 |
|
395 nsAutoLockRulesSniffing lockIt(this); |
|
396 |
|
397 NS_PRECONDITION(mActionNesting>0, "bad action nesting!"); |
|
398 nsresult res = NS_OK; |
|
399 if (!--mActionNesting) |
|
400 { |
|
401 // do all the tricky stuff |
|
402 res = AfterEditInner(action, aDirection); |
|
403 |
|
404 // free up selectionState range item |
|
405 NS_ENSURE_STATE(mHTMLEditor); |
|
406 (mHTMLEditor->mRangeUpdater).DropRangeItem(mRangeItem); |
|
407 |
|
408 // Reset the contenteditable count to its previous value |
|
409 if (mRestoreContentEditableCount) { |
|
410 NS_ENSURE_STATE(mHTMLEditor); |
|
411 nsCOMPtr<nsIDOMDocument> doc = mHTMLEditor->GetDOMDocument(); |
|
412 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); |
|
413 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc); |
|
414 NS_ENSURE_TRUE(htmlDoc, NS_ERROR_FAILURE); |
|
415 if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) { |
|
416 htmlDoc->ChangeContentEditableCount(nullptr, -1); |
|
417 } |
|
418 mRestoreContentEditableCount = false; |
|
419 } |
|
420 } |
|
421 |
|
422 return res; |
|
423 } |
|
424 |
|
425 |
|
426 nsresult |
|
427 nsHTMLEditRules::AfterEditInner(EditAction action, |
|
428 nsIEditor::EDirection aDirection) |
|
429 { |
|
430 ConfirmSelectionInBody(); |
|
431 if (action == EditAction::ignore) return NS_OK; |
|
432 |
|
433 nsCOMPtr<nsISelection>selection; |
|
434 NS_ENSURE_STATE(mHTMLEditor); |
|
435 nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); |
|
436 NS_ENSURE_SUCCESS(res, res); |
|
437 |
|
438 nsCOMPtr<nsIDOMNode> rangeStartParent, rangeEndParent; |
|
439 int32_t rangeStartOffset = 0, rangeEndOffset = 0; |
|
440 // do we have a real range to act on? |
|
441 bool bDamagedRange = false; |
|
442 if (mDocChangeRange) |
|
443 { |
|
444 mDocChangeRange->GetStartContainer(getter_AddRefs(rangeStartParent)); |
|
445 mDocChangeRange->GetEndContainer(getter_AddRefs(rangeEndParent)); |
|
446 mDocChangeRange->GetStartOffset(&rangeStartOffset); |
|
447 mDocChangeRange->GetEndOffset(&rangeEndOffset); |
|
448 if (rangeStartParent && rangeEndParent) |
|
449 bDamagedRange = true; |
|
450 } |
|
451 |
|
452 if (bDamagedRange && !((action == EditAction::undo) || (action == EditAction::redo))) |
|
453 { |
|
454 // don't let any txns in here move the selection around behind our back. |
|
455 // Note that this won't prevent explicit selection setting from working. |
|
456 NS_ENSURE_STATE(mHTMLEditor); |
|
457 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); |
|
458 |
|
459 // expand the "changed doc range" as needed |
|
460 res = PromoteRange(mDocChangeRange, action); |
|
461 NS_ENSURE_SUCCESS(res, res); |
|
462 |
|
463 // if we did a ranged deletion, make sure we have a place to put caret. |
|
464 // Note we only want to do this if the overall operation was deletion, |
|
465 // not if deletion was done along the way for EditAction::loadHTML, EditAction::insertText, etc. |
|
466 // That's why this is here rather than DidDeleteSelection(). |
|
467 if ((action == EditAction::deleteSelection) && mDidRangedDelete) |
|
468 { |
|
469 res = InsertBRIfNeeded(selection); |
|
470 NS_ENSURE_SUCCESS(res, res); |
|
471 } |
|
472 |
|
473 // add in any needed <br>s, and remove any unneeded ones. |
|
474 res = AdjustSpecialBreaks(); |
|
475 NS_ENSURE_SUCCESS(res, res); |
|
476 |
|
477 // merge any adjacent text nodes |
|
478 if ( (action != EditAction::insertText && |
|
479 action != EditAction::insertIMEText) ) |
|
480 { |
|
481 NS_ENSURE_STATE(mHTMLEditor); |
|
482 res = mHTMLEditor->CollapseAdjacentTextNodes(mDocChangeRange); |
|
483 NS_ENSURE_SUCCESS(res, res); |
|
484 } |
|
485 |
|
486 // clean up any empty nodes in the selection |
|
487 res = RemoveEmptyNodes(); |
|
488 NS_ENSURE_SUCCESS(res, res); |
|
489 |
|
490 // attempt to transform any unneeded nbsp's into spaces after doing various operations |
|
491 if ((action == EditAction::insertText) || |
|
492 (action == EditAction::insertIMEText) || |
|
493 (action == EditAction::deleteSelection) || |
|
494 (action == EditAction::insertBreak) || |
|
495 (action == EditAction::htmlPaste || |
|
496 (action == EditAction::loadHTML))) |
|
497 { |
|
498 res = AdjustWhitespace(selection); |
|
499 NS_ENSURE_SUCCESS(res, res); |
|
500 |
|
501 // also do this for original selection endpoints. |
|
502 NS_ENSURE_STATE(mHTMLEditor); |
|
503 nsWSRunObject(mHTMLEditor, mRangeItem->startNode, |
|
504 mRangeItem->startOffset).AdjustWhitespace(); |
|
505 // we only need to handle old selection endpoint if it was different from start |
|
506 if (mRangeItem->startNode != mRangeItem->endNode || |
|
507 mRangeItem->startOffset != mRangeItem->endOffset) { |
|
508 NS_ENSURE_STATE(mHTMLEditor); |
|
509 nsWSRunObject(mHTMLEditor, mRangeItem->endNode, |
|
510 mRangeItem->endOffset).AdjustWhitespace(); |
|
511 } |
|
512 } |
|
513 |
|
514 // if we created a new block, make sure selection lands in it |
|
515 if (mNewBlock) |
|
516 { |
|
517 res = PinSelectionToNewBlock(selection); |
|
518 mNewBlock = 0; |
|
519 } |
|
520 |
|
521 // adjust selection for insert text, html paste, and delete actions |
|
522 if ((action == EditAction::insertText) || |
|
523 (action == EditAction::insertIMEText) || |
|
524 (action == EditAction::deleteSelection) || |
|
525 (action == EditAction::insertBreak) || |
|
526 (action == EditAction::htmlPaste || |
|
527 (action == EditAction::loadHTML))) |
|
528 { |
|
529 res = AdjustSelection(selection, aDirection); |
|
530 NS_ENSURE_SUCCESS(res, res); |
|
531 } |
|
532 |
|
533 // check for any styles which were removed inappropriately |
|
534 if (action == EditAction::insertText || |
|
535 action == EditAction::insertIMEText || |
|
536 action == EditAction::deleteSelection || |
|
537 IsStyleCachePreservingAction(action)) { |
|
538 NS_ENSURE_STATE(mHTMLEditor); |
|
539 mHTMLEditor->mTypeInState->UpdateSelState(selection); |
|
540 res = ReapplyCachedStyles(); |
|
541 NS_ENSURE_SUCCESS(res, res); |
|
542 ClearCachedStyles(); |
|
543 } |
|
544 } |
|
545 |
|
546 NS_ENSURE_STATE(mHTMLEditor); |
|
547 |
|
548 res = mHTMLEditor->HandleInlineSpellCheck(action, selection, |
|
549 mRangeItem->startNode, |
|
550 mRangeItem->startOffset, |
|
551 rangeStartParent, rangeStartOffset, |
|
552 rangeEndParent, rangeEndOffset); |
|
553 NS_ENSURE_SUCCESS(res, res); |
|
554 |
|
555 // detect empty doc |
|
556 res = CreateBogusNodeIfNeeded(selection); |
|
557 |
|
558 // adjust selection HINT if needed |
|
559 NS_ENSURE_SUCCESS(res, res); |
|
560 |
|
561 if (!mDidExplicitlySetInterline) |
|
562 { |
|
563 res = CheckInterlinePosition(selection); |
|
564 } |
|
565 |
|
566 return res; |
|
567 } |
|
568 |
|
569 |
|
570 NS_IMETHODIMP |
|
571 nsHTMLEditRules::WillDoAction(Selection* aSelection, |
|
572 nsRulesInfo* aInfo, |
|
573 bool* aCancel, |
|
574 bool* aHandled) |
|
575 { |
|
576 MOZ_ASSERT(aInfo && aCancel && aHandled); |
|
577 |
|
578 *aCancel = false; |
|
579 *aHandled = false; |
|
580 |
|
581 // my kingdom for dynamic cast |
|
582 nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo); |
|
583 |
|
584 // Deal with actions for which we don't need to check whether the selection is |
|
585 // editable. |
|
586 if (info->action == EditAction::outputText || |
|
587 info->action == EditAction::undo || |
|
588 info->action == EditAction::redo) { |
|
589 return nsTextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled); |
|
590 } |
|
591 |
|
592 // Nothing to do if there's no selection to act on |
|
593 if (!aSelection) { |
|
594 return NS_OK; |
|
595 } |
|
596 NS_ENSURE_TRUE(aSelection->GetRangeCount(), NS_OK); |
|
597 |
|
598 nsRefPtr<nsRange> range = aSelection->GetRangeAt(0); |
|
599 nsCOMPtr<nsINode> selStartNode = range->GetStartParent(); |
|
600 |
|
601 NS_ENSURE_STATE(mHTMLEditor); |
|
602 if (!mHTMLEditor->IsModifiableNode(selStartNode)) { |
|
603 *aCancel = true; |
|
604 return NS_OK; |
|
605 } |
|
606 |
|
607 nsCOMPtr<nsINode> selEndNode = range->GetEndParent(); |
|
608 |
|
609 if (selStartNode != selEndNode) { |
|
610 NS_ENSURE_STATE(mHTMLEditor); |
|
611 if (!mHTMLEditor->IsModifiableNode(selEndNode)) { |
|
612 *aCancel = true; |
|
613 return NS_OK; |
|
614 } |
|
615 |
|
616 NS_ENSURE_STATE(mHTMLEditor); |
|
617 if (!mHTMLEditor->IsModifiableNode(range->GetCommonAncestor())) { |
|
618 *aCancel = true; |
|
619 return NS_OK; |
|
620 } |
|
621 } |
|
622 |
|
623 switch (info->action) { |
|
624 case EditAction::insertText: |
|
625 case EditAction::insertIMEText: |
|
626 return WillInsertText(info->action, aSelection, aCancel, aHandled, |
|
627 info->inString, info->outString, info->maxLength); |
|
628 case EditAction::loadHTML: |
|
629 return WillLoadHTML(aSelection, aCancel); |
|
630 case EditAction::insertBreak: |
|
631 return WillInsertBreak(aSelection, aCancel, aHandled); |
|
632 case EditAction::deleteSelection: |
|
633 return WillDeleteSelection(aSelection, info->collapsedAction, |
|
634 info->stripWrappers, aCancel, aHandled); |
|
635 case EditAction::makeList: |
|
636 return WillMakeList(aSelection, info->blockType, info->entireList, |
|
637 info->bulletType, aCancel, aHandled); |
|
638 case EditAction::indent: |
|
639 return WillIndent(aSelection, aCancel, aHandled); |
|
640 case EditAction::outdent: |
|
641 return WillOutdent(aSelection, aCancel, aHandled); |
|
642 case EditAction::setAbsolutePosition: |
|
643 return WillAbsolutePosition(aSelection, aCancel, aHandled); |
|
644 case EditAction::removeAbsolutePosition: |
|
645 return WillRemoveAbsolutePosition(aSelection, aCancel, aHandled); |
|
646 case EditAction::align: |
|
647 return WillAlign(aSelection, info->alignType, aCancel, aHandled); |
|
648 case EditAction::makeBasicBlock: |
|
649 return WillMakeBasicBlock(aSelection, info->blockType, aCancel, aHandled); |
|
650 case EditAction::removeList: |
|
651 return WillRemoveList(aSelection, info->bOrdered, aCancel, aHandled); |
|
652 case EditAction::makeDefListItem: |
|
653 return WillMakeDefListItem(aSelection, info->blockType, info->entireList, |
|
654 aCancel, aHandled); |
|
655 case EditAction::insertElement: |
|
656 return WillInsert(aSelection, aCancel); |
|
657 case EditAction::decreaseZIndex: |
|
658 return WillRelativeChangeZIndex(aSelection, -1, aCancel, aHandled); |
|
659 case EditAction::increaseZIndex: |
|
660 return WillRelativeChangeZIndex(aSelection, 1, aCancel, aHandled); |
|
661 default: |
|
662 return nsTextEditRules::WillDoAction(aSelection, aInfo, |
|
663 aCancel, aHandled); |
|
664 } |
|
665 } |
|
666 |
|
667 |
|
668 NS_IMETHODIMP |
|
669 nsHTMLEditRules::DidDoAction(nsISelection *aSelection, |
|
670 nsRulesInfo *aInfo, nsresult aResult) |
|
671 { |
|
672 nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo); |
|
673 switch (info->action) |
|
674 { |
|
675 case EditAction::insertBreak: |
|
676 return DidInsertBreak(aSelection, aResult); |
|
677 case EditAction::deleteSelection: |
|
678 return DidDeleteSelection(aSelection, info->collapsedAction, aResult); |
|
679 case EditAction::makeBasicBlock: |
|
680 case EditAction::indent: |
|
681 case EditAction::outdent: |
|
682 case EditAction::align: |
|
683 return DidMakeBasicBlock(aSelection, aInfo, aResult); |
|
684 case EditAction::setAbsolutePosition: { |
|
685 nsresult rv = DidMakeBasicBlock(aSelection, aInfo, aResult); |
|
686 NS_ENSURE_SUCCESS(rv, rv); |
|
687 return DidAbsolutePosition(); |
|
688 } |
|
689 default: |
|
690 // pass thru to nsTextEditRules |
|
691 return nsTextEditRules::DidDoAction(aSelection, aInfo, aResult); |
|
692 } |
|
693 } |
|
694 |
|
695 nsresult |
|
696 nsHTMLEditRules::GetListState(bool *aMixed, bool *aOL, bool *aUL, bool *aDL) |
|
697 { |
|
698 NS_ENSURE_TRUE(aMixed && aOL && aUL && aDL, NS_ERROR_NULL_POINTER); |
|
699 *aMixed = false; |
|
700 *aOL = false; |
|
701 *aUL = false; |
|
702 *aDL = false; |
|
703 bool bNonList = false; |
|
704 |
|
705 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
706 nsresult res = GetListActionNodes(arrayOfNodes, false, true); |
|
707 NS_ENSURE_SUCCESS(res, res); |
|
708 |
|
709 // Examine list type for nodes in selection. |
|
710 int32_t listCount = arrayOfNodes.Count(); |
|
711 for (int32_t i = listCount - 1; i >= 0; --i) { |
|
712 nsIDOMNode* curDOMNode = arrayOfNodes[i]; |
|
713 nsCOMPtr<dom::Element> curElement = do_QueryInterface(curDOMNode); |
|
714 |
|
715 if (!curElement) { |
|
716 bNonList = true; |
|
717 } else if (curElement->IsHTML(nsGkAtoms::ul)) { |
|
718 *aUL = true; |
|
719 } else if (curElement->IsHTML(nsGkAtoms::ol)) { |
|
720 *aOL = true; |
|
721 } else if (curElement->IsHTML(nsGkAtoms::li)) { |
|
722 if (dom::Element* parent = curElement->GetParentElement()) { |
|
723 if (parent->IsHTML(nsGkAtoms::ul)) { |
|
724 *aUL = true; |
|
725 } else if (parent->IsHTML(nsGkAtoms::ol)) { |
|
726 *aOL = true; |
|
727 } |
|
728 } |
|
729 } else if (curElement->IsHTML(nsGkAtoms::dl) || |
|
730 curElement->IsHTML(nsGkAtoms::dt) || |
|
731 curElement->IsHTML(nsGkAtoms::dd)) { |
|
732 *aDL = true; |
|
733 } else { |
|
734 bNonList = true; |
|
735 } |
|
736 } |
|
737 |
|
738 // hokey arithmetic with booleans |
|
739 if ((*aUL + *aOL + *aDL + bNonList) > 1) { |
|
740 *aMixed = true; |
|
741 } |
|
742 |
|
743 return NS_OK; |
|
744 } |
|
745 |
|
746 nsresult |
|
747 nsHTMLEditRules::GetListItemState(bool *aMixed, bool *aLI, bool *aDT, bool *aDD) |
|
748 { |
|
749 NS_ENSURE_TRUE(aMixed && aLI && aDT && aDD, NS_ERROR_NULL_POINTER); |
|
750 *aMixed = false; |
|
751 *aLI = false; |
|
752 *aDT = false; |
|
753 *aDD = false; |
|
754 bool bNonList = false; |
|
755 |
|
756 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
757 nsresult res = GetListActionNodes(arrayOfNodes, false, true); |
|
758 NS_ENSURE_SUCCESS(res, res); |
|
759 |
|
760 // examine list type for nodes in selection |
|
761 int32_t listCount = arrayOfNodes.Count(); |
|
762 for (int32_t i = listCount - 1; i >= 0; --i) { |
|
763 nsIDOMNode* curNode = arrayOfNodes[i]; |
|
764 nsCOMPtr<dom::Element> element = do_QueryInterface(curNode); |
|
765 if (!element) { |
|
766 bNonList = true; |
|
767 } else if (element->IsHTML(nsGkAtoms::ul) || |
|
768 element->IsHTML(nsGkAtoms::ol) || |
|
769 element->IsHTML(nsGkAtoms::li)) { |
|
770 *aLI = true; |
|
771 } else if (element->IsHTML(nsGkAtoms::dt)) { |
|
772 *aDT = true; |
|
773 } else if (element->IsHTML(nsGkAtoms::dd)) { |
|
774 *aDD = true; |
|
775 } else if (element->IsHTML(nsGkAtoms::dl)) { |
|
776 // need to look inside dl and see which types of items it has |
|
777 bool bDT, bDD; |
|
778 GetDefinitionListItemTypes(element, &bDT, &bDD); |
|
779 *aDT |= bDT; |
|
780 *aDD |= bDD; |
|
781 } else { |
|
782 bNonList = true; |
|
783 } |
|
784 } |
|
785 |
|
786 // hokey arithmetic with booleans |
|
787 if ( (*aDT + *aDD + bNonList) > 1) *aMixed = true; |
|
788 |
|
789 return NS_OK; |
|
790 } |
|
791 |
|
792 nsresult |
|
793 nsHTMLEditRules::GetAlignment(bool *aMixed, nsIHTMLEditor::EAlignment *aAlign) |
|
794 { |
|
795 // for now, just return first alignment. we'll lie about |
|
796 // if it's mixed. This is for efficiency |
|
797 // given that our current ui doesn't care if it's mixed. |
|
798 // cmanske: NOT TRUE! We would like to pay attention to mixed state |
|
799 // in Format | Align submenu! |
|
800 |
|
801 // this routine assumes that alignment is done ONLY via divs |
|
802 |
|
803 // default alignment is left |
|
804 NS_ENSURE_TRUE(aMixed && aAlign, NS_ERROR_NULL_POINTER); |
|
805 *aMixed = false; |
|
806 *aAlign = nsIHTMLEditor::eLeft; |
|
807 |
|
808 // get selection |
|
809 nsCOMPtr<nsISelection>selection; |
|
810 NS_ENSURE_STATE(mHTMLEditor); |
|
811 nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); |
|
812 NS_ENSURE_SUCCESS(res, res); |
|
813 |
|
814 // get selection location |
|
815 NS_ENSURE_STATE(mHTMLEditor); |
|
816 nsCOMPtr<nsIDOMElement> rootElem = do_QueryInterface(mHTMLEditor->GetRoot()); |
|
817 NS_ENSURE_TRUE(rootElem, NS_ERROR_FAILURE); |
|
818 |
|
819 int32_t offset, rootOffset; |
|
820 nsCOMPtr<nsIDOMNode> parent = nsEditor::GetNodeLocation(rootElem, &rootOffset); |
|
821 NS_ENSURE_STATE(mHTMLEditor); |
|
822 res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(parent), &offset); |
|
823 NS_ENSURE_SUCCESS(res, res); |
|
824 |
|
825 // is the selection collapsed? |
|
826 nsCOMPtr<nsIDOMNode> nodeToExamine; |
|
827 if (selection->Collapsed()) { |
|
828 // if it is, we want to look at 'parent' and its ancestors |
|
829 // for divs with alignment on them |
|
830 nodeToExamine = parent; |
|
831 } |
|
832 else if (!mHTMLEditor) { |
|
833 return NS_ERROR_UNEXPECTED; |
|
834 } |
|
835 else if (mHTMLEditor->IsTextNode(parent)) |
|
836 { |
|
837 // if we are in a text node, then that is the node of interest |
|
838 nodeToExamine = parent; |
|
839 } |
|
840 else if (nsEditor::NodeIsType(parent, nsEditProperty::html) && |
|
841 offset == rootOffset) |
|
842 { |
|
843 // if we have selected the body, let's look at the first editable node |
|
844 NS_ENSURE_STATE(mHTMLEditor); |
|
845 mHTMLEditor->GetNextNode(parent, offset, true, address_of(nodeToExamine)); |
|
846 } |
|
847 else |
|
848 { |
|
849 nsCOMArray<nsIDOMRange> arrayOfRanges; |
|
850 res = GetPromotedRanges(selection, arrayOfRanges, EditAction::align); |
|
851 NS_ENSURE_SUCCESS(res, res); |
|
852 |
|
853 // use these ranges to construct a list of nodes to act on. |
|
854 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
855 res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, |
|
856 EditAction::align, true); |
|
857 NS_ENSURE_SUCCESS(res, res); |
|
858 nodeToExamine = arrayOfNodes.SafeObjectAt(0); |
|
859 } |
|
860 |
|
861 NS_ENSURE_TRUE(nodeToExamine, NS_ERROR_NULL_POINTER); |
|
862 |
|
863 NS_NAMED_LITERAL_STRING(typeAttrName, "align"); |
|
864 nsIAtom *dummyProperty = nullptr; |
|
865 nsCOMPtr<nsIDOMNode> blockParent; |
|
866 NS_ENSURE_STATE(mHTMLEditor); |
|
867 if (mHTMLEditor->IsBlockNode(nodeToExamine)) |
|
868 blockParent = nodeToExamine; |
|
869 else { |
|
870 NS_ENSURE_STATE(mHTMLEditor); |
|
871 blockParent = mHTMLEditor->GetBlockNodeParent(nodeToExamine); |
|
872 } |
|
873 |
|
874 NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE); |
|
875 |
|
876 NS_ENSURE_STATE(mHTMLEditor); |
|
877 if (mHTMLEditor->IsCSSEnabled()) |
|
878 { |
|
879 nsCOMPtr<nsIContent> blockParentContent = do_QueryInterface(blockParent); |
|
880 NS_ENSURE_STATE(mHTMLEditor); |
|
881 if (blockParentContent && |
|
882 mHTMLEditor->mHTMLCSSUtils->IsCSSEditableProperty(blockParentContent, dummyProperty, &typeAttrName)) |
|
883 { |
|
884 // we are in CSS mode and we know how to align this element with CSS |
|
885 nsAutoString value; |
|
886 // let's get the value(s) of text-align or margin-left/margin-right |
|
887 NS_ENSURE_STATE(mHTMLEditor); |
|
888 mHTMLEditor->mHTMLCSSUtils->GetCSSEquivalentToHTMLInlineStyleSet( |
|
889 blockParentContent, dummyProperty, &typeAttrName, value, |
|
890 nsHTMLCSSUtils::eComputed); |
|
891 if (value.EqualsLiteral("center") || |
|
892 value.EqualsLiteral("-moz-center") || |
|
893 value.EqualsLiteral("auto auto")) |
|
894 { |
|
895 *aAlign = nsIHTMLEditor::eCenter; |
|
896 return NS_OK; |
|
897 } |
|
898 if (value.EqualsLiteral("right") || |
|
899 value.EqualsLiteral("-moz-right") || |
|
900 value.EqualsLiteral("auto 0px")) |
|
901 { |
|
902 *aAlign = nsIHTMLEditor::eRight; |
|
903 return NS_OK; |
|
904 } |
|
905 if (value.EqualsLiteral("justify")) |
|
906 { |
|
907 *aAlign = nsIHTMLEditor::eJustify; |
|
908 return NS_OK; |
|
909 } |
|
910 *aAlign = nsIHTMLEditor::eLeft; |
|
911 return NS_OK; |
|
912 } |
|
913 } |
|
914 |
|
915 // check up the ladder for divs with alignment |
|
916 nsCOMPtr<nsIDOMNode> temp = nodeToExamine; |
|
917 bool isFirstNodeToExamine = true; |
|
918 while (nodeToExamine) |
|
919 { |
|
920 if (!isFirstNodeToExamine && nsHTMLEditUtils::IsTable(nodeToExamine)) |
|
921 { |
|
922 // the node to examine is a table and this is not the first node |
|
923 // we examine; let's break here to materialize the 'inline-block' |
|
924 // behaviour of html tables regarding to text alignment |
|
925 return NS_OK; |
|
926 } |
|
927 if (nsHTMLEditUtils::SupportsAlignAttr(nodeToExamine)) |
|
928 { |
|
929 // check for alignment |
|
930 nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(nodeToExamine); |
|
931 if (elem) |
|
932 { |
|
933 nsAutoString typeAttrVal; |
|
934 res = elem->GetAttribute(NS_LITERAL_STRING("align"), typeAttrVal); |
|
935 ToLowerCase(typeAttrVal); |
|
936 if (NS_SUCCEEDED(res) && typeAttrVal.Length()) |
|
937 { |
|
938 if (typeAttrVal.EqualsLiteral("center")) |
|
939 *aAlign = nsIHTMLEditor::eCenter; |
|
940 else if (typeAttrVal.EqualsLiteral("right")) |
|
941 *aAlign = nsIHTMLEditor::eRight; |
|
942 else if (typeAttrVal.EqualsLiteral("justify")) |
|
943 *aAlign = nsIHTMLEditor::eJustify; |
|
944 else |
|
945 *aAlign = nsIHTMLEditor::eLeft; |
|
946 return res; |
|
947 } |
|
948 } |
|
949 } |
|
950 isFirstNodeToExamine = false; |
|
951 res = nodeToExamine->GetParentNode(getter_AddRefs(temp)); |
|
952 if (NS_FAILED(res)) temp = nullptr; |
|
953 nodeToExamine = temp; |
|
954 } |
|
955 return NS_OK; |
|
956 } |
|
957 |
|
958 nsIAtom* MarginPropertyAtomForIndent(nsHTMLCSSUtils* aHTMLCSSUtils, nsIDOMNode* aNode) { |
|
959 nsAutoString direction; |
|
960 aHTMLCSSUtils->GetComputedProperty(aNode, nsEditProperty::cssDirection, direction); |
|
961 return direction.EqualsLiteral("rtl") ? |
|
962 nsEditProperty::cssMarginRight : nsEditProperty::cssMarginLeft; |
|
963 } |
|
964 |
|
965 nsresult |
|
966 nsHTMLEditRules::GetIndentState(bool *aCanIndent, bool *aCanOutdent) |
|
967 { |
|
968 NS_ENSURE_TRUE(aCanIndent && aCanOutdent, NS_ERROR_FAILURE); |
|
969 *aCanIndent = true; |
|
970 *aCanOutdent = false; |
|
971 |
|
972 // get selection |
|
973 nsCOMPtr<nsISelection>selection; |
|
974 NS_ENSURE_STATE(mHTMLEditor); |
|
975 nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); |
|
976 NS_ENSURE_SUCCESS(res, res); |
|
977 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection)); |
|
978 NS_ENSURE_TRUE(selPriv, NS_ERROR_FAILURE); |
|
979 |
|
980 // contruct a list of nodes to act on. |
|
981 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
982 res = GetNodesFromSelection(selection, EditAction::indent, |
|
983 arrayOfNodes, true); |
|
984 NS_ENSURE_SUCCESS(res, res); |
|
985 |
|
986 // examine nodes in selection for blockquotes or list elements; |
|
987 // these we can outdent. Note that we return true for canOutdent |
|
988 // if *any* of the selection is outdentable, rather than all of it. |
|
989 int32_t listCount = arrayOfNodes.Count(); |
|
990 int32_t i; |
|
991 NS_ENSURE_STATE(mHTMLEditor); |
|
992 bool useCSS = mHTMLEditor->IsCSSEnabled(); |
|
993 for (i=listCount-1; i>=0; i--) |
|
994 { |
|
995 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i]; |
|
996 |
|
997 if (nsHTMLEditUtils::IsNodeThatCanOutdent(curNode)) |
|
998 { |
|
999 *aCanOutdent = true; |
|
1000 break; |
|
1001 } |
|
1002 else if (useCSS) { |
|
1003 // we are in CSS mode, indentation is done using the margin-left (or margin-right) property |
|
1004 NS_ENSURE_STATE(mHTMLEditor); |
|
1005 nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, curNode); |
|
1006 nsAutoString value; |
|
1007 // retrieve its specified value |
|
1008 NS_ENSURE_STATE(mHTMLEditor); |
|
1009 mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(curNode, marginProperty, value); |
|
1010 float f; |
|
1011 nsCOMPtr<nsIAtom> unit; |
|
1012 // get its number part and its unit |
|
1013 NS_ENSURE_STATE(mHTMLEditor); |
|
1014 mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit)); |
|
1015 // if the number part is strictly positive, outdent is possible |
|
1016 if (0 < f) { |
|
1017 *aCanOutdent = true; |
|
1018 break; |
|
1019 } |
|
1020 } |
|
1021 } |
|
1022 |
|
1023 if (!*aCanOutdent) |
|
1024 { |
|
1025 // if we haven't found something to outdent yet, also check the parents |
|
1026 // of selection endpoints. We might have a blockquote or list item |
|
1027 // in the parent hierarchy. |
|
1028 |
|
1029 // gather up info we need for test |
|
1030 NS_ENSURE_STATE(mHTMLEditor); |
|
1031 nsCOMPtr<nsIDOMNode> parent, tmp, root = do_QueryInterface(mHTMLEditor->GetRoot()); |
|
1032 NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER); |
|
1033 nsCOMPtr<nsISelection> selection; |
|
1034 int32_t selOffset; |
|
1035 NS_ENSURE_STATE(mHTMLEditor); |
|
1036 res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); |
|
1037 NS_ENSURE_SUCCESS(res, res); |
|
1038 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
1039 |
|
1040 // test start parent hierarchy |
|
1041 NS_ENSURE_STATE(mHTMLEditor); |
|
1042 res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(parent), &selOffset); |
|
1043 NS_ENSURE_SUCCESS(res, res); |
|
1044 while (parent && (parent!=root)) |
|
1045 { |
|
1046 if (nsHTMLEditUtils::IsNodeThatCanOutdent(parent)) |
|
1047 { |
|
1048 *aCanOutdent = true; |
|
1049 break; |
|
1050 } |
|
1051 tmp=parent; |
|
1052 tmp->GetParentNode(getter_AddRefs(parent)); |
|
1053 } |
|
1054 |
|
1055 // test end parent hierarchy |
|
1056 NS_ENSURE_STATE(mHTMLEditor); |
|
1057 res = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(parent), &selOffset); |
|
1058 NS_ENSURE_SUCCESS(res, res); |
|
1059 while (parent && (parent!=root)) |
|
1060 { |
|
1061 if (nsHTMLEditUtils::IsNodeThatCanOutdent(parent)) |
|
1062 { |
|
1063 *aCanOutdent = true; |
|
1064 break; |
|
1065 } |
|
1066 tmp=parent; |
|
1067 tmp->GetParentNode(getter_AddRefs(parent)); |
|
1068 } |
|
1069 } |
|
1070 return res; |
|
1071 } |
|
1072 |
|
1073 |
|
1074 nsresult |
|
1075 nsHTMLEditRules::GetParagraphState(bool *aMixed, nsAString &outFormat) |
|
1076 { |
|
1077 // This routine is *heavily* tied to our ui choices in the paragraph |
|
1078 // style popup. I can't see a way around that. |
|
1079 NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER); |
|
1080 *aMixed = true; |
|
1081 outFormat.Truncate(0); |
|
1082 |
|
1083 bool bMixed = false; |
|
1084 // using "x" as an uninitialized value, since "" is meaningful |
|
1085 nsAutoString formatStr(NS_LITERAL_STRING("x")); |
|
1086 |
|
1087 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
1088 nsresult res = GetParagraphFormatNodes(arrayOfNodes, true); |
|
1089 NS_ENSURE_SUCCESS(res, res); |
|
1090 |
|
1091 // post process list. We need to replace any block nodes that are not format |
|
1092 // nodes with their content. This is so we only have to look "up" the hierarchy |
|
1093 // to find format nodes, instead of both up and down. |
|
1094 int32_t listCount = arrayOfNodes.Count(); |
|
1095 int32_t i; |
|
1096 for (i=listCount-1; i>=0; i--) |
|
1097 { |
|
1098 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i]; |
|
1099 nsAutoString format; |
|
1100 // if it is a known format node we have it easy |
|
1101 if (IsBlockNode(curNode) && !nsHTMLEditUtils::IsFormatNode(curNode)) |
|
1102 { |
|
1103 // arrayOfNodes.RemoveObject(curNode); |
|
1104 res = AppendInnerFormatNodes(arrayOfNodes, curNode); |
|
1105 NS_ENSURE_SUCCESS(res, res); |
|
1106 } |
|
1107 } |
|
1108 |
|
1109 // we might have an empty node list. if so, find selection parent |
|
1110 // and put that on the list |
|
1111 listCount = arrayOfNodes.Count(); |
|
1112 if (!listCount) |
|
1113 { |
|
1114 nsCOMPtr<nsIDOMNode> selNode; |
|
1115 int32_t selOffset; |
|
1116 nsCOMPtr<nsISelection>selection; |
|
1117 NS_ENSURE_STATE(mHTMLEditor); |
|
1118 res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); |
|
1119 NS_ENSURE_SUCCESS(res, res); |
|
1120 NS_ENSURE_STATE(mHTMLEditor); |
|
1121 res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); |
|
1122 NS_ENSURE_SUCCESS(res, res); |
|
1123 NS_ENSURE_TRUE(selNode, NS_ERROR_NULL_POINTER); |
|
1124 arrayOfNodes.AppendObject(selNode); |
|
1125 listCount = 1; |
|
1126 } |
|
1127 |
|
1128 // remember root node |
|
1129 NS_ENSURE_STATE(mHTMLEditor); |
|
1130 nsCOMPtr<nsIDOMElement> rootElem = do_QueryInterface(mHTMLEditor->GetRoot()); |
|
1131 NS_ENSURE_TRUE(rootElem, NS_ERROR_NULL_POINTER); |
|
1132 |
|
1133 // loop through the nodes in selection and examine their paragraph format |
|
1134 for (i=listCount-1; i>=0; i--) |
|
1135 { |
|
1136 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i]; |
|
1137 nsAutoString format; |
|
1138 // if it is a known format node we have it easy |
|
1139 if (nsHTMLEditUtils::IsFormatNode(curNode)) |
|
1140 GetFormatString(curNode, format); |
|
1141 else if (IsBlockNode(curNode)) |
|
1142 { |
|
1143 // this is a div or some other non-format block. |
|
1144 // we should ignore it. Its children were appended to this list |
|
1145 // by AppendInnerFormatNodes() call above. We will get needed |
|
1146 // info when we examine them instead. |
|
1147 continue; |
|
1148 } |
|
1149 else |
|
1150 { |
|
1151 nsCOMPtr<nsIDOMNode> node, tmp = curNode; |
|
1152 tmp->GetParentNode(getter_AddRefs(node)); |
|
1153 while (node) |
|
1154 { |
|
1155 if (node == rootElem) |
|
1156 { |
|
1157 format.Truncate(0); |
|
1158 break; |
|
1159 } |
|
1160 else if (nsHTMLEditUtils::IsFormatNode(node)) |
|
1161 { |
|
1162 GetFormatString(node, format); |
|
1163 break; |
|
1164 } |
|
1165 // else keep looking up |
|
1166 tmp = node; |
|
1167 tmp->GetParentNode(getter_AddRefs(node)); |
|
1168 } |
|
1169 } |
|
1170 |
|
1171 // if this is the first node, we've found, remember it as the format |
|
1172 if (formatStr.EqualsLiteral("x")) |
|
1173 formatStr = format; |
|
1174 // else make sure it matches previously found format |
|
1175 else if (format != formatStr) |
|
1176 { |
|
1177 bMixed = true; |
|
1178 break; |
|
1179 } |
|
1180 } |
|
1181 |
|
1182 *aMixed = bMixed; |
|
1183 outFormat = formatStr; |
|
1184 return res; |
|
1185 } |
|
1186 |
|
1187 nsresult |
|
1188 nsHTMLEditRules::AppendInnerFormatNodes(nsCOMArray<nsIDOMNode>& aArray, |
|
1189 nsIDOMNode *aNode) |
|
1190 { |
|
1191 nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
|
1192 NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); |
|
1193 |
|
1194 return AppendInnerFormatNodes(aArray, node); |
|
1195 } |
|
1196 |
|
1197 nsresult |
|
1198 nsHTMLEditRules::AppendInnerFormatNodes(nsCOMArray<nsIDOMNode>& aArray, |
|
1199 nsINode* aNode) |
|
1200 { |
|
1201 MOZ_ASSERT(aNode); |
|
1202 |
|
1203 // we only need to place any one inline inside this node onto |
|
1204 // the list. They are all the same for purposes of determining |
|
1205 // paragraph style. We use foundInline to track this as we are |
|
1206 // going through the children in the loop below. |
|
1207 bool foundInline = false; |
|
1208 for (nsIContent* child = aNode->GetFirstChild(); |
|
1209 child; |
|
1210 child = child->GetNextSibling()) { |
|
1211 bool isBlock = IsBlockNode(child->AsDOMNode()); |
|
1212 bool isFormat = nsHTMLEditUtils::IsFormatNode(child); |
|
1213 if (isBlock && !isFormat) { |
|
1214 // if it's a div, etc, recurse |
|
1215 AppendInnerFormatNodes(aArray, child); |
|
1216 } else if (isFormat) { |
|
1217 aArray.AppendObject(child->AsDOMNode()); |
|
1218 } else if (!foundInline) { |
|
1219 // if this is the first inline we've found, use it |
|
1220 foundInline = true; |
|
1221 aArray.AppendObject(child->AsDOMNode()); |
|
1222 } |
|
1223 } |
|
1224 return NS_OK; |
|
1225 } |
|
1226 |
|
1227 nsresult |
|
1228 nsHTMLEditRules::GetFormatString(nsIDOMNode *aNode, nsAString &outFormat) |
|
1229 { |
|
1230 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); |
|
1231 |
|
1232 if (nsHTMLEditUtils::IsFormatNode(aNode)) |
|
1233 { |
|
1234 nsCOMPtr<nsIAtom> atom = nsEditor::GetTag(aNode); |
|
1235 atom->ToString(outFormat); |
|
1236 } |
|
1237 else |
|
1238 outFormat.Truncate(); |
|
1239 |
|
1240 return NS_OK; |
|
1241 } |
|
1242 |
|
1243 /******************************************************** |
|
1244 * Protected rules methods |
|
1245 ********************************************************/ |
|
1246 |
|
1247 nsresult |
|
1248 nsHTMLEditRules::WillInsert(nsISelection *aSelection, bool *aCancel) |
|
1249 { |
|
1250 nsresult res = nsTextEditRules::WillInsert(aSelection, aCancel); |
|
1251 NS_ENSURE_SUCCESS(res, res); |
|
1252 |
|
1253 // Adjust selection to prevent insertion after a moz-BR. |
|
1254 // this next only works for collapsed selections right now, |
|
1255 // because selection is a pain to work with when not collapsed. |
|
1256 // (no good way to extend start or end of selection), so we ignore |
|
1257 // those types of selections. |
|
1258 if (!aSelection->Collapsed()) { |
|
1259 return NS_OK; |
|
1260 } |
|
1261 |
|
1262 // if we are after a mozBR in the same block, then move selection |
|
1263 // to be before it |
|
1264 nsCOMPtr<nsIDOMNode> selNode, priorNode; |
|
1265 int32_t selOffset; |
|
1266 // get the (collapsed) selection location |
|
1267 NS_ENSURE_STATE(mHTMLEditor); |
|
1268 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), |
|
1269 &selOffset); |
|
1270 NS_ENSURE_SUCCESS(res, res); |
|
1271 // get prior node |
|
1272 NS_ENSURE_STATE(mHTMLEditor); |
|
1273 res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, |
|
1274 address_of(priorNode)); |
|
1275 if (NS_SUCCEEDED(res) && priorNode && nsTextEditUtils::IsMozBR(priorNode)) |
|
1276 { |
|
1277 nsCOMPtr<nsIDOMNode> block1, block2; |
|
1278 if (IsBlockNode(selNode)) { |
|
1279 block1 = selNode; |
|
1280 } |
|
1281 else { |
|
1282 NS_ENSURE_STATE(mHTMLEditor); |
|
1283 block1 = mHTMLEditor->GetBlockNodeParent(selNode); |
|
1284 } |
|
1285 NS_ENSURE_STATE(mHTMLEditor); |
|
1286 block2 = mHTMLEditor->GetBlockNodeParent(priorNode); |
|
1287 |
|
1288 if (block1 == block2) |
|
1289 { |
|
1290 // if we are here then the selection is right after a mozBR |
|
1291 // that is in the same block as the selection. We need to move |
|
1292 // the selection start to be before the mozBR. |
|
1293 selNode = nsEditor::GetNodeLocation(priorNode, &selOffset); |
|
1294 res = aSelection->Collapse(selNode,selOffset); |
|
1295 NS_ENSURE_SUCCESS(res, res); |
|
1296 } |
|
1297 } |
|
1298 |
|
1299 if (mDidDeleteSelection && |
|
1300 (mTheAction == EditAction::insertText || |
|
1301 mTheAction == EditAction::insertIMEText || |
|
1302 mTheAction == EditAction::deleteSelection)) { |
|
1303 res = ReapplyCachedStyles(); |
|
1304 NS_ENSURE_SUCCESS(res, res); |
|
1305 } |
|
1306 // For most actions we want to clear the cached styles, but there are |
|
1307 // exceptions |
|
1308 if (!IsStyleCachePreservingAction(mTheAction)) { |
|
1309 ClearCachedStyles(); |
|
1310 } |
|
1311 |
|
1312 return NS_OK; |
|
1313 } |
|
1314 |
|
1315 nsresult |
|
1316 nsHTMLEditRules::WillInsertText(EditAction aAction, |
|
1317 Selection* aSelection, |
|
1318 bool *aCancel, |
|
1319 bool *aHandled, |
|
1320 const nsAString *inString, |
|
1321 nsAString *outString, |
|
1322 int32_t aMaxLength) |
|
1323 { |
|
1324 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } |
|
1325 |
|
1326 if (inString->IsEmpty() && aAction != EditAction::insertIMEText) { |
|
1327 // HACK: this is a fix for bug 19395 |
|
1328 // I can't outlaw all empty insertions |
|
1329 // because IME transaction depend on them |
|
1330 // There is more work to do to make the |
|
1331 // world safe for IME. |
|
1332 *aCancel = true; |
|
1333 *aHandled = false; |
|
1334 return NS_OK; |
|
1335 } |
|
1336 |
|
1337 // initialize out param |
|
1338 *aCancel = false; |
|
1339 *aHandled = true; |
|
1340 nsresult res; |
|
1341 nsCOMPtr<nsIDOMNode> selNode; |
|
1342 int32_t selOffset; |
|
1343 |
|
1344 // If the selection isn't collapsed, delete it. Don't delete existing inline |
|
1345 // tags, because we're hopefully going to insert text (bug 787432). |
|
1346 if (!aSelection->Collapsed()) { |
|
1347 NS_ENSURE_STATE(mHTMLEditor); |
|
1348 res = mHTMLEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eNoStrip); |
|
1349 NS_ENSURE_SUCCESS(res, res); |
|
1350 } |
|
1351 |
|
1352 res = WillInsert(aSelection, aCancel); |
|
1353 NS_ENSURE_SUCCESS(res, res); |
|
1354 // initialize out param |
|
1355 // we want to ignore result of WillInsert() |
|
1356 *aCancel = false; |
|
1357 |
|
1358 // we need to get the doc |
|
1359 NS_ENSURE_STATE(mHTMLEditor); |
|
1360 nsCOMPtr<nsIDOMDocument> doc = mHTMLEditor->GetDOMDocument(); |
|
1361 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); |
|
1362 |
|
1363 // for every property that is set, insert a new inline style node |
|
1364 res = CreateStyleForInsertText(aSelection, doc); |
|
1365 NS_ENSURE_SUCCESS(res, res); |
|
1366 |
|
1367 // get the (collapsed) selection location |
|
1368 NS_ENSURE_STATE(mHTMLEditor); |
|
1369 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); |
|
1370 NS_ENSURE_SUCCESS(res, res); |
|
1371 |
|
1372 // dont put text in places that can't have it |
|
1373 NS_ENSURE_STATE(mHTMLEditor); |
|
1374 if (!mHTMLEditor->IsTextNode(selNode) && |
|
1375 (!mHTMLEditor || |
|
1376 !mHTMLEditor->CanContainTag(selNode, nsGkAtoms::textTagName))) { |
|
1377 return NS_ERROR_FAILURE; |
|
1378 } |
|
1379 |
|
1380 if (aAction == EditAction::insertIMEText) { |
|
1381 // Right now the nsWSRunObject code bails on empty strings, but IME needs |
|
1382 // the InsertTextImpl() call to still happen since empty strings are meaningful there. |
|
1383 if (inString->IsEmpty()) |
|
1384 { |
|
1385 NS_ENSURE_STATE(mHTMLEditor); |
|
1386 res = mHTMLEditor->InsertTextImpl(*inString, address_of(selNode), &selOffset, doc); |
|
1387 } |
|
1388 else |
|
1389 { |
|
1390 NS_ENSURE_STATE(mHTMLEditor); |
|
1391 nsWSRunObject wsObj(mHTMLEditor, selNode, selOffset); |
|
1392 res = wsObj.InsertText(*inString, address_of(selNode), &selOffset, doc); |
|
1393 } |
|
1394 NS_ENSURE_SUCCESS(res, res); |
|
1395 } |
|
1396 else // aAction == kInsertText |
|
1397 { |
|
1398 // find where we are |
|
1399 nsCOMPtr<nsIDOMNode> curNode = selNode; |
|
1400 int32_t curOffset = selOffset; |
|
1401 |
|
1402 // is our text going to be PREformatted? |
|
1403 // We remember this so that we know how to handle tabs. |
|
1404 bool isPRE; |
|
1405 NS_ENSURE_STATE(mHTMLEditor); |
|
1406 res = mHTMLEditor->IsPreformatted(selNode, &isPRE); |
|
1407 NS_ENSURE_SUCCESS(res, res); |
|
1408 |
|
1409 // turn off the edit listener: we know how to |
|
1410 // build the "doc changed range" ourselves, and it's |
|
1411 // must faster to do it once here than to track all |
|
1412 // the changes one at a time. |
|
1413 nsAutoLockListener lockit(&mListenerEnabled); |
|
1414 |
|
1415 // don't spaz my selection in subtransactions |
|
1416 NS_ENSURE_STATE(mHTMLEditor); |
|
1417 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); |
|
1418 nsAutoString tString(*inString); |
|
1419 const char16_t *unicodeBuf = tString.get(); |
|
1420 nsCOMPtr<nsIDOMNode> unused; |
|
1421 int32_t pos = 0; |
|
1422 NS_NAMED_LITERAL_STRING(newlineStr, LFSTR); |
|
1423 |
|
1424 // for efficiency, break out the pre case separately. This is because |
|
1425 // its a lot cheaper to search the input string for only newlines than |
|
1426 // it is to search for both tabs and newlines. |
|
1427 if (isPRE || IsPlaintextEditor()) |
|
1428 { |
|
1429 while (unicodeBuf && (pos != -1) && (pos < (int32_t)(*inString).Length())) |
|
1430 { |
|
1431 int32_t oldPos = pos; |
|
1432 int32_t subStrLen; |
|
1433 pos = tString.FindChar(nsCRT::LF, oldPos); |
|
1434 |
|
1435 if (pos != -1) |
|
1436 { |
|
1437 subStrLen = pos - oldPos; |
|
1438 // if first char is newline, then use just it |
|
1439 if (subStrLen == 0) |
|
1440 subStrLen = 1; |
|
1441 } |
|
1442 else |
|
1443 { |
|
1444 subStrLen = tString.Length() - oldPos; |
|
1445 pos = tString.Length(); |
|
1446 } |
|
1447 |
|
1448 nsDependentSubstring subStr(tString, oldPos, subStrLen); |
|
1449 |
|
1450 // is it a return? |
|
1451 if (subStr.Equals(newlineStr)) |
|
1452 { |
|
1453 NS_ENSURE_STATE(mHTMLEditor); |
|
1454 res = mHTMLEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone); |
|
1455 pos++; |
|
1456 } |
|
1457 else |
|
1458 { |
|
1459 NS_ENSURE_STATE(mHTMLEditor); |
|
1460 res = mHTMLEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc); |
|
1461 } |
|
1462 NS_ENSURE_SUCCESS(res, res); |
|
1463 } |
|
1464 } |
|
1465 else |
|
1466 { |
|
1467 NS_NAMED_LITERAL_STRING(tabStr, "\t"); |
|
1468 NS_NAMED_LITERAL_STRING(spacesStr, " "); |
|
1469 char specialChars[] = {TAB, nsCRT::LF, 0}; |
|
1470 while (unicodeBuf && (pos != -1) && (pos < (int32_t)inString->Length())) |
|
1471 { |
|
1472 int32_t oldPos = pos; |
|
1473 int32_t subStrLen; |
|
1474 pos = tString.FindCharInSet(specialChars, oldPos); |
|
1475 |
|
1476 if (pos != -1) |
|
1477 { |
|
1478 subStrLen = pos - oldPos; |
|
1479 // if first char is newline, then use just it |
|
1480 if (subStrLen == 0) |
|
1481 subStrLen = 1; |
|
1482 } |
|
1483 else |
|
1484 { |
|
1485 subStrLen = tString.Length() - oldPos; |
|
1486 pos = tString.Length(); |
|
1487 } |
|
1488 |
|
1489 nsDependentSubstring subStr(tString, oldPos, subStrLen); |
|
1490 NS_ENSURE_STATE(mHTMLEditor); |
|
1491 nsWSRunObject wsObj(mHTMLEditor, curNode, curOffset); |
|
1492 |
|
1493 // is it a tab? |
|
1494 if (subStr.Equals(tabStr)) |
|
1495 { |
|
1496 res = wsObj.InsertText(spacesStr, address_of(curNode), &curOffset, doc); |
|
1497 NS_ENSURE_SUCCESS(res, res); |
|
1498 pos++; |
|
1499 } |
|
1500 // is it a return? |
|
1501 else if (subStr.Equals(newlineStr)) |
|
1502 { |
|
1503 res = wsObj.InsertBreak(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone); |
|
1504 NS_ENSURE_SUCCESS(res, res); |
|
1505 pos++; |
|
1506 } |
|
1507 else |
|
1508 { |
|
1509 res = wsObj.InsertText(subStr, address_of(curNode), &curOffset, doc); |
|
1510 NS_ENSURE_SUCCESS(res, res); |
|
1511 } |
|
1512 NS_ENSURE_SUCCESS(res, res); |
|
1513 } |
|
1514 } |
|
1515 nsCOMPtr<nsISelection> selection(aSelection); |
|
1516 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection)); |
|
1517 selPriv->SetInterlinePosition(false); |
|
1518 if (curNode) aSelection->Collapse(curNode, curOffset); |
|
1519 // manually update the doc changed range so that AfterEdit will clean up |
|
1520 // the correct portion of the document. |
|
1521 if (!mDocChangeRange) |
|
1522 { |
|
1523 nsCOMPtr<nsINode> node = do_QueryInterface(selNode); |
|
1524 NS_ENSURE_STATE(node); |
|
1525 mDocChangeRange = new nsRange(node); |
|
1526 } |
|
1527 res = mDocChangeRange->SetStart(selNode, selOffset); |
|
1528 NS_ENSURE_SUCCESS(res, res); |
|
1529 if (curNode) |
|
1530 res = mDocChangeRange->SetEnd(curNode, curOffset); |
|
1531 else |
|
1532 res = mDocChangeRange->SetEnd(selNode, selOffset); |
|
1533 NS_ENSURE_SUCCESS(res, res); |
|
1534 } |
|
1535 return res; |
|
1536 } |
|
1537 |
|
1538 nsresult |
|
1539 nsHTMLEditRules::WillLoadHTML(nsISelection *aSelection, bool *aCancel) |
|
1540 { |
|
1541 NS_ENSURE_TRUE(aSelection && aCancel, NS_ERROR_NULL_POINTER); |
|
1542 |
|
1543 *aCancel = false; |
|
1544 |
|
1545 // Delete mBogusNode if it exists. If we really need one, |
|
1546 // it will be added during post-processing in AfterEditInner(). |
|
1547 |
|
1548 if (mBogusNode) |
|
1549 { |
|
1550 mEditor->DeleteNode(mBogusNode); |
|
1551 mBogusNode = nullptr; |
|
1552 } |
|
1553 |
|
1554 return NS_OK; |
|
1555 } |
|
1556 |
|
1557 nsresult |
|
1558 nsHTMLEditRules::WillInsertBreak(Selection* aSelection, |
|
1559 bool* aCancel, bool* aHandled) |
|
1560 { |
|
1561 if (!aSelection || !aCancel || !aHandled) { |
|
1562 return NS_ERROR_NULL_POINTER; |
|
1563 } |
|
1564 // initialize out params |
|
1565 *aCancel = false; |
|
1566 *aHandled = false; |
|
1567 |
|
1568 // if the selection isn't collapsed, delete it. |
|
1569 nsresult res = NS_OK; |
|
1570 if (!aSelection->Collapsed()) { |
|
1571 NS_ENSURE_STATE(mHTMLEditor); |
|
1572 res = mHTMLEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip); |
|
1573 NS_ENSURE_SUCCESS(res, res); |
|
1574 } |
|
1575 |
|
1576 res = WillInsert(aSelection, aCancel); |
|
1577 NS_ENSURE_SUCCESS(res, res); |
|
1578 |
|
1579 // initialize out param |
|
1580 // we want to ignore result of WillInsert() |
|
1581 *aCancel = false; |
|
1582 |
|
1583 // split any mailcites in the way. |
|
1584 // should we abort this if we encounter table cell boundaries? |
|
1585 if (IsMailEditor()) { |
|
1586 res = SplitMailCites(aSelection, IsPlaintextEditor(), aHandled); |
|
1587 NS_ENSURE_SUCCESS(res, res); |
|
1588 if (*aHandled) { |
|
1589 return NS_OK; |
|
1590 } |
|
1591 } |
|
1592 |
|
1593 // smart splitting rules |
|
1594 nsCOMPtr<nsIDOMNode> node; |
|
1595 int32_t offset; |
|
1596 |
|
1597 NS_ENSURE_STATE(mHTMLEditor); |
|
1598 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), |
|
1599 &offset); |
|
1600 NS_ENSURE_SUCCESS(res, res); |
|
1601 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); |
|
1602 |
|
1603 // do nothing if the node is read-only |
|
1604 NS_ENSURE_STATE(mHTMLEditor); |
|
1605 if (!mHTMLEditor->IsModifiableNode(node)) { |
|
1606 *aCancel = true; |
|
1607 return NS_OK; |
|
1608 } |
|
1609 |
|
1610 // identify the block |
|
1611 nsCOMPtr<nsIDOMNode> blockParent; |
|
1612 if (IsBlockNode(node)) { |
|
1613 blockParent = node; |
|
1614 } else { |
|
1615 NS_ENSURE_STATE(mHTMLEditor); |
|
1616 blockParent = mHTMLEditor->GetBlockNodeParent(node); |
|
1617 } |
|
1618 NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE); |
|
1619 |
|
1620 // if the active editing host is an inline element, or if the active editing |
|
1621 // host is the block parent itself, just append a br. |
|
1622 NS_ENSURE_STATE(mHTMLEditor); |
|
1623 nsCOMPtr<nsIContent> hostContent = mHTMLEditor->GetActiveEditingHost(); |
|
1624 nsCOMPtr<nsIDOMNode> hostNode = do_QueryInterface(hostContent); |
|
1625 if (!nsEditorUtils::IsDescendantOf(blockParent, hostNode)) { |
|
1626 res = StandardBreakImpl(node, offset, aSelection); |
|
1627 NS_ENSURE_SUCCESS(res, res); |
|
1628 *aHandled = true; |
|
1629 return NS_OK; |
|
1630 } |
|
1631 |
|
1632 // if block is empty, populate with br. (for example, imagine a div that |
|
1633 // contains the word "text". the user selects "text" and types return. |
|
1634 // "text" is deleted leaving an empty block. we want to put in one br to |
|
1635 // make block have a line. then code further below will put in a second br.) |
|
1636 bool isEmpty; |
|
1637 IsEmptyBlock(blockParent, &isEmpty); |
|
1638 if (isEmpty) { |
|
1639 uint32_t blockLen; |
|
1640 NS_ENSURE_STATE(mHTMLEditor); |
|
1641 res = mHTMLEditor->GetLengthOfDOMNode(blockParent, blockLen); |
|
1642 NS_ENSURE_SUCCESS(res, res); |
|
1643 nsCOMPtr<nsIDOMNode> brNode; |
|
1644 NS_ENSURE_STATE(mHTMLEditor); |
|
1645 res = mHTMLEditor->CreateBR(blockParent, blockLen, address_of(brNode)); |
|
1646 NS_ENSURE_SUCCESS(res, res); |
|
1647 } |
|
1648 |
|
1649 nsCOMPtr<nsIDOMNode> listItem = IsInListItem(blockParent); |
|
1650 if (listItem && listItem != hostNode) { |
|
1651 ReturnInListItem(aSelection, listItem, node, offset); |
|
1652 *aHandled = true; |
|
1653 return NS_OK; |
|
1654 } else if (nsHTMLEditUtils::IsHeader(blockParent)) { |
|
1655 // headers: close (or split) header |
|
1656 ReturnInHeader(aSelection, blockParent, node, offset); |
|
1657 *aHandled = true; |
|
1658 return NS_OK; |
|
1659 } else if (nsHTMLEditUtils::IsParagraph(blockParent)) { |
|
1660 // paragraphs: special rules to look for <br>s |
|
1661 res = ReturnInParagraph(aSelection, blockParent, node, offset, |
|
1662 aCancel, aHandled); |
|
1663 NS_ENSURE_SUCCESS(res, res); |
|
1664 // fall through, we may not have handled it in ReturnInParagraph() |
|
1665 } |
|
1666 |
|
1667 // if not already handled then do the standard thing |
|
1668 if (!(*aHandled)) { |
|
1669 *aHandled = true; |
|
1670 return StandardBreakImpl(node, offset, aSelection); |
|
1671 } |
|
1672 return NS_OK; |
|
1673 } |
|
1674 |
|
1675 nsresult |
|
1676 nsHTMLEditRules::StandardBreakImpl(nsIDOMNode* aNode, int32_t aOffset, |
|
1677 nsISelection* aSelection) |
|
1678 { |
|
1679 nsCOMPtr<nsIDOMNode> brNode; |
|
1680 bool bAfterBlock = false; |
|
1681 bool bBeforeBlock = false; |
|
1682 nsresult res = NS_OK; |
|
1683 nsCOMPtr<nsIDOMNode> node(aNode); |
|
1684 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(aSelection)); |
|
1685 |
|
1686 if (IsPlaintextEditor()) { |
|
1687 NS_ENSURE_STATE(mHTMLEditor); |
|
1688 res = mHTMLEditor->CreateBR(node, aOffset, address_of(brNode)); |
|
1689 } else { |
|
1690 NS_ENSURE_STATE(mHTMLEditor); |
|
1691 nsWSRunObject wsObj(mHTMLEditor, node, aOffset); |
|
1692 nsCOMPtr<nsIDOMNode> visNode, linkNode; |
|
1693 int32_t visOffset = 0, newOffset; |
|
1694 WSType wsType; |
|
1695 wsObj.PriorVisibleNode(node, aOffset, address_of(visNode), |
|
1696 &visOffset, &wsType); |
|
1697 if (wsType & WSType::block) { |
|
1698 bAfterBlock = true; |
|
1699 } |
|
1700 wsObj.NextVisibleNode(node, aOffset, address_of(visNode), |
|
1701 &visOffset, &wsType); |
|
1702 if (wsType & WSType::block) { |
|
1703 bBeforeBlock = true; |
|
1704 } |
|
1705 NS_ENSURE_STATE(mHTMLEditor); |
|
1706 if (mHTMLEditor->IsInLink(node, address_of(linkNode))) { |
|
1707 // split the link |
|
1708 nsCOMPtr<nsIDOMNode> linkParent; |
|
1709 res = linkNode->GetParentNode(getter_AddRefs(linkParent)); |
|
1710 NS_ENSURE_SUCCESS(res, res); |
|
1711 NS_ENSURE_STATE(mHTMLEditor); |
|
1712 res = mHTMLEditor->SplitNodeDeep(linkNode, node, aOffset, |
|
1713 &newOffset, true); |
|
1714 NS_ENSURE_SUCCESS(res, res); |
|
1715 // reset {node,aOffset} to the point where link was split |
|
1716 node = linkParent; |
|
1717 aOffset = newOffset; |
|
1718 } |
|
1719 res = wsObj.InsertBreak(address_of(node), &aOffset, |
|
1720 address_of(brNode), nsIEditor::eNone); |
|
1721 } |
|
1722 NS_ENSURE_SUCCESS(res, res); |
|
1723 node = nsEditor::GetNodeLocation(brNode, &aOffset); |
|
1724 NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); |
|
1725 if (bAfterBlock && bBeforeBlock) { |
|
1726 // we just placed a br between block boundaries. This is the one case |
|
1727 // where we want the selection to be before the br we just placed, as the |
|
1728 // br will be on a new line, rather than at end of prior line. |
|
1729 selPriv->SetInterlinePosition(true); |
|
1730 res = aSelection->Collapse(node, aOffset); |
|
1731 } else { |
|
1732 NS_ENSURE_STATE(mHTMLEditor); |
|
1733 nsWSRunObject wsObj(mHTMLEditor, node, aOffset+1); |
|
1734 nsCOMPtr<nsIDOMNode> secondBR; |
|
1735 int32_t visOffset = 0; |
|
1736 WSType wsType; |
|
1737 wsObj.NextVisibleNode(node, aOffset+1, address_of(secondBR), |
|
1738 &visOffset, &wsType); |
|
1739 if (wsType == WSType::br) { |
|
1740 // the next thing after the break we inserted is another break. Move |
|
1741 // the 2nd break to be the first breaks sibling. This will prevent them |
|
1742 // from being in different inline nodes, which would break |
|
1743 // SetInterlinePosition(). It will also assure that if the user clicks |
|
1744 // away and then clicks back on their new blank line, they will still |
|
1745 // get the style from the line above. |
|
1746 int32_t brOffset; |
|
1747 nsCOMPtr<nsIDOMNode> brParent = nsEditor::GetNodeLocation(secondBR, &brOffset); |
|
1748 if (brParent != node || brOffset != aOffset + 1) { |
|
1749 NS_ENSURE_STATE(mHTMLEditor); |
|
1750 res = mHTMLEditor->MoveNode(secondBR, node, aOffset+1); |
|
1751 NS_ENSURE_SUCCESS(res, res); |
|
1752 } |
|
1753 } |
|
1754 // SetInterlinePosition(true) means we want the caret to stick to the |
|
1755 // content on the "right". We want the caret to stick to whatever is past |
|
1756 // the break. This is because the break is on the same line we were on, |
|
1757 // but the next content will be on the following line. |
|
1758 |
|
1759 // An exception to this is if the break has a next sibling that is a block |
|
1760 // node. Then we stick to the left to avoid an uber caret. |
|
1761 nsCOMPtr<nsIDOMNode> siblingNode; |
|
1762 brNode->GetNextSibling(getter_AddRefs(siblingNode)); |
|
1763 if (siblingNode && IsBlockNode(siblingNode)) { |
|
1764 selPriv->SetInterlinePosition(false); |
|
1765 } else { |
|
1766 selPriv->SetInterlinePosition(true); |
|
1767 } |
|
1768 res = aSelection->Collapse(node, aOffset+1); |
|
1769 } |
|
1770 return res; |
|
1771 } |
|
1772 |
|
1773 nsresult |
|
1774 nsHTMLEditRules::DidInsertBreak(nsISelection *aSelection, nsresult aResult) |
|
1775 { |
|
1776 return NS_OK; |
|
1777 } |
|
1778 |
|
1779 |
|
1780 nsresult |
|
1781 nsHTMLEditRules::SplitMailCites(nsISelection *aSelection, bool aPlaintext, bool *aHandled) |
|
1782 { |
|
1783 NS_ENSURE_TRUE(aSelection && aHandled, NS_ERROR_NULL_POINTER); |
|
1784 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(aSelection)); |
|
1785 nsCOMPtr<nsIDOMNode> citeNode, selNode, leftCite, rightCite; |
|
1786 int32_t selOffset, newOffset; |
|
1787 NS_ENSURE_STATE(mHTMLEditor); |
|
1788 nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); |
|
1789 NS_ENSURE_SUCCESS(res, res); |
|
1790 res = GetTopEnclosingMailCite(selNode, address_of(citeNode), aPlaintext); |
|
1791 NS_ENSURE_SUCCESS(res, res); |
|
1792 if (citeNode) |
|
1793 { |
|
1794 // If our selection is just before a break, nudge it to be |
|
1795 // just after it. This does two things for us. It saves us the trouble of having to add |
|
1796 // a break here ourselves to preserve the "blockness" of the inline span mailquote |
|
1797 // (in the inline case), and : |
|
1798 // it means the break won't end up making an empty line that happens to be inside a |
|
1799 // mailquote (in either inline or block case). |
|
1800 // The latter can confuse a user if they click there and start typing, |
|
1801 // because being in the mailquote may affect wrapping behavior, or font color, etc. |
|
1802 NS_ENSURE_STATE(mHTMLEditor); |
|
1803 nsWSRunObject wsObj(mHTMLEditor, selNode, selOffset); |
|
1804 nsCOMPtr<nsIDOMNode> visNode; |
|
1805 int32_t visOffset=0; |
|
1806 WSType wsType; |
|
1807 wsObj.NextVisibleNode(selNode, selOffset, address_of(visNode), |
|
1808 &visOffset, &wsType); |
|
1809 if (wsType == WSType::br) { |
|
1810 // ok, we are just before a break. is it inside the mailquote? |
|
1811 int32_t unused; |
|
1812 if (nsEditorUtils::IsDescendantOf(visNode, citeNode, &unused)) |
|
1813 { |
|
1814 // it is. so lets reset our selection to be just after it. |
|
1815 NS_ENSURE_STATE(mHTMLEditor); |
|
1816 selNode = mHTMLEditor->GetNodeLocation(visNode, &selOffset); |
|
1817 ++selOffset; |
|
1818 } |
|
1819 } |
|
1820 |
|
1821 nsCOMPtr<nsIDOMNode> brNode; |
|
1822 NS_ENSURE_STATE(mHTMLEditor); |
|
1823 res = mHTMLEditor->SplitNodeDeep(citeNode, selNode, selOffset, &newOffset, |
|
1824 true, address_of(leftCite), address_of(rightCite)); |
|
1825 NS_ENSURE_SUCCESS(res, res); |
|
1826 res = citeNode->GetParentNode(getter_AddRefs(selNode)); |
|
1827 NS_ENSURE_SUCCESS(res, res); |
|
1828 NS_ENSURE_STATE(mHTMLEditor); |
|
1829 res = mHTMLEditor->CreateBR(selNode, newOffset, address_of(brNode)); |
|
1830 NS_ENSURE_SUCCESS(res, res); |
|
1831 // want selection before the break, and on same line |
|
1832 selPriv->SetInterlinePosition(true); |
|
1833 res = aSelection->Collapse(selNode, newOffset); |
|
1834 NS_ENSURE_SUCCESS(res, res); |
|
1835 // if citeNode wasn't a block, we might also want another break before it. |
|
1836 // We need to examine the content both before the br we just added and also |
|
1837 // just after it. If we don't have another br or block boundary adjacent, |
|
1838 // then we will need a 2nd br added to achieve blank line that user expects. |
|
1839 if (IsInlineNode(citeNode)) |
|
1840 { |
|
1841 NS_ENSURE_STATE(mHTMLEditor); |
|
1842 nsWSRunObject wsObj(mHTMLEditor, selNode, newOffset); |
|
1843 nsCOMPtr<nsIDOMNode> visNode; |
|
1844 int32_t visOffset=0; |
|
1845 WSType wsType; |
|
1846 wsObj.PriorVisibleNode(selNode, newOffset, address_of(visNode), |
|
1847 &visOffset, &wsType); |
|
1848 if (wsType == WSType::normalWS || wsType == WSType::text || |
|
1849 wsType == WSType::special) { |
|
1850 NS_ENSURE_STATE(mHTMLEditor); |
|
1851 nsWSRunObject wsObjAfterBR(mHTMLEditor, selNode, newOffset+1); |
|
1852 wsObjAfterBR.NextVisibleNode(selNode, newOffset+1, address_of(visNode), |
|
1853 &visOffset, &wsType); |
|
1854 if (wsType == WSType::normalWS || wsType == WSType::text || |
|
1855 wsType == WSType::special) { |
|
1856 NS_ENSURE_STATE(mHTMLEditor); |
|
1857 res = mHTMLEditor->CreateBR(selNode, newOffset, address_of(brNode)); |
|
1858 NS_ENSURE_SUCCESS(res, res); |
|
1859 } |
|
1860 } |
|
1861 } |
|
1862 // delete any empty cites |
|
1863 bool bEmptyCite = false; |
|
1864 if (leftCite) |
|
1865 { |
|
1866 NS_ENSURE_STATE(mHTMLEditor); |
|
1867 res = mHTMLEditor->IsEmptyNode(leftCite, &bEmptyCite, true, false); |
|
1868 if (NS_SUCCEEDED(res) && bEmptyCite) { |
|
1869 NS_ENSURE_STATE(mHTMLEditor); |
|
1870 res = mHTMLEditor->DeleteNode(leftCite); |
|
1871 } |
|
1872 NS_ENSURE_SUCCESS(res, res); |
|
1873 } |
|
1874 if (rightCite) |
|
1875 { |
|
1876 NS_ENSURE_STATE(mHTMLEditor); |
|
1877 res = mHTMLEditor->IsEmptyNode(rightCite, &bEmptyCite, true, false); |
|
1878 if (NS_SUCCEEDED(res) && bEmptyCite) { |
|
1879 NS_ENSURE_STATE(mHTMLEditor); |
|
1880 res = mHTMLEditor->DeleteNode(rightCite); |
|
1881 } |
|
1882 NS_ENSURE_SUCCESS(res, res); |
|
1883 } |
|
1884 *aHandled = true; |
|
1885 } |
|
1886 return NS_OK; |
|
1887 } |
|
1888 |
|
1889 |
|
1890 nsresult |
|
1891 nsHTMLEditRules::WillDeleteSelection(Selection* aSelection, |
|
1892 nsIEditor::EDirection aAction, |
|
1893 nsIEditor::EStripWrappers aStripWrappers, |
|
1894 bool* aCancel, |
|
1895 bool* aHandled) |
|
1896 { |
|
1897 MOZ_ASSERT(aStripWrappers == nsIEditor::eStrip || |
|
1898 aStripWrappers == nsIEditor::eNoStrip); |
|
1899 |
|
1900 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } |
|
1901 // initialize out param |
|
1902 *aCancel = false; |
|
1903 *aHandled = false; |
|
1904 |
|
1905 // remember that we did a selection deletion. Used by CreateStyleForInsertText() |
|
1906 mDidDeleteSelection = true; |
|
1907 |
|
1908 // if there is only bogus content, cancel the operation |
|
1909 if (mBogusNode) |
|
1910 { |
|
1911 *aCancel = true; |
|
1912 return NS_OK; |
|
1913 } |
|
1914 |
|
1915 bool bCollapsed = aSelection->Collapsed(), join = false; |
|
1916 |
|
1917 // origCollapsed is used later to determine whether we should join |
|
1918 // blocks. We don't really care about bCollapsed because it will be |
|
1919 // modified by ExtendSelectionForDelete later. JoinBlocks should |
|
1920 // happen if the original selection is collapsed and the cursor is |
|
1921 // at the end of a block element, in which case ExtendSelectionForDelete |
|
1922 // would always make the selection not collapsed. |
|
1923 bool origCollapsed = bCollapsed; |
|
1924 nsCOMPtr<nsIDOMNode> startNode, selNode; |
|
1925 int32_t startOffset, selOffset; |
|
1926 |
|
1927 // first check for table selection mode. If so, |
|
1928 // hand off to table editor. |
|
1929 nsCOMPtr<nsIDOMElement> cell; |
|
1930 NS_ENSURE_STATE(mHTMLEditor); |
|
1931 nsresult res = mHTMLEditor->GetFirstSelectedCell(nullptr, getter_AddRefs(cell)); |
|
1932 if (NS_SUCCEEDED(res) && cell) { |
|
1933 NS_ENSURE_STATE(mHTMLEditor); |
|
1934 res = mHTMLEditor->DeleteTableCellContents(); |
|
1935 *aHandled = true; |
|
1936 return res; |
|
1937 } |
|
1938 cell = nullptr; |
|
1939 |
|
1940 NS_ENSURE_STATE(mHTMLEditor); |
|
1941 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset); |
|
1942 NS_ENSURE_SUCCESS(res, res); |
|
1943 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); |
|
1944 |
|
1945 if (bCollapsed) |
|
1946 { |
|
1947 // if we are inside an empty block, delete it. |
|
1948 NS_ENSURE_STATE(mHTMLEditor); |
|
1949 nsCOMPtr<nsIContent> hostContent = mHTMLEditor->GetActiveEditingHost(); |
|
1950 nsCOMPtr<nsIDOMNode> hostNode = do_QueryInterface(hostContent); |
|
1951 NS_ENSURE_TRUE(hostNode, NS_ERROR_FAILURE); |
|
1952 res = CheckForEmptyBlock(startNode, hostNode, aSelection, aHandled); |
|
1953 NS_ENSURE_SUCCESS(res, res); |
|
1954 if (*aHandled) return NS_OK; |
|
1955 |
|
1956 // Test for distance between caret and text that will be deleted |
|
1957 res = CheckBidiLevelForDeletion(aSelection, startNode, startOffset, aAction, aCancel); |
|
1958 NS_ENSURE_SUCCESS(res, res); |
|
1959 if (*aCancel) return NS_OK; |
|
1960 |
|
1961 NS_ENSURE_STATE(mHTMLEditor); |
|
1962 res = mHTMLEditor->ExtendSelectionForDelete(aSelection, &aAction); |
|
1963 NS_ENSURE_SUCCESS(res, res); |
|
1964 |
|
1965 // We should delete nothing. |
|
1966 if (aAction == nsIEditor::eNone) |
|
1967 return NS_OK; |
|
1968 |
|
1969 // ExtendSelectionForDelete() may have changed the selection, update it |
|
1970 NS_ENSURE_STATE(mHTMLEditor); |
|
1971 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset); |
|
1972 NS_ENSURE_SUCCESS(res, res); |
|
1973 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); |
|
1974 |
|
1975 bCollapsed = aSelection->Collapsed(); |
|
1976 } |
|
1977 |
|
1978 if (bCollapsed) |
|
1979 { |
|
1980 // what's in the direction we are deleting? |
|
1981 NS_ENSURE_STATE(mHTMLEditor); |
|
1982 nsWSRunObject wsObj(mHTMLEditor, startNode, startOffset); |
|
1983 nsCOMPtr<nsIDOMNode> visNode; |
|
1984 int32_t visOffset; |
|
1985 WSType wsType; |
|
1986 |
|
1987 // find next visible node |
|
1988 if (aAction == nsIEditor::eNext) |
|
1989 wsObj.NextVisibleNode(startNode, startOffset, address_of(visNode), |
|
1990 &visOffset, &wsType); |
|
1991 else |
|
1992 wsObj.PriorVisibleNode(startNode, startOffset, address_of(visNode), |
|
1993 &visOffset, &wsType); |
|
1994 |
|
1995 if (!visNode) // can't find anything to delete! |
|
1996 { |
|
1997 *aCancel = true; |
|
1998 return res; |
|
1999 } |
|
2000 |
|
2001 if (wsType == WSType::normalWS) { |
|
2002 // we found some visible ws to delete. Let ws code handle it. |
|
2003 if (aAction == nsIEditor::eNext) |
|
2004 res = wsObj.DeleteWSForward(); |
|
2005 else |
|
2006 res = wsObj.DeleteWSBackward(); |
|
2007 *aHandled = true; |
|
2008 NS_ENSURE_SUCCESS(res, res); |
|
2009 res = InsertBRIfNeeded(aSelection); |
|
2010 return res; |
|
2011 } else if (wsType == WSType::text) { |
|
2012 // found normal text to delete. |
|
2013 int32_t so = visOffset; |
|
2014 int32_t eo = visOffset+1; |
|
2015 if (aAction == nsIEditor::ePrevious) |
|
2016 { |
|
2017 if (so == 0) return NS_ERROR_UNEXPECTED; |
|
2018 so--; |
|
2019 eo--; |
|
2020 } |
|
2021 else |
|
2022 { |
|
2023 nsCOMPtr<nsIDOMRange> range; |
|
2024 res = aSelection->GetRangeAt(0, getter_AddRefs(range)); |
|
2025 NS_ENSURE_SUCCESS(res, res); |
|
2026 |
|
2027 #ifdef DEBUG |
|
2028 nsIDOMNode *container; |
|
2029 |
|
2030 res = range->GetStartContainer(&container); |
|
2031 NS_ENSURE_SUCCESS(res, res); |
|
2032 NS_ASSERTION(container == visNode, "selection start not in visNode"); |
|
2033 |
|
2034 res = range->GetEndContainer(&container); |
|
2035 NS_ENSURE_SUCCESS(res, res); |
|
2036 NS_ASSERTION(container == visNode, "selection end not in visNode"); |
|
2037 #endif |
|
2038 |
|
2039 res = range->GetStartOffset(&so); |
|
2040 NS_ENSURE_SUCCESS(res, res); |
|
2041 res = range->GetEndOffset(&eo); |
|
2042 NS_ENSURE_SUCCESS(res, res); |
|
2043 } |
|
2044 NS_ENSURE_STATE(mHTMLEditor); |
|
2045 res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(visNode), &so, address_of(visNode), &eo); |
|
2046 NS_ENSURE_SUCCESS(res, res); |
|
2047 nsCOMPtr<nsIDOMCharacterData> nodeAsText(do_QueryInterface(visNode)); |
|
2048 NS_ENSURE_STATE(mHTMLEditor); |
|
2049 res = mHTMLEditor->DeleteText(nodeAsText, std::min(so, eo), DeprecatedAbs(eo - so)); |
|
2050 *aHandled = true; |
|
2051 NS_ENSURE_SUCCESS(res, res); |
|
2052 res = InsertBRIfNeeded(aSelection); |
|
2053 return res; |
|
2054 } else if (wsType == WSType::special || wsType == WSType::br || |
|
2055 nsHTMLEditUtils::IsHR(visNode)) { |
|
2056 // short circuit for invisible breaks. delete them and recurse. |
|
2057 if (nsTextEditUtils::IsBreak(visNode) && |
|
2058 (!mHTMLEditor || !mHTMLEditor->IsVisBreak(visNode))) |
|
2059 { |
|
2060 NS_ENSURE_STATE(mHTMLEditor); |
|
2061 res = mHTMLEditor->DeleteNode(visNode); |
|
2062 NS_ENSURE_SUCCESS(res, res); |
|
2063 return WillDeleteSelection(aSelection, aAction, aStripWrappers, |
|
2064 aCancel, aHandled); |
|
2065 } |
|
2066 |
|
2067 // special handling for backspace when positioned after <hr> |
|
2068 if (aAction == nsIEditor::ePrevious && nsHTMLEditUtils::IsHR(visNode)) |
|
2069 { |
|
2070 /* |
|
2071 Only if the caret is positioned at the end-of-hr-line position, |
|
2072 we want to delete the <hr>. |
|
2073 |
|
2074 In other words, we only want to delete, if |
|
2075 our selection position (indicated by startNode and startOffset) |
|
2076 is the position directly after the <hr>, |
|
2077 on the same line as the <hr>. |
|
2078 |
|
2079 To detect this case we check: |
|
2080 startNode == parentOfVisNode |
|
2081 and |
|
2082 startOffset -1 == visNodeOffsetToVisNodeParent |
|
2083 and |
|
2084 interline position is false (left) |
|
2085 |
|
2086 In any other case we set the position to |
|
2087 startnode -1 and interlineposition to false, |
|
2088 only moving the caret to the end-of-hr-line position. |
|
2089 */ |
|
2090 |
|
2091 bool moveOnly = true; |
|
2092 |
|
2093 selNode = nsEditor::GetNodeLocation(visNode, &selOffset); |
|
2094 |
|
2095 bool interLineIsRight; |
|
2096 res = aSelection->GetInterlinePosition(&interLineIsRight); |
|
2097 NS_ENSURE_SUCCESS(res, res); |
|
2098 |
|
2099 if (startNode == selNode && |
|
2100 startOffset -1 == selOffset && |
|
2101 !interLineIsRight) |
|
2102 { |
|
2103 moveOnly = false; |
|
2104 } |
|
2105 |
|
2106 if (moveOnly) |
|
2107 { |
|
2108 // Go to the position after the <hr>, but to the end of the <hr> line |
|
2109 // by setting the interline position to left. |
|
2110 ++selOffset; |
|
2111 res = aSelection->Collapse(selNode, selOffset); |
|
2112 aSelection->SetInterlinePosition(false); |
|
2113 mDidExplicitlySetInterline = true; |
|
2114 *aHandled = true; |
|
2115 |
|
2116 // There is one exception to the move only case. |
|
2117 // If the <hr> is followed by a <br> we want to delete the <br>. |
|
2118 |
|
2119 WSType otherWSType; |
|
2120 nsCOMPtr<nsIDOMNode> otherNode; |
|
2121 int32_t otherOffset; |
|
2122 |
|
2123 wsObj.NextVisibleNode(startNode, startOffset, address_of(otherNode), |
|
2124 &otherOffset, &otherWSType); |
|
2125 |
|
2126 if (otherWSType == WSType::br) { |
|
2127 // Delete the <br> |
|
2128 |
|
2129 NS_ENSURE_STATE(mHTMLEditor); |
|
2130 res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, otherNode); |
|
2131 NS_ENSURE_SUCCESS(res, res); |
|
2132 NS_ENSURE_STATE(mHTMLEditor); |
|
2133 res = mHTMLEditor->DeleteNode(otherNode); |
|
2134 NS_ENSURE_SUCCESS(res, res); |
|
2135 } |
|
2136 |
|
2137 return NS_OK; |
|
2138 } |
|
2139 // else continue with normal delete code |
|
2140 } |
|
2141 |
|
2142 // found break or image, or hr. |
|
2143 NS_ENSURE_STATE(mHTMLEditor); |
|
2144 res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, visNode); |
|
2145 NS_ENSURE_SUCCESS(res, res); |
|
2146 // remember sibling to visnode, if any |
|
2147 nsCOMPtr<nsIDOMNode> sibling, stepbrother; |
|
2148 NS_ENSURE_STATE(mHTMLEditor); |
|
2149 mHTMLEditor->GetPriorHTMLSibling(visNode, address_of(sibling)); |
|
2150 // delete the node, and join like nodes if appropriate |
|
2151 NS_ENSURE_STATE(mHTMLEditor); |
|
2152 res = mHTMLEditor->DeleteNode(visNode); |
|
2153 NS_ENSURE_SUCCESS(res, res); |
|
2154 // we did something, so lets say so. |
|
2155 *aHandled = true; |
|
2156 // is there a prior node and are they siblings? |
|
2157 if (sibling) { |
|
2158 NS_ENSURE_STATE(mHTMLEditor); |
|
2159 mHTMLEditor->GetNextHTMLSibling(sibling, address_of(stepbrother)); |
|
2160 } |
|
2161 if (startNode == stepbrother) |
|
2162 { |
|
2163 // are they both text nodes? |
|
2164 NS_ENSURE_STATE(mHTMLEditor); |
|
2165 if (mHTMLEditor->IsTextNode(startNode) && |
|
2166 (!mHTMLEditor || mHTMLEditor->IsTextNode(sibling))) |
|
2167 { |
|
2168 NS_ENSURE_STATE(mHTMLEditor); |
|
2169 // if so, join them! |
|
2170 res = JoinNodesSmart(sibling, startNode, address_of(selNode), &selOffset); |
|
2171 NS_ENSURE_SUCCESS(res, res); |
|
2172 // fix up selection |
|
2173 res = aSelection->Collapse(selNode, selOffset); |
|
2174 } |
|
2175 } |
|
2176 NS_ENSURE_SUCCESS(res, res); |
|
2177 res = InsertBRIfNeeded(aSelection); |
|
2178 return res; |
|
2179 } else if (wsType == WSType::otherBlock) { |
|
2180 // make sure it's not a table element. If so, cancel the operation |
|
2181 // (translation: users cannot backspace or delete across table cells) |
|
2182 if (nsHTMLEditUtils::IsTableElement(visNode)) |
|
2183 { |
|
2184 *aCancel = true; |
|
2185 return NS_OK; |
|
2186 } |
|
2187 |
|
2188 // next to a block. See if we are between a block and a br. If so, we really |
|
2189 // want to delete the br. Else join content at selection to the block. |
|
2190 |
|
2191 bool bDeletedBR = false; |
|
2192 WSType otherWSType; |
|
2193 nsCOMPtr<nsIDOMNode> otherNode; |
|
2194 int32_t otherOffset; |
|
2195 |
|
2196 // find node in other direction |
|
2197 if (aAction == nsIEditor::eNext) |
|
2198 wsObj.PriorVisibleNode(startNode, startOffset, address_of(otherNode), |
|
2199 &otherOffset, &otherWSType); |
|
2200 else |
|
2201 wsObj.NextVisibleNode(startNode, startOffset, address_of(otherNode), |
|
2202 &otherOffset, &otherWSType); |
|
2203 |
|
2204 // first find the adjacent node in the block |
|
2205 nsCOMPtr<nsIDOMNode> leafNode, leftNode, rightNode; |
|
2206 if (aAction == nsIEditor::ePrevious) |
|
2207 { |
|
2208 NS_ENSURE_STATE(mHTMLEditor); |
|
2209 res = mHTMLEditor->GetLastEditableLeaf( visNode, address_of(leafNode)); |
|
2210 NS_ENSURE_SUCCESS(res, res); |
|
2211 leftNode = leafNode; |
|
2212 rightNode = startNode; |
|
2213 } |
|
2214 else |
|
2215 { |
|
2216 NS_ENSURE_STATE(mHTMLEditor); |
|
2217 res = mHTMLEditor->GetFirstEditableLeaf( visNode, address_of(leafNode)); |
|
2218 NS_ENSURE_SUCCESS(res, res); |
|
2219 leftNode = startNode; |
|
2220 rightNode = leafNode; |
|
2221 } |
|
2222 |
|
2223 if (nsTextEditUtils::IsBreak(otherNode)) |
|
2224 { |
|
2225 NS_ENSURE_STATE(mHTMLEditor); |
|
2226 res = mHTMLEditor->DeleteNode(otherNode); |
|
2227 NS_ENSURE_SUCCESS(res, res); |
|
2228 *aHandled = true; |
|
2229 bDeletedBR = true; |
|
2230 } |
|
2231 |
|
2232 // don't cross table boundaries |
|
2233 if (leftNode && rightNode && InDifferentTableElements(leftNode, rightNode)) { |
|
2234 return NS_OK; |
|
2235 } |
|
2236 |
|
2237 if (bDeletedBR) |
|
2238 { |
|
2239 // put selection at edge of block and we are done. |
|
2240 nsCOMPtr<nsIDOMNode> newSelNode; |
|
2241 int32_t newSelOffset; |
|
2242 res = GetGoodSelPointForNode(leafNode, aAction, address_of(newSelNode), &newSelOffset); |
|
2243 NS_ENSURE_SUCCESS(res, res); |
|
2244 aSelection->Collapse(newSelNode, newSelOffset); |
|
2245 return res; |
|
2246 } |
|
2247 |
|
2248 // else we are joining content to block |
|
2249 |
|
2250 nsCOMPtr<nsIDOMNode> selPointNode = startNode; |
|
2251 int32_t selPointOffset = startOffset; |
|
2252 { |
|
2253 NS_ENSURE_STATE(mHTMLEditor); |
|
2254 nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(selPointNode), &selPointOffset); |
|
2255 res = JoinBlocks(leftNode, rightNode, aCancel); |
|
2256 *aHandled = true; |
|
2257 NS_ENSURE_SUCCESS(res, res); |
|
2258 } |
|
2259 aSelection->Collapse(selPointNode, selPointOffset); |
|
2260 return res; |
|
2261 } else if (wsType == WSType::thisBlock) { |
|
2262 // at edge of our block. Look beside it and see if we can join to an adjacent block |
|
2263 |
|
2264 // make sure it's not a table element. If so, cancel the operation |
|
2265 // (translation: users cannot backspace or delete across table cells) |
|
2266 if (nsHTMLEditUtils::IsTableElement(visNode)) |
|
2267 { |
|
2268 *aCancel = true; |
|
2269 return NS_OK; |
|
2270 } |
|
2271 |
|
2272 // first find the relavent nodes |
|
2273 nsCOMPtr<nsIDOMNode> leftNode, rightNode; |
|
2274 if (aAction == nsIEditor::ePrevious) |
|
2275 { |
|
2276 NS_ENSURE_STATE(mHTMLEditor); |
|
2277 res = mHTMLEditor->GetPriorHTMLNode(visNode, address_of(leftNode)); |
|
2278 NS_ENSURE_SUCCESS(res, res); |
|
2279 rightNode = startNode; |
|
2280 } |
|
2281 else |
|
2282 { |
|
2283 NS_ENSURE_STATE(mHTMLEditor); |
|
2284 res = mHTMLEditor->GetNextHTMLNode( visNode, address_of(rightNode)); |
|
2285 NS_ENSURE_SUCCESS(res, res); |
|
2286 leftNode = startNode; |
|
2287 } |
|
2288 |
|
2289 // nothing to join |
|
2290 if (!leftNode || !rightNode) |
|
2291 { |
|
2292 *aCancel = true; |
|
2293 return NS_OK; |
|
2294 } |
|
2295 |
|
2296 // don't cross table boundaries -- cancel it |
|
2297 if (InDifferentTableElements(leftNode, rightNode)) { |
|
2298 *aCancel = true; |
|
2299 return NS_OK; |
|
2300 } |
|
2301 |
|
2302 nsCOMPtr<nsIDOMNode> selPointNode = startNode; |
|
2303 int32_t selPointOffset = startOffset; |
|
2304 { |
|
2305 NS_ENSURE_STATE(mHTMLEditor); |
|
2306 nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(selPointNode), &selPointOffset); |
|
2307 res = JoinBlocks(leftNode, rightNode, aCancel); |
|
2308 *aHandled = true; |
|
2309 NS_ENSURE_SUCCESS(res, res); |
|
2310 } |
|
2311 aSelection->Collapse(selPointNode, selPointOffset); |
|
2312 return res; |
|
2313 } |
|
2314 } |
|
2315 |
|
2316 |
|
2317 // else we have a non collapsed selection |
|
2318 // first adjust the selection |
|
2319 res = ExpandSelectionForDeletion(aSelection); |
|
2320 NS_ENSURE_SUCCESS(res, res); |
|
2321 |
|
2322 // remember that we did a ranged delete for the benefit of AfterEditInner(). |
|
2323 mDidRangedDelete = true; |
|
2324 |
|
2325 // refresh start and end points |
|
2326 NS_ENSURE_STATE(mHTMLEditor); |
|
2327 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset); |
|
2328 NS_ENSURE_SUCCESS(res, res); |
|
2329 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); |
|
2330 nsCOMPtr<nsIDOMNode> endNode; |
|
2331 int32_t endOffset; |
|
2332 NS_ENSURE_STATE(mHTMLEditor); |
|
2333 res = mHTMLEditor->GetEndNodeAndOffset(aSelection, getter_AddRefs(endNode), &endOffset); |
|
2334 NS_ENSURE_SUCCESS(res, res); |
|
2335 NS_ENSURE_TRUE(endNode, NS_ERROR_FAILURE); |
|
2336 |
|
2337 // figure out if the endpoints are in nodes that can be merged |
|
2338 // adjust surrounding whitespace in preperation to delete selection |
|
2339 if (!IsPlaintextEditor()) |
|
2340 { |
|
2341 NS_ENSURE_STATE(mHTMLEditor); |
|
2342 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); |
|
2343 res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, |
|
2344 address_of(startNode), &startOffset, |
|
2345 address_of(endNode), &endOffset); |
|
2346 NS_ENSURE_SUCCESS(res, res); |
|
2347 } |
|
2348 |
|
2349 { |
|
2350 // track location of where we are deleting |
|
2351 NS_ENSURE_STATE(mHTMLEditor); |
|
2352 nsAutoTrackDOMPoint startTracker(mHTMLEditor->mRangeUpdater, |
|
2353 address_of(startNode), &startOffset); |
|
2354 nsAutoTrackDOMPoint endTracker(mHTMLEditor->mRangeUpdater, |
|
2355 address_of(endNode), &endOffset); |
|
2356 // we are handling all ranged deletions directly now. |
|
2357 *aHandled = true; |
|
2358 |
|
2359 if (endNode == startNode) |
|
2360 { |
|
2361 NS_ENSURE_STATE(mHTMLEditor); |
|
2362 res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers); |
|
2363 NS_ENSURE_SUCCESS(res, res); |
|
2364 } |
|
2365 else |
|
2366 { |
|
2367 // figure out mailcite ancestors |
|
2368 nsCOMPtr<nsIDOMNode> endCiteNode, startCiteNode; |
|
2369 res = GetTopEnclosingMailCite(startNode, address_of(startCiteNode), |
|
2370 IsPlaintextEditor()); |
|
2371 NS_ENSURE_SUCCESS(res, res); |
|
2372 res = GetTopEnclosingMailCite(endNode, address_of(endCiteNode), |
|
2373 IsPlaintextEditor()); |
|
2374 NS_ENSURE_SUCCESS(res, res); |
|
2375 |
|
2376 // if we only have a mailcite at one of the two endpoints, set the directionality |
|
2377 // of the deletion so that the selection will end up outside the mailcite. |
|
2378 if (startCiteNode && !endCiteNode) |
|
2379 { |
|
2380 aAction = nsIEditor::eNext; |
|
2381 } |
|
2382 else if (!startCiteNode && endCiteNode) |
|
2383 { |
|
2384 aAction = nsIEditor::ePrevious; |
|
2385 } |
|
2386 |
|
2387 // figure out block parents |
|
2388 nsCOMPtr<nsIDOMNode> leftParent; |
|
2389 nsCOMPtr<nsIDOMNode> rightParent; |
|
2390 if (IsBlockNode(startNode)) |
|
2391 leftParent = startNode; |
|
2392 else { |
|
2393 NS_ENSURE_STATE(mHTMLEditor); |
|
2394 leftParent = mHTMLEditor->GetBlockNodeParent(startNode); |
|
2395 } |
|
2396 |
|
2397 if (IsBlockNode(endNode)) |
|
2398 rightParent = endNode; |
|
2399 else { |
|
2400 NS_ENSURE_STATE(mHTMLEditor); |
|
2401 rightParent = mHTMLEditor->GetBlockNodeParent(endNode); |
|
2402 } |
|
2403 |
|
2404 // are endpoint block parents the same? use default deletion |
|
2405 if (leftParent == rightParent) |
|
2406 { |
|
2407 NS_ENSURE_STATE(mHTMLEditor); |
|
2408 res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers); |
|
2409 } |
|
2410 else |
|
2411 { |
|
2412 // deleting across blocks |
|
2413 // are the blocks of same type? |
|
2414 NS_ENSURE_STATE(leftParent && rightParent); |
|
2415 |
|
2416 // are the blocks siblings? |
|
2417 nsCOMPtr<nsIDOMNode> leftBlockParent; |
|
2418 nsCOMPtr<nsIDOMNode> rightBlockParent; |
|
2419 leftParent->GetParentNode(getter_AddRefs(leftBlockParent)); |
|
2420 rightParent->GetParentNode(getter_AddRefs(rightBlockParent)); |
|
2421 |
|
2422 // MOOSE: this could conceivably screw up a table.. fix me. |
|
2423 if ( (leftBlockParent == rightBlockParent) |
|
2424 && (!mHTMLEditor || mHTMLEditor->NodesSameType(leftParent, rightParent)) ) |
|
2425 { |
|
2426 NS_ENSURE_STATE(mHTMLEditor); |
|
2427 if (nsHTMLEditUtils::IsParagraph(leftParent)) |
|
2428 { |
|
2429 // first delete the selection |
|
2430 NS_ENSURE_STATE(mHTMLEditor); |
|
2431 res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers); |
|
2432 NS_ENSURE_SUCCESS(res, res); |
|
2433 // then join para's, insert break |
|
2434 NS_ENSURE_STATE(mHTMLEditor); |
|
2435 res = mHTMLEditor->JoinNodeDeep(leftParent,rightParent,address_of(selNode),&selOffset); |
|
2436 NS_ENSURE_SUCCESS(res, res); |
|
2437 // fix up selection |
|
2438 res = aSelection->Collapse(selNode,selOffset); |
|
2439 return res; |
|
2440 } |
|
2441 if (nsHTMLEditUtils::IsListItem(leftParent) |
|
2442 || nsHTMLEditUtils::IsHeader(leftParent)) |
|
2443 { |
|
2444 // first delete the selection |
|
2445 NS_ENSURE_STATE(mHTMLEditor); |
|
2446 res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers); |
|
2447 NS_ENSURE_SUCCESS(res, res); |
|
2448 // join blocks |
|
2449 NS_ENSURE_STATE(mHTMLEditor); |
|
2450 res = mHTMLEditor->JoinNodeDeep(leftParent,rightParent,address_of(selNode),&selOffset); |
|
2451 NS_ENSURE_SUCCESS(res, res); |
|
2452 // fix up selection |
|
2453 res = aSelection->Collapse(selNode,selOffset); |
|
2454 return res; |
|
2455 } |
|
2456 } |
|
2457 |
|
2458 // else blocks not same type, or not siblings. Delete everything except |
|
2459 // table elements. |
|
2460 join = true; |
|
2461 |
|
2462 uint32_t rangeCount = aSelection->GetRangeCount(); |
|
2463 for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { |
|
2464 nsRefPtr<nsRange> range = aSelection->GetRangeAt(rangeIdx); |
|
2465 |
|
2466 // build a list of nodes in the range |
|
2467 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
2468 nsTrivialFunctor functor; |
|
2469 nsDOMSubtreeIterator iter; |
|
2470 res = iter.Init(range); |
|
2471 NS_ENSURE_SUCCESS(res, res); |
|
2472 res = iter.AppendList(functor, arrayOfNodes); |
|
2473 NS_ENSURE_SUCCESS(res, res); |
|
2474 |
|
2475 // now that we have the list, delete non table elements |
|
2476 int32_t listCount = arrayOfNodes.Count(); |
|
2477 for (int32_t j = 0; j < listCount; j++) { |
|
2478 nsCOMPtr<nsINode> somenode = do_QueryInterface(arrayOfNodes[0]); |
|
2479 NS_ENSURE_STATE(somenode); |
|
2480 DeleteNonTableElements(somenode); |
|
2481 arrayOfNodes.RemoveObjectAt(0); |
|
2482 // If something visible is deleted, no need to join. |
|
2483 // Visible means all nodes except non-visible textnodes and breaks. |
|
2484 if (join && origCollapsed) { |
|
2485 if (!somenode->IsContent()) { |
|
2486 join = false; |
|
2487 continue; |
|
2488 } |
|
2489 nsCOMPtr<nsIContent> content = somenode->AsContent(); |
|
2490 if (content->NodeType() == nsIDOMNode::TEXT_NODE) { |
|
2491 NS_ENSURE_STATE(mHTMLEditor); |
|
2492 mHTMLEditor->IsVisTextNode(content, &join, true); |
|
2493 } else { |
|
2494 NS_ENSURE_STATE(mHTMLEditor); |
|
2495 join = content->IsHTML(nsGkAtoms::br) && |
|
2496 !mHTMLEditor->IsVisBreak(somenode->AsDOMNode()); |
|
2497 } |
|
2498 } |
|
2499 } |
|
2500 } |
|
2501 |
|
2502 // check endopints for possible text deletion. |
|
2503 // we can assume that if text node is found, we can |
|
2504 // delete to end or to begining as appropriate, |
|
2505 // since the case where both sel endpoints in same |
|
2506 // text node was already handled (we wouldn't be here) |
|
2507 NS_ENSURE_STATE(mHTMLEditor); |
|
2508 if ( mHTMLEditor->IsTextNode(startNode) ) |
|
2509 { |
|
2510 // delete to last character |
|
2511 nsCOMPtr<nsIDOMCharacterData>nodeAsText; |
|
2512 uint32_t len; |
|
2513 nodeAsText = do_QueryInterface(startNode); |
|
2514 nodeAsText->GetLength(&len); |
|
2515 if (len > (uint32_t)startOffset) |
|
2516 { |
|
2517 NS_ENSURE_STATE(mHTMLEditor); |
|
2518 res = mHTMLEditor->DeleteText(nodeAsText,startOffset,len-startOffset); |
|
2519 NS_ENSURE_SUCCESS(res, res); |
|
2520 } |
|
2521 } |
|
2522 NS_ENSURE_STATE(mHTMLEditor); |
|
2523 if ( mHTMLEditor->IsTextNode(endNode) ) |
|
2524 { |
|
2525 // delete to first character |
|
2526 nsCOMPtr<nsIDOMCharacterData>nodeAsText; |
|
2527 nodeAsText = do_QueryInterface(endNode); |
|
2528 if (endOffset) |
|
2529 { |
|
2530 NS_ENSURE_STATE(mHTMLEditor); |
|
2531 res = mHTMLEditor->DeleteText(nodeAsText,0,endOffset); |
|
2532 NS_ENSURE_SUCCESS(res, res); |
|
2533 } |
|
2534 } |
|
2535 |
|
2536 if (join) { |
|
2537 res = JoinBlocks(leftParent, rightParent, aCancel); |
|
2538 NS_ENSURE_SUCCESS(res, res); |
|
2539 } |
|
2540 } |
|
2541 } |
|
2542 } |
|
2543 //If we're joining blocks: if deleting forward the selection should be |
|
2544 //collapsed to the end of the selection, if deleting backward the selection |
|
2545 //should be collapsed to the beginning of the selection. But if we're not |
|
2546 //joining then the selection should collapse to the beginning of the |
|
2547 //selection if we'redeleting forward, because the end of the selection will |
|
2548 //still be in the next block. And same thing for deleting backwards |
|
2549 //(selection should collapse to the end, because the beginning will still |
|
2550 //be in the first block). See Bug 507936 |
|
2551 if (join ? aAction == nsIEditor::eNext : aAction == nsIEditor::ePrevious) |
|
2552 { |
|
2553 res = aSelection->Collapse(endNode,endOffset); |
|
2554 } |
|
2555 else |
|
2556 { |
|
2557 res = aSelection->Collapse(startNode,startOffset); |
|
2558 } |
|
2559 return res; |
|
2560 } |
|
2561 |
|
2562 |
|
2563 /***************************************************************************************************** |
|
2564 * InsertBRIfNeeded: determines if a br is needed for current selection to not be spastic. |
|
2565 * If so, it inserts one. Callers responsibility to only call with collapsed selection. |
|
2566 * nsISelection *aSelection the collapsed selection |
|
2567 */ |
|
2568 nsresult |
|
2569 nsHTMLEditRules::InsertBRIfNeeded(nsISelection *aSelection) |
|
2570 { |
|
2571 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); |
|
2572 |
|
2573 // get selection |
|
2574 nsCOMPtr<nsIDOMNode> node; |
|
2575 int32_t offset; |
|
2576 nsresult res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset); |
|
2577 NS_ENSURE_SUCCESS(res, res); |
|
2578 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); |
|
2579 |
|
2580 // inline elements don't need any br |
|
2581 if (!IsBlockNode(node)) |
|
2582 return res; |
|
2583 |
|
2584 // examine selection |
|
2585 NS_ENSURE_STATE(mHTMLEditor); |
|
2586 nsWSRunObject wsObj(mHTMLEditor, node, offset); |
|
2587 if (((wsObj.mStartReason & WSType::block) || |
|
2588 (wsObj.mStartReason & WSType::br)) && |
|
2589 (wsObj.mEndReason & WSType::block)) { |
|
2590 // if we are tucked between block boundaries then insert a br |
|
2591 // first check that we are allowed to |
|
2592 NS_ENSURE_STATE(mHTMLEditor); |
|
2593 if (mHTMLEditor->CanContainTag(node, nsGkAtoms::br)) { |
|
2594 nsCOMPtr<nsIDOMNode> brNode; |
|
2595 NS_ENSURE_STATE(mHTMLEditor); |
|
2596 res = mHTMLEditor->CreateBR(node, offset, address_of(brNode), nsIEditor::ePrevious); |
|
2597 } |
|
2598 } |
|
2599 return res; |
|
2600 } |
|
2601 |
|
2602 /***************************************************************************************************** |
|
2603 * GetGoodSelPointForNode: Finds where at a node you would want to set the selection if you were |
|
2604 * trying to have a caret next to it. |
|
2605 * nsIDOMNode *aNode the node |
|
2606 * nsIEditor::EDirection aAction which edge to find: eNext indicates beginning, ePrevious ending |
|
2607 * nsCOMPtr<nsIDOMNode> *outSelNode desired sel node |
|
2608 * int32_t *outSelOffset desired sel offset |
|
2609 */ |
|
2610 nsresult |
|
2611 nsHTMLEditRules::GetGoodSelPointForNode(nsIDOMNode *aNode, nsIEditor::EDirection aAction, |
|
2612 nsCOMPtr<nsIDOMNode> *outSelNode, int32_t *outSelOffset) |
|
2613 { |
|
2614 NS_ENSURE_TRUE(aNode && outSelNode && outSelOffset, NS_ERROR_NULL_POINTER); |
|
2615 |
|
2616 nsresult res = NS_OK; |
|
2617 |
|
2618 // default values |
|
2619 *outSelNode = aNode; |
|
2620 *outSelOffset = 0; |
|
2621 |
|
2622 NS_ENSURE_STATE(mHTMLEditor); |
|
2623 if (mHTMLEditor->IsTextNode(aNode) || |
|
2624 !mHTMLEditor || mHTMLEditor->IsContainer(aNode)) |
|
2625 { |
|
2626 NS_ENSURE_STATE(mHTMLEditor); |
|
2627 if (aAction == nsIEditor::ePrevious) |
|
2628 { |
|
2629 uint32_t len; |
|
2630 res = mHTMLEditor->GetLengthOfDOMNode(aNode, len); |
|
2631 *outSelOffset = int32_t(len); |
|
2632 NS_ENSURE_SUCCESS(res, res); |
|
2633 } |
|
2634 } |
|
2635 else |
|
2636 { |
|
2637 *outSelNode = nsEditor::GetNodeLocation(aNode, outSelOffset); |
|
2638 if (!nsTextEditUtils::IsBreak(aNode) || |
|
2639 !mHTMLEditor || mHTMLEditor->IsVisBreak(aNode)) |
|
2640 { |
|
2641 NS_ENSURE_STATE(mHTMLEditor); |
|
2642 if (aAction == nsIEditor::ePrevious) |
|
2643 (*outSelOffset)++; |
|
2644 } |
|
2645 } |
|
2646 return res; |
|
2647 } |
|
2648 |
|
2649 |
|
2650 /***************************************************************************************************** |
|
2651 * JoinBlocks: this method is used to join two block elements. The right element is always joined |
|
2652 * to the left element. If the elements are the same type and not nested within each other, |
|
2653 * JoinNodesSmart is called (example, joining two list items together into one). If the elements |
|
2654 * are not the same type, or one is a descendant of the other, we instead destroy the right block |
|
2655 * placing its children into leftblock. DTD containment rules are followed throughout. |
|
2656 * nsCOMPtr<nsIDOMNode> *aLeftBlock pointer to the left block |
|
2657 * nsCOMPtr<nsIDOMNode> *aRightBlock pointer to the right block; will have contents moved to left block |
|
2658 * bool *aCanceled return TRUE if we had to cancel operation |
|
2659 */ |
|
2660 nsresult |
|
2661 nsHTMLEditRules::JoinBlocks(nsIDOMNode *aLeftNode, |
|
2662 nsIDOMNode *aRightNode, |
|
2663 bool *aCanceled) |
|
2664 { |
|
2665 NS_ENSURE_ARG_POINTER(aLeftNode && aRightNode); |
|
2666 |
|
2667 nsCOMPtr<nsIDOMNode> aLeftBlock, aRightBlock; |
|
2668 |
|
2669 if (IsBlockNode(aLeftNode)) { |
|
2670 aLeftBlock = aLeftNode; |
|
2671 } else if (aLeftNode) { |
|
2672 NS_ENSURE_STATE(mHTMLEditor); |
|
2673 aLeftBlock = mHTMLEditor->GetBlockNodeParent(aLeftNode); |
|
2674 } |
|
2675 |
|
2676 if (IsBlockNode(aRightNode)) { |
|
2677 aRightBlock = aRightNode; |
|
2678 } else if (aRightNode) { |
|
2679 NS_ENSURE_STATE(mHTMLEditor); |
|
2680 aRightBlock = mHTMLEditor->GetBlockNodeParent(aRightNode); |
|
2681 } |
|
2682 |
|
2683 // sanity checks |
|
2684 NS_ENSURE_TRUE(aLeftBlock && aRightBlock, NS_ERROR_NULL_POINTER); |
|
2685 NS_ENSURE_STATE(aLeftBlock != aRightBlock); |
|
2686 |
|
2687 if (nsHTMLEditUtils::IsTableElement(aLeftBlock) || |
|
2688 nsHTMLEditUtils::IsTableElement(aRightBlock)) { |
|
2689 // do not try to merge table elements |
|
2690 *aCanceled = true; |
|
2691 return NS_OK; |
|
2692 } |
|
2693 |
|
2694 // make sure we don't try to move thing's into HR's, which look like blocks but aren't containers |
|
2695 if (nsHTMLEditUtils::IsHR(aLeftBlock)) { |
|
2696 NS_ENSURE_STATE(mHTMLEditor); |
|
2697 nsCOMPtr<nsIDOMNode> realLeft = mHTMLEditor->GetBlockNodeParent(aLeftBlock); |
|
2698 aLeftBlock = realLeft; |
|
2699 } |
|
2700 if (nsHTMLEditUtils::IsHR(aRightBlock)) { |
|
2701 NS_ENSURE_STATE(mHTMLEditor); |
|
2702 nsCOMPtr<nsIDOMNode> realRight = mHTMLEditor->GetBlockNodeParent(aRightBlock); |
|
2703 aRightBlock = realRight; |
|
2704 } |
|
2705 |
|
2706 // bail if both blocks the same |
|
2707 if (aLeftBlock == aRightBlock) { |
|
2708 *aCanceled = true; |
|
2709 return NS_OK; |
|
2710 } |
|
2711 |
|
2712 // Joining a list item to its parent is a NOP. |
|
2713 if (nsHTMLEditUtils::IsList(aLeftBlock) && |
|
2714 nsHTMLEditUtils::IsListItem(aRightBlock)) { |
|
2715 nsCOMPtr<nsIDOMNode> rightParent; |
|
2716 aRightBlock->GetParentNode(getter_AddRefs(rightParent)); |
|
2717 if (rightParent == aLeftBlock) { |
|
2718 return NS_OK; |
|
2719 } |
|
2720 } |
|
2721 |
|
2722 // special rule here: if we are trying to join list items, and they are in different lists, |
|
2723 // join the lists instead. |
|
2724 bool bMergeLists = false; |
|
2725 nsIAtom* existingList = nsGkAtoms::_empty; |
|
2726 int32_t theOffset; |
|
2727 nsCOMPtr<nsIDOMNode> leftList, rightList; |
|
2728 if (nsHTMLEditUtils::IsListItem(aLeftBlock) && |
|
2729 nsHTMLEditUtils::IsListItem(aRightBlock)) { |
|
2730 aLeftBlock->GetParentNode(getter_AddRefs(leftList)); |
|
2731 aRightBlock->GetParentNode(getter_AddRefs(rightList)); |
|
2732 if (leftList && rightList && (leftList!=rightList)) |
|
2733 { |
|
2734 // there are some special complications if the lists are descendants of |
|
2735 // the other lists' items. Note that it is ok for them to be descendants |
|
2736 // of the other lists themselves, which is the usual case for sublists |
|
2737 // in our impllementation. |
|
2738 if (!nsEditorUtils::IsDescendantOf(leftList, aRightBlock, &theOffset) && |
|
2739 !nsEditorUtils::IsDescendantOf(rightList, aLeftBlock, &theOffset)) |
|
2740 { |
|
2741 aLeftBlock = leftList; |
|
2742 aRightBlock = rightList; |
|
2743 bMergeLists = true; |
|
2744 NS_ENSURE_STATE(mHTMLEditor); |
|
2745 existingList = mHTMLEditor->GetTag(leftList); |
|
2746 } |
|
2747 } |
|
2748 } |
|
2749 |
|
2750 NS_ENSURE_STATE(mHTMLEditor); |
|
2751 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); |
|
2752 |
|
2753 nsresult res = NS_OK; |
|
2754 int32_t rightOffset = 0; |
|
2755 int32_t leftOffset = -1; |
|
2756 |
|
2757 // theOffset below is where you find yourself in aRightBlock when you traverse upwards |
|
2758 // from aLeftBlock |
|
2759 if (nsEditorUtils::IsDescendantOf(aLeftBlock, aRightBlock, &rightOffset)) { |
|
2760 // tricky case. left block is inside right block. |
|
2761 // Do ws adjustment. This just destroys non-visible ws at boundaries we will be joining. |
|
2762 rightOffset++; |
|
2763 NS_ENSURE_STATE(mHTMLEditor); |
|
2764 res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, |
|
2765 address_of(aLeftBlock), |
|
2766 nsWSRunObject::kBlockEnd); |
|
2767 NS_ENSURE_SUCCESS(res, res); |
|
2768 NS_ENSURE_STATE(mHTMLEditor); |
|
2769 res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, |
|
2770 address_of(aRightBlock), |
|
2771 nsWSRunObject::kAfterBlock, |
|
2772 &rightOffset); |
|
2773 NS_ENSURE_SUCCESS(res, res); |
|
2774 // Do br adjustment. |
|
2775 nsCOMPtr<nsIDOMNode> brNode; |
|
2776 res = CheckForInvisibleBR(aLeftBlock, kBlockEnd, address_of(brNode)); |
|
2777 NS_ENSURE_SUCCESS(res, res); |
|
2778 if (bMergeLists) |
|
2779 { |
|
2780 // idea here is to take all children in rightList that are past |
|
2781 // theOffset, and pull them into leftlist. |
|
2782 nsCOMPtr<nsIDOMNode> childToMove; |
|
2783 nsCOMPtr<nsIContent> parent(do_QueryInterface(rightList)); |
|
2784 NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER); |
|
2785 |
|
2786 nsIContent *child = parent->GetChildAt(theOffset); |
|
2787 while (child) |
|
2788 { |
|
2789 childToMove = do_QueryInterface(child); |
|
2790 NS_ENSURE_STATE(mHTMLEditor); |
|
2791 res = mHTMLEditor->MoveNode(childToMove, leftList, -1); |
|
2792 NS_ENSURE_SUCCESS(res, res); |
|
2793 |
|
2794 child = parent->GetChildAt(rightOffset); |
|
2795 } |
|
2796 } |
|
2797 else |
|
2798 { |
|
2799 res = MoveBlock(aLeftBlock, aRightBlock, leftOffset, rightOffset); |
|
2800 } |
|
2801 NS_ENSURE_STATE(mHTMLEditor); |
|
2802 if (brNode) mHTMLEditor->DeleteNode(brNode); |
|
2803 // theOffset below is where you find yourself in aLeftBlock when you traverse upwards |
|
2804 // from aRightBlock |
|
2805 } else if (nsEditorUtils::IsDescendantOf(aRightBlock, aLeftBlock, &leftOffset)) { |
|
2806 // tricky case. right block is inside left block. |
|
2807 // Do ws adjustment. This just destroys non-visible ws at boundaries we will be joining. |
|
2808 NS_ENSURE_STATE(mHTMLEditor); |
|
2809 res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, |
|
2810 address_of(aRightBlock), |
|
2811 nsWSRunObject::kBlockStart); |
|
2812 NS_ENSURE_SUCCESS(res, res); |
|
2813 NS_ENSURE_STATE(mHTMLEditor); |
|
2814 res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, |
|
2815 address_of(aLeftBlock), |
|
2816 nsWSRunObject::kBeforeBlock, |
|
2817 &leftOffset); |
|
2818 NS_ENSURE_SUCCESS(res, res); |
|
2819 // Do br adjustment. |
|
2820 nsCOMPtr<nsIDOMNode> brNode; |
|
2821 res = CheckForInvisibleBR(aLeftBlock, kBeforeBlock, address_of(brNode), |
|
2822 leftOffset); |
|
2823 NS_ENSURE_SUCCESS(res, res); |
|
2824 if (bMergeLists) |
|
2825 { |
|
2826 res = MoveContents(rightList, leftList, &leftOffset); |
|
2827 } |
|
2828 else |
|
2829 { |
|
2830 // Left block is a parent of right block, and the parent of the previous |
|
2831 // visible content. Right block is a child and contains the contents we |
|
2832 // want to move. |
|
2833 |
|
2834 int32_t previousContentOffset; |
|
2835 nsCOMPtr<nsIDOMNode> previousContentParent; |
|
2836 |
|
2837 if (aLeftNode == aLeftBlock) { |
|
2838 // We are working with valid HTML, aLeftNode is a block node, and is |
|
2839 // therefore allowed to contain aRightBlock. This is the simple case, |
|
2840 // we will simply move the content in aRightBlock out of its block. |
|
2841 previousContentParent = aLeftBlock; |
|
2842 previousContentOffset = leftOffset; |
|
2843 } else { |
|
2844 // We try to work as well as possible with HTML that's already invalid. |
|
2845 // Although "right block" is a block, and a block must not be contained |
|
2846 // in inline elements, reality is that broken documents do exist. The |
|
2847 // DIRECT parent of "left NODE" might be an inline element. Previous |
|
2848 // versions of this code skipped inline parents until the first block |
|
2849 // parent was found (and used "left block" as the destination). |
|
2850 // However, in some situations this strategy moves the content to an |
|
2851 // unexpected position. (see bug 200416) The new idea is to make the |
|
2852 // moving content a sibling, next to the previous visible content. |
|
2853 |
|
2854 previousContentParent = |
|
2855 nsEditor::GetNodeLocation(aLeftNode, &previousContentOffset); |
|
2856 |
|
2857 // We want to move our content just after the previous visible node. |
|
2858 previousContentOffset++; |
|
2859 } |
|
2860 |
|
2861 // Because we don't want the moving content to receive the style of the |
|
2862 // previous content, we split the previous content's style. |
|
2863 |
|
2864 NS_ENSURE_STATE(mHTMLEditor); |
|
2865 nsCOMPtr<nsINode> editorRoot = mHTMLEditor->GetEditorRoot(); |
|
2866 if (!editorRoot || aLeftNode != editorRoot->AsDOMNode()) { |
|
2867 nsCOMPtr<nsIDOMNode> splittedPreviousContent; |
|
2868 NS_ENSURE_STATE(mHTMLEditor); |
|
2869 res = mHTMLEditor->SplitStyleAbovePoint(address_of(previousContentParent), |
|
2870 &previousContentOffset, |
|
2871 nullptr, nullptr, nullptr, |
|
2872 address_of(splittedPreviousContent)); |
|
2873 NS_ENSURE_SUCCESS(res, res); |
|
2874 |
|
2875 if (splittedPreviousContent) { |
|
2876 previousContentParent = |
|
2877 nsEditor::GetNodeLocation(splittedPreviousContent, |
|
2878 &previousContentOffset); |
|
2879 } |
|
2880 } |
|
2881 |
|
2882 res = MoveBlock(previousContentParent, aRightBlock, |
|
2883 previousContentOffset, rightOffset); |
|
2884 } |
|
2885 NS_ENSURE_STATE(mHTMLEditor); |
|
2886 if (brNode) mHTMLEditor->DeleteNode(brNode); |
|
2887 } |
|
2888 else |
|
2889 { |
|
2890 // normal case. blocks are siblings, or at least close enough to siblings. An example |
|
2891 // of the latter is a <p>paragraph</p><ul><li>one<li>two<li>three</ul>. The first |
|
2892 // li and the p are not true siblings, but we still want to join them if you backspace |
|
2893 // from li into p. |
|
2894 |
|
2895 // adjust whitespace at block boundaries |
|
2896 NS_ENSURE_STATE(mHTMLEditor); |
|
2897 res = nsWSRunObject::PrepareToJoinBlocks(mHTMLEditor, aLeftBlock, aRightBlock); |
|
2898 NS_ENSURE_SUCCESS(res, res); |
|
2899 // Do br adjustment. |
|
2900 nsCOMPtr<nsIDOMNode> brNode; |
|
2901 res = CheckForInvisibleBR(aLeftBlock, kBlockEnd, address_of(brNode)); |
|
2902 NS_ENSURE_SUCCESS(res, res); |
|
2903 NS_ENSURE_STATE(mHTMLEditor); |
|
2904 if (bMergeLists || mHTMLEditor->NodesSameType(aLeftBlock, aRightBlock)) { |
|
2905 // nodes are same type. merge them. |
|
2906 nsCOMPtr<nsIDOMNode> parent; |
|
2907 int32_t offset; |
|
2908 res = JoinNodesSmart(aLeftBlock, aRightBlock, address_of(parent), &offset); |
|
2909 if (NS_SUCCEEDED(res) && bMergeLists) |
|
2910 { |
|
2911 nsCOMPtr<nsIDOMNode> newBlock; |
|
2912 res = ConvertListType(aRightBlock, address_of(newBlock), |
|
2913 existingList, nsGkAtoms::li); |
|
2914 } |
|
2915 } |
|
2916 else |
|
2917 { |
|
2918 // nodes are disimilar types. |
|
2919 res = MoveBlock(aLeftBlock, aRightBlock, leftOffset, rightOffset); |
|
2920 } |
|
2921 if (NS_SUCCEEDED(res) && brNode) |
|
2922 { |
|
2923 NS_ENSURE_STATE(mHTMLEditor); |
|
2924 res = mHTMLEditor->DeleteNode(brNode); |
|
2925 } |
|
2926 } |
|
2927 return res; |
|
2928 } |
|
2929 |
|
2930 |
|
2931 /***************************************************************************************************** |
|
2932 * MoveBlock: this method is used to move the content from rightBlock into leftBlock |
|
2933 * Note that the "block" might merely be inline nodes between <br>s, or between blocks, etc. |
|
2934 * DTD containment rules are followed throughout. |
|
2935 * nsIDOMNode *aLeftBlock parent to receive moved content |
|
2936 * nsIDOMNode *aRightBlock parent to provide moved content |
|
2937 * int32_t aLeftOffset offset in aLeftBlock to move content to |
|
2938 * int32_t aRightOffset offset in aRightBlock to move content from |
|
2939 */ |
|
2940 nsresult |
|
2941 nsHTMLEditRules::MoveBlock(nsIDOMNode *aLeftBlock, nsIDOMNode *aRightBlock, int32_t aLeftOffset, int32_t aRightOffset) |
|
2942 { |
|
2943 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
2944 nsCOMPtr<nsISupports> isupports; |
|
2945 // GetNodesFromPoint is the workhorse that figures out what we wnat to move. |
|
2946 nsresult res = GetNodesFromPoint(::DOMPoint(aRightBlock,aRightOffset), |
|
2947 EditAction::makeList, arrayOfNodes, true); |
|
2948 NS_ENSURE_SUCCESS(res, res); |
|
2949 int32_t listCount = arrayOfNodes.Count(); |
|
2950 int32_t i; |
|
2951 for (i=0; i<listCount; i++) |
|
2952 { |
|
2953 // get the node to act on |
|
2954 nsIDOMNode* curNode = arrayOfNodes[i]; |
|
2955 if (IsBlockNode(curNode)) |
|
2956 { |
|
2957 // For block nodes, move their contents only, then delete block. |
|
2958 res = MoveContents(curNode, aLeftBlock, &aLeftOffset); |
|
2959 NS_ENSURE_SUCCESS(res, res); |
|
2960 NS_ENSURE_STATE(mHTMLEditor); |
|
2961 res = mHTMLEditor->DeleteNode(curNode); |
|
2962 } |
|
2963 else |
|
2964 { |
|
2965 // otherwise move the content as is, checking against the dtd. |
|
2966 res = MoveNodeSmart(curNode, aLeftBlock, &aLeftOffset); |
|
2967 } |
|
2968 } |
|
2969 return res; |
|
2970 } |
|
2971 |
|
2972 /***************************************************************************************************** |
|
2973 * MoveNodeSmart: this method is used to move node aSource to (aDest,aOffset). |
|
2974 * DTD containment rules are followed throughout. aOffset is updated to point _after_ |
|
2975 * inserted content. |
|
2976 * nsIDOMNode *aSource the selection. |
|
2977 * nsIDOMNode *aDest parent to receive moved content |
|
2978 * int32_t *aOffset offset in aDest to move content to |
|
2979 */ |
|
2980 nsresult |
|
2981 nsHTMLEditRules::MoveNodeSmart(nsIDOMNode *aSource, nsIDOMNode *aDest, int32_t *aOffset) |
|
2982 { |
|
2983 NS_ENSURE_TRUE(aSource && aDest && aOffset, NS_ERROR_NULL_POINTER); |
|
2984 |
|
2985 nsresult res; |
|
2986 // check if this node can go into the destination node |
|
2987 NS_ENSURE_STATE(mHTMLEditor); |
|
2988 if (mHTMLEditor->CanContain(aDest, aSource)) { |
|
2989 // if it can, move it there |
|
2990 NS_ENSURE_STATE(mHTMLEditor); |
|
2991 res = mHTMLEditor->MoveNode(aSource, aDest, *aOffset); |
|
2992 NS_ENSURE_SUCCESS(res, res); |
|
2993 if (*aOffset != -1) ++(*aOffset); |
|
2994 } |
|
2995 else |
|
2996 { |
|
2997 // if it can't, move its children, and then delete it. |
|
2998 res = MoveContents(aSource, aDest, aOffset); |
|
2999 NS_ENSURE_SUCCESS(res, res); |
|
3000 NS_ENSURE_STATE(mHTMLEditor); |
|
3001 res = mHTMLEditor->DeleteNode(aSource); |
|
3002 NS_ENSURE_SUCCESS(res, res); |
|
3003 } |
|
3004 return NS_OK; |
|
3005 } |
|
3006 |
|
3007 /***************************************************************************************************** |
|
3008 * MoveContents: this method is used to move node the _contents_ of aSource to (aDest,aOffset). |
|
3009 * DTD containment rules are followed throughout. aOffset is updated to point _after_ |
|
3010 * inserted content. aSource is deleted. |
|
3011 * nsIDOMNode *aSource the selection. |
|
3012 * nsIDOMNode *aDest parent to receive moved content |
|
3013 * int32_t *aOffset offset in aDest to move content to |
|
3014 */ |
|
3015 nsresult |
|
3016 nsHTMLEditRules::MoveContents(nsIDOMNode *aSource, nsIDOMNode *aDest, int32_t *aOffset) |
|
3017 { |
|
3018 NS_ENSURE_TRUE(aSource && aDest && aOffset, NS_ERROR_NULL_POINTER); |
|
3019 if (aSource == aDest) return NS_ERROR_ILLEGAL_VALUE; |
|
3020 NS_ENSURE_STATE(mHTMLEditor); |
|
3021 NS_ASSERTION(!mHTMLEditor->IsTextNode(aSource), "#text does not have contents"); |
|
3022 |
|
3023 nsCOMPtr<nsIDOMNode> child; |
|
3024 nsAutoString tag; |
|
3025 nsresult res; |
|
3026 aSource->GetFirstChild(getter_AddRefs(child)); |
|
3027 while (child) |
|
3028 { |
|
3029 res = MoveNodeSmart(child, aDest, aOffset); |
|
3030 NS_ENSURE_SUCCESS(res, res); |
|
3031 aSource->GetFirstChild(getter_AddRefs(child)); |
|
3032 } |
|
3033 return NS_OK; |
|
3034 } |
|
3035 |
|
3036 |
|
3037 nsresult |
|
3038 nsHTMLEditRules::DeleteNonTableElements(nsINode* aNode) |
|
3039 { |
|
3040 MOZ_ASSERT(aNode); |
|
3041 if (!nsHTMLEditUtils::IsTableElementButNotTable(aNode)) { |
|
3042 NS_ENSURE_STATE(mHTMLEditor); |
|
3043 return mHTMLEditor->DeleteNode(aNode->AsDOMNode()); |
|
3044 } |
|
3045 |
|
3046 for (int32_t i = aNode->GetChildCount() - 1; i >= 0; --i) { |
|
3047 nsresult rv = DeleteNonTableElements(aNode->GetChildAt(i)); |
|
3048 NS_ENSURE_SUCCESS(rv, rv); |
|
3049 } |
|
3050 return NS_OK; |
|
3051 } |
|
3052 |
|
3053 nsresult |
|
3054 nsHTMLEditRules::DidDeleteSelection(nsISelection *aSelection, |
|
3055 nsIEditor::EDirection aDir, |
|
3056 nsresult aResult) |
|
3057 { |
|
3058 if (!aSelection) { return NS_ERROR_NULL_POINTER; } |
|
3059 |
|
3060 // find where we are |
|
3061 nsCOMPtr<nsIDOMNode> startNode; |
|
3062 int32_t startOffset; |
|
3063 nsresult res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset); |
|
3064 NS_ENSURE_SUCCESS(res, res); |
|
3065 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); |
|
3066 |
|
3067 // find any enclosing mailcite |
|
3068 nsCOMPtr<nsIDOMNode> citeNode; |
|
3069 res = GetTopEnclosingMailCite(startNode, address_of(citeNode), |
|
3070 IsPlaintextEditor()); |
|
3071 NS_ENSURE_SUCCESS(res, res); |
|
3072 if (citeNode) { |
|
3073 nsCOMPtr<nsINode> cite = do_QueryInterface(citeNode); |
|
3074 bool isEmpty = true, seenBR = false; |
|
3075 NS_ENSURE_STATE(mHTMLEditor); |
|
3076 mHTMLEditor->IsEmptyNodeImpl(cite, &isEmpty, true, true, false, &seenBR); |
|
3077 if (isEmpty) |
|
3078 { |
|
3079 nsCOMPtr<nsIDOMNode> brNode; |
|
3080 int32_t offset; |
|
3081 nsCOMPtr<nsIDOMNode> parent = nsEditor::GetNodeLocation(citeNode, &offset); |
|
3082 NS_ENSURE_STATE(mHTMLEditor); |
|
3083 res = mHTMLEditor->DeleteNode(citeNode); |
|
3084 NS_ENSURE_SUCCESS(res, res); |
|
3085 if (parent && seenBR) |
|
3086 { |
|
3087 NS_ENSURE_STATE(mHTMLEditor); |
|
3088 res = mHTMLEditor->CreateBR(parent, offset, address_of(brNode)); |
|
3089 NS_ENSURE_SUCCESS(res, res); |
|
3090 aSelection->Collapse(parent, offset); |
|
3091 } |
|
3092 } |
|
3093 } |
|
3094 |
|
3095 // call through to base class |
|
3096 return nsTextEditRules::DidDeleteSelection(aSelection, aDir, aResult); |
|
3097 } |
|
3098 |
|
3099 nsresult |
|
3100 nsHTMLEditRules::WillMakeList(Selection* aSelection, |
|
3101 const nsAString* aListType, |
|
3102 bool aEntireList, |
|
3103 const nsAString* aBulletType, |
|
3104 bool* aCancel, |
|
3105 bool* aHandled, |
|
3106 const nsAString* aItemType) |
|
3107 { |
|
3108 if (!aSelection || !aListType || !aCancel || !aHandled) { |
|
3109 return NS_ERROR_NULL_POINTER; |
|
3110 } |
|
3111 nsCOMPtr<nsIAtom> listTypeAtom = do_GetAtom(*aListType); |
|
3112 NS_ENSURE_TRUE(listTypeAtom, NS_ERROR_OUT_OF_MEMORY); |
|
3113 |
|
3114 nsresult res = WillInsert(aSelection, aCancel); |
|
3115 NS_ENSURE_SUCCESS(res, res); |
|
3116 |
|
3117 // initialize out param |
|
3118 // we want to ignore result of WillInsert() |
|
3119 *aCancel = false; |
|
3120 *aHandled = false; |
|
3121 |
|
3122 // deduce what tag to use for list items |
|
3123 nsCOMPtr<nsIAtom> itemType; |
|
3124 if (aItemType) { |
|
3125 itemType = do_GetAtom(*aItemType); |
|
3126 NS_ENSURE_TRUE(itemType, NS_ERROR_OUT_OF_MEMORY); |
|
3127 } else if (listTypeAtom == nsGkAtoms::dl) { |
|
3128 itemType = nsGkAtoms::dd; |
|
3129 } else { |
|
3130 itemType = nsGkAtoms::li; |
|
3131 } |
|
3132 |
|
3133 // convert the selection ranges into "promoted" selection ranges: |
|
3134 // this basically just expands the range to include the immediate |
|
3135 // block parent, and then further expands to include any ancestors |
|
3136 // whose children are all in the range |
|
3137 |
|
3138 *aHandled = true; |
|
3139 |
|
3140 res = NormalizeSelection(aSelection); |
|
3141 NS_ENSURE_SUCCESS(res, res); |
|
3142 NS_ENSURE_STATE(mHTMLEditor); |
|
3143 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); |
|
3144 |
|
3145 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
3146 res = GetListActionNodes(arrayOfNodes, aEntireList); |
|
3147 NS_ENSURE_SUCCESS(res, res); |
|
3148 |
|
3149 int32_t listCount = arrayOfNodes.Count(); |
|
3150 |
|
3151 // check if all our nodes are <br>s, or empty inlines |
|
3152 bool bOnlyBreaks = true; |
|
3153 for (int32_t j = 0; j < listCount; j++) { |
|
3154 nsIDOMNode* curNode = arrayOfNodes[j]; |
|
3155 // if curNode is not a Break or empty inline, we're done |
|
3156 if (!nsTextEditUtils::IsBreak(curNode) && !IsEmptyInline(curNode)) { |
|
3157 bOnlyBreaks = false; |
|
3158 break; |
|
3159 } |
|
3160 } |
|
3161 |
|
3162 // if no nodes, we make empty list. Ditto if the user tried to make a list |
|
3163 // of some # of breaks. |
|
3164 if (!listCount || bOnlyBreaks) { |
|
3165 nsCOMPtr<nsIDOMNode> parent, theList, theListItem; |
|
3166 int32_t offset; |
|
3167 |
|
3168 // if only breaks, delete them |
|
3169 if (bOnlyBreaks) { |
|
3170 for (int32_t j = 0; j < (int32_t)listCount; j++) { |
|
3171 NS_ENSURE_STATE(mHTMLEditor); |
|
3172 res = mHTMLEditor->DeleteNode(arrayOfNodes[j]); |
|
3173 NS_ENSURE_SUCCESS(res, res); |
|
3174 } |
|
3175 } |
|
3176 |
|
3177 // get selection location |
|
3178 NS_ENSURE_STATE(mHTMLEditor); |
|
3179 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, |
|
3180 getter_AddRefs(parent), &offset); |
|
3181 NS_ENSURE_SUCCESS(res, res); |
|
3182 |
|
3183 // make sure we can put a list here |
|
3184 NS_ENSURE_STATE(mHTMLEditor); |
|
3185 if (!mHTMLEditor->CanContainTag(parent, listTypeAtom)) { |
|
3186 *aCancel = true; |
|
3187 return NS_OK; |
|
3188 } |
|
3189 res = SplitAsNeeded(aListType, address_of(parent), &offset); |
|
3190 NS_ENSURE_SUCCESS(res, res); |
|
3191 NS_ENSURE_STATE(mHTMLEditor); |
|
3192 res = mHTMLEditor->CreateNode(*aListType, parent, offset, |
|
3193 getter_AddRefs(theList)); |
|
3194 NS_ENSURE_SUCCESS(res, res); |
|
3195 NS_ENSURE_STATE(mHTMLEditor); |
|
3196 res = mHTMLEditor->CreateNode(nsDependentAtomString(itemType), theList, 0, |
|
3197 getter_AddRefs(theListItem)); |
|
3198 NS_ENSURE_SUCCESS(res, res); |
|
3199 // remember our new block for postprocessing |
|
3200 mNewBlock = theListItem; |
|
3201 // put selection in new list item |
|
3202 res = aSelection->Collapse(theListItem, 0); |
|
3203 // to prevent selection resetter from overriding us |
|
3204 selectionResetter.Abort(); |
|
3205 *aHandled = true; |
|
3206 return res; |
|
3207 } |
|
3208 |
|
3209 // if there is only one node in the array, and it is a list, div, or |
|
3210 // blockquote, then look inside of it until we find inner list or content. |
|
3211 |
|
3212 res = LookInsideDivBQandList(arrayOfNodes); |
|
3213 NS_ENSURE_SUCCESS(res, res); |
|
3214 |
|
3215 // Ok, now go through all the nodes and put then in the list, |
|
3216 // or whatever is approriate. Wohoo! |
|
3217 |
|
3218 listCount = arrayOfNodes.Count(); |
|
3219 nsCOMPtr<nsIDOMNode> curParent; |
|
3220 nsCOMPtr<nsIDOMNode> curList; |
|
3221 nsCOMPtr<nsIDOMNode> prevListItem; |
|
3222 |
|
3223 for (int32_t i = 0; i < listCount; i++) { |
|
3224 // here's where we actually figure out what to do |
|
3225 nsCOMPtr<nsIDOMNode> newBlock; |
|
3226 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i]; |
|
3227 int32_t offset; |
|
3228 curParent = nsEditor::GetNodeLocation(curNode, &offset); |
|
3229 |
|
3230 // make sure we don't assemble content that is in different table cells |
|
3231 // into the same list. respect table cell boundaries when listifying. |
|
3232 if (curList && InDifferentTableElements(curList, curNode)) { |
|
3233 curList = nullptr; |
|
3234 } |
|
3235 |
|
3236 // if curNode is a Break, delete it, and quit remembering prev list item |
|
3237 if (nsTextEditUtils::IsBreak(curNode)) { |
|
3238 NS_ENSURE_STATE(mHTMLEditor); |
|
3239 res = mHTMLEditor->DeleteNode(curNode); |
|
3240 NS_ENSURE_SUCCESS(res, res); |
|
3241 prevListItem = 0; |
|
3242 continue; |
|
3243 } else if (IsEmptyInline(curNode)) { |
|
3244 // if curNode is an empty inline container, delete it |
|
3245 NS_ENSURE_STATE(mHTMLEditor); |
|
3246 res = mHTMLEditor->DeleteNode(curNode); |
|
3247 NS_ENSURE_SUCCESS(res, res); |
|
3248 continue; |
|
3249 } |
|
3250 |
|
3251 if (nsHTMLEditUtils::IsList(curNode)) { |
|
3252 // do we have a curList already? |
|
3253 if (curList && !nsEditorUtils::IsDescendantOf(curNode, curList)) { |
|
3254 // move all of our children into curList. cheezy way to do it: move |
|
3255 // whole list and then RemoveContainer() on the list. ConvertListType |
|
3256 // first: that routine handles converting the list item types, if |
|
3257 // needed |
|
3258 NS_ENSURE_STATE(mHTMLEditor); |
|
3259 res = mHTMLEditor->MoveNode(curNode, curList, -1); |
|
3260 NS_ENSURE_SUCCESS(res, res); |
|
3261 res = ConvertListType(curNode, address_of(newBlock), listTypeAtom, |
|
3262 itemType); |
|
3263 NS_ENSURE_SUCCESS(res, res); |
|
3264 NS_ENSURE_STATE(mHTMLEditor); |
|
3265 res = mHTMLEditor->RemoveBlockContainer(newBlock); |
|
3266 NS_ENSURE_SUCCESS(res, res); |
|
3267 } else { |
|
3268 // replace list with new list type |
|
3269 res = ConvertListType(curNode, address_of(newBlock), listTypeAtom, |
|
3270 itemType); |
|
3271 NS_ENSURE_SUCCESS(res, res); |
|
3272 curList = newBlock; |
|
3273 } |
|
3274 prevListItem = 0; |
|
3275 continue; |
|
3276 } |
|
3277 |
|
3278 if (nsHTMLEditUtils::IsListItem(curNode)) { |
|
3279 NS_ENSURE_STATE(mHTMLEditor); |
|
3280 if (mHTMLEditor->GetTag(curParent) != listTypeAtom) { |
|
3281 // list item is in wrong type of list. if we don't have a curList, |
|
3282 // split the old list and make a new list of correct type. |
|
3283 if (!curList || nsEditorUtils::IsDescendantOf(curNode, curList)) { |
|
3284 NS_ENSURE_STATE(mHTMLEditor); |
|
3285 res = mHTMLEditor->SplitNode(curParent, offset, |
|
3286 getter_AddRefs(newBlock)); |
|
3287 NS_ENSURE_SUCCESS(res, res); |
|
3288 int32_t offset; |
|
3289 nsCOMPtr<nsIDOMNode> parent = nsEditor::GetNodeLocation(curParent, &offset); |
|
3290 NS_ENSURE_STATE(mHTMLEditor); |
|
3291 res = mHTMLEditor->CreateNode(*aListType, parent, offset, |
|
3292 getter_AddRefs(curList)); |
|
3293 NS_ENSURE_SUCCESS(res, res); |
|
3294 } |
|
3295 // move list item to new list |
|
3296 NS_ENSURE_STATE(mHTMLEditor); |
|
3297 res = mHTMLEditor->MoveNode(curNode, curList, -1); |
|
3298 NS_ENSURE_SUCCESS(res, res); |
|
3299 // convert list item type if needed |
|
3300 NS_ENSURE_STATE(mHTMLEditor); |
|
3301 if (!mHTMLEditor->NodeIsType(curNode, itemType)) { |
|
3302 NS_ENSURE_STATE(mHTMLEditor); |
|
3303 res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock), |
|
3304 nsDependentAtomString(itemType)); |
|
3305 NS_ENSURE_SUCCESS(res, res); |
|
3306 } |
|
3307 } else { |
|
3308 // item is in right type of list. But we might still have to move it. |
|
3309 // and we might need to convert list item types. |
|
3310 if (!curList) { |
|
3311 curList = curParent; |
|
3312 } else if (curParent != curList) { |
|
3313 // move list item to new list |
|
3314 NS_ENSURE_STATE(mHTMLEditor); |
|
3315 res = mHTMLEditor->MoveNode(curNode, curList, -1); |
|
3316 NS_ENSURE_SUCCESS(res, res); |
|
3317 } |
|
3318 NS_ENSURE_STATE(mHTMLEditor); |
|
3319 if (!mHTMLEditor->NodeIsType(curNode, itemType)) { |
|
3320 NS_ENSURE_STATE(mHTMLEditor); |
|
3321 res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock), |
|
3322 nsDependentAtomString(itemType)); |
|
3323 NS_ENSURE_SUCCESS(res, res); |
|
3324 } |
|
3325 } |
|
3326 nsCOMPtr<nsIDOMElement> curElement = do_QueryInterface(curNode); |
|
3327 NS_NAMED_LITERAL_STRING(typestr, "type"); |
|
3328 if (aBulletType && !aBulletType->IsEmpty()) { |
|
3329 NS_ENSURE_STATE(mHTMLEditor); |
|
3330 res = mHTMLEditor->SetAttribute(curElement, typestr, *aBulletType); |
|
3331 } else { |
|
3332 NS_ENSURE_STATE(mHTMLEditor); |
|
3333 res = mHTMLEditor->RemoveAttribute(curElement, typestr); |
|
3334 } |
|
3335 NS_ENSURE_SUCCESS(res, res); |
|
3336 continue; |
|
3337 } |
|
3338 |
|
3339 // if we hit a div clear our prevListItem, insert divs contents |
|
3340 // into our node array, and remove the div |
|
3341 if (nsHTMLEditUtils::IsDiv(curNode)) { |
|
3342 prevListItem = nullptr; |
|
3343 int32_t j = i + 1; |
|
3344 res = GetInnerContent(curNode, arrayOfNodes, &j); |
|
3345 NS_ENSURE_SUCCESS(res, res); |
|
3346 NS_ENSURE_STATE(mHTMLEditor); |
|
3347 res = mHTMLEditor->RemoveContainer(curNode); |
|
3348 NS_ENSURE_SUCCESS(res, res); |
|
3349 listCount = arrayOfNodes.Count(); |
|
3350 continue; |
|
3351 } |
|
3352 |
|
3353 // need to make a list to put things in if we haven't already, |
|
3354 if (!curList) { |
|
3355 res = SplitAsNeeded(aListType, address_of(curParent), &offset); |
|
3356 NS_ENSURE_SUCCESS(res, res); |
|
3357 NS_ENSURE_STATE(mHTMLEditor); |
|
3358 res = mHTMLEditor->CreateNode(*aListType, curParent, offset, |
|
3359 getter_AddRefs(curList)); |
|
3360 NS_ENSURE_SUCCESS(res, res); |
|
3361 // remember our new block for postprocessing |
|
3362 mNewBlock = curList; |
|
3363 // curList is now the correct thing to put curNode in |
|
3364 prevListItem = 0; |
|
3365 } |
|
3366 |
|
3367 // if curNode isn't a list item, we must wrap it in one |
|
3368 nsCOMPtr<nsIDOMNode> listItem; |
|
3369 if (!nsHTMLEditUtils::IsListItem(curNode)) { |
|
3370 if (IsInlineNode(curNode) && prevListItem) { |
|
3371 // this is a continuation of some inline nodes that belong together in |
|
3372 // the same list item. use prevListItem |
|
3373 NS_ENSURE_STATE(mHTMLEditor); |
|
3374 res = mHTMLEditor->MoveNode(curNode, prevListItem, -1); |
|
3375 NS_ENSURE_SUCCESS(res, res); |
|
3376 } else { |
|
3377 // don't wrap li around a paragraph. instead replace paragraph with li |
|
3378 if (nsHTMLEditUtils::IsParagraph(curNode)) { |
|
3379 NS_ENSURE_STATE(mHTMLEditor); |
|
3380 res = mHTMLEditor->ReplaceContainer(curNode, address_of(listItem), |
|
3381 nsDependentAtomString(itemType)); |
|
3382 } else { |
|
3383 NS_ENSURE_STATE(mHTMLEditor); |
|
3384 res = mHTMLEditor->InsertContainerAbove(curNode, address_of(listItem), |
|
3385 nsDependentAtomString(itemType)); |
|
3386 } |
|
3387 NS_ENSURE_SUCCESS(res, res); |
|
3388 if (IsInlineNode(curNode)) { |
|
3389 prevListItem = listItem; |
|
3390 } else { |
|
3391 prevListItem = nullptr; |
|
3392 } |
|
3393 } |
|
3394 } else { |
|
3395 listItem = curNode; |
|
3396 } |
|
3397 |
|
3398 if (listItem) { |
|
3399 // if we made a new list item, deal with it: tuck the listItem into the |
|
3400 // end of the active list |
|
3401 NS_ENSURE_STATE(mHTMLEditor); |
|
3402 res = mHTMLEditor->MoveNode(listItem, curList, -1); |
|
3403 NS_ENSURE_SUCCESS(res, res); |
|
3404 } |
|
3405 } |
|
3406 |
|
3407 return res; |
|
3408 } |
|
3409 |
|
3410 |
|
3411 nsresult |
|
3412 nsHTMLEditRules::WillRemoveList(Selection* aSelection, |
|
3413 bool aOrdered, |
|
3414 bool *aCancel, |
|
3415 bool *aHandled) |
|
3416 { |
|
3417 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } |
|
3418 // initialize out param |
|
3419 *aCancel = false; |
|
3420 *aHandled = true; |
|
3421 |
|
3422 nsresult res = NormalizeSelection(aSelection); |
|
3423 NS_ENSURE_SUCCESS(res, res); |
|
3424 NS_ENSURE_STATE(mHTMLEditor); |
|
3425 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); |
|
3426 |
|
3427 nsCOMArray<nsIDOMRange> arrayOfRanges; |
|
3428 res = GetPromotedRanges(aSelection, arrayOfRanges, EditAction::makeList); |
|
3429 NS_ENSURE_SUCCESS(res, res); |
|
3430 |
|
3431 // use these ranges to contruct a list of nodes to act on. |
|
3432 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
3433 res = GetListActionNodes(arrayOfNodes, false); |
|
3434 NS_ENSURE_SUCCESS(res, res); |
|
3435 |
|
3436 // Remove all non-editable nodes. Leave them be. |
|
3437 int32_t listCount = arrayOfNodes.Count(); |
|
3438 int32_t i; |
|
3439 for (i=listCount-1; i>=0; i--) |
|
3440 { |
|
3441 nsIDOMNode* testNode = arrayOfNodes[i]; |
|
3442 NS_ENSURE_STATE(mHTMLEditor); |
|
3443 if (!mHTMLEditor->IsEditable(testNode)) |
|
3444 { |
|
3445 arrayOfNodes.RemoveObjectAt(i); |
|
3446 } |
|
3447 } |
|
3448 |
|
3449 // reset list count |
|
3450 listCount = arrayOfNodes.Count(); |
|
3451 |
|
3452 // Only act on lists or list items in the array |
|
3453 nsCOMPtr<nsIDOMNode> curParent; |
|
3454 for (i=0; i<listCount; i++) |
|
3455 { |
|
3456 // here's where we actually figure out what to do |
|
3457 nsIDOMNode* curNode = arrayOfNodes[i]; |
|
3458 int32_t offset; |
|
3459 curParent = nsEditor::GetNodeLocation(curNode, &offset); |
|
3460 |
|
3461 if (nsHTMLEditUtils::IsListItem(curNode)) // unlist this listitem |
|
3462 { |
|
3463 bool bOutOfList; |
|
3464 do |
|
3465 { |
|
3466 res = PopListItem(curNode, &bOutOfList); |
|
3467 NS_ENSURE_SUCCESS(res, res); |
|
3468 } while (!bOutOfList); // keep popping it out until it's not in a list anymore |
|
3469 } |
|
3470 else if (nsHTMLEditUtils::IsList(curNode)) // node is a list, move list items out |
|
3471 { |
|
3472 res = RemoveListStructure(curNode); |
|
3473 NS_ENSURE_SUCCESS(res, res); |
|
3474 } |
|
3475 } |
|
3476 return res; |
|
3477 } |
|
3478 |
|
3479 |
|
3480 nsresult |
|
3481 nsHTMLEditRules::WillMakeDefListItem(Selection* aSelection, |
|
3482 const nsAString *aItemType, |
|
3483 bool aEntireList, |
|
3484 bool *aCancel, |
|
3485 bool *aHandled) |
|
3486 { |
|
3487 // for now we let WillMakeList handle this |
|
3488 NS_NAMED_LITERAL_STRING(listType, "dl"); |
|
3489 return WillMakeList(aSelection, &listType, aEntireList, nullptr, aCancel, aHandled, aItemType); |
|
3490 } |
|
3491 |
|
3492 nsresult |
|
3493 nsHTMLEditRules::WillMakeBasicBlock(Selection* aSelection, |
|
3494 const nsAString *aBlockType, |
|
3495 bool *aCancel, |
|
3496 bool *aHandled) |
|
3497 { |
|
3498 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } |
|
3499 // initialize out param |
|
3500 *aCancel = false; |
|
3501 *aHandled = false; |
|
3502 |
|
3503 nsresult res = WillInsert(aSelection, aCancel); |
|
3504 NS_ENSURE_SUCCESS(res, res); |
|
3505 // initialize out param |
|
3506 // we want to ignore result of WillInsert() |
|
3507 *aCancel = false; |
|
3508 res = NormalizeSelection(aSelection); |
|
3509 NS_ENSURE_SUCCESS(res, res); |
|
3510 NS_ENSURE_STATE(mHTMLEditor); |
|
3511 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); |
|
3512 NS_ENSURE_STATE(mHTMLEditor); |
|
3513 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); |
|
3514 *aHandled = true; |
|
3515 nsString tString(*aBlockType); |
|
3516 |
|
3517 // contruct a list of nodes to act on. |
|
3518 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
3519 res = GetNodesFromSelection(aSelection, EditAction::makeBasicBlock, |
|
3520 arrayOfNodes); |
|
3521 NS_ENSURE_SUCCESS(res, res); |
|
3522 |
|
3523 // Remove all non-editable nodes. Leave them be. |
|
3524 int32_t listCount = arrayOfNodes.Count(); |
|
3525 int32_t i; |
|
3526 for (i=listCount-1; i>=0; i--) |
|
3527 { |
|
3528 NS_ENSURE_STATE(mHTMLEditor); |
|
3529 if (!mHTMLEditor->IsEditable(arrayOfNodes[i])) |
|
3530 { |
|
3531 arrayOfNodes.RemoveObjectAt(i); |
|
3532 } |
|
3533 } |
|
3534 |
|
3535 // reset list count |
|
3536 listCount = arrayOfNodes.Count(); |
|
3537 |
|
3538 // if nothing visible in list, make an empty block |
|
3539 if (ListIsEmptyLine(arrayOfNodes)) |
|
3540 { |
|
3541 nsCOMPtr<nsIDOMNode> parent, theBlock; |
|
3542 int32_t offset; |
|
3543 |
|
3544 // get selection location |
|
3545 NS_ENSURE_STATE(mHTMLEditor); |
|
3546 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); |
|
3547 NS_ENSURE_SUCCESS(res, res); |
|
3548 if (tString.EqualsLiteral("normal") || |
|
3549 tString.IsEmpty() ) // we are removing blocks (going to "body text") |
|
3550 { |
|
3551 nsCOMPtr<nsIDOMNode> curBlock = parent; |
|
3552 if (!IsBlockNode(curBlock)) { |
|
3553 NS_ENSURE_STATE(mHTMLEditor); |
|
3554 curBlock = mHTMLEditor->GetBlockNodeParent(parent); |
|
3555 } |
|
3556 nsCOMPtr<nsIDOMNode> curBlockPar; |
|
3557 NS_ENSURE_TRUE(curBlock, NS_ERROR_NULL_POINTER); |
|
3558 curBlock->GetParentNode(getter_AddRefs(curBlockPar)); |
|
3559 if (nsHTMLEditUtils::IsFormatNode(curBlock)) |
|
3560 { |
|
3561 // if the first editable node after selection is a br, consume it. Otherwise |
|
3562 // it gets pushed into a following block after the split, which is visually bad. |
|
3563 nsCOMPtr<nsIDOMNode> brNode; |
|
3564 NS_ENSURE_STATE(mHTMLEditor); |
|
3565 res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode)); |
|
3566 NS_ENSURE_SUCCESS(res, res); |
|
3567 if (brNode && nsTextEditUtils::IsBreak(brNode)) |
|
3568 { |
|
3569 NS_ENSURE_STATE(mHTMLEditor); |
|
3570 res = mHTMLEditor->DeleteNode(brNode); |
|
3571 NS_ENSURE_SUCCESS(res, res); |
|
3572 } |
|
3573 // do the splits! |
|
3574 NS_ENSURE_STATE(mHTMLEditor); |
|
3575 res = mHTMLEditor->SplitNodeDeep(curBlock, parent, offset, &offset, true); |
|
3576 NS_ENSURE_SUCCESS(res, res); |
|
3577 // put a br at the split point |
|
3578 NS_ENSURE_STATE(mHTMLEditor); |
|
3579 res = mHTMLEditor->CreateBR(curBlockPar, offset, address_of(brNode)); |
|
3580 NS_ENSURE_SUCCESS(res, res); |
|
3581 // put selection at the split point |
|
3582 res = aSelection->Collapse(curBlockPar, offset); |
|
3583 selectionResetter.Abort(); // to prevent selection reseter from overriding us. |
|
3584 *aHandled = true; |
|
3585 } |
|
3586 // else nothing to do! |
|
3587 } |
|
3588 else // we are making a block |
|
3589 { |
|
3590 // consume a br, if needed |
|
3591 nsCOMPtr<nsIDOMNode> brNode; |
|
3592 NS_ENSURE_STATE(mHTMLEditor); |
|
3593 res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode), true); |
|
3594 NS_ENSURE_SUCCESS(res, res); |
|
3595 if (brNode && nsTextEditUtils::IsBreak(brNode)) |
|
3596 { |
|
3597 NS_ENSURE_STATE(mHTMLEditor); |
|
3598 res = mHTMLEditor->DeleteNode(brNode); |
|
3599 NS_ENSURE_SUCCESS(res, res); |
|
3600 // we don't need to act on this node any more |
|
3601 arrayOfNodes.RemoveObject(brNode); |
|
3602 } |
|
3603 // make sure we can put a block here |
|
3604 res = SplitAsNeeded(aBlockType, address_of(parent), &offset); |
|
3605 NS_ENSURE_SUCCESS(res, res); |
|
3606 NS_ENSURE_STATE(mHTMLEditor); |
|
3607 res = mHTMLEditor->CreateNode(*aBlockType, parent, offset, getter_AddRefs(theBlock)); |
|
3608 NS_ENSURE_SUCCESS(res, res); |
|
3609 // remember our new block for postprocessing |
|
3610 mNewBlock = theBlock; |
|
3611 // delete anything that was in the list of nodes |
|
3612 for (int32_t j = arrayOfNodes.Count() - 1; j >= 0; --j) |
|
3613 { |
|
3614 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[0]; |
|
3615 NS_ENSURE_STATE(mHTMLEditor); |
|
3616 res = mHTMLEditor->DeleteNode(curNode); |
|
3617 NS_ENSURE_SUCCESS(res, res); |
|
3618 arrayOfNodes.RemoveObjectAt(0); |
|
3619 } |
|
3620 // put selection in new block |
|
3621 res = aSelection->Collapse(theBlock,0); |
|
3622 selectionResetter.Abort(); // to prevent selection reseter from overriding us. |
|
3623 *aHandled = true; |
|
3624 } |
|
3625 return res; |
|
3626 } |
|
3627 else |
|
3628 { |
|
3629 // Ok, now go through all the nodes and make the right kind of blocks, |
|
3630 // or whatever is approriate. Wohoo! |
|
3631 // Note: blockquote is handled a little differently |
|
3632 if (tString.EqualsLiteral("blockquote")) |
|
3633 res = MakeBlockquote(arrayOfNodes); |
|
3634 else if (tString.EqualsLiteral("normal") || |
|
3635 tString.IsEmpty() ) |
|
3636 res = RemoveBlockStyle(arrayOfNodes); |
|
3637 else |
|
3638 res = ApplyBlockStyle(arrayOfNodes, aBlockType); |
|
3639 return res; |
|
3640 } |
|
3641 return res; |
|
3642 } |
|
3643 |
|
3644 nsresult |
|
3645 nsHTMLEditRules::DidMakeBasicBlock(nsISelection *aSelection, |
|
3646 nsRulesInfo *aInfo, nsresult aResult) |
|
3647 { |
|
3648 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); |
|
3649 // check for empty block. if so, put a moz br in it. |
|
3650 if (!aSelection->Collapsed()) { |
|
3651 return NS_OK; |
|
3652 } |
|
3653 |
|
3654 nsCOMPtr<nsIDOMNode> parent; |
|
3655 int32_t offset; |
|
3656 nsresult res = nsEditor::GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); |
|
3657 NS_ENSURE_SUCCESS(res, res); |
|
3658 res = InsertMozBRIfNeeded(parent); |
|
3659 return res; |
|
3660 } |
|
3661 |
|
3662 nsresult |
|
3663 nsHTMLEditRules::WillIndent(Selection* aSelection, |
|
3664 bool* aCancel, bool* aHandled) |
|
3665 { |
|
3666 nsresult res; |
|
3667 NS_ENSURE_STATE(mHTMLEditor); |
|
3668 if (mHTMLEditor->IsCSSEnabled()) { |
|
3669 res = WillCSSIndent(aSelection, aCancel, aHandled); |
|
3670 } |
|
3671 else { |
|
3672 res = WillHTMLIndent(aSelection, aCancel, aHandled); |
|
3673 } |
|
3674 return res; |
|
3675 } |
|
3676 |
|
3677 nsresult |
|
3678 nsHTMLEditRules::WillCSSIndent(Selection* aSelection, |
|
3679 bool* aCancel, bool* aHandled) |
|
3680 { |
|
3681 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } |
|
3682 |
|
3683 nsresult res = WillInsert(aSelection, aCancel); |
|
3684 NS_ENSURE_SUCCESS(res, res); |
|
3685 |
|
3686 // initialize out param |
|
3687 // we want to ignore result of WillInsert() |
|
3688 *aCancel = false; |
|
3689 *aHandled = true; |
|
3690 |
|
3691 res = NormalizeSelection(aSelection); |
|
3692 NS_ENSURE_SUCCESS(res, res); |
|
3693 NS_ENSURE_STATE(mHTMLEditor); |
|
3694 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); |
|
3695 nsCOMArray<nsIDOMRange> arrayOfRanges; |
|
3696 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
3697 |
|
3698 // short circuit: detect case of collapsed selection inside an <li>. |
|
3699 // just sublist that <li>. This prevents bug 97797. |
|
3700 |
|
3701 nsCOMPtr<nsIDOMNode> liNode; |
|
3702 if (aSelection->Collapsed()) { |
|
3703 nsCOMPtr<nsIDOMNode> node, block; |
|
3704 int32_t offset; |
|
3705 NS_ENSURE_STATE(mHTMLEditor); |
|
3706 nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset); |
|
3707 NS_ENSURE_SUCCESS(res, res); |
|
3708 if (IsBlockNode(node)) { |
|
3709 block = node; |
|
3710 } else { |
|
3711 NS_ENSURE_STATE(mHTMLEditor); |
|
3712 block = mHTMLEditor->GetBlockNodeParent(node); |
|
3713 } |
|
3714 if (block && nsHTMLEditUtils::IsListItem(block)) |
|
3715 liNode = block; |
|
3716 } |
|
3717 |
|
3718 if (liNode) |
|
3719 { |
|
3720 arrayOfNodes.AppendObject(liNode); |
|
3721 } |
|
3722 else |
|
3723 { |
|
3724 // convert the selection ranges into "promoted" selection ranges: |
|
3725 // this basically just expands the range to include the immediate |
|
3726 // block parent, and then further expands to include any ancestors |
|
3727 // whose children are all in the range |
|
3728 res = GetNodesFromSelection(aSelection, EditAction::indent, arrayOfNodes); |
|
3729 NS_ENSURE_SUCCESS(res, res); |
|
3730 } |
|
3731 |
|
3732 NS_NAMED_LITERAL_STRING(quoteType, "blockquote"); |
|
3733 // if nothing visible in list, make an empty block |
|
3734 if (ListIsEmptyLine(arrayOfNodes)) |
|
3735 { |
|
3736 nsCOMPtr<nsIDOMNode> parent, theBlock; |
|
3737 int32_t offset; |
|
3738 nsAutoString quoteType(NS_LITERAL_STRING("div")); |
|
3739 // get selection location |
|
3740 NS_ENSURE_STATE(mHTMLEditor); |
|
3741 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); |
|
3742 NS_ENSURE_SUCCESS(res, res); |
|
3743 // make sure we can put a block here |
|
3744 res = SplitAsNeeded("eType, address_of(parent), &offset); |
|
3745 NS_ENSURE_SUCCESS(res, res); |
|
3746 NS_ENSURE_STATE(mHTMLEditor); |
|
3747 res = mHTMLEditor->CreateNode(quoteType, parent, offset, getter_AddRefs(theBlock)); |
|
3748 NS_ENSURE_SUCCESS(res, res); |
|
3749 // remember our new block for postprocessing |
|
3750 mNewBlock = theBlock; |
|
3751 RelativeChangeIndentationOfElementNode(theBlock, +1); |
|
3752 // delete anything that was in the list of nodes |
|
3753 for (int32_t j = arrayOfNodes.Count() - 1; j >= 0; --j) |
|
3754 { |
|
3755 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[0]; |
|
3756 NS_ENSURE_STATE(mHTMLEditor); |
|
3757 res = mHTMLEditor->DeleteNode(curNode); |
|
3758 NS_ENSURE_SUCCESS(res, res); |
|
3759 arrayOfNodes.RemoveObjectAt(0); |
|
3760 } |
|
3761 // put selection in new block |
|
3762 res = aSelection->Collapse(theBlock,0); |
|
3763 selectionResetter.Abort(); // to prevent selection reseter from overriding us. |
|
3764 *aHandled = true; |
|
3765 return res; |
|
3766 } |
|
3767 |
|
3768 // Ok, now go through all the nodes and put them in a blockquote, |
|
3769 // or whatever is appropriate. Wohoo! |
|
3770 int32_t i; |
|
3771 nsCOMPtr<nsIDOMNode> curParent; |
|
3772 nsCOMPtr<nsIDOMNode> curQuote; |
|
3773 nsCOMPtr<nsIDOMNode> curList; |
|
3774 nsCOMPtr<nsIDOMNode> sibling; |
|
3775 int32_t listCount = arrayOfNodes.Count(); |
|
3776 for (i=0; i<listCount; i++) |
|
3777 { |
|
3778 // here's where we actually figure out what to do |
|
3779 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i]; |
|
3780 |
|
3781 // Ignore all non-editable nodes. Leave them be. |
|
3782 NS_ENSURE_STATE(mHTMLEditor); |
|
3783 if (!mHTMLEditor->IsEditable(curNode)) continue; |
|
3784 |
|
3785 int32_t offset; |
|
3786 curParent = nsEditor::GetNodeLocation(curNode, &offset); |
|
3787 |
|
3788 // some logic for putting list items into nested lists... |
|
3789 if (nsHTMLEditUtils::IsList(curParent)) |
|
3790 { |
|
3791 sibling = nullptr; |
|
3792 |
|
3793 // Check for whether we should join a list that follows curNode. |
|
3794 // We do this if the next element is a list, and the list is of the |
|
3795 // same type (li/ol) as curNode was a part it. |
|
3796 NS_ENSURE_STATE(mHTMLEditor); |
|
3797 mHTMLEditor->GetNextHTMLSibling(curNode, address_of(sibling)); |
|
3798 if (sibling && nsHTMLEditUtils::IsList(sibling)) |
|
3799 { |
|
3800 nsAutoString curListTag, siblingListTag; |
|
3801 nsEditor::GetTagString(curParent, curListTag); |
|
3802 nsEditor::GetTagString(sibling, siblingListTag); |
|
3803 if (curListTag == siblingListTag) |
|
3804 { |
|
3805 NS_ENSURE_STATE(mHTMLEditor); |
|
3806 res = mHTMLEditor->MoveNode(curNode, sibling, 0); |
|
3807 NS_ENSURE_SUCCESS(res, res); |
|
3808 continue; |
|
3809 } |
|
3810 } |
|
3811 // Check for whether we should join a list that preceeds curNode. |
|
3812 // We do this if the previous element is a list, and the list is of |
|
3813 // the same type (li/ol) as curNode was a part of. |
|
3814 NS_ENSURE_STATE(mHTMLEditor); |
|
3815 mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); |
|
3816 if (sibling && nsHTMLEditUtils::IsList(sibling)) |
|
3817 { |
|
3818 nsAutoString curListTag, siblingListTag; |
|
3819 nsEditor::GetTagString(curParent, curListTag); |
|
3820 nsEditor::GetTagString(sibling, siblingListTag); |
|
3821 if (curListTag == siblingListTag) |
|
3822 { |
|
3823 NS_ENSURE_STATE(mHTMLEditor); |
|
3824 res = mHTMLEditor->MoveNode(curNode, sibling, -1); |
|
3825 NS_ENSURE_SUCCESS(res, res); |
|
3826 continue; |
|
3827 } |
|
3828 } |
|
3829 sibling = nullptr; |
|
3830 |
|
3831 // check to see if curList is still appropriate. Which it is if |
|
3832 // curNode is still right after it in the same list. |
|
3833 if (curList) { |
|
3834 NS_ENSURE_STATE(mHTMLEditor); |
|
3835 mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); |
|
3836 } |
|
3837 |
|
3838 if (!curList || (sibling && sibling != curList)) |
|
3839 { |
|
3840 nsAutoString listTag; |
|
3841 nsEditor::GetTagString(curParent,listTag); |
|
3842 ToLowerCase(listTag); |
|
3843 // create a new nested list of correct type |
|
3844 res = SplitAsNeeded(&listTag, address_of(curParent), &offset); |
|
3845 NS_ENSURE_SUCCESS(res, res); |
|
3846 NS_ENSURE_STATE(mHTMLEditor); |
|
3847 res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList)); |
|
3848 NS_ENSURE_SUCCESS(res, res); |
|
3849 // curList is now the correct thing to put curNode in |
|
3850 // remember our new block for postprocessing |
|
3851 mNewBlock = curList; |
|
3852 } |
|
3853 // tuck the node into the end of the active list |
|
3854 uint32_t listLen; |
|
3855 NS_ENSURE_STATE(mHTMLEditor); |
|
3856 res = mHTMLEditor->GetLengthOfDOMNode(curList, listLen); |
|
3857 NS_ENSURE_SUCCESS(res, res); |
|
3858 NS_ENSURE_STATE(mHTMLEditor); |
|
3859 res = mHTMLEditor->MoveNode(curNode, curList, listLen); |
|
3860 NS_ENSURE_SUCCESS(res, res); |
|
3861 } |
|
3862 |
|
3863 else // not a list item |
|
3864 { |
|
3865 if (IsBlockNode(curNode)) { |
|
3866 RelativeChangeIndentationOfElementNode(curNode, +1); |
|
3867 curQuote = nullptr; |
|
3868 } |
|
3869 else { |
|
3870 if (!curQuote) |
|
3871 { |
|
3872 // First, check that our element can contain a div. |
|
3873 if (!mEditor->CanContainTag(curParent, nsGkAtoms::div)) { |
|
3874 return NS_OK; // cancelled |
|
3875 } |
|
3876 |
|
3877 NS_NAMED_LITERAL_STRING(divquoteType, "div"); |
|
3878 res = SplitAsNeeded(&divquoteType, address_of(curParent), &offset); |
|
3879 NS_ENSURE_SUCCESS(res, res); |
|
3880 NS_ENSURE_STATE(mHTMLEditor); |
|
3881 res = mHTMLEditor->CreateNode(divquoteType, curParent, offset, getter_AddRefs(curQuote)); |
|
3882 NS_ENSURE_SUCCESS(res, res); |
|
3883 RelativeChangeIndentationOfElementNode(curQuote, +1); |
|
3884 // remember our new block for postprocessing |
|
3885 mNewBlock = curQuote; |
|
3886 // curQuote is now the correct thing to put curNode in |
|
3887 } |
|
3888 |
|
3889 // tuck the node into the end of the active blockquote |
|
3890 uint32_t quoteLen; |
|
3891 NS_ENSURE_STATE(mHTMLEditor); |
|
3892 res = mHTMLEditor->GetLengthOfDOMNode(curQuote, quoteLen); |
|
3893 NS_ENSURE_SUCCESS(res, res); |
|
3894 NS_ENSURE_STATE(mHTMLEditor); |
|
3895 res = mHTMLEditor->MoveNode(curNode, curQuote, quoteLen); |
|
3896 NS_ENSURE_SUCCESS(res, res); |
|
3897 } |
|
3898 } |
|
3899 } |
|
3900 return res; |
|
3901 } |
|
3902 |
|
3903 nsresult |
|
3904 nsHTMLEditRules::WillHTMLIndent(Selection* aSelection, |
|
3905 bool* aCancel, bool* aHandled) |
|
3906 { |
|
3907 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } |
|
3908 nsresult res = WillInsert(aSelection, aCancel); |
|
3909 NS_ENSURE_SUCCESS(res, res); |
|
3910 |
|
3911 // initialize out param |
|
3912 // we want to ignore result of WillInsert() |
|
3913 *aCancel = false; |
|
3914 *aHandled = true; |
|
3915 |
|
3916 res = NormalizeSelection(aSelection); |
|
3917 NS_ENSURE_SUCCESS(res, res); |
|
3918 NS_ENSURE_STATE(mHTMLEditor); |
|
3919 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); |
|
3920 |
|
3921 // convert the selection ranges into "promoted" selection ranges: |
|
3922 // this basically just expands the range to include the immediate |
|
3923 // block parent, and then further expands to include any ancestors |
|
3924 // whose children are all in the range |
|
3925 |
|
3926 nsCOMArray<nsIDOMRange> arrayOfRanges; |
|
3927 res = GetPromotedRanges(aSelection, arrayOfRanges, EditAction::indent); |
|
3928 NS_ENSURE_SUCCESS(res, res); |
|
3929 |
|
3930 // use these ranges to contruct a list of nodes to act on. |
|
3931 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
3932 res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, EditAction::indent); |
|
3933 NS_ENSURE_SUCCESS(res, res); |
|
3934 |
|
3935 NS_NAMED_LITERAL_STRING(quoteType, "blockquote"); |
|
3936 |
|
3937 // if nothing visible in list, make an empty block |
|
3938 if (ListIsEmptyLine(arrayOfNodes)) |
|
3939 { |
|
3940 nsCOMPtr<nsIDOMNode> parent, theBlock; |
|
3941 int32_t offset; |
|
3942 |
|
3943 // get selection location |
|
3944 NS_ENSURE_STATE(mHTMLEditor); |
|
3945 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); |
|
3946 NS_ENSURE_SUCCESS(res, res); |
|
3947 // make sure we can put a block here |
|
3948 res = SplitAsNeeded("eType, address_of(parent), &offset); |
|
3949 NS_ENSURE_SUCCESS(res, res); |
|
3950 NS_ENSURE_STATE(mHTMLEditor); |
|
3951 res = mHTMLEditor->CreateNode(quoteType, parent, offset, getter_AddRefs(theBlock)); |
|
3952 NS_ENSURE_SUCCESS(res, res); |
|
3953 // remember our new block for postprocessing |
|
3954 mNewBlock = theBlock; |
|
3955 // delete anything that was in the list of nodes |
|
3956 for (int32_t j = arrayOfNodes.Count() - 1; j >= 0; --j) |
|
3957 { |
|
3958 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[0]; |
|
3959 NS_ENSURE_STATE(mHTMLEditor); |
|
3960 res = mHTMLEditor->DeleteNode(curNode); |
|
3961 NS_ENSURE_SUCCESS(res, res); |
|
3962 arrayOfNodes.RemoveObjectAt(0); |
|
3963 } |
|
3964 // put selection in new block |
|
3965 res = aSelection->Collapse(theBlock,0); |
|
3966 selectionResetter.Abort(); // to prevent selection reseter from overriding us. |
|
3967 *aHandled = true; |
|
3968 return res; |
|
3969 } |
|
3970 |
|
3971 // Ok, now go through all the nodes and put them in a blockquote, |
|
3972 // or whatever is appropriate. Wohoo! |
|
3973 int32_t i; |
|
3974 nsCOMPtr<nsIDOMNode> curParent, curQuote, curList, indentedLI, sibling; |
|
3975 int32_t listCount = arrayOfNodes.Count(); |
|
3976 for (i=0; i<listCount; i++) |
|
3977 { |
|
3978 // here's where we actually figure out what to do |
|
3979 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i]; |
|
3980 |
|
3981 // Ignore all non-editable nodes. Leave them be. |
|
3982 NS_ENSURE_STATE(mHTMLEditor); |
|
3983 if (!mHTMLEditor->IsEditable(curNode)) continue; |
|
3984 |
|
3985 int32_t offset; |
|
3986 curParent = nsEditor::GetNodeLocation(curNode, &offset); |
|
3987 |
|
3988 // some logic for putting list items into nested lists... |
|
3989 if (nsHTMLEditUtils::IsList(curParent)) |
|
3990 { |
|
3991 sibling = nullptr; |
|
3992 |
|
3993 // Check for whether we should join a list that follows curNode. |
|
3994 // We do this if the next element is a list, and the list is of the |
|
3995 // same type (li/ol) as curNode was a part it. |
|
3996 NS_ENSURE_STATE(mHTMLEditor); |
|
3997 mHTMLEditor->GetNextHTMLSibling(curNode, address_of(sibling)); |
|
3998 if (sibling && nsHTMLEditUtils::IsList(sibling)) |
|
3999 { |
|
4000 nsAutoString curListTag, siblingListTag; |
|
4001 nsEditor::GetTagString(curParent, curListTag); |
|
4002 nsEditor::GetTagString(sibling, siblingListTag); |
|
4003 if (curListTag == siblingListTag) |
|
4004 { |
|
4005 NS_ENSURE_STATE(mHTMLEditor); |
|
4006 res = mHTMLEditor->MoveNode(curNode, sibling, 0); |
|
4007 NS_ENSURE_SUCCESS(res, res); |
|
4008 continue; |
|
4009 } |
|
4010 } |
|
4011 |
|
4012 // Check for whether we should join a list that preceeds curNode. |
|
4013 // We do this if the previous element is a list, and the list is of |
|
4014 // the same type (li/ol) as curNode was a part of. |
|
4015 NS_ENSURE_STATE(mHTMLEditor); |
|
4016 mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); |
|
4017 if (sibling && nsHTMLEditUtils::IsList(sibling)) |
|
4018 { |
|
4019 nsAutoString curListTag, siblingListTag; |
|
4020 nsEditor::GetTagString(curParent, curListTag); |
|
4021 nsEditor::GetTagString(sibling, siblingListTag); |
|
4022 if (curListTag == siblingListTag) |
|
4023 { |
|
4024 NS_ENSURE_STATE(mHTMLEditor); |
|
4025 res = mHTMLEditor->MoveNode(curNode, sibling, -1); |
|
4026 NS_ENSURE_SUCCESS(res, res); |
|
4027 continue; |
|
4028 } |
|
4029 } |
|
4030 |
|
4031 sibling = nullptr; |
|
4032 |
|
4033 // check to see if curList is still appropriate. Which it is if |
|
4034 // curNode is still right after it in the same list. |
|
4035 if (curList) { |
|
4036 NS_ENSURE_STATE(mHTMLEditor); |
|
4037 mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); |
|
4038 } |
|
4039 |
|
4040 if (!curList || (sibling && sibling != curList) ) |
|
4041 { |
|
4042 nsAutoString listTag; |
|
4043 nsEditor::GetTagString(curParent,listTag); |
|
4044 ToLowerCase(listTag); |
|
4045 // create a new nested list of correct type |
|
4046 res = SplitAsNeeded(&listTag, address_of(curParent), &offset); |
|
4047 NS_ENSURE_SUCCESS(res, res); |
|
4048 NS_ENSURE_STATE(mHTMLEditor); |
|
4049 res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList)); |
|
4050 NS_ENSURE_SUCCESS(res, res); |
|
4051 // curList is now the correct thing to put curNode in |
|
4052 // remember our new block for postprocessing |
|
4053 mNewBlock = curList; |
|
4054 } |
|
4055 // tuck the node into the end of the active list |
|
4056 NS_ENSURE_STATE(mHTMLEditor); |
|
4057 res = mHTMLEditor->MoveNode(curNode, curList, -1); |
|
4058 NS_ENSURE_SUCCESS(res, res); |
|
4059 // forget curQuote, if any |
|
4060 curQuote = nullptr; |
|
4061 } |
|
4062 |
|
4063 else // not a list item, use blockquote? |
|
4064 { |
|
4065 // if we are inside a list item, we don't want to blockquote, we want |
|
4066 // to sublist the list item. We may have several nodes listed in the |
|
4067 // array of nodes to act on, that are in the same list item. Since |
|
4068 // we only want to indent that li once, we must keep track of the most |
|
4069 // recent indented list item, and not indent it if we find another node |
|
4070 // to act on that is still inside the same li. |
|
4071 nsCOMPtr<nsIDOMNode> listitem=IsInListItem(curNode); |
|
4072 if (listitem) |
|
4073 { |
|
4074 if (indentedLI == listitem) continue; // already indented this list item |
|
4075 curParent = nsEditor::GetNodeLocation(listitem, &offset); |
|
4076 // check to see if curList is still appropriate. Which it is if |
|
4077 // curNode is still right after it in the same list. |
|
4078 if (curList) |
|
4079 { |
|
4080 sibling = nullptr; |
|
4081 NS_ENSURE_STATE(mHTMLEditor); |
|
4082 mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); |
|
4083 } |
|
4084 |
|
4085 if (!curList || (sibling && sibling != curList) ) |
|
4086 { |
|
4087 nsAutoString listTag; |
|
4088 nsEditor::GetTagString(curParent,listTag); |
|
4089 ToLowerCase(listTag); |
|
4090 // create a new nested list of correct type |
|
4091 res = SplitAsNeeded(&listTag, address_of(curParent), &offset); |
|
4092 NS_ENSURE_SUCCESS(res, res); |
|
4093 NS_ENSURE_STATE(mHTMLEditor); |
|
4094 res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList)); |
|
4095 NS_ENSURE_SUCCESS(res, res); |
|
4096 } |
|
4097 NS_ENSURE_STATE(mHTMLEditor); |
|
4098 res = mHTMLEditor->MoveNode(listitem, curList, -1); |
|
4099 NS_ENSURE_SUCCESS(res, res); |
|
4100 // remember we indented this li |
|
4101 indentedLI = listitem; |
|
4102 } |
|
4103 |
|
4104 else |
|
4105 { |
|
4106 // need to make a blockquote to put things in if we haven't already, |
|
4107 // or if this node doesn't go in blockquote we used earlier. |
|
4108 // One reason it might not go in prio blockquote is if we are now |
|
4109 // in a different table cell. |
|
4110 if (curQuote && InDifferentTableElements(curQuote, curNode)) { |
|
4111 curQuote = nullptr; |
|
4112 } |
|
4113 |
|
4114 if (!curQuote) |
|
4115 { |
|
4116 // First, check that our element can contain a blockquote. |
|
4117 if (!mEditor->CanContainTag(curParent, nsGkAtoms::blockquote)) { |
|
4118 return NS_OK; // cancelled |
|
4119 } |
|
4120 |
|
4121 res = SplitAsNeeded("eType, address_of(curParent), &offset); |
|
4122 NS_ENSURE_SUCCESS(res, res); |
|
4123 NS_ENSURE_STATE(mHTMLEditor); |
|
4124 res = mHTMLEditor->CreateNode(quoteType, curParent, offset, getter_AddRefs(curQuote)); |
|
4125 NS_ENSURE_SUCCESS(res, res); |
|
4126 // remember our new block for postprocessing |
|
4127 mNewBlock = curQuote; |
|
4128 // curQuote is now the correct thing to put curNode in |
|
4129 } |
|
4130 |
|
4131 // tuck the node into the end of the active blockquote |
|
4132 NS_ENSURE_STATE(mHTMLEditor); |
|
4133 res = mHTMLEditor->MoveNode(curNode, curQuote, -1); |
|
4134 NS_ENSURE_SUCCESS(res, res); |
|
4135 // forget curList, if any |
|
4136 curList = nullptr; |
|
4137 } |
|
4138 } |
|
4139 } |
|
4140 return res; |
|
4141 } |
|
4142 |
|
4143 |
|
4144 nsresult |
|
4145 nsHTMLEditRules::WillOutdent(Selection* aSelection, |
|
4146 bool* aCancel, bool* aHandled) |
|
4147 { |
|
4148 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } |
|
4149 // initialize out param |
|
4150 *aCancel = false; |
|
4151 *aHandled = true; |
|
4152 nsresult res = NS_OK; |
|
4153 nsCOMPtr<nsIDOMNode> rememberedLeftBQ, rememberedRightBQ; |
|
4154 NS_ENSURE_STATE(mHTMLEditor); |
|
4155 bool useCSS = mHTMLEditor->IsCSSEnabled(); |
|
4156 |
|
4157 res = NormalizeSelection(aSelection); |
|
4158 NS_ENSURE_SUCCESS(res, res); |
|
4159 // some scoping for selection resetting - we may need to tweak it |
|
4160 { |
|
4161 NS_ENSURE_STATE(mHTMLEditor); |
|
4162 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); |
|
4163 |
|
4164 // convert the selection ranges into "promoted" selection ranges: |
|
4165 // this basically just expands the range to include the immediate |
|
4166 // block parent, and then further expands to include any ancestors |
|
4167 // whose children are all in the range |
|
4168 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
4169 res = GetNodesFromSelection(aSelection, EditAction::outdent, |
|
4170 arrayOfNodes); |
|
4171 NS_ENSURE_SUCCESS(res, res); |
|
4172 |
|
4173 // Ok, now go through all the nodes and remove a level of blockquoting, |
|
4174 // or whatever is appropriate. Wohoo! |
|
4175 |
|
4176 nsCOMPtr<nsIDOMNode> curBlockQuote, firstBQChild, lastBQChild; |
|
4177 bool curBlockQuoteIsIndentedWithCSS = false; |
|
4178 int32_t listCount = arrayOfNodes.Count(); |
|
4179 int32_t i; |
|
4180 nsCOMPtr<nsIDOMNode> curParent; |
|
4181 for (i=0; i<listCount; i++) |
|
4182 { |
|
4183 // here's where we actually figure out what to do |
|
4184 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i]; |
|
4185 int32_t offset; |
|
4186 curParent = nsEditor::GetNodeLocation(curNode, &offset); |
|
4187 |
|
4188 // is it a blockquote? |
|
4189 if (nsHTMLEditUtils::IsBlockquote(curNode)) |
|
4190 { |
|
4191 // if it is a blockquote, remove it. |
|
4192 // So we need to finish up dealng with any curBlockQuote first. |
|
4193 if (curBlockQuote) |
|
4194 { |
|
4195 res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild, |
|
4196 curBlockQuoteIsIndentedWithCSS, |
|
4197 address_of(rememberedLeftBQ), |
|
4198 address_of(rememberedRightBQ)); |
|
4199 NS_ENSURE_SUCCESS(res, res); |
|
4200 curBlockQuote = 0; firstBQChild = 0; lastBQChild = 0; |
|
4201 curBlockQuoteIsIndentedWithCSS = false; |
|
4202 } |
|
4203 NS_ENSURE_STATE(mHTMLEditor); |
|
4204 res = mHTMLEditor->RemoveBlockContainer(curNode); |
|
4205 NS_ENSURE_SUCCESS(res, res); |
|
4206 continue; |
|
4207 } |
|
4208 // is it a block with a 'margin' property? |
|
4209 if (useCSS && IsBlockNode(curNode)) |
|
4210 { |
|
4211 NS_ENSURE_STATE(mHTMLEditor); |
|
4212 nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, curNode); |
|
4213 nsAutoString value; |
|
4214 NS_ENSURE_STATE(mHTMLEditor); |
|
4215 mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(curNode, marginProperty, value); |
|
4216 float f; |
|
4217 nsCOMPtr<nsIAtom> unit; |
|
4218 NS_ENSURE_STATE(mHTMLEditor); |
|
4219 mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit)); |
|
4220 if (f > 0) |
|
4221 { |
|
4222 RelativeChangeIndentationOfElementNode(curNode, -1); |
|
4223 continue; |
|
4224 } |
|
4225 } |
|
4226 // is it a list item? |
|
4227 if (nsHTMLEditUtils::IsListItem(curNode)) |
|
4228 { |
|
4229 // if it is a list item, that means we are not outdenting whole list. |
|
4230 // So we need to finish up dealing with any curBlockQuote, and then |
|
4231 // pop this list item. |
|
4232 if (curBlockQuote) |
|
4233 { |
|
4234 res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild, |
|
4235 curBlockQuoteIsIndentedWithCSS, |
|
4236 address_of(rememberedLeftBQ), |
|
4237 address_of(rememberedRightBQ)); |
|
4238 NS_ENSURE_SUCCESS(res, res); |
|
4239 curBlockQuote = 0; firstBQChild = 0; lastBQChild = 0; |
|
4240 curBlockQuoteIsIndentedWithCSS = false; |
|
4241 } |
|
4242 bool bOutOfList; |
|
4243 res = PopListItem(curNode, &bOutOfList); |
|
4244 NS_ENSURE_SUCCESS(res, res); |
|
4245 continue; |
|
4246 } |
|
4247 // do we have a blockquote that we are already committed to removing? |
|
4248 if (curBlockQuote) |
|
4249 { |
|
4250 // if so, is this node a descendant? |
|
4251 if (nsEditorUtils::IsDescendantOf(curNode, curBlockQuote)) |
|
4252 { |
|
4253 lastBQChild = curNode; |
|
4254 continue; // then we don't need to do anything different for this node |
|
4255 } |
|
4256 else |
|
4257 { |
|
4258 // otherwise, we have progressed beyond end of curBlockQuote, |
|
4259 // so lets handle it now. We need to remove the portion of |
|
4260 // curBlockQuote that contains [firstBQChild - lastBQChild]. |
|
4261 res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild, |
|
4262 curBlockQuoteIsIndentedWithCSS, |
|
4263 address_of(rememberedLeftBQ), |
|
4264 address_of(rememberedRightBQ)); |
|
4265 NS_ENSURE_SUCCESS(res, res); |
|
4266 curBlockQuote = 0; firstBQChild = 0; lastBQChild = 0; |
|
4267 curBlockQuoteIsIndentedWithCSS = false; |
|
4268 // fall out and handle curNode |
|
4269 } |
|
4270 } |
|
4271 |
|
4272 // are we inside a blockquote? |
|
4273 nsCOMPtr<nsIDOMNode> n = curNode; |
|
4274 nsCOMPtr<nsIDOMNode> tmp; |
|
4275 curBlockQuoteIsIndentedWithCSS = false; |
|
4276 // keep looking up the hierarchy as long as we don't hit the body or the |
|
4277 // active editing host or a table element (other than an entire table) |
|
4278 while (!nsTextEditUtils::IsBody(n) && mHTMLEditor && |
|
4279 mHTMLEditor->IsDescendantOfEditorRoot(n) |
|
4280 && (nsHTMLEditUtils::IsTable(n) || !nsHTMLEditUtils::IsTableElement(n))) |
|
4281 { |
|
4282 n->GetParentNode(getter_AddRefs(tmp)); |
|
4283 if (!tmp) { |
|
4284 break; |
|
4285 } |
|
4286 n = tmp; |
|
4287 if (nsHTMLEditUtils::IsBlockquote(n)) |
|
4288 { |
|
4289 // if so, remember it, and remember first node we are taking out of it. |
|
4290 curBlockQuote = n; |
|
4291 firstBQChild = curNode; |
|
4292 lastBQChild = curNode; |
|
4293 break; |
|
4294 } |
|
4295 else if (useCSS) |
|
4296 { |
|
4297 NS_ENSURE_STATE(mHTMLEditor); |
|
4298 nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, curNode); |
|
4299 nsAutoString value; |
|
4300 NS_ENSURE_STATE(mHTMLEditor); |
|
4301 mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(n, marginProperty, value); |
|
4302 float f; |
|
4303 nsCOMPtr<nsIAtom> unit; |
|
4304 NS_ENSURE_STATE(mHTMLEditor); |
|
4305 mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit)); |
|
4306 if (f > 0 && !(nsHTMLEditUtils::IsList(curParent) && nsHTMLEditUtils::IsList(curNode))) |
|
4307 { |
|
4308 curBlockQuote = n; |
|
4309 firstBQChild = curNode; |
|
4310 lastBQChild = curNode; |
|
4311 curBlockQuoteIsIndentedWithCSS = true; |
|
4312 break; |
|
4313 } |
|
4314 } |
|
4315 } |
|
4316 |
|
4317 if (!curBlockQuote) |
|
4318 { |
|
4319 // could not find an enclosing blockquote for this node. handle list cases. |
|
4320 if (nsHTMLEditUtils::IsList(curParent)) // move node out of list |
|
4321 { |
|
4322 if (nsHTMLEditUtils::IsList(curNode)) // just unwrap this sublist |
|
4323 { |
|
4324 NS_ENSURE_STATE(mHTMLEditor); |
|
4325 res = mHTMLEditor->RemoveBlockContainer(curNode); |
|
4326 NS_ENSURE_SUCCESS(res, res); |
|
4327 } |
|
4328 // handled list item case above |
|
4329 } |
|
4330 else if (nsHTMLEditUtils::IsList(curNode)) // node is a list, but parent is non-list: move list items out |
|
4331 { |
|
4332 nsCOMPtr<nsIDOMNode> child; |
|
4333 curNode->GetLastChild(getter_AddRefs(child)); |
|
4334 while (child) |
|
4335 { |
|
4336 if (nsHTMLEditUtils::IsListItem(child)) |
|
4337 { |
|
4338 bool bOutOfList; |
|
4339 res = PopListItem(child, &bOutOfList); |
|
4340 NS_ENSURE_SUCCESS(res, res); |
|
4341 } |
|
4342 else if (nsHTMLEditUtils::IsList(child)) |
|
4343 { |
|
4344 // We have an embedded list, so move it out from under the |
|
4345 // parent list. Be sure to put it after the parent list |
|
4346 // because this loop iterates backwards through the parent's |
|
4347 // list of children. |
|
4348 |
|
4349 NS_ENSURE_STATE(mHTMLEditor); |
|
4350 res = mHTMLEditor->MoveNode(child, curParent, offset + 1); |
|
4351 NS_ENSURE_SUCCESS(res, res); |
|
4352 } |
|
4353 else |
|
4354 { |
|
4355 // delete any non- list items for now |
|
4356 NS_ENSURE_STATE(mHTMLEditor); |
|
4357 res = mHTMLEditor->DeleteNode(child); |
|
4358 NS_ENSURE_SUCCESS(res, res); |
|
4359 } |
|
4360 curNode->GetLastChild(getter_AddRefs(child)); |
|
4361 } |
|
4362 // delete the now-empty list |
|
4363 NS_ENSURE_STATE(mHTMLEditor); |
|
4364 res = mHTMLEditor->RemoveBlockContainer(curNode); |
|
4365 NS_ENSURE_SUCCESS(res, res); |
|
4366 } |
|
4367 else if (useCSS) { |
|
4368 nsCOMPtr<nsIDOMElement> element; |
|
4369 nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(curNode); |
|
4370 if (textNode) { |
|
4371 // We want to outdent the parent of text nodes |
|
4372 nsCOMPtr<nsIDOMNode> parent; |
|
4373 textNode->GetParentNode(getter_AddRefs(parent)); |
|
4374 element = do_QueryInterface(parent); |
|
4375 } else { |
|
4376 element = do_QueryInterface(curNode); |
|
4377 } |
|
4378 if (element) { |
|
4379 RelativeChangeIndentationOfElementNode(element, -1); |
|
4380 } |
|
4381 } |
|
4382 } |
|
4383 } |
|
4384 if (curBlockQuote) |
|
4385 { |
|
4386 // we have a blockquote we haven't finished handling |
|
4387 res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild, |
|
4388 curBlockQuoteIsIndentedWithCSS, |
|
4389 address_of(rememberedLeftBQ), |
|
4390 address_of(rememberedRightBQ)); |
|
4391 NS_ENSURE_SUCCESS(res, res); |
|
4392 } |
|
4393 } |
|
4394 // make sure selection didn't stick to last piece of content in old bq |
|
4395 // (only a problem for collapsed selections) |
|
4396 if (rememberedLeftBQ || rememberedRightBQ) { |
|
4397 if (aSelection->Collapsed()) { |
|
4398 // push selection past end of rememberedLeftBQ |
|
4399 nsCOMPtr<nsIDOMNode> sNode; |
|
4400 int32_t sOffset; |
|
4401 NS_ENSURE_STATE(mHTMLEditor); |
|
4402 mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(sNode), &sOffset); |
|
4403 if (rememberedLeftBQ && |
|
4404 ((sNode == rememberedLeftBQ) || nsEditorUtils::IsDescendantOf(sNode, rememberedLeftBQ))) |
|
4405 { |
|
4406 // selection is inside rememberedLeftBQ - push it past it. |
|
4407 sNode = nsEditor::GetNodeLocation(rememberedLeftBQ, &sOffset); |
|
4408 sOffset++; |
|
4409 aSelection->Collapse(sNode, sOffset); |
|
4410 } |
|
4411 // and pull selection before beginning of rememberedRightBQ |
|
4412 NS_ENSURE_STATE(mHTMLEditor); |
|
4413 mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(sNode), &sOffset); |
|
4414 if (rememberedRightBQ && |
|
4415 ((sNode == rememberedRightBQ) || nsEditorUtils::IsDescendantOf(sNode, rememberedRightBQ))) |
|
4416 { |
|
4417 // selection is inside rememberedRightBQ - push it before it. |
|
4418 sNode = nsEditor::GetNodeLocation(rememberedRightBQ, &sOffset); |
|
4419 aSelection->Collapse(sNode, sOffset); |
|
4420 } |
|
4421 } |
|
4422 return NS_OK; |
|
4423 } |
|
4424 return res; |
|
4425 } |
|
4426 |
|
4427 |
|
4428 /////////////////////////////////////////////////////////////////////////// |
|
4429 // RemovePartOfBlock: split aBlock and move aStartChild to aEndChild out |
|
4430 // of aBlock. return left side of block (if any) in |
|
4431 // aLeftNode. return right side of block (if any) in |
|
4432 // aRightNode. |
|
4433 // |
|
4434 nsresult |
|
4435 nsHTMLEditRules::RemovePartOfBlock(nsIDOMNode *aBlock, |
|
4436 nsIDOMNode *aStartChild, |
|
4437 nsIDOMNode *aEndChild, |
|
4438 nsCOMPtr<nsIDOMNode> *aLeftNode, |
|
4439 nsCOMPtr<nsIDOMNode> *aRightNode) |
|
4440 { |
|
4441 nsCOMPtr<nsIDOMNode> middleNode; |
|
4442 nsresult res = SplitBlock(aBlock, aStartChild, aEndChild, |
|
4443 aLeftNode, aRightNode, |
|
4444 address_of(middleNode)); |
|
4445 NS_ENSURE_SUCCESS(res, res); |
|
4446 // get rid of part of blockquote we are outdenting |
|
4447 |
|
4448 NS_ENSURE_STATE(mHTMLEditor); |
|
4449 return mHTMLEditor->RemoveBlockContainer(aBlock); |
|
4450 } |
|
4451 |
|
4452 nsresult |
|
4453 nsHTMLEditRules::SplitBlock(nsIDOMNode *aBlock, |
|
4454 nsIDOMNode *aStartChild, |
|
4455 nsIDOMNode *aEndChild, |
|
4456 nsCOMPtr<nsIDOMNode> *aLeftNode, |
|
4457 nsCOMPtr<nsIDOMNode> *aRightNode, |
|
4458 nsCOMPtr<nsIDOMNode> *aMiddleNode) |
|
4459 { |
|
4460 NS_ENSURE_TRUE(aBlock && aStartChild && aEndChild, NS_ERROR_NULL_POINTER); |
|
4461 |
|
4462 nsCOMPtr<nsIDOMNode> leftNode, rightNode; |
|
4463 int32_t startOffset, endOffset, offset; |
|
4464 nsresult res; |
|
4465 |
|
4466 // get split point location |
|
4467 nsCOMPtr<nsIDOMNode> startParent = nsEditor::GetNodeLocation(aStartChild, &startOffset); |
|
4468 |
|
4469 // do the splits! |
|
4470 NS_ENSURE_STATE(mHTMLEditor); |
|
4471 res = mHTMLEditor->SplitNodeDeep(aBlock, startParent, startOffset, &offset, |
|
4472 true, address_of(leftNode), address_of(rightNode)); |
|
4473 NS_ENSURE_SUCCESS(res, res); |
|
4474 if (rightNode) aBlock = rightNode; |
|
4475 |
|
4476 // remember left portion of block if caller requested |
|
4477 if (aLeftNode) |
|
4478 *aLeftNode = leftNode; |
|
4479 |
|
4480 // get split point location |
|
4481 nsCOMPtr<nsIDOMNode> endParent = nsEditor::GetNodeLocation(aEndChild, &endOffset); |
|
4482 endOffset++; // want to be after lastBQChild |
|
4483 |
|
4484 // do the splits! |
|
4485 NS_ENSURE_STATE(mHTMLEditor); |
|
4486 res = mHTMLEditor->SplitNodeDeep(aBlock, endParent, endOffset, &offset, |
|
4487 true, address_of(leftNode), address_of(rightNode)); |
|
4488 NS_ENSURE_SUCCESS(res, res); |
|
4489 if (leftNode) aBlock = leftNode; |
|
4490 |
|
4491 // remember right portion of block if caller requested |
|
4492 if (aRightNode) |
|
4493 *aRightNode = rightNode; |
|
4494 |
|
4495 if (aMiddleNode) |
|
4496 *aMiddleNode = aBlock; |
|
4497 |
|
4498 return NS_OK; |
|
4499 } |
|
4500 |
|
4501 nsresult |
|
4502 nsHTMLEditRules::OutdentPartOfBlock(nsIDOMNode *aBlock, |
|
4503 nsIDOMNode *aStartChild, |
|
4504 nsIDOMNode *aEndChild, |
|
4505 bool aIsBlockIndentedWithCSS, |
|
4506 nsCOMPtr<nsIDOMNode> *aLeftNode, |
|
4507 nsCOMPtr<nsIDOMNode> *aRightNode) |
|
4508 { |
|
4509 nsCOMPtr<nsIDOMNode> middleNode; |
|
4510 nsresult res = SplitBlock(aBlock, aStartChild, aEndChild, |
|
4511 aLeftNode, |
|
4512 aRightNode, |
|
4513 address_of(middleNode)); |
|
4514 NS_ENSURE_SUCCESS(res, res); |
|
4515 if (aIsBlockIndentedWithCSS) { |
|
4516 res = RelativeChangeIndentationOfElementNode(middleNode, -1); |
|
4517 } else { |
|
4518 NS_ENSURE_STATE(mHTMLEditor); |
|
4519 res = mHTMLEditor->RemoveBlockContainer(middleNode); |
|
4520 } |
|
4521 return res; |
|
4522 } |
|
4523 |
|
4524 /////////////////////////////////////////////////////////////////////////// |
|
4525 // ConvertListType: convert list type and list item type. |
|
4526 // |
|
4527 // |
|
4528 nsresult |
|
4529 nsHTMLEditRules::ConvertListType(nsIDOMNode* aList, |
|
4530 nsCOMPtr<nsIDOMNode>* outList, |
|
4531 nsIAtom* aListType, |
|
4532 nsIAtom* aItemType) |
|
4533 { |
|
4534 MOZ_ASSERT(aListType); |
|
4535 MOZ_ASSERT(aItemType); |
|
4536 |
|
4537 NS_ENSURE_TRUE(aList && outList, NS_ERROR_NULL_POINTER); |
|
4538 nsCOMPtr<nsINode> list = do_QueryInterface(aList); |
|
4539 NS_ENSURE_STATE(list); |
|
4540 |
|
4541 nsCOMPtr<dom::Element> outNode; |
|
4542 nsresult rv = ConvertListType(list, getter_AddRefs(outNode), aListType, aItemType); |
|
4543 *outList = outNode ? outNode->AsDOMNode() : nullptr; |
|
4544 return rv; |
|
4545 } |
|
4546 |
|
4547 nsresult |
|
4548 nsHTMLEditRules::ConvertListType(nsINode* aList, |
|
4549 dom::Element** aOutList, |
|
4550 nsIAtom* aListType, |
|
4551 nsIAtom* aItemType) |
|
4552 { |
|
4553 MOZ_ASSERT(aList); |
|
4554 MOZ_ASSERT(aOutList); |
|
4555 MOZ_ASSERT(aListType); |
|
4556 MOZ_ASSERT(aItemType); |
|
4557 |
|
4558 nsCOMPtr<nsINode> child = aList->GetFirstChild(); |
|
4559 while (child) |
|
4560 { |
|
4561 if (child->IsElement()) { |
|
4562 dom::Element* element = child->AsElement(); |
|
4563 if (nsHTMLEditUtils::IsListItem(element) && !element->IsHTML(aItemType)) { |
|
4564 nsCOMPtr<dom::Element> temp; |
|
4565 nsresult rv = |
|
4566 mHTMLEditor->ReplaceContainer(child, getter_AddRefs(temp), |
|
4567 nsDependentAtomString(aItemType)); |
|
4568 NS_ENSURE_SUCCESS(rv, rv); |
|
4569 child = temp.forget(); |
|
4570 } else if (nsHTMLEditUtils::IsList(element) && |
|
4571 !element->IsHTML(aListType)) { |
|
4572 nsCOMPtr<dom::Element> temp; |
|
4573 nsresult rv = |
|
4574 ConvertListType(child, getter_AddRefs(temp), aListType, aItemType); |
|
4575 NS_ENSURE_SUCCESS(rv, rv); |
|
4576 child = temp.forget(); |
|
4577 } |
|
4578 } |
|
4579 child = child->GetNextSibling(); |
|
4580 } |
|
4581 |
|
4582 if (aList->IsElement() && aList->AsElement()->IsHTML(aListType)) { |
|
4583 nsCOMPtr<dom::Element> list = aList->AsElement(); |
|
4584 list.forget(aOutList); |
|
4585 return NS_OK; |
|
4586 } |
|
4587 |
|
4588 return mHTMLEditor->ReplaceContainer(aList, aOutList, |
|
4589 nsDependentAtomString(aListType)); |
|
4590 } |
|
4591 |
|
4592 |
|
4593 /////////////////////////////////////////////////////////////////////////// |
|
4594 // CreateStyleForInsertText: take care of clearing and setting appropriate |
|
4595 // style nodes for text insertion. |
|
4596 // |
|
4597 // |
|
4598 nsresult |
|
4599 nsHTMLEditRules::CreateStyleForInsertText(nsISelection *aSelection, |
|
4600 nsIDOMDocument *aDoc) |
|
4601 { |
|
4602 MOZ_ASSERT(aSelection && aDoc && mHTMLEditor->mTypeInState); |
|
4603 |
|
4604 bool weDidSomething = false; |
|
4605 nsCOMPtr<nsIDOMNode> node, tmp; |
|
4606 int32_t offset; |
|
4607 NS_ENSURE_STATE(mHTMLEditor); |
|
4608 nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, |
|
4609 getter_AddRefs(node), |
|
4610 &offset); |
|
4611 NS_ENSURE_SUCCESS(res, res); |
|
4612 |
|
4613 // next examine our present style and make sure default styles are either |
|
4614 // present or explicitly overridden. If neither, add the default style to |
|
4615 // the TypeInState |
|
4616 int32_t length = mHTMLEditor->mDefaultStyles.Length(); |
|
4617 for (int32_t j = 0; j < length; j++) { |
|
4618 PropItem* propItem = mHTMLEditor->mDefaultStyles[j]; |
|
4619 MOZ_ASSERT(propItem); |
|
4620 bool bFirst, bAny, bAll; |
|
4621 |
|
4622 // GetInlineProperty also examine TypeInState. The only gotcha here is |
|
4623 // that a cleared property looks like an unset property. For now I'm |
|
4624 // assuming that's not a problem: that default styles will always be |
|
4625 // multivalue styles (like font face or size) where clearing the style |
|
4626 // means we want to go back to the default. If we ever wanted a "toggle" |
|
4627 // style like bold for a default, though, I'll have to add code to detect |
|
4628 // the difference between unset and explicitly cleared, else user would |
|
4629 // never be able to unbold, for instance. |
|
4630 nsAutoString curValue; |
|
4631 NS_ENSURE_STATE(mHTMLEditor); |
|
4632 res = mHTMLEditor->GetInlinePropertyBase(propItem->tag, &propItem->attr, |
|
4633 nullptr, &bFirst, &bAny, &bAll, |
|
4634 &curValue, false); |
|
4635 NS_ENSURE_SUCCESS(res, res); |
|
4636 |
|
4637 if (!bAny) { |
|
4638 // no style set for this prop/attr |
|
4639 mHTMLEditor->mTypeInState->SetProp(propItem->tag, propItem->attr, |
|
4640 propItem->value); |
|
4641 } |
|
4642 } |
|
4643 |
|
4644 nsCOMPtr<nsIDOMElement> rootElement; |
|
4645 res = aDoc->GetDocumentElement(getter_AddRefs(rootElement)); |
|
4646 NS_ENSURE_SUCCESS(res, res); |
|
4647 |
|
4648 // process clearing any styles first |
|
4649 nsAutoPtr<PropItem> item(mHTMLEditor->mTypeInState->TakeClearProperty()); |
|
4650 while (item && node != rootElement) { |
|
4651 NS_ENSURE_STATE(mHTMLEditor); |
|
4652 res = mHTMLEditor->ClearStyle(address_of(node), &offset, |
|
4653 item->tag, &item->attr); |
|
4654 NS_ENSURE_SUCCESS(res, res); |
|
4655 item = mHTMLEditor->mTypeInState->TakeClearProperty(); |
|
4656 weDidSomething = true; |
|
4657 } |
|
4658 |
|
4659 // then process setting any styles |
|
4660 int32_t relFontSize = mHTMLEditor->mTypeInState->TakeRelativeFontSize(); |
|
4661 item = mHTMLEditor->mTypeInState->TakeSetProperty(); |
|
4662 |
|
4663 if (item || relFontSize) { |
|
4664 // we have at least one style to add; make a new text node to insert style |
|
4665 // nodes above. |
|
4666 if (mHTMLEditor->IsTextNode(node)) { |
|
4667 // if we are in a text node, split it |
|
4668 NS_ENSURE_STATE(mHTMLEditor); |
|
4669 res = mHTMLEditor->SplitNodeDeep(node, node, offset, &offset); |
|
4670 NS_ENSURE_SUCCESS(res, res); |
|
4671 node->GetParentNode(getter_AddRefs(tmp)); |
|
4672 node = tmp; |
|
4673 } |
|
4674 if (!mHTMLEditor->IsContainer(node)) { |
|
4675 return NS_OK; |
|
4676 } |
|
4677 nsCOMPtr<nsIDOMNode> newNode; |
|
4678 nsCOMPtr<nsIDOMText> nodeAsText; |
|
4679 res = aDoc->CreateTextNode(EmptyString(), getter_AddRefs(nodeAsText)); |
|
4680 NS_ENSURE_SUCCESS(res, res); |
|
4681 NS_ENSURE_TRUE(nodeAsText, NS_ERROR_NULL_POINTER); |
|
4682 newNode = do_QueryInterface(nodeAsText); |
|
4683 NS_ENSURE_STATE(mHTMLEditor); |
|
4684 res = mHTMLEditor->InsertNode(newNode, node, offset); |
|
4685 NS_ENSURE_SUCCESS(res, res); |
|
4686 node = newNode; |
|
4687 offset = 0; |
|
4688 weDidSomething = true; |
|
4689 |
|
4690 if (relFontSize) { |
|
4691 // dir indicated bigger versus smaller. 1 = bigger, -1 = smaller |
|
4692 int32_t dir = relFontSize > 0 ? 1 : -1; |
|
4693 for (int32_t j = 0; j < DeprecatedAbs(relFontSize); j++) { |
|
4694 NS_ENSURE_STATE(mHTMLEditor); |
|
4695 res = mHTMLEditor->RelativeFontChangeOnTextNode(dir, nodeAsText, |
|
4696 0, -1); |
|
4697 NS_ENSURE_SUCCESS(res, res); |
|
4698 } |
|
4699 } |
|
4700 |
|
4701 while (item) { |
|
4702 NS_ENSURE_STATE(mHTMLEditor); |
|
4703 res = mHTMLEditor->SetInlinePropertyOnNode(node, item->tag, &item->attr, |
|
4704 &item->value); |
|
4705 NS_ENSURE_SUCCESS(res, res); |
|
4706 item = mHTMLEditor->mTypeInState->TakeSetProperty(); |
|
4707 } |
|
4708 } |
|
4709 if (weDidSomething) { |
|
4710 return aSelection->Collapse(node, offset); |
|
4711 } |
|
4712 |
|
4713 return NS_OK; |
|
4714 } |
|
4715 |
|
4716 |
|
4717 /////////////////////////////////////////////////////////////////////////// |
|
4718 // IsEmptyBlock: figure out if aNode is (or is inside) an empty block. |
|
4719 // A block can have children and still be considered empty, |
|
4720 // if the children are empty or non-editable. |
|
4721 // |
|
4722 nsresult |
|
4723 nsHTMLEditRules::IsEmptyBlock(nsIDOMNode *aNode, |
|
4724 bool *outIsEmptyBlock, |
|
4725 bool aMozBRDoesntCount, |
|
4726 bool aListItemsNotEmpty) |
|
4727 { |
|
4728 NS_ENSURE_TRUE(aNode && outIsEmptyBlock, NS_ERROR_NULL_POINTER); |
|
4729 *outIsEmptyBlock = true; |
|
4730 |
|
4731 // nsresult res = NS_OK; |
|
4732 nsCOMPtr<nsIDOMNode> nodeToTest; |
|
4733 if (IsBlockNode(aNode)) nodeToTest = do_QueryInterface(aNode); |
|
4734 // else nsCOMPtr<nsIDOMElement> block; |
|
4735 // looks like I forgot to finish this. Wonder what I was going to do? |
|
4736 |
|
4737 NS_ENSURE_TRUE(nodeToTest, NS_ERROR_NULL_POINTER); |
|
4738 return mHTMLEditor->IsEmptyNode(nodeToTest, outIsEmptyBlock, |
|
4739 aMozBRDoesntCount, aListItemsNotEmpty); |
|
4740 } |
|
4741 |
|
4742 |
|
4743 nsresult |
|
4744 nsHTMLEditRules::WillAlign(Selection* aSelection, |
|
4745 const nsAString *alignType, |
|
4746 bool *aCancel, |
|
4747 bool *aHandled) |
|
4748 { |
|
4749 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } |
|
4750 |
|
4751 nsresult res = WillInsert(aSelection, aCancel); |
|
4752 NS_ENSURE_SUCCESS(res, res); |
|
4753 |
|
4754 // initialize out param |
|
4755 // we want to ignore result of WillInsert() |
|
4756 *aCancel = false; |
|
4757 *aHandled = false; |
|
4758 |
|
4759 res = NormalizeSelection(aSelection); |
|
4760 NS_ENSURE_SUCCESS(res, res); |
|
4761 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); |
|
4762 |
|
4763 // convert the selection ranges into "promoted" selection ranges: |
|
4764 // this basically just expands the range to include the immediate |
|
4765 // block parent, and then further expands to include any ancestors |
|
4766 // whose children are all in the range |
|
4767 *aHandled = true; |
|
4768 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
4769 res = GetNodesFromSelection(aSelection, EditAction::align, arrayOfNodes); |
|
4770 NS_ENSURE_SUCCESS(res, res); |
|
4771 |
|
4772 // if we don't have any nodes, or we have only a single br, then we are |
|
4773 // creating an empty alignment div. We have to do some different things for these. |
|
4774 bool emptyDiv = false; |
|
4775 int32_t listCount = arrayOfNodes.Count(); |
|
4776 if (!listCount) emptyDiv = true; |
|
4777 if (listCount == 1) |
|
4778 { |
|
4779 nsCOMPtr<nsIDOMNode> theNode = arrayOfNodes[0]; |
|
4780 |
|
4781 if (nsHTMLEditUtils::SupportsAlignAttr(theNode)) |
|
4782 { |
|
4783 // the node is a table element, an horiz rule, a paragraph, a div |
|
4784 // or a section header; in HTML 4, it can directly carry the ALIGN |
|
4785 // attribute and we don't need to make a div! If we are in CSS mode, |
|
4786 // all the work is done in AlignBlock |
|
4787 nsCOMPtr<nsIDOMElement> theElem = do_QueryInterface(theNode); |
|
4788 res = AlignBlock(theElem, alignType, true); |
|
4789 NS_ENSURE_SUCCESS(res, res); |
|
4790 return NS_OK; |
|
4791 } |
|
4792 |
|
4793 if (nsTextEditUtils::IsBreak(theNode)) |
|
4794 { |
|
4795 // The special case emptyDiv code (below) that consumes BRs can |
|
4796 // cause tables to split if the start node of the selection is |
|
4797 // not in a table cell or caption, for example parent is a <tr>. |
|
4798 // Avoid this unnecessary splitting if possible by leaving emptyDiv |
|
4799 // FALSE so that we fall through to the normal case alignment code. |
|
4800 // |
|
4801 // XXX: It seems a little error prone for the emptyDiv special |
|
4802 // case code to assume that the start node of the selection |
|
4803 // is the parent of the single node in the arrayOfNodes, as |
|
4804 // the paragraph above points out. Do we rely on the selection |
|
4805 // start node because of the fact that arrayOfNodes can be empty? |
|
4806 // We should probably revisit this issue. - kin |
|
4807 |
|
4808 nsCOMPtr<nsIDOMNode> parent; |
|
4809 int32_t offset; |
|
4810 NS_ENSURE_STATE(mHTMLEditor); |
|
4811 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); |
|
4812 |
|
4813 if (!nsHTMLEditUtils::IsTableElement(parent) || nsHTMLEditUtils::IsTableCellOrCaption(parent)) |
|
4814 emptyDiv = true; |
|
4815 } |
|
4816 } |
|
4817 if (emptyDiv) |
|
4818 { |
|
4819 int32_t offset; |
|
4820 nsCOMPtr<nsIDOMNode> brNode, parent, theDiv, sib; |
|
4821 NS_NAMED_LITERAL_STRING(divType, "div"); |
|
4822 NS_ENSURE_STATE(mHTMLEditor); |
|
4823 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); |
|
4824 NS_ENSURE_SUCCESS(res, res); |
|
4825 res = SplitAsNeeded(&divType, address_of(parent), &offset); |
|
4826 NS_ENSURE_SUCCESS(res, res); |
|
4827 // consume a trailing br, if any. This is to keep an alignment from |
|
4828 // creating extra lines, if possible. |
|
4829 NS_ENSURE_STATE(mHTMLEditor); |
|
4830 res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode)); |
|
4831 NS_ENSURE_SUCCESS(res, res); |
|
4832 if (brNode && nsTextEditUtils::IsBreak(brNode)) |
|
4833 { |
|
4834 // making use of html structure... if next node after where |
|
4835 // we are putting our div is not a block, then the br we |
|
4836 // found is in same block we are, so its safe to consume it. |
|
4837 NS_ENSURE_STATE(mHTMLEditor); |
|
4838 res = mHTMLEditor->GetNextHTMLSibling(parent, offset, address_of(sib)); |
|
4839 NS_ENSURE_SUCCESS(res, res); |
|
4840 if (!IsBlockNode(sib)) |
|
4841 { |
|
4842 NS_ENSURE_STATE(mHTMLEditor); |
|
4843 res = mHTMLEditor->DeleteNode(brNode); |
|
4844 NS_ENSURE_SUCCESS(res, res); |
|
4845 } |
|
4846 } |
|
4847 NS_ENSURE_STATE(mHTMLEditor); |
|
4848 res = mHTMLEditor->CreateNode(divType, parent, offset, getter_AddRefs(theDiv)); |
|
4849 NS_ENSURE_SUCCESS(res, res); |
|
4850 // remember our new block for postprocessing |
|
4851 mNewBlock = theDiv; |
|
4852 // set up the alignment on the div, using HTML or CSS |
|
4853 nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(theDiv); |
|
4854 res = AlignBlock(divElem, alignType, true); |
|
4855 NS_ENSURE_SUCCESS(res, res); |
|
4856 *aHandled = true; |
|
4857 // put in a moz-br so that it won't get deleted |
|
4858 res = CreateMozBR(theDiv, 0); |
|
4859 NS_ENSURE_SUCCESS(res, res); |
|
4860 res = aSelection->Collapse(theDiv, 0); |
|
4861 selectionResetter.Abort(); // don't reset our selection in this case. |
|
4862 return res; |
|
4863 } |
|
4864 |
|
4865 // Next we detect all the transitions in the array, where a transition |
|
4866 // means that adjacent nodes in the array don't have the same parent. |
|
4867 |
|
4868 nsTArray<bool> transitionList; |
|
4869 res = MakeTransitionList(arrayOfNodes, transitionList); |
|
4870 NS_ENSURE_SUCCESS(res, res); |
|
4871 |
|
4872 // Ok, now go through all the nodes and give them an align attrib or put them in a div, |
|
4873 // or whatever is appropriate. Wohoo! |
|
4874 |
|
4875 nsCOMPtr<nsIDOMNode> curParent; |
|
4876 nsCOMPtr<nsIDOMNode> curDiv; |
|
4877 bool useCSS = mHTMLEditor->IsCSSEnabled(); |
|
4878 for (int32_t i = 0; i < listCount; ++i) { |
|
4879 // here's where we actually figure out what to do |
|
4880 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i]; |
|
4881 |
|
4882 // Ignore all non-editable nodes. Leave them be. |
|
4883 if (!mHTMLEditor->IsEditable(curNode)) continue; |
|
4884 |
|
4885 int32_t offset; |
|
4886 curParent = nsEditor::GetNodeLocation(curNode, &offset); |
|
4887 |
|
4888 // the node is a table element, an horiz rule, a paragraph, a div |
|
4889 // or a section header; in HTML 4, it can directly carry the ALIGN |
|
4890 // attribute and we don't need to nest it, just set the alignment. |
|
4891 // In CSS, assign the corresponding CSS styles in AlignBlock |
|
4892 if (nsHTMLEditUtils::SupportsAlignAttr(curNode)) |
|
4893 { |
|
4894 nsCOMPtr<nsIDOMElement> curElem = do_QueryInterface(curNode); |
|
4895 res = AlignBlock(curElem, alignType, false); |
|
4896 NS_ENSURE_SUCCESS(res, res); |
|
4897 // clear out curDiv so that we don't put nodes after this one into it |
|
4898 curDiv = 0; |
|
4899 continue; |
|
4900 } |
|
4901 |
|
4902 // Skip insignificant formatting text nodes to prevent |
|
4903 // unnecessary structure splitting! |
|
4904 bool isEmptyTextNode = false; |
|
4905 if (nsEditor::IsTextNode(curNode) && |
|
4906 ((nsHTMLEditUtils::IsTableElement(curParent) && !nsHTMLEditUtils::IsTableCellOrCaption(curParent)) || |
|
4907 nsHTMLEditUtils::IsList(curParent) || |
|
4908 (NS_SUCCEEDED(mHTMLEditor->IsEmptyNode(curNode, &isEmptyTextNode)) && isEmptyTextNode))) |
|
4909 continue; |
|
4910 |
|
4911 // if it's a list item, or a list |
|
4912 // inside a list, forget any "current" div, and instead put divs inside |
|
4913 // the appropriate block (td, li, etc) |
|
4914 if ( nsHTMLEditUtils::IsListItem(curNode) |
|
4915 || nsHTMLEditUtils::IsList(curNode)) |
|
4916 { |
|
4917 res = RemoveAlignment(curNode, *alignType, true); |
|
4918 NS_ENSURE_SUCCESS(res, res); |
|
4919 if (useCSS) { |
|
4920 nsCOMPtr<nsIDOMElement> curElem = do_QueryInterface(curNode); |
|
4921 NS_NAMED_LITERAL_STRING(attrName, "align"); |
|
4922 int32_t count; |
|
4923 mHTMLEditor->mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(curNode, nullptr, |
|
4924 &attrName, alignType, |
|
4925 &count, false); |
|
4926 curDiv = 0; |
|
4927 continue; |
|
4928 } |
|
4929 else if (nsHTMLEditUtils::IsList(curParent)) { |
|
4930 // if we don't use CSS, add a contraint to list element : they have |
|
4931 // to be inside another list, ie >= second level of nesting |
|
4932 res = AlignInnerBlocks(curNode, alignType); |
|
4933 NS_ENSURE_SUCCESS(res, res); |
|
4934 curDiv = 0; |
|
4935 continue; |
|
4936 } |
|
4937 // clear out curDiv so that we don't put nodes after this one into it |
|
4938 } |
|
4939 |
|
4940 // need to make a div to put things in if we haven't already, |
|
4941 // or if this node doesn't go in div we used earlier. |
|
4942 if (!curDiv || transitionList[i]) |
|
4943 { |
|
4944 // First, check that our element can contain a div. |
|
4945 NS_NAMED_LITERAL_STRING(divType, "div"); |
|
4946 if (!mEditor->CanContainTag(curParent, nsGkAtoms::div)) { |
|
4947 return NS_OK; // cancelled |
|
4948 } |
|
4949 |
|
4950 res = SplitAsNeeded(&divType, address_of(curParent), &offset); |
|
4951 NS_ENSURE_SUCCESS(res, res); |
|
4952 NS_ENSURE_STATE(mHTMLEditor); |
|
4953 res = mHTMLEditor->CreateNode(divType, curParent, offset, getter_AddRefs(curDiv)); |
|
4954 NS_ENSURE_SUCCESS(res, res); |
|
4955 // remember our new block for postprocessing |
|
4956 mNewBlock = curDiv; |
|
4957 // set up the alignment on the div |
|
4958 nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(curDiv); |
|
4959 res = AlignBlock(divElem, alignType, true); |
|
4960 //nsAutoString attr(NS_LITERAL_STRING("align")); |
|
4961 //res = mHTMLEditor->SetAttribute(divElem, attr, *alignType); |
|
4962 //NS_ENSURE_SUCCESS(res, res); |
|
4963 // curDiv is now the correct thing to put curNode in |
|
4964 } |
|
4965 |
|
4966 // tuck the node into the end of the active div |
|
4967 NS_ENSURE_STATE(mHTMLEditor); |
|
4968 res = mHTMLEditor->MoveNode(curNode, curDiv, -1); |
|
4969 NS_ENSURE_SUCCESS(res, res); |
|
4970 } |
|
4971 |
|
4972 return res; |
|
4973 } |
|
4974 |
|
4975 |
|
4976 /////////////////////////////////////////////////////////////////////////// |
|
4977 // AlignInnerBlocks: align inside table cells or list items |
|
4978 // |
|
4979 nsresult |
|
4980 nsHTMLEditRules::AlignInnerBlocks(nsIDOMNode *aNode, const nsAString *alignType) |
|
4981 { |
|
4982 NS_ENSURE_TRUE(aNode && alignType, NS_ERROR_NULL_POINTER); |
|
4983 nsresult res; |
|
4984 |
|
4985 // gather list of table cells or list items |
|
4986 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
4987 nsTableCellAndListItemFunctor functor; |
|
4988 nsDOMIterator iter; |
|
4989 res = iter.Init(aNode); |
|
4990 NS_ENSURE_SUCCESS(res, res); |
|
4991 res = iter.AppendList(functor, arrayOfNodes); |
|
4992 NS_ENSURE_SUCCESS(res, res); |
|
4993 |
|
4994 // now that we have the list, align their contents as requested |
|
4995 int32_t listCount = arrayOfNodes.Count(); |
|
4996 int32_t j; |
|
4997 |
|
4998 for (j = 0; j < listCount; j++) |
|
4999 { |
|
5000 nsIDOMNode* node = arrayOfNodes[0]; |
|
5001 res = AlignBlockContents(node, alignType); |
|
5002 NS_ENSURE_SUCCESS(res, res); |
|
5003 arrayOfNodes.RemoveObjectAt(0); |
|
5004 } |
|
5005 |
|
5006 return res; |
|
5007 } |
|
5008 |
|
5009 |
|
5010 /////////////////////////////////////////////////////////////////////////// |
|
5011 // AlignBlockContents: align contents of a block element |
|
5012 // |
|
5013 nsresult |
|
5014 nsHTMLEditRules::AlignBlockContents(nsIDOMNode *aNode, const nsAString *alignType) |
|
5015 { |
|
5016 NS_ENSURE_TRUE(aNode && alignType, NS_ERROR_NULL_POINTER); |
|
5017 nsresult res; |
|
5018 nsCOMPtr <nsIDOMNode> firstChild, lastChild, divNode; |
|
5019 |
|
5020 bool useCSS = mHTMLEditor->IsCSSEnabled(); |
|
5021 |
|
5022 NS_ENSURE_STATE(mHTMLEditor); |
|
5023 res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(firstChild)); |
|
5024 NS_ENSURE_SUCCESS(res, res); |
|
5025 NS_ENSURE_STATE(mHTMLEditor); |
|
5026 res = mHTMLEditor->GetLastEditableChild(aNode, address_of(lastChild)); |
|
5027 NS_ENSURE_SUCCESS(res, res); |
|
5028 NS_NAMED_LITERAL_STRING(attr, "align"); |
|
5029 if (!firstChild) |
|
5030 { |
|
5031 // this cell has no content, nothing to align |
|
5032 } |
|
5033 else if ((firstChild==lastChild) && nsHTMLEditUtils::IsDiv(firstChild)) |
|
5034 { |
|
5035 // the cell already has a div containing all of its content: just |
|
5036 // act on this div. |
|
5037 nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(firstChild); |
|
5038 if (useCSS) { |
|
5039 NS_ENSURE_STATE(mHTMLEditor); |
|
5040 res = mHTMLEditor->SetAttributeOrEquivalent(divElem, attr, *alignType, false); |
|
5041 } |
|
5042 else { |
|
5043 NS_ENSURE_STATE(mHTMLEditor); |
|
5044 res = mHTMLEditor->SetAttribute(divElem, attr, *alignType); |
|
5045 } |
|
5046 NS_ENSURE_SUCCESS(res, res); |
|
5047 } |
|
5048 else |
|
5049 { |
|
5050 // else we need to put in a div, set the alignment, and toss in all the children |
|
5051 NS_ENSURE_STATE(mHTMLEditor); |
|
5052 res = mHTMLEditor->CreateNode(NS_LITERAL_STRING("div"), aNode, 0, getter_AddRefs(divNode)); |
|
5053 NS_ENSURE_SUCCESS(res, res); |
|
5054 // set up the alignment on the div |
|
5055 nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(divNode); |
|
5056 if (useCSS) { |
|
5057 NS_ENSURE_STATE(mHTMLEditor); |
|
5058 res = mHTMLEditor->SetAttributeOrEquivalent(divElem, attr, *alignType, false); |
|
5059 } |
|
5060 else { |
|
5061 NS_ENSURE_STATE(mHTMLEditor); |
|
5062 res = mHTMLEditor->SetAttribute(divElem, attr, *alignType); |
|
5063 } |
|
5064 NS_ENSURE_SUCCESS(res, res); |
|
5065 // tuck the children into the end of the active div |
|
5066 while (lastChild && (lastChild != divNode)) |
|
5067 { |
|
5068 NS_ENSURE_STATE(mHTMLEditor); |
|
5069 res = mHTMLEditor->MoveNode(lastChild, divNode, 0); |
|
5070 NS_ENSURE_SUCCESS(res, res); |
|
5071 NS_ENSURE_STATE(mHTMLEditor); |
|
5072 res = mHTMLEditor->GetLastEditableChild(aNode, address_of(lastChild)); |
|
5073 NS_ENSURE_SUCCESS(res, res); |
|
5074 } |
|
5075 } |
|
5076 return res; |
|
5077 } |
|
5078 |
|
5079 /////////////////////////////////////////////////////////////////////////// |
|
5080 // CheckForEmptyBlock: Called by WillDeleteSelection to detect and handle |
|
5081 // case of deleting from inside an empty block. |
|
5082 // |
|
5083 nsresult |
|
5084 nsHTMLEditRules::CheckForEmptyBlock(nsIDOMNode *aStartNode, |
|
5085 nsIDOMNode *aBodyNode, |
|
5086 nsISelection *aSelection, |
|
5087 bool *aHandled) |
|
5088 { |
|
5089 // If the editing host is an inline element, bail out early. |
|
5090 if (IsInlineNode(aBodyNode)) { |
|
5091 return NS_OK; |
|
5092 } |
|
5093 // if we are inside an empty block, delete it. |
|
5094 // Note: do NOT delete table elements this way. |
|
5095 nsresult res = NS_OK; |
|
5096 nsCOMPtr<nsIDOMNode> block, emptyBlock; |
|
5097 if (IsBlockNode(aStartNode)) |
|
5098 block = aStartNode; |
|
5099 else |
|
5100 block = mHTMLEditor->GetBlockNodeParent(aStartNode); |
|
5101 bool bIsEmptyNode; |
|
5102 if (block != aBodyNode) // efficiency hack. avoiding IsEmptyNode() call when in body |
|
5103 { |
|
5104 NS_ENSURE_STATE(mHTMLEditor); |
|
5105 res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, true, false); |
|
5106 NS_ENSURE_SUCCESS(res, res); |
|
5107 while (bIsEmptyNode && !nsHTMLEditUtils::IsTableElement(block) && (block != aBodyNode)) |
|
5108 { |
|
5109 emptyBlock = block; |
|
5110 block = mHTMLEditor->GetBlockNodeParent(emptyBlock); |
|
5111 NS_ENSURE_STATE(mHTMLEditor); |
|
5112 res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, true, false); |
|
5113 NS_ENSURE_SUCCESS(res, res); |
|
5114 } |
|
5115 } |
|
5116 |
|
5117 nsCOMPtr<nsIContent> emptyContent = do_QueryInterface(emptyBlock); |
|
5118 if (emptyBlock && emptyContent->IsEditable()) |
|
5119 { |
|
5120 int32_t offset; |
|
5121 nsCOMPtr<nsIDOMNode> blockParent = nsEditor::GetNodeLocation(emptyBlock, &offset); |
|
5122 NS_ENSURE_TRUE(blockParent && offset >= 0, NS_ERROR_FAILURE); |
|
5123 |
|
5124 if (nsHTMLEditUtils::IsListItem(emptyBlock)) |
|
5125 { |
|
5126 // are we the first list item in the list? |
|
5127 bool bIsFirst; |
|
5128 NS_ENSURE_STATE(mHTMLEditor); |
|
5129 res = mHTMLEditor->IsFirstEditableChild(emptyBlock, &bIsFirst); |
|
5130 NS_ENSURE_SUCCESS(res, res); |
|
5131 if (bIsFirst) |
|
5132 { |
|
5133 int32_t listOffset; |
|
5134 nsCOMPtr<nsIDOMNode> listParent = nsEditor::GetNodeLocation(blockParent, |
|
5135 &listOffset); |
|
5136 NS_ENSURE_TRUE(listParent && listOffset >= 0, NS_ERROR_FAILURE); |
|
5137 // if we are a sublist, skip the br creation |
|
5138 if (!nsHTMLEditUtils::IsList(listParent)) |
|
5139 { |
|
5140 // create a br before list |
|
5141 nsCOMPtr<nsIDOMNode> brNode; |
|
5142 NS_ENSURE_STATE(mHTMLEditor); |
|
5143 res = mHTMLEditor->CreateBR(listParent, listOffset, address_of(brNode)); |
|
5144 NS_ENSURE_SUCCESS(res, res); |
|
5145 // adjust selection to be right before it |
|
5146 res = aSelection->Collapse(listParent, listOffset); |
|
5147 NS_ENSURE_SUCCESS(res, res); |
|
5148 } |
|
5149 // else just let selection perculate up. We'll adjust it in AfterEdit() |
|
5150 } |
|
5151 } |
|
5152 else |
|
5153 { |
|
5154 // adjust selection to be right after it |
|
5155 res = aSelection->Collapse(blockParent, offset+1); |
|
5156 NS_ENSURE_SUCCESS(res, res); |
|
5157 } |
|
5158 NS_ENSURE_STATE(mHTMLEditor); |
|
5159 res = mHTMLEditor->DeleteNode(emptyBlock); |
|
5160 *aHandled = true; |
|
5161 } |
|
5162 return res; |
|
5163 } |
|
5164 |
|
5165 nsresult |
|
5166 nsHTMLEditRules::CheckForInvisibleBR(nsIDOMNode *aBlock, |
|
5167 BRLocation aWhere, |
|
5168 nsCOMPtr<nsIDOMNode> *outBRNode, |
|
5169 int32_t aOffset) |
|
5170 { |
|
5171 NS_ENSURE_TRUE(aBlock && outBRNode, NS_ERROR_NULL_POINTER); |
|
5172 *outBRNode = nullptr; |
|
5173 |
|
5174 nsCOMPtr<nsIDOMNode> testNode; |
|
5175 int32_t testOffset = 0; |
|
5176 bool runTest = false; |
|
5177 |
|
5178 if (aWhere == kBlockEnd) |
|
5179 { |
|
5180 nsCOMPtr<nsIDOMNode> rightmostNode = |
|
5181 mHTMLEditor->GetRightmostChild(aBlock, true); // no block crossing |
|
5182 |
|
5183 if (rightmostNode) |
|
5184 { |
|
5185 int32_t nodeOffset; |
|
5186 nsCOMPtr<nsIDOMNode> nodeParent = nsEditor::GetNodeLocation(rightmostNode, |
|
5187 &nodeOffset); |
|
5188 runTest = true; |
|
5189 testNode = nodeParent; |
|
5190 // use offset + 1, because we want the last node included in our |
|
5191 // evaluation |
|
5192 testOffset = nodeOffset + 1; |
|
5193 } |
|
5194 } |
|
5195 else if (aOffset) |
|
5196 { |
|
5197 runTest = true; |
|
5198 testNode = aBlock; |
|
5199 // we'll check everything to the left of the input position |
|
5200 testOffset = aOffset; |
|
5201 } |
|
5202 |
|
5203 if (runTest) |
|
5204 { |
|
5205 nsWSRunObject wsTester(mHTMLEditor, testNode, testOffset); |
|
5206 if (WSType::br == wsTester.mStartReason) { |
|
5207 *outBRNode = wsTester.mStartReasonNode; |
|
5208 } |
|
5209 } |
|
5210 |
|
5211 return NS_OK; |
|
5212 } |
|
5213 |
|
5214 |
|
5215 /////////////////////////////////////////////////////////////////////////// |
|
5216 // GetInnerContent: aList and aTbl allow the caller to specify what kind |
|
5217 // of content to "look inside". If aTbl is true, look inside |
|
5218 // any table content, and insert the inner content into the |
|
5219 // supplied issupportsarray at offset aIndex. |
|
5220 // Similarly with aList and list content. |
|
5221 // aIndex is updated to point past inserted elements. |
|
5222 // |
|
5223 nsresult |
|
5224 nsHTMLEditRules::GetInnerContent(nsIDOMNode *aNode, nsCOMArray<nsIDOMNode> &outArrayOfNodes, |
|
5225 int32_t *aIndex, bool aList, bool aTbl) |
|
5226 { |
|
5227 NS_ENSURE_TRUE(aNode && aIndex, NS_ERROR_NULL_POINTER); |
|
5228 |
|
5229 nsCOMPtr<nsIDOMNode> node; |
|
5230 |
|
5231 nsresult res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(node)); |
|
5232 while (NS_SUCCEEDED(res) && node) |
|
5233 { |
|
5234 if ( ( aList && (nsHTMLEditUtils::IsList(node) || |
|
5235 nsHTMLEditUtils::IsListItem(node) ) ) |
|
5236 || ( aTbl && nsHTMLEditUtils::IsTableElement(node) ) ) |
|
5237 { |
|
5238 res = GetInnerContent(node, outArrayOfNodes, aIndex, aList, aTbl); |
|
5239 NS_ENSURE_SUCCESS(res, res); |
|
5240 } |
|
5241 else |
|
5242 { |
|
5243 outArrayOfNodes.InsertObjectAt(node, *aIndex); |
|
5244 (*aIndex)++; |
|
5245 } |
|
5246 nsCOMPtr<nsIDOMNode> tmp; |
|
5247 res = node->GetNextSibling(getter_AddRefs(tmp)); |
|
5248 node = tmp; |
|
5249 } |
|
5250 |
|
5251 return res; |
|
5252 } |
|
5253 |
|
5254 /////////////////////////////////////////////////////////////////////////// |
|
5255 // ExpandSelectionForDeletion: this promotes our selection to include blocks |
|
5256 // that have all their children selected. |
|
5257 // |
|
5258 nsresult |
|
5259 nsHTMLEditRules::ExpandSelectionForDeletion(nsISelection *aSelection) |
|
5260 { |
|
5261 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); |
|
5262 |
|
5263 // don't need to touch collapsed selections |
|
5264 if (aSelection->Collapsed()) { |
|
5265 return NS_OK; |
|
5266 } |
|
5267 |
|
5268 int32_t rangeCount; |
|
5269 nsresult res = aSelection->GetRangeCount(&rangeCount); |
|
5270 NS_ENSURE_SUCCESS(res, res); |
|
5271 |
|
5272 // we don't need to mess with cell selections, and we assume multirange selections are those. |
|
5273 if (rangeCount != 1) return NS_OK; |
|
5274 |
|
5275 // find current sel start and end |
|
5276 nsCOMPtr<nsIDOMRange> range; |
|
5277 res = aSelection->GetRangeAt(0, getter_AddRefs(range)); |
|
5278 NS_ENSURE_SUCCESS(res, res); |
|
5279 NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER); |
|
5280 nsCOMPtr<nsIDOMNode> selStartNode, selEndNode, selCommon; |
|
5281 int32_t selStartOffset, selEndOffset; |
|
5282 |
|
5283 res = range->GetStartContainer(getter_AddRefs(selStartNode)); |
|
5284 NS_ENSURE_SUCCESS(res, res); |
|
5285 res = range->GetStartOffset(&selStartOffset); |
|
5286 NS_ENSURE_SUCCESS(res, res); |
|
5287 res = range->GetEndContainer(getter_AddRefs(selEndNode)); |
|
5288 NS_ENSURE_SUCCESS(res, res); |
|
5289 res = range->GetEndOffset(&selEndOffset); |
|
5290 NS_ENSURE_SUCCESS(res, res); |
|
5291 |
|
5292 // find current selection common block parent |
|
5293 res = range->GetCommonAncestorContainer(getter_AddRefs(selCommon)); |
|
5294 NS_ENSURE_SUCCESS(res, res); |
|
5295 if (!IsBlockNode(selCommon)) |
|
5296 selCommon = nsHTMLEditor::GetBlockNodeParent(selCommon); |
|
5297 |
|
5298 // set up for loops and cache our root element |
|
5299 bool stillLooking = true; |
|
5300 nsCOMPtr<nsIDOMNode> visNode, firstBRParent; |
|
5301 int32_t visOffset=0, firstBROffset=0; |
|
5302 WSType wsType; |
|
5303 nsCOMPtr<nsIContent> rootContent = mHTMLEditor->GetActiveEditingHost(); |
|
5304 nsCOMPtr<nsIDOMNode> rootElement = do_QueryInterface(rootContent); |
|
5305 NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE); |
|
5306 |
|
5307 // find previous visible thingy before start of selection |
|
5308 if ((selStartNode!=selCommon) && (selStartNode!=rootElement)) |
|
5309 { |
|
5310 while (stillLooking) |
|
5311 { |
|
5312 nsWSRunObject wsObj(mHTMLEditor, selStartNode, selStartOffset); |
|
5313 wsObj.PriorVisibleNode(selStartNode, selStartOffset, address_of(visNode), |
|
5314 &visOffset, &wsType); |
|
5315 if (wsType == WSType::thisBlock) { |
|
5316 // we want to keep looking up. But stop if we are crossing table element |
|
5317 // boundaries, or if we hit the root. |
|
5318 if ( nsHTMLEditUtils::IsTableElement(wsObj.mStartReasonNode) || |
|
5319 (selCommon == wsObj.mStartReasonNode) || |
|
5320 (rootElement == wsObj.mStartReasonNode) ) |
|
5321 { |
|
5322 stillLooking = false; |
|
5323 } |
|
5324 else |
|
5325 { |
|
5326 selStartNode = nsEditor::GetNodeLocation(wsObj.mStartReasonNode, |
|
5327 &selStartOffset); |
|
5328 } |
|
5329 } |
|
5330 else |
|
5331 { |
|
5332 stillLooking = false; |
|
5333 } |
|
5334 } |
|
5335 } |
|
5336 |
|
5337 stillLooking = true; |
|
5338 // find next visible thingy after end of selection |
|
5339 if ((selEndNode!=selCommon) && (selEndNode!=rootElement)) |
|
5340 { |
|
5341 while (stillLooking) |
|
5342 { |
|
5343 nsWSRunObject wsObj(mHTMLEditor, selEndNode, selEndOffset); |
|
5344 wsObj.NextVisibleNode(selEndNode, selEndOffset, address_of(visNode), |
|
5345 &visOffset, &wsType); |
|
5346 if (wsType == WSType::br) { |
|
5347 if (mHTMLEditor->IsVisBreak(wsObj.mEndReasonNode)) |
|
5348 { |
|
5349 stillLooking = false; |
|
5350 } |
|
5351 else |
|
5352 { |
|
5353 if (!firstBRParent) |
|
5354 { |
|
5355 firstBRParent = selEndNode; |
|
5356 firstBROffset = selEndOffset; |
|
5357 } |
|
5358 selEndNode = nsEditor::GetNodeLocation(wsObj.mEndReasonNode, &selEndOffset); |
|
5359 ++selEndOffset; |
|
5360 } |
|
5361 } else if (wsType == WSType::thisBlock) { |
|
5362 // we want to keep looking up. But stop if we are crossing table element |
|
5363 // boundaries, or if we hit the root. |
|
5364 if ( nsHTMLEditUtils::IsTableElement(wsObj.mEndReasonNode) || |
|
5365 (selCommon == wsObj.mEndReasonNode) || |
|
5366 (rootElement == wsObj.mEndReasonNode) ) |
|
5367 { |
|
5368 stillLooking = false; |
|
5369 } |
|
5370 else |
|
5371 { |
|
5372 selEndNode = nsEditor::GetNodeLocation(wsObj.mEndReasonNode, &selEndOffset); |
|
5373 ++selEndOffset; |
|
5374 } |
|
5375 } |
|
5376 else |
|
5377 { |
|
5378 stillLooking = false; |
|
5379 } |
|
5380 } |
|
5381 } |
|
5382 // now set the selection to the new range |
|
5383 aSelection->Collapse(selStartNode, selStartOffset); |
|
5384 |
|
5385 // expand selection endpoint only if we didnt pass a br, |
|
5386 // or if we really needed to pass that br (ie, its block is now |
|
5387 // totally selected) |
|
5388 bool doEndExpansion = true; |
|
5389 if (firstBRParent) |
|
5390 { |
|
5391 // find block node containing br |
|
5392 nsCOMPtr<nsIDOMNode> brBlock = firstBRParent; |
|
5393 if (!IsBlockNode(brBlock)) |
|
5394 brBlock = nsHTMLEditor::GetBlockNodeParent(brBlock); |
|
5395 bool nodeBefore=false, nodeAfter=false; |
|
5396 |
|
5397 // create a range that represents expanded selection |
|
5398 nsCOMPtr<nsINode> node = do_QueryInterface(selStartNode); |
|
5399 NS_ENSURE_STATE(node); |
|
5400 nsRefPtr<nsRange> range = new nsRange(node); |
|
5401 res = range->SetStart(selStartNode, selStartOffset); |
|
5402 NS_ENSURE_SUCCESS(res, res); |
|
5403 res = range->SetEnd(selEndNode, selEndOffset); |
|
5404 NS_ENSURE_SUCCESS(res, res); |
|
5405 |
|
5406 // check if block is entirely inside range |
|
5407 nsCOMPtr<nsIContent> brContentBlock = do_QueryInterface(brBlock); |
|
5408 res = nsRange::CompareNodeToRange(brContentBlock, range, &nodeBefore, &nodeAfter); |
|
5409 |
|
5410 // if block isn't contained, forgo grabbing the br in the expanded selection |
|
5411 if (nodeBefore || nodeAfter) |
|
5412 doEndExpansion = false; |
|
5413 } |
|
5414 if (doEndExpansion) |
|
5415 { |
|
5416 res = aSelection->Extend(selEndNode, selEndOffset); |
|
5417 } |
|
5418 else |
|
5419 { |
|
5420 // only expand to just before br |
|
5421 res = aSelection->Extend(firstBRParent, firstBROffset); |
|
5422 } |
|
5423 |
|
5424 return res; |
|
5425 } |
|
5426 |
|
5427 |
|
5428 /////////////////////////////////////////////////////////////////////////// |
|
5429 // NormalizeSelection: tweak non-collapsed selections to be more "natural". |
|
5430 // Idea here is to adjust selection endpoint so that they do not cross |
|
5431 // breaks or block boundaries unless something editable beyond that boundary |
|
5432 // is also selected. This adjustment makes it much easier for the various |
|
5433 // block operations to determine what nodes to act on. |
|
5434 // |
|
5435 nsresult |
|
5436 nsHTMLEditRules::NormalizeSelection(nsISelection *inSelection) |
|
5437 { |
|
5438 NS_ENSURE_TRUE(inSelection, NS_ERROR_NULL_POINTER); |
|
5439 |
|
5440 // don't need to touch collapsed selections |
|
5441 if (inSelection->Collapsed()) { |
|
5442 return NS_OK; |
|
5443 } |
|
5444 |
|
5445 int32_t rangeCount; |
|
5446 nsresult res = inSelection->GetRangeCount(&rangeCount); |
|
5447 NS_ENSURE_SUCCESS(res, res); |
|
5448 |
|
5449 // we don't need to mess with cell selections, and we assume multirange selections are those. |
|
5450 if (rangeCount != 1) return NS_OK; |
|
5451 |
|
5452 nsCOMPtr<nsIDOMRange> range; |
|
5453 res = inSelection->GetRangeAt(0, getter_AddRefs(range)); |
|
5454 NS_ENSURE_SUCCESS(res, res); |
|
5455 NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER); |
|
5456 nsCOMPtr<nsIDOMNode> startNode, endNode; |
|
5457 int32_t startOffset, endOffset; |
|
5458 nsCOMPtr<nsIDOMNode> newStartNode, newEndNode; |
|
5459 int32_t newStartOffset, newEndOffset; |
|
5460 |
|
5461 res = range->GetStartContainer(getter_AddRefs(startNode)); |
|
5462 NS_ENSURE_SUCCESS(res, res); |
|
5463 res = range->GetStartOffset(&startOffset); |
|
5464 NS_ENSURE_SUCCESS(res, res); |
|
5465 res = range->GetEndContainer(getter_AddRefs(endNode)); |
|
5466 NS_ENSURE_SUCCESS(res, res); |
|
5467 res = range->GetEndOffset(&endOffset); |
|
5468 NS_ENSURE_SUCCESS(res, res); |
|
5469 |
|
5470 // adjusted values default to original values |
|
5471 newStartNode = startNode; |
|
5472 newStartOffset = startOffset; |
|
5473 newEndNode = endNode; |
|
5474 newEndOffset = endOffset; |
|
5475 |
|
5476 // some locals we need for whitespace code |
|
5477 nsCOMPtr<nsIDOMNode> someNode; |
|
5478 int32_t offset; |
|
5479 WSType wsType; |
|
5480 |
|
5481 // let the whitespace code do the heavy lifting |
|
5482 nsWSRunObject wsEndObj(mHTMLEditor, endNode, endOffset); |
|
5483 // is there any intervening visible whitespace? if so we can't push selection past that, |
|
5484 // it would visibly change maening of users selection |
|
5485 wsEndObj.PriorVisibleNode(endNode, endOffset, address_of(someNode), |
|
5486 &offset, &wsType); |
|
5487 if (wsType != WSType::text && wsType != WSType::normalWS) { |
|
5488 // eThisBlock and eOtherBlock conveniently distinquish cases |
|
5489 // of going "down" into a block and "up" out of a block. |
|
5490 if (wsEndObj.mStartReason == WSType::otherBlock) { |
|
5491 // endpoint is just after the close of a block. |
|
5492 nsCOMPtr<nsIDOMNode> child = mHTMLEditor->GetRightmostChild(wsEndObj.mStartReasonNode, true); |
|
5493 if (child) |
|
5494 { |
|
5495 newEndNode = nsEditor::GetNodeLocation(child, &newEndOffset); |
|
5496 ++newEndOffset; // offset *after* child |
|
5497 } |
|
5498 // else block is empty - we can leave selection alone here, i think. |
|
5499 } else if (wsEndObj.mStartReason == WSType::thisBlock) { |
|
5500 // endpoint is just after start of this block |
|
5501 nsCOMPtr<nsIDOMNode> child; |
|
5502 NS_ENSURE_STATE(mHTMLEditor); |
|
5503 res = mHTMLEditor->GetPriorHTMLNode(endNode, endOffset, address_of(child)); |
|
5504 if (child) |
|
5505 { |
|
5506 newEndNode = nsEditor::GetNodeLocation(child, &newEndOffset); |
|
5507 ++newEndOffset; // offset *after* child |
|
5508 } |
|
5509 // else block is empty - we can leave selection alone here, i think. |
|
5510 } else if (wsEndObj.mStartReason == WSType::br) { |
|
5511 // endpoint is just after break. lets adjust it to before it. |
|
5512 newEndNode = nsEditor::GetNodeLocation(wsEndObj.mStartReasonNode, |
|
5513 &newEndOffset); |
|
5514 } |
|
5515 } |
|
5516 |
|
5517 |
|
5518 // similar dealio for start of range |
|
5519 nsWSRunObject wsStartObj(mHTMLEditor, startNode, startOffset); |
|
5520 // is there any intervening visible whitespace? if so we can't push selection past that, |
|
5521 // it would visibly change maening of users selection |
|
5522 wsStartObj.NextVisibleNode(startNode, startOffset, address_of(someNode), |
|
5523 &offset, &wsType); |
|
5524 if (wsType != WSType::text && wsType != WSType::normalWS) { |
|
5525 // eThisBlock and eOtherBlock conveniently distinquish cases |
|
5526 // of going "down" into a block and "up" out of a block. |
|
5527 if (wsStartObj.mEndReason == WSType::otherBlock) { |
|
5528 // startpoint is just before the start of a block. |
|
5529 nsCOMPtr<nsIDOMNode> child = mHTMLEditor->GetLeftmostChild(wsStartObj.mEndReasonNode, true); |
|
5530 if (child) |
|
5531 { |
|
5532 newStartNode = nsEditor::GetNodeLocation(child, &newStartOffset); |
|
5533 } |
|
5534 // else block is empty - we can leave selection alone here, i think. |
|
5535 } else if (wsStartObj.mEndReason == WSType::thisBlock) { |
|
5536 // startpoint is just before end of this block |
|
5537 nsCOMPtr<nsIDOMNode> child; |
|
5538 NS_ENSURE_STATE(mHTMLEditor); |
|
5539 res = mHTMLEditor->GetNextHTMLNode(startNode, startOffset, address_of(child)); |
|
5540 if (child) |
|
5541 { |
|
5542 newStartNode = nsEditor::GetNodeLocation(child, &newStartOffset); |
|
5543 } |
|
5544 // else block is empty - we can leave selection alone here, i think. |
|
5545 } else if (wsStartObj.mEndReason == WSType::br) { |
|
5546 // startpoint is just before a break. lets adjust it to after it. |
|
5547 newStartNode = nsEditor::GetNodeLocation(wsStartObj.mEndReasonNode, |
|
5548 &newStartOffset); |
|
5549 ++newStartOffset; // offset *after* break |
|
5550 } |
|
5551 } |
|
5552 |
|
5553 // there is a demented possiblity we have to check for. We might have a very strange selection |
|
5554 // that is not collapsed and yet does not contain any editable content, and satisfies some of the |
|
5555 // above conditions that cause tweaking. In this case we don't want to tweak the selection into |
|
5556 // a block it was never in, etc. There are a variety of strategies one might use to try to |
|
5557 // detect these cases, but I think the most straightforward is to see if the adjusted locations |
|
5558 // "cross" the old values: ie, new end before old start, or new start after old end. If so |
|
5559 // then just leave things alone. |
|
5560 |
|
5561 int16_t comp; |
|
5562 comp = nsContentUtils::ComparePoints(startNode, startOffset, |
|
5563 newEndNode, newEndOffset); |
|
5564 if (comp == 1) return NS_OK; // new end before old start |
|
5565 comp = nsContentUtils::ComparePoints(newStartNode, newStartOffset, |
|
5566 endNode, endOffset); |
|
5567 if (comp == 1) return NS_OK; // new start after old end |
|
5568 |
|
5569 // otherwise set selection to new values. |
|
5570 inSelection->Collapse(newStartNode, newStartOffset); |
|
5571 inSelection->Extend(newEndNode, newEndOffset); |
|
5572 return NS_OK; |
|
5573 } |
|
5574 |
|
5575 |
|
5576 /////////////////////////////////////////////////////////////////////////// |
|
5577 // GetPromotedPoint: figure out where a start or end point for a block |
|
5578 // operation really is |
|
5579 void |
|
5580 nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode* aNode, |
|
5581 int32_t aOffset, |
|
5582 EditAction actionID, |
|
5583 nsCOMPtr<nsIDOMNode>* outNode, |
|
5584 int32_t* outOffset) |
|
5585 { |
|
5586 nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
|
5587 MOZ_ASSERT(node && outNode && outOffset); |
|
5588 |
|
5589 // default values |
|
5590 *outNode = node->AsDOMNode(); |
|
5591 *outOffset = aOffset; |
|
5592 |
|
5593 // we do one thing for text actions, something else entirely for other |
|
5594 // actions |
|
5595 if (actionID == EditAction::insertText || |
|
5596 actionID == EditAction::insertIMEText || |
|
5597 actionID == EditAction::insertBreak || |
|
5598 actionID == EditAction::deleteText) { |
|
5599 bool isSpace, isNBSP; |
|
5600 nsCOMPtr<nsIContent> content = do_QueryInterface(node), temp; |
|
5601 // for text actions, we want to look backwards (or forwards, as |
|
5602 // appropriate) for additional whitespace or nbsp's. We may have to act on |
|
5603 // these later even though they are outside of the initial selection. Even |
|
5604 // if they are in another node! |
|
5605 while (content) { |
|
5606 int32_t offset; |
|
5607 if (aWhere == kStart) { |
|
5608 NS_ENSURE_TRUE(mHTMLEditor, /* void */); |
|
5609 mHTMLEditor->IsPrevCharInNodeWhitespace(content, *outOffset, |
|
5610 &isSpace, &isNBSP, |
|
5611 getter_AddRefs(temp), &offset); |
|
5612 } else { |
|
5613 NS_ENSURE_TRUE(mHTMLEditor, /* void */); |
|
5614 mHTMLEditor->IsNextCharInNodeWhitespace(content, *outOffset, |
|
5615 &isSpace, &isNBSP, |
|
5616 getter_AddRefs(temp), &offset); |
|
5617 } |
|
5618 if (isSpace || isNBSP) { |
|
5619 content = temp; |
|
5620 *outOffset = offset; |
|
5621 } else { |
|
5622 break; |
|
5623 } |
|
5624 } |
|
5625 |
|
5626 *outNode = content->AsDOMNode(); |
|
5627 return; |
|
5628 } |
|
5629 |
|
5630 int32_t offset = aOffset; |
|
5631 |
|
5632 // else not a text section. In this case we want to see if we should grab |
|
5633 // any adjacent inline nodes and/or parents and other ancestors |
|
5634 if (aWhere == kStart) { |
|
5635 // some special casing for text nodes |
|
5636 if (node->IsNodeOfType(nsINode::eTEXT)) { |
|
5637 if (!node->GetParentNode()) { |
|
5638 // Okay, can't promote any further |
|
5639 return; |
|
5640 } |
|
5641 offset = node->GetParentNode()->IndexOf(node); |
|
5642 node = node->GetParentNode(); |
|
5643 } |
|
5644 |
|
5645 // look back through any further inline nodes that aren't across a <br> |
|
5646 // from us, and that are enclosed in the same block. |
|
5647 NS_ENSURE_TRUE(mHTMLEditor, /* void */); |
|
5648 nsCOMPtr<nsINode> priorNode = |
|
5649 mHTMLEditor->GetPriorHTMLNode(node, offset, true); |
|
5650 |
|
5651 while (priorNode && priorNode->GetParentNode() && |
|
5652 mHTMLEditor && !mHTMLEditor->IsVisBreak(priorNode->AsDOMNode()) && |
|
5653 !IsBlockNode(priorNode->AsDOMNode())) { |
|
5654 offset = priorNode->GetParentNode()->IndexOf(priorNode); |
|
5655 node = priorNode->GetParentNode(); |
|
5656 NS_ENSURE_TRUE(mHTMLEditor, /* void */); |
|
5657 priorNode = mHTMLEditor->GetPriorHTMLNode(node, offset, true); |
|
5658 } |
|
5659 |
|
5660 // finding the real start for this point. look up the tree for as long as |
|
5661 // we are the first node in the container, and as long as we haven't hit |
|
5662 // the body node. |
|
5663 NS_ENSURE_TRUE(mHTMLEditor, /* void */); |
|
5664 nsCOMPtr<nsIContent> nearNode = |
|
5665 mHTMLEditor->GetPriorHTMLNode(node, offset, true); |
|
5666 while (!nearNode && node->Tag() != nsGkAtoms::body && |
|
5667 node->GetParentNode()) { |
|
5668 // some cutoffs are here: we don't need to also include them in the |
|
5669 // aWhere == kEnd case. as long as they are in one or the other it will |
|
5670 // work. special case for outdent: don't keep looking up if we have |
|
5671 // found a blockquote element to act on |
|
5672 if (actionID == EditAction::outdent && |
|
5673 node->Tag() == nsGkAtoms::blockquote) { |
|
5674 break; |
|
5675 } |
|
5676 |
|
5677 int32_t parentOffset = node->GetParentNode()->IndexOf(node); |
|
5678 nsCOMPtr<nsINode> parent = node->GetParentNode(); |
|
5679 |
|
5680 // Don't walk past the editable section. Note that we need to check |
|
5681 // before walking up to a parent because we need to return the parent |
|
5682 // object, so the parent itself might not be in the editable area, but |
|
5683 // it's OK if we're not performing a block-level action. |
|
5684 bool blockLevelAction = actionID == EditAction::indent || |
|
5685 actionID == EditAction::outdent || |
|
5686 actionID == EditAction::align || |
|
5687 actionID == EditAction::makeBasicBlock; |
|
5688 NS_ENSURE_TRUE(mHTMLEditor, /* void */); |
|
5689 if (!mHTMLEditor->IsDescendantOfEditorRoot(parent) && |
|
5690 (blockLevelAction || !mHTMLEditor || |
|
5691 !mHTMLEditor->IsDescendantOfEditorRoot(node))) { |
|
5692 NS_ENSURE_TRUE(mHTMLEditor, /* void */); |
|
5693 break; |
|
5694 } |
|
5695 |
|
5696 node = parent; |
|
5697 offset = parentOffset; |
|
5698 NS_ENSURE_TRUE(mHTMLEditor, /* void */); |
|
5699 nearNode = mHTMLEditor->GetPriorHTMLNode(node, offset, true); |
|
5700 } |
|
5701 *outNode = node->AsDOMNode(); |
|
5702 *outOffset = offset; |
|
5703 return; |
|
5704 } |
|
5705 |
|
5706 // aWhere == kEnd |
|
5707 // some special casing for text nodes |
|
5708 if (node->IsNodeOfType(nsINode::eTEXT)) { |
|
5709 if (!node->GetParentNode()) { |
|
5710 // Okay, can't promote any further |
|
5711 return; |
|
5712 } |
|
5713 // want to be after the text node |
|
5714 offset = 1 + node->GetParentNode()->IndexOf(node); |
|
5715 node = node->GetParentNode(); |
|
5716 } |
|
5717 |
|
5718 // look ahead through any further inline nodes that aren't across a <br> from |
|
5719 // us, and that are enclosed in the same block. |
|
5720 NS_ENSURE_TRUE(mHTMLEditor, /* void */); |
|
5721 nsCOMPtr<nsIContent> nextNode = |
|
5722 mHTMLEditor->GetNextHTMLNode(node, offset, true); |
|
5723 |
|
5724 while (nextNode && !IsBlockNode(nextNode->AsDOMNode()) && |
|
5725 nextNode->GetParentNode()) { |
|
5726 offset = 1 + nextNode->GetParentNode()->IndexOf(nextNode); |
|
5727 node = nextNode->GetParentNode(); |
|
5728 NS_ENSURE_TRUE(mHTMLEditor, /* void */); |
|
5729 if (mHTMLEditor->IsVisBreak(nextNode->AsDOMNode())) { |
|
5730 break; |
|
5731 } |
|
5732 NS_ENSURE_TRUE(mHTMLEditor, /* void */); |
|
5733 nextNode = mHTMLEditor->GetNextHTMLNode(node, offset, true); |
|
5734 } |
|
5735 |
|
5736 // finding the real end for this point. look up the tree for as long as we |
|
5737 // are the last node in the container, and as long as we haven't hit the body |
|
5738 // node. |
|
5739 NS_ENSURE_TRUE(mHTMLEditor, /* void */); |
|
5740 nsCOMPtr<nsIContent> nearNode = |
|
5741 mHTMLEditor->GetNextHTMLNode(node, offset, true); |
|
5742 while (!nearNode && node->Tag() != nsGkAtoms::body && |
|
5743 node->GetParentNode()) { |
|
5744 int32_t parentOffset = node->GetParentNode()->IndexOf(node); |
|
5745 nsCOMPtr<nsINode> parent = node->GetParentNode(); |
|
5746 |
|
5747 // Don't walk past the editable section. Note that we need to check before |
|
5748 // walking up to a parent because we need to return the parent object, so |
|
5749 // the parent itself might not be in the editable area, but it's OK. |
|
5750 if ((!mHTMLEditor || !mHTMLEditor->IsDescendantOfEditorRoot(node)) && |
|
5751 (!mHTMLEditor || !mHTMLEditor->IsDescendantOfEditorRoot(parent))) { |
|
5752 NS_ENSURE_TRUE(mHTMLEditor, /* void */); |
|
5753 break; |
|
5754 } |
|
5755 |
|
5756 node = parent; |
|
5757 // we want to be AFTER nearNode |
|
5758 offset = parentOffset + 1; |
|
5759 NS_ENSURE_TRUE(mHTMLEditor, /* void */); |
|
5760 nearNode = mHTMLEditor->GetNextHTMLNode(node, offset, true); |
|
5761 } |
|
5762 *outNode = node->AsDOMNode(); |
|
5763 *outOffset = offset; |
|
5764 } |
|
5765 |
|
5766 |
|
5767 /////////////////////////////////////////////////////////////////////////// |
|
5768 // GetPromotedRanges: run all the selection range endpoint through |
|
5769 // GetPromotedPoint() |
|
5770 // |
|
5771 nsresult |
|
5772 nsHTMLEditRules::GetPromotedRanges(nsISelection *inSelection, |
|
5773 nsCOMArray<nsIDOMRange> &outArrayOfRanges, |
|
5774 EditAction inOperationType) |
|
5775 { |
|
5776 NS_ENSURE_TRUE(inSelection, NS_ERROR_NULL_POINTER); |
|
5777 |
|
5778 int32_t rangeCount; |
|
5779 nsresult res = inSelection->GetRangeCount(&rangeCount); |
|
5780 NS_ENSURE_SUCCESS(res, res); |
|
5781 |
|
5782 int32_t i; |
|
5783 nsCOMPtr<nsIDOMRange> selectionRange; |
|
5784 nsCOMPtr<nsIDOMRange> opRange; |
|
5785 |
|
5786 for (i = 0; i < rangeCount; i++) |
|
5787 { |
|
5788 res = inSelection->GetRangeAt(i, getter_AddRefs(selectionRange)); |
|
5789 NS_ENSURE_SUCCESS(res, res); |
|
5790 |
|
5791 // clone range so we don't muck with actual selection ranges |
|
5792 res = selectionRange->CloneRange(getter_AddRefs(opRange)); |
|
5793 NS_ENSURE_SUCCESS(res, res); |
|
5794 |
|
5795 // make a new adjusted range to represent the appropriate block content. |
|
5796 // The basic idea is to push out the range endpoints |
|
5797 // to truly enclose the blocks that we will affect. |
|
5798 // This call alters opRange. |
|
5799 res = PromoteRange(opRange, inOperationType); |
|
5800 NS_ENSURE_SUCCESS(res, res); |
|
5801 |
|
5802 // stuff new opRange into array |
|
5803 outArrayOfRanges.AppendObject(opRange); |
|
5804 } |
|
5805 return res; |
|
5806 } |
|
5807 |
|
5808 |
|
5809 /////////////////////////////////////////////////////////////////////////// |
|
5810 // PromoteRange: expand a range to include any parents for which all |
|
5811 // editable children are already in range. |
|
5812 // |
|
5813 nsresult |
|
5814 nsHTMLEditRules::PromoteRange(nsIDOMRange *inRange, |
|
5815 EditAction inOperationType) |
|
5816 { |
|
5817 NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER); |
|
5818 nsresult res; |
|
5819 nsCOMPtr<nsIDOMNode> startNode, endNode; |
|
5820 int32_t startOffset, endOffset; |
|
5821 |
|
5822 res = inRange->GetStartContainer(getter_AddRefs(startNode)); |
|
5823 NS_ENSURE_SUCCESS(res, res); |
|
5824 res = inRange->GetStartOffset(&startOffset); |
|
5825 NS_ENSURE_SUCCESS(res, res); |
|
5826 res = inRange->GetEndContainer(getter_AddRefs(endNode)); |
|
5827 NS_ENSURE_SUCCESS(res, res); |
|
5828 res = inRange->GetEndOffset(&endOffset); |
|
5829 NS_ENSURE_SUCCESS(res, res); |
|
5830 |
|
5831 // MOOSE major hack: |
|
5832 // GetPromotedPoint doesn't really do the right thing for collapsed ranges |
|
5833 // inside block elements that contain nothing but a solo <br>. It's easier |
|
5834 // to put a workaround here than to revamp GetPromotedPoint. :-( |
|
5835 if ( (startNode == endNode) && (startOffset == endOffset)) |
|
5836 { |
|
5837 nsCOMPtr<nsIDOMNode> block; |
|
5838 if (IsBlockNode(startNode)) { |
|
5839 block = startNode; |
|
5840 } else { |
|
5841 NS_ENSURE_STATE(mHTMLEditor); |
|
5842 block = mHTMLEditor->GetBlockNodeParent(startNode); |
|
5843 } |
|
5844 if (block) |
|
5845 { |
|
5846 bool bIsEmptyNode = false; |
|
5847 // check for the editing host |
|
5848 NS_ENSURE_STATE(mHTMLEditor); |
|
5849 nsIContent *rootContent = mHTMLEditor->GetActiveEditingHost(); |
|
5850 nsCOMPtr<nsINode> rootNode = do_QueryInterface(rootContent); |
|
5851 nsCOMPtr<nsINode> blockNode = do_QueryInterface(block); |
|
5852 NS_ENSURE_TRUE(rootNode && blockNode, NS_ERROR_UNEXPECTED); |
|
5853 // Make sure we don't go higher than our root element in the content tree |
|
5854 if (!nsContentUtils::ContentIsDescendantOf(rootNode, blockNode)) |
|
5855 { |
|
5856 NS_ENSURE_STATE(mHTMLEditor); |
|
5857 res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, true, false); |
|
5858 } |
|
5859 if (bIsEmptyNode) |
|
5860 { |
|
5861 uint32_t numChildren; |
|
5862 nsEditor::GetLengthOfDOMNode(block, numChildren); |
|
5863 startNode = block; |
|
5864 endNode = block; |
|
5865 startOffset = 0; |
|
5866 endOffset = numChildren; |
|
5867 } |
|
5868 } |
|
5869 } |
|
5870 |
|
5871 // make a new adjusted range to represent the appropriate block content. |
|
5872 // this is tricky. the basic idea is to push out the range endpoints |
|
5873 // to truly enclose the blocks that we will affect |
|
5874 |
|
5875 nsCOMPtr<nsIDOMNode> opStartNode; |
|
5876 nsCOMPtr<nsIDOMNode> opEndNode; |
|
5877 int32_t opStartOffset, opEndOffset; |
|
5878 nsCOMPtr<nsIDOMRange> opRange; |
|
5879 |
|
5880 GetPromotedPoint(kStart, startNode, startOffset, inOperationType, |
|
5881 address_of(opStartNode), &opStartOffset); |
|
5882 GetPromotedPoint(kEnd, endNode, endOffset, inOperationType, |
|
5883 address_of(opEndNode), &opEndOffset); |
|
5884 |
|
5885 // Make sure that the new range ends up to be in the editable section. |
|
5886 NS_ENSURE_STATE(mHTMLEditor); |
|
5887 if (!mHTMLEditor->IsDescendantOfEditorRoot(nsEditor::GetNodeAtRangeOffsetPoint(opStartNode, opStartOffset)) || |
|
5888 !mHTMLEditor || // Check again, since it may have gone away |
|
5889 !mHTMLEditor->IsDescendantOfEditorRoot(nsEditor::GetNodeAtRangeOffsetPoint(opEndNode, opEndOffset - 1))) { |
|
5890 NS_ENSURE_STATE(mHTMLEditor); |
|
5891 return NS_OK; |
|
5892 } |
|
5893 |
|
5894 res = inRange->SetStart(opStartNode, opStartOffset); |
|
5895 NS_ENSURE_SUCCESS(res, res); |
|
5896 res = inRange->SetEnd(opEndNode, opEndOffset); |
|
5897 return res; |
|
5898 } |
|
5899 |
|
5900 class nsUniqueFunctor : public nsBoolDomIterFunctor |
|
5901 { |
|
5902 public: |
|
5903 nsUniqueFunctor(nsCOMArray<nsIDOMNode> &aArray) : mArray(aArray) |
|
5904 { |
|
5905 } |
|
5906 virtual bool operator()(nsIDOMNode* aNode) // used to build list of all nodes iterator covers |
|
5907 { |
|
5908 return mArray.IndexOf(aNode) < 0; |
|
5909 } |
|
5910 |
|
5911 private: |
|
5912 nsCOMArray<nsIDOMNode> &mArray; |
|
5913 }; |
|
5914 |
|
5915 /////////////////////////////////////////////////////////////////////////// |
|
5916 // GetNodesForOperation: run through the ranges in the array and construct |
|
5917 // a new array of nodes to be acted on. |
|
5918 // |
|
5919 nsresult |
|
5920 nsHTMLEditRules::GetNodesForOperation(nsCOMArray<nsIDOMRange>& inArrayOfRanges, |
|
5921 nsCOMArray<nsIDOMNode>& outArrayOfNodes, |
|
5922 EditAction inOperationType, |
|
5923 bool aDontTouchContent) |
|
5924 { |
|
5925 int32_t rangeCount = inArrayOfRanges.Count(); |
|
5926 |
|
5927 int32_t i; |
|
5928 nsCOMPtr<nsIDOMRange> opRange; |
|
5929 |
|
5930 nsresult res = NS_OK; |
|
5931 |
|
5932 // bust up any inlines that cross our range endpoints, |
|
5933 // but only if we are allowed to touch content. |
|
5934 |
|
5935 if (!aDontTouchContent) |
|
5936 { |
|
5937 nsTArray<nsRefPtr<nsRangeStore> > rangeItemArray; |
|
5938 if (!rangeItemArray.AppendElements(rangeCount)) { |
|
5939 return NS_ERROR_OUT_OF_MEMORY; |
|
5940 } |
|
5941 |
|
5942 NS_ASSERTION(static_cast<uint32_t>(rangeCount) == rangeItemArray.Length(), |
|
5943 "How did that happen?"); |
|
5944 |
|
5945 // first register ranges for special editor gravity |
|
5946 for (i = 0; i < rangeCount; i++) |
|
5947 { |
|
5948 opRange = inArrayOfRanges[0]; |
|
5949 rangeItemArray[i] = new nsRangeStore(); |
|
5950 rangeItemArray[i]->StoreRange(opRange); |
|
5951 NS_ENSURE_STATE(mHTMLEditor); |
|
5952 mHTMLEditor->mRangeUpdater.RegisterRangeItem(rangeItemArray[i]); |
|
5953 inArrayOfRanges.RemoveObjectAt(0); |
|
5954 } |
|
5955 // now bust up inlines. Safe to start at rangeCount-1, since we |
|
5956 // asserted we have enough items above. |
|
5957 for (i = rangeCount-1; i >= 0 && NS_SUCCEEDED(res); i--) |
|
5958 { |
|
5959 res = BustUpInlinesAtRangeEndpoints(*rangeItemArray[i]); |
|
5960 } |
|
5961 // then unregister the ranges |
|
5962 for (i = 0; i < rangeCount; i++) |
|
5963 { |
|
5964 nsRangeStore* item = rangeItemArray[i]; |
|
5965 NS_ENSURE_STATE(mHTMLEditor); |
|
5966 mHTMLEditor->mRangeUpdater.DropRangeItem(item); |
|
5967 nsRefPtr<nsRange> range; |
|
5968 nsresult res2 = item->GetRange(getter_AddRefs(range)); |
|
5969 opRange = range; |
|
5970 if (NS_FAILED(res2) && NS_SUCCEEDED(res)) { |
|
5971 // Remember the failure, but keep going so we make sure to unregister |
|
5972 // all our range items. |
|
5973 res = res2; |
|
5974 } |
|
5975 inArrayOfRanges.AppendObject(opRange); |
|
5976 } |
|
5977 NS_ENSURE_SUCCESS(res, res); |
|
5978 } |
|
5979 // gather up a list of all the nodes |
|
5980 for (i = 0; i < rangeCount; i++) |
|
5981 { |
|
5982 opRange = inArrayOfRanges[i]; |
|
5983 |
|
5984 nsDOMSubtreeIterator iter; |
|
5985 res = iter.Init(opRange); |
|
5986 NS_ENSURE_SUCCESS(res, res); |
|
5987 if (outArrayOfNodes.Count() == 0) { |
|
5988 nsTrivialFunctor functor; |
|
5989 res = iter.AppendList(functor, outArrayOfNodes); |
|
5990 NS_ENSURE_SUCCESS(res, res); |
|
5991 } |
|
5992 else { |
|
5993 // We don't want duplicates in outArrayOfNodes, so we use an |
|
5994 // iterator/functor that only return nodes that are not already in |
|
5995 // outArrayOfNodes. |
|
5996 nsCOMArray<nsIDOMNode> nodes; |
|
5997 nsUniqueFunctor functor(outArrayOfNodes); |
|
5998 res = iter.AppendList(functor, nodes); |
|
5999 NS_ENSURE_SUCCESS(res, res); |
|
6000 if (!outArrayOfNodes.AppendObjects(nodes)) |
|
6001 return NS_ERROR_OUT_OF_MEMORY; |
|
6002 } |
|
6003 } |
|
6004 |
|
6005 // certain operations should not act on li's and td's, but rather inside |
|
6006 // them. alter the list as needed |
|
6007 if (inOperationType == EditAction::makeBasicBlock) { |
|
6008 int32_t listCount = outArrayOfNodes.Count(); |
|
6009 for (i=listCount-1; i>=0; i--) |
|
6010 { |
|
6011 nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i]; |
|
6012 if (nsHTMLEditUtils::IsListItem(node)) |
|
6013 { |
|
6014 int32_t j=i; |
|
6015 outArrayOfNodes.RemoveObjectAt(i); |
|
6016 res = GetInnerContent(node, outArrayOfNodes, &j); |
|
6017 NS_ENSURE_SUCCESS(res, res); |
|
6018 } |
|
6019 } |
|
6020 } |
|
6021 // indent/outdent already do something special for list items, but |
|
6022 // we still need to make sure we don't act on table elements |
|
6023 else if (inOperationType == EditAction::outdent || |
|
6024 inOperationType == EditAction::indent || |
|
6025 inOperationType == EditAction::setAbsolutePosition) { |
|
6026 int32_t listCount = outArrayOfNodes.Count(); |
|
6027 for (i=listCount-1; i>=0; i--) |
|
6028 { |
|
6029 nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i]; |
|
6030 if (nsHTMLEditUtils::IsTableElementButNotTable(node)) |
|
6031 { |
|
6032 int32_t j=i; |
|
6033 outArrayOfNodes.RemoveObjectAt(i); |
|
6034 res = GetInnerContent(node, outArrayOfNodes, &j); |
|
6035 NS_ENSURE_SUCCESS(res, res); |
|
6036 } |
|
6037 } |
|
6038 } |
|
6039 // outdent should look inside of divs. |
|
6040 if (inOperationType == EditAction::outdent && |
|
6041 (!mHTMLEditor || !mHTMLEditor->IsCSSEnabled())) { |
|
6042 NS_ENSURE_STATE(mHTMLEditor); |
|
6043 int32_t listCount = outArrayOfNodes.Count(); |
|
6044 for (i=listCount-1; i>=0; i--) |
|
6045 { |
|
6046 nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i]; |
|
6047 if (nsHTMLEditUtils::IsDiv(node)) |
|
6048 { |
|
6049 int32_t j=i; |
|
6050 outArrayOfNodes.RemoveObjectAt(i); |
|
6051 res = GetInnerContent(node, outArrayOfNodes, &j, false, false); |
|
6052 NS_ENSURE_SUCCESS(res, res); |
|
6053 } |
|
6054 } |
|
6055 } |
|
6056 |
|
6057 |
|
6058 // post process the list to break up inline containers that contain br's. |
|
6059 // but only for operations that might care, like making lists or para's... |
|
6060 if (inOperationType == EditAction::makeBasicBlock || |
|
6061 inOperationType == EditAction::makeList || |
|
6062 inOperationType == EditAction::align || |
|
6063 inOperationType == EditAction::setAbsolutePosition || |
|
6064 inOperationType == EditAction::indent || |
|
6065 inOperationType == EditAction::outdent) { |
|
6066 int32_t listCount = outArrayOfNodes.Count(); |
|
6067 for (i=listCount-1; i>=0; i--) |
|
6068 { |
|
6069 nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i]; |
|
6070 if (!aDontTouchContent && IsInlineNode(node) && |
|
6071 (!mHTMLEditor || mHTMLEditor->IsContainer(node)) && |
|
6072 (!mHTMLEditor || !mHTMLEditor->IsTextNode(node))) |
|
6073 { |
|
6074 NS_ENSURE_STATE(mHTMLEditor); |
|
6075 nsCOMArray<nsIDOMNode> arrayOfInlines; |
|
6076 res = BustUpInlinesAtBRs(node, arrayOfInlines); |
|
6077 NS_ENSURE_SUCCESS(res, res); |
|
6078 // put these nodes in outArrayOfNodes, replacing the current node |
|
6079 outArrayOfNodes.RemoveObjectAt(i); |
|
6080 outArrayOfNodes.InsertObjectsAt(arrayOfInlines, i); |
|
6081 } |
|
6082 } |
|
6083 } |
|
6084 return res; |
|
6085 } |
|
6086 |
|
6087 |
|
6088 |
|
6089 /////////////////////////////////////////////////////////////////////////// |
|
6090 // GetChildNodesForOperation: |
|
6091 // |
|
6092 nsresult |
|
6093 nsHTMLEditRules::GetChildNodesForOperation(nsIDOMNode *inNode, |
|
6094 nsCOMArray<nsIDOMNode>& outArrayOfNodes) |
|
6095 { |
|
6096 nsCOMPtr<nsINode> node = do_QueryInterface(inNode); |
|
6097 NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); |
|
6098 |
|
6099 for (nsIContent* child = node->GetFirstChild(); |
|
6100 child; |
|
6101 child = child->GetNextSibling()) { |
|
6102 nsIDOMNode* childNode = child->AsDOMNode(); |
|
6103 if (!outArrayOfNodes.AppendObject(childNode)) { |
|
6104 return NS_ERROR_FAILURE; |
|
6105 } |
|
6106 } |
|
6107 return NS_OK; |
|
6108 } |
|
6109 |
|
6110 |
|
6111 |
|
6112 /////////////////////////////////////////////////////////////////////////// |
|
6113 // GetListActionNodes: |
|
6114 // |
|
6115 nsresult |
|
6116 nsHTMLEditRules::GetListActionNodes(nsCOMArray<nsIDOMNode> &outArrayOfNodes, |
|
6117 bool aEntireList, |
|
6118 bool aDontTouchContent) |
|
6119 { |
|
6120 nsresult res = NS_OK; |
|
6121 |
|
6122 nsCOMPtr<nsISelection>selection; |
|
6123 NS_ENSURE_STATE(mHTMLEditor); |
|
6124 res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); |
|
6125 NS_ENSURE_SUCCESS(res, res); |
|
6126 Selection* sel = static_cast<Selection*>(selection.get()); |
|
6127 NS_ENSURE_TRUE(sel, NS_ERROR_FAILURE); |
|
6128 // added this in so that ui code can ask to change an entire list, even if selection |
|
6129 // is only in part of it. used by list item dialog. |
|
6130 if (aEntireList) |
|
6131 { |
|
6132 uint32_t rangeCount = sel->GetRangeCount(); |
|
6133 for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { |
|
6134 nsRefPtr<nsRange> range = sel->GetRangeAt(rangeIdx); |
|
6135 nsCOMPtr<nsIDOMNode> commonParent, parent, tmp; |
|
6136 range->GetCommonAncestorContainer(getter_AddRefs(commonParent)); |
|
6137 if (commonParent) |
|
6138 { |
|
6139 parent = commonParent; |
|
6140 while (parent) |
|
6141 { |
|
6142 if (nsHTMLEditUtils::IsList(parent)) |
|
6143 { |
|
6144 outArrayOfNodes.AppendObject(parent); |
|
6145 break; |
|
6146 } |
|
6147 parent->GetParentNode(getter_AddRefs(tmp)); |
|
6148 parent = tmp; |
|
6149 } |
|
6150 } |
|
6151 } |
|
6152 // if we didn't find any nodes this way, then try the normal way. perhaps the |
|
6153 // selection spans multiple lists but with no common list parent. |
|
6154 if (outArrayOfNodes.Count()) return NS_OK; |
|
6155 } |
|
6156 |
|
6157 { |
|
6158 // We don't like other people messing with our selection! |
|
6159 NS_ENSURE_STATE(mHTMLEditor); |
|
6160 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); |
|
6161 |
|
6162 // contruct a list of nodes to act on. |
|
6163 res = GetNodesFromSelection(selection, EditAction::makeList, |
|
6164 outArrayOfNodes, aDontTouchContent); |
|
6165 NS_ENSURE_SUCCESS(res, res); |
|
6166 } |
|
6167 |
|
6168 // pre process our list of nodes... |
|
6169 int32_t listCount = outArrayOfNodes.Count(); |
|
6170 int32_t i; |
|
6171 for (i=listCount-1; i>=0; i--) |
|
6172 { |
|
6173 nsCOMPtr<nsIDOMNode> testNode = outArrayOfNodes[i]; |
|
6174 |
|
6175 // Remove all non-editable nodes. Leave them be. |
|
6176 NS_ENSURE_STATE(mHTMLEditor); |
|
6177 if (!mHTMLEditor->IsEditable(testNode)) |
|
6178 { |
|
6179 outArrayOfNodes.RemoveObjectAt(i); |
|
6180 } |
|
6181 |
|
6182 // scan for table elements and divs. If we find table elements other than table, |
|
6183 // replace it with a list of any editable non-table content. |
|
6184 if (nsHTMLEditUtils::IsTableElementButNotTable(testNode)) |
|
6185 { |
|
6186 int32_t j=i; |
|
6187 outArrayOfNodes.RemoveObjectAt(i); |
|
6188 res = GetInnerContent(testNode, outArrayOfNodes, &j, false); |
|
6189 NS_ENSURE_SUCCESS(res, res); |
|
6190 } |
|
6191 } |
|
6192 |
|
6193 // if there is only one node in the array, and it is a list, div, or blockquote, |
|
6194 // then look inside of it until we find inner list or content. |
|
6195 res = LookInsideDivBQandList(outArrayOfNodes); |
|
6196 return res; |
|
6197 } |
|
6198 |
|
6199 |
|
6200 /////////////////////////////////////////////////////////////////////////// |
|
6201 // LookInsideDivBQandList: |
|
6202 // |
|
6203 nsresult |
|
6204 nsHTMLEditRules::LookInsideDivBQandList(nsCOMArray<nsIDOMNode>& aNodeArray) |
|
6205 { |
|
6206 // if there is only one node in the array, and it is a list, div, or blockquote, |
|
6207 // then look inside of it until we find inner list or content. |
|
6208 int32_t listCount = aNodeArray.Count(); |
|
6209 if (listCount != 1) { |
|
6210 return NS_OK; |
|
6211 } |
|
6212 |
|
6213 nsCOMPtr<nsINode> curNode = do_QueryInterface(aNodeArray[0]); |
|
6214 NS_ENSURE_STATE(curNode); |
|
6215 |
|
6216 while (curNode->IsElement() && |
|
6217 (curNode->AsElement()->IsHTML(nsGkAtoms::div) || |
|
6218 nsHTMLEditUtils::IsList(curNode) || |
|
6219 curNode->AsElement()->IsHTML(nsGkAtoms::blockquote))) { |
|
6220 // dive as long as there is only one child, and it is a list, div, blockquote |
|
6221 NS_ENSURE_STATE(mHTMLEditor); |
|
6222 uint32_t numChildren = mHTMLEditor->CountEditableChildren(curNode); |
|
6223 if (numChildren != 1) { |
|
6224 break; |
|
6225 } |
|
6226 |
|
6227 // keep diving |
|
6228 // XXX One would expect to dive into the one editable node. |
|
6229 nsIContent* tmp = curNode->GetFirstChild(); |
|
6230 if (!tmp->IsElement()) { |
|
6231 break; |
|
6232 } |
|
6233 |
|
6234 dom::Element* element = tmp->AsElement(); |
|
6235 if (!element->IsHTML(nsGkAtoms::div) && |
|
6236 !nsHTMLEditUtils::IsList(element) && |
|
6237 !element->IsHTML(nsGkAtoms::blockquote)) { |
|
6238 break; |
|
6239 } |
|
6240 |
|
6241 // check editablility XXX floppy moose |
|
6242 curNode = tmp; |
|
6243 } |
|
6244 |
|
6245 // we've found innermost list/blockquote/div: |
|
6246 // replace the one node in the array with these nodes |
|
6247 aNodeArray.RemoveObjectAt(0); |
|
6248 if (curNode->IsElement() && |
|
6249 (curNode->AsElement()->IsHTML(nsGkAtoms::div) || |
|
6250 curNode->AsElement()->IsHTML(nsGkAtoms::blockquote))) { |
|
6251 int32_t j = 0; |
|
6252 return GetInnerContent(curNode->AsDOMNode(), aNodeArray, &j, false, false); |
|
6253 } |
|
6254 |
|
6255 aNodeArray.AppendObject(curNode->AsDOMNode()); |
|
6256 return NS_OK; |
|
6257 } |
|
6258 |
|
6259 |
|
6260 /////////////////////////////////////////////////////////////////////////// |
|
6261 // GetDefinitionListItemTypes: |
|
6262 // |
|
6263 void |
|
6264 nsHTMLEditRules::GetDefinitionListItemTypes(dom::Element* aElement, bool* aDT, bool* aDD) |
|
6265 { |
|
6266 MOZ_ASSERT(aElement); |
|
6267 MOZ_ASSERT(aElement->IsHTML(nsGkAtoms::dl)); |
|
6268 MOZ_ASSERT(aDT); |
|
6269 MOZ_ASSERT(aDD); |
|
6270 |
|
6271 *aDT = *aDD = false; |
|
6272 for (nsIContent* child = aElement->GetFirstChild(); |
|
6273 child; |
|
6274 child = child->GetNextSibling()) { |
|
6275 if (child->IsHTML(nsGkAtoms::dt)) { |
|
6276 *aDT = true; |
|
6277 } else if (child->IsHTML(nsGkAtoms::dd)) { |
|
6278 *aDD = true; |
|
6279 } |
|
6280 } |
|
6281 } |
|
6282 |
|
6283 /////////////////////////////////////////////////////////////////////////// |
|
6284 // GetParagraphFormatNodes: |
|
6285 // |
|
6286 nsresult |
|
6287 nsHTMLEditRules::GetParagraphFormatNodes(nsCOMArray<nsIDOMNode>& outArrayOfNodes, |
|
6288 bool aDontTouchContent) |
|
6289 { |
|
6290 nsCOMPtr<nsISelection>selection; |
|
6291 NS_ENSURE_STATE(mHTMLEditor); |
|
6292 nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); |
|
6293 NS_ENSURE_SUCCESS(res, res); |
|
6294 |
|
6295 // contruct a list of nodes to act on. |
|
6296 res = GetNodesFromSelection(selection, EditAction::makeBasicBlock, |
|
6297 outArrayOfNodes, aDontTouchContent); |
|
6298 NS_ENSURE_SUCCESS(res, res); |
|
6299 |
|
6300 // pre process our list of nodes... |
|
6301 int32_t listCount = outArrayOfNodes.Count(); |
|
6302 int32_t i; |
|
6303 for (i=listCount-1; i>=0; i--) |
|
6304 { |
|
6305 nsCOMPtr<nsIDOMNode> testNode = outArrayOfNodes[i]; |
|
6306 |
|
6307 // Remove all non-editable nodes. Leave them be. |
|
6308 NS_ENSURE_STATE(mHTMLEditor); |
|
6309 if (!mHTMLEditor->IsEditable(testNode)) |
|
6310 { |
|
6311 outArrayOfNodes.RemoveObjectAt(i); |
|
6312 } |
|
6313 |
|
6314 // scan for table elements. If we find table elements other than table, |
|
6315 // replace it with a list of any editable non-table content. Ditto for list elements. |
|
6316 if (nsHTMLEditUtils::IsTableElement(testNode) || |
|
6317 nsHTMLEditUtils::IsList(testNode) || |
|
6318 nsHTMLEditUtils::IsListItem(testNode) ) |
|
6319 { |
|
6320 int32_t j=i; |
|
6321 outArrayOfNodes.RemoveObjectAt(i); |
|
6322 res = GetInnerContent(testNode, outArrayOfNodes, &j); |
|
6323 NS_ENSURE_SUCCESS(res, res); |
|
6324 } |
|
6325 } |
|
6326 return res; |
|
6327 } |
|
6328 |
|
6329 |
|
6330 /////////////////////////////////////////////////////////////////////////// |
|
6331 // BustUpInlinesAtRangeEndpoints: |
|
6332 // |
|
6333 nsresult |
|
6334 nsHTMLEditRules::BustUpInlinesAtRangeEndpoints(nsRangeStore &item) |
|
6335 { |
|
6336 nsresult res = NS_OK; |
|
6337 bool isCollapsed = ((item.startNode == item.endNode) && (item.startOffset == item.endOffset)); |
|
6338 |
|
6339 nsCOMPtr<nsIDOMNode> endInline = GetHighestInlineParent(item.endNode); |
|
6340 |
|
6341 // if we have inline parents above range endpoints, split them |
|
6342 if (endInline && !isCollapsed) |
|
6343 { |
|
6344 nsCOMPtr<nsIDOMNode> resultEndNode; |
|
6345 int32_t resultEndOffset; |
|
6346 endInline->GetParentNode(getter_AddRefs(resultEndNode)); |
|
6347 NS_ENSURE_STATE(mHTMLEditor); |
|
6348 res = mHTMLEditor->SplitNodeDeep(endInline, item.endNode, item.endOffset, |
|
6349 &resultEndOffset, true); |
|
6350 NS_ENSURE_SUCCESS(res, res); |
|
6351 // reset range |
|
6352 item.endNode = resultEndNode; item.endOffset = resultEndOffset; |
|
6353 } |
|
6354 |
|
6355 nsCOMPtr<nsIDOMNode> startInline = GetHighestInlineParent(item.startNode); |
|
6356 |
|
6357 if (startInline) |
|
6358 { |
|
6359 nsCOMPtr<nsIDOMNode> resultStartNode; |
|
6360 int32_t resultStartOffset; |
|
6361 startInline->GetParentNode(getter_AddRefs(resultStartNode)); |
|
6362 NS_ENSURE_STATE(mHTMLEditor); |
|
6363 res = mHTMLEditor->SplitNodeDeep(startInline, item.startNode, item.startOffset, |
|
6364 &resultStartOffset, true); |
|
6365 NS_ENSURE_SUCCESS(res, res); |
|
6366 // reset range |
|
6367 item.startNode = resultStartNode; item.startOffset = resultStartOffset; |
|
6368 } |
|
6369 |
|
6370 return res; |
|
6371 } |
|
6372 |
|
6373 |
|
6374 |
|
6375 /////////////////////////////////////////////////////////////////////////// |
|
6376 // BustUpInlinesAtBRs: |
|
6377 // |
|
6378 nsresult |
|
6379 nsHTMLEditRules::BustUpInlinesAtBRs(nsIDOMNode *inNode, |
|
6380 nsCOMArray<nsIDOMNode>& outArrayOfNodes) |
|
6381 { |
|
6382 NS_ENSURE_TRUE(inNode, NS_ERROR_NULL_POINTER); |
|
6383 |
|
6384 // first step is to build up a list of all the break nodes inside |
|
6385 // the inline container. |
|
6386 nsCOMArray<nsIDOMNode> arrayOfBreaks; |
|
6387 nsBRNodeFunctor functor; |
|
6388 nsDOMIterator iter; |
|
6389 nsresult res = iter.Init(inNode); |
|
6390 NS_ENSURE_SUCCESS(res, res); |
|
6391 res = iter.AppendList(functor, arrayOfBreaks); |
|
6392 NS_ENSURE_SUCCESS(res, res); |
|
6393 |
|
6394 // if there aren't any breaks, just put inNode itself in the array |
|
6395 int32_t listCount = arrayOfBreaks.Count(); |
|
6396 if (!listCount) |
|
6397 { |
|
6398 if (!outArrayOfNodes.AppendObject(inNode)) |
|
6399 return NS_ERROR_FAILURE; |
|
6400 } |
|
6401 else |
|
6402 { |
|
6403 // else we need to bust up inNode along all the breaks |
|
6404 nsCOMPtr<nsIDOMNode> breakNode; |
|
6405 nsCOMPtr<nsIDOMNode> inlineParentNode; |
|
6406 nsCOMPtr<nsIDOMNode> leftNode; |
|
6407 nsCOMPtr<nsIDOMNode> rightNode; |
|
6408 nsCOMPtr<nsIDOMNode> splitDeepNode = inNode; |
|
6409 nsCOMPtr<nsIDOMNode> splitParentNode; |
|
6410 int32_t splitOffset, resultOffset, i; |
|
6411 inNode->GetParentNode(getter_AddRefs(inlineParentNode)); |
|
6412 |
|
6413 for (i=0; i< listCount; i++) |
|
6414 { |
|
6415 breakNode = arrayOfBreaks[i]; |
|
6416 NS_ENSURE_TRUE(breakNode, NS_ERROR_NULL_POINTER); |
|
6417 NS_ENSURE_TRUE(splitDeepNode, NS_ERROR_NULL_POINTER); |
|
6418 splitParentNode = nsEditor::GetNodeLocation(breakNode, &splitOffset); |
|
6419 NS_ENSURE_STATE(mHTMLEditor); |
|
6420 res = mHTMLEditor->SplitNodeDeep(splitDeepNode, splitParentNode, splitOffset, |
|
6421 &resultOffset, false, address_of(leftNode), address_of(rightNode)); |
|
6422 NS_ENSURE_SUCCESS(res, res); |
|
6423 // put left node in node list |
|
6424 if (leftNode) |
|
6425 { |
|
6426 // might not be a left node. a break might have been at the very |
|
6427 // beginning of inline container, in which case splitnodedeep |
|
6428 // would not actually split anything |
|
6429 if (!outArrayOfNodes.AppendObject(leftNode)) |
|
6430 return NS_ERROR_FAILURE; |
|
6431 } |
|
6432 // move break outside of container and also put in node list |
|
6433 NS_ENSURE_STATE(mHTMLEditor); |
|
6434 res = mHTMLEditor->MoveNode(breakNode, inlineParentNode, resultOffset); |
|
6435 NS_ENSURE_SUCCESS(res, res); |
|
6436 if (!outArrayOfNodes.AppendObject(breakNode)) |
|
6437 return NS_ERROR_FAILURE; |
|
6438 // now rightNode becomes the new node to split |
|
6439 splitDeepNode = rightNode; |
|
6440 } |
|
6441 // now tack on remaining rightNode, if any, to the list |
|
6442 if (rightNode) |
|
6443 { |
|
6444 if (!outArrayOfNodes.AppendObject(rightNode)) |
|
6445 return NS_ERROR_FAILURE; |
|
6446 } |
|
6447 } |
|
6448 return res; |
|
6449 } |
|
6450 |
|
6451 |
|
6452 nsCOMPtr<nsIDOMNode> |
|
6453 nsHTMLEditRules::GetHighestInlineParent(nsIDOMNode* aNode) |
|
6454 { |
|
6455 NS_ENSURE_TRUE(aNode, nullptr); |
|
6456 if (IsBlockNode(aNode)) return nullptr; |
|
6457 nsCOMPtr<nsIDOMNode> inlineNode, node=aNode; |
|
6458 |
|
6459 while (node && IsInlineNode(node)) |
|
6460 { |
|
6461 inlineNode = node; |
|
6462 inlineNode->GetParentNode(getter_AddRefs(node)); |
|
6463 } |
|
6464 return inlineNode; |
|
6465 } |
|
6466 |
|
6467 |
|
6468 /////////////////////////////////////////////////////////////////////////// |
|
6469 // GetNodesFromPoint: given a particular operation, construct a list |
|
6470 // of nodes from a point that will be operated on. |
|
6471 // |
|
6472 nsresult |
|
6473 nsHTMLEditRules::GetNodesFromPoint(::DOMPoint point, |
|
6474 EditAction operation, |
|
6475 nsCOMArray<nsIDOMNode> &arrayOfNodes, |
|
6476 bool dontTouchContent) |
|
6477 { |
|
6478 nsresult res; |
|
6479 |
|
6480 // get our point |
|
6481 nsCOMPtr<nsIDOMNode> node; |
|
6482 int32_t offset; |
|
6483 point.GetPoint(node, offset); |
|
6484 |
|
6485 // use it to make a range |
|
6486 nsCOMPtr<nsINode> nativeNode = do_QueryInterface(node); |
|
6487 NS_ENSURE_STATE(nativeNode); |
|
6488 nsRefPtr<nsRange> range = new nsRange(nativeNode); |
|
6489 res = range->SetStart(node, offset); |
|
6490 NS_ENSURE_SUCCESS(res, res); |
|
6491 /* SetStart() will also set the end for this new range |
|
6492 res = range->SetEnd(node, offset); |
|
6493 NS_ENSURE_SUCCESS(res, res); */ |
|
6494 |
|
6495 // expand the range to include adjacent inlines |
|
6496 res = PromoteRange(range, operation); |
|
6497 NS_ENSURE_SUCCESS(res, res); |
|
6498 |
|
6499 // make array of ranges |
|
6500 nsCOMArray<nsIDOMRange> arrayOfRanges; |
|
6501 |
|
6502 // stuff new opRange into array |
|
6503 arrayOfRanges.AppendObject(range); |
|
6504 |
|
6505 // use these ranges to contruct a list of nodes to act on. |
|
6506 res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, operation, dontTouchContent); |
|
6507 return res; |
|
6508 } |
|
6509 |
|
6510 |
|
6511 /////////////////////////////////////////////////////////////////////////// |
|
6512 // GetNodesFromSelection: given a particular operation, construct a list |
|
6513 // of nodes from the selection that will be operated on. |
|
6514 // |
|
6515 nsresult |
|
6516 nsHTMLEditRules::GetNodesFromSelection(nsISelection *selection, |
|
6517 EditAction operation, |
|
6518 nsCOMArray<nsIDOMNode>& arrayOfNodes, |
|
6519 bool dontTouchContent) |
|
6520 { |
|
6521 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
6522 nsresult res; |
|
6523 |
|
6524 // promote selection ranges |
|
6525 nsCOMArray<nsIDOMRange> arrayOfRanges; |
|
6526 res = GetPromotedRanges(selection, arrayOfRanges, operation); |
|
6527 NS_ENSURE_SUCCESS(res, res); |
|
6528 |
|
6529 // use these ranges to contruct a list of nodes to act on. |
|
6530 res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, operation, dontTouchContent); |
|
6531 return res; |
|
6532 } |
|
6533 |
|
6534 |
|
6535 /////////////////////////////////////////////////////////////////////////// |
|
6536 // MakeTransitionList: detect all the transitions in the array, where a |
|
6537 // transition means that adjacent nodes in the array |
|
6538 // don't have the same parent. |
|
6539 // |
|
6540 nsresult |
|
6541 nsHTMLEditRules::MakeTransitionList(nsCOMArray<nsIDOMNode>& inArrayOfNodes, |
|
6542 nsTArray<bool> &inTransitionArray) |
|
6543 { |
|
6544 uint32_t listCount = inArrayOfNodes.Count(); |
|
6545 inTransitionArray.EnsureLengthAtLeast(listCount); |
|
6546 uint32_t i; |
|
6547 nsCOMPtr<nsIDOMNode> prevElementParent; |
|
6548 nsCOMPtr<nsIDOMNode> curElementParent; |
|
6549 |
|
6550 for (i=0; i<listCount; i++) |
|
6551 { |
|
6552 nsIDOMNode* transNode = inArrayOfNodes[i]; |
|
6553 transNode->GetParentNode(getter_AddRefs(curElementParent)); |
|
6554 if (curElementParent != prevElementParent) |
|
6555 { |
|
6556 // different parents, or separated by <br>: transition point |
|
6557 inTransitionArray[i] = true; |
|
6558 } |
|
6559 else |
|
6560 { |
|
6561 // same parents: these nodes grew up together |
|
6562 inTransitionArray[i] = false; |
|
6563 } |
|
6564 prevElementParent = curElementParent; |
|
6565 } |
|
6566 return NS_OK; |
|
6567 } |
|
6568 |
|
6569 |
|
6570 |
|
6571 /******************************************************** |
|
6572 * main implementation methods |
|
6573 ********************************************************/ |
|
6574 |
|
6575 /////////////////////////////////////////////////////////////////////////// |
|
6576 // IsInListItem: if aNode is the descendant of a listitem, return that li. |
|
6577 // But table element boundaries are stoppers on the search. |
|
6578 // Also stops on the active editor host (contenteditable). |
|
6579 // Also test if aNode is an li itself. |
|
6580 // |
|
6581 already_AddRefed<nsIDOMNode> |
|
6582 nsHTMLEditRules::IsInListItem(nsIDOMNode* aNode) |
|
6583 { |
|
6584 nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
|
6585 nsCOMPtr<nsIDOMNode> retval = do_QueryInterface(IsInListItem(node)); |
|
6586 return retval.forget(); |
|
6587 } |
|
6588 |
|
6589 nsINode* |
|
6590 nsHTMLEditRules::IsInListItem(nsINode* aNode) |
|
6591 { |
|
6592 NS_ENSURE_TRUE(aNode, nullptr); |
|
6593 if (nsHTMLEditUtils::IsListItem(aNode)) { |
|
6594 return aNode; |
|
6595 } |
|
6596 |
|
6597 nsINode* parent = aNode->GetParentNode(); |
|
6598 while (parent && mHTMLEditor && mHTMLEditor->IsDescendantOfEditorRoot(parent) && |
|
6599 !nsHTMLEditUtils::IsTableElement(parent)) { |
|
6600 if (nsHTMLEditUtils::IsListItem(parent)) { |
|
6601 return parent; |
|
6602 } |
|
6603 parent = parent->GetParentNode(); |
|
6604 } |
|
6605 return nullptr; |
|
6606 } |
|
6607 |
|
6608 |
|
6609 /////////////////////////////////////////////////////////////////////////// |
|
6610 // ReturnInHeader: do the right thing for returns pressed in headers |
|
6611 // |
|
6612 nsresult |
|
6613 nsHTMLEditRules::ReturnInHeader(nsISelection *aSelection, |
|
6614 nsIDOMNode *aHeader, |
|
6615 nsIDOMNode *aNode, |
|
6616 int32_t aOffset) |
|
6617 { |
|
6618 NS_ENSURE_TRUE(aSelection && aHeader && aNode, NS_ERROR_NULL_POINTER); |
|
6619 |
|
6620 // remeber where the header is |
|
6621 int32_t offset; |
|
6622 nsCOMPtr<nsIDOMNode> headerParent = nsEditor::GetNodeLocation(aHeader, &offset); |
|
6623 |
|
6624 // get ws code to adjust any ws |
|
6625 nsCOMPtr<nsIDOMNode> selNode = aNode; |
|
6626 NS_ENSURE_STATE(mHTMLEditor); |
|
6627 nsresult res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, |
|
6628 address_of(selNode), |
|
6629 &aOffset); |
|
6630 NS_ENSURE_SUCCESS(res, res); |
|
6631 |
|
6632 // split the header |
|
6633 int32_t newOffset; |
|
6634 NS_ENSURE_STATE(mHTMLEditor); |
|
6635 res = mHTMLEditor->SplitNodeDeep( aHeader, selNode, aOffset, &newOffset); |
|
6636 NS_ENSURE_SUCCESS(res, res); |
|
6637 |
|
6638 // if the leftand heading is empty, put a mozbr in it |
|
6639 nsCOMPtr<nsIDOMNode> prevItem; |
|
6640 NS_ENSURE_STATE(mHTMLEditor); |
|
6641 mHTMLEditor->GetPriorHTMLSibling(aHeader, address_of(prevItem)); |
|
6642 if (prevItem && nsHTMLEditUtils::IsHeader(prevItem)) |
|
6643 { |
|
6644 bool bIsEmptyNode; |
|
6645 NS_ENSURE_STATE(mHTMLEditor); |
|
6646 res = mHTMLEditor->IsEmptyNode(prevItem, &bIsEmptyNode); |
|
6647 NS_ENSURE_SUCCESS(res, res); |
|
6648 if (bIsEmptyNode) { |
|
6649 res = CreateMozBR(prevItem, 0); |
|
6650 NS_ENSURE_SUCCESS(res, res); |
|
6651 } |
|
6652 } |
|
6653 |
|
6654 // if the new (righthand) header node is empty, delete it |
|
6655 bool isEmpty; |
|
6656 res = IsEmptyBlock(aHeader, &isEmpty, true); |
|
6657 NS_ENSURE_SUCCESS(res, res); |
|
6658 if (isEmpty) |
|
6659 { |
|
6660 NS_ENSURE_STATE(mHTMLEditor); |
|
6661 res = mHTMLEditor->DeleteNode(aHeader); |
|
6662 NS_ENSURE_SUCCESS(res, res); |
|
6663 // layout tells the caret to blink in a weird place |
|
6664 // if we don't place a break after the header. |
|
6665 nsCOMPtr<nsIDOMNode> sibling; |
|
6666 NS_ENSURE_STATE(mHTMLEditor); |
|
6667 res = mHTMLEditor->GetNextHTMLSibling(headerParent, offset+1, address_of(sibling)); |
|
6668 NS_ENSURE_SUCCESS(res, res); |
|
6669 if (!sibling || !nsTextEditUtils::IsBreak(sibling)) |
|
6670 { |
|
6671 ClearCachedStyles(); |
|
6672 NS_ENSURE_STATE(mHTMLEditor); |
|
6673 mHTMLEditor->mTypeInState->ClearAllProps(); |
|
6674 |
|
6675 // create a paragraph |
|
6676 NS_NAMED_LITERAL_STRING(pType, "p"); |
|
6677 nsCOMPtr<nsIDOMNode> pNode; |
|
6678 NS_ENSURE_STATE(mHTMLEditor); |
|
6679 res = mHTMLEditor->CreateNode(pType, headerParent, offset+1, getter_AddRefs(pNode)); |
|
6680 NS_ENSURE_SUCCESS(res, res); |
|
6681 |
|
6682 // append a <br> to it |
|
6683 nsCOMPtr<nsIDOMNode> brNode; |
|
6684 NS_ENSURE_STATE(mHTMLEditor); |
|
6685 res = mHTMLEditor->CreateBR(pNode, 0, address_of(brNode)); |
|
6686 NS_ENSURE_SUCCESS(res, res); |
|
6687 |
|
6688 // set selection to before the break |
|
6689 res = aSelection->Collapse(pNode, 0); |
|
6690 } |
|
6691 else |
|
6692 { |
|
6693 headerParent = nsEditor::GetNodeLocation(sibling, &offset); |
|
6694 // put selection after break |
|
6695 res = aSelection->Collapse(headerParent,offset+1); |
|
6696 } |
|
6697 } |
|
6698 else |
|
6699 { |
|
6700 // put selection at front of righthand heading |
|
6701 res = aSelection->Collapse(aHeader,0); |
|
6702 } |
|
6703 return res; |
|
6704 } |
|
6705 |
|
6706 /////////////////////////////////////////////////////////////////////////// |
|
6707 // ReturnInParagraph: do the right thing for returns pressed in paragraphs |
|
6708 // |
|
6709 nsresult |
|
6710 nsHTMLEditRules::ReturnInParagraph(nsISelection* aSelection, |
|
6711 nsIDOMNode* aPara, |
|
6712 nsIDOMNode* aNode, |
|
6713 int32_t aOffset, |
|
6714 bool* aCancel, |
|
6715 bool* aHandled) |
|
6716 { |
|
6717 if (!aSelection || !aPara || !aNode || !aCancel || !aHandled) { |
|
6718 return NS_ERROR_NULL_POINTER; |
|
6719 } |
|
6720 *aCancel = false; |
|
6721 *aHandled = false; |
|
6722 nsresult res; |
|
6723 |
|
6724 int32_t offset; |
|
6725 nsCOMPtr<nsIDOMNode> parent = nsEditor::GetNodeLocation(aNode, &offset); |
|
6726 |
|
6727 NS_ENSURE_STATE(mHTMLEditor); |
|
6728 bool doesCRCreateNewP = mHTMLEditor->GetReturnInParagraphCreatesNewParagraph(); |
|
6729 |
|
6730 bool newBRneeded = false; |
|
6731 nsCOMPtr<nsIDOMNode> sibling; |
|
6732 |
|
6733 NS_ENSURE_STATE(mHTMLEditor); |
|
6734 if (aNode == aPara && doesCRCreateNewP) { |
|
6735 // we are at the edges of the block, newBRneeded not needed! |
|
6736 sibling = aNode; |
|
6737 } else if (mHTMLEditor->IsTextNode(aNode)) { |
|
6738 nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(aNode); |
|
6739 uint32_t strLength; |
|
6740 res = textNode->GetLength(&strLength); |
|
6741 NS_ENSURE_SUCCESS(res, res); |
|
6742 |
|
6743 // at beginning of text node? |
|
6744 if (!aOffset) { |
|
6745 // is there a BR prior to it? |
|
6746 NS_ENSURE_STATE(mHTMLEditor); |
|
6747 mHTMLEditor->GetPriorHTMLSibling(aNode, address_of(sibling)); |
|
6748 if (!sibling || !mHTMLEditor || !mHTMLEditor->IsVisBreak(sibling) || |
|
6749 nsTextEditUtils::HasMozAttr(sibling)) { |
|
6750 NS_ENSURE_STATE(mHTMLEditor); |
|
6751 newBRneeded = true; |
|
6752 } |
|
6753 } else if (aOffset == (int32_t)strLength) { |
|
6754 // we're at the end of text node... |
|
6755 // is there a BR after to it? |
|
6756 NS_ENSURE_STATE(mHTMLEditor); |
|
6757 res = mHTMLEditor->GetNextHTMLSibling(aNode, address_of(sibling)); |
|
6758 if (!sibling || !mHTMLEditor || !mHTMLEditor->IsVisBreak(sibling) || |
|
6759 nsTextEditUtils::HasMozAttr(sibling)) { |
|
6760 NS_ENSURE_STATE(mHTMLEditor); |
|
6761 newBRneeded = true; |
|
6762 offset++; |
|
6763 } |
|
6764 } else { |
|
6765 if (doesCRCreateNewP) { |
|
6766 nsCOMPtr<nsIDOMNode> tmp; |
|
6767 res = mEditor->SplitNode(aNode, aOffset, getter_AddRefs(tmp)); |
|
6768 NS_ENSURE_SUCCESS(res, res); |
|
6769 aNode = tmp; |
|
6770 } |
|
6771 |
|
6772 newBRneeded = true; |
|
6773 offset++; |
|
6774 } |
|
6775 } else { |
|
6776 // not in a text node. |
|
6777 // is there a BR prior to it? |
|
6778 nsCOMPtr<nsIDOMNode> nearNode, selNode = aNode; |
|
6779 NS_ENSURE_STATE(mHTMLEditor); |
|
6780 res = mHTMLEditor->GetPriorHTMLNode(aNode, aOffset, address_of(nearNode)); |
|
6781 NS_ENSURE_SUCCESS(res, res); |
|
6782 NS_ENSURE_STATE(mHTMLEditor); |
|
6783 if (!nearNode || !mHTMLEditor->IsVisBreak(nearNode) || |
|
6784 nsTextEditUtils::HasMozAttr(nearNode)) { |
|
6785 // is there a BR after it? |
|
6786 NS_ENSURE_STATE(mHTMLEditor); |
|
6787 res = mHTMLEditor->GetNextHTMLNode(aNode, aOffset, address_of(nearNode)); |
|
6788 NS_ENSURE_SUCCESS(res, res); |
|
6789 NS_ENSURE_STATE(mHTMLEditor); |
|
6790 if (!nearNode || !mHTMLEditor->IsVisBreak(nearNode) || |
|
6791 nsTextEditUtils::HasMozAttr(nearNode)) { |
|
6792 newBRneeded = true; |
|
6793 } |
|
6794 } |
|
6795 if (!newBRneeded) { |
|
6796 sibling = nearNode; |
|
6797 } |
|
6798 } |
|
6799 if (newBRneeded) { |
|
6800 // if CR does not create a new P, default to BR creation |
|
6801 NS_ENSURE_TRUE(doesCRCreateNewP, NS_OK); |
|
6802 |
|
6803 nsCOMPtr<nsIDOMNode> brNode; |
|
6804 NS_ENSURE_STATE(mHTMLEditor); |
|
6805 res = mHTMLEditor->CreateBR(parent, offset, address_of(brNode)); |
|
6806 sibling = brNode; |
|
6807 } |
|
6808 nsCOMPtr<nsIDOMNode> selNode = aNode; |
|
6809 *aHandled = true; |
|
6810 return SplitParagraph(aPara, sibling, aSelection, address_of(selNode), &aOffset); |
|
6811 } |
|
6812 |
|
6813 /////////////////////////////////////////////////////////////////////////// |
|
6814 // SplitParagraph: split a paragraph at selection point, possibly deleting a br |
|
6815 // |
|
6816 nsresult |
|
6817 nsHTMLEditRules::SplitParagraph(nsIDOMNode *aPara, |
|
6818 nsIDOMNode *aBRNode, |
|
6819 nsISelection *aSelection, |
|
6820 nsCOMPtr<nsIDOMNode> *aSelNode, |
|
6821 int32_t *aOffset) |
|
6822 { |
|
6823 NS_ENSURE_TRUE(aPara && aBRNode && aSelNode && *aSelNode && aOffset && aSelection, NS_ERROR_NULL_POINTER); |
|
6824 nsresult res = NS_OK; |
|
6825 |
|
6826 // split para |
|
6827 int32_t newOffset; |
|
6828 // get ws code to adjust any ws |
|
6829 nsCOMPtr<nsIDOMNode> leftPara, rightPara; |
|
6830 NS_ENSURE_STATE(mHTMLEditor); |
|
6831 res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, aSelNode, aOffset); |
|
6832 NS_ENSURE_SUCCESS(res, res); |
|
6833 // split the paragraph |
|
6834 NS_ENSURE_STATE(mHTMLEditor); |
|
6835 res = mHTMLEditor->SplitNodeDeep(aPara, *aSelNode, *aOffset, &newOffset, false, |
|
6836 address_of(leftPara), address_of(rightPara)); |
|
6837 NS_ENSURE_SUCCESS(res, res); |
|
6838 // get rid of the break, if it is visible (otherwise it may be needed to prevent an empty p) |
|
6839 NS_ENSURE_STATE(mHTMLEditor); |
|
6840 if (mHTMLEditor->IsVisBreak(aBRNode)) |
|
6841 { |
|
6842 NS_ENSURE_STATE(mHTMLEditor); |
|
6843 res = mHTMLEditor->DeleteNode(aBRNode); |
|
6844 NS_ENSURE_SUCCESS(res, res); |
|
6845 } |
|
6846 |
|
6847 // remove ID attribute on the paragraph we just created |
|
6848 nsCOMPtr<nsIDOMElement> rightElt = do_QueryInterface(rightPara); |
|
6849 NS_ENSURE_STATE(mHTMLEditor); |
|
6850 res = mHTMLEditor->RemoveAttribute(rightElt, NS_LITERAL_STRING("id")); |
|
6851 NS_ENSURE_SUCCESS(res, res); |
|
6852 |
|
6853 // check both halves of para to see if we need mozBR |
|
6854 res = InsertMozBRIfNeeded(leftPara); |
|
6855 NS_ENSURE_SUCCESS(res, res); |
|
6856 res = InsertMozBRIfNeeded(rightPara); |
|
6857 NS_ENSURE_SUCCESS(res, res); |
|
6858 |
|
6859 // selection to beginning of right hand para; |
|
6860 // look inside any containers that are up front. |
|
6861 NS_ENSURE_STATE(mHTMLEditor); |
|
6862 nsCOMPtr<nsIDOMNode> child = mHTMLEditor->GetLeftmostChild(rightPara, true); |
|
6863 NS_ENSURE_STATE(mHTMLEditor); |
|
6864 if (mHTMLEditor->IsTextNode(child) || !mHTMLEditor || |
|
6865 mHTMLEditor->IsContainer(child)) |
|
6866 { |
|
6867 NS_ENSURE_STATE(mHTMLEditor); |
|
6868 aSelection->Collapse(child,0); |
|
6869 } |
|
6870 else |
|
6871 { |
|
6872 int32_t offset; |
|
6873 nsCOMPtr<nsIDOMNode> parent = nsEditor::GetNodeLocation(child, &offset); |
|
6874 aSelection->Collapse(parent,offset); |
|
6875 } |
|
6876 return res; |
|
6877 } |
|
6878 |
|
6879 |
|
6880 /////////////////////////////////////////////////////////////////////////// |
|
6881 // ReturnInListItem: do the right thing for returns pressed in list items |
|
6882 // |
|
6883 nsresult |
|
6884 nsHTMLEditRules::ReturnInListItem(nsISelection *aSelection, |
|
6885 nsIDOMNode *aListItem, |
|
6886 nsIDOMNode *aNode, |
|
6887 int32_t aOffset) |
|
6888 { |
|
6889 NS_ENSURE_TRUE(aSelection && aListItem && aNode, NS_ERROR_NULL_POINTER); |
|
6890 nsCOMPtr<nsISelection> selection(aSelection); |
|
6891 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection)); |
|
6892 nsresult res = NS_OK; |
|
6893 |
|
6894 nsCOMPtr<nsIDOMNode> listitem; |
|
6895 |
|
6896 // sanity check |
|
6897 NS_PRECONDITION(true == nsHTMLEditUtils::IsListItem(aListItem), |
|
6898 "expected a list item and didn't get one"); |
|
6899 |
|
6900 // get the listitem parent and the active editing host. |
|
6901 NS_ENSURE_STATE(mHTMLEditor); |
|
6902 nsIContent* rootContent = mHTMLEditor->GetActiveEditingHost(); |
|
6903 nsCOMPtr<nsIDOMNode> rootNode = do_QueryInterface(rootContent); |
|
6904 int32_t itemOffset; |
|
6905 nsCOMPtr<nsIDOMNode> list = nsEditor::GetNodeLocation(aListItem, &itemOffset); |
|
6906 |
|
6907 // if we are in an empty listitem, then we want to pop up out of the list |
|
6908 // but only if prefs says it's ok and if the parent isn't the active editing host. |
|
6909 bool isEmpty; |
|
6910 res = IsEmptyBlock(aListItem, &isEmpty, true, false); |
|
6911 NS_ENSURE_SUCCESS(res, res); |
|
6912 if (isEmpty && (rootNode != list) && mReturnInEmptyLIKillsList) |
|
6913 { |
|
6914 // get the list offset now -- before we might eventually split the list |
|
6915 int32_t offset; |
|
6916 nsCOMPtr<nsIDOMNode> listparent = nsEditor::GetNodeLocation(list, &offset); |
|
6917 |
|
6918 // are we the last list item in the list? |
|
6919 bool bIsLast; |
|
6920 NS_ENSURE_STATE(mHTMLEditor); |
|
6921 res = mHTMLEditor->IsLastEditableChild(aListItem, &bIsLast); |
|
6922 NS_ENSURE_SUCCESS(res, res); |
|
6923 if (!bIsLast) |
|
6924 { |
|
6925 // we need to split the list! |
|
6926 nsCOMPtr<nsIDOMNode> tempNode; |
|
6927 NS_ENSURE_STATE(mHTMLEditor); |
|
6928 res = mHTMLEditor->SplitNode(list, itemOffset, getter_AddRefs(tempNode)); |
|
6929 NS_ENSURE_SUCCESS(res, res); |
|
6930 } |
|
6931 |
|
6932 // are we in a sublist? |
|
6933 if (nsHTMLEditUtils::IsList(listparent)) //in a sublist |
|
6934 { |
|
6935 // if so, move this list item out of this list and into the grandparent list |
|
6936 NS_ENSURE_STATE(mHTMLEditor); |
|
6937 res = mHTMLEditor->MoveNode(aListItem,listparent,offset+1); |
|
6938 NS_ENSURE_SUCCESS(res, res); |
|
6939 res = aSelection->Collapse(aListItem,0); |
|
6940 } |
|
6941 else |
|
6942 { |
|
6943 // otherwise kill this listitem |
|
6944 NS_ENSURE_STATE(mHTMLEditor); |
|
6945 res = mHTMLEditor->DeleteNode(aListItem); |
|
6946 NS_ENSURE_SUCCESS(res, res); |
|
6947 |
|
6948 // time to insert a paragraph |
|
6949 NS_NAMED_LITERAL_STRING(pType, "p"); |
|
6950 nsCOMPtr<nsIDOMNode> pNode; |
|
6951 NS_ENSURE_STATE(mHTMLEditor); |
|
6952 res = mHTMLEditor->CreateNode(pType, listparent, offset+1, getter_AddRefs(pNode)); |
|
6953 NS_ENSURE_SUCCESS(res, res); |
|
6954 |
|
6955 // append a <br> to it |
|
6956 nsCOMPtr<nsIDOMNode> brNode; |
|
6957 NS_ENSURE_STATE(mHTMLEditor); |
|
6958 res = mHTMLEditor->CreateBR(pNode, 0, address_of(brNode)); |
|
6959 NS_ENSURE_SUCCESS(res, res); |
|
6960 |
|
6961 // set selection to before the break |
|
6962 res = aSelection->Collapse(pNode, 0); |
|
6963 } |
|
6964 return res; |
|
6965 } |
|
6966 |
|
6967 // else we want a new list item at the same list level. |
|
6968 // get ws code to adjust any ws |
|
6969 nsCOMPtr<nsIDOMNode> selNode = aNode; |
|
6970 NS_ENSURE_STATE(mHTMLEditor); |
|
6971 res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset); |
|
6972 NS_ENSURE_SUCCESS(res, res); |
|
6973 // now split list item |
|
6974 int32_t newOffset; |
|
6975 NS_ENSURE_STATE(mHTMLEditor); |
|
6976 res = mHTMLEditor->SplitNodeDeep( aListItem, selNode, aOffset, &newOffset, false); |
|
6977 NS_ENSURE_SUCCESS(res, res); |
|
6978 // hack: until I can change the damaged doc range code back to being |
|
6979 // extra inclusive, I have to manually detect certain list items that |
|
6980 // may be left empty. |
|
6981 nsCOMPtr<nsIDOMNode> prevItem; |
|
6982 NS_ENSURE_STATE(mHTMLEditor); |
|
6983 mHTMLEditor->GetPriorHTMLSibling(aListItem, address_of(prevItem)); |
|
6984 |
|
6985 if (prevItem && nsHTMLEditUtils::IsListItem(prevItem)) |
|
6986 { |
|
6987 bool bIsEmptyNode; |
|
6988 NS_ENSURE_STATE(mHTMLEditor); |
|
6989 res = mHTMLEditor->IsEmptyNode(prevItem, &bIsEmptyNode); |
|
6990 NS_ENSURE_SUCCESS(res, res); |
|
6991 if (bIsEmptyNode) { |
|
6992 res = CreateMozBR(prevItem, 0); |
|
6993 NS_ENSURE_SUCCESS(res, res); |
|
6994 } else { |
|
6995 NS_ENSURE_STATE(mHTMLEditor); |
|
6996 res = mHTMLEditor->IsEmptyNode(aListItem, &bIsEmptyNode, true); |
|
6997 NS_ENSURE_SUCCESS(res, res); |
|
6998 if (bIsEmptyNode) |
|
6999 { |
|
7000 nsCOMPtr<nsIAtom> nodeAtom = nsEditor::GetTag(aListItem); |
|
7001 if (nodeAtom == nsEditProperty::dd || nodeAtom == nsEditProperty::dt) |
|
7002 { |
|
7003 int32_t itemOffset; |
|
7004 nsCOMPtr<nsIDOMNode> list = nsEditor::GetNodeLocation(aListItem, &itemOffset); |
|
7005 |
|
7006 nsAutoString listTag((nodeAtom == nsEditProperty::dt) ? NS_LITERAL_STRING("dd") : NS_LITERAL_STRING("dt")); |
|
7007 nsCOMPtr<nsIDOMNode> newListItem; |
|
7008 NS_ENSURE_STATE(mHTMLEditor); |
|
7009 res = mHTMLEditor->CreateNode(listTag, list, itemOffset+1, getter_AddRefs(newListItem)); |
|
7010 NS_ENSURE_SUCCESS(res, res); |
|
7011 res = mEditor->DeleteNode(aListItem); |
|
7012 NS_ENSURE_SUCCESS(res, res); |
|
7013 return aSelection->Collapse(newListItem, 0); |
|
7014 } |
|
7015 |
|
7016 nsCOMPtr<nsIDOMNode> brNode; |
|
7017 NS_ENSURE_STATE(mHTMLEditor); |
|
7018 res = mHTMLEditor->CopyLastEditableChildStyles(prevItem, aListItem, getter_AddRefs(brNode)); |
|
7019 NS_ENSURE_SUCCESS(res, res); |
|
7020 if (brNode) |
|
7021 { |
|
7022 int32_t offset; |
|
7023 nsCOMPtr<nsIDOMNode> brParent = nsEditor::GetNodeLocation(brNode, &offset); |
|
7024 return aSelection->Collapse(brParent, offset); |
|
7025 } |
|
7026 } |
|
7027 else |
|
7028 { |
|
7029 NS_ENSURE_STATE(mHTMLEditor); |
|
7030 nsWSRunObject wsObj(mHTMLEditor, aListItem, 0); |
|
7031 nsCOMPtr<nsIDOMNode> visNode; |
|
7032 int32_t visOffset = 0; |
|
7033 WSType wsType; |
|
7034 wsObj.NextVisibleNode(aListItem, 0, address_of(visNode), |
|
7035 &visOffset, &wsType); |
|
7036 if (wsType == WSType::special || wsType == WSType::br || |
|
7037 nsHTMLEditUtils::IsHR(visNode)) { |
|
7038 int32_t offset; |
|
7039 nsCOMPtr<nsIDOMNode> parent = nsEditor::GetNodeLocation(visNode, &offset); |
|
7040 return aSelection->Collapse(parent, offset); |
|
7041 } |
|
7042 else |
|
7043 { |
|
7044 return aSelection->Collapse(visNode, visOffset); |
|
7045 } |
|
7046 } |
|
7047 } |
|
7048 } |
|
7049 res = aSelection->Collapse(aListItem,0); |
|
7050 return res; |
|
7051 } |
|
7052 |
|
7053 |
|
7054 /////////////////////////////////////////////////////////////////////////// |
|
7055 // MakeBlockquote: put the list of nodes into one or more blockquotes. |
|
7056 // |
|
7057 nsresult |
|
7058 nsHTMLEditRules::MakeBlockquote(nsCOMArray<nsIDOMNode>& arrayOfNodes) |
|
7059 { |
|
7060 // the idea here is to put the nodes into a minimal number of |
|
7061 // blockquotes. When the user blockquotes something, they expect |
|
7062 // one blockquote. That may not be possible (for instance, if they |
|
7063 // have two table cells selected, you need two blockquotes inside the cells). |
|
7064 |
|
7065 nsresult res = NS_OK; |
|
7066 |
|
7067 nsCOMPtr<nsIDOMNode> curNode, curParent, curBlock, newBlock; |
|
7068 int32_t offset; |
|
7069 int32_t listCount = arrayOfNodes.Count(); |
|
7070 |
|
7071 nsCOMPtr<nsIDOMNode> prevParent; |
|
7072 |
|
7073 int32_t i; |
|
7074 for (i=0; i<listCount; i++) |
|
7075 { |
|
7076 // get the node to act on, and its location |
|
7077 curNode = arrayOfNodes[i]; |
|
7078 curParent = nsEditor::GetNodeLocation(curNode, &offset); |
|
7079 |
|
7080 // if the node is a table element or list item, dive inside |
|
7081 if (nsHTMLEditUtils::IsTableElementButNotTable(curNode) || |
|
7082 nsHTMLEditUtils::IsListItem(curNode)) |
|
7083 { |
|
7084 curBlock = 0; // forget any previous block |
|
7085 // recursion time |
|
7086 nsCOMArray<nsIDOMNode> childArray; |
|
7087 res = GetChildNodesForOperation(curNode, childArray); |
|
7088 NS_ENSURE_SUCCESS(res, res); |
|
7089 res = MakeBlockquote(childArray); |
|
7090 NS_ENSURE_SUCCESS(res, res); |
|
7091 } |
|
7092 |
|
7093 // if the node has different parent than previous node, |
|
7094 // further nodes in a new parent |
|
7095 if (prevParent) |
|
7096 { |
|
7097 nsCOMPtr<nsIDOMNode> temp; |
|
7098 curNode->GetParentNode(getter_AddRefs(temp)); |
|
7099 if (temp != prevParent) |
|
7100 { |
|
7101 curBlock = 0; // forget any previous blockquote node we were using |
|
7102 prevParent = temp; |
|
7103 } |
|
7104 } |
|
7105 else |
|
7106 |
|
7107 { |
|
7108 curNode->GetParentNode(getter_AddRefs(prevParent)); |
|
7109 } |
|
7110 |
|
7111 // if no curBlock, make one |
|
7112 if (!curBlock) |
|
7113 { |
|
7114 NS_NAMED_LITERAL_STRING(quoteType, "blockquote"); |
|
7115 res = SplitAsNeeded("eType, address_of(curParent), &offset); |
|
7116 NS_ENSURE_SUCCESS(res, res); |
|
7117 NS_ENSURE_STATE(mHTMLEditor); |
|
7118 res = mHTMLEditor->CreateNode(quoteType, curParent, offset, getter_AddRefs(curBlock)); |
|
7119 NS_ENSURE_SUCCESS(res, res); |
|
7120 // remember our new block for postprocessing |
|
7121 mNewBlock = curBlock; |
|
7122 // note: doesn't matter if we set mNewBlock multiple times. |
|
7123 } |
|
7124 |
|
7125 NS_ENSURE_STATE(mHTMLEditor); |
|
7126 res = mHTMLEditor->MoveNode(curNode, curBlock, -1); |
|
7127 NS_ENSURE_SUCCESS(res, res); |
|
7128 } |
|
7129 return res; |
|
7130 } |
|
7131 |
|
7132 |
|
7133 |
|
7134 /////////////////////////////////////////////////////////////////////////// |
|
7135 // RemoveBlockStyle: make the nodes have no special block type. |
|
7136 // |
|
7137 nsresult |
|
7138 nsHTMLEditRules::RemoveBlockStyle(nsCOMArray<nsIDOMNode>& arrayOfNodes) |
|
7139 { |
|
7140 // intent of this routine is to be used for converting to/from |
|
7141 // headers, paragraphs, pre, and address. Those blocks |
|
7142 // that pretty much just contain inline things... |
|
7143 |
|
7144 nsresult res = NS_OK; |
|
7145 |
|
7146 nsCOMPtr<nsIDOMNode> curBlock, firstNode, lastNode; |
|
7147 int32_t listCount = arrayOfNodes.Count(); |
|
7148 for (int32_t i = 0; i < listCount; ++i) { |
|
7149 // get the node to act on, and its location |
|
7150 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i]; |
|
7151 |
|
7152 nsCOMPtr<dom::Element> curElement = do_QueryInterface(curNode); |
|
7153 |
|
7154 // if curNode is a address, p, header, address, or pre, remove it |
|
7155 if (curElement && nsHTMLEditUtils::IsFormatNode(curElement)) { |
|
7156 // process any partial progress saved |
|
7157 if (curBlock) |
|
7158 { |
|
7159 res = RemovePartOfBlock(curBlock, firstNode, lastNode); |
|
7160 NS_ENSURE_SUCCESS(res, res); |
|
7161 curBlock = 0; firstNode = 0; lastNode = 0; |
|
7162 } |
|
7163 // remove curent block |
|
7164 NS_ENSURE_STATE(mHTMLEditor); |
|
7165 res = mHTMLEditor->RemoveBlockContainer(curNode); |
|
7166 NS_ENSURE_SUCCESS(res, res); |
|
7167 } else if (curElement && |
|
7168 (curElement->IsHTML(nsGkAtoms::table) || |
|
7169 curElement->IsHTML(nsGkAtoms::tr) || |
|
7170 curElement->IsHTML(nsGkAtoms::tbody) || |
|
7171 curElement->IsHTML(nsGkAtoms::td) || |
|
7172 nsHTMLEditUtils::IsList(curElement) || |
|
7173 curElement->IsHTML(nsGkAtoms::li) || |
|
7174 curElement->IsHTML(nsGkAtoms::blockquote) || |
|
7175 curElement->IsHTML(nsGkAtoms::div))) { |
|
7176 // process any partial progress saved |
|
7177 if (curBlock) |
|
7178 { |
|
7179 res = RemovePartOfBlock(curBlock, firstNode, lastNode); |
|
7180 NS_ENSURE_SUCCESS(res, res); |
|
7181 curBlock = 0; firstNode = 0; lastNode = 0; |
|
7182 } |
|
7183 // recursion time |
|
7184 nsCOMArray<nsIDOMNode> childArray; |
|
7185 res = GetChildNodesForOperation(curNode, childArray); |
|
7186 NS_ENSURE_SUCCESS(res, res); |
|
7187 res = RemoveBlockStyle(childArray); |
|
7188 NS_ENSURE_SUCCESS(res, res); |
|
7189 } |
|
7190 else if (IsInlineNode(curNode)) |
|
7191 { |
|
7192 if (curBlock) |
|
7193 { |
|
7194 // if so, is this node a descendant? |
|
7195 if (nsEditorUtils::IsDescendantOf(curNode, curBlock)) |
|
7196 { |
|
7197 lastNode = curNode; |
|
7198 continue; // then we don't need to do anything different for this node |
|
7199 } |
|
7200 else |
|
7201 { |
|
7202 // otherwise, we have progressed beyond end of curBlock, |
|
7203 // so lets handle it now. We need to remove the portion of |
|
7204 // curBlock that contains [firstNode - lastNode]. |
|
7205 res = RemovePartOfBlock(curBlock, firstNode, lastNode); |
|
7206 NS_ENSURE_SUCCESS(res, res); |
|
7207 curBlock = 0; firstNode = 0; lastNode = 0; |
|
7208 // fall out and handle curNode |
|
7209 } |
|
7210 } |
|
7211 NS_ENSURE_STATE(mHTMLEditor); |
|
7212 curBlock = mHTMLEditor->GetBlockNodeParent(curNode); |
|
7213 if (nsHTMLEditUtils::IsFormatNode(curBlock)) |
|
7214 { |
|
7215 firstNode = curNode; |
|
7216 lastNode = curNode; |
|
7217 } |
|
7218 else |
|
7219 curBlock = 0; // not a block kind that we care about. |
|
7220 } |
|
7221 else |
|
7222 { // some node that is already sans block style. skip over it and |
|
7223 // process any partial progress saved |
|
7224 if (curBlock) |
|
7225 { |
|
7226 res = RemovePartOfBlock(curBlock, firstNode, lastNode); |
|
7227 NS_ENSURE_SUCCESS(res, res); |
|
7228 curBlock = 0; firstNode = 0; lastNode = 0; |
|
7229 } |
|
7230 } |
|
7231 } |
|
7232 // process any partial progress saved |
|
7233 if (curBlock) |
|
7234 { |
|
7235 res = RemovePartOfBlock(curBlock, firstNode, lastNode); |
|
7236 NS_ENSURE_SUCCESS(res, res); |
|
7237 curBlock = 0; firstNode = 0; lastNode = 0; |
|
7238 } |
|
7239 return res; |
|
7240 } |
|
7241 |
|
7242 |
|
7243 /////////////////////////////////////////////////////////////////////////// |
|
7244 // ApplyBlockStyle: do whatever it takes to make the list of nodes into |
|
7245 // one or more blocks of type blockTag. |
|
7246 // |
|
7247 nsresult |
|
7248 nsHTMLEditRules::ApplyBlockStyle(nsCOMArray<nsIDOMNode>& arrayOfNodes, const nsAString *aBlockTag) |
|
7249 { |
|
7250 // intent of this routine is to be used for converting to/from |
|
7251 // headers, paragraphs, pre, and address. Those blocks |
|
7252 // that pretty much just contain inline things... |
|
7253 |
|
7254 NS_ENSURE_TRUE(aBlockTag, NS_ERROR_NULL_POINTER); |
|
7255 nsresult res = NS_OK; |
|
7256 |
|
7257 nsCOMPtr<nsIDOMNode> curNode, curParent, curBlock, newBlock; |
|
7258 int32_t offset; |
|
7259 int32_t listCount = arrayOfNodes.Count(); |
|
7260 nsString tString(*aBlockTag);////MJUDGE SCC NEED HELP |
|
7261 |
|
7262 // Remove all non-editable nodes. Leave them be. |
|
7263 int32_t j; |
|
7264 for (j=listCount-1; j>=0; j--) |
|
7265 { |
|
7266 NS_ENSURE_STATE(mHTMLEditor); |
|
7267 if (!mHTMLEditor->IsEditable(arrayOfNodes[j])) |
|
7268 { |
|
7269 arrayOfNodes.RemoveObjectAt(j); |
|
7270 } |
|
7271 } |
|
7272 |
|
7273 // reset list count |
|
7274 listCount = arrayOfNodes.Count(); |
|
7275 |
|
7276 int32_t i; |
|
7277 for (i=0; i<listCount; i++) |
|
7278 { |
|
7279 // get the node to act on, and its location |
|
7280 curNode = arrayOfNodes[i]; |
|
7281 curParent = nsEditor::GetNodeLocation(curNode, &offset); |
|
7282 nsAutoString curNodeTag; |
|
7283 nsEditor::GetTagString(curNode, curNodeTag); |
|
7284 ToLowerCase(curNodeTag); |
|
7285 |
|
7286 // is it already the right kind of block? |
|
7287 if (curNodeTag == *aBlockTag) |
|
7288 { |
|
7289 curBlock = 0; // forget any previous block used for previous inline nodes |
|
7290 continue; // do nothing to this block |
|
7291 } |
|
7292 |
|
7293 // if curNode is a address, p, header, address, or pre, replace |
|
7294 // it with a new block of correct type. |
|
7295 // xxx floppy moose: pre can't hold everything the others can |
|
7296 if (nsHTMLEditUtils::IsMozDiv(curNode) || |
|
7297 nsHTMLEditUtils::IsFormatNode(curNode)) |
|
7298 { |
|
7299 curBlock = 0; // forget any previous block used for previous inline nodes |
|
7300 NS_ENSURE_STATE(mHTMLEditor); |
|
7301 res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock), *aBlockTag, |
|
7302 nullptr, nullptr, true); |
|
7303 NS_ENSURE_SUCCESS(res, res); |
|
7304 } |
|
7305 else if (nsHTMLEditUtils::IsTable(curNode) || |
|
7306 (curNodeTag.EqualsLiteral("tbody")) || |
|
7307 (curNodeTag.EqualsLiteral("tr")) || |
|
7308 (curNodeTag.EqualsLiteral("td")) || |
|
7309 nsHTMLEditUtils::IsList(curNode) || |
|
7310 (curNodeTag.EqualsLiteral("li")) || |
|
7311 nsHTMLEditUtils::IsBlockquote(curNode) || |
|
7312 nsHTMLEditUtils::IsDiv(curNode)) |
|
7313 { |
|
7314 curBlock = 0; // forget any previous block used for previous inline nodes |
|
7315 // recursion time |
|
7316 nsCOMArray<nsIDOMNode> childArray; |
|
7317 res = GetChildNodesForOperation(curNode, childArray); |
|
7318 NS_ENSURE_SUCCESS(res, res); |
|
7319 int32_t childCount = childArray.Count(); |
|
7320 if (childCount) |
|
7321 { |
|
7322 res = ApplyBlockStyle(childArray, aBlockTag); |
|
7323 NS_ENSURE_SUCCESS(res, res); |
|
7324 } |
|
7325 else |
|
7326 { |
|
7327 // make sure we can put a block here |
|
7328 res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset); |
|
7329 NS_ENSURE_SUCCESS(res, res); |
|
7330 nsCOMPtr<nsIDOMNode> theBlock; |
|
7331 NS_ENSURE_STATE(mHTMLEditor); |
|
7332 res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(theBlock)); |
|
7333 NS_ENSURE_SUCCESS(res, res); |
|
7334 // remember our new block for postprocessing |
|
7335 mNewBlock = theBlock; |
|
7336 } |
|
7337 } |
|
7338 |
|
7339 // if the node is a break, we honor it by putting further nodes in a new parent |
|
7340 else if (curNodeTag.EqualsLiteral("br")) |
|
7341 { |
|
7342 if (curBlock) |
|
7343 { |
|
7344 curBlock = 0; // forget any previous block used for previous inline nodes |
|
7345 NS_ENSURE_STATE(mHTMLEditor); |
|
7346 res = mHTMLEditor->DeleteNode(curNode); |
|
7347 NS_ENSURE_SUCCESS(res, res); |
|
7348 } |
|
7349 else |
|
7350 { |
|
7351 // the break is the first (or even only) node we encountered. Create a |
|
7352 // block for it. |
|
7353 res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset); |
|
7354 NS_ENSURE_SUCCESS(res, res); |
|
7355 NS_ENSURE_STATE(mHTMLEditor); |
|
7356 res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(curBlock)); |
|
7357 NS_ENSURE_SUCCESS(res, res); |
|
7358 // remember our new block for postprocessing |
|
7359 mNewBlock = curBlock; |
|
7360 // note: doesn't matter if we set mNewBlock multiple times. |
|
7361 NS_ENSURE_STATE(mHTMLEditor); |
|
7362 res = mHTMLEditor->MoveNode(curNode, curBlock, -1); |
|
7363 NS_ENSURE_SUCCESS(res, res); |
|
7364 } |
|
7365 } |
|
7366 |
|
7367 |
|
7368 // if curNode is inline, pull it into curBlock |
|
7369 // note: it's assumed that consecutive inline nodes in the |
|
7370 // arrayOfNodes are actually members of the same block parent. |
|
7371 // this happens to be true now as a side effect of how |
|
7372 // arrayOfNodes is contructed, but some additional logic should |
|
7373 // be added here if that should change |
|
7374 |
|
7375 else if (IsInlineNode(curNode)) |
|
7376 { |
|
7377 // if curNode is a non editable, drop it if we are going to <pre> |
|
7378 NS_ENSURE_STATE(mHTMLEditor); |
|
7379 if (tString.LowerCaseEqualsLiteral("pre") |
|
7380 && (!mHTMLEditor->IsEditable(curNode))) |
|
7381 continue; // do nothing to this block |
|
7382 |
|
7383 // if no curBlock, make one |
|
7384 if (!curBlock) |
|
7385 { |
|
7386 res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset); |
|
7387 NS_ENSURE_SUCCESS(res, res); |
|
7388 NS_ENSURE_STATE(mHTMLEditor); |
|
7389 res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(curBlock)); |
|
7390 NS_ENSURE_SUCCESS(res, res); |
|
7391 // remember our new block for postprocessing |
|
7392 mNewBlock = curBlock; |
|
7393 // note: doesn't matter if we set mNewBlock multiple times. |
|
7394 } |
|
7395 |
|
7396 // if curNode is a Break, replace it with a return if we are going to <pre> |
|
7397 // xxx floppy moose |
|
7398 |
|
7399 // this is a continuation of some inline nodes that belong together in |
|
7400 // the same block item. use curBlock |
|
7401 NS_ENSURE_STATE(mHTMLEditor); |
|
7402 res = mHTMLEditor->MoveNode(curNode, curBlock, -1); |
|
7403 NS_ENSURE_SUCCESS(res, res); |
|
7404 } |
|
7405 } |
|
7406 return res; |
|
7407 } |
|
7408 |
|
7409 |
|
7410 /////////////////////////////////////////////////////////////////////////// |
|
7411 // SplitAsNeeded: given a tag name, split inOutParent up to the point |
|
7412 // where we can insert the tag. Adjust inOutParent and |
|
7413 // inOutOffset to pint to new location for tag. |
|
7414 nsresult |
|
7415 nsHTMLEditRules::SplitAsNeeded(const nsAString *aTag, |
|
7416 nsCOMPtr<nsIDOMNode> *inOutParent, |
|
7417 int32_t *inOutOffset) |
|
7418 { |
|
7419 NS_ENSURE_TRUE(aTag && inOutParent && inOutOffset, NS_ERROR_NULL_POINTER); |
|
7420 NS_ENSURE_TRUE(*inOutParent, NS_ERROR_NULL_POINTER); |
|
7421 nsCOMPtr<nsIDOMNode> tagParent, temp, splitNode, parent = *inOutParent; |
|
7422 nsresult res = NS_OK; |
|
7423 nsCOMPtr<nsIAtom> tagAtom = do_GetAtom(*aTag); |
|
7424 |
|
7425 // check that we have a place that can legally contain the tag |
|
7426 while (!tagParent) |
|
7427 { |
|
7428 // sniffing up the parent tree until we find |
|
7429 // a legal place for the block |
|
7430 if (!parent) break; |
|
7431 // Don't leave the active editing host |
|
7432 NS_ENSURE_STATE(mHTMLEditor); |
|
7433 if (!mHTMLEditor->IsDescendantOfEditorRoot(parent)) { |
|
7434 nsCOMPtr<nsIContent> parentContent = do_QueryInterface(parent); |
|
7435 NS_ENSURE_STATE(mHTMLEditor); |
|
7436 if (parentContent != mHTMLEditor->GetActiveEditingHost()) { |
|
7437 break; |
|
7438 } |
|
7439 } |
|
7440 NS_ENSURE_STATE(mHTMLEditor); |
|
7441 if (mHTMLEditor->CanContainTag(parent, tagAtom)) { |
|
7442 tagParent = parent; |
|
7443 break; |
|
7444 } |
|
7445 splitNode = parent; |
|
7446 parent->GetParentNode(getter_AddRefs(temp)); |
|
7447 parent = temp; |
|
7448 } |
|
7449 if (!tagParent) |
|
7450 { |
|
7451 // could not find a place to build tag! |
|
7452 return NS_ERROR_FAILURE; |
|
7453 } |
|
7454 if (splitNode) |
|
7455 { |
|
7456 // we found a place for block, but above inOutParent. We need to split nodes. |
|
7457 NS_ENSURE_STATE(mHTMLEditor); |
|
7458 res = mHTMLEditor->SplitNodeDeep(splitNode, *inOutParent, *inOutOffset, inOutOffset); |
|
7459 NS_ENSURE_SUCCESS(res, res); |
|
7460 *inOutParent = tagParent; |
|
7461 } |
|
7462 return res; |
|
7463 } |
|
7464 |
|
7465 /////////////////////////////////////////////////////////////////////////// |
|
7466 // JoinNodesSmart: join two nodes, doing whatever makes sense for their |
|
7467 // children (which often means joining them, too). |
|
7468 // aNodeLeft & aNodeRight must be same type of node. |
|
7469 nsresult |
|
7470 nsHTMLEditRules::JoinNodesSmart( nsIDOMNode *aNodeLeft, |
|
7471 nsIDOMNode *aNodeRight, |
|
7472 nsCOMPtr<nsIDOMNode> *aOutMergeParent, |
|
7473 int32_t *aOutMergeOffset) |
|
7474 { |
|
7475 // check parms |
|
7476 NS_ENSURE_TRUE(aNodeLeft && |
|
7477 aNodeRight && |
|
7478 aOutMergeParent && |
|
7479 aOutMergeOffset, NS_ERROR_NULL_POINTER); |
|
7480 |
|
7481 nsresult res = NS_OK; |
|
7482 // caller responsible for: |
|
7483 // left & right node are same type |
|
7484 int32_t parOffset; |
|
7485 nsCOMPtr<nsIDOMNode> rightParent; |
|
7486 nsCOMPtr<nsIDOMNode> parent = nsEditor::GetNodeLocation(aNodeLeft, &parOffset); |
|
7487 aNodeRight->GetParentNode(getter_AddRefs(rightParent)); |
|
7488 |
|
7489 // if they don't have the same parent, first move the 'right' node |
|
7490 // to after the 'left' one |
|
7491 if (parent != rightParent) |
|
7492 { |
|
7493 NS_ENSURE_STATE(mHTMLEditor); |
|
7494 res = mHTMLEditor->MoveNode(aNodeRight, parent, parOffset); |
|
7495 NS_ENSURE_SUCCESS(res, res); |
|
7496 } |
|
7497 |
|
7498 // defaults for outParams |
|
7499 *aOutMergeParent = aNodeRight; |
|
7500 NS_ENSURE_STATE(mHTMLEditor); |
|
7501 res = mHTMLEditor->GetLengthOfDOMNode(aNodeLeft, *((uint32_t*)aOutMergeOffset)); |
|
7502 NS_ENSURE_SUCCESS(res, res); |
|
7503 |
|
7504 // separate join rules for differing blocks |
|
7505 if (nsHTMLEditUtils::IsList(aNodeLeft) || |
|
7506 !mHTMLEditor || |
|
7507 mHTMLEditor->IsTextNode(aNodeLeft)) |
|
7508 { |
|
7509 // for list's, merge shallow (wouldn't want to combine list items) |
|
7510 NS_ENSURE_STATE(mHTMLEditor); |
|
7511 res = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight, parent); |
|
7512 NS_ENSURE_SUCCESS(res, res); |
|
7513 return res; |
|
7514 } |
|
7515 else |
|
7516 { |
|
7517 // remember the last left child, and firt right child |
|
7518 nsCOMPtr<nsIDOMNode> lastLeft, firstRight; |
|
7519 NS_ENSURE_STATE(mHTMLEditor); |
|
7520 res = mHTMLEditor->GetLastEditableChild(aNodeLeft, address_of(lastLeft)); |
|
7521 NS_ENSURE_SUCCESS(res, res); |
|
7522 NS_ENSURE_STATE(mHTMLEditor); |
|
7523 res = mHTMLEditor->GetFirstEditableChild(aNodeRight, address_of(firstRight)); |
|
7524 NS_ENSURE_SUCCESS(res, res); |
|
7525 |
|
7526 // for list items, divs, etc, merge smart |
|
7527 NS_ENSURE_STATE(mHTMLEditor); |
|
7528 res = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight, parent); |
|
7529 NS_ENSURE_SUCCESS(res, res); |
|
7530 |
|
7531 if (lastLeft && firstRight && mHTMLEditor && |
|
7532 mHTMLEditor->NodesSameType(lastLeft, firstRight) && |
|
7533 (nsEditor::IsTextNode(lastLeft) || |
|
7534 !mHTMLEditor || |
|
7535 mHTMLEditor->mHTMLCSSUtils->ElementsSameStyle(lastLeft, firstRight))) { |
|
7536 NS_ENSURE_STATE(mHTMLEditor); |
|
7537 return JoinNodesSmart(lastLeft, firstRight, aOutMergeParent, aOutMergeOffset); |
|
7538 } |
|
7539 } |
|
7540 return res; |
|
7541 } |
|
7542 |
|
7543 |
|
7544 nsresult |
|
7545 nsHTMLEditRules::GetTopEnclosingMailCite(nsIDOMNode *aNode, |
|
7546 nsCOMPtr<nsIDOMNode> *aOutCiteNode, |
|
7547 bool aPlainText) |
|
7548 { |
|
7549 // check parms |
|
7550 NS_ENSURE_TRUE(aNode && aOutCiteNode, NS_ERROR_NULL_POINTER); |
|
7551 |
|
7552 nsresult res = NS_OK; |
|
7553 nsCOMPtr<nsIDOMNode> node, parentNode; |
|
7554 node = do_QueryInterface(aNode); |
|
7555 |
|
7556 while (node) |
|
7557 { |
|
7558 if ( (aPlainText && nsHTMLEditUtils::IsPre(node)) || |
|
7559 nsHTMLEditUtils::IsMailCite(node) ) |
|
7560 *aOutCiteNode = node; |
|
7561 if (nsTextEditUtils::IsBody(node)) break; |
|
7562 |
|
7563 res = node->GetParentNode(getter_AddRefs(parentNode)); |
|
7564 NS_ENSURE_SUCCESS(res, res); |
|
7565 node = parentNode; |
|
7566 } |
|
7567 |
|
7568 return res; |
|
7569 } |
|
7570 |
|
7571 |
|
7572 nsresult |
|
7573 nsHTMLEditRules::CacheInlineStyles(nsIDOMNode *aNode) |
|
7574 { |
|
7575 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); |
|
7576 |
|
7577 NS_ENSURE_STATE(mHTMLEditor); |
|
7578 bool useCSS = mHTMLEditor->IsCSSEnabled(); |
|
7579 |
|
7580 for (int32_t j = 0; j < SIZE_STYLE_TABLE; ++j) |
|
7581 { |
|
7582 bool isSet = false; |
|
7583 nsAutoString outValue; |
|
7584 // Don't use CSS for <font size>, we don't support it usefully (bug 780035) |
|
7585 if (!useCSS || (mCachedStyles[j].tag == nsGkAtoms::font && |
|
7586 mCachedStyles[j].attr.EqualsLiteral("size"))) { |
|
7587 NS_ENSURE_STATE(mHTMLEditor); |
|
7588 mHTMLEditor->IsTextPropertySetByContent(aNode, mCachedStyles[j].tag, |
|
7589 &(mCachedStyles[j].attr), nullptr, |
|
7590 isSet, &outValue); |
|
7591 } |
|
7592 else |
|
7593 { |
|
7594 NS_ENSURE_STATE(mHTMLEditor); |
|
7595 mHTMLEditor->mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(aNode, |
|
7596 mCachedStyles[j].tag, &(mCachedStyles[j].attr), isSet, outValue, |
|
7597 nsHTMLCSSUtils::eComputed); |
|
7598 } |
|
7599 if (isSet) |
|
7600 { |
|
7601 mCachedStyles[j].mPresent = true; |
|
7602 mCachedStyles[j].value.Assign(outValue); |
|
7603 } |
|
7604 } |
|
7605 return NS_OK; |
|
7606 } |
|
7607 |
|
7608 |
|
7609 nsresult |
|
7610 nsHTMLEditRules::ReapplyCachedStyles() |
|
7611 { |
|
7612 // The idea here is to examine our cached list of styles and see if any have |
|
7613 // been removed. If so, add typeinstate for them, so that they will be |
|
7614 // reinserted when new content is added. |
|
7615 |
|
7616 // remember if we are in css mode |
|
7617 NS_ENSURE_STATE(mHTMLEditor); |
|
7618 bool useCSS = mHTMLEditor->IsCSSEnabled(); |
|
7619 |
|
7620 // get selection point; if it doesn't exist, we have nothing to do |
|
7621 NS_ENSURE_STATE(mHTMLEditor); |
|
7622 nsRefPtr<Selection> selection = mHTMLEditor->GetSelection(); |
|
7623 MOZ_ASSERT(selection); |
|
7624 if (!selection->GetRangeCount()) { |
|
7625 // Nothing to do |
|
7626 return NS_OK; |
|
7627 } |
|
7628 nsCOMPtr<nsIContent> selNode = |
|
7629 do_QueryInterface(selection->GetRangeAt(0)->GetStartParent()); |
|
7630 if (!selNode) { |
|
7631 // Nothing to do |
|
7632 return NS_OK; |
|
7633 } |
|
7634 |
|
7635 for (int32_t i = 0; i < SIZE_STYLE_TABLE; ++i) { |
|
7636 if (mCachedStyles[i].mPresent) { |
|
7637 bool bFirst, bAny, bAll; |
|
7638 bFirst = bAny = bAll = false; |
|
7639 |
|
7640 nsAutoString curValue; |
|
7641 if (useCSS) { |
|
7642 // check computed style first in css case |
|
7643 NS_ENSURE_STATE(mHTMLEditor); |
|
7644 bAny = mHTMLEditor->mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet( |
|
7645 selNode, mCachedStyles[i].tag, &(mCachedStyles[i].attr), curValue, |
|
7646 nsHTMLCSSUtils::eComputed); |
|
7647 } |
|
7648 if (!bAny) { |
|
7649 // then check typeinstate and html style |
|
7650 NS_ENSURE_STATE(mHTMLEditor); |
|
7651 nsresult res = mHTMLEditor->GetInlinePropertyBase(mCachedStyles[i].tag, |
|
7652 &(mCachedStyles[i].attr), |
|
7653 &(mCachedStyles[i].value), |
|
7654 &bFirst, &bAny, &bAll, |
|
7655 &curValue, false); |
|
7656 NS_ENSURE_SUCCESS(res, res); |
|
7657 } |
|
7658 // this style has disappeared through deletion. Add to our typeinstate: |
|
7659 if (!bAny || IsStyleCachePreservingAction(mTheAction)) { |
|
7660 NS_ENSURE_STATE(mHTMLEditor); |
|
7661 mHTMLEditor->mTypeInState->SetProp(mCachedStyles[i].tag, |
|
7662 mCachedStyles[i].attr, |
|
7663 mCachedStyles[i].value); |
|
7664 } |
|
7665 } |
|
7666 } |
|
7667 |
|
7668 return NS_OK; |
|
7669 } |
|
7670 |
|
7671 |
|
7672 void |
|
7673 nsHTMLEditRules::ClearCachedStyles() |
|
7674 { |
|
7675 // clear the mPresent bits in mCachedStyles array |
|
7676 for (uint32_t j = 0; j < SIZE_STYLE_TABLE; j++) { |
|
7677 mCachedStyles[j].mPresent = false; |
|
7678 mCachedStyles[j].value.Truncate(); |
|
7679 } |
|
7680 } |
|
7681 |
|
7682 |
|
7683 nsresult |
|
7684 nsHTMLEditRules::AdjustSpecialBreaks(bool aSafeToAskFrames) |
|
7685 { |
|
7686 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
7687 nsCOMPtr<nsISupports> isupports; |
|
7688 int32_t nodeCount,j; |
|
7689 |
|
7690 // gather list of empty nodes |
|
7691 NS_ENSURE_STATE(mHTMLEditor); |
|
7692 nsEmptyEditableFunctor functor(mHTMLEditor); |
|
7693 nsDOMIterator iter; |
|
7694 nsresult res = iter.Init(mDocChangeRange); |
|
7695 NS_ENSURE_SUCCESS(res, res); |
|
7696 res = iter.AppendList(functor, arrayOfNodes); |
|
7697 NS_ENSURE_SUCCESS(res, res); |
|
7698 |
|
7699 // put moz-br's into these empty li's and td's |
|
7700 nodeCount = arrayOfNodes.Count(); |
|
7701 for (j = 0; j < nodeCount; j++) |
|
7702 { |
|
7703 // need to put br at END of node. It may have |
|
7704 // empty containers in it and still pass the "IsEmptynode" test, |
|
7705 // and we want the br's to be after them. Also, we want the br |
|
7706 // to be after the selection if the selection is in this node. |
|
7707 uint32_t len; |
|
7708 nsCOMPtr<nsIDOMNode> theNode = arrayOfNodes[0]; |
|
7709 arrayOfNodes.RemoveObjectAt(0); |
|
7710 res = nsEditor::GetLengthOfDOMNode(theNode, len); |
|
7711 NS_ENSURE_SUCCESS(res, res); |
|
7712 res = CreateMozBR(theNode, (int32_t)len); |
|
7713 NS_ENSURE_SUCCESS(res, res); |
|
7714 } |
|
7715 |
|
7716 return res; |
|
7717 } |
|
7718 |
|
7719 nsresult |
|
7720 nsHTMLEditRules::AdjustWhitespace(nsISelection *aSelection) |
|
7721 { |
|
7722 // get selection point |
|
7723 nsCOMPtr<nsIDOMNode> selNode; |
|
7724 int32_t selOffset; |
|
7725 NS_ENSURE_STATE(mHTMLEditor); |
|
7726 nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); |
|
7727 NS_ENSURE_SUCCESS(res, res); |
|
7728 |
|
7729 // ask whitespace object to tweak nbsp's |
|
7730 NS_ENSURE_STATE(mHTMLEditor); |
|
7731 return nsWSRunObject(mHTMLEditor, selNode, selOffset).AdjustWhitespace(); |
|
7732 } |
|
7733 |
|
7734 nsresult |
|
7735 nsHTMLEditRules::PinSelectionToNewBlock(nsISelection *aSelection) |
|
7736 { |
|
7737 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); |
|
7738 if (!aSelection->Collapsed()) { |
|
7739 return NS_OK; |
|
7740 } |
|
7741 |
|
7742 // get the (collapsed) selection location |
|
7743 nsCOMPtr<nsIDOMNode> selNode, temp; |
|
7744 int32_t selOffset; |
|
7745 NS_ENSURE_STATE(mHTMLEditor); |
|
7746 nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); |
|
7747 NS_ENSURE_SUCCESS(res, res); |
|
7748 temp = selNode; |
|
7749 |
|
7750 // use ranges and sRangeHelper to compare sel point to new block |
|
7751 nsCOMPtr<nsINode> node = do_QueryInterface(selNode); |
|
7752 NS_ENSURE_STATE(node); |
|
7753 nsRefPtr<nsRange> range = new nsRange(node); |
|
7754 res = range->SetStart(selNode, selOffset); |
|
7755 NS_ENSURE_SUCCESS(res, res); |
|
7756 res = range->SetEnd(selNode, selOffset); |
|
7757 NS_ENSURE_SUCCESS(res, res); |
|
7758 nsCOMPtr<nsIContent> block (do_QueryInterface(mNewBlock)); |
|
7759 NS_ENSURE_TRUE(block, NS_ERROR_NO_INTERFACE); |
|
7760 bool nodeBefore, nodeAfter; |
|
7761 res = nsRange::CompareNodeToRange(block, range, &nodeBefore, &nodeAfter); |
|
7762 NS_ENSURE_SUCCESS(res, res); |
|
7763 |
|
7764 if (nodeBefore && nodeAfter) |
|
7765 return NS_OK; // selection is inside block |
|
7766 else if (nodeBefore) |
|
7767 { |
|
7768 // selection is after block. put at end of block. |
|
7769 nsCOMPtr<nsIDOMNode> tmp = mNewBlock; |
|
7770 NS_ENSURE_STATE(mHTMLEditor); |
|
7771 mHTMLEditor->GetLastEditableChild(mNewBlock, address_of(tmp)); |
|
7772 uint32_t endPoint; |
|
7773 NS_ENSURE_STATE(mHTMLEditor); |
|
7774 if (mHTMLEditor->IsTextNode(tmp) || !mHTMLEditor || |
|
7775 mHTMLEditor->IsContainer(tmp)) |
|
7776 { |
|
7777 NS_ENSURE_STATE(mHTMLEditor); |
|
7778 res = nsEditor::GetLengthOfDOMNode(tmp, endPoint); |
|
7779 NS_ENSURE_SUCCESS(res, res); |
|
7780 } |
|
7781 else |
|
7782 { |
|
7783 tmp = nsEditor::GetNodeLocation(tmp, (int32_t*)&endPoint); |
|
7784 endPoint++; // want to be after this node |
|
7785 } |
|
7786 return aSelection->Collapse(tmp, (int32_t)endPoint); |
|
7787 } |
|
7788 else |
|
7789 { |
|
7790 // selection is before block. put at start of block. |
|
7791 nsCOMPtr<nsIDOMNode> tmp = mNewBlock; |
|
7792 NS_ENSURE_STATE(mHTMLEditor); |
|
7793 mHTMLEditor->GetFirstEditableChild(mNewBlock, address_of(tmp)); |
|
7794 int32_t offset; |
|
7795 if (!(mHTMLEditor->IsTextNode(tmp) || !mHTMLEditor || |
|
7796 mHTMLEditor->IsContainer(tmp))) |
|
7797 { |
|
7798 tmp = nsEditor::GetNodeLocation(tmp, &offset); |
|
7799 } |
|
7800 NS_ENSURE_STATE(mHTMLEditor); |
|
7801 return aSelection->Collapse(tmp, 0); |
|
7802 } |
|
7803 } |
|
7804 |
|
7805 nsresult |
|
7806 nsHTMLEditRules::CheckInterlinePosition(nsISelection *aSelection) |
|
7807 { |
|
7808 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); |
|
7809 nsCOMPtr<nsISelection> selection(aSelection); |
|
7810 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection)); |
|
7811 |
|
7812 // if the selection isn't collapsed, do nothing. |
|
7813 if (!aSelection->Collapsed()) { |
|
7814 return NS_OK; |
|
7815 } |
|
7816 |
|
7817 // get the (collapsed) selection location |
|
7818 nsCOMPtr<nsIDOMNode> selNode, node; |
|
7819 int32_t selOffset; |
|
7820 NS_ENSURE_STATE(mHTMLEditor); |
|
7821 nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); |
|
7822 NS_ENSURE_SUCCESS(res, res); |
|
7823 |
|
7824 // First, let's check to see if we are after a <br>. We take care of this |
|
7825 // special-case first so that we don't accidentally fall through into one |
|
7826 // of the other conditionals. |
|
7827 NS_ENSURE_STATE(mHTMLEditor); |
|
7828 mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(node), true); |
|
7829 if (node && nsTextEditUtils::IsBreak(node)) |
|
7830 { |
|
7831 selPriv->SetInterlinePosition(true); |
|
7832 return NS_OK; |
|
7833 } |
|
7834 |
|
7835 // are we after a block? If so try set caret to following content |
|
7836 NS_ENSURE_STATE(mHTMLEditor); |
|
7837 mHTMLEditor->GetPriorHTMLSibling(selNode, selOffset, address_of(node)); |
|
7838 if (node && IsBlockNode(node)) |
|
7839 { |
|
7840 selPriv->SetInterlinePosition(true); |
|
7841 return NS_OK; |
|
7842 } |
|
7843 |
|
7844 // are we before a block? If so try set caret to prior content |
|
7845 NS_ENSURE_STATE(mHTMLEditor); |
|
7846 mHTMLEditor->GetNextHTMLSibling(selNode, selOffset, address_of(node)); |
|
7847 if (node && IsBlockNode(node)) |
|
7848 selPriv->SetInterlinePosition(false); |
|
7849 return NS_OK; |
|
7850 } |
|
7851 |
|
7852 nsresult |
|
7853 nsHTMLEditRules::AdjustSelection(nsISelection *aSelection, nsIEditor::EDirection aAction) |
|
7854 { |
|
7855 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); |
|
7856 nsCOMPtr<nsISelection> selection(aSelection); |
|
7857 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection)); |
|
7858 |
|
7859 // if the selection isn't collapsed, do nothing. |
|
7860 // moose: one thing to do instead is check for the case of |
|
7861 // only a single break selected, and collapse it. Good thing? Beats me. |
|
7862 if (!aSelection->Collapsed()) { |
|
7863 return NS_OK; |
|
7864 } |
|
7865 |
|
7866 // get the (collapsed) selection location |
|
7867 nsCOMPtr<nsIDOMNode> selNode, temp; |
|
7868 int32_t selOffset; |
|
7869 NS_ENSURE_STATE(mHTMLEditor); |
|
7870 nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); |
|
7871 NS_ENSURE_SUCCESS(res, res); |
|
7872 temp = selNode; |
|
7873 |
|
7874 // are we in an editable node? |
|
7875 NS_ENSURE_STATE(mHTMLEditor); |
|
7876 while (!mHTMLEditor->IsEditable(selNode)) |
|
7877 { |
|
7878 // scan up the tree until we find an editable place to be |
|
7879 selNode = nsEditor::GetNodeLocation(temp, &selOffset); |
|
7880 NS_ENSURE_TRUE(selNode, NS_ERROR_FAILURE); |
|
7881 temp = selNode; |
|
7882 NS_ENSURE_STATE(mHTMLEditor); |
|
7883 } |
|
7884 |
|
7885 // make sure we aren't in an empty block - user will see no cursor. If this |
|
7886 // is happening, put a <br> in the block if allowed. |
|
7887 nsCOMPtr<nsIDOMNode> theblock; |
|
7888 if (IsBlockNode(selNode)) { |
|
7889 theblock = selNode; |
|
7890 } else { |
|
7891 NS_ENSURE_STATE(mHTMLEditor); |
|
7892 theblock = mHTMLEditor->GetBlockNodeParent(selNode); |
|
7893 } |
|
7894 NS_ENSURE_STATE(mHTMLEditor); |
|
7895 if (theblock && mHTMLEditor->IsEditable(theblock)) { |
|
7896 bool bIsEmptyNode; |
|
7897 NS_ENSURE_STATE(mHTMLEditor); |
|
7898 res = mHTMLEditor->IsEmptyNode(theblock, &bIsEmptyNode, false, false); |
|
7899 NS_ENSURE_SUCCESS(res, res); |
|
7900 // check if br can go into the destination node |
|
7901 NS_ENSURE_STATE(mHTMLEditor); |
|
7902 if (bIsEmptyNode && mHTMLEditor->CanContainTag(selNode, nsGkAtoms::br)) { |
|
7903 NS_ENSURE_STATE(mHTMLEditor); |
|
7904 nsCOMPtr<nsIDOMNode> rootNode = do_QueryInterface(mHTMLEditor->GetRoot()); |
|
7905 NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE); |
|
7906 if (selNode == rootNode) |
|
7907 { |
|
7908 // Our root node is completely empty. Don't add a <br> here. |
|
7909 // AfterEditInner() will add one for us when it calls |
|
7910 // CreateBogusNodeIfNeeded()! |
|
7911 return NS_OK; |
|
7912 } |
|
7913 |
|
7914 // we know we can skip the rest of this routine given the cirumstance |
|
7915 return CreateMozBR(selNode, selOffset); |
|
7916 } |
|
7917 } |
|
7918 |
|
7919 // are we in a text node? |
|
7920 nsCOMPtr<nsIDOMCharacterData> textNode = do_QueryInterface(selNode); |
|
7921 if (textNode) |
|
7922 return NS_OK; // we LIKE it when we are in a text node. that RULZ |
|
7923 |
|
7924 // do we need to insert a special mozBR? We do if we are: |
|
7925 // 1) prior node is in same block where selection is AND |
|
7926 // 2) prior node is a br AND |
|
7927 // 3) that br is not visible |
|
7928 |
|
7929 nsCOMPtr<nsIDOMNode> nearNode; |
|
7930 NS_ENSURE_STATE(mHTMLEditor); |
|
7931 res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(nearNode)); |
|
7932 NS_ENSURE_SUCCESS(res, res); |
|
7933 if (nearNode) |
|
7934 { |
|
7935 // is nearNode also a descendant of same block? |
|
7936 nsCOMPtr<nsIDOMNode> block, nearBlock; |
|
7937 if (IsBlockNode(selNode)) { |
|
7938 block = selNode; |
|
7939 } else { |
|
7940 NS_ENSURE_STATE(mHTMLEditor); |
|
7941 block = mHTMLEditor->GetBlockNodeParent(selNode); |
|
7942 } |
|
7943 NS_ENSURE_STATE(mHTMLEditor); |
|
7944 nearBlock = mHTMLEditor->GetBlockNodeParent(nearNode); |
|
7945 if (block == nearBlock) |
|
7946 { |
|
7947 if (nearNode && nsTextEditUtils::IsBreak(nearNode) ) |
|
7948 { |
|
7949 NS_ENSURE_STATE(mHTMLEditor); |
|
7950 if (!mHTMLEditor->IsVisBreak(nearNode)) |
|
7951 { |
|
7952 // need to insert special moz BR. Why? Because if we don't |
|
7953 // the user will see no new line for the break. Also, things |
|
7954 // like table cells won't grow in height. |
|
7955 nsCOMPtr<nsIDOMNode> brNode; |
|
7956 res = CreateMozBR(selNode, selOffset, getter_AddRefs(brNode)); |
|
7957 NS_ENSURE_SUCCESS(res, res); |
|
7958 selNode = nsEditor::GetNodeLocation(brNode, &selOffset); |
|
7959 // selection stays *before* moz-br, sticking to it |
|
7960 selPriv->SetInterlinePosition(true); |
|
7961 res = aSelection->Collapse(selNode,selOffset); |
|
7962 NS_ENSURE_SUCCESS(res, res); |
|
7963 } |
|
7964 else |
|
7965 { |
|
7966 nsCOMPtr<nsIDOMNode> nextNode; |
|
7967 NS_ENSURE_STATE(mHTMLEditor); |
|
7968 mHTMLEditor->GetNextHTMLNode(nearNode, address_of(nextNode), true); |
|
7969 if (nextNode && nsTextEditUtils::IsMozBR(nextNode)) |
|
7970 { |
|
7971 // selection between br and mozbr. make it stick to mozbr |
|
7972 // so that it will be on blank line. |
|
7973 selPriv->SetInterlinePosition(true); |
|
7974 } |
|
7975 } |
|
7976 } |
|
7977 } |
|
7978 } |
|
7979 |
|
7980 // we aren't in a textnode: are we adjacent to text or a break or an image? |
|
7981 NS_ENSURE_STATE(mHTMLEditor); |
|
7982 res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(nearNode), true); |
|
7983 NS_ENSURE_SUCCESS(res, res); |
|
7984 if (nearNode && (nsTextEditUtils::IsBreak(nearNode) |
|
7985 || nsEditor::IsTextNode(nearNode) |
|
7986 || nsHTMLEditUtils::IsImage(nearNode) |
|
7987 || nsHTMLEditUtils::IsHR(nearNode))) |
|
7988 return NS_OK; // this is a good place for the caret to be |
|
7989 NS_ENSURE_STATE(mHTMLEditor); |
|
7990 res = mHTMLEditor->GetNextHTMLNode(selNode, selOffset, address_of(nearNode), true); |
|
7991 NS_ENSURE_SUCCESS(res, res); |
|
7992 if (nearNode && (nsTextEditUtils::IsBreak(nearNode) |
|
7993 || nsEditor::IsTextNode(nearNode) |
|
7994 || nsHTMLEditUtils::IsImage(nearNode) |
|
7995 || nsHTMLEditUtils::IsHR(nearNode))) |
|
7996 return NS_OK; // this is a good place for the caret to be |
|
7997 |
|
7998 // look for a nearby text node. |
|
7999 // prefer the correct direction. |
|
8000 res = FindNearSelectableNode(selNode, selOffset, aAction, address_of(nearNode)); |
|
8001 NS_ENSURE_SUCCESS(res, res); |
|
8002 |
|
8003 if (nearNode) |
|
8004 { |
|
8005 // is the nearnode a text node? |
|
8006 textNode = do_QueryInterface(nearNode); |
|
8007 if (textNode) |
|
8008 { |
|
8009 int32_t offset = 0; |
|
8010 // put selection in right place: |
|
8011 if (aAction == nsIEditor::ePrevious) |
|
8012 textNode->GetLength((uint32_t*)&offset); |
|
8013 res = aSelection->Collapse(nearNode,offset); |
|
8014 } |
|
8015 else // must be break or image |
|
8016 { |
|
8017 selNode = nsEditor::GetNodeLocation(nearNode, &selOffset); |
|
8018 if (aAction == nsIEditor::ePrevious) selOffset++; // want to be beyond it if we backed up to it |
|
8019 res = aSelection->Collapse(selNode, selOffset); |
|
8020 } |
|
8021 } |
|
8022 return res; |
|
8023 } |
|
8024 |
|
8025 |
|
8026 nsresult |
|
8027 nsHTMLEditRules::FindNearSelectableNode(nsIDOMNode *aSelNode, |
|
8028 int32_t aSelOffset, |
|
8029 nsIEditor::EDirection &aDirection, |
|
8030 nsCOMPtr<nsIDOMNode> *outSelectableNode) |
|
8031 { |
|
8032 NS_ENSURE_TRUE(aSelNode && outSelectableNode, NS_ERROR_NULL_POINTER); |
|
8033 *outSelectableNode = nullptr; |
|
8034 nsresult res = NS_OK; |
|
8035 |
|
8036 nsCOMPtr<nsIDOMNode> nearNode, curNode; |
|
8037 if (aDirection == nsIEditor::ePrevious) { |
|
8038 NS_ENSURE_STATE(mHTMLEditor); |
|
8039 res = mHTMLEditor->GetPriorHTMLNode(aSelNode, aSelOffset, address_of(nearNode)); |
|
8040 } else { |
|
8041 NS_ENSURE_STATE(mHTMLEditor); |
|
8042 res = mHTMLEditor->GetNextHTMLNode(aSelNode, aSelOffset, address_of(nearNode)); |
|
8043 } |
|
8044 NS_ENSURE_SUCCESS(res, res); |
|
8045 |
|
8046 if (!nearNode) // try the other direction then |
|
8047 { |
|
8048 if (aDirection == nsIEditor::ePrevious) |
|
8049 aDirection = nsIEditor::eNext; |
|
8050 else |
|
8051 aDirection = nsIEditor::ePrevious; |
|
8052 |
|
8053 if (aDirection == nsIEditor::ePrevious) { |
|
8054 NS_ENSURE_STATE(mHTMLEditor); |
|
8055 res = mHTMLEditor->GetPriorHTMLNode(aSelNode, aSelOffset, address_of(nearNode)); |
|
8056 } else { |
|
8057 NS_ENSURE_STATE(mHTMLEditor); |
|
8058 res = mHTMLEditor->GetNextHTMLNode(aSelNode, aSelOffset, address_of(nearNode)); |
|
8059 } |
|
8060 NS_ENSURE_SUCCESS(res, res); |
|
8061 } |
|
8062 |
|
8063 // scan in the right direction until we find an eligible text node, |
|
8064 // but don't cross any breaks, images, or table elements. |
|
8065 NS_ENSURE_STATE(mHTMLEditor); |
|
8066 while (nearNode && !(mHTMLEditor->IsTextNode(nearNode) |
|
8067 || nsTextEditUtils::IsBreak(nearNode) |
|
8068 || nsHTMLEditUtils::IsImage(nearNode))) |
|
8069 { |
|
8070 curNode = nearNode; |
|
8071 if (aDirection == nsIEditor::ePrevious) { |
|
8072 NS_ENSURE_STATE(mHTMLEditor); |
|
8073 res = mHTMLEditor->GetPriorHTMLNode(curNode, address_of(nearNode)); |
|
8074 } else { |
|
8075 NS_ENSURE_STATE(mHTMLEditor); |
|
8076 res = mHTMLEditor->GetNextHTMLNode(curNode, address_of(nearNode)); |
|
8077 } |
|
8078 NS_ENSURE_SUCCESS(res, res); |
|
8079 NS_ENSURE_STATE(mHTMLEditor); |
|
8080 } |
|
8081 |
|
8082 if (nearNode) |
|
8083 { |
|
8084 // don't cross any table elements |
|
8085 if (InDifferentTableElements(nearNode, aSelNode)) { |
|
8086 return NS_OK; |
|
8087 } |
|
8088 |
|
8089 // otherwise, ok, we have found a good spot to put the selection |
|
8090 *outSelectableNode = do_QueryInterface(nearNode); |
|
8091 } |
|
8092 return res; |
|
8093 } |
|
8094 |
|
8095 |
|
8096 bool nsHTMLEditRules::InDifferentTableElements(nsIDOMNode* aNode1, |
|
8097 nsIDOMNode* aNode2) |
|
8098 { |
|
8099 nsCOMPtr<nsINode> node1 = do_QueryInterface(aNode1); |
|
8100 nsCOMPtr<nsINode> node2 = do_QueryInterface(aNode2); |
|
8101 return InDifferentTableElements(node1, node2); |
|
8102 } |
|
8103 |
|
8104 bool |
|
8105 nsHTMLEditRules::InDifferentTableElements(nsINode* aNode1, nsINode* aNode2) |
|
8106 { |
|
8107 MOZ_ASSERT(aNode1 && aNode2); |
|
8108 |
|
8109 while (aNode1 && !nsHTMLEditUtils::IsTableElement(aNode1)) { |
|
8110 aNode1 = aNode1->GetParentNode(); |
|
8111 } |
|
8112 |
|
8113 while (aNode2 && !nsHTMLEditUtils::IsTableElement(aNode2)) { |
|
8114 aNode2 = aNode2->GetParentNode(); |
|
8115 } |
|
8116 |
|
8117 return aNode1 != aNode2; |
|
8118 } |
|
8119 |
|
8120 |
|
8121 nsresult |
|
8122 nsHTMLEditRules::RemoveEmptyNodes() |
|
8123 { |
|
8124 // some general notes on the algorithm used here: the goal is to examine all the |
|
8125 // nodes in mDocChangeRange, and remove the empty ones. We do this by using a |
|
8126 // content iterator to traverse all the nodes in the range, and placing the empty |
|
8127 // nodes into an array. After finishing the iteration, we delete the empty nodes |
|
8128 // in the array. (they cannot be deleted as we find them becasue that would |
|
8129 // invalidate the iterator.) |
|
8130 // Since checking to see if a node is empty can be costly for nodes with many |
|
8131 // descendants, there are some optimizations made. I rely on the fact that the |
|
8132 // iterator is post-order: it will visit children of a node before visiting the |
|
8133 // parent node. So if I find that a child node is not empty, I know that its |
|
8134 // parent is not empty without even checking. So I put the parent on a "skipList" |
|
8135 // which is just a voidArray of nodes I can skip the empty check on. If I |
|
8136 // encounter a node on the skiplist, i skip the processing for that node and replace |
|
8137 // its slot in the skiplist with that node's parent. |
|
8138 // An interseting idea is to go ahead and regard parent nodes that are NOT on the |
|
8139 // skiplist as being empty (without even doing the IsEmptyNode check) on the theory |
|
8140 // that if they weren't empty, we would have encountered a non-empty child earlier |
|
8141 // and thus put this parent node on the skiplist. |
|
8142 // Unfortunately I can't use that strategy here, because the range may include |
|
8143 // some children of a node while excluding others. Thus I could find all the |
|
8144 // _examined_ children empty, but still not have an empty parent. |
|
8145 |
|
8146 // need an iterator |
|
8147 nsCOMPtr<nsIContentIterator> iter = |
|
8148 do_CreateInstance("@mozilla.org/content/post-content-iterator;1"); |
|
8149 NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER); |
|
8150 |
|
8151 nsresult res = iter->Init(mDocChangeRange); |
|
8152 NS_ENSURE_SUCCESS(res, res); |
|
8153 |
|
8154 nsCOMArray<nsINode> arrayOfEmptyNodes, arrayOfEmptyCites; |
|
8155 nsTArray<nsCOMPtr<nsINode> > skipList; |
|
8156 |
|
8157 // check for empty nodes |
|
8158 while (!iter->IsDone()) { |
|
8159 nsINode* node = iter->GetCurrentNode(); |
|
8160 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); |
|
8161 |
|
8162 nsINode* parent = node->GetParentNode(); |
|
8163 |
|
8164 uint32_t idx = skipList.IndexOf(node); |
|
8165 if (idx != skipList.NoIndex) { |
|
8166 // this node is on our skip list. Skip processing for this node, |
|
8167 // and replace its value in the skip list with the value of its parent |
|
8168 skipList[idx] = parent; |
|
8169 } else { |
|
8170 bool bIsCandidate = false; |
|
8171 bool bIsEmptyNode = false; |
|
8172 bool bIsMailCite = false; |
|
8173 |
|
8174 if (node->IsElement()) { |
|
8175 dom::Element* element = node->AsElement(); |
|
8176 if (element->IsHTML(nsGkAtoms::body)) { |
|
8177 // don't delete the body |
|
8178 } else if ((bIsMailCite = nsHTMLEditUtils::IsMailCite(element)) || |
|
8179 element->IsHTML(nsGkAtoms::a) || |
|
8180 nsHTMLEditUtils::IsInlineStyle(element) || |
|
8181 nsHTMLEditUtils::IsList(element) || |
|
8182 element->IsHTML(nsGkAtoms::div)) { |
|
8183 // only consider certain nodes to be empty for purposes of removal |
|
8184 bIsCandidate = true; |
|
8185 } else if (nsHTMLEditUtils::IsFormatNode(element) || |
|
8186 nsHTMLEditUtils::IsListItem(element) || |
|
8187 element->IsHTML(nsGkAtoms::blockquote)) { |
|
8188 // these node types are candidates if selection is not in them |
|
8189 // if it is one of these, don't delete if selection inside. |
|
8190 // this is so we can create empty headings, etc, for the |
|
8191 // user to type into. |
|
8192 bool bIsSelInNode; |
|
8193 res = SelectionEndpointInNode(node, &bIsSelInNode); |
|
8194 NS_ENSURE_SUCCESS(res, res); |
|
8195 if (!bIsSelInNode) |
|
8196 { |
|
8197 bIsCandidate = true; |
|
8198 } |
|
8199 } |
|
8200 } |
|
8201 |
|
8202 if (bIsCandidate) { |
|
8203 // we delete mailcites even if they have a solo br in them |
|
8204 // other nodes we require to be empty |
|
8205 NS_ENSURE_STATE(mHTMLEditor); |
|
8206 res = mHTMLEditor->IsEmptyNode(node->AsDOMNode(), &bIsEmptyNode, |
|
8207 bIsMailCite, true); |
|
8208 NS_ENSURE_SUCCESS(res, res); |
|
8209 if (bIsEmptyNode) { |
|
8210 if (bIsMailCite) { |
|
8211 // mailcites go on a separate list from other empty nodes |
|
8212 arrayOfEmptyCites.AppendObject(node); |
|
8213 } else { |
|
8214 arrayOfEmptyNodes.AppendObject(node); |
|
8215 } |
|
8216 } |
|
8217 } |
|
8218 |
|
8219 if (!bIsEmptyNode) { |
|
8220 // put parent on skip list |
|
8221 skipList.AppendElement(parent); |
|
8222 } |
|
8223 } |
|
8224 |
|
8225 iter->Next(); |
|
8226 } |
|
8227 |
|
8228 // now delete the empty nodes |
|
8229 int32_t nodeCount = arrayOfEmptyNodes.Count(); |
|
8230 for (int32_t j = 0; j < nodeCount; j++) { |
|
8231 nsCOMPtr<nsIDOMNode> delNode = arrayOfEmptyNodes[0]->AsDOMNode(); |
|
8232 arrayOfEmptyNodes.RemoveObjectAt(0); |
|
8233 NS_ENSURE_STATE(mHTMLEditor); |
|
8234 if (mHTMLEditor->IsModifiableNode(delNode)) { |
|
8235 NS_ENSURE_STATE(mHTMLEditor); |
|
8236 res = mHTMLEditor->DeleteNode(delNode); |
|
8237 NS_ENSURE_SUCCESS(res, res); |
|
8238 } |
|
8239 } |
|
8240 |
|
8241 // now delete the empty mailcites |
|
8242 // this is a separate step because we want to pull out any br's and preserve them. |
|
8243 nodeCount = arrayOfEmptyCites.Count(); |
|
8244 for (int32_t j = 0; j < nodeCount; j++) { |
|
8245 nsCOMPtr<nsIDOMNode> delNode = arrayOfEmptyCites[0]->AsDOMNode(); |
|
8246 arrayOfEmptyCites.RemoveObjectAt(0); |
|
8247 bool bIsEmptyNode; |
|
8248 NS_ENSURE_STATE(mHTMLEditor); |
|
8249 res = mHTMLEditor->IsEmptyNode(delNode, &bIsEmptyNode, false, true); |
|
8250 NS_ENSURE_SUCCESS(res, res); |
|
8251 if (!bIsEmptyNode) |
|
8252 { |
|
8253 // we are deleting a cite that has just a br. We want to delete cite, |
|
8254 // but preserve br. |
|
8255 nsCOMPtr<nsIDOMNode> parent, brNode; |
|
8256 int32_t offset; |
|
8257 parent = nsEditor::GetNodeLocation(delNode, &offset); |
|
8258 NS_ENSURE_STATE(mHTMLEditor); |
|
8259 res = mHTMLEditor->CreateBR(parent, offset, address_of(brNode)); |
|
8260 NS_ENSURE_SUCCESS(res, res); |
|
8261 } |
|
8262 NS_ENSURE_STATE(mHTMLEditor); |
|
8263 res = mHTMLEditor->DeleteNode(delNode); |
|
8264 NS_ENSURE_SUCCESS(res, res); |
|
8265 } |
|
8266 |
|
8267 return res; |
|
8268 } |
|
8269 |
|
8270 nsresult |
|
8271 nsHTMLEditRules::SelectionEndpointInNode(nsINode* aNode, bool* aResult) |
|
8272 { |
|
8273 NS_ENSURE_TRUE(aNode && aResult, NS_ERROR_NULL_POINTER); |
|
8274 |
|
8275 nsIDOMNode* node = aNode->AsDOMNode(); |
|
8276 |
|
8277 *aResult = false; |
|
8278 |
|
8279 nsCOMPtr<nsISelection>selection; |
|
8280 NS_ENSURE_STATE(mHTMLEditor); |
|
8281 nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); |
|
8282 NS_ENSURE_SUCCESS(res, res); |
|
8283 |
|
8284 Selection* sel = static_cast<Selection*>(selection.get()); |
|
8285 uint32_t rangeCount = sel->GetRangeCount(); |
|
8286 for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { |
|
8287 nsRefPtr<nsRange> range = sel->GetRangeAt(rangeIdx); |
|
8288 nsCOMPtr<nsIDOMNode> startParent, endParent; |
|
8289 range->GetStartContainer(getter_AddRefs(startParent)); |
|
8290 if (startParent) |
|
8291 { |
|
8292 if (node == startParent) { |
|
8293 *aResult = true; |
|
8294 return NS_OK; |
|
8295 } |
|
8296 if (nsEditorUtils::IsDescendantOf(startParent, node)) { |
|
8297 *aResult = true; |
|
8298 return NS_OK; |
|
8299 } |
|
8300 } |
|
8301 range->GetEndContainer(getter_AddRefs(endParent)); |
|
8302 if (startParent == endParent) continue; |
|
8303 if (endParent) |
|
8304 { |
|
8305 if (node == endParent) { |
|
8306 *aResult = true; |
|
8307 return NS_OK; |
|
8308 } |
|
8309 if (nsEditorUtils::IsDescendantOf(endParent, node)) { |
|
8310 *aResult = true; |
|
8311 return NS_OK; |
|
8312 } |
|
8313 } |
|
8314 } |
|
8315 return res; |
|
8316 } |
|
8317 |
|
8318 /////////////////////////////////////////////////////////////////////////// |
|
8319 // IsEmptyInline: return true if aNode is an empty inline container |
|
8320 // |
|
8321 // |
|
8322 bool |
|
8323 nsHTMLEditRules::IsEmptyInline(nsIDOMNode *aNode) |
|
8324 { |
|
8325 if (aNode && IsInlineNode(aNode) && mHTMLEditor && |
|
8326 mHTMLEditor->IsContainer(aNode)) |
|
8327 { |
|
8328 bool bEmpty; |
|
8329 NS_ENSURE_TRUE(mHTMLEditor, false); |
|
8330 mHTMLEditor->IsEmptyNode(aNode, &bEmpty); |
|
8331 return bEmpty; |
|
8332 } |
|
8333 return false; |
|
8334 } |
|
8335 |
|
8336 |
|
8337 bool |
|
8338 nsHTMLEditRules::ListIsEmptyLine(nsCOMArray<nsIDOMNode> &arrayOfNodes) |
|
8339 { |
|
8340 // we have a list of nodes which we are candidates for being moved |
|
8341 // into a new block. Determine if it's anything more than a blank line. |
|
8342 // Look for editable content above and beyond one single BR. |
|
8343 int32_t listCount = arrayOfNodes.Count(); |
|
8344 NS_ENSURE_TRUE(listCount, true); |
|
8345 nsCOMPtr<nsIDOMNode> somenode; |
|
8346 int32_t j, brCount=0; |
|
8347 for (j = 0; j < listCount; j++) |
|
8348 { |
|
8349 somenode = arrayOfNodes[j]; |
|
8350 NS_ENSURE_TRUE(mHTMLEditor, false); |
|
8351 if (somenode && mHTMLEditor->IsEditable(somenode)) |
|
8352 { |
|
8353 if (nsTextEditUtils::IsBreak(somenode)) |
|
8354 { |
|
8355 // first break doesn't count |
|
8356 if (brCount) return false; |
|
8357 brCount++; |
|
8358 } |
|
8359 else if (IsEmptyInline(somenode)) |
|
8360 { |
|
8361 // empty inline, keep looking |
|
8362 } |
|
8363 else return false; |
|
8364 } |
|
8365 } |
|
8366 return true; |
|
8367 } |
|
8368 |
|
8369 |
|
8370 nsresult |
|
8371 nsHTMLEditRules::PopListItem(nsIDOMNode *aListItem, bool *aOutOfList) |
|
8372 { |
|
8373 // check parms |
|
8374 NS_ENSURE_TRUE(aListItem && aOutOfList, NS_ERROR_NULL_POINTER); |
|
8375 |
|
8376 // init out params |
|
8377 *aOutOfList = false; |
|
8378 |
|
8379 nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(aListItem)); |
|
8380 int32_t offset; |
|
8381 nsCOMPtr<nsIDOMNode> curParent = nsEditor::GetNodeLocation(curNode, &offset); |
|
8382 |
|
8383 if (!nsHTMLEditUtils::IsListItem(curNode)) |
|
8384 return NS_ERROR_FAILURE; |
|
8385 |
|
8386 // if it's first or last list item, don't need to split the list |
|
8387 // otherwise we do. |
|
8388 int32_t parOffset; |
|
8389 nsCOMPtr<nsIDOMNode> curParPar = nsEditor::GetNodeLocation(curParent, &parOffset); |
|
8390 |
|
8391 bool bIsFirstListItem; |
|
8392 NS_ENSURE_STATE(mHTMLEditor); |
|
8393 nsresult res = mHTMLEditor->IsFirstEditableChild(curNode, &bIsFirstListItem); |
|
8394 NS_ENSURE_SUCCESS(res, res); |
|
8395 |
|
8396 bool bIsLastListItem; |
|
8397 NS_ENSURE_STATE(mHTMLEditor); |
|
8398 res = mHTMLEditor->IsLastEditableChild(curNode, &bIsLastListItem); |
|
8399 NS_ENSURE_SUCCESS(res, res); |
|
8400 |
|
8401 if (!bIsFirstListItem && !bIsLastListItem) |
|
8402 { |
|
8403 // split the list |
|
8404 nsCOMPtr<nsIDOMNode> newBlock; |
|
8405 NS_ENSURE_STATE(mHTMLEditor); |
|
8406 res = mHTMLEditor->SplitNode(curParent, offset, getter_AddRefs(newBlock)); |
|
8407 NS_ENSURE_SUCCESS(res, res); |
|
8408 } |
|
8409 |
|
8410 if (!bIsFirstListItem) parOffset++; |
|
8411 |
|
8412 NS_ENSURE_STATE(mHTMLEditor); |
|
8413 res = mHTMLEditor->MoveNode(curNode, curParPar, parOffset); |
|
8414 NS_ENSURE_SUCCESS(res, res); |
|
8415 |
|
8416 // unwrap list item contents if they are no longer in a list |
|
8417 if (!nsHTMLEditUtils::IsList(curParPar) |
|
8418 && nsHTMLEditUtils::IsListItem(curNode)) |
|
8419 { |
|
8420 NS_ENSURE_STATE(mHTMLEditor); |
|
8421 res = mHTMLEditor->RemoveBlockContainer(curNode); |
|
8422 NS_ENSURE_SUCCESS(res, res); |
|
8423 *aOutOfList = true; |
|
8424 } |
|
8425 return res; |
|
8426 } |
|
8427 |
|
8428 nsresult |
|
8429 nsHTMLEditRules::RemoveListStructure(nsIDOMNode *aList) |
|
8430 { |
|
8431 NS_ENSURE_ARG_POINTER(aList); |
|
8432 |
|
8433 nsresult res; |
|
8434 |
|
8435 nsCOMPtr<nsIDOMNode> child; |
|
8436 aList->GetFirstChild(getter_AddRefs(child)); |
|
8437 |
|
8438 while (child) |
|
8439 { |
|
8440 if (nsHTMLEditUtils::IsListItem(child)) |
|
8441 { |
|
8442 bool bOutOfList; |
|
8443 do |
|
8444 { |
|
8445 res = PopListItem(child, &bOutOfList); |
|
8446 NS_ENSURE_SUCCESS(res, res); |
|
8447 } while (!bOutOfList); // keep popping it out until it's not in a list anymore |
|
8448 } |
|
8449 else if (nsHTMLEditUtils::IsList(child)) |
|
8450 { |
|
8451 res = RemoveListStructure(child); |
|
8452 NS_ENSURE_SUCCESS(res, res); |
|
8453 } |
|
8454 else |
|
8455 { |
|
8456 // delete any non- list items for now |
|
8457 NS_ENSURE_STATE(mHTMLEditor); |
|
8458 res = mHTMLEditor->DeleteNode(child); |
|
8459 NS_ENSURE_SUCCESS(res, res); |
|
8460 } |
|
8461 aList->GetFirstChild(getter_AddRefs(child)); |
|
8462 } |
|
8463 // delete the now-empty list |
|
8464 NS_ENSURE_STATE(mHTMLEditor); |
|
8465 res = mHTMLEditor->RemoveBlockContainer(aList); |
|
8466 NS_ENSURE_SUCCESS(res, res); |
|
8467 |
|
8468 return res; |
|
8469 } |
|
8470 |
|
8471 |
|
8472 nsresult |
|
8473 nsHTMLEditRules::ConfirmSelectionInBody() |
|
8474 { |
|
8475 nsresult res = NS_OK; |
|
8476 |
|
8477 // get the body |
|
8478 NS_ENSURE_STATE(mHTMLEditor); |
|
8479 nsCOMPtr<nsIDOMElement> rootElement = do_QueryInterface(mHTMLEditor->GetRoot()); |
|
8480 NS_ENSURE_TRUE(rootElement, NS_ERROR_UNEXPECTED); |
|
8481 |
|
8482 // get the selection |
|
8483 nsCOMPtr<nsISelection>selection; |
|
8484 NS_ENSURE_STATE(mHTMLEditor); |
|
8485 res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); |
|
8486 NS_ENSURE_SUCCESS(res, res); |
|
8487 |
|
8488 // get the selection start location |
|
8489 nsCOMPtr<nsIDOMNode> selNode, temp, parent; |
|
8490 int32_t selOffset; |
|
8491 NS_ENSURE_STATE(mHTMLEditor); |
|
8492 res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); |
|
8493 NS_ENSURE_SUCCESS(res, res); |
|
8494 temp = selNode; |
|
8495 |
|
8496 // check that selNode is inside body |
|
8497 while (temp && !nsTextEditUtils::IsBody(temp)) |
|
8498 { |
|
8499 res = temp->GetParentNode(getter_AddRefs(parent)); |
|
8500 temp = parent; |
|
8501 } |
|
8502 |
|
8503 // if we aren't in the body, force the issue |
|
8504 if (!temp) |
|
8505 { |
|
8506 // uncomment this to see when we get bad selections |
|
8507 // NS_NOTREACHED("selection not in body"); |
|
8508 selection->Collapse(rootElement, 0); |
|
8509 } |
|
8510 |
|
8511 // get the selection end location |
|
8512 NS_ENSURE_STATE(mHTMLEditor); |
|
8513 res = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); |
|
8514 NS_ENSURE_SUCCESS(res, res); |
|
8515 temp = selNode; |
|
8516 |
|
8517 // check that selNode is inside body |
|
8518 while (temp && !nsTextEditUtils::IsBody(temp)) |
|
8519 { |
|
8520 res = temp->GetParentNode(getter_AddRefs(parent)); |
|
8521 temp = parent; |
|
8522 } |
|
8523 |
|
8524 // if we aren't in the body, force the issue |
|
8525 if (!temp) |
|
8526 { |
|
8527 // uncomment this to see when we get bad selections |
|
8528 // NS_NOTREACHED("selection not in body"); |
|
8529 selection->Collapse(rootElement, 0); |
|
8530 } |
|
8531 |
|
8532 return res; |
|
8533 } |
|
8534 |
|
8535 |
|
8536 nsresult |
|
8537 nsHTMLEditRules::UpdateDocChangeRange(nsIDOMRange *aRange) |
|
8538 { |
|
8539 nsresult res = NS_OK; |
|
8540 |
|
8541 // first make sure aRange is in the document. It might not be if |
|
8542 // portions of our editting action involved manipulating nodes |
|
8543 // prior to placing them in the document (e.g., populating a list item |
|
8544 // before placing it in its list) |
|
8545 nsCOMPtr<nsIDOMNode> startNode; |
|
8546 res = aRange->GetStartContainer(getter_AddRefs(startNode)); |
|
8547 NS_ENSURE_SUCCESS(res, res); |
|
8548 NS_ENSURE_STATE(mHTMLEditor); |
|
8549 if (!mHTMLEditor->IsDescendantOfRoot(startNode)) { |
|
8550 // just return - we don't need to adjust mDocChangeRange in this case |
|
8551 return NS_OK; |
|
8552 } |
|
8553 |
|
8554 if (!mDocChangeRange) |
|
8555 { |
|
8556 // clone aRange. |
|
8557 nsCOMPtr<nsIDOMRange> range; |
|
8558 res = aRange->CloneRange(getter_AddRefs(range)); |
|
8559 mDocChangeRange = static_cast<nsRange*>(range.get()); |
|
8560 } |
|
8561 else |
|
8562 { |
|
8563 int16_t result; |
|
8564 |
|
8565 // compare starts of ranges |
|
8566 res = mDocChangeRange->CompareBoundaryPoints(nsIDOMRange::START_TO_START, aRange, &result); |
|
8567 if (res == NS_ERROR_NOT_INITIALIZED) { |
|
8568 // This will happen is mDocChangeRange is non-null, but the range is |
|
8569 // uninitialized. In this case we'll set the start to aRange start. |
|
8570 // The same test won't be needed further down since after we've set |
|
8571 // the start the range will be collapsed to that point. |
|
8572 result = 1; |
|
8573 res = NS_OK; |
|
8574 } |
|
8575 NS_ENSURE_SUCCESS(res, res); |
|
8576 if (result > 0) // positive result means mDocChangeRange start is after aRange start |
|
8577 { |
|
8578 int32_t startOffset; |
|
8579 res = aRange->GetStartOffset(&startOffset); |
|
8580 NS_ENSURE_SUCCESS(res, res); |
|
8581 res = mDocChangeRange->SetStart(startNode, startOffset); |
|
8582 NS_ENSURE_SUCCESS(res, res); |
|
8583 } |
|
8584 |
|
8585 // compare ends of ranges |
|
8586 res = mDocChangeRange->CompareBoundaryPoints(nsIDOMRange::END_TO_END, aRange, &result); |
|
8587 NS_ENSURE_SUCCESS(res, res); |
|
8588 if (result < 0) // negative result means mDocChangeRange end is before aRange end |
|
8589 { |
|
8590 nsCOMPtr<nsIDOMNode> endNode; |
|
8591 int32_t endOffset; |
|
8592 res = aRange->GetEndContainer(getter_AddRefs(endNode)); |
|
8593 NS_ENSURE_SUCCESS(res, res); |
|
8594 res = aRange->GetEndOffset(&endOffset); |
|
8595 NS_ENSURE_SUCCESS(res, res); |
|
8596 res = mDocChangeRange->SetEnd(endNode, endOffset); |
|
8597 NS_ENSURE_SUCCESS(res, res); |
|
8598 } |
|
8599 } |
|
8600 return res; |
|
8601 } |
|
8602 |
|
8603 nsresult |
|
8604 nsHTMLEditRules::InsertMozBRIfNeeded(nsIDOMNode *aNode) |
|
8605 { |
|
8606 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); |
|
8607 if (!IsBlockNode(aNode)) return NS_OK; |
|
8608 |
|
8609 bool isEmpty; |
|
8610 NS_ENSURE_STATE(mHTMLEditor); |
|
8611 nsresult res = mHTMLEditor->IsEmptyNode(aNode, &isEmpty); |
|
8612 NS_ENSURE_SUCCESS(res, res); |
|
8613 if (!isEmpty) { |
|
8614 return NS_OK; |
|
8615 } |
|
8616 |
|
8617 return CreateMozBR(aNode, 0); |
|
8618 } |
|
8619 |
|
8620 NS_IMETHODIMP |
|
8621 nsHTMLEditRules::WillCreateNode(const nsAString& aTag, nsIDOMNode *aParent, int32_t aPosition) |
|
8622 { |
|
8623 return NS_OK; |
|
8624 } |
|
8625 |
|
8626 NS_IMETHODIMP |
|
8627 nsHTMLEditRules::DidCreateNode(const nsAString& aTag, |
|
8628 nsIDOMNode *aNode, |
|
8629 nsIDOMNode *aParent, |
|
8630 int32_t aPosition, |
|
8631 nsresult aResult) |
|
8632 { |
|
8633 if (!mListenerEnabled) { |
|
8634 return NS_OK; |
|
8635 } |
|
8636 // assumption that Join keeps the righthand node |
|
8637 nsresult res = mUtilRange->SelectNode(aNode); |
|
8638 NS_ENSURE_SUCCESS(res, res); |
|
8639 res = UpdateDocChangeRange(mUtilRange); |
|
8640 return res; |
|
8641 } |
|
8642 |
|
8643 |
|
8644 NS_IMETHODIMP |
|
8645 nsHTMLEditRules::WillInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, int32_t aPosition) |
|
8646 { |
|
8647 return NS_OK; |
|
8648 } |
|
8649 |
|
8650 |
|
8651 NS_IMETHODIMP |
|
8652 nsHTMLEditRules::DidInsertNode(nsIDOMNode *aNode, |
|
8653 nsIDOMNode *aParent, |
|
8654 int32_t aPosition, |
|
8655 nsresult aResult) |
|
8656 { |
|
8657 if (!mListenerEnabled) { |
|
8658 return NS_OK; |
|
8659 } |
|
8660 nsresult res = mUtilRange->SelectNode(aNode); |
|
8661 NS_ENSURE_SUCCESS(res, res); |
|
8662 res = UpdateDocChangeRange(mUtilRange); |
|
8663 return res; |
|
8664 } |
|
8665 |
|
8666 |
|
8667 NS_IMETHODIMP |
|
8668 nsHTMLEditRules::WillDeleteNode(nsIDOMNode *aChild) |
|
8669 { |
|
8670 if (!mListenerEnabled) { |
|
8671 return NS_OK; |
|
8672 } |
|
8673 nsresult res = mUtilRange->SelectNode(aChild); |
|
8674 NS_ENSURE_SUCCESS(res, res); |
|
8675 res = UpdateDocChangeRange(mUtilRange); |
|
8676 return res; |
|
8677 } |
|
8678 |
|
8679 |
|
8680 NS_IMETHODIMP |
|
8681 nsHTMLEditRules::DidDeleteNode(nsIDOMNode *aChild, nsresult aResult) |
|
8682 { |
|
8683 return NS_OK; |
|
8684 } |
|
8685 |
|
8686 |
|
8687 NS_IMETHODIMP |
|
8688 nsHTMLEditRules::WillSplitNode(nsIDOMNode *aExistingRightNode, int32_t aOffset) |
|
8689 { |
|
8690 return NS_OK; |
|
8691 } |
|
8692 |
|
8693 |
|
8694 NS_IMETHODIMP |
|
8695 nsHTMLEditRules::DidSplitNode(nsIDOMNode *aExistingRightNode, |
|
8696 int32_t aOffset, |
|
8697 nsIDOMNode *aNewLeftNode, |
|
8698 nsresult aResult) |
|
8699 { |
|
8700 if (!mListenerEnabled) { |
|
8701 return NS_OK; |
|
8702 } |
|
8703 nsresult res = mUtilRange->SetStart(aNewLeftNode, 0); |
|
8704 NS_ENSURE_SUCCESS(res, res); |
|
8705 res = mUtilRange->SetEnd(aExistingRightNode, 0); |
|
8706 NS_ENSURE_SUCCESS(res, res); |
|
8707 res = UpdateDocChangeRange(mUtilRange); |
|
8708 return res; |
|
8709 } |
|
8710 |
|
8711 |
|
8712 NS_IMETHODIMP |
|
8713 nsHTMLEditRules::WillJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent) |
|
8714 { |
|
8715 if (!mListenerEnabled) { |
|
8716 return NS_OK; |
|
8717 } |
|
8718 // remember split point |
|
8719 nsresult res = nsEditor::GetLengthOfDOMNode(aLeftNode, mJoinOffset); |
|
8720 return res; |
|
8721 } |
|
8722 |
|
8723 |
|
8724 NS_IMETHODIMP |
|
8725 nsHTMLEditRules::DidJoinNodes(nsIDOMNode *aLeftNode, |
|
8726 nsIDOMNode *aRightNode, |
|
8727 nsIDOMNode *aParent, |
|
8728 nsresult aResult) |
|
8729 { |
|
8730 if (!mListenerEnabled) { |
|
8731 return NS_OK; |
|
8732 } |
|
8733 // assumption that Join keeps the righthand node |
|
8734 nsresult res = mUtilRange->SetStart(aRightNode, mJoinOffset); |
|
8735 NS_ENSURE_SUCCESS(res, res); |
|
8736 res = mUtilRange->SetEnd(aRightNode, mJoinOffset); |
|
8737 NS_ENSURE_SUCCESS(res, res); |
|
8738 res = UpdateDocChangeRange(mUtilRange); |
|
8739 return res; |
|
8740 } |
|
8741 |
|
8742 |
|
8743 NS_IMETHODIMP |
|
8744 nsHTMLEditRules::WillInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString &aString) |
|
8745 { |
|
8746 return NS_OK; |
|
8747 } |
|
8748 |
|
8749 |
|
8750 NS_IMETHODIMP |
|
8751 nsHTMLEditRules::DidInsertText(nsIDOMCharacterData *aTextNode, |
|
8752 int32_t aOffset, |
|
8753 const nsAString &aString, |
|
8754 nsresult aResult) |
|
8755 { |
|
8756 if (!mListenerEnabled) { |
|
8757 return NS_OK; |
|
8758 } |
|
8759 int32_t length = aString.Length(); |
|
8760 nsCOMPtr<nsIDOMNode> theNode = do_QueryInterface(aTextNode); |
|
8761 nsresult res = mUtilRange->SetStart(theNode, aOffset); |
|
8762 NS_ENSURE_SUCCESS(res, res); |
|
8763 res = mUtilRange->SetEnd(theNode, aOffset+length); |
|
8764 NS_ENSURE_SUCCESS(res, res); |
|
8765 res = UpdateDocChangeRange(mUtilRange); |
|
8766 return res; |
|
8767 } |
|
8768 |
|
8769 |
|
8770 NS_IMETHODIMP |
|
8771 nsHTMLEditRules::WillDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength) |
|
8772 { |
|
8773 return NS_OK; |
|
8774 } |
|
8775 |
|
8776 |
|
8777 NS_IMETHODIMP |
|
8778 nsHTMLEditRules::DidDeleteText(nsIDOMCharacterData *aTextNode, |
|
8779 int32_t aOffset, |
|
8780 int32_t aLength, |
|
8781 nsresult aResult) |
|
8782 { |
|
8783 if (!mListenerEnabled) { |
|
8784 return NS_OK; |
|
8785 } |
|
8786 nsCOMPtr<nsIDOMNode> theNode = do_QueryInterface(aTextNode); |
|
8787 nsresult res = mUtilRange->SetStart(theNode, aOffset); |
|
8788 NS_ENSURE_SUCCESS(res, res); |
|
8789 res = mUtilRange->SetEnd(theNode, aOffset); |
|
8790 NS_ENSURE_SUCCESS(res, res); |
|
8791 res = UpdateDocChangeRange(mUtilRange); |
|
8792 return res; |
|
8793 } |
|
8794 |
|
8795 NS_IMETHODIMP |
|
8796 nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection) |
|
8797 { |
|
8798 if (!mListenerEnabled) { |
|
8799 return NS_OK; |
|
8800 } |
|
8801 // get the (collapsed) selection location |
|
8802 nsCOMPtr<nsIDOMNode> selNode; |
|
8803 int32_t selOffset; |
|
8804 |
|
8805 NS_ENSURE_STATE(mHTMLEditor); |
|
8806 nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); |
|
8807 NS_ENSURE_SUCCESS(res, res); |
|
8808 res = mUtilRange->SetStart(selNode, selOffset); |
|
8809 NS_ENSURE_SUCCESS(res, res); |
|
8810 NS_ENSURE_STATE(mHTMLEditor); |
|
8811 res = mHTMLEditor->GetEndNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); |
|
8812 NS_ENSURE_SUCCESS(res, res); |
|
8813 res = mUtilRange->SetEnd(selNode, selOffset); |
|
8814 NS_ENSURE_SUCCESS(res, res); |
|
8815 res = UpdateDocChangeRange(mUtilRange); |
|
8816 return res; |
|
8817 } |
|
8818 |
|
8819 NS_IMETHODIMP |
|
8820 nsHTMLEditRules::DidDeleteSelection(nsISelection *aSelection) |
|
8821 { |
|
8822 return NS_OK; |
|
8823 } |
|
8824 |
|
8825 // Let's remove all alignment hints in the children of aNode; it can |
|
8826 // be an ALIGN attribute (in case we just remove it) or a CENTER |
|
8827 // element (here we have to remove the container and keep its |
|
8828 // children). We break on tables and don't look at their children. |
|
8829 nsresult |
|
8830 nsHTMLEditRules::RemoveAlignment(nsIDOMNode * aNode, const nsAString & aAlignType, bool aChildrenOnly) |
|
8831 { |
|
8832 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); |
|
8833 |
|
8834 NS_ENSURE_STATE(mHTMLEditor); |
|
8835 if (mHTMLEditor->IsTextNode(aNode) || nsHTMLEditUtils::IsTable(aNode)) return NS_OK; |
|
8836 nsresult res = NS_OK; |
|
8837 |
|
8838 nsCOMPtr<nsIDOMNode> child = aNode,tmp; |
|
8839 if (aChildrenOnly) |
|
8840 { |
|
8841 aNode->GetFirstChild(getter_AddRefs(child)); |
|
8842 } |
|
8843 NS_ENSURE_STATE(mHTMLEditor); |
|
8844 bool useCSS = mHTMLEditor->IsCSSEnabled(); |
|
8845 |
|
8846 while (child) |
|
8847 { |
|
8848 if (aChildrenOnly) { |
|
8849 // get the next sibling right now because we could have to remove child |
|
8850 child->GetNextSibling(getter_AddRefs(tmp)); |
|
8851 } |
|
8852 else |
|
8853 { |
|
8854 tmp = nullptr; |
|
8855 } |
|
8856 bool isBlock; |
|
8857 NS_ENSURE_STATE(mHTMLEditor); |
|
8858 res = mHTMLEditor->NodeIsBlockStatic(child, &isBlock); |
|
8859 NS_ENSURE_SUCCESS(res, res); |
|
8860 |
|
8861 if (nsEditor::NodeIsType(child, nsEditProperty::center)) |
|
8862 { |
|
8863 // the current node is a CENTER element |
|
8864 // first remove children's alignment |
|
8865 res = RemoveAlignment(child, aAlignType, true); |
|
8866 NS_ENSURE_SUCCESS(res, res); |
|
8867 |
|
8868 // we may have to insert BRs in first and last position of element's children |
|
8869 // if the nodes before/after are not blocks and not BRs |
|
8870 res = MakeSureElemStartsOrEndsOnCR(child); |
|
8871 NS_ENSURE_SUCCESS(res, res); |
|
8872 |
|
8873 // now remove the CENTER container |
|
8874 NS_ENSURE_STATE(mHTMLEditor); |
|
8875 res = mHTMLEditor->RemoveContainer(child); |
|
8876 NS_ENSURE_SUCCESS(res, res); |
|
8877 } |
|
8878 else if (isBlock || nsHTMLEditUtils::IsHR(child)) |
|
8879 { |
|
8880 // the current node is a block element |
|
8881 nsCOMPtr<nsIDOMElement> curElem = do_QueryInterface(child); |
|
8882 if (nsHTMLEditUtils::SupportsAlignAttr(child)) |
|
8883 { |
|
8884 // remove the ALIGN attribute if this element can have it |
|
8885 NS_ENSURE_STATE(mHTMLEditor); |
|
8886 res = mHTMLEditor->RemoveAttribute(curElem, NS_LITERAL_STRING("align")); |
|
8887 NS_ENSURE_SUCCESS(res, res); |
|
8888 } |
|
8889 if (useCSS) |
|
8890 { |
|
8891 if (nsHTMLEditUtils::IsTable(child) || nsHTMLEditUtils::IsHR(child)) |
|
8892 { |
|
8893 NS_ENSURE_STATE(mHTMLEditor); |
|
8894 res = mHTMLEditor->SetAttributeOrEquivalent(curElem, NS_LITERAL_STRING("align"), aAlignType, false); |
|
8895 } |
|
8896 else |
|
8897 { |
|
8898 nsAutoString dummyCssValue; |
|
8899 NS_ENSURE_STATE(mHTMLEditor); |
|
8900 res = mHTMLEditor->mHTMLCSSUtils->RemoveCSSInlineStyle(child, nsEditProperty::cssTextAlign, dummyCssValue); |
|
8901 } |
|
8902 NS_ENSURE_SUCCESS(res, res); |
|
8903 } |
|
8904 if (!nsHTMLEditUtils::IsTable(child)) |
|
8905 { |
|
8906 // unless this is a table, look at children |
|
8907 res = RemoveAlignment(child, aAlignType, true); |
|
8908 NS_ENSURE_SUCCESS(res, res); |
|
8909 } |
|
8910 } |
|
8911 child = tmp; |
|
8912 } |
|
8913 return NS_OK; |
|
8914 } |
|
8915 |
|
8916 // Let's insert a BR as first (resp. last) child of aNode if its |
|
8917 // first (resp. last) child is not a block nor a BR, and if the |
|
8918 // previous (resp. next) sibling is not a block nor a BR |
|
8919 nsresult |
|
8920 nsHTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsIDOMNode *aNode, bool aStarts) |
|
8921 { |
|
8922 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); |
|
8923 |
|
8924 nsCOMPtr<nsIDOMNode> child; |
|
8925 nsresult res; |
|
8926 if (aStarts) |
|
8927 { |
|
8928 NS_ENSURE_STATE(mHTMLEditor); |
|
8929 res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(child)); |
|
8930 } |
|
8931 else |
|
8932 { |
|
8933 NS_ENSURE_STATE(mHTMLEditor); |
|
8934 res = mHTMLEditor->GetLastEditableChild(aNode, address_of(child)); |
|
8935 } |
|
8936 NS_ENSURE_SUCCESS(res, res); |
|
8937 NS_ENSURE_TRUE(child, NS_OK); |
|
8938 bool isChildBlock; |
|
8939 NS_ENSURE_STATE(mHTMLEditor); |
|
8940 res = mHTMLEditor->NodeIsBlockStatic(child, &isChildBlock); |
|
8941 NS_ENSURE_SUCCESS(res, res); |
|
8942 bool foundCR = false; |
|
8943 if (isChildBlock || nsTextEditUtils::IsBreak(child)) |
|
8944 { |
|
8945 foundCR = true; |
|
8946 } |
|
8947 else |
|
8948 { |
|
8949 nsCOMPtr<nsIDOMNode> sibling; |
|
8950 if (aStarts) |
|
8951 { |
|
8952 NS_ENSURE_STATE(mHTMLEditor); |
|
8953 res = mHTMLEditor->GetPriorHTMLSibling(aNode, address_of(sibling)); |
|
8954 } |
|
8955 else |
|
8956 { |
|
8957 NS_ENSURE_STATE(mHTMLEditor); |
|
8958 res = mHTMLEditor->GetNextHTMLSibling(aNode, address_of(sibling)); |
|
8959 } |
|
8960 NS_ENSURE_SUCCESS(res, res); |
|
8961 if (sibling) |
|
8962 { |
|
8963 bool isBlock; |
|
8964 NS_ENSURE_STATE(mHTMLEditor); |
|
8965 res = mHTMLEditor->NodeIsBlockStatic(sibling, &isBlock); |
|
8966 NS_ENSURE_SUCCESS(res, res); |
|
8967 if (isBlock || nsTextEditUtils::IsBreak(sibling)) |
|
8968 { |
|
8969 foundCR = true; |
|
8970 } |
|
8971 } |
|
8972 else |
|
8973 { |
|
8974 foundCR = true; |
|
8975 } |
|
8976 } |
|
8977 if (!foundCR) |
|
8978 { |
|
8979 int32_t offset = 0; |
|
8980 if (!aStarts) { |
|
8981 nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
|
8982 NS_ENSURE_STATE(node); |
|
8983 offset = node->GetChildCount(); |
|
8984 } |
|
8985 nsCOMPtr<nsIDOMNode> brNode; |
|
8986 NS_ENSURE_STATE(mHTMLEditor); |
|
8987 res = mHTMLEditor->CreateBR(aNode, offset, address_of(brNode)); |
|
8988 NS_ENSURE_SUCCESS(res, res); |
|
8989 } |
|
8990 return NS_OK; |
|
8991 } |
|
8992 |
|
8993 nsresult |
|
8994 nsHTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsIDOMNode *aNode) |
|
8995 { |
|
8996 nsresult res = MakeSureElemStartsOrEndsOnCR(aNode, false); |
|
8997 NS_ENSURE_SUCCESS(res, res); |
|
8998 res = MakeSureElemStartsOrEndsOnCR(aNode, true); |
|
8999 return res; |
|
9000 } |
|
9001 |
|
9002 nsresult |
|
9003 nsHTMLEditRules::AlignBlock(nsIDOMElement * aElement, const nsAString * aAlignType, bool aContentsOnly) |
|
9004 { |
|
9005 NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); |
|
9006 |
|
9007 nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement); |
|
9008 bool isBlock = IsBlockNode(node); |
|
9009 if (!isBlock && !nsHTMLEditUtils::IsHR(node)) { |
|
9010 // we deal only with blocks; early way out |
|
9011 return NS_OK; |
|
9012 } |
|
9013 |
|
9014 nsresult res = RemoveAlignment(node, *aAlignType, aContentsOnly); |
|
9015 NS_ENSURE_SUCCESS(res, res); |
|
9016 NS_NAMED_LITERAL_STRING(attr, "align"); |
|
9017 NS_ENSURE_STATE(mHTMLEditor); |
|
9018 if (mHTMLEditor->IsCSSEnabled()) { |
|
9019 // let's use CSS alignment; we use margin-left and margin-right for tables |
|
9020 // and text-align for other block-level elements |
|
9021 NS_ENSURE_STATE(mHTMLEditor); |
|
9022 res = mHTMLEditor->SetAttributeOrEquivalent(aElement, attr, *aAlignType, false); |
|
9023 NS_ENSURE_SUCCESS(res, res); |
|
9024 } |
|
9025 else { |
|
9026 // HTML case; this code is supposed to be called ONLY if the element |
|
9027 // supports the align attribute but we'll never know... |
|
9028 if (nsHTMLEditUtils::SupportsAlignAttr(node)) { |
|
9029 NS_ENSURE_STATE(mHTMLEditor); |
|
9030 res = mHTMLEditor->SetAttribute(aElement, attr, *aAlignType); |
|
9031 NS_ENSURE_SUCCESS(res, res); |
|
9032 } |
|
9033 } |
|
9034 return NS_OK; |
|
9035 } |
|
9036 |
|
9037 nsresult |
|
9038 nsHTMLEditRules::RelativeChangeIndentationOfElementNode(nsIDOMNode *aNode, int8_t aRelativeChange) |
|
9039 { |
|
9040 NS_ENSURE_ARG_POINTER(aNode); |
|
9041 |
|
9042 if (aRelativeChange != 1 && aRelativeChange != -1) { |
|
9043 return NS_ERROR_ILLEGAL_VALUE; |
|
9044 } |
|
9045 |
|
9046 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode); |
|
9047 if (!element) { |
|
9048 return NS_OK; |
|
9049 } |
|
9050 |
|
9051 NS_ENSURE_STATE(mHTMLEditor); |
|
9052 nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, element); |
|
9053 nsAutoString value; |
|
9054 NS_ENSURE_STATE(mHTMLEditor); |
|
9055 mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(aNode, marginProperty, value); |
|
9056 float f; |
|
9057 nsCOMPtr<nsIAtom> unit; |
|
9058 NS_ENSURE_STATE(mHTMLEditor); |
|
9059 mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit)); |
|
9060 if (0 == f) { |
|
9061 nsAutoString defaultLengthUnit; |
|
9062 NS_ENSURE_STATE(mHTMLEditor); |
|
9063 mHTMLEditor->mHTMLCSSUtils->GetDefaultLengthUnit(defaultLengthUnit); |
|
9064 unit = do_GetAtom(defaultLengthUnit); |
|
9065 } |
|
9066 if (nsEditProperty::cssInUnit == unit) |
|
9067 f += NS_EDITOR_INDENT_INCREMENT_IN * aRelativeChange; |
|
9068 else if (nsEditProperty::cssCmUnit == unit) |
|
9069 f += NS_EDITOR_INDENT_INCREMENT_CM * aRelativeChange; |
|
9070 else if (nsEditProperty::cssMmUnit == unit) |
|
9071 f += NS_EDITOR_INDENT_INCREMENT_MM * aRelativeChange; |
|
9072 else if (nsEditProperty::cssPtUnit == unit) |
|
9073 f += NS_EDITOR_INDENT_INCREMENT_PT * aRelativeChange; |
|
9074 else if (nsEditProperty::cssPcUnit == unit) |
|
9075 f += NS_EDITOR_INDENT_INCREMENT_PC * aRelativeChange; |
|
9076 else if (nsEditProperty::cssEmUnit == unit) |
|
9077 f += NS_EDITOR_INDENT_INCREMENT_EM * aRelativeChange; |
|
9078 else if (nsEditProperty::cssExUnit == unit) |
|
9079 f += NS_EDITOR_INDENT_INCREMENT_EX * aRelativeChange; |
|
9080 else if (nsEditProperty::cssPxUnit == unit) |
|
9081 f += NS_EDITOR_INDENT_INCREMENT_PX * aRelativeChange; |
|
9082 else if (nsEditProperty::cssPercentUnit == unit) |
|
9083 f += NS_EDITOR_INDENT_INCREMENT_PERCENT * aRelativeChange; |
|
9084 |
|
9085 if (0 < f) { |
|
9086 nsAutoString newValue; |
|
9087 newValue.AppendFloat(f); |
|
9088 newValue.Append(nsDependentAtomString(unit)); |
|
9089 NS_ENSURE_STATE(mHTMLEditor); |
|
9090 mHTMLEditor->mHTMLCSSUtils->SetCSSProperty(element, marginProperty, newValue, false); |
|
9091 return NS_OK; |
|
9092 } |
|
9093 |
|
9094 NS_ENSURE_STATE(mHTMLEditor); |
|
9095 mHTMLEditor->mHTMLCSSUtils->RemoveCSSProperty(element, marginProperty, value, false); |
|
9096 |
|
9097 // remove unnecessary DIV blocks: |
|
9098 // we could skip this section but that would cause a FAIL in |
|
9099 // editor/libeditor/html/tests/browserscope/richtext.html, which expects |
|
9100 // to unapply a CSS "indent" (<div style="margin-left: 40px;">) by |
|
9101 // removing the DIV container instead of just removing the CSS property. |
|
9102 nsCOMPtr<dom::Element> node = do_QueryInterface(aNode); |
|
9103 if (!node || !node->IsHTML(nsGkAtoms::div) || |
|
9104 !mHTMLEditor || |
|
9105 node == mHTMLEditor->GetActiveEditingHost() || |
|
9106 !mHTMLEditor->IsDescendantOfEditorRoot(node) || |
|
9107 nsHTMLEditor::HasAttributes(node)) { |
|
9108 NS_ENSURE_STATE(mHTMLEditor); |
|
9109 return NS_OK; |
|
9110 } |
|
9111 |
|
9112 NS_ENSURE_STATE(mHTMLEditor); |
|
9113 return mHTMLEditor->RemoveContainer(element); |
|
9114 } |
|
9115 |
|
9116 // |
|
9117 // Support for Absolute Positioning |
|
9118 // |
|
9119 |
|
9120 nsresult |
|
9121 nsHTMLEditRules::WillAbsolutePosition(Selection* aSelection, |
|
9122 bool* aCancel, bool* aHandled) |
|
9123 { |
|
9124 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } |
|
9125 nsresult res = WillInsert(aSelection, aCancel); |
|
9126 NS_ENSURE_SUCCESS(res, res); |
|
9127 |
|
9128 // initialize out param |
|
9129 // we want to ignore result of WillInsert() |
|
9130 *aCancel = false; |
|
9131 *aHandled = true; |
|
9132 |
|
9133 nsCOMPtr<nsIDOMElement> focusElement; |
|
9134 NS_ENSURE_STATE(mHTMLEditor); |
|
9135 res = mHTMLEditor->GetSelectionContainer(getter_AddRefs(focusElement)); |
|
9136 if (focusElement) { |
|
9137 nsCOMPtr<nsIDOMNode> node = do_QueryInterface(focusElement); |
|
9138 if (nsHTMLEditUtils::IsImage(node)) { |
|
9139 mNewBlock = node; |
|
9140 return NS_OK; |
|
9141 } |
|
9142 } |
|
9143 |
|
9144 res = NormalizeSelection(aSelection); |
|
9145 NS_ENSURE_SUCCESS(res, res); |
|
9146 NS_ENSURE_STATE(mHTMLEditor); |
|
9147 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); |
|
9148 |
|
9149 // convert the selection ranges into "promoted" selection ranges: |
|
9150 // this basically just expands the range to include the immediate |
|
9151 // block parent, and then further expands to include any ancestors |
|
9152 // whose children are all in the range |
|
9153 |
|
9154 nsCOMArray<nsIDOMRange> arrayOfRanges; |
|
9155 res = GetPromotedRanges(aSelection, arrayOfRanges, |
|
9156 EditAction::setAbsolutePosition); |
|
9157 NS_ENSURE_SUCCESS(res, res); |
|
9158 |
|
9159 // use these ranges to contruct a list of nodes to act on. |
|
9160 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
9161 res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, |
|
9162 EditAction::setAbsolutePosition); |
|
9163 NS_ENSURE_SUCCESS(res, res); |
|
9164 |
|
9165 NS_NAMED_LITERAL_STRING(divType, "div"); |
|
9166 |
|
9167 |
|
9168 // if nothing visible in list, make an empty block |
|
9169 if (ListIsEmptyLine(arrayOfNodes)) |
|
9170 { |
|
9171 nsCOMPtr<nsIDOMNode> parent, thePositionedDiv; |
|
9172 int32_t offset; |
|
9173 |
|
9174 // get selection location |
|
9175 NS_ENSURE_STATE(mHTMLEditor); |
|
9176 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); |
|
9177 NS_ENSURE_SUCCESS(res, res); |
|
9178 // make sure we can put a block here |
|
9179 res = SplitAsNeeded(&divType, address_of(parent), &offset); |
|
9180 NS_ENSURE_SUCCESS(res, res); |
|
9181 NS_ENSURE_STATE(mHTMLEditor); |
|
9182 res = mHTMLEditor->CreateNode(divType, parent, offset, getter_AddRefs(thePositionedDiv)); |
|
9183 NS_ENSURE_SUCCESS(res, res); |
|
9184 // remember our new block for postprocessing |
|
9185 mNewBlock = thePositionedDiv; |
|
9186 // delete anything that was in the list of nodes |
|
9187 for (int32_t j = arrayOfNodes.Count() - 1; j >= 0; --j) |
|
9188 { |
|
9189 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[0]; |
|
9190 NS_ENSURE_STATE(mHTMLEditor); |
|
9191 res = mHTMLEditor->DeleteNode(curNode); |
|
9192 NS_ENSURE_SUCCESS(res, res); |
|
9193 arrayOfNodes.RemoveObjectAt(0); |
|
9194 } |
|
9195 // put selection in new block |
|
9196 res = aSelection->Collapse(thePositionedDiv,0); |
|
9197 selectionResetter.Abort(); // to prevent selection reseter from overriding us. |
|
9198 *aHandled = true; |
|
9199 return res; |
|
9200 } |
|
9201 |
|
9202 // Ok, now go through all the nodes and put them in a blockquote, |
|
9203 // or whatever is appropriate. Wohoo! |
|
9204 int32_t i; |
|
9205 nsCOMPtr<nsIDOMNode> curParent, curPositionedDiv, curList, indentedLI, sibling; |
|
9206 int32_t listCount = arrayOfNodes.Count(); |
|
9207 for (i=0; i<listCount; i++) |
|
9208 { |
|
9209 // here's where we actually figure out what to do |
|
9210 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i]; |
|
9211 |
|
9212 // Ignore all non-editable nodes. Leave them be. |
|
9213 NS_ENSURE_STATE(mHTMLEditor); |
|
9214 if (!mHTMLEditor->IsEditable(curNode)) continue; |
|
9215 |
|
9216 int32_t offset; |
|
9217 curParent = nsEditor::GetNodeLocation(curNode, &offset); |
|
9218 |
|
9219 // some logic for putting list items into nested lists... |
|
9220 if (nsHTMLEditUtils::IsList(curParent)) |
|
9221 { |
|
9222 // check to see if curList is still appropriate. Which it is if |
|
9223 // curNode is still right after it in the same list. |
|
9224 if (curList) |
|
9225 { |
|
9226 sibling = nullptr; |
|
9227 NS_ENSURE_STATE(mHTMLEditor); |
|
9228 mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); |
|
9229 } |
|
9230 |
|
9231 if (!curList || (sibling && sibling != curList) ) |
|
9232 { |
|
9233 nsAutoString listTag; |
|
9234 nsEditor::GetTagString(curParent,listTag); |
|
9235 ToLowerCase(listTag); |
|
9236 // create a new nested list of correct type |
|
9237 res = SplitAsNeeded(&listTag, address_of(curParent), &offset); |
|
9238 NS_ENSURE_SUCCESS(res, res); |
|
9239 if (!curPositionedDiv) { |
|
9240 int32_t parentOffset; |
|
9241 nsCOMPtr<nsIDOMNode> curParentParent = nsEditor::GetNodeLocation(curParent, &parentOffset); |
|
9242 NS_ENSURE_STATE(mHTMLEditor); |
|
9243 res = mHTMLEditor->CreateNode(divType, curParentParent, parentOffset, getter_AddRefs(curPositionedDiv)); |
|
9244 mNewBlock = curPositionedDiv; |
|
9245 } |
|
9246 NS_ENSURE_STATE(mHTMLEditor); |
|
9247 res = mHTMLEditor->CreateNode(listTag, curPositionedDiv, -1, getter_AddRefs(curList)); |
|
9248 NS_ENSURE_SUCCESS(res, res); |
|
9249 // curList is now the correct thing to put curNode in |
|
9250 // remember our new block for postprocessing |
|
9251 // mNewBlock = curList; |
|
9252 } |
|
9253 // tuck the node into the end of the active list |
|
9254 NS_ENSURE_STATE(mHTMLEditor); |
|
9255 res = mHTMLEditor->MoveNode(curNode, curList, -1); |
|
9256 NS_ENSURE_SUCCESS(res, res); |
|
9257 // forget curPositionedDiv, if any |
|
9258 // curPositionedDiv = nullptr; |
|
9259 } |
|
9260 |
|
9261 else // not a list item, use blockquote? |
|
9262 { |
|
9263 // if we are inside a list item, we don't want to blockquote, we want |
|
9264 // to sublist the list item. We may have several nodes listed in the |
|
9265 // array of nodes to act on, that are in the same list item. Since |
|
9266 // we only want to indent that li once, we must keep track of the most |
|
9267 // recent indented list item, and not indent it if we find another node |
|
9268 // to act on that is still inside the same li. |
|
9269 nsCOMPtr<nsIDOMNode> listitem=IsInListItem(curNode); |
|
9270 if (listitem) |
|
9271 { |
|
9272 if (indentedLI == listitem) continue; // already indented this list item |
|
9273 curParent = nsEditor::GetNodeLocation(listitem, &offset); |
|
9274 // check to see if curList is still appropriate. Which it is if |
|
9275 // curNode is still right after it in the same list. |
|
9276 if (curList) |
|
9277 { |
|
9278 sibling = nullptr; |
|
9279 NS_ENSURE_STATE(mHTMLEditor); |
|
9280 mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); |
|
9281 } |
|
9282 |
|
9283 if (!curList || (sibling && sibling != curList) ) |
|
9284 { |
|
9285 nsAutoString listTag; |
|
9286 nsEditor::GetTagString(curParent,listTag); |
|
9287 ToLowerCase(listTag); |
|
9288 // create a new nested list of correct type |
|
9289 res = SplitAsNeeded(&listTag, address_of(curParent), &offset); |
|
9290 NS_ENSURE_SUCCESS(res, res); |
|
9291 if (!curPositionedDiv) { |
|
9292 int32_t parentOffset; |
|
9293 nsCOMPtr<nsIDOMNode> curParentParent = nsEditor::GetNodeLocation(curParent, &parentOffset); |
|
9294 NS_ENSURE_STATE(mHTMLEditor); |
|
9295 res = mHTMLEditor->CreateNode(divType, curParentParent, parentOffset, getter_AddRefs(curPositionedDiv)); |
|
9296 mNewBlock = curPositionedDiv; |
|
9297 } |
|
9298 NS_ENSURE_STATE(mHTMLEditor); |
|
9299 res = mHTMLEditor->CreateNode(listTag, curPositionedDiv, -1, getter_AddRefs(curList)); |
|
9300 NS_ENSURE_SUCCESS(res, res); |
|
9301 } |
|
9302 NS_ENSURE_STATE(mHTMLEditor); |
|
9303 res = mHTMLEditor->MoveNode(listitem, curList, -1); |
|
9304 NS_ENSURE_SUCCESS(res, res); |
|
9305 // remember we indented this li |
|
9306 indentedLI = listitem; |
|
9307 } |
|
9308 |
|
9309 else |
|
9310 { |
|
9311 // need to make a div to put things in if we haven't already |
|
9312 |
|
9313 if (!curPositionedDiv) |
|
9314 { |
|
9315 if (nsHTMLEditUtils::IsDiv(curNode)) |
|
9316 { |
|
9317 curPositionedDiv = curNode; |
|
9318 mNewBlock = curPositionedDiv; |
|
9319 curList = nullptr; |
|
9320 continue; |
|
9321 } |
|
9322 res = SplitAsNeeded(&divType, address_of(curParent), &offset); |
|
9323 NS_ENSURE_SUCCESS(res, res); |
|
9324 NS_ENSURE_STATE(mHTMLEditor); |
|
9325 res = mHTMLEditor->CreateNode(divType, curParent, offset, getter_AddRefs(curPositionedDiv)); |
|
9326 NS_ENSURE_SUCCESS(res, res); |
|
9327 // remember our new block for postprocessing |
|
9328 mNewBlock = curPositionedDiv; |
|
9329 // curPositionedDiv is now the correct thing to put curNode in |
|
9330 } |
|
9331 |
|
9332 // tuck the node into the end of the active blockquote |
|
9333 NS_ENSURE_STATE(mHTMLEditor); |
|
9334 res = mHTMLEditor->MoveNode(curNode, curPositionedDiv, -1); |
|
9335 NS_ENSURE_SUCCESS(res, res); |
|
9336 // forget curList, if any |
|
9337 curList = nullptr; |
|
9338 } |
|
9339 } |
|
9340 } |
|
9341 return res; |
|
9342 } |
|
9343 |
|
9344 nsresult |
|
9345 nsHTMLEditRules::DidAbsolutePosition() |
|
9346 { |
|
9347 NS_ENSURE_STATE(mHTMLEditor); |
|
9348 nsCOMPtr<nsIHTMLAbsPosEditor> absPosHTMLEditor = mHTMLEditor; |
|
9349 nsCOMPtr<nsIDOMElement> elt = do_QueryInterface(mNewBlock); |
|
9350 return absPosHTMLEditor->AbsolutelyPositionElement(elt, true); |
|
9351 } |
|
9352 |
|
9353 nsresult |
|
9354 nsHTMLEditRules::WillRemoveAbsolutePosition(Selection* aSelection, |
|
9355 bool* aCancel, bool* aHandled) { |
|
9356 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } |
|
9357 nsresult res = WillInsert(aSelection, aCancel); |
|
9358 NS_ENSURE_SUCCESS(res, res); |
|
9359 |
|
9360 // initialize out param |
|
9361 // we want to ignore aCancel from WillInsert() |
|
9362 *aCancel = false; |
|
9363 *aHandled = true; |
|
9364 |
|
9365 nsCOMPtr<nsIDOMElement> elt; |
|
9366 NS_ENSURE_STATE(mHTMLEditor); |
|
9367 res = mHTMLEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt)); |
|
9368 NS_ENSURE_SUCCESS(res, res); |
|
9369 |
|
9370 NS_ENSURE_STATE(mHTMLEditor); |
|
9371 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); |
|
9372 |
|
9373 NS_ENSURE_STATE(mHTMLEditor); |
|
9374 nsCOMPtr<nsIHTMLAbsPosEditor> absPosHTMLEditor = mHTMLEditor; |
|
9375 return absPosHTMLEditor->AbsolutelyPositionElement(elt, false); |
|
9376 } |
|
9377 |
|
9378 nsresult |
|
9379 nsHTMLEditRules::WillRelativeChangeZIndex(Selection* aSelection, |
|
9380 int32_t aChange, |
|
9381 bool *aCancel, |
|
9382 bool * aHandled) |
|
9383 { |
|
9384 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } |
|
9385 nsresult res = WillInsert(aSelection, aCancel); |
|
9386 NS_ENSURE_SUCCESS(res, res); |
|
9387 |
|
9388 // initialize out param |
|
9389 // we want to ignore aCancel from WillInsert() |
|
9390 *aCancel = false; |
|
9391 *aHandled = true; |
|
9392 |
|
9393 nsCOMPtr<nsIDOMElement> elt; |
|
9394 NS_ENSURE_STATE(mHTMLEditor); |
|
9395 res = mHTMLEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt)); |
|
9396 NS_ENSURE_SUCCESS(res, res); |
|
9397 |
|
9398 NS_ENSURE_STATE(mHTMLEditor); |
|
9399 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); |
|
9400 |
|
9401 NS_ENSURE_STATE(mHTMLEditor); |
|
9402 nsCOMPtr<nsIHTMLAbsPosEditor> absPosHTMLEditor = mHTMLEditor; |
|
9403 int32_t zIndex; |
|
9404 return absPosHTMLEditor->RelativeChangeElementZIndex(elt, aChange, &zIndex); |
|
9405 } |
|
9406 |
|
9407 NS_IMETHODIMP |
|
9408 nsHTMLEditRules::DocumentModified() |
|
9409 { |
|
9410 nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, &nsHTMLEditRules::DocumentModifiedWorker)); |
|
9411 return NS_OK; |
|
9412 } |
|
9413 |
|
9414 void |
|
9415 nsHTMLEditRules::DocumentModifiedWorker() |
|
9416 { |
|
9417 if (!mHTMLEditor) { |
|
9418 return; |
|
9419 } |
|
9420 |
|
9421 // DeleteNode below may cause a flush, which could destroy the editor |
|
9422 nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker; |
|
9423 |
|
9424 nsCOMPtr<nsIHTMLEditor> kungFuDeathGrip(mHTMLEditor); |
|
9425 nsCOMPtr<nsISelection> selection; |
|
9426 nsresult rv = mHTMLEditor->GetSelection(getter_AddRefs(selection)); |
|
9427 if (NS_FAILED(rv)) { |
|
9428 return; |
|
9429 } |
|
9430 |
|
9431 // Delete our bogus node, if we have one, since the document might not be |
|
9432 // empty any more. |
|
9433 if (mBogusNode) { |
|
9434 mEditor->DeleteNode(mBogusNode); |
|
9435 mBogusNode = nullptr; |
|
9436 } |
|
9437 |
|
9438 // Try to recreate the bogus node if needed. |
|
9439 CreateBogusNodeIfNeeded(selection); |
|
9440 } |