1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "mozilla/HTMLEditor.h"
7 
8 #include "mozilla/DebugOnly.h"
9 #include "mozilla/EventStates.h"
10 #include "mozilla/TextEvents.h"
11 
12 #include "nsCRT.h"
13 
14 #include "nsUnicharUtils.h"
15 
16 #include "HTMLEditorEventListener.h"
17 #include "HTMLEditRules.h"
18 #include "HTMLEditUtils.h"
19 #include "HTMLURIRefObject.h"
20 #include "SetDocumentTitleTransaction.h"
21 #include "StyleSheetTransactions.h"
22 #include "TextEditUtils.h"
23 #include "TypeInState.h"
24 
25 #include "nsIDOMText.h"
26 #include "nsIDOMMozNamedAttrMap.h"
27 #include "nsIDOMNodeList.h"
28 #include "nsIDOMDocument.h"
29 #include "nsIDOMAttr.h"
30 #include "nsIDocumentInlines.h"
31 #include "nsIDOMEventTarget.h"
32 #include "nsIDOMKeyEvent.h"
33 #include "nsIDOMMouseEvent.h"
34 #include "nsIDOMHTMLAnchorElement.h"
35 #include "nsISelectionController.h"
36 #include "nsIDOMHTMLDocument.h"
37 #include "nsILinkHandler.h"
38 #include "nsIInlineSpellChecker.h"
39 
40 #include "mozilla/css/Loader.h"
41 #include "nsIDOMStyleSheet.h"
42 
43 #include "nsIContent.h"
44 #include "nsIContentIterator.h"
45 #include "nsIMutableArray.h"
46 #include "nsContentUtils.h"
47 #include "nsIDocumentEncoder.h"
48 #include "nsIPresShell.h"
49 #include "nsPresContext.h"
50 #include "nsFocusManager.h"
51 #include "nsPIDOMWindow.h"
52 
53 // netwerk
54 #include "nsIURI.h"
55 #include "nsNetUtil.h"
56 
57 // Misc
58 #include "mozilla/EditorUtils.h"
59 #include "HTMLEditorObjectResizerUtils.h"
60 #include "TextEditorTest.h"
61 #include "WSRunObject.h"
62 #include "nsGkAtoms.h"
63 #include "nsIWidget.h"
64 
65 #include "nsIFrame.h"
66 #include "nsIParserService.h"
67 #include "mozilla/dom/Selection.h"
68 #include "mozilla/dom/DocumentFragment.h"
69 #include "mozilla/dom/Element.h"
70 #include "mozilla/dom/Event.h"
71 #include "mozilla/dom/EventTarget.h"
72 #include "mozilla/dom/HTMLBodyElement.h"
73 #include "nsTextFragment.h"
74 #include "nsContentList.h"
75 #include "mozilla/StyleSheet.h"
76 #include "mozilla/StyleSheetInlines.h"
77 
78 namespace mozilla {
79 
80 using namespace dom;
81 using namespace widget;
82 
83 // Some utilities to handle overloading of "A" tag for link and named anchor.
84 static bool
IsLinkTag(const nsString & s)85 IsLinkTag(const nsString& s)
86 {
87   return s.EqualsIgnoreCase("href");
88 }
89 
90 static bool
IsNamedAnchorTag(const nsString & s)91 IsNamedAnchorTag(const nsString& s)
92 {
93   return s.EqualsIgnoreCase("anchor") || s.EqualsIgnoreCase("namedanchor");
94 }
95 
HTMLEditor()96 HTMLEditor::HTMLEditor()
97   : mCRInParagraphCreatesParagraph(false)
98   , mCSSAware(false)
99   , mSelectedCellIndex(0)
100   , mIsObjectResizingEnabled(true)
101   , mIsResizing(false)
102   , mPreserveRatio(false)
103   , mResizedObjectIsAnImage(false)
104   , mIsAbsolutelyPositioningEnabled(true)
105   , mResizedObjectIsAbsolutelyPositioned(false)
106   , mGrabberClicked(false)
107   , mIsMoving(false)
108   , mSnapToGridEnabled(false)
109   , mIsInlineTableEditingEnabled(true)
110   , mOriginalX(0)
111   , mOriginalY(0)
112   , mResizedObjectX(0)
113   , mResizedObjectY(0)
114   , mResizedObjectWidth(0)
115   , mResizedObjectHeight(0)
116   , mResizedObjectMarginLeft(0)
117   , mResizedObjectMarginTop(0)
118   , mResizedObjectBorderLeft(0)
119   , mResizedObjectBorderTop(0)
120   , mXIncrementFactor(0)
121   , mYIncrementFactor(0)
122   , mWidthIncrementFactor(0)
123   , mHeightIncrementFactor(0)
124   , mInfoXIncrement(20)
125   , mInfoYIncrement(20)
126   , mPositionedObjectX(0)
127   , mPositionedObjectY(0)
128   , mPositionedObjectWidth(0)
129   , mPositionedObjectHeight(0)
130   , mPositionedObjectMarginLeft(0)
131   , mPositionedObjectMarginTop(0)
132   , mPositionedObjectBorderLeft(0)
133   , mPositionedObjectBorderTop(0)
134   , mGridSize(0)
135 {
136 }
137 
~HTMLEditor()138 HTMLEditor::~HTMLEditor()
139 {
140   // remove the rules as an action listener.  Else we get a bad
141   // ownership loop later on.  it's ok if the rules aren't a listener;
142   // we ignore the error.
143   nsCOMPtr<nsIEditActionListener> mListener = do_QueryInterface(mRules);
144   RemoveEditActionListener(mListener);
145 
146   //the autopointers will clear themselves up.
147   //but we need to also remove the listeners or we have a leak
148   RefPtr<Selection> selection = GetSelection();
149   // if we don't get the selection, just skip this
150   if (selection) {
151     nsCOMPtr<nsISelectionListener>listener;
152     listener = do_QueryInterface(mTypeInState);
153     if (listener) {
154       selection->RemoveSelectionListener(listener);
155     }
156     listener = do_QueryInterface(mSelectionListenerP);
157     if (listener) {
158       selection->RemoveSelectionListener(listener);
159     }
160   }
161 
162   mTypeInState = nullptr;
163   mSelectionListenerP = nullptr;
164 
165   // free any default style propItems
166   RemoveAllDefaultProperties();
167 
168   if (mLinkHandler && mDocWeak) {
169     nsCOMPtr<nsIPresShell> ps = GetPresShell();
170 
171     if (ps && ps->GetPresContext()) {
172       ps->GetPresContext()->SetLinkHandler(mLinkHandler);
173     }
174   }
175 
176   RemoveEventListeners();
177 
178   HideAnonymousEditingUIs();
179 }
180 
181 void
HideAnonymousEditingUIs()182 HTMLEditor::HideAnonymousEditingUIs()
183 {
184   if (mAbsolutelyPositionedObject) {
185     HideGrabber();
186   }
187   if (mInlineEditedCell) {
188     HideInlineTableEditingUI();
189   }
190   if (mResizedObject) {
191     HideResizers();
192   }
193 }
194 
195 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLEditor)
196 
197 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLEditor, TextEditor)
198   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTypeInState)
199   NS_IMPL_CYCLE_COLLECTION_UNLINK(mStyleSheets)
200 
201   tmp->HideAnonymousEditingUIs();
202 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
203 
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLEditor,TextEditor)204 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLEditor, TextEditor)
205   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTypeInState)
206   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets)
207 
208   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopLeftHandle)
209   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopHandle)
210   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopRightHandle)
211   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLeftHandle)
212   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRightHandle)
213   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomLeftHandle)
214   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomHandle)
215   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomRightHandle)
216   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActivatedHandle)
217   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizingShadow)
218   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizingInfo)
219   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizedObject)
220   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMouseMotionListenerP)
221   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionListenerP)
222   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizeEventListenerP)
223   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObjectResizeEventListeners)
224 
225   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAbsolutelyPositionedObject)
226   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGrabber)
227   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPositioningShadow)
228 
229   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInlineEditedCell)
230   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddColumnBeforeButton)
231   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRemoveColumnButton)
232   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddColumnAfterButton)
233   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddRowBeforeButton)
234   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRemoveRowButton)
235   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddRowAfterButton)
236 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
237 
238 NS_IMPL_ADDREF_INHERITED(HTMLEditor, EditorBase)
239 NS_IMPL_RELEASE_INHERITED(HTMLEditor, EditorBase)
240 
241 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLEditor)
242   NS_INTERFACE_MAP_ENTRY(nsIHTMLEditor)
243   NS_INTERFACE_MAP_ENTRY(nsIHTMLObjectResizer)
244   NS_INTERFACE_MAP_ENTRY(nsIHTMLAbsPosEditor)
245   NS_INTERFACE_MAP_ENTRY(nsIHTMLInlineTableEditor)
246   NS_INTERFACE_MAP_ENTRY(nsITableEditor)
247   NS_INTERFACE_MAP_ENTRY(nsIEditorStyleSheets)
248   NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
249   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
250 NS_INTERFACE_MAP_END_INHERITING(TextEditor)
251 
252 NS_IMETHODIMP
253 HTMLEditor::Init(nsIDOMDocument* aDoc,
254                  nsIContent* aRoot,
255                  nsISelectionController* aSelCon,
256                  uint32_t aFlags,
257                  const nsAString& aInitialValue)
258 {
259   NS_PRECONDITION(aDoc && !aSelCon, "bad arg");
260   NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER);
261   MOZ_ASSERT(aInitialValue.IsEmpty(), "Non-empty initial values not supported");
262 
263   nsresult rulesRv = NS_OK;
264 
265   {
266     // block to scope AutoEditInitRulesTrigger
267     AutoEditInitRulesTrigger rulesTrigger(this, rulesRv);
268 
269     // Init the plaintext editor
270     nsresult rv = TextEditor::Init(aDoc, aRoot, nullptr, aFlags, aInitialValue);
271     if (NS_FAILED(rv)) {
272       return rv;
273     }
274 
275     // Init mutation observer
276     nsCOMPtr<nsINode> document = do_QueryInterface(aDoc);
277     document->AddMutationObserverUnlessExists(this);
278 
279     if (!mRootElement) {
280       UpdateRootElement();
281     }
282 
283     // disable Composer-only features
284     if (IsMailEditor()) {
285       SetAbsolutePositioningEnabled(false);
286       SetSnapToGridEnabled(false);
287     }
288 
289     // Init the HTML-CSS utils
290     mCSSEditUtils = new CSSEditUtils(this);
291 
292     // disable links
293     nsCOMPtr<nsIPresShell> presShell = GetPresShell();
294     NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
295     nsPresContext *context = presShell->GetPresContext();
296     NS_ENSURE_TRUE(context, NS_ERROR_NULL_POINTER);
297     if (!IsPlaintextEditor() && !IsInteractionAllowed()) {
298       mLinkHandler = context->GetLinkHandler();
299       context->SetLinkHandler(nullptr);
300     }
301 
302     // init the type-in state
303     mTypeInState = new TypeInState();
304 
305     // init the selection listener for image resizing
306     mSelectionListenerP = new ResizerSelectionListener(this);
307 
308     if (!IsInteractionAllowed()) {
309       // ignore any errors from this in case the file is missing
310       AddOverrideStyleSheet(NS_LITERAL_STRING("resource://gre/res/EditorOverride.css"));
311     }
312 
313     RefPtr<Selection> selection = GetSelection();
314     if (selection) {
315       nsCOMPtr<nsISelectionListener>listener;
316       listener = do_QueryInterface(mTypeInState);
317       if (listener) {
318         selection->AddSelectionListener(listener);
319       }
320       listener = do_QueryInterface(mSelectionListenerP);
321       if (listener) {
322         selection->AddSelectionListener(listener);
323       }
324     }
325   }
326   NS_ENSURE_SUCCESS(rulesRv, rulesRv);
327 
328   return NS_OK;
329 }
330 
331 NS_IMETHODIMP
PreDestroy(bool aDestroyingFrames)332 HTMLEditor::PreDestroy(bool aDestroyingFrames)
333 {
334   if (mDidPreDestroy) {
335     return NS_OK;
336   }
337 
338   nsCOMPtr<nsINode> document = do_QueryReferent(mDocWeak);
339   if (document) {
340     document->RemoveMutationObserver(this);
341   }
342 
343   while (!mStyleSheetURLs.IsEmpty()) {
344     RemoveOverrideStyleSheet(mStyleSheetURLs[0]);
345   }
346 
347   // Clean up after our anonymous content -- we don't want these nodes to
348   // stay around (which they would, since the frames have an owning reference).
349   HideAnonymousEditingUIs();
350 
351   return TextEditor::PreDestroy(aDestroyingFrames);
352 }
353 
354 void
UpdateRootElement()355 HTMLEditor::UpdateRootElement()
356 {
357   // Use the HTML documents body element as the editor root if we didn't
358   // get a root element during initialization.
359 
360   nsCOMPtr<nsIDOMElement> rootElement;
361   nsCOMPtr<nsIDOMHTMLElement> bodyElement;
362   GetBodyElement(getter_AddRefs(bodyElement));
363   if (bodyElement) {
364     rootElement = bodyElement;
365   } else {
366     // If there is no HTML body element,
367     // we should use the document root element instead.
368     nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(mDocWeak);
369     if (doc) {
370       doc->GetDocumentElement(getter_AddRefs(rootElement));
371     }
372   }
373 
374   mRootElement = do_QueryInterface(rootElement);
375 }
376 
377 already_AddRefed<nsIContent>
FindSelectionRoot(nsINode * aNode)378 HTMLEditor::FindSelectionRoot(nsINode* aNode)
379 {
380   NS_PRECONDITION(aNode->IsNodeOfType(nsINode::eDOCUMENT) ||
381                   aNode->IsNodeOfType(nsINode::eCONTENT),
382                   "aNode must be content or document node");
383 
384   nsCOMPtr<nsIDocument> doc = aNode->GetUncomposedDoc();
385   if (!doc) {
386     return nullptr;
387   }
388 
389   nsCOMPtr<nsIContent> content;
390   if (doc->HasFlag(NODE_IS_EDITABLE) || !aNode->IsContent()) {
391     content = doc->GetRootElement();
392     return content.forget();
393   }
394   content = aNode->AsContent();
395 
396   // XXX If we have readonly flag, shouldn't return the element which has
397   // contenteditable="true"?  However, such case isn't there without chrome
398   // permission script.
399   if (IsReadonly()) {
400     // We still want to allow selection in a readonly editor.
401     content = do_QueryInterface(GetRoot());
402     return content.forget();
403   }
404 
405   if (!content->HasFlag(NODE_IS_EDITABLE)) {
406     // If the content is in read-write state but is not editable itself,
407     // return it as the selection root.
408     if (content->IsElement() &&
409         content->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) {
410       return content.forget();
411     }
412     return nullptr;
413   }
414 
415   // For non-readonly editors we want to find the root of the editable subtree
416   // containing aContent.
417   content = content->GetEditingHost();
418   return content.forget();
419 }
420 
421 void
CreateEventListeners()422 HTMLEditor::CreateEventListeners()
423 {
424   // Don't create the handler twice
425   if (!mEventListener) {
426     mEventListener = new HTMLEditorEventListener();
427   }
428 }
429 
430 nsresult
InstallEventListeners()431 HTMLEditor::InstallEventListeners()
432 {
433   NS_ENSURE_TRUE(mDocWeak && mEventListener,
434                  NS_ERROR_NOT_INITIALIZED);
435 
436   // NOTE: HTMLEditor doesn't need to initialize mEventTarget here because
437   // the target must be document node and it must be referenced as weak pointer.
438 
439   HTMLEditorEventListener* listener =
440     reinterpret_cast<HTMLEditorEventListener*>(mEventListener.get());
441   return listener->Connect(this);
442 }
443 
444 void
RemoveEventListeners()445 HTMLEditor::RemoveEventListeners()
446 {
447   if (!mDocWeak) {
448     return;
449   }
450 
451   nsCOMPtr<nsIDOMEventTarget> target = GetDOMEventTarget();
452 
453   if (target) {
454     // Both mMouseMotionListenerP and mResizeEventListenerP can be
455     // registerd with other targets than the DOM event receiver that
456     // we can reach from here. But nonetheless, unregister the event
457     // listeners with the DOM event reveiver (if it's registerd with
458     // other targets, it'll get unregisterd once the target goes
459     // away).
460 
461     if (mMouseMotionListenerP) {
462       // mMouseMotionListenerP might be registerd either as bubbling or
463       // capturing, unregister by both.
464       target->RemoveEventListener(NS_LITERAL_STRING("mousemove"),
465                                   mMouseMotionListenerP, false);
466       target->RemoveEventListener(NS_LITERAL_STRING("mousemove"),
467                                   mMouseMotionListenerP, true);
468     }
469 
470     if (mResizeEventListenerP) {
471       target->RemoveEventListener(NS_LITERAL_STRING("resize"),
472                                   mResizeEventListenerP, false);
473     }
474   }
475 
476   mMouseMotionListenerP = nullptr;
477   mResizeEventListenerP = nullptr;
478 
479   TextEditor::RemoveEventListeners();
480 }
481 
482 NS_IMETHODIMP
SetFlags(uint32_t aFlags)483 HTMLEditor::SetFlags(uint32_t aFlags)
484 {
485   nsresult rv = TextEditor::SetFlags(aFlags);
486   NS_ENSURE_SUCCESS(rv, rv);
487 
488   // Sets mCSSAware to correspond to aFlags. This toggles whether CSS is
489   // used to style elements in the editor. Note that the editor is only CSS
490   // aware by default in Composer and in the mail editor.
491   mCSSAware = !NoCSS() && !IsMailEditor();
492 
493   return NS_OK;
494 }
495 
496 NS_IMETHODIMP
InitRules()497 HTMLEditor::InitRules()
498 {
499   if (!mRules) {
500     // instantiate the rules for the html editor
501     mRules = new HTMLEditRules();
502   }
503   return mRules->Init(static_cast<TextEditor*>(this));
504 }
505 
506 NS_IMETHODIMP
BeginningOfDocument()507 HTMLEditor::BeginningOfDocument()
508 {
509   if (!mDocWeak) {
510     return NS_ERROR_NOT_INITIALIZED;
511   }
512 
513   // Get the selection
514   RefPtr<Selection> selection = GetSelection();
515   NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
516 
517   // Get the root element.
518   nsCOMPtr<Element> rootElement = GetRoot();
519   if (!rootElement) {
520     NS_WARNING("GetRoot() returned a null pointer (mRootElement is null)");
521     return NS_OK;
522   }
523 
524   // Find first editable thingy
525   bool done = false;
526   nsCOMPtr<nsINode> curNode = rootElement.get(), selNode;
527   int32_t curOffset = 0, selOffset = 0;
528   while (!done) {
529     WSRunObject wsObj(this, curNode, curOffset);
530     int32_t visOffset = 0;
531     WSType visType;
532     nsCOMPtr<nsINode> visNode;
533     wsObj.NextVisibleNode(curNode, curOffset, address_of(visNode), &visOffset,
534                           &visType);
535     if (visType == WSType::normalWS || visType == WSType::text) {
536       selNode = visNode;
537       selOffset = visOffset;
538       done = true;
539     } else if (visType == WSType::br || visType == WSType::special) {
540       selNode = visNode->GetParentNode();
541       selOffset = selNode ? selNode->IndexOf(visNode) : -1;
542       done = true;
543     } else if (visType == WSType::otherBlock) {
544       // By definition of WSRunObject, a block element terminates a
545       // whitespace run. That is, although we are calling a method that is
546       // named "NextVisibleNode", the node returned might not be
547       // visible/editable!
548       //
549       // If the given block does not contain any visible/editable items, we
550       // want to skip it and continue our search.
551 
552       if (!IsContainer(visNode)) {
553         // However, we were given a block that is not a container.  Since the
554         // block can not contain anything that's visible, such a block only
555         // makes sense if it is visible by itself, like a <hr>.  We want to
556         // place the caret in front of that block.
557         selNode = visNode->GetParentNode();
558         selOffset = selNode ? selNode->IndexOf(visNode) : -1;
559         done = true;
560       } else {
561         bool isEmptyBlock;
562         if (NS_SUCCEEDED(IsEmptyNode(visNode, &isEmptyBlock)) &&
563             isEmptyBlock) {
564           // Skip the empty block
565           curNode = visNode->GetParentNode();
566           curOffset = curNode ? curNode->IndexOf(visNode) : -1;
567           curOffset++;
568         } else {
569           curNode = visNode;
570           curOffset = 0;
571         }
572         // Keep looping
573       }
574     } else {
575       // Else we found nothing useful
576       selNode = curNode;
577       selOffset = curOffset;
578       done = true;
579     }
580   }
581   return selection->Collapse(selNode, selOffset);
582 }
583 
584 nsresult
HandleKeyPressEvent(nsIDOMKeyEvent * aKeyEvent)585 HTMLEditor::HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent)
586 {
587   // NOTE: When you change this method, you should also change:
588   //   * editor/libeditor/tests/test_htmleditor_keyevent_handling.html
589 
590   if (IsReadonly() || IsDisabled()) {
591     // When we're not editable, the events are handled on EditorBase, so, we can
592     // bypass TextEditor.
593     return EditorBase::HandleKeyPressEvent(aKeyEvent);
594   }
595 
596   WidgetKeyboardEvent* nativeKeyEvent =
597     aKeyEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
598   NS_ENSURE_TRUE(nativeKeyEvent, NS_ERROR_UNEXPECTED);
599   NS_ASSERTION(nativeKeyEvent->mMessage == eKeyPress,
600                "HandleKeyPressEvent gets non-keypress event");
601 
602   switch (nativeKeyEvent->mKeyCode) {
603     case NS_VK_META:
604     case NS_VK_WIN:
605     case NS_VK_SHIFT:
606     case NS_VK_CONTROL:
607     case NS_VK_ALT:
608     case NS_VK_BACK:
609     case NS_VK_DELETE:
610       // These keys are handled on EditorBase, so, we can bypass
611       // TextEditor.
612       return EditorBase::HandleKeyPressEvent(aKeyEvent);
613     case NS_VK_TAB: {
614       if (IsPlaintextEditor()) {
615         // If this works as plain text editor, e.g., mail editor for plain
616         // text, should be handled on TextEditor.
617         return TextEditor::HandleKeyPressEvent(aKeyEvent);
618       }
619 
620       if (IsTabbable()) {
621         return NS_OK; // let it be used for focus switching
622       }
623 
624       if (nativeKeyEvent->IsControl() || nativeKeyEvent->IsAlt() ||
625           nativeKeyEvent->IsMeta() || nativeKeyEvent->IsOS()) {
626         return NS_OK;
627       }
628 
629       RefPtr<Selection> selection = GetSelection();
630       NS_ENSURE_TRUE(selection && selection->RangeCount(), NS_ERROR_FAILURE);
631 
632       nsCOMPtr<nsINode> node = selection->GetRangeAt(0)->GetStartParent();
633       MOZ_ASSERT(node);
634 
635       nsCOMPtr<Element> blockParent = GetBlock(*node);
636 
637       if (!blockParent) {
638         break;
639       }
640 
641       bool handled = false;
642       nsresult rv = NS_OK;
643       if (HTMLEditUtils::IsTableElement(blockParent)) {
644         rv = TabInTable(nativeKeyEvent->IsShift(), &handled);
645         if (handled) {
646           ScrollSelectionIntoView(false);
647         }
648       } else if (HTMLEditUtils::IsListItem(blockParent)) {
649         rv = Indent(nativeKeyEvent->IsShift()
650                     ? NS_LITERAL_STRING("outdent")
651                     : NS_LITERAL_STRING("indent"));
652         handled = true;
653       }
654       NS_ENSURE_SUCCESS(rv, rv);
655       if (handled) {
656         return aKeyEvent->AsEvent()->PreventDefault(); // consumed
657       }
658       if (nativeKeyEvent->IsShift()) {
659         return NS_OK; // don't type text for shift tabs
660       }
661       aKeyEvent->AsEvent()->PreventDefault();
662       return TypedText(NS_LITERAL_STRING("\t"), eTypedText);
663     }
664     case NS_VK_RETURN:
665       if (nativeKeyEvent->IsControl() || nativeKeyEvent->IsAlt() ||
666           nativeKeyEvent->IsMeta() || nativeKeyEvent->IsOS()) {
667         return NS_OK;
668       }
669       aKeyEvent->AsEvent()->PreventDefault(); // consumed
670       if (nativeKeyEvent->IsShift() && !IsPlaintextEditor()) {
671         // only inserts a br node
672         return TypedText(EmptyString(), eTypedBR);
673       }
674       // uses rules to figure out what to insert
675       return TypedText(EmptyString(), eTypedBreak);
676   }
677 
678   // NOTE: On some keyboard layout, some characters are inputted with Control
679   // key or Alt key, but at that time, widget sets FALSE to these keys.
680   if (!nativeKeyEvent->mCharCode || nativeKeyEvent->IsControl() ||
681       nativeKeyEvent->IsAlt() || nativeKeyEvent->IsMeta() ||
682       nativeKeyEvent->IsOS()) {
683     // we don't PreventDefault() here or keybindings like control-x won't work
684     return NS_OK;
685   }
686   aKeyEvent->AsEvent()->PreventDefault();
687   nsAutoString str(nativeKeyEvent->mCharCode);
688   return TypedText(str, eTypedText);
689 }
690 
691 static void
AssertParserServiceIsCorrect(nsIAtom * aTag,bool aIsBlock)692 AssertParserServiceIsCorrect(nsIAtom* aTag, bool aIsBlock)
693 {
694 #ifdef DEBUG
695   // Check this against what we would have said with the old code:
696   if (aTag == nsGkAtoms::p ||
697       aTag == nsGkAtoms::div ||
698       aTag == nsGkAtoms::blockquote ||
699       aTag == nsGkAtoms::h1 ||
700       aTag == nsGkAtoms::h2 ||
701       aTag == nsGkAtoms::h3 ||
702       aTag == nsGkAtoms::h4 ||
703       aTag == nsGkAtoms::h5 ||
704       aTag == nsGkAtoms::h6 ||
705       aTag == nsGkAtoms::ul ||
706       aTag == nsGkAtoms::ol ||
707       aTag == nsGkAtoms::dl ||
708       aTag == nsGkAtoms::noscript ||
709       aTag == nsGkAtoms::form ||
710       aTag == nsGkAtoms::hr ||
711       aTag == nsGkAtoms::table ||
712       aTag == nsGkAtoms::fieldset ||
713       aTag == nsGkAtoms::address ||
714       aTag == nsGkAtoms::col ||
715       aTag == nsGkAtoms::colgroup ||
716       aTag == nsGkAtoms::li ||
717       aTag == nsGkAtoms::dt ||
718       aTag == nsGkAtoms::dd ||
719       aTag == nsGkAtoms::legend) {
720     if (!aIsBlock) {
721       nsAutoString assertmsg (NS_LITERAL_STRING("Parser and editor disagree on blockness: "));
722 
723       nsAutoString tagName;
724       aTag->ToString(tagName);
725       assertmsg.Append(tagName);
726       char* assertstr = ToNewCString(assertmsg);
727       NS_ASSERTION(aIsBlock, assertstr);
728       free(assertstr);
729     }
730   }
731 #endif // DEBUG
732 }
733 
734 /**
735  * Returns true if the id represents an element of block type.
736  * Can be used to determine if a new paragraph should be started.
737  */
738 bool
NodeIsBlockStatic(const nsINode * aElement)739 HTMLEditor::NodeIsBlockStatic(const nsINode* aElement)
740 {
741   MOZ_ASSERT(aElement);
742 
743   // Nodes we know we want to treat as block
744   // even though the parser says they're not:
745   if (aElement->IsAnyOfHTMLElements(nsGkAtoms::body,
746                                     nsGkAtoms::head,
747                                     nsGkAtoms::tbody,
748                                     nsGkAtoms::thead,
749                                     nsGkAtoms::tfoot,
750                                     nsGkAtoms::tr,
751                                     nsGkAtoms::th,
752                                     nsGkAtoms::td,
753                                     nsGkAtoms::li,
754                                     nsGkAtoms::dt,
755                                     nsGkAtoms::dd,
756                                     nsGkAtoms::pre)) {
757     return true;
758   }
759 
760   bool isBlock;
761 #ifdef DEBUG
762   // XXX we can't use DebugOnly here because VC++ is stupid (bug 802884)
763   nsresult rv =
764 #endif
765     nsContentUtils::GetParserService()->
766     IsBlock(nsContentUtils::GetParserService()->HTMLAtomTagToId(
767               aElement->NodeInfo()->NameAtom()),
768             isBlock);
769   MOZ_ASSERT(rv == NS_OK);
770 
771   AssertParserServiceIsCorrect(aElement->NodeInfo()->NameAtom(), isBlock);
772 
773   return isBlock;
774 }
775 
776 nsresult
NodeIsBlockStatic(nsIDOMNode * aNode,bool * aIsBlock)777 HTMLEditor::NodeIsBlockStatic(nsIDOMNode* aNode,
778                               bool* aIsBlock)
779 {
780   if (!aNode || !aIsBlock) {
781     return NS_ERROR_NULL_POINTER;
782   }
783 
784   nsCOMPtr<dom::Element> element = do_QueryInterface(aNode);
785   *aIsBlock = element && NodeIsBlockStatic(element);
786   return NS_OK;
787 }
788 
789 NS_IMETHODIMP
NodeIsBlock(nsIDOMNode * aNode,bool * aIsBlock)790 HTMLEditor::NodeIsBlock(nsIDOMNode* aNode,
791                         bool* aIsBlock)
792 {
793   return NodeIsBlockStatic(aNode, aIsBlock);
794 }
795 
796 bool
IsBlockNode(nsINode * aNode)797 HTMLEditor::IsBlockNode(nsINode* aNode)
798 {
799   return aNode && NodeIsBlockStatic(aNode);
800 }
801 
802 // Non-static version for the nsIEditor interface and JavaScript
803 NS_IMETHODIMP
SetDocumentTitle(const nsAString & aTitle)804 HTMLEditor::SetDocumentTitle(const nsAString& aTitle)
805 {
806   RefPtr<SetDocumentTitleTransaction> transaction =
807     new SetDocumentTitleTransaction();
808   NS_ENSURE_TRUE(transaction, NS_ERROR_OUT_OF_MEMORY);
809 
810   nsresult rv = transaction->Init(this, &aTitle);
811   NS_ENSURE_SUCCESS(rv, rv);
812 
813   //Don't let Rules System change the selection
814   AutoTransactionsConserveSelection dontChangeSelection(this);
815   return EditorBase::DoTransaction(transaction);
816 }
817 
818 /**
819  * GetBlockNodeParent returns enclosing block level ancestor, if any.
820  */
821 Element*
GetBlockNodeParent(nsINode * aNode)822 HTMLEditor::GetBlockNodeParent(nsINode* aNode)
823 {
824   MOZ_ASSERT(aNode);
825 
826   nsCOMPtr<nsINode> p = aNode->GetParentNode();
827 
828   while (p) {
829     if (NodeIsBlockStatic(p)) {
830       return p->AsElement();
831     }
832     p = p->GetParentNode();
833   }
834 
835   return nullptr;
836 }
837 
838 nsIDOMNode*
GetBlockNodeParent(nsIDOMNode * aNode)839 HTMLEditor::GetBlockNodeParent(nsIDOMNode* aNode)
840 {
841   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
842 
843   if (!node) {
844     NS_NOTREACHED("null node passed to GetBlockNodeParent()");
845     return nullptr;
846   }
847 
848   return GetAsDOMNode(GetBlockNodeParent(node));
849 }
850 
851 /**
852  * Returns the node if it's a block, otherwise GetBlockNodeParent
853  */
854 Element*
GetBlock(nsINode & aNode)855 HTMLEditor::GetBlock(nsINode& aNode)
856 {
857   if (NodeIsBlockStatic(&aNode)) {
858     return aNode.AsElement();
859   }
860   return GetBlockNodeParent(&aNode);
861 }
862 
863 /**
864  * IsNextCharInNodeWhitespace() checks the adjacent content in the same node to
865  * see if following selection is whitespace or nbsp.
866  */
867 void
IsNextCharInNodeWhitespace(nsIContent * aContent,int32_t aOffset,bool * outIsSpace,bool * outIsNBSP,nsIContent ** outNode,int32_t * outOffset)868 HTMLEditor::IsNextCharInNodeWhitespace(nsIContent* aContent,
869                                        int32_t aOffset,
870                                        bool* outIsSpace,
871                                        bool* outIsNBSP,
872                                        nsIContent** outNode,
873                                        int32_t* outOffset)
874 {
875   MOZ_ASSERT(aContent && outIsSpace && outIsNBSP);
876   MOZ_ASSERT((outNode && outOffset) || (!outNode && !outOffset));
877   *outIsSpace = false;
878   *outIsNBSP = false;
879   if (outNode && outOffset) {
880     *outNode = nullptr;
881     *outOffset = -1;
882   }
883 
884   if (aContent->IsNodeOfType(nsINode::eTEXT) &&
885       (uint32_t)aOffset < aContent->Length()) {
886     char16_t ch = aContent->GetText()->CharAt(aOffset);
887     *outIsSpace = nsCRT::IsAsciiSpace(ch);
888     *outIsNBSP = (ch == kNBSP);
889     if (outNode && outOffset) {
890       NS_IF_ADDREF(*outNode = aContent);
891       // yes, this is _past_ the character
892       *outOffset = aOffset + 1;
893     }
894   }
895 }
896 
897 
898 /**
899  * IsPrevCharInNodeWhitespace() checks the adjacent content in the same node to
900  * see if following selection is whitespace.
901  */
902 void
IsPrevCharInNodeWhitespace(nsIContent * aContent,int32_t aOffset,bool * outIsSpace,bool * outIsNBSP,nsIContent ** outNode,int32_t * outOffset)903 HTMLEditor::IsPrevCharInNodeWhitespace(nsIContent* aContent,
904                                        int32_t aOffset,
905                                        bool* outIsSpace,
906                                        bool* outIsNBSP,
907                                        nsIContent** outNode,
908                                        int32_t* outOffset)
909 {
910   MOZ_ASSERT(aContent && outIsSpace && outIsNBSP);
911   MOZ_ASSERT((outNode && outOffset) || (!outNode && !outOffset));
912   *outIsSpace = false;
913   *outIsNBSP = false;
914   if (outNode && outOffset) {
915     *outNode = nullptr;
916     *outOffset = -1;
917   }
918 
919   if (aContent->IsNodeOfType(nsINode::eTEXT) && aOffset > 0) {
920     char16_t ch = aContent->GetText()->CharAt(aOffset - 1);
921     *outIsSpace = nsCRT::IsAsciiSpace(ch);
922     *outIsNBSP = (ch == kNBSP);
923     if (outNode && outOffset) {
924       NS_IF_ADDREF(*outNode = aContent);
925       *outOffset = aOffset - 1;
926     }
927   }
928 }
929 
930 bool
IsVisBreak(nsINode * aNode)931 HTMLEditor::IsVisBreak(nsINode* aNode)
932 {
933   MOZ_ASSERT(aNode);
934   if (!TextEditUtils::IsBreak(aNode)) {
935     return false;
936   }
937   // Check if there is a later node in block after br
938   nsCOMPtr<nsINode> nextNode = GetNextHTMLNode(aNode, true);
939   if (nextNode && TextEditUtils::IsBreak(nextNode)) {
940     return true;
941   }
942 
943   // A single line break before a block boundary is not displayed, so e.g.
944   // foo<p>bar<br></p> and foo<br><p>bar</p> display the same as foo<p>bar</p>.
945   // But if there are multiple <br>s in a row, all but the last are visible.
946   if (!nextNode) {
947     // This break is trailer in block, it's not visible
948     return false;
949   }
950   if (IsBlockNode(nextNode)) {
951     // Break is right before a block, it's not visible
952     return false;
953   }
954 
955   // If there's an inline node after this one that's not a break, and also a
956   // prior break, this break must be visible.
957   nsCOMPtr<nsINode> priorNode = GetPriorHTMLNode(aNode, true);
958   if (priorNode && TextEditUtils::IsBreak(priorNode)) {
959     return true;
960   }
961 
962   // Sigh.  We have to use expensive whitespace calculation code to
963   // determine what is going on
964   int32_t selOffset;
965   nsCOMPtr<nsINode> selNode = GetNodeLocation(aNode, &selOffset);
966   // Let's look after the break
967   selOffset++;
968   WSRunObject wsObj(this, selNode, selOffset);
969   nsCOMPtr<nsINode> unused;
970   int32_t visOffset = 0;
971   WSType visType;
972   wsObj.NextVisibleNode(selNode, selOffset, address_of(unused),
973                         &visOffset, &visType);
974   if (visType & WSType::block) {
975     return false;
976   }
977 
978   return true;
979 }
980 
981 NS_IMETHODIMP
GetIsDocumentEditable(bool * aIsDocumentEditable)982 HTMLEditor::GetIsDocumentEditable(bool* aIsDocumentEditable)
983 {
984   NS_ENSURE_ARG_POINTER(aIsDocumentEditable);
985 
986   nsCOMPtr<nsIDOMDocument> doc = GetDOMDocument();
987   *aIsDocumentEditable = doc && IsModifiable();
988 
989   return NS_OK;
990 }
991 
992 bool
IsModifiable()993 HTMLEditor::IsModifiable()
994 {
995   return !IsReadonly();
996 }
997 
998 NS_IMETHODIMP
UpdateBaseURL()999 HTMLEditor::UpdateBaseURL()
1000 {
1001   nsCOMPtr<nsIDocument> doc = GetDocument();
1002   NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
1003 
1004   // Look for an HTML <base> tag
1005   RefPtr<nsContentList> nodeList =
1006     doc->GetElementsByTagName(NS_LITERAL_STRING("base"));
1007 
1008   // If no base tag, then set baseURL to the document's URL.  This is very
1009   // important, else relative URLs for links and images are wrong
1010   if (!nodeList || !nodeList->Item(0)) {
1011     doc->SetBaseURI(doc->GetDocumentURI());
1012   }
1013   return NS_OK;
1014 }
1015 
1016 /**
1017  * This routine is needed to provide a bottleneck for typing for logging
1018  * purposes.  Can't use HandleKeyPress() (above) for that since it takes
1019  * a nsIDOMKeyEvent* parameter.  So instead we pass enough info through
1020  * to TypedText() to determine what action to take, but without passing
1021  * an event.
1022  */
1023 NS_IMETHODIMP
TypedText(const nsAString & aString,ETypingAction aAction)1024 HTMLEditor::TypedText(const nsAString& aString,
1025                       ETypingAction aAction)
1026 {
1027   AutoPlaceHolderBatch batch(this, nsGkAtoms::TypingTxnName);
1028 
1029   if (aAction == eTypedBR) {
1030     // only inserts a br node
1031     nsCOMPtr<nsIDOMNode> brNode;
1032     return InsertBR(address_of(brNode));
1033   }
1034 
1035   return TextEditor::TypedText(aString, aAction);
1036 }
1037 
1038 NS_IMETHODIMP
TabInTable(bool inIsShift,bool * outHandled)1039 HTMLEditor::TabInTable(bool inIsShift,
1040                        bool* outHandled)
1041 {
1042   NS_ENSURE_TRUE(outHandled, NS_ERROR_NULL_POINTER);
1043   *outHandled = false;
1044 
1045   // Find enclosing table cell from selection (cell may be selected element)
1046   nsCOMPtr<Element> cellElement =
1047     GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr);
1048   // Do nothing -- we didn't find a table cell
1049   NS_ENSURE_TRUE(cellElement, NS_OK);
1050 
1051   // find enclosing table
1052   nsCOMPtr<Element> table = GetEnclosingTable(cellElement);
1053   NS_ENSURE_TRUE(table, NS_OK);
1054 
1055   // advance to next cell
1056   // first create an iterator over the table
1057   nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
1058   nsresult rv = iter->Init(table);
1059   NS_ENSURE_SUCCESS(rv, rv);
1060   // position iter at block
1061   rv = iter->PositionAt(cellElement);
1062   NS_ENSURE_SUCCESS(rv, rv);
1063 
1064   nsCOMPtr<nsINode> node;
1065   do {
1066     if (inIsShift) {
1067       iter->Prev();
1068     } else {
1069       iter->Next();
1070     }
1071 
1072     node = iter->GetCurrentNode();
1073 
1074     if (node && HTMLEditUtils::IsTableCell(node) &&
1075         GetEnclosingTable(node) == table) {
1076       CollapseSelectionToDeepestNonTableFirstChild(nullptr, node);
1077       *outHandled = true;
1078       return NS_OK;
1079     }
1080   } while (!iter->IsDone());
1081 
1082   if (!(*outHandled) && !inIsShift) {
1083     // If we haven't handled it yet, then we must have run off the end of the
1084     // table.  Insert a new row.
1085     rv = InsertTableRow(1, true);
1086     NS_ENSURE_SUCCESS(rv, rv);
1087     *outHandled = true;
1088     // Put selection in right place.  Use table code to get selection and index
1089     // to new row...
1090     RefPtr<Selection> selection;
1091     nsCOMPtr<nsIDOMElement> tblElement, cell;
1092     int32_t row;
1093     rv = GetCellContext(getter_AddRefs(selection),
1094                         getter_AddRefs(tblElement),
1095                         getter_AddRefs(cell),
1096                         nullptr, nullptr,
1097                         &row, nullptr);
1098     NS_ENSURE_SUCCESS(rv, rv);
1099     // ...so that we can ask for first cell in that row...
1100     rv = GetCellAt(tblElement, row, 0, getter_AddRefs(cell));
1101     NS_ENSURE_SUCCESS(rv, rv);
1102     // ...and then set selection there.  (Note that normally you should use
1103     // CollapseSelectionToDeepestNonTableFirstChild(), but we know cell is an
1104     // empty new cell, so this works fine)
1105     if (cell) {
1106       selection->Collapse(cell, 0);
1107     }
1108   }
1109 
1110   return NS_OK;
1111 }
1112 
1113 already_AddRefed<Element>
CreateBR(nsINode * aNode,int32_t aOffset,EDirection aSelect)1114 HTMLEditor::CreateBR(nsINode* aNode,
1115                      int32_t aOffset,
1116                      EDirection aSelect)
1117 {
1118   nsCOMPtr<nsIDOMNode> parent = GetAsDOMNode(aNode);
1119   int32_t offset = aOffset;
1120   nsCOMPtr<nsIDOMNode> outBRNode;
1121   // We assume everything is fine if the br is not null, irrespective of retval
1122   CreateBRImpl(address_of(parent), &offset, address_of(outBRNode), aSelect);
1123   nsCOMPtr<Element> ret = do_QueryInterface(outBRNode);
1124   return ret.forget();
1125 }
1126 
1127 NS_IMETHODIMP
CreateBR(nsIDOMNode * aNode,int32_t aOffset,nsCOMPtr<nsIDOMNode> * outBRNode,EDirection aSelect)1128 HTMLEditor::CreateBR(nsIDOMNode* aNode,
1129                      int32_t aOffset,
1130                      nsCOMPtr<nsIDOMNode>* outBRNode,
1131                      EDirection aSelect)
1132 {
1133   nsCOMPtr<nsIDOMNode> parent = aNode;
1134   int32_t offset = aOffset;
1135   return CreateBRImpl(address_of(parent), &offset, outBRNode, aSelect);
1136 }
1137 
1138 void
CollapseSelectionToDeepestNonTableFirstChild(Selection * aSelection,nsINode * aNode)1139 HTMLEditor::CollapseSelectionToDeepestNonTableFirstChild(Selection* aSelection,
1140                                                          nsINode* aNode)
1141 {
1142   MOZ_ASSERT(aNode);
1143 
1144   RefPtr<Selection> selection = aSelection;
1145   if (!selection) {
1146     selection = GetSelection();
1147   }
1148   if (!selection) {
1149     // Nothing to do
1150     return;
1151   }
1152 
1153   nsCOMPtr<nsINode> node = aNode;
1154 
1155   for (nsCOMPtr<nsIContent> child = node->GetFirstChild();
1156        child;
1157        child = child->GetFirstChild()) {
1158     // Stop if we find a table, don't want to go into nested tables
1159     if (HTMLEditUtils::IsTable(child) || !IsContainer(child)) {
1160       break;
1161     }
1162     node = child;
1163   }
1164 
1165   selection->Collapse(node, 0);
1166 }
1167 
1168 
1169 /**
1170  * This is mostly like InsertHTMLWithCharsetAndContext, but we can't use that
1171  * because it is selection-based and the rules code won't let us edit under the
1172  * <head> node
1173  */
1174 NS_IMETHODIMP
ReplaceHeadContentsWithHTML(const nsAString & aSourceToInsert)1175 HTMLEditor::ReplaceHeadContentsWithHTML(const nsAString& aSourceToInsert)
1176 {
1177   // don't do any post processing, rules get confused
1178   AutoRules beginRulesSniffing(this, EditAction::ignore, nsIEditor::eNone);
1179   RefPtr<Selection> selection = GetSelection();
1180   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1181 
1182   ForceCompositionEnd();
1183 
1184   // Do not use AutoRules -- rules code won't let us insert in <head>.  Use
1185   // the head node as a parent and delete/insert directly.
1186   nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
1187   NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
1188 
1189   RefPtr<nsContentList> nodeList =
1190     doc->GetElementsByTagName(NS_LITERAL_STRING("head"));
1191   NS_ENSURE_TRUE(nodeList, NS_ERROR_NULL_POINTER);
1192 
1193   nsCOMPtr<nsIContent> headNode = nodeList->Item(0);
1194   NS_ENSURE_TRUE(headNode, NS_ERROR_NULL_POINTER);
1195 
1196   // First, make sure there are no return chars in the source.  Bad things
1197   // happen if you insert returns (instead of dom newlines, \n) into an editor
1198   // document.
1199   nsAutoString inputString (aSourceToInsert);  // hope this does copy-on-write
1200 
1201   // Windows linebreaks: Map CRLF to LF:
1202   inputString.ReplaceSubstring(u"\r\n", u"\n");
1203 
1204   // Mac linebreaks: Map any remaining CR to LF:
1205   inputString.ReplaceSubstring(u"\r", u"\n");
1206 
1207   AutoEditBatch beginBatching(this);
1208 
1209   // Get the first range in the selection, for context:
1210   RefPtr<nsRange> range = selection->GetRangeAt(0);
1211   NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER);
1212 
1213   ErrorResult err;
1214   RefPtr<DocumentFragment> docfrag =
1215     range->CreateContextualFragment(inputString, err);
1216 
1217   // XXXX BUG 50965: This is not returning the text between <title>...</title>
1218   // Special code is needed in JS to handle title anyway, so it doesn't matter!
1219 
1220   if (err.Failed()) {
1221 #ifdef DEBUG
1222     printf("Couldn't create contextual fragment: error was %X\n",
1223            err.ErrorCodeAsInt());
1224 #endif
1225     return err.StealNSResult();
1226   }
1227   NS_ENSURE_TRUE(docfrag, NS_ERROR_NULL_POINTER);
1228 
1229   // First delete all children in head
1230   while (nsCOMPtr<nsIContent> child = headNode->GetFirstChild()) {
1231     nsresult rv = DeleteNode(child);
1232     NS_ENSURE_SUCCESS(rv, rv);
1233   }
1234 
1235   // Now insert the new nodes
1236   int32_t offsetOfNewNode = 0;
1237 
1238   // Loop over the contents of the fragment and move into the document
1239   while (nsCOMPtr<nsIContent> child = docfrag->GetFirstChild()) {
1240     nsresult rv = InsertNode(*child, *headNode, offsetOfNewNode++);
1241     NS_ENSURE_SUCCESS(rv, rv);
1242   }
1243 
1244   return NS_OK;
1245 }
1246 
1247 NS_IMETHODIMP
RebuildDocumentFromSource(const nsAString & aSourceString)1248 HTMLEditor::RebuildDocumentFromSource(const nsAString& aSourceString)
1249 {
1250   ForceCompositionEnd();
1251 
1252   RefPtr<Selection> selection = GetSelection();
1253   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1254 
1255   nsCOMPtr<Element> bodyElement = GetRoot();
1256   NS_ENSURE_TRUE(bodyElement, NS_ERROR_NULL_POINTER);
1257 
1258   // Find where the <body> tag starts.
1259   nsReadingIterator<char16_t> beginbody;
1260   nsReadingIterator<char16_t> endbody;
1261   aSourceString.BeginReading(beginbody);
1262   aSourceString.EndReading(endbody);
1263   bool foundbody = CaseInsensitiveFindInReadable(NS_LITERAL_STRING("<body"),
1264                                                  beginbody, endbody);
1265 
1266   nsReadingIterator<char16_t> beginhead;
1267   nsReadingIterator<char16_t> endhead;
1268   aSourceString.BeginReading(beginhead);
1269   aSourceString.EndReading(endhead);
1270   bool foundhead = CaseInsensitiveFindInReadable(NS_LITERAL_STRING("<head"),
1271                                                  beginhead, endhead);
1272   // a valid head appears before the body
1273   if (foundbody && beginhead.get() > beginbody.get()) {
1274     foundhead = false;
1275   }
1276 
1277   nsReadingIterator<char16_t> beginclosehead;
1278   nsReadingIterator<char16_t> endclosehead;
1279   aSourceString.BeginReading(beginclosehead);
1280   aSourceString.EndReading(endclosehead);
1281 
1282   // Find the index after "<head>"
1283   bool foundclosehead = CaseInsensitiveFindInReadable(
1284            NS_LITERAL_STRING("</head>"), beginclosehead, endclosehead);
1285   // a valid close head appears after a found head
1286   if (foundhead && beginhead.get() > beginclosehead.get()) {
1287     foundclosehead = false;
1288   }
1289   // a valid close head appears before a found body
1290   if (foundbody && beginclosehead.get() > beginbody.get()) {
1291     foundclosehead = false;
1292   }
1293 
1294   // Time to change the document
1295   AutoEditBatch beginBatching(this);
1296 
1297   nsReadingIterator<char16_t> endtotal;
1298   aSourceString.EndReading(endtotal);
1299 
1300   if (foundhead) {
1301     if (foundclosehead) {
1302       nsresult rv =
1303         ReplaceHeadContentsWithHTML(Substring(beginhead, beginclosehead));
1304       if (NS_WARN_IF(NS_FAILED(rv))) {
1305         return rv;
1306       }
1307     } else if (foundbody) {
1308       nsresult rv =
1309         ReplaceHeadContentsWithHTML(Substring(beginhead, beginbody));
1310       if (NS_WARN_IF(NS_FAILED(rv))) {
1311         return rv;
1312       }
1313     } else {
1314       // XXX Without recourse to some parser/content sink/docshell hackery we
1315       // don't really know where the head ends and the body begins so we assume
1316       // that there is no body
1317       nsresult rv = ReplaceHeadContentsWithHTML(Substring(beginhead, endtotal));
1318       if (NS_WARN_IF(NS_FAILED(rv))) {
1319         return rv;
1320       }
1321     }
1322   } else {
1323     nsReadingIterator<char16_t> begintotal;
1324     aSourceString.BeginReading(begintotal);
1325     NS_NAMED_LITERAL_STRING(head, "<head>");
1326     if (foundclosehead) {
1327       nsresult rv =
1328         ReplaceHeadContentsWithHTML(head + Substring(begintotal,
1329                                                      beginclosehead));
1330       if (NS_WARN_IF(NS_FAILED(rv))) {
1331         return rv;
1332       }
1333     } else if (foundbody) {
1334       nsresult rv = ReplaceHeadContentsWithHTML(head + Substring(begintotal,
1335                                                                  beginbody));
1336       if (NS_WARN_IF(NS_FAILED(rv))) {
1337         return rv;
1338       }
1339     } else {
1340       // XXX Without recourse to some parser/content sink/docshell hackery we
1341       // don't really know where the head ends and the body begins so we assume
1342       // that there is no head
1343       nsresult rv = ReplaceHeadContentsWithHTML(head);
1344       if (NS_WARN_IF(NS_FAILED(rv))) {
1345         return rv;
1346       }
1347     }
1348   }
1349 
1350   nsresult rv = SelectAll();
1351   NS_ENSURE_SUCCESS(rv, rv);
1352 
1353   if (!foundbody) {
1354     NS_NAMED_LITERAL_STRING(body, "<body>");
1355     // XXX Without recourse to some parser/content sink/docshell hackery we
1356     // don't really know where the head ends and the body begins
1357     if (foundclosehead) {
1358       // assume body starts after the head ends
1359       nsresult rv = LoadHTML(body + Substring(endclosehead, endtotal));
1360       if (NS_WARN_IF(NS_FAILED(rv))) {
1361         return rv;
1362       }
1363     } else if (foundhead) {
1364       // assume there is no body
1365       nsresult rv = LoadHTML(body);
1366       if (NS_WARN_IF(NS_FAILED(rv))) {
1367         return rv;
1368       }
1369     } else {
1370       // assume there is no head, the entire source is body
1371       nsresult rv = LoadHTML(body + aSourceString);
1372       if (NS_WARN_IF(NS_FAILED(rv))) {
1373         return rv;
1374       }
1375     }
1376 
1377     nsCOMPtr<Element> divElement =
1378       CreateElementWithDefaults(NS_LITERAL_STRING("div"));
1379     NS_ENSURE_TRUE(divElement, NS_ERROR_FAILURE);
1380 
1381     CloneAttributes(bodyElement, divElement);
1382 
1383     return BeginningOfDocument();
1384   }
1385 
1386   rv = LoadHTML(Substring(beginbody, endtotal));
1387   NS_ENSURE_SUCCESS(rv, rv);
1388 
1389   // Now we must copy attributes user might have edited on the <body> tag
1390   // because InsertHTML (actually, CreateContextualFragment()) will never
1391   // return a body node in the DOM fragment
1392 
1393   // We already know where "<body" begins
1394   nsReadingIterator<char16_t> beginclosebody = beginbody;
1395   nsReadingIterator<char16_t> endclosebody;
1396   aSourceString.EndReading(endclosebody);
1397   if (!FindInReadable(NS_LITERAL_STRING(">"), beginclosebody, endclosebody)) {
1398     return NS_ERROR_FAILURE;
1399   }
1400 
1401   // Truncate at the end of the body tag.  Kludge of the year: fool the parser
1402   // by replacing "body" with "div" so we get a node
1403   nsAutoString bodyTag;
1404   bodyTag.AssignLiteral("<div ");
1405   bodyTag.Append(Substring(endbody, endclosebody));
1406 
1407   RefPtr<nsRange> range = selection->GetRangeAt(0);
1408   NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
1409 
1410   ErrorResult erv;
1411   RefPtr<DocumentFragment> docfrag =
1412     range->CreateContextualFragment(bodyTag, erv);
1413   NS_ENSURE_TRUE(!erv.Failed(), erv.StealNSResult());
1414   NS_ENSURE_TRUE(docfrag, NS_ERROR_NULL_POINTER);
1415 
1416   nsCOMPtr<nsIContent> child = docfrag->GetFirstChild();
1417   NS_ENSURE_TRUE(child && child->IsElement(), NS_ERROR_NULL_POINTER);
1418 
1419   // Copy all attributes from the div child to current body element
1420   CloneAttributes(bodyElement, child->AsElement());
1421 
1422   // place selection at first editable content
1423   return BeginningOfDocument();
1424 }
1425 
1426 void
NormalizeEOLInsertPosition(nsINode * firstNodeToInsert,nsCOMPtr<nsIDOMNode> * insertParentNode,int32_t * insertOffset)1427 HTMLEditor::NormalizeEOLInsertPosition(nsINode* firstNodeToInsert,
1428                                        nsCOMPtr<nsIDOMNode>* insertParentNode,
1429                                        int32_t* insertOffset)
1430 {
1431   /*
1432     This function will either correct the position passed in,
1433     or leave the position unchanged.
1434 
1435     When the (first) item to insert is a block level element,
1436     and our insertion position is after the last visible item in a line,
1437     i.e. the insertion position is just before a visible line break <br>,
1438     we want to skip to the position just after the line break (see bug 68767)
1439 
1440     However, our logic to detect whether we should skip or not
1441     needs to be more clever.
1442     We must not skip when the caret appears to be positioned at the beginning
1443     of a block, in that case skipping the <br> would not insert the <br>
1444     at the caret position, but after the current empty line.
1445 
1446     So we have several cases to test:
1447 
1448     1) We only ever want to skip, if the next visible thing after the current position is a break
1449 
1450     2) We do not want to skip if there is no previous visible thing at all
1451        That is detected if the call to PriorVisibleNode gives us an offset of zero.
1452        Because PriorVisibleNode always positions after the prior node, we would
1453        see an offset > 0, if there were a prior node.
1454 
1455     3) We do not want to skip, if both the next and the previous visible things are breaks.
1456 
1457     4) We do not want to skip if the previous visible thing is in a different block
1458        than the insertion position.
1459   */
1460 
1461   if (!IsBlockNode(firstNodeToInsert)) {
1462     return;
1463   }
1464 
1465   WSRunObject wsObj(this, *insertParentNode, *insertOffset);
1466   nsCOMPtr<nsINode> nextVisNode, prevVisNode;
1467   int32_t nextVisOffset=0;
1468   WSType nextVisType;
1469   int32_t prevVisOffset=0;
1470   WSType prevVisType;
1471 
1472   nsCOMPtr<nsINode> parent(do_QueryInterface(*insertParentNode));
1473   wsObj.NextVisibleNode(parent, *insertOffset, address_of(nextVisNode), &nextVisOffset, &nextVisType);
1474   if (!nextVisNode) {
1475     return;
1476   }
1477 
1478   if (!(nextVisType & WSType::br)) {
1479     return;
1480   }
1481 
1482   wsObj.PriorVisibleNode(parent, *insertOffset, address_of(prevVisNode), &prevVisOffset, &prevVisType);
1483   if (!prevVisNode) {
1484     return;
1485   }
1486 
1487   if (prevVisType & WSType::br) {
1488     return;
1489   }
1490 
1491   if (prevVisType & WSType::thisBlock) {
1492     return;
1493   }
1494 
1495   int32_t brOffset=0;
1496   nsCOMPtr<nsIDOMNode> brNode = GetNodeLocation(GetAsDOMNode(nextVisNode), &brOffset);
1497 
1498   *insertParentNode = brNode;
1499   *insertOffset = brOffset + 1;
1500 }
1501 
1502 NS_IMETHODIMP
InsertElementAtSelection(nsIDOMElement * aElement,bool aDeleteSelection)1503 HTMLEditor::InsertElementAtSelection(nsIDOMElement* aElement,
1504                                      bool aDeleteSelection)
1505 {
1506   // Protect the edit rules object from dying
1507   nsCOMPtr<nsIEditRules> rules(mRules);
1508 
1509   nsCOMPtr<Element> element = do_QueryInterface(aElement);
1510   NS_ENSURE_TRUE(element, NS_ERROR_NULL_POINTER);
1511 
1512   nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement);
1513 
1514   ForceCompositionEnd();
1515   AutoEditBatch beginBatching(this);
1516   AutoRules beginRulesSniffing(this, EditAction::insertElement,
1517                                nsIEditor::eNext);
1518 
1519   RefPtr<Selection> selection = GetSelection();
1520   if (!selection) {
1521     return NS_ERROR_FAILURE;
1522   }
1523 
1524   // hand off to the rules system, see if it has anything to say about this
1525   bool cancel, handled;
1526   TextRulesInfo ruleInfo(EditAction::insertElement);
1527   ruleInfo.insertElement = aElement;
1528   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
1529   if (cancel || NS_FAILED(rv)) {
1530     return rv;
1531   }
1532 
1533   if (!handled) {
1534     if (aDeleteSelection) {
1535       if (!IsBlockNode(element)) {
1536         // E.g., inserting an image.  In this case we don't need to delete any
1537         // inline wrappers before we do the insertion.  Otherwise we let
1538         // DeleteSelectionAndPrepareToCreateNode do the deletion for us, which
1539         // calls DeleteSelection with aStripWrappers = eStrip.
1540         rv = DeleteSelection(nsIEditor::eNone, nsIEditor::eNoStrip);
1541         NS_ENSURE_SUCCESS(rv, rv);
1542       }
1543 
1544       nsresult rv = DeleteSelectionAndPrepareToCreateNode();
1545       NS_ENSURE_SUCCESS(rv, rv);
1546     }
1547 
1548     // If deleting, selection will be collapsed.
1549     // so if not, we collapse it
1550     if (!aDeleteSelection) {
1551       // Named Anchor is a special case,
1552       // We collapse to insert element BEFORE the selection
1553       // For all other tags, we insert AFTER the selection
1554       if (HTMLEditUtils::IsNamedAnchor(node)) {
1555         selection->CollapseToStart();
1556       } else {
1557         selection->CollapseToEnd();
1558       }
1559     }
1560 
1561     nsCOMPtr<nsIDOMNode> parentSelectedNode;
1562     int32_t offsetForInsert;
1563     rv = selection->GetAnchorNode(getter_AddRefs(parentSelectedNode));
1564     // XXX: ERROR_HANDLING bad XPCOM usage
1565     if (NS_SUCCEEDED(rv) &&
1566         NS_SUCCEEDED(selection->GetAnchorOffset(&offsetForInsert)) &&
1567         parentSelectedNode) {
1568       // Adjust position based on the node we are going to insert.
1569       NormalizeEOLInsertPosition(element, address_of(parentSelectedNode),
1570                                  &offsetForInsert);
1571 
1572       rv = InsertNodeAtPoint(node, address_of(parentSelectedNode),
1573                              &offsetForInsert, false);
1574       NS_ENSURE_SUCCESS(rv, rv);
1575       // Set caret after element, but check for special case
1576       //  of inserting table-related elements: set in first cell instead
1577       if (!SetCaretInTableCell(aElement)) {
1578         rv = SetCaretAfterElement(aElement);
1579         NS_ENSURE_SUCCESS(rv, rv);
1580       }
1581       // check for inserting a whole table at the end of a block. If so insert a br after it.
1582       if (HTMLEditUtils::IsTable(node)) {
1583         bool isLast;
1584         rv = IsLastEditableChild(node, &isLast);
1585         NS_ENSURE_SUCCESS(rv, rv);
1586         if (isLast) {
1587           nsCOMPtr<nsIDOMNode> brNode;
1588           rv = CreateBR(parentSelectedNode, offsetForInsert + 1,
1589                         address_of(brNode));
1590           NS_ENSURE_SUCCESS(rv, rv);
1591           selection->Collapse(parentSelectedNode, offsetForInsert+1);
1592         }
1593       }
1594     }
1595   }
1596   rv = rules->DidDoAction(selection, &ruleInfo, rv);
1597   return rv;
1598 }
1599 
1600 
1601 /**
1602  * InsertNodeAtPoint() attempts to insert aNode into the document, at a point
1603  * specified by {*ioParent,*ioOffset}.  Checks with strict dtd to see if
1604  * containment is allowed.  If not allowed, will attempt to find a parent in
1605  * the parent hierarchy of *ioParent that will accept aNode as a child.  If
1606  * such a parent is found, will split the document tree from
1607  * {*ioParent,*ioOffset} up to parent, and then insert aNode.
1608  * ioParent & ioOffset are then adjusted to point to the actual location that
1609  * aNode was inserted at.  aNoEmptyNodes specifies if the splitting process
1610  * is allowed to reslt in empty nodes.
1611  *
1612  * @param aNode             Node to insert.
1613  * @param ioParent          Insertion parent.
1614  * @param ioOffset          Insertion offset.
1615  * @param aNoEmptyNodes     Splitting can result in empty nodes?
1616  */
1617 nsresult
InsertNodeAtPoint(nsIDOMNode * aNode,nsCOMPtr<nsIDOMNode> * ioParent,int32_t * ioOffset,bool aNoEmptyNodes)1618 HTMLEditor::InsertNodeAtPoint(nsIDOMNode* aNode,
1619                               nsCOMPtr<nsIDOMNode>* ioParent,
1620                               int32_t* ioOffset,
1621                               bool aNoEmptyNodes)
1622 {
1623   nsCOMPtr<nsIContent> node = do_QueryInterface(aNode);
1624   NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
1625   NS_ENSURE_TRUE(ioParent, NS_ERROR_NULL_POINTER);
1626   NS_ENSURE_TRUE(*ioParent, NS_ERROR_NULL_POINTER);
1627   NS_ENSURE_TRUE(ioOffset, NS_ERROR_NULL_POINTER);
1628 
1629   nsCOMPtr<nsIContent> parent = do_QueryInterface(*ioParent);
1630   NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
1631   nsCOMPtr<nsIContent> topChild = parent;
1632   nsCOMPtr<nsIContent> origParent = parent;
1633 
1634   // Search up the parent chain to find a suitable container
1635   while (!CanContain(*parent, *node)) {
1636     // If the current parent is a root (body or table element)
1637     // then go no further - we can't insert
1638     if (parent->IsHTMLElement(nsGkAtoms::body) ||
1639         HTMLEditUtils::IsTableElement(parent)) {
1640       return NS_ERROR_FAILURE;
1641     }
1642     // Get the next parent
1643     NS_ENSURE_TRUE(parent->GetParentNode(), NS_ERROR_FAILURE);
1644     if (!IsEditable(parent->GetParentNode())) {
1645       // There's no suitable place to put the node in this editing host.  Maybe
1646       // someone is trying to put block content in a span.  So just put it
1647       // where we were originally asked.
1648       parent = topChild = origParent;
1649       break;
1650     }
1651     topChild = parent;
1652     parent = parent->GetParent();
1653   }
1654   if (parent != topChild) {
1655     // we need to split some levels above the original selection parent
1656     int32_t offset = SplitNodeDeep(*topChild, *origParent, *ioOffset,
1657                                    aNoEmptyNodes ? EmptyContainers::no
1658                                                  : EmptyContainers::yes);
1659     NS_ENSURE_STATE(offset != -1);
1660     *ioParent = GetAsDOMNode(parent);
1661     *ioOffset = offset;
1662   }
1663   // Now we can insert the new node
1664   return InsertNode(*node, *parent, *ioOffset);
1665 }
1666 
1667 NS_IMETHODIMP
SelectElement(nsIDOMElement * aElement)1668 HTMLEditor::SelectElement(nsIDOMElement* aElement)
1669 {
1670   nsCOMPtr<Element> element = do_QueryInterface(aElement);
1671   NS_ENSURE_STATE(element || !aElement);
1672 
1673   // Must be sure that element is contained in the document body
1674   if (!IsDescendantOfEditorRoot(element)) {
1675     return NS_ERROR_NULL_POINTER;
1676   }
1677 
1678   RefPtr<Selection> selection = GetSelection();
1679   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1680   nsCOMPtr<nsIDOMNode>parent;
1681   nsresult rv = aElement->GetParentNode(getter_AddRefs(parent));
1682   if (NS_SUCCEEDED(rv) && parent) {
1683     int32_t offsetInParent = GetChildOffset(aElement, parent);
1684 
1685     // Collapse selection to just before desired element,
1686     rv = selection->Collapse(parent, offsetInParent);
1687     if (NS_SUCCEEDED(rv)) {
1688       // then extend it to just after
1689       rv = selection->Extend(parent, offsetInParent + 1);
1690     }
1691   }
1692   return rv;
1693 }
1694 
1695 NS_IMETHODIMP
SetCaretAfterElement(nsIDOMElement * aElement)1696 HTMLEditor::SetCaretAfterElement(nsIDOMElement* aElement)
1697 {
1698   nsCOMPtr<Element> element = do_QueryInterface(aElement);
1699   NS_ENSURE_STATE(element || !aElement);
1700 
1701   // Be sure the element is contained in the document body
1702   if (!aElement || !IsDescendantOfEditorRoot(element)) {
1703     return NS_ERROR_NULL_POINTER;
1704   }
1705 
1706   RefPtr<Selection> selection = GetSelection();
1707   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1708   nsCOMPtr<nsIDOMNode>parent;
1709   nsresult rv = aElement->GetParentNode(getter_AddRefs(parent));
1710   NS_ENSURE_SUCCESS(rv, rv);
1711   NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
1712   int32_t offsetInParent = GetChildOffset(aElement, parent);
1713   // Collapse selection to just after desired element,
1714   return selection->Collapse(parent, offsetInParent + 1);
1715 }
1716 
1717 NS_IMETHODIMP
SetParagraphFormat(const nsAString & aParagraphFormat)1718 HTMLEditor::SetParagraphFormat(const nsAString& aParagraphFormat)
1719 {
1720   nsAutoString tag; tag.Assign(aParagraphFormat);
1721   ToLowerCase(tag);
1722   if (tag.EqualsLiteral("dd") || tag.EqualsLiteral("dt")) {
1723     return MakeDefinitionItem(tag);
1724   }
1725   return InsertBasicBlock(tag);
1726 }
1727 
1728 NS_IMETHODIMP
GetParagraphState(bool * aMixed,nsAString & outFormat)1729 HTMLEditor::GetParagraphState(bool* aMixed,
1730                               nsAString& outFormat)
1731 {
1732   if (!mRules) {
1733     return NS_ERROR_NOT_INITIALIZED;
1734   }
1735   NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
1736   RefPtr<HTMLEditRules> htmlRules =
1737     static_cast<HTMLEditRules*>(mRules.get());
1738 
1739   return htmlRules->GetParagraphState(aMixed, outFormat);
1740 }
1741 
1742 NS_IMETHODIMP
GetBackgroundColorState(bool * aMixed,nsAString & aOutColor)1743 HTMLEditor::GetBackgroundColorState(bool* aMixed,
1744                                     nsAString& aOutColor)
1745 {
1746   if (IsCSSEnabled()) {
1747     // if we are in CSS mode, we have to check if the containing block defines
1748     // a background color
1749     return GetCSSBackgroundColorState(aMixed, aOutColor, true);
1750   }
1751   // in HTML mode, we look only at page's background
1752   return GetHTMLBackgroundColorState(aMixed, aOutColor);
1753 }
1754 
1755 NS_IMETHODIMP
GetHighlightColorState(bool * aMixed,nsAString & aOutColor)1756 HTMLEditor::GetHighlightColorState(bool* aMixed,
1757                                    nsAString& aOutColor)
1758 {
1759   *aMixed = false;
1760   aOutColor.AssignLiteral("transparent");
1761   if (!IsCSSEnabled()) {
1762     return NS_OK;
1763   }
1764 
1765   // in CSS mode, text background can be added by the Text Highlight button
1766   // we need to query the background of the selection without looking for
1767   // the block container of the ranges in the selection
1768   return GetCSSBackgroundColorState(aMixed, aOutColor, false);
1769 }
1770 
1771 nsresult
GetCSSBackgroundColorState(bool * aMixed,nsAString & aOutColor,bool aBlockLevel)1772 HTMLEditor::GetCSSBackgroundColorState(bool* aMixed,
1773                                        nsAString& aOutColor,
1774                                        bool aBlockLevel)
1775 {
1776   NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
1777   *aMixed = false;
1778   // the default background color is transparent
1779   aOutColor.AssignLiteral("transparent");
1780 
1781   // get selection
1782   RefPtr<Selection> selection = GetSelection();
1783   NS_ENSURE_STATE(selection && selection->GetRangeAt(0));
1784 
1785   // get selection location
1786   nsCOMPtr<nsINode> parent = selection->GetRangeAt(0)->GetStartParent();
1787   int32_t offset = selection->GetRangeAt(0)->StartOffset();
1788   NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
1789 
1790   // is the selection collapsed?
1791   nsCOMPtr<nsINode> nodeToExamine;
1792   if (selection->Collapsed() || IsTextNode(parent)) {
1793     // we want to look at the parent and ancestors
1794     nodeToExamine = parent;
1795   } else {
1796     // otherwise we want to look at the first editable node after
1797     // {parent,offset} and its ancestors for divs with alignment on them
1798     nodeToExamine = parent->GetChildAt(offset);
1799     //GetNextNode(parent, offset, true, address_of(nodeToExamine));
1800   }
1801 
1802   NS_ENSURE_TRUE(nodeToExamine, NS_ERROR_NULL_POINTER);
1803 
1804   if (aBlockLevel) {
1805     // we are querying the block background (and not the text background), let's
1806     // climb to the block container
1807     nsCOMPtr<Element> blockParent = GetBlock(*nodeToExamine);
1808     NS_ENSURE_TRUE(blockParent, NS_OK);
1809 
1810     // Make sure to not walk off onto the Document node
1811     do {
1812       // retrieve the computed style of background-color for blockParent
1813       mCSSEditUtils->GetComputedProperty(*blockParent,
1814                                          *nsGkAtoms::backgroundColor,
1815                                          aOutColor);
1816       blockParent = blockParent->GetParentElement();
1817       // look at parent if the queried color is transparent and if the node to
1818       // examine is not the root of the document
1819     } while (aOutColor.EqualsLiteral("transparent") && blockParent);
1820     if (aOutColor.EqualsLiteral("transparent")) {
1821       // we have hit the root of the document and the color is still transparent !
1822       // Grumble... Let's look at the default background color because that's the
1823       // color we are looking for
1824       mCSSEditUtils->GetDefaultBackgroundColor(aOutColor);
1825     }
1826   }
1827   else {
1828     // no, we are querying the text background for the Text Highlight button
1829     if (IsTextNode(nodeToExamine)) {
1830       // if the node of interest is a text node, let's climb a level
1831       nodeToExamine = nodeToExamine->GetParentNode();
1832     }
1833     do {
1834       // is the node to examine a block ?
1835       if (NodeIsBlockStatic(nodeToExamine)) {
1836         // yes it is a block; in that case, the text background color is transparent
1837         aOutColor.AssignLiteral("transparent");
1838         break;
1839       } else {
1840         // no, it's not; let's retrieve the computed style of background-color for the
1841         // node to examine
1842         mCSSEditUtils->GetComputedProperty(*nodeToExamine,
1843                                            *nsGkAtoms::backgroundColor,
1844                                            aOutColor);
1845         if (!aOutColor.EqualsLiteral("transparent")) {
1846           break;
1847         }
1848       }
1849       nodeToExamine = nodeToExamine->GetParentNode();
1850     } while ( aOutColor.EqualsLiteral("transparent") && nodeToExamine );
1851   }
1852   return NS_OK;
1853 }
1854 
1855 NS_IMETHODIMP
GetHTMLBackgroundColorState(bool * aMixed,nsAString & aOutColor)1856 HTMLEditor::GetHTMLBackgroundColorState(bool* aMixed,
1857                                         nsAString& aOutColor)
1858 {
1859   //TODO: We don't handle "mixed" correctly!
1860   NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
1861   *aMixed = false;
1862   aOutColor.Truncate();
1863 
1864   nsCOMPtr<nsIDOMElement> domElement;
1865   int32_t selectedCount;
1866   nsAutoString tagName;
1867   nsresult rv = GetSelectedOrParentTableElement(tagName,
1868                                                 &selectedCount,
1869                                                 getter_AddRefs(domElement));
1870   NS_ENSURE_SUCCESS(rv, rv);
1871 
1872   nsCOMPtr<dom::Element> element = do_QueryInterface(domElement);
1873 
1874   while (element) {
1875     // We are in a cell or selected table
1876     element->GetAttr(kNameSpaceID_None, nsGkAtoms::bgcolor, aOutColor);
1877 
1878     // Done if we have a color explicitly set
1879     if (!aOutColor.IsEmpty()) {
1880       return NS_OK;
1881     }
1882 
1883     // Once we hit the body, we're done
1884     if (element->IsHTMLElement(nsGkAtoms::body)) {
1885       return NS_OK;
1886     }
1887 
1888     // No color is set, but we need to report visible color inherited
1889     // from nested cells/tables, so search up parent chain
1890     element = element->GetParentElement();
1891   }
1892 
1893   // If no table or cell found, get page body
1894   dom::Element* bodyElement = GetRoot();
1895   NS_ENSURE_TRUE(bodyElement, NS_ERROR_NULL_POINTER);
1896 
1897   bodyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::bgcolor, aOutColor);
1898   return NS_OK;
1899 }
1900 
1901 NS_IMETHODIMP
GetListState(bool * aMixed,bool * aOL,bool * aUL,bool * aDL)1902 HTMLEditor::GetListState(bool* aMixed,
1903                          bool* aOL,
1904                          bool* aUL,
1905                          bool* aDL)
1906 {
1907   if (!mRules) {
1908     return NS_ERROR_NOT_INITIALIZED;
1909   }
1910   NS_ENSURE_TRUE(aMixed && aOL && aUL && aDL, NS_ERROR_NULL_POINTER);
1911   RefPtr<HTMLEditRules> htmlRules =
1912     static_cast<HTMLEditRules*>(mRules.get());
1913 
1914   return htmlRules->GetListState(aMixed, aOL, aUL, aDL);
1915 }
1916 
1917 NS_IMETHODIMP
GetListItemState(bool * aMixed,bool * aLI,bool * aDT,bool * aDD)1918 HTMLEditor::GetListItemState(bool* aMixed,
1919                              bool* aLI,
1920                              bool* aDT,
1921                              bool* aDD)
1922 {
1923   if (!mRules) {
1924     return NS_ERROR_NOT_INITIALIZED;
1925   }
1926   NS_ENSURE_TRUE(aMixed && aLI && aDT && aDD, NS_ERROR_NULL_POINTER);
1927 
1928   RefPtr<HTMLEditRules> htmlRules =
1929     static_cast<HTMLEditRules*>(mRules.get());
1930 
1931   return htmlRules->GetListItemState(aMixed, aLI, aDT, aDD);
1932 }
1933 
1934 NS_IMETHODIMP
GetAlignment(bool * aMixed,nsIHTMLEditor::EAlignment * aAlign)1935 HTMLEditor::GetAlignment(bool* aMixed,
1936                          nsIHTMLEditor::EAlignment* aAlign)
1937 {
1938   if (!mRules) {
1939     return NS_ERROR_NOT_INITIALIZED;
1940   }
1941   NS_ENSURE_TRUE(aMixed && aAlign, NS_ERROR_NULL_POINTER);
1942   RefPtr<HTMLEditRules> htmlRules =
1943     static_cast<HTMLEditRules*>(mRules.get());
1944 
1945   return htmlRules->GetAlignment(aMixed, aAlign);
1946 }
1947 
1948 NS_IMETHODIMP
GetIndentState(bool * aCanIndent,bool * aCanOutdent)1949 HTMLEditor::GetIndentState(bool* aCanIndent,
1950                            bool* aCanOutdent)
1951 {
1952   if (!mRules) {
1953     return NS_ERROR_NOT_INITIALIZED;
1954   }
1955   NS_ENSURE_TRUE(aCanIndent && aCanOutdent, NS_ERROR_NULL_POINTER);
1956 
1957   RefPtr<HTMLEditRules> htmlRules =
1958     static_cast<HTMLEditRules*>(mRules.get());
1959 
1960   return htmlRules->GetIndentState(aCanIndent, aCanOutdent);
1961 }
1962 
1963 NS_IMETHODIMP
MakeOrChangeList(const nsAString & aListType,bool entireList,const nsAString & aBulletType)1964 HTMLEditor::MakeOrChangeList(const nsAString& aListType,
1965                              bool entireList,
1966                              const nsAString& aBulletType)
1967 {
1968   if (!mRules) {
1969     return NS_ERROR_NOT_INITIALIZED;
1970   }
1971 
1972   // Protect the edit rules object from dying
1973   nsCOMPtr<nsIEditRules> rules(mRules);
1974 
1975   bool cancel, handled;
1976 
1977   AutoEditBatch beginBatching(this);
1978   AutoRules beginRulesSniffing(this, EditAction::makeList, nsIEditor::eNext);
1979 
1980   // pre-process
1981   RefPtr<Selection> selection = GetSelection();
1982   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1983 
1984   TextRulesInfo ruleInfo(EditAction::makeList);
1985   ruleInfo.blockType = &aListType;
1986   ruleInfo.entireList = entireList;
1987   ruleInfo.bulletType = &aBulletType;
1988   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
1989   if (cancel || NS_FAILED(rv)) {
1990     return rv;
1991   }
1992 
1993   if (!handled) {
1994     // Find out if the selection is collapsed:
1995     bool isCollapsed = selection->Collapsed();
1996 
1997     NS_ENSURE_TRUE(selection->GetRangeAt(0) &&
1998                    selection->GetRangeAt(0)->GetStartParent() &&
1999                    selection->GetRangeAt(0)->GetStartParent()->IsContent(),
2000                    NS_ERROR_FAILURE);
2001     OwningNonNull<nsIContent> node =
2002       *selection->GetRangeAt(0)->GetStartParent()->AsContent();
2003     int32_t offset = selection->GetRangeAt(0)->StartOffset();
2004 
2005     if (isCollapsed) {
2006       // have to find a place to put the list
2007       nsCOMPtr<nsIContent> parent = node;
2008       nsCOMPtr<nsIContent> topChild = node;
2009 
2010       nsCOMPtr<nsIAtom> listAtom = NS_Atomize(aListType);
2011       while (!CanContainTag(*parent, *listAtom)) {
2012         topChild = parent;
2013         parent = parent->GetParent();
2014       }
2015 
2016       if (parent != node) {
2017         // we need to split up to the child of parent
2018         offset = SplitNodeDeep(*topChild, *node, offset);
2019         NS_ENSURE_STATE(offset != -1);
2020       }
2021 
2022       // make a list
2023       nsCOMPtr<Element> newList = CreateNode(listAtom, parent, offset);
2024       NS_ENSURE_STATE(newList);
2025       // make a list item
2026       nsCOMPtr<Element> newItem = CreateNode(nsGkAtoms::li, newList, 0);
2027       NS_ENSURE_STATE(newItem);
2028       rv = selection->Collapse(newItem, 0);
2029       NS_ENSURE_SUCCESS(rv, rv);
2030     }
2031   }
2032 
2033   return rules->DidDoAction(selection, &ruleInfo, rv);
2034 }
2035 
2036 NS_IMETHODIMP
RemoveList(const nsAString & aListType)2037 HTMLEditor::RemoveList(const nsAString& aListType)
2038 {
2039   if (!mRules) {
2040     return NS_ERROR_NOT_INITIALIZED;
2041   }
2042 
2043   // Protect the edit rules object from dying
2044   nsCOMPtr<nsIEditRules> rules(mRules);
2045 
2046   bool cancel, handled;
2047 
2048   AutoEditBatch beginBatching(this);
2049   AutoRules beginRulesSniffing(this, EditAction::removeList, nsIEditor::eNext);
2050 
2051   // pre-process
2052   RefPtr<Selection> selection = GetSelection();
2053   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2054 
2055   TextRulesInfo ruleInfo(EditAction::removeList);
2056   if (aListType.LowerCaseEqualsLiteral("ol")) {
2057     ruleInfo.bOrdered = true;
2058   } else {
2059     ruleInfo.bOrdered = false;
2060   }
2061   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
2062   if (cancel || NS_FAILED(rv)) {
2063     return rv;
2064   }
2065 
2066   // no default behavior for this yet.  what would it mean?
2067 
2068   return rules->DidDoAction(selection, &ruleInfo, rv);
2069 }
2070 
2071 nsresult
MakeDefinitionItem(const nsAString & aItemType)2072 HTMLEditor::MakeDefinitionItem(const nsAString& aItemType)
2073 {
2074   if (!mRules) {
2075     return NS_ERROR_NOT_INITIALIZED;
2076   }
2077 
2078   // Protect the edit rules object from dying
2079   nsCOMPtr<nsIEditRules> rules(mRules);
2080 
2081   bool cancel, handled;
2082 
2083   AutoEditBatch beginBatching(this);
2084   AutoRules beginRulesSniffing(this, EditAction::makeDefListItem,
2085                                nsIEditor::eNext);
2086 
2087   // pre-process
2088   RefPtr<Selection> selection = GetSelection();
2089   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2090   TextRulesInfo ruleInfo(EditAction::makeDefListItem);
2091   ruleInfo.blockType = &aItemType;
2092   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
2093   if (cancel || NS_FAILED(rv)) {
2094     return rv;
2095   }
2096 
2097   if (!handled) {
2098     // todo: no default for now.  we count on rules to handle it.
2099   }
2100 
2101   return rules->DidDoAction(selection, &ruleInfo, rv);
2102 }
2103 
2104 nsresult
InsertBasicBlock(const nsAString & aBlockType)2105 HTMLEditor::InsertBasicBlock(const nsAString& aBlockType)
2106 {
2107   if (!mRules) {
2108     return NS_ERROR_NOT_INITIALIZED;
2109   }
2110 
2111   // Protect the edit rules object from dying
2112   nsCOMPtr<nsIEditRules> rules(mRules);
2113 
2114   bool cancel, handled;
2115 
2116   AutoEditBatch beginBatching(this);
2117   AutoRules beginRulesSniffing(this, EditAction::makeBasicBlock,
2118                                nsIEditor::eNext);
2119 
2120   // pre-process
2121   RefPtr<Selection> selection = GetSelection();
2122   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2123   TextRulesInfo ruleInfo(EditAction::makeBasicBlock);
2124   ruleInfo.blockType = &aBlockType;
2125   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
2126   if (cancel || NS_FAILED(rv)) {
2127     return rv;
2128   }
2129 
2130   if (!handled) {
2131     // Find out if the selection is collapsed:
2132     bool isCollapsed = selection->Collapsed();
2133 
2134     NS_ENSURE_TRUE(selection->GetRangeAt(0) &&
2135                    selection->GetRangeAt(0)->GetStartParent() &&
2136                    selection->GetRangeAt(0)->GetStartParent()->IsContent(),
2137                    NS_ERROR_FAILURE);
2138     OwningNonNull<nsIContent> node =
2139       *selection->GetRangeAt(0)->GetStartParent()->AsContent();
2140     int32_t offset = selection->GetRangeAt(0)->StartOffset();
2141 
2142     if (isCollapsed) {
2143       // have to find a place to put the block
2144       nsCOMPtr<nsIContent> parent = node;
2145       nsCOMPtr<nsIContent> topChild = node;
2146 
2147       nsCOMPtr<nsIAtom> blockAtom = NS_Atomize(aBlockType);
2148       while (!CanContainTag(*parent, *blockAtom)) {
2149         NS_ENSURE_TRUE(parent->GetParent(), NS_ERROR_FAILURE);
2150         topChild = parent;
2151         parent = parent->GetParent();
2152       }
2153 
2154       if (parent != node) {
2155         // we need to split up to the child of parent
2156         offset = SplitNodeDeep(*topChild, *node, offset);
2157         NS_ENSURE_STATE(offset != -1);
2158       }
2159 
2160       // make a block
2161       nsCOMPtr<Element> newBlock = CreateNode(blockAtom, parent, offset);
2162       NS_ENSURE_STATE(newBlock);
2163 
2164       // reposition selection to inside the block
2165       rv = selection->Collapse(newBlock, 0);
2166       NS_ENSURE_SUCCESS(rv, rv);
2167     }
2168   }
2169 
2170   return rules->DidDoAction(selection, &ruleInfo, rv);
2171 }
2172 
2173 NS_IMETHODIMP
Indent(const nsAString & aIndent)2174 HTMLEditor::Indent(const nsAString& aIndent)
2175 {
2176   if (!mRules) {
2177     return NS_ERROR_NOT_INITIALIZED;
2178   }
2179 
2180   // Protect the edit rules object from dying
2181   nsCOMPtr<nsIEditRules> rules(mRules);
2182 
2183   bool cancel, handled;
2184   EditAction opID = EditAction::indent;
2185   if (aIndent.LowerCaseEqualsLiteral("outdent")) {
2186     opID = EditAction::outdent;
2187   }
2188   AutoEditBatch beginBatching(this);
2189   AutoRules beginRulesSniffing(this, opID, nsIEditor::eNext);
2190 
2191   // pre-process
2192   RefPtr<Selection> selection = GetSelection();
2193   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2194 
2195   TextRulesInfo ruleInfo(opID);
2196   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
2197   if (cancel || NS_FAILED(rv)) {
2198     return rv;
2199   }
2200 
2201   if (!handled) {
2202     // Do default - insert a blockquote node if selection collapsed
2203     bool isCollapsed = selection->Collapsed();
2204 
2205     NS_ENSURE_TRUE(selection->GetRangeAt(0) &&
2206                    selection->GetRangeAt(0)->GetStartParent() &&
2207                    selection->GetRangeAt(0)->GetStartParent()->IsContent(),
2208                    NS_ERROR_FAILURE);
2209     OwningNonNull<nsIContent> node =
2210       *selection->GetRangeAt(0)->GetStartParent()->AsContent();
2211     int32_t offset = selection->GetRangeAt(0)->StartOffset();
2212 
2213     if (aIndent.EqualsLiteral("indent")) {
2214       if (isCollapsed) {
2215         // have to find a place to put the blockquote
2216         nsCOMPtr<nsIContent> parent = node;
2217         nsCOMPtr<nsIContent> topChild = node;
2218         while (!CanContainTag(*parent, *nsGkAtoms::blockquote)) {
2219           NS_ENSURE_TRUE(parent->GetParent(), NS_ERROR_FAILURE);
2220           topChild = parent;
2221           parent = parent->GetParent();
2222         }
2223 
2224         if (parent != node) {
2225           // we need to split up to the child of parent
2226           offset = SplitNodeDeep(*topChild, *node, offset);
2227           NS_ENSURE_STATE(offset != -1);
2228         }
2229 
2230         // make a blockquote
2231         nsCOMPtr<Element> newBQ = CreateNode(nsGkAtoms::blockquote, parent, offset);
2232         NS_ENSURE_STATE(newBQ);
2233         // put a space in it so layout will draw the list item
2234         rv = selection->Collapse(newBQ, 0);
2235         NS_ENSURE_SUCCESS(rv, rv);
2236         rv = InsertText(NS_LITERAL_STRING(" "));
2237         NS_ENSURE_SUCCESS(rv, rv);
2238         // reposition selection to before the space character
2239         NS_ENSURE_STATE(selection->GetRangeAt(0));
2240         rv = selection->Collapse(selection->GetRangeAt(0)->GetStartParent(), 0);
2241         NS_ENSURE_SUCCESS(rv, rv);
2242       }
2243     }
2244   }
2245   return rules->DidDoAction(selection, &ruleInfo, rv);
2246 }
2247 
2248 //TODO: IMPLEMENT ALIGNMENT!
2249 
2250 NS_IMETHODIMP
Align(const nsAString & aAlignType)2251 HTMLEditor::Align(const nsAString& aAlignType)
2252 {
2253   // Protect the edit rules object from dying
2254   nsCOMPtr<nsIEditRules> rules(mRules);
2255 
2256   AutoEditBatch beginBatching(this);
2257   AutoRules beginRulesSniffing(this, EditAction::align, nsIEditor::eNext);
2258 
2259   bool cancel, handled;
2260 
2261   // Find out if the selection is collapsed:
2262   RefPtr<Selection> selection = GetSelection();
2263   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2264   TextRulesInfo ruleInfo(EditAction::align);
2265   ruleInfo.alignType = &aAlignType;
2266   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
2267   if (cancel || NS_FAILED(rv)) {
2268     return rv;
2269   }
2270 
2271   return rules->DidDoAction(selection, &ruleInfo, rv);
2272 }
2273 
2274 already_AddRefed<Element>
GetElementOrParentByTagName(const nsAString & aTagName,nsINode * aNode)2275 HTMLEditor::GetElementOrParentByTagName(const nsAString& aTagName,
2276                                         nsINode* aNode)
2277 {
2278   MOZ_ASSERT(!aTagName.IsEmpty());
2279 
2280   nsCOMPtr<nsINode> node = aNode;
2281   if (!node) {
2282     // If no node supplied, get it from anchor node of current selection
2283     RefPtr<Selection> selection = GetSelection();
2284     NS_ENSURE_TRUE(selection, nullptr);
2285 
2286     nsCOMPtr<nsINode> anchorNode = selection->GetAnchorNode();
2287     NS_ENSURE_TRUE(anchorNode, nullptr);
2288 
2289     // Try to get the actual selected node
2290     if (anchorNode->HasChildNodes() && anchorNode->IsContent()) {
2291       node = anchorNode->GetChildAt(selection->AnchorOffset());
2292     }
2293     // Anchor node is probably a text node - just use that
2294     if (!node) {
2295       node = anchorNode;
2296     }
2297   }
2298 
2299   nsCOMPtr<Element> current;
2300   if (node->IsElement()) {
2301     current = node->AsElement();
2302   } else if (node->GetParentElement()) {
2303     current = node->GetParentElement();
2304   } else {
2305     // Neither aNode nor its parent is an element, so no ancestor is
2306     MOZ_ASSERT(!node->GetParentNode() ||
2307                !node->GetParentNode()->GetParentNode());
2308     return nullptr;
2309   }
2310 
2311   nsAutoString tagName(aTagName);
2312   ToLowerCase(tagName);
2313   bool getLink = IsLinkTag(tagName);
2314   bool getNamedAnchor = IsNamedAnchorTag(tagName);
2315   if (getLink || getNamedAnchor) {
2316     tagName.Assign('a');
2317   }
2318   bool findTableCell = tagName.EqualsLiteral("td");
2319   bool findList = tagName.EqualsLiteral("list");
2320 
2321   for (; current; current = current->GetParentElement()) {
2322     // Test if we have a link (an anchor with href set)
2323     if ((getLink && HTMLEditUtils::IsLink(current)) ||
2324         (getNamedAnchor && HTMLEditUtils::IsNamedAnchor(current))) {
2325       return current.forget();
2326     }
2327     if (findList) {
2328       // Match "ol", "ul", or "dl" for lists
2329       if (HTMLEditUtils::IsList(current)) {
2330         return current.forget();
2331       }
2332     } else if (findTableCell) {
2333       // Table cells are another special case: match either "td" or "th"
2334       if (HTMLEditUtils::IsTableCell(current)) {
2335         return current.forget();
2336       }
2337     } else if (current->NodeName().Equals(tagName,
2338                    nsCaseInsensitiveStringComparator())) {
2339       return current.forget();
2340     }
2341 
2342     // Stop searching if parent is a body tag.  Note: Originally used IsRoot to
2343     // stop at table cells, but that's too messy when you are trying to find
2344     // the parent table
2345     if (current->GetParentElement() &&
2346         current->GetParentElement()->IsHTMLElement(nsGkAtoms::body)) {
2347       break;
2348     }
2349   }
2350 
2351   return nullptr;
2352 }
2353 
2354 NS_IMETHODIMP
GetElementOrParentByTagName(const nsAString & aTagName,nsIDOMNode * aNode,nsIDOMElement ** aReturn)2355 HTMLEditor::GetElementOrParentByTagName(const nsAString& aTagName,
2356                                         nsIDOMNode* aNode,
2357                                         nsIDOMElement** aReturn)
2358 {
2359   NS_ENSURE_TRUE(!aTagName.IsEmpty(), NS_ERROR_NULL_POINTER);
2360   NS_ENSURE_TRUE(aReturn, NS_ERROR_NULL_POINTER);
2361 
2362   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
2363   nsCOMPtr<Element> parent =
2364     GetElementOrParentByTagName(aTagName, node);
2365   nsCOMPtr<nsIDOMElement> ret = do_QueryInterface(parent);
2366 
2367   if (!ret) {
2368     return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2369   }
2370 
2371   ret.forget(aReturn);
2372   return NS_OK;
2373 }
2374 
2375 NS_IMETHODIMP
GetSelectedElement(const nsAString & aTagName,nsIDOMElement ** aReturn)2376 HTMLEditor::GetSelectedElement(const nsAString& aTagName,
2377                                nsIDOMElement** aReturn)
2378 {
2379   NS_ENSURE_TRUE(aReturn , NS_ERROR_NULL_POINTER);
2380 
2381   // default is null - no element found
2382   *aReturn = nullptr;
2383 
2384   // First look for a single element in selection
2385   RefPtr<Selection> selection = GetSelection();
2386   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2387 
2388   bool bNodeFound = false;
2389   bool isCollapsed = selection->Collapsed();
2390 
2391   nsAutoString domTagName;
2392   nsAutoString TagName(aTagName);
2393   ToLowerCase(TagName);
2394   // Empty string indicates we should match any element tag
2395   bool anyTag = (TagName.IsEmpty());
2396   bool isLinkTag = IsLinkTag(TagName);
2397   bool isNamedAnchorTag = IsNamedAnchorTag(TagName);
2398 
2399   nsCOMPtr<nsIDOMElement> selectedElement;
2400   RefPtr<nsRange> range = selection->GetRangeAt(0);
2401   NS_ENSURE_STATE(range);
2402 
2403   nsCOMPtr<nsIDOMNode> startParent;
2404   int32_t startOffset, endOffset;
2405   nsresult rv = range->GetStartContainer(getter_AddRefs(startParent));
2406   NS_ENSURE_SUCCESS(rv, rv);
2407   rv = range->GetStartOffset(&startOffset);
2408   NS_ENSURE_SUCCESS(rv, rv);
2409 
2410   nsCOMPtr<nsIDOMNode> endParent;
2411   rv = range->GetEndContainer(getter_AddRefs(endParent));
2412   NS_ENSURE_SUCCESS(rv, rv);
2413   rv = range->GetEndOffset(&endOffset);
2414   NS_ENSURE_SUCCESS(rv, rv);
2415 
2416   // Optimization for a single selected element
2417   if (startParent && startParent == endParent && endOffset - startOffset == 1) {
2418     nsCOMPtr<nsIDOMNode> selectedNode = GetChildAt(startParent, startOffset);
2419     NS_ENSURE_SUCCESS(rv, NS_OK);
2420     if (selectedNode) {
2421       selectedNode->GetNodeName(domTagName);
2422       ToLowerCase(domTagName);
2423 
2424       // Test for appropriate node type requested
2425       if (anyTag || (TagName == domTagName) ||
2426           (isLinkTag && HTMLEditUtils::IsLink(selectedNode)) ||
2427           (isNamedAnchorTag && HTMLEditUtils::IsNamedAnchor(selectedNode))) {
2428         bNodeFound = true;
2429         selectedElement = do_QueryInterface(selectedNode);
2430       }
2431     }
2432   }
2433 
2434   if (!bNodeFound) {
2435     if (isLinkTag) {
2436       // Link tag is a special case - we return the anchor node
2437       //  found for any selection that is totally within a link,
2438       //  included a collapsed selection (just a caret in a link)
2439       nsCOMPtr<nsIDOMNode> anchorNode;
2440       rv = selection->GetAnchorNode(getter_AddRefs(anchorNode));
2441       NS_ENSURE_SUCCESS(rv, rv);
2442       int32_t anchorOffset = -1;
2443       if (anchorNode) {
2444         selection->GetAnchorOffset(&anchorOffset);
2445       }
2446 
2447       nsCOMPtr<nsIDOMNode> focusNode;
2448       rv = selection->GetFocusNode(getter_AddRefs(focusNode));
2449       NS_ENSURE_SUCCESS(rv, rv);
2450       int32_t focusOffset = -1;
2451       if (focusNode) {
2452         selection->GetFocusOffset(&focusOffset);
2453       }
2454 
2455       // Link node must be the same for both ends of selection
2456       if (NS_SUCCEEDED(rv) && anchorNode) {
2457         nsCOMPtr<nsIDOMElement> parentLinkOfAnchor;
2458         rv = GetElementOrParentByTagName(NS_LITERAL_STRING("href"),
2459                                          anchorNode,
2460                                          getter_AddRefs(parentLinkOfAnchor));
2461         // XXX: ERROR_HANDLING  can parentLinkOfAnchor be null?
2462         if (NS_SUCCEEDED(rv) && parentLinkOfAnchor) {
2463           if (isCollapsed) {
2464             // We have just a caret in the link
2465             bNodeFound = true;
2466           } else if (focusNode) {
2467             // Link node must be the same for both ends of selection.
2468             nsCOMPtr<nsIDOMElement> parentLinkOfFocus;
2469             rv = GetElementOrParentByTagName(NS_LITERAL_STRING("href"),
2470                                              focusNode,
2471                                              getter_AddRefs(parentLinkOfFocus));
2472             if (NS_SUCCEEDED(rv) && parentLinkOfFocus == parentLinkOfAnchor) {
2473               bNodeFound = true;
2474             }
2475           }
2476 
2477           // We found a link node parent
2478           if (bNodeFound) {
2479             // GetElementOrParentByTagName addref'd this, so we don't need to do it here
2480             *aReturn = parentLinkOfAnchor;
2481             NS_IF_ADDREF(*aReturn);
2482             return NS_OK;
2483           }
2484         } else if (anchorOffset >= 0) {
2485           // Check if link node is the only thing selected
2486           nsCOMPtr<nsIDOMNode> anchorChild;
2487           anchorChild = GetChildAt(anchorNode,anchorOffset);
2488           if (anchorChild && HTMLEditUtils::IsLink(anchorChild) &&
2489               anchorNode == focusNode && focusOffset == anchorOffset + 1) {
2490             selectedElement = do_QueryInterface(anchorChild);
2491             bNodeFound = true;
2492           }
2493         }
2494       }
2495     }
2496 
2497     if (!isCollapsed) {
2498       RefPtr<nsRange> currange = selection->GetRangeAt(0);
2499       if (currange) {
2500         nsCOMPtr<nsIContentIterator> iter =
2501           do_CreateInstance("@mozilla.org/content/post-content-iterator;1",
2502                             &rv);
2503         NS_ENSURE_SUCCESS(rv, rv);
2504 
2505         iter->Init(currange);
2506         // loop through the content iterator for each content node
2507         while (!iter->IsDone()) {
2508           // Query interface to cast nsIContent to nsIDOMNode
2509           //  then get tagType to compare to  aTagName
2510           // Clone node of each desired type and append it to the aDomFrag
2511           selectedElement = do_QueryInterface(iter->GetCurrentNode());
2512           if (selectedElement) {
2513             // If we already found a node, then we have another element,
2514             //  thus there's not just one element selected
2515             if (bNodeFound) {
2516               bNodeFound = false;
2517               break;
2518             }
2519 
2520             selectedElement->GetNodeName(domTagName);
2521             ToLowerCase(domTagName);
2522 
2523             if (anyTag) {
2524               // Get name of first selected element
2525               selectedElement->GetTagName(TagName);
2526               ToLowerCase(TagName);
2527               anyTag = false;
2528             }
2529 
2530             // The "A" tag is a pain,
2531             //  used for both link(href is set) and "Named Anchor"
2532             nsCOMPtr<nsIDOMNode> selectedNode = do_QueryInterface(selectedElement);
2533             if ((isLinkTag &&
2534                  HTMLEditUtils::IsLink(selectedNode)) ||
2535                 (isNamedAnchorTag &&
2536                  HTMLEditUtils::IsNamedAnchor(selectedNode))) {
2537               bNodeFound = true;
2538             } else if (TagName == domTagName) { // All other tag names are handled here
2539               bNodeFound = true;
2540             }
2541             if (!bNodeFound) {
2542               // Check if node we have is really part of the selection???
2543               break;
2544             }
2545           }
2546           iter->Next();
2547         }
2548       } else {
2549         // Should never get here?
2550         isCollapsed = true;
2551         NS_WARNING("isCollapsed was FALSE, but no elements found in selection\n");
2552       }
2553     }
2554   }
2555 
2556   if (!bNodeFound) {
2557     return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2558   }
2559 
2560   *aReturn = selectedElement;
2561   if (selectedElement) {
2562     // Getters must addref
2563     NS_ADDREF(*aReturn);
2564   }
2565   return rv;
2566 }
2567 
2568 already_AddRefed<Element>
CreateElementWithDefaults(const nsAString & aTagName)2569 HTMLEditor::CreateElementWithDefaults(const nsAString& aTagName)
2570 {
2571   MOZ_ASSERT(!aTagName.IsEmpty());
2572 
2573   nsAutoString tagName(aTagName);
2574   ToLowerCase(tagName);
2575   nsAutoString realTagName;
2576 
2577   if (IsLinkTag(tagName) || IsNamedAnchorTag(tagName)) {
2578     realTagName.Assign('a');
2579   } else {
2580     realTagName = tagName;
2581   }
2582   // We don't use editor's CreateElement because we don't want to go through
2583   // the transaction system
2584 
2585   // New call to use instead to get proper HTML element, bug 39919
2586   nsCOMPtr<nsIAtom> realTagAtom = NS_Atomize(realTagName);
2587   nsCOMPtr<Element> newElement = CreateHTMLContent(realTagAtom);
2588   if (!newElement) {
2589     return nullptr;
2590   }
2591 
2592   // Mark the new element dirty, so it will be formatted
2593   ErrorResult rv;
2594   newElement->SetAttribute(NS_LITERAL_STRING("_moz_dirty"), EmptyString(), rv);
2595 
2596   // Set default values for new elements
2597   if (tagName.EqualsLiteral("table")) {
2598     newElement->SetAttribute(NS_LITERAL_STRING("cellpadding"),
2599                              NS_LITERAL_STRING("2"), rv);
2600     if (NS_WARN_IF(rv.Failed())) {
2601       rv.SuppressException();
2602       return nullptr;
2603     }
2604     newElement->SetAttribute(NS_LITERAL_STRING("cellspacing"),
2605                              NS_LITERAL_STRING("2"), rv);
2606     if (NS_WARN_IF(rv.Failed())) {
2607       rv.SuppressException();
2608       return nullptr;
2609     }
2610     newElement->SetAttribute(NS_LITERAL_STRING("border"),
2611                              NS_LITERAL_STRING("1"), rv);
2612     if (NS_WARN_IF(rv.Failed())) {
2613       rv.SuppressException();
2614       return nullptr;
2615     }
2616   } else if (tagName.EqualsLiteral("td")) {
2617     nsresult rv =
2618       SetAttributeOrEquivalent(
2619         static_cast<nsIDOMElement*>(newElement->AsDOMNode()),
2620         NS_LITERAL_STRING("valign"), NS_LITERAL_STRING("top"), true);
2621     NS_ENSURE_SUCCESS(rv, nullptr);
2622   }
2623   // ADD OTHER TAGS HERE
2624 
2625   return newElement.forget();
2626 }
2627 
2628 NS_IMETHODIMP
CreateElementWithDefaults(const nsAString & aTagName,nsIDOMElement ** aReturn)2629 HTMLEditor::CreateElementWithDefaults(const nsAString& aTagName,
2630                                       nsIDOMElement** aReturn)
2631 {
2632   NS_ENSURE_TRUE(!aTagName.IsEmpty() && aReturn, NS_ERROR_NULL_POINTER);
2633   *aReturn = nullptr;
2634 
2635   nsCOMPtr<Element> newElement = CreateElementWithDefaults(aTagName);
2636   nsCOMPtr<nsIDOMElement> ret = do_QueryInterface(newElement);
2637   NS_ENSURE_TRUE(ret, NS_ERROR_FAILURE);
2638 
2639   ret.forget(aReturn);
2640   return NS_OK;
2641 }
2642 
2643 NS_IMETHODIMP
InsertLinkAroundSelection(nsIDOMElement * aAnchorElement)2644 HTMLEditor::InsertLinkAroundSelection(nsIDOMElement* aAnchorElement)
2645 {
2646   NS_ENSURE_TRUE(aAnchorElement, NS_ERROR_NULL_POINTER);
2647 
2648   // We must have a real selection
2649   RefPtr<Selection> selection = GetSelection();
2650   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2651 
2652   if (selection->Collapsed()) {
2653     NS_WARNING("InsertLinkAroundSelection called but there is no selection!!!");
2654     return NS_OK;
2655   }
2656 
2657   // Be sure we were given an anchor element
2658   nsCOMPtr<nsIDOMHTMLAnchorElement> anchor = do_QueryInterface(aAnchorElement);
2659   if (!anchor) {
2660     return NS_OK;
2661   }
2662 
2663   nsAutoString href;
2664   nsresult rv = anchor->GetHref(href);
2665   NS_ENSURE_SUCCESS(rv, rv);
2666   if (href.IsEmpty()) {
2667     return NS_OK;
2668   }
2669 
2670   AutoEditBatch beginBatching(this);
2671 
2672   // Set all attributes found on the supplied anchor element
2673   nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap;
2674   aAnchorElement->GetAttributes(getter_AddRefs(attrMap));
2675   NS_ENSURE_TRUE(attrMap, NS_ERROR_FAILURE);
2676 
2677   uint32_t count;
2678   attrMap->GetLength(&count);
2679   nsAutoString name, value;
2680 
2681   for (uint32_t i = 0; i < count; ++i) {
2682     nsCOMPtr<nsIDOMAttr> attribute;
2683     rv = attrMap->Item(i, getter_AddRefs(attribute));
2684     NS_ENSURE_SUCCESS(rv, rv);
2685 
2686     if (attribute) {
2687       // We must clear the string buffers
2688       //   because GetName, GetValue appends to previous string!
2689       name.Truncate();
2690       value.Truncate();
2691 
2692       rv = attribute->GetName(name);
2693       NS_ENSURE_SUCCESS(rv, rv);
2694 
2695       rv = attribute->GetValue(value);
2696       NS_ENSURE_SUCCESS(rv, rv);
2697 
2698       rv = SetInlineProperty(nsGkAtoms::a, name, value);
2699       NS_ENSURE_SUCCESS(rv, rv);
2700     }
2701   }
2702   return NS_OK;
2703 }
2704 
2705 nsresult
SetHTMLBackgroundColor(const nsAString & aColor)2706 HTMLEditor::SetHTMLBackgroundColor(const nsAString& aColor)
2707 {
2708   NS_PRECONDITION(mDocWeak, "Missing Editor DOM Document");
2709 
2710   // Find a selected or enclosing table element to set background on
2711   nsCOMPtr<nsIDOMElement> element;
2712   int32_t selectedCount;
2713   nsAutoString tagName;
2714   nsresult rv = GetSelectedOrParentTableElement(tagName, &selectedCount,
2715                                                 getter_AddRefs(element));
2716   NS_ENSURE_SUCCESS(rv, rv);
2717 
2718   bool setColor = !aColor.IsEmpty();
2719 
2720   NS_NAMED_LITERAL_STRING(bgcolor, "bgcolor");
2721   if (element) {
2722     if (selectedCount > 0) {
2723       // Traverse all selected cells
2724       nsCOMPtr<nsIDOMElement> cell;
2725       rv = GetFirstSelectedCell(nullptr, getter_AddRefs(cell));
2726       if (NS_SUCCEEDED(rv) && cell) {
2727         while (cell) {
2728           rv = setColor ? SetAttribute(cell, bgcolor, aColor) :
2729                           RemoveAttribute(cell, bgcolor);
2730           if (NS_FAILED(rv)) {
2731             return rv;
2732           }
2733 
2734           GetNextSelectedCell(nullptr, getter_AddRefs(cell));
2735         }
2736         return NS_OK;
2737       }
2738     }
2739     // If we failed to find a cell, fall through to use originally-found element
2740   } else {
2741     // No table element -- set the background color on the body tag
2742     element = do_QueryInterface(GetRoot());
2743     NS_ENSURE_TRUE(element, NS_ERROR_NULL_POINTER);
2744   }
2745   // Use the editor method that goes through the transaction system
2746   return setColor ? SetAttribute(element, bgcolor, aColor) :
2747                     RemoveAttribute(element, bgcolor);
2748 }
2749 
2750 NS_IMETHODIMP
SetBodyAttribute(const nsAString & aAttribute,const nsAString & aValue)2751 HTMLEditor::SetBodyAttribute(const nsAString& aAttribute,
2752                              const nsAString& aValue)
2753 {
2754   // TODO: Check selection for Cell, Row, Column or table and do color on appropriate level
2755 
2756   NS_ASSERTION(mDocWeak, "Missing Editor DOM Document");
2757 
2758   // Set the background color attribute on the body tag
2759   nsCOMPtr<nsIDOMElement> bodyElement = do_QueryInterface(GetRoot());
2760   NS_ENSURE_TRUE(bodyElement, NS_ERROR_NULL_POINTER);
2761 
2762   // Use the editor method that goes through the transaction system
2763   return SetAttribute(bodyElement, aAttribute, aValue);
2764 }
2765 
2766 NS_IMETHODIMP
GetLinkedObjects(nsIArray ** aNodeList)2767 HTMLEditor::GetLinkedObjects(nsIArray** aNodeList)
2768 {
2769   NS_ENSURE_TRUE(aNodeList, NS_ERROR_NULL_POINTER);
2770 
2771   nsresult rv;
2772   nsCOMPtr<nsIMutableArray> nodes = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
2773   if (NS_WARN_IF(NS_FAILED(rv))) {
2774     return rv;
2775   }
2776 
2777   nsCOMPtr<nsIContentIterator> iter =
2778     do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &rv);
2779   NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER);
2780   if (NS_SUCCEEDED(rv)) {
2781     nsCOMPtr<nsIDocument> doc = GetDocument();
2782     NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
2783 
2784     iter->Init(doc->GetRootElement());
2785 
2786     // loop through the content iterator for each content node
2787     while (!iter->IsDone()) {
2788       nsCOMPtr<nsIDOMNode> node (do_QueryInterface(iter->GetCurrentNode()));
2789       if (node) {
2790         // Let nsURIRefObject make the hard decisions:
2791         nsCOMPtr<nsIURIRefObject> refObject;
2792         rv = NS_NewHTMLURIRefObject(getter_AddRefs(refObject), node);
2793         if (NS_SUCCEEDED(rv)) {
2794           nodes->AppendElement(refObject, false);
2795         }
2796       }
2797       iter->Next();
2798     }
2799   }
2800 
2801   nodes.forget(aNodeList);
2802   return NS_OK;
2803 }
2804 
2805 
2806 NS_IMETHODIMP
AddStyleSheet(const nsAString & aURL)2807 HTMLEditor::AddStyleSheet(const nsAString& aURL)
2808 {
2809   // Enable existing sheet if already loaded.
2810   if (EnableExistingStyleSheet(aURL)) {
2811     return NS_OK;
2812   }
2813 
2814   // Lose the previously-loaded sheet so there's nothing to replace
2815   // This pattern is different from Override methods because
2816   //  we must wait to remove mLastStyleSheetURL and add new sheet
2817   //  at the same time (in StyleSheetLoaded callback) so they are undoable together
2818   mLastStyleSheetURL.Truncate();
2819   return ReplaceStyleSheet(aURL);
2820 }
2821 
2822 NS_IMETHODIMP
ReplaceStyleSheet(const nsAString & aURL)2823 HTMLEditor::ReplaceStyleSheet(const nsAString& aURL)
2824 {
2825   // Enable existing sheet if already loaded.
2826   if (EnableExistingStyleSheet(aURL)) {
2827     // Disable last sheet if not the same as new one
2828     if (!mLastStyleSheetURL.IsEmpty() && !mLastStyleSheetURL.Equals(aURL)) {
2829       return EnableStyleSheet(mLastStyleSheetURL, false);
2830     }
2831     return NS_OK;
2832   }
2833 
2834   // Make sure the pres shell doesn't disappear during the load.
2835   NS_ENSURE_TRUE(mDocWeak, NS_ERROR_NOT_INITIALIZED);
2836   nsCOMPtr<nsIPresShell> ps = GetPresShell();
2837   NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
2838 
2839   nsCOMPtr<nsIURI> uaURI;
2840   nsresult rv = NS_NewURI(getter_AddRefs(uaURI), aURL);
2841   NS_ENSURE_SUCCESS(rv, rv);
2842 
2843   return ps->GetDocument()->CSSLoader()->
2844     LoadSheet(uaURI, false, nullptr, EmptyCString(), this);
2845 }
2846 
2847 NS_IMETHODIMP
RemoveStyleSheet(const nsAString & aURL)2848 HTMLEditor::RemoveStyleSheet(const nsAString& aURL)
2849 {
2850   RefPtr<StyleSheet> sheet = GetStyleSheetForURL(aURL);
2851   NS_ENSURE_TRUE(sheet, NS_ERROR_UNEXPECTED);
2852 
2853   RefPtr<RemoveStyleSheetTransaction> transaction;
2854   nsresult rv =
2855     CreateTxnForRemoveStyleSheet(sheet, getter_AddRefs(transaction));
2856   if (!transaction) {
2857     rv = NS_ERROR_NULL_POINTER;
2858   }
2859   if (NS_SUCCEEDED(rv)) {
2860     rv = DoTransaction(transaction);
2861     if (NS_SUCCEEDED(rv)) {
2862       mLastStyleSheetURL.Truncate();        // forget it
2863     }
2864     // Remove it from our internal list
2865     rv = RemoveStyleSheetFromList(aURL);
2866   }
2867 
2868   return rv;
2869 }
2870 
2871 
2872 NS_IMETHODIMP
AddOverrideStyleSheet(const nsAString & aURL)2873 HTMLEditor::AddOverrideStyleSheet(const nsAString& aURL)
2874 {
2875   // Enable existing sheet if already loaded.
2876   if (EnableExistingStyleSheet(aURL)) {
2877     return NS_OK;
2878   }
2879 
2880   // Make sure the pres shell doesn't disappear during the load.
2881   nsCOMPtr<nsIPresShell> ps = GetPresShell();
2882   NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
2883 
2884   nsCOMPtr<nsIURI> uaURI;
2885   nsresult rv = NS_NewURI(getter_AddRefs(uaURI), aURL);
2886   NS_ENSURE_SUCCESS(rv, rv);
2887 
2888   // We MUST ONLY load synchronous local files (no @import)
2889   // XXXbz Except this will actually try to load remote files
2890   // synchronously, of course..
2891   RefPtr<StyleSheet> sheet;
2892   // Editor override style sheets may want to style Gecko anonymous boxes
2893   rv = ps->GetDocument()->CSSLoader()->
2894     LoadSheetSync(uaURI, mozilla::css::eAgentSheetFeatures, true,
2895                   &sheet);
2896 
2897   // Synchronous loads should ALWAYS return completed
2898   NS_ENSURE_TRUE(sheet, NS_ERROR_NULL_POINTER);
2899 
2900   // Add the override style sheet
2901   // (This checks if already exists)
2902   ps->AddOverrideStyleSheet(sheet);
2903 
2904   ps->RestyleForCSSRuleChanges();
2905 
2906   // Save as the last-loaded sheet
2907   mLastOverrideStyleSheetURL = aURL;
2908 
2909   //Add URL and style sheet to our lists
2910   return AddNewStyleSheetToList(aURL, sheet);
2911 }
2912 
2913 NS_IMETHODIMP
ReplaceOverrideStyleSheet(const nsAString & aURL)2914 HTMLEditor::ReplaceOverrideStyleSheet(const nsAString& aURL)
2915 {
2916   // Enable existing sheet if already loaded.
2917   if (EnableExistingStyleSheet(aURL)) {
2918     // Disable last sheet if not the same as new one
2919     if (!mLastOverrideStyleSheetURL.IsEmpty() &&
2920         !mLastOverrideStyleSheetURL.Equals(aURL)) {
2921       return EnableStyleSheet(mLastOverrideStyleSheetURL, false);
2922     }
2923     return NS_OK;
2924   }
2925   // Remove the previous sheet
2926   if (!mLastOverrideStyleSheetURL.IsEmpty()) {
2927     RemoveOverrideStyleSheet(mLastOverrideStyleSheetURL);
2928   }
2929   return AddOverrideStyleSheet(aURL);
2930 }
2931 
2932 // Do NOT use transaction system for override style sheets
2933 NS_IMETHODIMP
RemoveOverrideStyleSheet(const nsAString & aURL)2934 HTMLEditor::RemoveOverrideStyleSheet(const nsAString& aURL)
2935 {
2936   RefPtr<StyleSheet> sheet = GetStyleSheetForURL(aURL);
2937 
2938   // Make sure we remove the stylesheet from our internal list in all
2939   // cases.
2940   nsresult rv = RemoveStyleSheetFromList(aURL);
2941 
2942   NS_ENSURE_TRUE(sheet, NS_OK); /// Don't fail if sheet not found
2943 
2944   NS_ENSURE_TRUE(mDocWeak, NS_ERROR_NOT_INITIALIZED);
2945   nsCOMPtr<nsIPresShell> ps = GetPresShell();
2946   NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
2947 
2948   ps->RemoveOverrideStyleSheet(sheet);
2949   ps->RestyleForCSSRuleChanges();
2950 
2951   // Remove it from our internal list
2952   return rv;
2953 }
2954 
2955 NS_IMETHODIMP
EnableStyleSheet(const nsAString & aURL,bool aEnable)2956 HTMLEditor::EnableStyleSheet(const nsAString& aURL,
2957                              bool aEnable)
2958 {
2959   RefPtr<StyleSheet> sheet = GetStyleSheetForURL(aURL);
2960   NS_ENSURE_TRUE(sheet, NS_OK); // Don't fail if sheet not found
2961 
2962   // Ensure the style sheet is owned by our document.
2963   nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
2964   sheet->SetOwningDocument(doc);
2965 
2966   if (sheet->IsServo()) {
2967     // XXXheycam ServoStyleSheets don't support being enabled/disabled yet.
2968     NS_ERROR("stylo: ServoStyleSheets can't be disabled yet");
2969     return NS_ERROR_FAILURE;
2970   }
2971   return sheet->AsGecko()->SetDisabled(!aEnable);
2972 }
2973 
2974 bool
EnableExistingStyleSheet(const nsAString & aURL)2975 HTMLEditor::EnableExistingStyleSheet(const nsAString& aURL)
2976 {
2977   RefPtr<StyleSheet> sheet = GetStyleSheetForURL(aURL);
2978 
2979   // Enable sheet if already loaded.
2980   if (!sheet) {
2981     return false;
2982   }
2983 
2984   // Ensure the style sheet is owned by our document.
2985   nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
2986   sheet->SetOwningDocument(doc);
2987 
2988   if (sheet->IsServo()) {
2989     // XXXheycam ServoStyleSheets don't support being enabled/disabled yet.
2990     NS_ERROR("stylo: ServoStyleSheets can't be disabled yet");
2991     return true;
2992   }
2993   sheet->AsGecko()->SetDisabled(false);
2994   return true;
2995 }
2996 
2997 nsresult
AddNewStyleSheetToList(const nsAString & aURL,StyleSheet * aStyleSheet)2998 HTMLEditor::AddNewStyleSheetToList(const nsAString& aURL,
2999                                    StyleSheet* aStyleSheet)
3000 {
3001   uint32_t countSS = mStyleSheets.Length();
3002   uint32_t countU = mStyleSheetURLs.Length();
3003 
3004   if (countSS != countU) {
3005     return NS_ERROR_UNEXPECTED;
3006   }
3007 
3008   if (!mStyleSheetURLs.AppendElement(aURL)) {
3009     return NS_ERROR_UNEXPECTED;
3010   }
3011 
3012   return mStyleSheets.AppendElement(aStyleSheet) ? NS_OK : NS_ERROR_UNEXPECTED;
3013 }
3014 
3015 nsresult
RemoveStyleSheetFromList(const nsAString & aURL)3016 HTMLEditor::RemoveStyleSheetFromList(const nsAString& aURL)
3017 {
3018   // is it already in the list?
3019   size_t foundIndex;
3020   foundIndex = mStyleSheetURLs.IndexOf(aURL);
3021   if (foundIndex == mStyleSheetURLs.NoIndex) {
3022     return NS_ERROR_FAILURE;
3023   }
3024 
3025   // Attempt both removals; if one fails there's not much we can do.
3026   mStyleSheets.RemoveElementAt(foundIndex);
3027   mStyleSheetURLs.RemoveElementAt(foundIndex);
3028 
3029   return NS_OK;
3030 }
3031 
3032 StyleSheet*
GetStyleSheetForURL(const nsAString & aURL)3033 HTMLEditor::GetStyleSheetForURL(const nsAString& aURL)
3034 {
3035   // is it already in the list?
3036   size_t foundIndex;
3037   foundIndex = mStyleSheetURLs.IndexOf(aURL);
3038   if (foundIndex == mStyleSheetURLs.NoIndex) {
3039     return nullptr;
3040   }
3041 
3042   MOZ_ASSERT(mStyleSheets[foundIndex]);
3043   return mStyleSheets[foundIndex];
3044 }
3045 
3046 void
GetURLForStyleSheet(StyleSheet * aStyleSheet,nsAString & aURL)3047 HTMLEditor::GetURLForStyleSheet(StyleSheet* aStyleSheet,
3048                                 nsAString& aURL)
3049 {
3050   // is it already in the list?
3051   int32_t foundIndex = mStyleSheets.IndexOf(aStyleSheet);
3052 
3053   // Don't fail if we don't find it in our list
3054   if (foundIndex == -1) {
3055     return;
3056   }
3057 
3058   // Found it in the list!
3059   aURL = mStyleSheetURLs[foundIndex];
3060 }
3061 
3062 NS_IMETHODIMP
GetEmbeddedObjects(nsIArray ** aNodeList)3063 HTMLEditor::GetEmbeddedObjects(nsIArray** aNodeList)
3064 {
3065   NS_ENSURE_TRUE(aNodeList, NS_ERROR_NULL_POINTER);
3066 
3067   nsresult rv;
3068   nsCOMPtr<nsIMutableArray> nodes = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
3069   NS_ENSURE_SUCCESS(rv, rv);
3070 
3071   nsCOMPtr<nsIContentIterator> iter =
3072       do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &rv);
3073   NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER);
3074   NS_ENSURE_SUCCESS(rv, rv);
3075 
3076   nsCOMPtr<nsIDocument> doc = GetDocument();
3077   NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
3078 
3079   iter->Init(doc->GetRootElement());
3080 
3081   // Loop through the content iterator for each content node.
3082   while (!iter->IsDone()) {
3083     nsINode* node = iter->GetCurrentNode();
3084     if (node->IsElement()) {
3085       dom::Element* element = node->AsElement();
3086 
3087       // See if it's an image or an embed and also include all links.
3088       // Let mail decide which link to send or not
3089       if (element->IsAnyOfHTMLElements(nsGkAtoms::img, nsGkAtoms::embed,
3090                                        nsGkAtoms::a) ||
3091           (element->IsHTMLElement(nsGkAtoms::body) &&
3092            element->HasAttr(kNameSpaceID_None, nsGkAtoms::background))) {
3093         nsCOMPtr<nsIDOMNode> domNode = do_QueryInterface(node);
3094         nodes->AppendElement(domNode, false);
3095        }
3096      }
3097      iter->Next();
3098    }
3099 
3100   nodes.forget(aNodeList);
3101   return rv;
3102 }
3103 
3104 NS_IMETHODIMP
DeleteSelectionImpl(EDirection aAction,EStripWrappers aStripWrappers)3105 HTMLEditor::DeleteSelectionImpl(EDirection aAction,
3106                                 EStripWrappers aStripWrappers)
3107 {
3108   MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
3109 
3110   nsresult rv = EditorBase::DeleteSelectionImpl(aAction, aStripWrappers);
3111   NS_ENSURE_SUCCESS(rv, rv);
3112 
3113   // If we weren't asked to strip any wrappers, we're done.
3114   if (aStripWrappers == eNoStrip) {
3115     return NS_OK;
3116   }
3117 
3118   RefPtr<Selection> selection = GetSelection();
3119   // Just checking that the selection itself is collapsed doesn't seem to work
3120   // right in the multi-range case
3121   NS_ENSURE_STATE(selection);
3122   NS_ENSURE_STATE(selection->GetAnchorFocusRange());
3123   NS_ENSURE_STATE(selection->GetAnchorFocusRange()->Collapsed());
3124 
3125   NS_ENSURE_STATE(selection->GetAnchorNode()->IsContent());
3126   nsCOMPtr<nsIContent> content = selection->GetAnchorNode()->AsContent();
3127 
3128   // Don't strip wrappers if this is the only wrapper in the block.  Then we'll
3129   // add a <br> later, so it won't be an empty wrapper in the end.
3130   nsCOMPtr<nsIContent> blockParent = content;
3131   while (blockParent && !IsBlockNode(blockParent)) {
3132     blockParent = blockParent->GetParent();
3133   }
3134   if (!blockParent) {
3135     return NS_OK;
3136   }
3137   bool emptyBlockParent;
3138   rv = IsEmptyNode(blockParent, &emptyBlockParent);
3139   NS_ENSURE_SUCCESS(rv, rv);
3140   if (emptyBlockParent) {
3141     return NS_OK;
3142   }
3143 
3144   if (content && !IsBlockNode(content) && !content->Length() &&
3145       content->IsEditable() && content != content->GetEditingHost()) {
3146     while (content->GetParent() && !IsBlockNode(content->GetParent()) &&
3147            content->GetParent()->Length() == 1 &&
3148            content->GetParent()->IsEditable() &&
3149            content->GetParent() != content->GetEditingHost()) {
3150       content = content->GetParent();
3151     }
3152     rv = DeleteNode(content);
3153     NS_ENSURE_SUCCESS(rv, rv);
3154   }
3155 
3156   return NS_OK;
3157 }
3158 
3159 nsresult
DeleteNode(nsINode * aNode)3160 HTMLEditor::DeleteNode(nsINode* aNode)
3161 {
3162   nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aNode);
3163   return DeleteNode(node);
3164 }
3165 
3166 NS_IMETHODIMP
DeleteNode(nsIDOMNode * aNode)3167 HTMLEditor::DeleteNode(nsIDOMNode* aNode)
3168 {
3169   // do nothing if the node is read-only
3170   nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
3171   if (!IsModifiableNode(aNode) && !IsMozEditorBogusNode(content)) {
3172     return NS_ERROR_FAILURE;
3173   }
3174 
3175   return EditorBase::DeleteNode(aNode);
3176 }
3177 
3178 nsresult
DeleteText(nsGenericDOMDataNode & aCharData,uint32_t aOffset,uint32_t aLength)3179 HTMLEditor::DeleteText(nsGenericDOMDataNode& aCharData,
3180                        uint32_t aOffset,
3181                        uint32_t aLength)
3182 {
3183   // Do nothing if the node is read-only
3184   if (!IsModifiableNode(&aCharData)) {
3185     return NS_ERROR_FAILURE;
3186   }
3187 
3188   return EditorBase::DeleteText(aCharData, aOffset, aLength);
3189 }
3190 
3191 nsresult
InsertTextImpl(const nsAString & aStringToInsert,nsCOMPtr<nsINode> * aInOutNode,int32_t * aInOutOffset,nsIDocument * aDoc)3192 HTMLEditor::InsertTextImpl(const nsAString& aStringToInsert,
3193                            nsCOMPtr<nsINode>* aInOutNode,
3194                            int32_t* aInOutOffset,
3195                            nsIDocument* aDoc)
3196 {
3197   // Do nothing if the node is read-only
3198   if (!IsModifiableNode(*aInOutNode)) {
3199     return NS_ERROR_FAILURE;
3200   }
3201 
3202   return EditorBase::InsertTextImpl(aStringToInsert, aInOutNode, aInOutOffset,
3203                                     aDoc);
3204 }
3205 
3206 void
ContentAppended(nsIDocument * aDocument,nsIContent * aContainer,nsIContent * aFirstNewContent,int32_t aIndexInContainer)3207 HTMLEditor::ContentAppended(nsIDocument* aDocument,
3208                             nsIContent* aContainer,
3209                             nsIContent* aFirstNewContent,
3210                             int32_t aIndexInContainer)
3211 {
3212   DoContentInserted(aDocument, aContainer, aFirstNewContent, aIndexInContainer,
3213                     eAppended);
3214 }
3215 
3216 void
ContentInserted(nsIDocument * aDocument,nsIContent * aContainer,nsIContent * aChild,int32_t aIndexInContainer)3217 HTMLEditor::ContentInserted(nsIDocument* aDocument,
3218                             nsIContent* aContainer,
3219                             nsIContent* aChild,
3220                             int32_t aIndexInContainer)
3221 {
3222   DoContentInserted(aDocument, aContainer, aChild, aIndexInContainer,
3223                     eInserted);
3224 }
3225 
3226 bool
IsInObservedSubtree(nsIDocument * aDocument,nsIContent * aContainer,nsIContent * aChild)3227 HTMLEditor::IsInObservedSubtree(nsIDocument* aDocument,
3228                                 nsIContent* aContainer,
3229                                 nsIContent* aChild)
3230 {
3231   if (!aChild) {
3232     return false;
3233   }
3234 
3235   Element* root = GetRoot();
3236   // To be super safe here, check both ChromeOnlyAccess and GetBindingParent.
3237   // That catches (also unbound) native anonymous content, XBL and ShadowDOM.
3238   if (root &&
3239       (root->ChromeOnlyAccess() != aChild->ChromeOnlyAccess() ||
3240        root->GetBindingParent() != aChild->GetBindingParent())) {
3241     return false;
3242   }
3243 
3244   return !aChild->ChromeOnlyAccess() && !aChild->GetBindingParent();
3245 }
3246 
3247 void
DoContentInserted(nsIDocument * aDocument,nsIContent * aContainer,nsIContent * aChild,int32_t aIndexInContainer,InsertedOrAppended aInsertedOrAppended)3248 HTMLEditor::DoContentInserted(nsIDocument* aDocument,
3249                               nsIContent* aContainer,
3250                               nsIContent* aChild,
3251                               int32_t aIndexInContainer,
3252                               InsertedOrAppended aInsertedOrAppended)
3253 {
3254   if (!IsInObservedSubtree(aDocument, aContainer, aChild)) {
3255     return;
3256   }
3257 
3258   nsCOMPtr<nsIHTMLEditor> kungFuDeathGrip(this);
3259 
3260   if (ShouldReplaceRootElement()) {
3261     UpdateRootElement();
3262     nsContentUtils::AddScriptRunner(NewRunnableMethod(
3263       this, &HTMLEditor::NotifyRootChanged));
3264   }
3265   // We don't need to handle our own modifications
3266   else if (!mAction && (aContainer ? aContainer->IsEditable() : aDocument->IsEditable())) {
3267     if (IsMozEditorBogusNode(aChild)) {
3268       // Ignore insertion of the bogus node
3269       return;
3270     }
3271     // Protect the edit rules object from dying
3272     nsCOMPtr<nsIEditRules> rules(mRules);
3273     rules->DocumentModified();
3274 
3275     // Update spellcheck for only the newly-inserted node (bug 743819)
3276     if (mInlineSpellChecker) {
3277       RefPtr<nsRange> range = new nsRange(aChild);
3278       int32_t endIndex = aIndexInContainer + 1;
3279       if (aInsertedOrAppended == eAppended) {
3280         // Count all the appended nodes
3281         nsIContent* sibling = aChild->GetNextSibling();
3282         while (sibling) {
3283           endIndex++;
3284           sibling = sibling->GetNextSibling();
3285         }
3286       }
3287       nsresult rv = range->Set(aContainer, aIndexInContainer,
3288                                aContainer, endIndex);
3289       if (NS_SUCCEEDED(rv)) {
3290         mInlineSpellChecker->SpellCheckRange(range);
3291       }
3292     }
3293   }
3294 }
3295 
3296 void
ContentRemoved(nsIDocument * aDocument,nsIContent * aContainer,nsIContent * aChild,int32_t aIndexInContainer,nsIContent * aPreviousSibling)3297 HTMLEditor::ContentRemoved(nsIDocument* aDocument,
3298                            nsIContent* aContainer,
3299                            nsIContent* aChild,
3300                            int32_t aIndexInContainer,
3301                            nsIContent* aPreviousSibling)
3302 {
3303   if (!IsInObservedSubtree(aDocument, aContainer, aChild)) {
3304     return;
3305   }
3306 
3307   nsCOMPtr<nsIHTMLEditor> kungFuDeathGrip(this);
3308 
3309   if (SameCOMIdentity(aChild, mRootElement)) {
3310     mRootElement = nullptr;
3311     nsContentUtils::AddScriptRunner(NewRunnableMethod(
3312       this, &HTMLEditor::NotifyRootChanged));
3313   }
3314   // We don't need to handle our own modifications
3315   else if (!mAction && (aContainer ? aContainer->IsEditable() : aDocument->IsEditable())) {
3316     if (aChild && IsMozEditorBogusNode(aChild)) {
3317       // Ignore removal of the bogus node
3318       return;
3319     }
3320     // Protect the edit rules object from dying
3321     nsCOMPtr<nsIEditRules> rules(mRules);
3322     rules->DocumentModified();
3323   }
3324 }
3325 
NS_IMETHODIMP_(bool)3326 NS_IMETHODIMP_(bool)
3327 HTMLEditor::IsModifiableNode(nsIDOMNode* aNode)
3328 {
3329   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
3330   return IsModifiableNode(node);
3331 }
3332 
3333 bool
IsModifiableNode(nsINode * aNode)3334 HTMLEditor::IsModifiableNode(nsINode* aNode)
3335 {
3336   return !aNode || aNode->IsEditable();
3337 }
3338 
3339 NS_IMETHODIMP
GetIsSelectionEditable(bool * aIsSelectionEditable)3340 HTMLEditor::GetIsSelectionEditable(bool* aIsSelectionEditable)
3341 {
3342   MOZ_ASSERT(aIsSelectionEditable);
3343 
3344   RefPtr<Selection> selection = GetSelection();
3345   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
3346 
3347   // Per the editing spec as of June 2012: we have to have a selection whose
3348   // start and end nodes are editable, and which share an ancestor editing
3349   // host.  (Bug 766387.)
3350   *aIsSelectionEditable = selection->RangeCount() &&
3351                           selection->GetAnchorNode()->IsEditable() &&
3352                           selection->GetFocusNode()->IsEditable();
3353 
3354   if (*aIsSelectionEditable) {
3355     nsINode* commonAncestor =
3356       selection->GetAnchorFocusRange()->GetCommonAncestor();
3357     while (commonAncestor && !commonAncestor->IsEditable()) {
3358       commonAncestor = commonAncestor->GetParentNode();
3359     }
3360     if (!commonAncestor) {
3361       // No editable common ancestor
3362       *aIsSelectionEditable = false;
3363     }
3364   }
3365 
3366   return NS_OK;
3367 }
3368 
3369 static nsresult
SetSelectionAroundHeadChildren(Selection * aSelection,nsIWeakReference * aDocWeak)3370 SetSelectionAroundHeadChildren(Selection* aSelection,
3371                                nsIWeakReference* aDocWeak)
3372 {
3373   // Set selection around <head> node
3374   nsCOMPtr<nsIDocument> doc = do_QueryReferent(aDocWeak);
3375   NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
3376 
3377   dom::Element* headNode = doc->GetHeadElement();
3378   NS_ENSURE_STATE(headNode);
3379 
3380   // Collapse selection to before first child of the head,
3381   nsresult rv = aSelection->CollapseNative(headNode, 0);
3382   NS_ENSURE_SUCCESS(rv, rv);
3383 
3384   // Then extend it to just after.
3385   uint32_t childCount = headNode->GetChildCount();
3386   return aSelection->ExtendNative(headNode, childCount + 1);
3387 }
3388 
3389 NS_IMETHODIMP
GetHeadContentsAsHTML(nsAString & aOutputString)3390 HTMLEditor::GetHeadContentsAsHTML(nsAString& aOutputString)
3391 {
3392   RefPtr<Selection> selection = GetSelection();
3393   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
3394 
3395   // Save current selection
3396   AutoSelectionRestorer selectionRestorer(selection, this);
3397 
3398   nsresult rv = SetSelectionAroundHeadChildren(selection, mDocWeak);
3399   NS_ENSURE_SUCCESS(rv, rv);
3400 
3401   rv = OutputToString(NS_LITERAL_STRING("text/html"),
3402                       nsIDocumentEncoder::OutputSelectionOnly,
3403                       aOutputString);
3404   if (NS_FAILED(rv)) {
3405     return rv;
3406   }
3407 
3408   // Selection always includes <body></body>,
3409   //  so terminate there
3410   nsReadingIterator<char16_t> findIter,endFindIter;
3411   aOutputString.BeginReading(findIter);
3412   aOutputString.EndReading(endFindIter);
3413   //counting on our parser to always lower case!!!
3414   if (CaseInsensitiveFindInReadable(NS_LITERAL_STRING("<body"),
3415                                     findIter, endFindIter)) {
3416     nsReadingIterator<char16_t> beginIter;
3417     aOutputString.BeginReading(beginIter);
3418     int32_t offset = Distance(beginIter, findIter);//get the distance
3419 
3420     nsWritingIterator<char16_t> writeIter;
3421     aOutputString.BeginWriting(writeIter);
3422     // Ensure the string ends in a newline
3423     char16_t newline ('\n');
3424     findIter.advance(-1);
3425     if (!offset || (offset > 0 && (*findIter) != newline)) {
3426       writeIter.advance(offset);
3427       *writeIter = newline;
3428       aOutputString.Truncate(offset+1);
3429     }
3430   }
3431   return NS_OK;
3432 }
3433 
3434 NS_IMETHODIMP
DebugUnitTests(int32_t * outNumTests,int32_t * outNumTestsFailed)3435 HTMLEditor::DebugUnitTests(int32_t* outNumTests,
3436                            int32_t* outNumTestsFailed)
3437 {
3438 #ifdef DEBUG
3439   NS_ENSURE_TRUE(outNumTests && outNumTestsFailed, NS_ERROR_NULL_POINTER);
3440 
3441   TextEditorTest *tester = new TextEditorTest();
3442   NS_ENSURE_TRUE(tester, NS_ERROR_OUT_OF_MEMORY);
3443 
3444   tester->Run(this, outNumTests, outNumTestsFailed);
3445   delete tester;
3446   return NS_OK;
3447 #else
3448   return NS_ERROR_NOT_IMPLEMENTED;
3449 #endif
3450 }
3451 
3452 NS_IMETHODIMP
StyleSheetLoaded(StyleSheet * aSheet,bool aWasAlternate,nsresult aStatus)3453 HTMLEditor::StyleSheetLoaded(StyleSheet* aSheet,
3454                              bool aWasAlternate,
3455                              nsresult aStatus)
3456 {
3457   nsresult rv = NS_OK;
3458   AutoEditBatch batchIt(this);
3459 
3460   if (!mLastStyleSheetURL.IsEmpty())
3461     RemoveStyleSheet(mLastStyleSheetURL);
3462 
3463   RefPtr<AddStyleSheetTransaction> transaction;
3464   rv = CreateTxnForAddStyleSheet(aSheet, getter_AddRefs(transaction));
3465   if (!transaction) {
3466     rv = NS_ERROR_NULL_POINTER;
3467   }
3468   if (NS_SUCCEEDED(rv)) {
3469     rv = DoTransaction(transaction);
3470     if (NS_SUCCEEDED(rv)) {
3471       // Get the URI, then url spec from the sheet
3472       nsAutoCString spec;
3473       rv = aSheet->GetSheetURI()->GetSpec(spec);
3474 
3475       if (NS_SUCCEEDED(rv)) {
3476         // Save it so we can remove before applying the next one
3477         mLastStyleSheetURL.AssignWithConversion(spec.get());
3478 
3479         // Also save in our arrays of urls and sheets
3480         AddNewStyleSheetToList(mLastStyleSheetURL, aSheet);
3481       }
3482     }
3483   }
3484 
3485   return NS_OK;
3486 }
3487 
3488 /**
3489  * All editor operations which alter the doc should be prefaced
3490  * with a call to StartOperation, naming the action and direction.
3491  */
3492 NS_IMETHODIMP
StartOperation(EditAction opID,nsIEditor::EDirection aDirection)3493 HTMLEditor::StartOperation(EditAction opID,
3494                            nsIEditor::EDirection aDirection)
3495 {
3496   // Protect the edit rules object from dying
3497   nsCOMPtr<nsIEditRules> rules(mRules);
3498 
3499   EditorBase::StartOperation(opID, aDirection);  // will set mAction, mDirection
3500   if (rules) {
3501     return rules->BeforeEdit(mAction, mDirection);
3502   }
3503   return NS_OK;
3504 }
3505 
3506 /**
3507  * All editor operations which alter the doc should be followed
3508  * with a call to EndOperation.
3509  */
3510 NS_IMETHODIMP
EndOperation()3511 HTMLEditor::EndOperation()
3512 {
3513   // Protect the edit rules object from dying
3514   nsCOMPtr<nsIEditRules> rules(mRules);
3515 
3516   // post processing
3517   nsresult rv = rules ? rules->AfterEdit(mAction, mDirection) : NS_OK;
3518   EditorBase::EndOperation();  // will clear mAction, mDirection
3519   return rv;
3520 }
3521 
3522 bool
TagCanContainTag(nsIAtom & aParentTag,nsIAtom & aChildTag)3523 HTMLEditor::TagCanContainTag(nsIAtom& aParentTag,
3524                              nsIAtom& aChildTag)
3525 {
3526   nsIParserService* parserService = nsContentUtils::GetParserService();
3527 
3528   int32_t childTagEnum;
3529   // XXX Should this handle #cdata-section too?
3530   if (&aChildTag == nsGkAtoms::textTagName) {
3531     childTagEnum = eHTMLTag_text;
3532   } else {
3533     childTagEnum = parserService->HTMLAtomTagToId(&aChildTag);
3534   }
3535 
3536   int32_t parentTagEnum = parserService->HTMLAtomTagToId(&aParentTag);
3537   return HTMLEditUtils::CanContain(parentTagEnum, childTagEnum);
3538 }
3539 
3540 bool
IsContainer(nsINode * aNode)3541 HTMLEditor::IsContainer(nsINode* aNode)
3542 {
3543   MOZ_ASSERT(aNode);
3544 
3545   int32_t tagEnum;
3546   // XXX Should this handle #cdata-section too?
3547   if (aNode->IsNodeOfType(nsINode::eTEXT)) {
3548     tagEnum = eHTMLTag_text;
3549   } else {
3550     tagEnum =
3551       nsContentUtils::GetParserService()->HTMLStringTagToId(aNode->NodeName());
3552   }
3553 
3554   return HTMLEditUtils::IsContainer(tagEnum);
3555 }
3556 
3557 bool
IsContainer(nsIDOMNode * aNode)3558 HTMLEditor::IsContainer(nsIDOMNode* aNode)
3559 {
3560   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
3561   if (!node) {
3562     return false;
3563   }
3564   return IsContainer(node);
3565 }
3566 
3567 
3568 nsresult
SelectEntireDocument(Selection * aSelection)3569 HTMLEditor::SelectEntireDocument(Selection* aSelection)
3570 {
3571   if (!aSelection || !mRules) {
3572     return NS_ERROR_NULL_POINTER;
3573   }
3574 
3575   // Protect the edit rules object from dying
3576   nsCOMPtr<nsIEditRules> rules(mRules);
3577 
3578   // get editor root node
3579   nsCOMPtr<nsIDOMElement> rootElement = do_QueryInterface(GetRoot());
3580 
3581   // is doc empty?
3582   bool bDocIsEmpty;
3583   nsresult rv = rules->DocumentIsEmpty(&bDocIsEmpty);
3584   NS_ENSURE_SUCCESS(rv, rv);
3585 
3586   if (bDocIsEmpty) {
3587     // if its empty dont select entire doc - that would select the bogus node
3588     return aSelection->Collapse(rootElement, 0);
3589   }
3590 
3591   return EditorBase::SelectEntireDocument(aSelection);
3592 }
3593 
3594 NS_IMETHODIMP
SelectAll()3595 HTMLEditor::SelectAll()
3596 {
3597   ForceCompositionEnd();
3598 
3599   RefPtr<Selection> selection = GetSelection();
3600   NS_ENSURE_STATE(selection);
3601 
3602   nsCOMPtr<nsIDOMNode> anchorNode;
3603   nsresult rv = selection->GetAnchorNode(getter_AddRefs(anchorNode));
3604   NS_ENSURE_SUCCESS(rv, rv);
3605 
3606   nsCOMPtr<nsIContent> anchorContent = do_QueryInterface(anchorNode, &rv);
3607   NS_ENSURE_SUCCESS(rv, rv);
3608 
3609   nsIContent *rootContent;
3610   if (anchorContent->HasIndependentSelection()) {
3611     rv = selection->SetAncestorLimiter(nullptr);
3612     NS_ENSURE_SUCCESS(rv, rv);
3613     rootContent = mRootElement;
3614   } else {
3615     nsCOMPtr<nsIPresShell> ps = GetPresShell();
3616     rootContent = anchorContent->GetSelectionRootContent(ps);
3617   }
3618 
3619   NS_ENSURE_TRUE(rootContent, NS_ERROR_UNEXPECTED);
3620 
3621   nsCOMPtr<nsIDOMNode> rootElement = do_QueryInterface(rootContent, &rv);
3622   NS_ENSURE_SUCCESS(rv, rv);
3623 
3624   Maybe<mozilla::dom::Selection::AutoUserInitiated> userSelection;
3625   if (!rootContent->IsEditable()) {
3626     userSelection.emplace(selection);
3627   }
3628   return selection->SelectAllChildren(rootElement);
3629 }
3630 
3631 // this will NOT find aAttribute unless aAttribute has a non-null value
3632 // so singleton attributes like <Table border> will not be matched!
3633 bool
IsTextPropertySetByContent(nsINode * aNode,nsIAtom * aProperty,const nsAString * aAttribute,const nsAString * aValue,nsAString * outValue)3634 HTMLEditor::IsTextPropertySetByContent(nsINode* aNode,
3635                                        nsIAtom* aProperty,
3636                                        const nsAString* aAttribute,
3637                                        const nsAString* aValue,
3638                                        nsAString* outValue)
3639 {
3640   MOZ_ASSERT(aNode && aProperty);
3641   bool isSet;
3642   IsTextPropertySetByContent(aNode->AsDOMNode(), aProperty, aAttribute, aValue,
3643                              isSet, outValue);
3644   return isSet;
3645 }
3646 
3647 void
IsTextPropertySetByContent(nsIDOMNode * aNode,nsIAtom * aProperty,const nsAString * aAttribute,const nsAString * aValue,bool & aIsSet,nsAString * outValue)3648 HTMLEditor::IsTextPropertySetByContent(nsIDOMNode* aNode,
3649                                        nsIAtom* aProperty,
3650                                        const nsAString* aAttribute,
3651                                        const nsAString* aValue,
3652                                        bool& aIsSet,
3653                                        nsAString* outValue)
3654 {
3655   aIsSet = false;  // must be initialized to false for code below to work
3656   nsAutoString propName;
3657   aProperty->ToString(propName);
3658   nsCOMPtr<nsIDOMNode>node = aNode;
3659 
3660   while (node) {
3661     nsCOMPtr<nsIDOMElement>element;
3662     element = do_QueryInterface(node);
3663     if (element) {
3664       nsAutoString tag, value;
3665       element->GetTagName(tag);
3666       if (propName.Equals(tag, nsCaseInsensitiveStringComparator())) {
3667         bool found = false;
3668         if (aAttribute && !aAttribute->IsEmpty()) {
3669           element->GetAttribute(*aAttribute, value);
3670           if (outValue) {
3671             *outValue = value;
3672           }
3673           if (!value.IsEmpty()) {
3674             if (!aValue) {
3675               found = true;
3676             } else {
3677               nsString tString(*aValue);
3678               if (tString.Equals(value, nsCaseInsensitiveStringComparator())) {
3679                 found = true;
3680               } else {
3681                 // We found the prop with the attribute, but the value doesn't
3682                 // match.
3683                 break;
3684               }
3685             }
3686           }
3687         } else {
3688           found = true;
3689         }
3690         if (found) {
3691           aIsSet = true;
3692           break;
3693         }
3694       }
3695     }
3696     nsCOMPtr<nsIDOMNode>temp;
3697     if (NS_SUCCEEDED(node->GetParentNode(getter_AddRefs(temp))) && temp) {
3698       node = temp;
3699     } else {
3700       node = nullptr;
3701     }
3702   }
3703 }
3704 
3705 bool
SetCaretInTableCell(nsIDOMElement * aElement)3706 HTMLEditor::SetCaretInTableCell(nsIDOMElement* aElement)
3707 {
3708   nsCOMPtr<dom::Element> element = do_QueryInterface(aElement);
3709   if (!element || !element->IsHTMLElement() ||
3710       !HTMLEditUtils::IsTableElement(element) ||
3711       !IsDescendantOfEditorRoot(element)) {
3712     return false;
3713   }
3714 
3715   nsIContent* node = element;
3716   while (node->HasChildren()) {
3717     node = node->GetFirstChild();
3718   }
3719 
3720   // Set selection at beginning of the found node
3721   RefPtr<Selection> selection = GetSelection();
3722   NS_ENSURE_TRUE(selection, false);
3723 
3724   return NS_SUCCEEDED(selection->CollapseNative(node, 0));
3725 }
3726 
3727 /**
3728  * GetEnclosingTable() finds ancestor who is a table, if any.
3729  */
3730 Element*
GetEnclosingTable(nsINode * aNode)3731 HTMLEditor::GetEnclosingTable(nsINode* aNode)
3732 {
3733   MOZ_ASSERT(aNode);
3734 
3735   for (nsCOMPtr<Element> block = GetBlockNodeParent(aNode);
3736        block;
3737        block = GetBlockNodeParent(block)) {
3738     if (HTMLEditUtils::IsTable(block)) {
3739       return block;
3740     }
3741   }
3742   return nullptr;
3743 }
3744 
3745 nsIDOMNode*
GetEnclosingTable(nsIDOMNode * aNode)3746 HTMLEditor::GetEnclosingTable(nsIDOMNode* aNode)
3747 {
3748   NS_PRECONDITION(aNode, "null node passed to HTMLEditor::GetEnclosingTable");
3749   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
3750   NS_ENSURE_TRUE(node, nullptr);
3751   nsCOMPtr<Element> table = GetEnclosingTable(node);
3752   nsCOMPtr<nsIDOMNode> ret = do_QueryInterface(table);
3753   return ret;
3754 }
3755 
3756 
3757 /**
3758  * This method scans the selection for adjacent text nodes
3759  * and collapses them into a single text node.
3760  * "adjacent" means literally adjacent siblings of the same parent.
3761  * Uses EditorBase::JoinNodes so action is undoable.
3762  * Should be called within the context of a batch transaction.
3763  */
3764 nsresult
CollapseAdjacentTextNodes(nsRange * aInRange)3765 HTMLEditor::CollapseAdjacentTextNodes(nsRange* aInRange)
3766 {
3767   NS_ENSURE_TRUE(aInRange, NS_ERROR_NULL_POINTER);
3768   AutoTransactionsConserveSelection dontSpazMySelection(this);
3769   nsTArray<nsCOMPtr<nsIDOMNode> > textNodes;
3770   // we can't actually do anything during iteration, so store the text nodes in an array
3771   // don't bother ref counting them because we know we can hold them for the
3772   // lifetime of this method
3773 
3774 
3775   // build a list of editable text nodes
3776   nsresult rv = NS_ERROR_UNEXPECTED;
3777   nsCOMPtr<nsIContentIterator> iter =
3778     do_CreateInstance("@mozilla.org/content/subtree-content-iterator;1", &rv);
3779   NS_ENSURE_SUCCESS(rv, rv);
3780 
3781   iter->Init(aInRange);
3782 
3783   while (!iter->IsDone()) {
3784     nsINode* node = iter->GetCurrentNode();
3785     if (node->NodeType() == nsIDOMNode::TEXT_NODE &&
3786         IsEditable(static_cast<nsIContent*>(node))) {
3787       nsCOMPtr<nsIDOMNode> domNode = do_QueryInterface(node);
3788       textNodes.AppendElement(domNode);
3789     }
3790 
3791     iter->Next();
3792   }
3793 
3794   // now that I have a list of text nodes, collapse adjacent text nodes
3795   // NOTE: assumption that JoinNodes keeps the righthand node
3796   while (textNodes.Length() > 1) {
3797     // we assume a textNodes entry can't be nullptr
3798     nsIDOMNode *leftTextNode = textNodes[0];
3799     nsIDOMNode *rightTextNode = textNodes[1];
3800     NS_ASSERTION(leftTextNode && rightTextNode,"left or rightTextNode null in CollapseAdjacentTextNodes");
3801 
3802     // get the prev sibling of the right node, and see if its leftTextNode
3803     nsCOMPtr<nsIDOMNode> prevSibOfRightNode;
3804     rv = rightTextNode->GetPreviousSibling(getter_AddRefs(prevSibOfRightNode));
3805     NS_ENSURE_SUCCESS(rv, rv);
3806     if (prevSibOfRightNode && prevSibOfRightNode == leftTextNode) {
3807       nsCOMPtr<nsIDOMNode> parent;
3808       rv = rightTextNode->GetParentNode(getter_AddRefs(parent));
3809       NS_ENSURE_SUCCESS(rv, rv);
3810       NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
3811       rv = JoinNodes(leftTextNode, rightTextNode, parent);
3812       NS_ENSURE_SUCCESS(rv, rv);
3813     }
3814 
3815     textNodes.RemoveElementAt(0); // remove the leftmost text node from the list
3816   }
3817 
3818   return NS_OK;
3819 }
3820 
3821 nsresult
SetSelectionAtDocumentStart(Selection * aSelection)3822 HTMLEditor::SetSelectionAtDocumentStart(Selection* aSelection)
3823 {
3824   dom::Element* rootElement = GetRoot();
3825   NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER);
3826 
3827   return aSelection->CollapseNative(rootElement, 0);
3828 }
3829 
3830 /**
3831  * Remove aNode, reparenting any children into the parent of aNode.  In
3832  * addition, insert any br's needed to preserve identity of removed block.
3833  */
3834 nsresult
RemoveBlockContainer(nsIContent & aNode)3835 HTMLEditor::RemoveBlockContainer(nsIContent& aNode)
3836 {
3837   // Two possibilities: the container could be empty of editable content.  If
3838   // that is the case, we need to compare what is before and after aNode to
3839   // determine if we need a br.
3840   //
3841   // Or it could be not empty, in which case we have to compare previous
3842   // sibling and first child to determine if we need a leading br, and compare
3843   // following sibling and last child to determine if we need a trailing br.
3844 
3845   nsCOMPtr<nsIContent> child = GetFirstEditableChild(aNode);
3846 
3847   if (child) {
3848     // The case of aNode not being empty.  We need a br at start unless:
3849     // 1) previous sibling of aNode is a block, OR
3850     // 2) previous sibling of aNode is a br, OR
3851     // 3) first child of aNode is a block OR
3852     // 4) either is null
3853 
3854     nsCOMPtr<nsIContent> sibling = GetPriorHTMLSibling(&aNode);
3855     if (sibling && !IsBlockNode(sibling) &&
3856         !sibling->IsHTMLElement(nsGkAtoms::br) && !IsBlockNode(child)) {
3857       // Insert br node
3858       nsCOMPtr<Element> br = CreateBR(&aNode, 0);
3859       NS_ENSURE_STATE(br);
3860     }
3861 
3862     // We need a br at end unless:
3863     // 1) following sibling of aNode is a block, OR
3864     // 2) last child of aNode is a block, OR
3865     // 3) last child of aNode is a br OR
3866     // 4) either is null
3867 
3868     sibling = GetNextHTMLSibling(&aNode);
3869     if (sibling && !IsBlockNode(sibling)) {
3870       child = GetLastEditableChild(aNode);
3871       MOZ_ASSERT(child, "aNode has first editable child but not last?");
3872       if (!IsBlockNode(child) && !child->IsHTMLElement(nsGkAtoms::br)) {
3873         // Insert br node
3874         nsCOMPtr<Element> br = CreateBR(&aNode, aNode.Length());
3875         NS_ENSURE_STATE(br);
3876       }
3877     }
3878   } else {
3879     // The case of aNode being empty.  We need a br at start unless:
3880     // 1) previous sibling of aNode is a block, OR
3881     // 2) previous sibling of aNode is a br, OR
3882     // 3) following sibling of aNode is a block, OR
3883     // 4) following sibling of aNode is a br OR
3884     // 5) either is null
3885     nsCOMPtr<nsIContent> sibling = GetPriorHTMLSibling(&aNode);
3886     if (sibling && !IsBlockNode(sibling) &&
3887         !sibling->IsHTMLElement(nsGkAtoms::br)) {
3888       sibling = GetNextHTMLSibling(&aNode);
3889       if (sibling && !IsBlockNode(sibling) &&
3890           !sibling->IsHTMLElement(nsGkAtoms::br)) {
3891         // Insert br node
3892         nsCOMPtr<Element> br = CreateBR(&aNode, 0);
3893         NS_ENSURE_STATE(br);
3894       }
3895     }
3896   }
3897 
3898   // Now remove container
3899   nsresult rv = RemoveContainer(&aNode);
3900   NS_ENSURE_SUCCESS(rv, rv);
3901 
3902   return NS_OK;
3903 }
3904 
3905 /**
3906  * GetPriorHTMLSibling() returns the previous editable sibling, if there is
3907  * one within the parent.
3908  */
3909 nsIContent*
GetPriorHTMLSibling(nsINode * aNode)3910 HTMLEditor::GetPriorHTMLSibling(nsINode* aNode)
3911 {
3912   MOZ_ASSERT(aNode);
3913 
3914   nsIContent* node = aNode->GetPreviousSibling();
3915   while (node && !IsEditable(node)) {
3916     node = node->GetPreviousSibling();
3917   }
3918 
3919   return node;
3920 }
3921 
3922 nsresult
GetPriorHTMLSibling(nsIDOMNode * inNode,nsCOMPtr<nsIDOMNode> * outNode)3923 HTMLEditor::GetPriorHTMLSibling(nsIDOMNode* inNode,
3924                                 nsCOMPtr<nsIDOMNode>* outNode)
3925 {
3926   NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER);
3927   *outNode = nullptr;
3928 
3929   nsCOMPtr<nsINode> node = do_QueryInterface(inNode);
3930   NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
3931 
3932   *outNode = do_QueryInterface(GetPriorHTMLSibling(node));
3933   return NS_OK;
3934 }
3935 
3936 /**
3937  * GetPriorHTMLSibling() returns the previous editable sibling, if there is
3938  * one within the parent.  just like above routine but takes a parent/offset
3939  * instead of a node.
3940  */
3941 nsIContent*
GetPriorHTMLSibling(nsINode * aParent,int32_t aOffset)3942 HTMLEditor::GetPriorHTMLSibling(nsINode* aParent,
3943                                 int32_t aOffset)
3944 {
3945   MOZ_ASSERT(aParent);
3946 
3947   nsIContent* node = aParent->GetChildAt(aOffset - 1);
3948   if (!node || IsEditable(node)) {
3949     return node;
3950   }
3951 
3952   return GetPriorHTMLSibling(node);
3953 }
3954 
3955 nsresult
GetPriorHTMLSibling(nsIDOMNode * inParent,int32_t inOffset,nsCOMPtr<nsIDOMNode> * outNode)3956 HTMLEditor::GetPriorHTMLSibling(nsIDOMNode* inParent,
3957                                 int32_t inOffset,
3958                                 nsCOMPtr<nsIDOMNode>* outNode)
3959 {
3960   NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER);
3961   *outNode = nullptr;
3962 
3963   nsCOMPtr<nsINode> parent = do_QueryInterface(inParent);
3964   NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
3965 
3966   *outNode = do_QueryInterface(GetPriorHTMLSibling(parent, inOffset));
3967   return NS_OK;
3968 }
3969 
3970 /**
3971  * GetNextHTMLSibling() returns the next editable sibling, if there is
3972  * one within the parent.
3973  */
3974 nsIContent*
GetNextHTMLSibling(nsINode * aNode)3975 HTMLEditor::GetNextHTMLSibling(nsINode* aNode)
3976 {
3977   MOZ_ASSERT(aNode);
3978 
3979   nsIContent* node = aNode->GetNextSibling();
3980   while (node && !IsEditable(node)) {
3981     node = node->GetNextSibling();
3982   }
3983 
3984   return node;
3985 }
3986 
3987 nsresult
GetNextHTMLSibling(nsIDOMNode * inNode,nsCOMPtr<nsIDOMNode> * outNode)3988 HTMLEditor::GetNextHTMLSibling(nsIDOMNode* inNode,
3989                                nsCOMPtr<nsIDOMNode>* outNode)
3990 {
3991   NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER);
3992   *outNode = nullptr;
3993 
3994   nsCOMPtr<nsINode> node = do_QueryInterface(inNode);
3995   NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
3996 
3997   *outNode = do_QueryInterface(GetNextHTMLSibling(node));
3998   return NS_OK;
3999 }
4000 
4001 /**
4002  * GetNextHTMLSibling() returns the next editable sibling, if there is
4003  * one within the parent.  just like above routine but takes a parent/offset
4004  * instead of a node.
4005  */
4006 nsIContent*
GetNextHTMLSibling(nsINode * aParent,int32_t aOffset)4007 HTMLEditor::GetNextHTMLSibling(nsINode* aParent,
4008                                int32_t aOffset)
4009 {
4010   MOZ_ASSERT(aParent);
4011 
4012   nsIContent* node = aParent->GetChildAt(aOffset + 1);
4013   if (!node || IsEditable(node)) {
4014     return node;
4015   }
4016 
4017   return GetNextHTMLSibling(node);
4018 }
4019 
4020 nsresult
GetNextHTMLSibling(nsIDOMNode * inParent,int32_t inOffset,nsCOMPtr<nsIDOMNode> * outNode)4021 HTMLEditor::GetNextHTMLSibling(nsIDOMNode* inParent,
4022                                int32_t inOffset,
4023                                nsCOMPtr<nsIDOMNode>* outNode)
4024 {
4025   NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER);
4026   *outNode = nullptr;
4027 
4028   nsCOMPtr<nsINode> parent = do_QueryInterface(inParent);
4029   NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
4030 
4031   *outNode = do_QueryInterface(GetNextHTMLSibling(parent, inOffset));
4032   return NS_OK;
4033 }
4034 
4035 /**
4036  * GetPriorHTMLNode() returns the previous editable leaf node, if there is
4037  * one within the <body>.
4038  */
4039 nsIContent*
GetPriorHTMLNode(nsINode * aNode,bool aNoBlockCrossing)4040 HTMLEditor::GetPriorHTMLNode(nsINode* aNode,
4041                              bool aNoBlockCrossing)
4042 {
4043   MOZ_ASSERT(aNode);
4044 
4045   if (!GetActiveEditingHost()) {
4046     return nullptr;
4047   }
4048 
4049   return GetPriorNode(aNode, true, aNoBlockCrossing);
4050 }
4051 
4052 nsresult
GetPriorHTMLNode(nsIDOMNode * aNode,nsCOMPtr<nsIDOMNode> * aResultNode,bool aNoBlockCrossing)4053 HTMLEditor::GetPriorHTMLNode(nsIDOMNode* aNode,
4054                              nsCOMPtr<nsIDOMNode>* aResultNode,
4055                              bool aNoBlockCrossing)
4056 {
4057   NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER);
4058 
4059   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
4060   NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
4061 
4062   *aResultNode = do_QueryInterface(GetPriorHTMLNode(node, aNoBlockCrossing));
4063   return NS_OK;
4064 }
4065 
4066 /**
4067  * GetPriorHTMLNode() is same as above but takes {parent,offset} instead of
4068  * node.
4069  */
4070 nsIContent*
GetPriorHTMLNode(nsINode * aParent,int32_t aOffset,bool aNoBlockCrossing)4071 HTMLEditor::GetPriorHTMLNode(nsINode* aParent,
4072                              int32_t aOffset,
4073                              bool aNoBlockCrossing)
4074 {
4075   MOZ_ASSERT(aParent);
4076 
4077   if (!GetActiveEditingHost()) {
4078     return nullptr;
4079   }
4080 
4081   return GetPriorNode(aParent, aOffset, true, aNoBlockCrossing);
4082 }
4083 
4084 nsresult
GetPriorHTMLNode(nsIDOMNode * aNode,int32_t aOffset,nsCOMPtr<nsIDOMNode> * aResultNode,bool aNoBlockCrossing)4085 HTMLEditor::GetPriorHTMLNode(nsIDOMNode* aNode,
4086                              int32_t aOffset,
4087                              nsCOMPtr<nsIDOMNode>* aResultNode,
4088                              bool aNoBlockCrossing)
4089 {
4090   NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER);
4091 
4092   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
4093   NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
4094 
4095   *aResultNode = do_QueryInterface(GetPriorHTMLNode(node, aOffset,
4096                                                     aNoBlockCrossing));
4097   return NS_OK;
4098 }
4099 
4100 /**
4101  * GetNextHTMLNode() returns the next editable leaf node, if there is
4102  * one within the <body>.
4103  */
4104 nsIContent*
GetNextHTMLNode(nsINode * aNode,bool aNoBlockCrossing)4105 HTMLEditor::GetNextHTMLNode(nsINode* aNode,
4106                             bool aNoBlockCrossing)
4107 {
4108   MOZ_ASSERT(aNode);
4109 
4110   nsIContent* result = GetNextNode(aNode, true, aNoBlockCrossing);
4111 
4112   if (result && !IsDescendantOfEditorRoot(result)) {
4113     return nullptr;
4114   }
4115 
4116   return result;
4117 }
4118 
4119 nsresult
GetNextHTMLNode(nsIDOMNode * aNode,nsCOMPtr<nsIDOMNode> * aResultNode,bool aNoBlockCrossing)4120 HTMLEditor::GetNextHTMLNode(nsIDOMNode* aNode,
4121                             nsCOMPtr<nsIDOMNode>* aResultNode,
4122                             bool aNoBlockCrossing)
4123 {
4124   NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER);
4125 
4126   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
4127   NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
4128 
4129   *aResultNode = do_QueryInterface(GetNextHTMLNode(node, aNoBlockCrossing));
4130   return NS_OK;
4131 }
4132 
4133 /**
4134  * GetNextHTMLNode() is same as above but takes {parent,offset} instead of node.
4135  */
4136 nsIContent*
GetNextHTMLNode(nsINode * aParent,int32_t aOffset,bool aNoBlockCrossing)4137 HTMLEditor::GetNextHTMLNode(nsINode* aParent,
4138                             int32_t aOffset,
4139                             bool aNoBlockCrossing)
4140 {
4141   nsIContent* content = GetNextNode(aParent, aOffset, true, aNoBlockCrossing);
4142   if (content && !IsDescendantOfEditorRoot(content)) {
4143     return nullptr;
4144   }
4145   return content;
4146 }
4147 
4148 nsresult
GetNextHTMLNode(nsIDOMNode * aNode,int32_t aOffset,nsCOMPtr<nsIDOMNode> * aResultNode,bool aNoBlockCrossing)4149 HTMLEditor::GetNextHTMLNode(nsIDOMNode* aNode,
4150                             int32_t aOffset,
4151                             nsCOMPtr<nsIDOMNode>* aResultNode,
4152                             bool aNoBlockCrossing)
4153 {
4154   NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER);
4155 
4156   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
4157   NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
4158 
4159   *aResultNode = do_QueryInterface(GetNextHTMLNode(node, aOffset,
4160                                                    aNoBlockCrossing));
4161   return NS_OK;
4162 }
4163 
4164 nsresult
IsFirstEditableChild(nsIDOMNode * aNode,bool * aOutIsFirst)4165 HTMLEditor::IsFirstEditableChild(nsIDOMNode* aNode,
4166                                  bool* aOutIsFirst)
4167 {
4168   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
4169   NS_ENSURE_TRUE(aOutIsFirst && node, NS_ERROR_NULL_POINTER);
4170 
4171   // init out parms
4172   *aOutIsFirst = false;
4173 
4174   // find first editable child and compare it to aNode
4175   nsCOMPtr<nsINode> parent = node->GetParentNode();
4176   NS_ENSURE_TRUE(parent, NS_ERROR_FAILURE);
4177 
4178   *aOutIsFirst = (GetFirstEditableChild(*parent) == node);
4179   return NS_OK;
4180 }
4181 
4182 nsresult
IsLastEditableChild(nsIDOMNode * aNode,bool * aOutIsLast)4183 HTMLEditor::IsLastEditableChild(nsIDOMNode* aNode,
4184                                 bool* aOutIsLast)
4185 {
4186   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
4187   NS_ENSURE_TRUE(aOutIsLast && node, NS_ERROR_NULL_POINTER);
4188 
4189   // init out parms
4190   *aOutIsLast = false;
4191 
4192   // find last editable child and compare it to aNode
4193   nsCOMPtr<nsINode> parent = node->GetParentNode();
4194   NS_ENSURE_TRUE(parent, NS_ERROR_FAILURE);
4195 
4196   *aOutIsLast = (GetLastEditableChild(*parent) == node);
4197   return NS_OK;
4198 }
4199 
4200 nsIContent*
GetFirstEditableChild(nsINode & aNode)4201 HTMLEditor::GetFirstEditableChild(nsINode& aNode)
4202 {
4203   nsCOMPtr<nsIContent> child = aNode.GetFirstChild();
4204 
4205   while (child && !IsEditable(child)) {
4206     child = child->GetNextSibling();
4207   }
4208 
4209   return child;
4210 }
4211 
4212 nsIContent*
GetLastEditableChild(nsINode & aNode)4213 HTMLEditor::GetLastEditableChild(nsINode& aNode)
4214 {
4215   nsCOMPtr<nsIContent> child = aNode.GetLastChild();
4216 
4217   while (child && !IsEditable(child)) {
4218     child = child->GetPreviousSibling();
4219   }
4220 
4221   return child;
4222 }
4223 
4224 nsIContent*
GetFirstEditableLeaf(nsINode & aNode)4225 HTMLEditor::GetFirstEditableLeaf(nsINode& aNode)
4226 {
4227   nsCOMPtr<nsIContent> child = GetLeftmostChild(&aNode);
4228   while (child && (!IsEditable(child) || child->HasChildren())) {
4229     child = GetNextHTMLNode(child);
4230 
4231     // Only accept nodes that are descendants of aNode
4232     if (!aNode.Contains(child)) {
4233       return nullptr;
4234     }
4235   }
4236 
4237   return child;
4238 }
4239 
4240 nsIContent*
GetLastEditableLeaf(nsINode & aNode)4241 HTMLEditor::GetLastEditableLeaf(nsINode& aNode)
4242 {
4243   nsCOMPtr<nsIContent> child = GetRightmostChild(&aNode, false);
4244   while (child && (!IsEditable(child) || child->HasChildren())) {
4245     child = GetPriorHTMLNode(child);
4246 
4247     // Only accept nodes that are descendants of aNode
4248     if (!aNode.Contains(child)) {
4249       return nullptr;
4250     }
4251   }
4252 
4253   return child;
4254 }
4255 
4256 /**
4257  * IsVisTextNode() figures out if textnode aTextNode has any visible content.
4258  */
4259 nsresult
IsVisTextNode(nsIContent * aNode,bool * outIsEmptyNode,bool aSafeToAskFrames)4260 HTMLEditor::IsVisTextNode(nsIContent* aNode,
4261                           bool* outIsEmptyNode,
4262                           bool aSafeToAskFrames)
4263 {
4264   MOZ_ASSERT(aNode);
4265   MOZ_ASSERT(aNode->NodeType() == nsIDOMNode::TEXT_NODE);
4266   MOZ_ASSERT(outIsEmptyNode);
4267 
4268   *outIsEmptyNode = true;
4269 
4270   uint32_t length = aNode->TextLength();
4271   if (aSafeToAskFrames) {
4272     nsCOMPtr<nsISelectionController> selCon;
4273     nsresult rv = GetSelectionController(getter_AddRefs(selCon));
4274     NS_ENSURE_SUCCESS(rv, rv);
4275     NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
4276     bool isVisible = false;
4277     // ask the selection controller for information about whether any
4278     // of the data in the node is really rendered.  This is really
4279     // something that frames know about, but we aren't supposed to talk to frames.
4280     // So we put a call in the selection controller interface, since it's already
4281     // in bed with frames anyway.  (this is a fix for bug 22227, and a
4282     // partial fix for bug 46209)
4283     rv = selCon->CheckVisibilityContent(aNode, 0, length, &isVisible);
4284     NS_ENSURE_SUCCESS(rv, rv);
4285     if (isVisible) {
4286       *outIsEmptyNode = false;
4287     }
4288   } else if (length) {
4289     if (aNode->TextIsOnlyWhitespace()) {
4290       WSRunObject wsRunObj(this, aNode, 0);
4291       nsCOMPtr<nsINode> visNode;
4292       int32_t outVisOffset=0;
4293       WSType visType;
4294       wsRunObj.NextVisibleNode(aNode, 0, address_of(visNode),
4295                                &outVisOffset, &visType);
4296       if (visType == WSType::normalWS || visType == WSType::text) {
4297         *outIsEmptyNode = (aNode != visNode);
4298       }
4299     } else {
4300       *outIsEmptyNode = false;
4301     }
4302   }
4303   return NS_OK;
4304 }
4305 
4306 /**
4307  * IsEmptyNode() figures out if aNode is an empty node.  A block can have
4308  * children and still be considered empty, if the children are empty or
4309  * non-editable.
4310  */
4311 nsresult
IsEmptyNode(nsIDOMNode * aNode,bool * outIsEmptyNode,bool aSingleBRDoesntCount,bool aListOrCellNotEmpty,bool aSafeToAskFrames)4312 HTMLEditor::IsEmptyNode(nsIDOMNode*aNode,
4313                         bool* outIsEmptyNode,
4314                         bool aSingleBRDoesntCount,
4315                         bool aListOrCellNotEmpty,
4316                         bool aSafeToAskFrames)
4317 {
4318   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
4319   return IsEmptyNode(node, outIsEmptyNode, aSingleBRDoesntCount,
4320                      aListOrCellNotEmpty, aSafeToAskFrames);
4321 }
4322 
4323 nsresult
IsEmptyNode(nsINode * aNode,bool * outIsEmptyNode,bool aSingleBRDoesntCount,bool aListOrCellNotEmpty,bool aSafeToAskFrames)4324 HTMLEditor::IsEmptyNode(nsINode* aNode,
4325                         bool* outIsEmptyNode,
4326                         bool aSingleBRDoesntCount,
4327                         bool aListOrCellNotEmpty,
4328                         bool aSafeToAskFrames)
4329 {
4330   NS_ENSURE_TRUE(aNode && outIsEmptyNode, NS_ERROR_NULL_POINTER);
4331   *outIsEmptyNode = true;
4332   bool seenBR = false;
4333   return IsEmptyNodeImpl(aNode, outIsEmptyNode, aSingleBRDoesntCount,
4334                          aListOrCellNotEmpty, aSafeToAskFrames, &seenBR);
4335 }
4336 
4337 /**
4338  * IsEmptyNodeImpl() is workhorse for IsEmptyNode().
4339  */
4340 nsresult
IsEmptyNodeImpl(nsINode * aNode,bool * outIsEmptyNode,bool aSingleBRDoesntCount,bool aListOrCellNotEmpty,bool aSafeToAskFrames,bool * aSeenBR)4341 HTMLEditor::IsEmptyNodeImpl(nsINode* aNode,
4342                             bool* outIsEmptyNode,
4343                             bool aSingleBRDoesntCount,
4344                             bool aListOrCellNotEmpty,
4345                             bool aSafeToAskFrames,
4346                             bool* aSeenBR)
4347 {
4348   NS_ENSURE_TRUE(aNode && outIsEmptyNode && aSeenBR, NS_ERROR_NULL_POINTER);
4349 
4350   if (aNode->NodeType() == nsIDOMNode::TEXT_NODE) {
4351     return IsVisTextNode(static_cast<nsIContent*>(aNode), outIsEmptyNode, aSafeToAskFrames);
4352   }
4353 
4354   // if it's not a text node (handled above) and it's not a container,
4355   // then we don't call it empty (it's an <hr>, or <br>, etc.).
4356   // Also, if it's an anchor then don't treat it as empty - even though
4357   // anchors are containers, named anchors are "empty" but we don't
4358   // want to treat them as such.  Also, don't call ListItems or table
4359   // cells empty if caller desires.  Form Widgets not empty.
4360   if (!IsContainer(aNode->AsDOMNode()) ||
4361       (HTMLEditUtils::IsNamedAnchor(aNode) ||
4362        HTMLEditUtils::IsFormWidget(aNode) ||
4363        (aListOrCellNotEmpty &&
4364         (HTMLEditUtils::IsListItem(aNode) ||
4365          HTMLEditUtils::IsTableCell(aNode))))) {
4366     *outIsEmptyNode = false;
4367     return NS_OK;
4368   }
4369 
4370   // need this for later
4371   bool isListItemOrCell = HTMLEditUtils::IsListItem(aNode) ||
4372                           HTMLEditUtils::IsTableCell(aNode);
4373 
4374   // loop over children of node. if no children, or all children are either
4375   // empty text nodes or non-editable, then node qualifies as empty
4376   for (nsCOMPtr<nsIContent> child = aNode->GetFirstChild();
4377        child;
4378        child = child->GetNextSibling()) {
4379     // Is the child editable and non-empty?  if so, return false
4380     if (EditorBase::IsEditable(child)) {
4381       if (child->NodeType() == nsIDOMNode::TEXT_NODE) {
4382         nsresult rv = IsVisTextNode(child, outIsEmptyNode, aSafeToAskFrames);
4383         NS_ENSURE_SUCCESS(rv, rv);
4384         // break out if we find we aren't emtpy
4385         if (!*outIsEmptyNode) {
4386           return NS_OK;
4387         }
4388       } else {
4389         // An editable, non-text node. We need to check its content.
4390         // Is it the node we are iterating over?
4391         if (child == aNode) {
4392           break;
4393         }
4394 
4395         if (aSingleBRDoesntCount && !*aSeenBR && child->IsHTMLElement(nsGkAtoms::br)) {
4396           // the first br in a block doesn't count if the caller so indicated
4397           *aSeenBR = true;
4398         } else {
4399           // is it an empty node of some sort?
4400           // note: list items or table cells are not considered empty
4401           // if they contain other lists or tables
4402           if (child->IsElement()) {
4403             if (isListItemOrCell) {
4404               if (HTMLEditUtils::IsList(child) ||
4405                   child->IsHTMLElement(nsGkAtoms::table)) {
4406                 // break out if we find we aren't empty
4407                 *outIsEmptyNode = false;
4408                 return NS_OK;
4409               }
4410             } else if (HTMLEditUtils::IsFormWidget(child)) {
4411               // is it a form widget?
4412               // break out if we find we aren't empty
4413               *outIsEmptyNode = false;
4414               return NS_OK;
4415             }
4416           }
4417 
4418           bool isEmptyNode = true;
4419           nsresult rv = IsEmptyNodeImpl(child, &isEmptyNode,
4420                                         aSingleBRDoesntCount,
4421                                         aListOrCellNotEmpty, aSafeToAskFrames,
4422                                         aSeenBR);
4423           NS_ENSURE_SUCCESS(rv, rv);
4424           if (!isEmptyNode) {
4425             // otherwise it ain't empty
4426             *outIsEmptyNode = false;
4427             return NS_OK;
4428           }
4429         }
4430       }
4431     }
4432   }
4433 
4434   return NS_OK;
4435 }
4436 
4437 // add to aElement the CSS inline styles corresponding to the HTML attribute
4438 // aAttribute with its value aValue
4439 nsresult
SetAttributeOrEquivalent(nsIDOMElement * aElement,const nsAString & aAttribute,const nsAString & aValue,bool aSuppressTransaction)4440 HTMLEditor::SetAttributeOrEquivalent(nsIDOMElement* aElement,
4441                                      const nsAString& aAttribute,
4442                                      const nsAString& aValue,
4443                                      bool aSuppressTransaction)
4444 {
4445   nsAutoScriptBlocker scriptBlocker;
4446 
4447   if (IsCSSEnabled() && mCSSEditUtils) {
4448     int32_t count;
4449     nsresult rv =
4450       mCSSEditUtils->SetCSSEquivalentToHTMLStyle(aElement, nullptr,
4451                                                  &aAttribute, &aValue,
4452                                                  &count,
4453                                                  aSuppressTransaction);
4454     NS_ENSURE_SUCCESS(rv, rv);
4455     if (count) {
4456       // we found an equivalence ; let's remove the HTML attribute itself if it is set
4457       nsAutoString existingValue;
4458       bool wasSet = false;
4459       rv = GetAttributeValue(aElement, aAttribute, existingValue, &wasSet);
4460       NS_ENSURE_SUCCESS(rv, rv);
4461       if (!wasSet) {
4462         return NS_OK;
4463       }
4464       return aSuppressTransaction ? aElement->RemoveAttribute(aAttribute) :
4465                                     RemoveAttribute(aElement, aAttribute);
4466     }
4467 
4468     // count is an integer that represents the number of CSS declarations applied to the
4469     // element. If it is zero, we found no equivalence in this implementation for the
4470     // attribute
4471     if (aAttribute.EqualsLiteral("style")) {
4472       // if it is the style attribute, just add the new value to the existing style
4473       // attribute's value
4474       nsAutoString existingValue;
4475       bool wasSet = false;
4476       nsresult rv = GetAttributeValue(aElement, NS_LITERAL_STRING("style"),
4477                                       existingValue, &wasSet);
4478       NS_ENSURE_SUCCESS(rv, rv);
4479       existingValue.Append(' ');
4480       existingValue.Append(aValue);
4481       return aSuppressTransaction ?
4482         aElement->SetAttribute(aAttribute, existingValue) :
4483         SetAttribute(aElement, aAttribute, existingValue);
4484     }
4485 
4486     // we have no CSS equivalence for this attribute and it is not the style
4487     // attribute; let's set it the good'n'old HTML way
4488     return aSuppressTransaction ? aElement->SetAttribute(aAttribute, aValue) :
4489                                   SetAttribute(aElement, aAttribute, aValue);
4490   }
4491 
4492   // we are not in an HTML+CSS editor; let's set the attribute the HTML way
4493   return aSuppressTransaction ? aElement->SetAttribute(aAttribute, aValue) :
4494                                 SetAttribute(aElement, aAttribute, aValue);
4495 }
4496 
4497 nsresult
RemoveAttributeOrEquivalent(nsIDOMElement * aElement,const nsAString & aAttribute,bool aSuppressTransaction)4498 HTMLEditor::RemoveAttributeOrEquivalent(nsIDOMElement* aElement,
4499                                         const nsAString& aAttribute,
4500                                         bool aSuppressTransaction)
4501 {
4502   nsCOMPtr<dom::Element> element = do_QueryInterface(aElement);
4503   NS_ENSURE_TRUE(element, NS_OK);
4504 
4505   nsCOMPtr<nsIAtom> attribute = NS_Atomize(aAttribute);
4506   MOZ_ASSERT(attribute);
4507 
4508   if (IsCSSEnabled() && mCSSEditUtils) {
4509     nsresult rv =
4510       mCSSEditUtils->RemoveCSSEquivalentToHTMLStyle(
4511         element, nullptr, &aAttribute, nullptr, aSuppressTransaction);
4512     NS_ENSURE_SUCCESS(rv, rv);
4513   }
4514 
4515   if (!element->HasAttr(kNameSpaceID_None, attribute)) {
4516     return NS_OK;
4517   }
4518 
4519   return aSuppressTransaction ?
4520     element->UnsetAttr(kNameSpaceID_None, attribute, /* aNotify = */ true) :
4521     RemoveAttribute(aElement, aAttribute);
4522 }
4523 
4524 nsresult
SetIsCSSEnabled(bool aIsCSSPrefChecked)4525 HTMLEditor::SetIsCSSEnabled(bool aIsCSSPrefChecked)
4526 {
4527   if (!mCSSEditUtils) {
4528     return NS_ERROR_NOT_INITIALIZED;
4529   }
4530 
4531   mCSSEditUtils->SetCSSEnabled(aIsCSSPrefChecked);
4532 
4533   // Disable the eEditorNoCSSMask flag if we're enabling StyleWithCSS.
4534   uint32_t flags = mFlags;
4535   if (aIsCSSPrefChecked) {
4536     // Turn off NoCSS as we're enabling CSS
4537     flags &= ~eEditorNoCSSMask;
4538   } else {
4539     // Turn on NoCSS, as we're disabling CSS.
4540     flags |= eEditorNoCSSMask;
4541   }
4542 
4543   return SetFlags(flags);
4544 }
4545 
4546 // Set the block background color
4547 nsresult
SetCSSBackgroundColor(const nsAString & aColor)4548 HTMLEditor::SetCSSBackgroundColor(const nsAString& aColor)
4549 {
4550   NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
4551   ForceCompositionEnd();
4552 
4553   // Protect the edit rules object from dying
4554   nsCOMPtr<nsIEditRules> rules(mRules);
4555 
4556   RefPtr<Selection> selection = GetSelection();
4557   NS_ENSURE_STATE(selection);
4558 
4559   bool isCollapsed = selection->Collapsed();
4560 
4561   AutoEditBatch batchIt(this);
4562   AutoRules beginRulesSniffing(this, EditAction::insertElement,
4563                                nsIEditor::eNext);
4564   AutoSelectionRestorer selectionRestorer(selection, this);
4565   AutoTransactionsConserveSelection dontSpazMySelection(this);
4566 
4567   bool cancel, handled;
4568   TextRulesInfo ruleInfo(EditAction::setTextProperty);
4569   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
4570   NS_ENSURE_SUCCESS(rv, rv);
4571   if (!cancel && !handled) {
4572     // Loop through the ranges in the selection
4573     NS_NAMED_LITERAL_STRING(bgcolor, "bgcolor");
4574     for (uint32_t i = 0; i < selection->RangeCount(); i++) {
4575       RefPtr<nsRange> range = selection->GetRangeAt(i);
4576       NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
4577 
4578       nsCOMPtr<Element> cachedBlockParent;
4579 
4580       // Check for easy case: both range endpoints in same text node
4581       nsCOMPtr<nsINode> startNode = range->GetStartParent();
4582       int32_t startOffset = range->StartOffset();
4583       nsCOMPtr<nsINode> endNode = range->GetEndParent();
4584       int32_t endOffset = range->EndOffset();
4585       if (startNode == endNode && IsTextNode(startNode)) {
4586         // Let's find the block container of the text node
4587         nsCOMPtr<Element> blockParent = GetBlockNodeParent(startNode);
4588         // And apply the background color to that block container
4589         if (blockParent && cachedBlockParent != blockParent) {
4590           cachedBlockParent = blockParent;
4591           mCSSEditUtils->SetCSSEquivalentToHTMLStyle(blockParent, nullptr,
4592                                                      &bgcolor, &aColor, false);
4593         }
4594       } else if (startNode == endNode &&
4595                  startNode->IsHTMLElement(nsGkAtoms::body) && isCollapsed) {
4596         // No block in the document, let's apply the background to the body
4597         mCSSEditUtils->SetCSSEquivalentToHTMLStyle(startNode->AsElement(),
4598                                                    nullptr, &bgcolor, &aColor,
4599                                                    false);
4600       } else if (startNode == endNode && (endOffset - startOffset == 1 ||
4601                                           (!startOffset && !endOffset))) {
4602         // A unique node is selected, let's also apply the background color to
4603         // the containing block, possibly the node itself
4604         nsCOMPtr<nsIContent> selectedNode = startNode->GetChildAt(startOffset);
4605         nsCOMPtr<Element> blockParent = GetBlock(*selectedNode);
4606         if (blockParent && cachedBlockParent != blockParent) {
4607           cachedBlockParent = blockParent;
4608           mCSSEditUtils->SetCSSEquivalentToHTMLStyle(blockParent, nullptr,
4609                                                      &bgcolor, &aColor, false);
4610         }
4611       } else {
4612         // Not the easy case.  Range not contained in single text node.  There
4613         // are up to three phases here.  There are all the nodes reported by
4614         // the subtree iterator to be processed.  And there are potentially a
4615         // starting textnode and an ending textnode which are only partially
4616         // contained by the range.
4617 
4618         // Let's handle the nodes reported by the iterator.  These nodes are
4619         // entirely contained in the selection range.  We build up a list of
4620         // them (since doing operations on the document during iteration would
4621         // perturb the iterator).
4622 
4623         OwningNonNull<nsIContentIterator> iter =
4624           NS_NewContentSubtreeIterator();
4625 
4626         nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
4627         nsCOMPtr<nsINode> node;
4628 
4629         // Iterate range and build up array
4630         rv = iter->Init(range);
4631         // Init returns an error if no nodes in range.  This can easily happen
4632         // with the subtree iterator if the selection doesn't contain any
4633         // *whole* nodes.
4634         if (NS_SUCCEEDED(rv)) {
4635           for (; !iter->IsDone(); iter->Next()) {
4636             node = do_QueryInterface(iter->GetCurrentNode());
4637             NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
4638 
4639             if (IsEditable(node)) {
4640               arrayOfNodes.AppendElement(*node);
4641             }
4642           }
4643         }
4644         // First check the start parent of the range to see if it needs to be
4645         // separately handled (it does if it's a text node, due to how the
4646         // subtree iterator works - it will not have reported it).
4647         if (IsTextNode(startNode) && IsEditable(startNode)) {
4648           nsCOMPtr<Element> blockParent = GetBlockNodeParent(startNode);
4649           if (blockParent && cachedBlockParent != blockParent) {
4650             cachedBlockParent = blockParent;
4651             mCSSEditUtils->SetCSSEquivalentToHTMLStyle(blockParent, nullptr,
4652                                                        &bgcolor, &aColor,
4653                                                        false);
4654           }
4655         }
4656 
4657         // Then loop through the list, set the property on each node
4658         for (auto& node : arrayOfNodes) {
4659           nsCOMPtr<Element> blockParent = GetBlock(node);
4660           if (blockParent && cachedBlockParent != blockParent) {
4661             cachedBlockParent = blockParent;
4662             mCSSEditUtils->SetCSSEquivalentToHTMLStyle(blockParent, nullptr,
4663                                                        &bgcolor, &aColor,
4664                                                        false);
4665           }
4666         }
4667         arrayOfNodes.Clear();
4668 
4669         // Last, check the end parent of the range to see if it needs to be
4670         // separately handled (it does if it's a text node, due to how the
4671         // subtree iterator works - it will not have reported it).
4672         if (IsTextNode(endNode) && IsEditable(endNode)) {
4673           nsCOMPtr<Element> blockParent = GetBlockNodeParent(endNode);
4674           if (blockParent && cachedBlockParent != blockParent) {
4675             cachedBlockParent = blockParent;
4676             mCSSEditUtils->SetCSSEquivalentToHTMLStyle(blockParent, nullptr,
4677                                                        &bgcolor, &aColor,
4678                                                        false);
4679           }
4680         }
4681       }
4682     }
4683   }
4684   if (!cancel) {
4685     // Post-process
4686     rv = rules->DidDoAction(selection, &ruleInfo, rv);
4687     NS_ENSURE_SUCCESS(rv, rv);
4688   }
4689   return NS_OK;
4690 }
4691 
4692 NS_IMETHODIMP
SetBackgroundColor(const nsAString & aColor)4693 HTMLEditor::SetBackgroundColor(const nsAString& aColor)
4694 {
4695   if (IsCSSEnabled()) {
4696     // if we are in CSS mode, we have to apply the background color to the
4697     // containing block (or the body if we have no block-level element in
4698     // the document)
4699     return SetCSSBackgroundColor(aColor);
4700   }
4701 
4702   // but in HTML mode, we can only set the document's background color
4703   return SetHTMLBackgroundColor(aColor);
4704 }
4705 
4706 /**
4707  * NodesSameType() does these nodes have the same tag?
4708  */
4709 bool
AreNodesSameType(nsIContent * aNode1,nsIContent * aNode2)4710 HTMLEditor::AreNodesSameType(nsIContent* aNode1,
4711                              nsIContent* aNode2)
4712 {
4713   MOZ_ASSERT(aNode1);
4714   MOZ_ASSERT(aNode2);
4715 
4716   if (aNode1->NodeInfo()->NameAtom() != aNode2->NodeInfo()->NameAtom()) {
4717     return false;
4718   }
4719 
4720   if (!IsCSSEnabled() || !aNode1->IsHTMLElement(nsGkAtoms::span)) {
4721     return true;
4722   }
4723 
4724   // If CSS is enabled, we are stricter about span nodes.
4725   return mCSSEditUtils->ElementsSameStyle(aNode1->AsDOMNode(),
4726                                           aNode2->AsDOMNode());
4727 }
4728 
4729 nsresult
CopyLastEditableChildStyles(nsIDOMNode * aPreviousBlock,nsIDOMNode * aNewBlock,Element ** aOutBrNode)4730 HTMLEditor::CopyLastEditableChildStyles(nsIDOMNode* aPreviousBlock,
4731                                         nsIDOMNode* aNewBlock,
4732                                         Element** aOutBrNode)
4733 {
4734   nsCOMPtr<nsINode> newBlock = do_QueryInterface(aNewBlock);
4735   NS_ENSURE_STATE(newBlock || !aNewBlock);
4736   *aOutBrNode = nullptr;
4737   nsCOMPtr<nsIDOMNode> child, tmp;
4738   // first, clear out aNewBlock.  Contract is that we want only the styles from previousBlock.
4739   nsresult rv = aNewBlock->GetFirstChild(getter_AddRefs(child));
4740   while (NS_SUCCEEDED(rv) && child) {
4741     rv = DeleteNode(child);
4742     NS_ENSURE_SUCCESS(rv, rv);
4743     rv = aNewBlock->GetFirstChild(getter_AddRefs(child));
4744   }
4745   // now find and clone the styles
4746   child = aPreviousBlock;
4747   tmp = aPreviousBlock;
4748   while (tmp) {
4749     child = tmp;
4750     nsCOMPtr<nsINode> child_ = do_QueryInterface(child);
4751     NS_ENSURE_STATE(child_ || !child);
4752     tmp = GetAsDOMNode(GetLastEditableChild(*child_));
4753   }
4754   while (child && TextEditUtils::IsBreak(child)) {
4755     nsCOMPtr<nsIDOMNode> priorNode;
4756     rv = GetPriorHTMLNode(child, address_of(priorNode));
4757     NS_ENSURE_SUCCESS(rv, rv);
4758     child = priorNode;
4759   }
4760   nsCOMPtr<Element> newStyles, deepestStyle;
4761   nsCOMPtr<nsINode> childNode = do_QueryInterface(child);
4762   nsCOMPtr<Element> childElement;
4763   if (childNode) {
4764     childElement = childNode->IsElement() ? childNode->AsElement()
4765                                           : childNode->GetParentElement();
4766   }
4767   while (childElement && (childElement->AsDOMNode() != aPreviousBlock)) {
4768     if (HTMLEditUtils::IsInlineStyle(childElement) ||
4769         childElement->IsHTMLElement(nsGkAtoms::span)) {
4770       if (newStyles) {
4771         newStyles = InsertContainerAbove(newStyles,
4772                                          childElement->NodeInfo()->NameAtom());
4773         NS_ENSURE_STATE(newStyles);
4774       } else {
4775         deepestStyle = newStyles =
4776           CreateNode(childElement->NodeInfo()->NameAtom(), newBlock, 0);
4777         NS_ENSURE_STATE(newStyles);
4778       }
4779       CloneAttributes(newStyles, childElement);
4780     }
4781     childElement = childElement->GetParentElement();
4782   }
4783   if (deepestStyle) {
4784     RefPtr<Element> retVal = CreateBR(deepestStyle, 0);
4785     retVal.forget(aOutBrNode);
4786     NS_ENSURE_STATE(*aOutBrNode);
4787   }
4788   return NS_OK;
4789 }
4790 
4791 nsresult
GetElementOrigin(nsIDOMElement * aElement,int32_t & aX,int32_t & aY)4792 HTMLEditor::GetElementOrigin(nsIDOMElement* aElement,
4793                              int32_t& aX,
4794                              int32_t& aY)
4795 {
4796   aX = 0;
4797   aY = 0;
4798 
4799   NS_ENSURE_TRUE(mDocWeak, NS_ERROR_NOT_INITIALIZED);
4800   nsCOMPtr<nsIPresShell> ps = GetPresShell();
4801   NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
4802 
4803   nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
4804   nsIFrame *frame = content->GetPrimaryFrame();
4805   NS_ENSURE_TRUE(frame, NS_OK);
4806 
4807   nsIFrame *container = ps->GetAbsoluteContainingBlock(frame);
4808   NS_ENSURE_TRUE(container, NS_OK);
4809   nsPoint off = frame->GetOffsetTo(container);
4810   aX = nsPresContext::AppUnitsToIntCSSPixels(off.x);
4811   aY = nsPresContext::AppUnitsToIntCSSPixels(off.y);
4812 
4813   return NS_OK;
4814 }
4815 
4816 nsresult
EndUpdateViewBatch()4817 HTMLEditor::EndUpdateViewBatch()
4818 {
4819   nsresult rv = EditorBase::EndUpdateViewBatch();
4820   NS_ENSURE_SUCCESS(rv, rv);
4821 
4822   if (mUpdateCount) {
4823     return NS_OK;
4824   }
4825 
4826   // We may need to show resizing handles or update existing ones after
4827   // all transactions are done. This way of doing is preferred to DOM
4828   // mutation events listeners because all the changes the user can apply
4829   // to a document may result in multiple events, some of them quite hard
4830   // to listen too (in particular when an ancestor of the selection is
4831   // changed but the selection itself is not changed).
4832   RefPtr<Selection> selection = GetSelection();
4833   NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
4834   return CheckSelectionStateForAnonymousButtons(selection);
4835 }
4836 
4837 NS_IMETHODIMP
GetSelectionContainer(nsIDOMElement ** aReturn)4838 HTMLEditor::GetSelectionContainer(nsIDOMElement** aReturn)
4839 {
4840   nsCOMPtr<nsIDOMElement> container =
4841     static_cast<nsIDOMElement*>(GetAsDOMNode(GetSelectionContainer()));
4842   NS_ENSURE_TRUE(container, NS_ERROR_FAILURE);
4843   container.forget(aReturn);
4844   return NS_OK;
4845 }
4846 
4847 Element*
GetSelectionContainer()4848 HTMLEditor::GetSelectionContainer()
4849 {
4850   // If we don't get the selection, just skip this
4851   NS_ENSURE_TRUE(GetSelection(), nullptr);
4852 
4853   OwningNonNull<Selection> selection = *GetSelection();
4854 
4855   nsCOMPtr<nsINode> focusNode;
4856 
4857   if (selection->Collapsed()) {
4858     focusNode = selection->GetFocusNode();
4859   } else {
4860     int32_t rangeCount = selection->RangeCount();
4861 
4862     if (rangeCount == 1) {
4863       RefPtr<nsRange> range = selection->GetRangeAt(0);
4864 
4865       nsCOMPtr<nsINode> startContainer = range->GetStartParent();
4866       int32_t startOffset = range->StartOffset();
4867       nsCOMPtr<nsINode> endContainer = range->GetEndParent();
4868       int32_t endOffset = range->EndOffset();
4869 
4870       if (startContainer == endContainer && startOffset + 1 == endOffset) {
4871         nsCOMPtr<nsIDOMElement> focusElement;
4872         nsresult rv = GetSelectedElement(EmptyString(),
4873                                          getter_AddRefs(focusElement));
4874         NS_ENSURE_SUCCESS(rv, nullptr);
4875         if (focusElement) {
4876           focusNode = do_QueryInterface(focusElement);
4877         }
4878       }
4879       if (!focusNode) {
4880         focusNode = range->GetCommonAncestor();
4881       }
4882     } else {
4883       for (int32_t i = 0; i < rangeCount; i++) {
4884         RefPtr<nsRange> range = selection->GetRangeAt(i);
4885 
4886         nsCOMPtr<nsINode> startContainer = range->GetStartParent();
4887         if (!focusNode) {
4888           focusNode = startContainer;
4889         } else if (focusNode != startContainer) {
4890           focusNode = startContainer->GetParentNode();
4891           break;
4892         }
4893       }
4894     }
4895   }
4896 
4897   if (focusNode && focusNode->GetAsText()) {
4898     focusNode = focusNode->GetParentNode();
4899   }
4900 
4901   if (focusNode && focusNode->IsElement()) {
4902     return focusNode->AsElement();
4903   }
4904 
4905   return nullptr;
4906 }
4907 
4908 NS_IMETHODIMP
IsAnonymousElement(nsIDOMElement * aElement,bool * aReturn)4909 HTMLEditor::IsAnonymousElement(nsIDOMElement* aElement,
4910                                bool* aReturn)
4911 {
4912   NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER);
4913   nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
4914   *aReturn = content->IsRootOfNativeAnonymousSubtree();
4915   return NS_OK;
4916 }
4917 
4918 nsresult
SetReturnInParagraphCreatesNewParagraph(bool aCreatesNewParagraph)4919 HTMLEditor::SetReturnInParagraphCreatesNewParagraph(bool aCreatesNewParagraph)
4920 {
4921   mCRInParagraphCreatesParagraph = aCreatesNewParagraph;
4922   return NS_OK;
4923 }
4924 
4925 bool
GetReturnInParagraphCreatesNewParagraph()4926 HTMLEditor::GetReturnInParagraphCreatesNewParagraph()
4927 {
4928   return mCRInParagraphCreatesParagraph;
4929 }
4930 
4931 nsresult
GetReturnInParagraphCreatesNewParagraph(bool * aCreatesNewParagraph)4932 HTMLEditor::GetReturnInParagraphCreatesNewParagraph(bool* aCreatesNewParagraph)
4933 {
4934   *aCreatesNewParagraph = mCRInParagraphCreatesParagraph;
4935   return NS_OK;
4936 }
4937 
4938 already_AddRefed<nsIContent>
GetFocusedContent()4939 HTMLEditor::GetFocusedContent()
4940 {
4941   NS_ENSURE_TRUE(mDocWeak, nullptr);
4942 
4943   nsFocusManager* fm = nsFocusManager::GetFocusManager();
4944   NS_ENSURE_TRUE(fm, nullptr);
4945 
4946   nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedContent();
4947 
4948   nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
4949   bool inDesignMode = doc->HasFlag(NODE_IS_EDITABLE);
4950   if (!focusedContent) {
4951     // in designMode, nobody gets focus in most cases.
4952     if (inDesignMode && OurWindowHasFocus()) {
4953       nsCOMPtr<nsIContent> docRoot = doc->GetRootElement();
4954       return docRoot.forget();
4955     }
4956     return nullptr;
4957   }
4958 
4959   if (inDesignMode) {
4960     return OurWindowHasFocus() &&
4961       nsContentUtils::ContentIsDescendantOf(focusedContent, doc) ?
4962       focusedContent.forget() : nullptr;
4963   }
4964 
4965   // We're HTML editor for contenteditable
4966 
4967   // If the focused content isn't editable, or it has independent selection,
4968   // we don't have focus.
4969   if (!focusedContent->HasFlag(NODE_IS_EDITABLE) ||
4970       focusedContent->HasIndependentSelection()) {
4971     return nullptr;
4972   }
4973   // If our window is focused, we're focused.
4974   return OurWindowHasFocus() ? focusedContent.forget() : nullptr;
4975 }
4976 
4977 already_AddRefed<nsIContent>
GetFocusedContentForIME()4978 HTMLEditor::GetFocusedContentForIME()
4979 {
4980   nsCOMPtr<nsIContent> focusedContent = GetFocusedContent();
4981   if (!focusedContent) {
4982     return nullptr;
4983   }
4984 
4985   nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
4986   NS_ENSURE_TRUE(doc, nullptr);
4987   return doc->HasFlag(NODE_IS_EDITABLE) ? nullptr : focusedContent.forget();
4988 }
4989 
4990 bool
IsActiveInDOMWindow()4991 HTMLEditor::IsActiveInDOMWindow()
4992 {
4993   NS_ENSURE_TRUE(mDocWeak, false);
4994 
4995   nsFocusManager* fm = nsFocusManager::GetFocusManager();
4996   NS_ENSURE_TRUE(fm, false);
4997 
4998   nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
4999   bool inDesignMode = doc->HasFlag(NODE_IS_EDITABLE);
5000 
5001   // If we're in designMode, we're always active in the DOM window.
5002   if (inDesignMode) {
5003     return true;
5004   }
5005 
5006   nsPIDOMWindowOuter* ourWindow = doc->GetWindow();
5007   nsCOMPtr<nsPIDOMWindowOuter> win;
5008   nsIContent* content =
5009     nsFocusManager::GetFocusedDescendant(ourWindow, false,
5010                                          getter_AddRefs(win));
5011   if (!content) {
5012     return false;
5013   }
5014 
5015   // We're HTML editor for contenteditable
5016 
5017   // If the active content isn't editable, or it has independent selection,
5018   // we're not active).
5019   if (!content->HasFlag(NODE_IS_EDITABLE) ||
5020       content->HasIndependentSelection()) {
5021     return false;
5022   }
5023   return true;
5024 }
5025 
5026 Element*
GetActiveEditingHost()5027 HTMLEditor::GetActiveEditingHost()
5028 {
5029   NS_ENSURE_TRUE(mDocWeak, nullptr);
5030 
5031   nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
5032   NS_ENSURE_TRUE(doc, nullptr);
5033   if (doc->HasFlag(NODE_IS_EDITABLE)) {
5034     return doc->GetBodyElement();
5035   }
5036 
5037   // We're HTML editor for contenteditable
5038   RefPtr<Selection> selection = GetSelection();
5039   NS_ENSURE_TRUE(selection, nullptr);
5040   nsCOMPtr<nsIDOMNode> focusNode;
5041   nsresult rv = selection->GetFocusNode(getter_AddRefs(focusNode));
5042   NS_ENSURE_SUCCESS(rv, nullptr);
5043   nsCOMPtr<nsIContent> content = do_QueryInterface(focusNode);
5044   if (!content) {
5045     return nullptr;
5046   }
5047 
5048   // If the active content isn't editable, or it has independent selection,
5049   // we're not active.
5050   if (!content->HasFlag(NODE_IS_EDITABLE) ||
5051       content->HasIndependentSelection()) {
5052     return nullptr;
5053   }
5054   return content->GetEditingHost();
5055 }
5056 
5057 already_AddRefed<EventTarget>
GetDOMEventTarget()5058 HTMLEditor::GetDOMEventTarget()
5059 {
5060   // Don't use getDocument here, because we have no way of knowing
5061   // whether Init() was ever called.  So we need to get the document
5062   // ourselves, if it exists.
5063   NS_PRECONDITION(mDocWeak, "This editor has not been initialized yet");
5064   nsCOMPtr<mozilla::dom::EventTarget> target = do_QueryReferent(mDocWeak);
5065   return target.forget();
5066 }
5067 
5068 bool
ShouldReplaceRootElement()5069 HTMLEditor::ShouldReplaceRootElement()
5070 {
5071   if (!mRootElement) {
5072     // If we don't know what is our root element, we should find our root.
5073     return true;
5074   }
5075 
5076   // If we temporary set document root element to mRootElement, but there is
5077   // body element now, we should replace the root element by the body element.
5078   nsCOMPtr<nsIDOMHTMLElement> docBody;
5079   GetBodyElement(getter_AddRefs(docBody));
5080   return !SameCOMIdentity(docBody, mRootElement);
5081 }
5082 
5083 void
NotifyRootChanged()5084 HTMLEditor::NotifyRootChanged()
5085 {
5086   nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
5087 
5088   RemoveEventListeners();
5089   nsresult rv = InstallEventListeners();
5090   if (NS_FAILED(rv)) {
5091     return;
5092   }
5093 
5094   UpdateRootElement();
5095   if (!mRootElement) {
5096     return;
5097   }
5098 
5099   rv = BeginningOfDocument();
5100   if (NS_FAILED(rv)) {
5101     return;
5102   }
5103 
5104   // When this editor has focus, we need to reset the selection limiter to
5105   // new root.  Otherwise, that is going to be done when this gets focus.
5106   nsCOMPtr<nsINode> node = GetFocusedNode();
5107   nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(node);
5108   if (target) {
5109     InitializeSelection(target);
5110   }
5111 
5112   SyncRealTimeSpell();
5113 }
5114 
5115 nsresult
GetBodyElement(nsIDOMHTMLElement ** aBody)5116 HTMLEditor::GetBodyElement(nsIDOMHTMLElement** aBody)
5117 {
5118   NS_PRECONDITION(mDocWeak, "bad state, null mDocWeak");
5119   nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryReferent(mDocWeak);
5120   if (!htmlDoc) {
5121     return NS_ERROR_NOT_INITIALIZED;
5122   }
5123   return htmlDoc->GetBody(aBody);
5124 }
5125 
5126 already_AddRefed<nsINode>
GetFocusedNode()5127 HTMLEditor::GetFocusedNode()
5128 {
5129   nsCOMPtr<nsIContent> focusedContent = GetFocusedContent();
5130   if (!focusedContent) {
5131     return nullptr;
5132   }
5133 
5134   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
5135   NS_ASSERTION(fm, "Focus manager is null");
5136   nsCOMPtr<nsIDOMElement> focusedElement;
5137   fm->GetFocusedElement(getter_AddRefs(focusedElement));
5138   if (focusedElement) {
5139     nsCOMPtr<nsINode> node = do_QueryInterface(focusedElement);
5140     return node.forget();
5141   }
5142 
5143   nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
5144   return doc.forget();
5145 }
5146 
5147 bool
OurWindowHasFocus()5148 HTMLEditor::OurWindowHasFocus()
5149 {
5150   NS_ENSURE_TRUE(mDocWeak, false);
5151   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
5152   NS_ENSURE_TRUE(fm, false);
5153   nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
5154   fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
5155   if (!focusedWindow) {
5156     return false;
5157   }
5158   nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
5159   nsPIDOMWindowOuter* ourWindow = doc->GetWindow();
5160   return ourWindow == focusedWindow;
5161 }
5162 
5163 bool
IsAcceptableInputEvent(nsIDOMEvent * aEvent)5164 HTMLEditor::IsAcceptableInputEvent(nsIDOMEvent* aEvent)
5165 {
5166   if (!EditorBase::IsAcceptableInputEvent(aEvent)) {
5167     return false;
5168   }
5169 
5170   // While there is composition, all composition events in its top level window
5171   // are always fired on the composing editor.  Therefore, if this editor has
5172   // composition, the composition events should be handled in this editor.
5173   if (mComposition && aEvent->WidgetEventPtr()->AsCompositionEvent()) {
5174     return true;
5175   }
5176 
5177   NS_ENSURE_TRUE(mDocWeak, false);
5178 
5179   nsCOMPtr<nsIDOMEventTarget> target;
5180   aEvent->GetTarget(getter_AddRefs(target));
5181   NS_ENSURE_TRUE(target, false);
5182 
5183   nsCOMPtr<nsIDocument> document = do_QueryReferent(mDocWeak);
5184   if (document->HasFlag(NODE_IS_EDITABLE)) {
5185     // If this editor is in designMode and the event target is the document,
5186     // the event is for this editor.
5187     nsCOMPtr<nsIDocument> targetDocument = do_QueryInterface(target);
5188     if (targetDocument) {
5189       return targetDocument == document;
5190     }
5191     // Otherwise, check whether the event target is in this document or not.
5192     nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
5193     NS_ENSURE_TRUE(targetContent, false);
5194     return document == targetContent->GetUncomposedDoc();
5195   }
5196 
5197   // This HTML editor is for contenteditable.  We need to check the validity of
5198   // the target.
5199   nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
5200   NS_ENSURE_TRUE(targetContent, false);
5201 
5202   // If the event is a mouse event, we need to check if the target content is
5203   // the focused editing host or its descendant.
5204   nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
5205   if (mouseEvent) {
5206     nsIContent* editingHost = GetActiveEditingHost();
5207     // If there is no active editing host, we cannot handle the mouse event
5208     // correctly.
5209     if (!editingHost) {
5210       return false;
5211     }
5212     // If clicked on non-editable root element but the body element is the
5213     // active editing host, we should assume that the click event is targetted.
5214     if (targetContent == document->GetRootElement() &&
5215         !targetContent->HasFlag(NODE_IS_EDITABLE) &&
5216         editingHost == document->GetBodyElement()) {
5217       targetContent = editingHost;
5218     }
5219     // If the target element is neither the active editing host nor a descendant
5220     // of it, we may not be able to handle the event.
5221     if (!nsContentUtils::ContentIsDescendantOf(targetContent, editingHost)) {
5222       return false;
5223     }
5224     // If the clicked element has an independent selection, we shouldn't
5225     // handle this click event.
5226     if (targetContent->HasIndependentSelection()) {
5227       return false;
5228     }
5229     // If the target content is editable, we should handle this event.
5230     return targetContent->HasFlag(NODE_IS_EDITABLE);
5231   }
5232 
5233   // If the target of the other events which target focused element isn't
5234   // editable or has an independent selection, this editor shouldn't handle the
5235   // event.
5236   if (!targetContent->HasFlag(NODE_IS_EDITABLE) ||
5237       targetContent->HasIndependentSelection()) {
5238     return false;
5239   }
5240 
5241   // Finally, check whether we're actually focused or not.  When we're not
5242   // focused, we should ignore the dispatched event by script (or something)
5243   // because content editable element needs selection in itself for editing.
5244   // However, when we're not focused, it's not guaranteed.
5245   return IsActiveInDOMWindow();
5246 }
5247 
5248 NS_IMETHODIMP
GetPreferredIMEState(IMEState * aState)5249 HTMLEditor::GetPreferredIMEState(IMEState* aState)
5250 {
5251   // HTML editor don't prefer the CSS ime-mode because IE didn't do so too.
5252   aState->mOpen = IMEState::DONT_CHANGE_OPEN_STATE;
5253   if (IsReadonly() || IsDisabled()) {
5254     aState->mEnabled = IMEState::DISABLED;
5255   } else {
5256     aState->mEnabled = IMEState::ENABLED;
5257   }
5258   return NS_OK;
5259 }
5260 
5261 already_AddRefed<nsIContent>
GetInputEventTargetContent()5262 HTMLEditor::GetInputEventTargetContent()
5263 {
5264   nsCOMPtr<nsIContent> target = GetActiveEditingHost();
5265   return target.forget();
5266 }
5267 
5268 bool
IsEditable(nsINode * aNode)5269 HTMLEditor::IsEditable(nsINode* aNode)
5270 {
5271   if (!TextEditor::IsEditable(aNode)) {
5272     return false;
5273   }
5274   if (aNode->IsElement()) {
5275     // If we're dealing with an element, then ask it whether it's editable.
5276     return aNode->IsEditable();
5277   }
5278   // We might be dealing with a text node for example, which we always consider
5279   // to be editable.
5280   return true;
5281 }
5282 
5283 Element*
GetEditorRoot()5284 HTMLEditor::GetEditorRoot()
5285 {
5286   return GetActiveEditingHost();
5287 }
5288 
5289 } // namespace mozilla
5290