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