Coverage Report

Created: 2018-09-25 14:53

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