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