Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/editor/libeditor/HTMLStyleEditor.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 "HTMLEditUtils.h"
9
#include "TextEditUtils.h"
10
#include "TypeInState.h"
11
#include "mozilla/Assertions.h"
12
#include "mozilla/EditAction.h"
13
#include "mozilla/EditorUtils.h"
14
#include "mozilla/SelectionState.h"
15
#include "mozilla/TextEditRules.h"
16
#include "mozilla/dom/Selection.h"
17
#include "mozilla/dom/Element.h"
18
#include "mozilla/mozalloc.h"
19
#include "nsAString.h"
20
#include "nsAttrName.h"
21
#include "nsCOMPtr.h"
22
#include "nsCaseTreatment.h"
23
#include "nsComponentManagerUtils.h"
24
#include "nsDebug.h"
25
#include "nsError.h"
26
#include "nsGkAtoms.h"
27
#include "nsAtom.h"
28
#include "nsIContent.h"
29
#include "nsIContentIterator.h"
30
#include "nsNameSpaceManager.h"
31
#include "nsINode.h"
32
#include "nsISupportsImpl.h"
33
#include "nsLiteralString.h"
34
#include "nsRange.h"
35
#include "nsReadableUtils.h"
36
#include "nsString.h"
37
#include "nsStringFwd.h"
38
#include "nsTArray.h"
39
#include "nsUnicharUtils.h"
40
#include "nscore.h"
41
42
class nsISupports;
43
44
namespace mozilla {
45
46
using namespace dom;
47
48
bool
49
HTMLEditor::IsEmptyTextNode(nsINode& aNode)
50
0
{
51
0
  bool isEmptyTextNode = false;
52
0
  return EditorBase::IsTextNode(&aNode) &&
53
0
         NS_SUCCEEDED(IsEmptyNode(&aNode, &isEmptyTextNode)) &&
54
0
         isEmptyTextNode;
55
0
}
56
57
NS_IMETHODIMP
58
HTMLEditor::SetInlineProperty(const nsAString& aProperty,
59
                              const nsAString& aAttribute,
60
                              const nsAString& aValue)
61
0
{
62
0
  RefPtr<nsAtom> property = NS_Atomize(aProperty);
63
0
  if (NS_WARN_IF(!property)) {
64
0
    return NS_ERROR_INVALID_ARG;
65
0
  }
66
0
  RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
67
0
  return SetInlinePropertyInternal(*property, attribute, aValue);
68
0
}
69
70
nsresult
71
HTMLEditor::SetInlinePropertyInternal(nsAtom& aProperty,
72
                                      nsAtom* aAttribute,
73
                                      const nsAString& aValue)
74
0
{
75
0
  if (NS_WARN_IF(!mRules)) {
76
0
    return NS_ERROR_NOT_INITIALIZED;
77
0
  }
78
0
79
0
  RefPtr<TextEditRules> rules(mRules);
80
0
  CommitComposition();
81
0
82
0
  RefPtr<Selection> selection = GetSelection();
83
0
  if (NS_WARN_IF(!selection)) {
84
0
    return NS_ERROR_FAILURE;
85
0
  }
86
0
87
0
  if (selection->IsCollapsed()) {
88
0
    // Manipulating text attributes on a collapsed selection only sets state
89
0
    // for the next text insertion
90
0
    mTypeInState->SetProp(&aProperty, aAttribute, aValue);
91
0
    return NS_OK;
92
0
  }
93
0
94
0
  AutoPlaceholderBatch batchIt(this);
95
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
96
0
                                      *this, EditSubAction::eInsertElement,
97
0
                                      nsIEditor::eNext);
98
0
  AutoSelectionRestorer selectionRestorer(selection, this);
99
0
  AutoTransactionsConserveSelection dontChangeMySelection(*this);
100
0
101
0
  bool cancel, handled;
102
0
  EditSubActionInfo subActionInfo(EditSubAction::eSetTextProperty);
103
0
  // Protect the edit rules object from dying
104
0
  nsresult rv =
105
0
    rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
106
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
107
0
    return rv;
108
0
  }
109
0
  if (!cancel && !handled) {
110
0
    // Loop through the ranges in the selection
111
0
    AutoRangeArray arrayOfRanges(selection);
112
0
    for (auto& range : arrayOfRanges.mRanges) {
113
0
      // Adjust range to include any ancestors whose children are entirely
114
0
      // selected
115
0
      rv = PromoteInlineRange(*range);
116
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
117
0
        return rv;
118
0
      }
119
0
120
0
      // Check for easy case: both range endpoints in same text node
121
0
      nsCOMPtr<nsINode> startNode = range->GetStartContainer();
122
0
      nsCOMPtr<nsINode> endNode = range->GetEndContainer();
123
0
      if (startNode && startNode == endNode && startNode->GetAsText()) {
124
0
        rv = SetInlinePropertyOnTextNode(*startNode->GetAsText(),
125
0
                                         range->StartOffset(),
126
0
                                         range->EndOffset(),
127
0
                                         aProperty, aAttribute, aValue);
128
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
129
0
          return rv;
130
0
        }
131
0
        continue;
132
0
      }
133
0
134
0
      // Not the easy case.  Range not contained in single text node.  There
135
0
      // are up to three phases here.  There are all the nodes reported by the
136
0
      // subtree iterator to be processed.  And there are potentially a
137
0
      // starting textnode and an ending textnode which are only partially
138
0
      // contained by the range.
139
0
140
0
      // Let's handle the nodes reported by the iterator.  These nodes are
141
0
      // entirely contained in the selection range.  We build up a list of them
142
0
      // (since doing operations on the document during iteration would perturb
143
0
      // the iterator).
144
0
145
0
      OwningNonNull<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
146
0
147
0
      nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
148
0
149
0
      // Iterate range and build up array
150
0
      rv = iter->Init(range);
151
0
      // Init returns an error if there are no nodes in range.  This can easily
152
0
      // happen with the subtree iterator if the selection doesn't contain any
153
0
      // *whole* nodes.
154
0
      if (NS_SUCCEEDED(rv)) {
155
0
        for (; !iter->IsDone(); iter->Next()) {
156
0
          OwningNonNull<nsINode> node = *iter->GetCurrentNode();
157
0
158
0
          if (node->IsContent() && IsEditable(node)) {
159
0
            arrayOfNodes.AppendElement(*node->AsContent());
160
0
          }
161
0
        }
162
0
      }
163
0
      // First check the start parent of the range to see if it needs to be
164
0
      // separately handled (it does if it's a text node, due to how the
165
0
      // subtree iterator works - it will not have reported it).
166
0
      if (startNode && startNode->GetAsText() && IsEditable(startNode)) {
167
0
        rv = SetInlinePropertyOnTextNode(*startNode->GetAsText(),
168
0
                                         range->StartOffset(),
169
0
                                         startNode->Length(), aProperty,
170
0
                                         aAttribute, aValue);
171
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
172
0
          return rv;
173
0
        }
174
0
      }
175
0
176
0
      // Then loop through the list, set the property on each node
177
0
      for (auto& node : arrayOfNodes) {
178
0
        rv = SetInlinePropertyOnNode(*node, aProperty, aAttribute, aValue);
179
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
180
0
          return rv;
181
0
        }
182
0
      }
183
0
184
0
      // Last check the end parent of the range to see if it needs to be
185
0
      // separately handled (it does if it's a text node, due to how the
186
0
      // subtree iterator works - it will not have reported it).
187
0
      if (endNode && endNode->GetAsText() && IsEditable(endNode)) {
188
0
        rv = SetInlinePropertyOnTextNode(*endNode->GetAsText(), 0,
189
0
                                          range->EndOffset(), aProperty,
190
0
                                          aAttribute, aValue);
191
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
192
0
          return rv;
193
0
        }
194
0
      }
195
0
    }
196
0
  }
197
0
  if (cancel) {
198
0
    return NS_OK;
199
0
  }
200
0
201
0
  rv = rules->DidDoAction(selection, subActionInfo, rv);
202
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
203
0
    return rv;
204
0
  }
205
0
  return NS_OK;
206
0
}
207
208
// Helper function for SetInlinePropertyOn*: is aNode a simple old <b>, <font>,
209
// <span style="">, etc. that we can reuse instead of creating a new one?
210
bool
211
HTMLEditor::IsSimpleModifiableNode(nsIContent* aContent,
212
                                   nsAtom* aProperty,
213
                                   nsAtom* aAttribute,
214
                                   const nsAString* aValue)
215
0
{
216
0
  // aContent can be null, in which case we'll return false in a few lines
217
0
  MOZ_ASSERT(aProperty);
218
0
  MOZ_ASSERT_IF(aAttribute, aValue);
219
0
220
0
  nsCOMPtr<dom::Element> element = do_QueryInterface(aContent);
221
0
  if (!element) {
222
0
    return false;
223
0
  }
224
0
225
0
  // First check for <b>, <i>, etc.
226
0
  if (element->IsHTMLElement(aProperty) && !element->GetAttrCount() &&
227
0
      !aAttribute) {
228
0
    return true;
229
0
  }
230
0
231
0
  // Special cases for various equivalencies: <strong>, <em>, <s>
232
0
  if (!element->GetAttrCount() &&
233
0
      ((aProperty == nsGkAtoms::b &&
234
0
        element->IsHTMLElement(nsGkAtoms::strong)) ||
235
0
       (aProperty == nsGkAtoms::i &&
236
0
        element->IsHTMLElement(nsGkAtoms::em)) ||
237
0
       (aProperty == nsGkAtoms::strike &&
238
0
        element->IsHTMLElement(nsGkAtoms::s)))) {
239
0
    return true;
240
0
  }
241
0
242
0
  // Now look for things like <font>
243
0
  if (aAttribute) {
244
0
    nsString attrValue;
245
0
    if (element->IsHTMLElement(aProperty) &&
246
0
        IsOnlyAttribute(element, aAttribute) &&
247
0
        element->GetAttr(kNameSpaceID_None, aAttribute, attrValue) &&
248
0
        attrValue.Equals(*aValue, nsCaseInsensitiveStringComparator())) {
249
0
      // This is not quite correct, because it excludes cases like
250
0
      // <font face=000> being the same as <font face=#000000>.
251
0
      // Property-specific handling is needed (bug 760211).
252
0
      return true;
253
0
    }
254
0
  }
255
0
256
0
  // No luck so far.  Now we check for a <span> with a single style=""
257
0
  // attribute that sets only the style we're looking for, if this type of
258
0
  // style supports it
259
0
  if (!CSSEditUtils::IsCSSEditableProperty(element, aProperty, aAttribute) ||
260
0
      !element->IsHTMLElement(nsGkAtoms::span) ||
261
0
      element->GetAttrCount() != 1 ||
262
0
      !element->HasAttr(kNameSpaceID_None, nsGkAtoms::style)) {
263
0
    return false;
264
0
  }
265
0
266
0
  // Some CSS styles are not so simple.  For instance, underline is
267
0
  // "text-decoration: underline", which decomposes into four different text-*
268
0
  // properties.  So for now, we just create a span, add the desired style, and
269
0
  // see if it matches.
270
0
  nsCOMPtr<Element> newSpan = CreateHTMLContent(nsGkAtoms::span);
271
0
  NS_ASSERTION(newSpan, "CreateHTMLContent failed");
272
0
  NS_ENSURE_TRUE(newSpan, false);
273
0
  mCSSEditUtils->SetCSSEquivalentToHTMLStyle(newSpan, aProperty,
274
0
                                             aAttribute, aValue,
275
0
                                             /*suppress transaction*/ true);
276
0
277
0
  return CSSEditUtils::ElementsSameStyle(newSpan, element);
278
0
}
279
280
nsresult
281
HTMLEditor::SetInlinePropertyOnTextNode(Text& aText,
282
                                        int32_t aStartOffset,
283
                                        int32_t aEndOffset,
284
                                        nsAtom& aProperty,
285
                                        nsAtom* aAttribute,
286
                                        const nsAString& aValue)
287
0
{
288
0
  if (!aText.GetParentNode() ||
289
0
      !CanContainTag(*aText.GetParentNode(), aProperty)) {
290
0
    return NS_OK;
291
0
  }
292
0
293
0
  // Don't need to do anything if no characters actually selected
294
0
  if (aStartOffset == aEndOffset) {
295
0
    return NS_OK;
296
0
  }
297
0
298
0
  // Don't need to do anything if property already set on node
299
0
  if (CSSEditUtils::IsCSSEditableProperty(&aText, &aProperty, aAttribute)) {
300
0
    // The HTML styles defined by aProperty/aAttribute have a CSS equivalence
301
0
    // for node; let's check if it carries those CSS styles
302
0
    if (CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(&aText, &aProperty,
303
0
          aAttribute, aValue, CSSEditUtils::eComputed)) {
304
0
      return NS_OK;
305
0
    }
306
0
  } else if (IsTextPropertySetByContent(&aText, &aProperty, aAttribute,
307
0
                                        &aValue)) {
308
0
    return NS_OK;
309
0
  }
310
0
311
0
  // Make the range an independent node.
312
0
  nsCOMPtr<nsIContent> textNodeForTheRange = &aText;
313
0
314
0
  // Split at the end of the range.
315
0
  EditorRawDOMPoint atEnd(textNodeForTheRange, aEndOffset);
316
0
  if (!atEnd.IsEndOfContainer()) {
317
0
    // We need to split off back of text node
318
0
    ErrorResult error;
319
0
    textNodeForTheRange = SplitNodeWithTransaction(atEnd, error);
320
0
    if (NS_WARN_IF(error.Failed())) {
321
0
      return error.StealNSResult();
322
0
    }
323
0
  }
324
0
325
0
  // Split at the start of the range.
326
0
  EditorRawDOMPoint atStart(textNodeForTheRange, aStartOffset);
327
0
  if (!atStart.IsStartOfContainer()) {
328
0
    // We need to split off front of text node
329
0
    ErrorResult error;
330
0
    nsCOMPtr<nsIContent> newLeftNode = SplitNodeWithTransaction(atStart, error);
331
0
    if (NS_WARN_IF(error.Failed())) {
332
0
      return error.StealNSResult();
333
0
    }
334
0
    Unused << newLeftNode;
335
0
  }
336
0
337
0
  if (aAttribute) {
338
0
    // Look for siblings that are correct type of node
339
0
    nsIContent* sibling = GetPriorHTMLSibling(textNodeForTheRange);
340
0
    if (IsSimpleModifiableNode(sibling, &aProperty, aAttribute, &aValue)) {
341
0
      // Previous sib is already right kind of inline node; slide this over
342
0
      return MoveNodeToEndWithTransaction(*textNodeForTheRange, *sibling);
343
0
    }
344
0
    sibling = GetNextHTMLSibling(textNodeForTheRange);
345
0
    if (IsSimpleModifiableNode(sibling, &aProperty, aAttribute, &aValue)) {
346
0
      // Following sib is already right kind of inline node; slide this over
347
0
      return MoveNodeWithTransaction(*textNodeForTheRange,
348
0
                                     EditorRawDOMPoint(sibling, 0));
349
0
    }
350
0
  }
351
0
352
0
  // Reparent the node inside inline node with appropriate {attribute,value}
353
0
  return SetInlinePropertyOnNode(*textNodeForTheRange,
354
0
                                 aProperty, aAttribute, aValue);
355
0
}
356
357
nsresult
358
HTMLEditor::SetInlinePropertyOnNodeImpl(nsIContent& aNode,
359
                                        nsAtom& aProperty,
360
                                        nsAtom* aAttribute,
361
                                        const nsAString& aValue)
362
0
{
363
0
  // If this is an element that can't be contained in a span, we have to
364
0
  // recurse to its children.
365
0
  if (!TagCanContain(*nsGkAtoms::span, aNode)) {
366
0
    if (aNode.HasChildren()) {
367
0
      nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
368
0
369
0
      // Populate the list.
370
0
      for (nsCOMPtr<nsIContent> child = aNode.GetFirstChild();
371
0
           child;
372
0
           child = child->GetNextSibling()) {
373
0
        if (IsEditable(child) && !IsEmptyTextNode(*child)) {
374
0
          arrayOfNodes.AppendElement(*child);
375
0
        }
376
0
      }
377
0
378
0
      // Then loop through the list, set the property on each node.
379
0
      for (auto& node : arrayOfNodes) {
380
0
        nsresult rv = SetInlinePropertyOnNode(node, aProperty, aAttribute,
381
0
                                              aValue);
382
0
        NS_ENSURE_SUCCESS(rv, rv);
383
0
      }
384
0
    }
385
0
    return NS_OK;
386
0
  }
387
0
388
0
  // First check if there's an adjacent sibling we can put our node into.
389
0
  nsCOMPtr<nsIContent> previousSibling = GetPriorHTMLSibling(&aNode);
390
0
  nsCOMPtr<nsIContent> nextSibling = GetNextHTMLSibling(&aNode);
391
0
  if (IsSimpleModifiableNode(previousSibling, &aProperty, aAttribute, &aValue)) {
392
0
    nsresult rv = MoveNodeToEndWithTransaction(aNode, *previousSibling);
393
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
394
0
      return rv;
395
0
    }
396
0
    if (IsSimpleModifiableNode(nextSibling, &aProperty, aAttribute, &aValue)) {
397
0
      rv = JoinNodesWithTransaction(*previousSibling, *nextSibling);
398
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
399
0
        return rv;
400
0
      }
401
0
    }
402
0
    return NS_OK;
403
0
  }
404
0
  if (IsSimpleModifiableNode(nextSibling, &aProperty, aAttribute, &aValue)) {
405
0
    nsresult rv =
406
0
      MoveNodeWithTransaction(aNode, EditorRawDOMPoint(nextSibling, 0));
407
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
408
0
      return rv;
409
0
    }
410
0
    return NS_OK;
411
0
  }
412
0
413
0
  // Don't need to do anything if property already set on node
414
0
  if (CSSEditUtils::IsCSSEditableProperty(&aNode, &aProperty, aAttribute)) {
415
0
    if (CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(
416
0
          &aNode, &aProperty, aAttribute, aValue, CSSEditUtils::eComputed)) {
417
0
      return NS_OK;
418
0
    }
419
0
  } else if (IsTextPropertySetByContent(&aNode, &aProperty,
420
0
                                        aAttribute, &aValue)) {
421
0
    return NS_OK;
422
0
  }
423
0
424
0
  bool useCSS = (IsCSSEnabled() &&
425
0
                 CSSEditUtils::IsCSSEditableProperty(&aNode, &aProperty,
426
0
                                                     aAttribute)) ||
427
0
                // bgcolor is always done using CSS
428
0
                aAttribute == nsGkAtoms::bgcolor;
429
0
430
0
  if (useCSS) {
431
0
    RefPtr<dom::Element> tmp;
432
0
    // We only add style="" to <span>s with no attributes (bug 746515).  If we
433
0
    // don't have one, we need to make one.
434
0
    if (aNode.IsHTMLElement(nsGkAtoms::span) &&
435
0
        !aNode.AsElement()->GetAttrCount()) {
436
0
      tmp = aNode.AsElement();
437
0
    } else {
438
0
      tmp = InsertContainerWithTransaction(aNode, *nsGkAtoms::span);
439
0
      if (NS_WARN_IF(!tmp)) {
440
0
        return NS_ERROR_FAILURE;
441
0
      }
442
0
    }
443
0
444
0
    // Add the CSS styles corresponding to the HTML style request
445
0
    mCSSEditUtils->SetCSSEquivalentToHTMLStyle(tmp,
446
0
                                               &aProperty, aAttribute,
447
0
                                               &aValue, false);
448
0
    return NS_OK;
449
0
  }
450
0
451
0
  // is it already the right kind of node, but with wrong attribute?
452
0
  if (aNode.IsHTMLElement(&aProperty)) {
453
0
    if (NS_WARN_IF(!aAttribute)) {
454
0
      return NS_ERROR_FAILURE;
455
0
    }
456
0
    // Just set the attribute on it.
457
0
    return SetAttributeWithTransaction(*aNode.AsElement(), *aAttribute, aValue);
458
0
  }
459
0
460
0
  // ok, chuck it in its very own container
461
0
  RefPtr<Element> tmp =
462
0
      InsertContainerWithTransaction(aNode, aProperty,
463
0
                                     aAttribute ? *aAttribute :
464
0
                                                  *nsGkAtoms::_empty,
465
0
                                     aValue);
466
0
  if (NS_WARN_IF(!tmp)) {
467
0
    return NS_ERROR_FAILURE;
468
0
  }
469
0
  return NS_OK;
470
0
}
471
472
nsresult
473
HTMLEditor::SetInlinePropertyOnNode(nsIContent& aNode,
474
                                    nsAtom& aProperty,
475
                                    nsAtom* aAttribute,
476
                                    const nsAString& aValue)
477
0
{
478
0
  nsCOMPtr<nsIContent> previousSibling = aNode.GetPreviousSibling(),
479
0
                       nextSibling = aNode.GetNextSibling();
480
0
  NS_ENSURE_STATE(aNode.GetParentNode());
481
0
  OwningNonNull<nsINode> parent = *aNode.GetParentNode();
482
0
483
0
  nsresult rv = RemoveStyleInside(aNode, &aProperty, aAttribute);
484
0
  NS_ENSURE_SUCCESS(rv, rv);
485
0
486
0
  if (aNode.GetParentNode()) {
487
0
    // The node is still where it was
488
0
    return SetInlinePropertyOnNodeImpl(aNode, aProperty,
489
0
                                       aAttribute, aValue);
490
0
  }
491
0
492
0
  // It's vanished.  Use the old siblings for reference to construct a
493
0
  // list.  But first, verify that the previous/next siblings are still
494
0
  // where we expect them; otherwise we have to give up.
495
0
  if ((previousSibling && previousSibling->GetParentNode() != parent) ||
496
0
      (nextSibling && nextSibling->GetParentNode() != parent)) {
497
0
    return NS_ERROR_UNEXPECTED;
498
0
  }
499
0
  nsTArray<OwningNonNull<nsIContent>> nodesToSet;
500
0
  nsCOMPtr<nsIContent> cur = previousSibling
501
0
    ? previousSibling->GetNextSibling() : parent->GetFirstChild();
502
0
  for (; cur && cur != nextSibling; cur = cur->GetNextSibling()) {
503
0
    if (IsEditable(cur)) {
504
0
      nodesToSet.AppendElement(*cur);
505
0
    }
506
0
  }
507
0
508
0
  for (auto& node : nodesToSet) {
509
0
    rv = SetInlinePropertyOnNodeImpl(node, aProperty, aAttribute, aValue);
510
0
    NS_ENSURE_SUCCESS(rv, rv);
511
0
  }
512
0
513
0
  return NS_OK;
514
0
}
515
516
nsresult
517
HTMLEditor::SplitStyleAboveRange(nsRange* inRange,
518
                                 nsAtom* aProperty,
519
                                 nsAtom* aAttribute)
520
0
{
521
0
  NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER);
522
0
523
0
  nsCOMPtr<nsINode> startNode = inRange->GetStartContainer();
524
0
  int32_t startOffset = inRange->StartOffset();
525
0
  nsCOMPtr<nsINode> endNode = inRange->GetEndContainer();
526
0
  int32_t endOffset = inRange->EndOffset();
527
0
528
0
  nsCOMPtr<nsINode> origStartNode = startNode;
529
0
530
0
  // split any matching style nodes above the start of range
531
0
  {
532
0
    AutoTrackDOMPoint tracker(mRangeUpdater, address_of(endNode), &endOffset);
533
0
    nsresult rv =
534
0
      SplitStyleAbovePoint(address_of(startNode), &startOffset, aProperty,
535
0
                           aAttribute);
536
0
    NS_ENSURE_SUCCESS(rv, rv);
537
0
  }
538
0
539
0
  // second verse, same as the first...
540
0
  nsresult rv =
541
0
    SplitStyleAbovePoint(address_of(endNode), &endOffset, aProperty,
542
0
                         aAttribute);
543
0
  NS_ENSURE_SUCCESS(rv, rv);
544
0
545
0
  // reset the range
546
0
  rv = inRange->SetStartAndEnd(startNode, startOffset, endNode, endOffset);
547
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
548
0
    return rv;
549
0
  }
550
0
  return NS_OK;
551
0
}
552
553
nsresult
554
HTMLEditor::SplitStyleAbovePoint(nsCOMPtr<nsINode>* aNode,
555
                                 int32_t* aOffset,
556
                                 // null here means we split all properties
557
                                 nsAtom* aProperty,
558
                                 nsAtom* aAttribute,
559
                                 nsIContent** aOutLeftNode,
560
                                 nsIContent** aOutRightNode)
561
0
{
562
0
  NS_ENSURE_TRUE(aNode && *aNode && aOffset, NS_ERROR_NULL_POINTER);
563
0
  NS_ENSURE_TRUE((*aNode)->IsContent(), NS_OK);
564
0
565
0
  if (aOutLeftNode) {
566
0
    *aOutLeftNode = nullptr;
567
0
  }
568
0
  if (aOutRightNode) {
569
0
    *aOutRightNode = nullptr;
570
0
  }
571
0
572
0
  // Split any matching style nodes above the node/offset
573
0
  nsCOMPtr<nsIContent> node = (*aNode)->AsContent();
574
0
575
0
  bool useCSS = IsCSSEnabled();
576
0
577
0
  bool isSet;
578
0
  while (!IsBlockNode(node) && node->GetParent() &&
579
0
         IsEditable(node->GetParent())) {
580
0
    isSet = false;
581
0
    if (useCSS && CSSEditUtils::IsCSSEditableProperty(node, aProperty,
582
0
                                                      aAttribute)) {
583
0
      // The HTML style defined by aProperty/aAttribute has a CSS equivalence
584
0
      // in this implementation for the node; let's check if it carries those
585
0
      // CSS styles
586
0
      nsAutoString firstValue;
587
0
      isSet = CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(
588
0
                node, aProperty, aAttribute, firstValue,
589
0
                CSSEditUtils::eSpecified);
590
0
    }
591
0
    if (// node is the correct inline prop
592
0
        (aProperty && node->IsHTMLElement(aProperty)) ||
593
0
        // node is href - test if really <a href=...
594
0
        (aProperty == nsGkAtoms::href && HTMLEditUtils::IsLink(node)) ||
595
0
        // or node is any prop, and we asked to split them all
596
0
        (!aProperty && NodeIsProperty(*node)) ||
597
0
        // or the style is specified in the style attribute
598
0
        isSet) {
599
0
      // Found a style node we need to split
600
0
      SplitNodeResult splitNodeResult =
601
0
        SplitNodeDeepWithTransaction(*node, EditorRawDOMPoint(*aNode, *aOffset),
602
0
                                     SplitAtEdges::eAllowToCreateEmptyContainer);
603
0
      NS_WARNING_ASSERTION(splitNodeResult.Succeeded(),
604
0
        "Failed to split the node");
605
0
606
0
      EditorRawDOMPoint atRightNode(splitNodeResult.SplitPoint());
607
0
      *aNode = atRightNode.GetContainer();
608
0
      *aOffset = atRightNode.Offset();
609
0
      if (aOutLeftNode) {
610
0
        NS_IF_ADDREF(*aOutLeftNode = splitNodeResult.GetPreviousNode());
611
0
      }
612
0
      if (aOutRightNode) {
613
0
        NS_IF_ADDREF(*aOutRightNode = splitNodeResult.GetNextNode());
614
0
      }
615
0
    }
616
0
    node = node->GetParent();
617
0
    if (NS_WARN_IF(!node)) {
618
0
      return NS_ERROR_FAILURE;
619
0
    }
620
0
  }
621
0
622
0
  return NS_OK;
623
0
}
624
625
nsresult
626
HTMLEditor::ClearStyle(nsCOMPtr<nsINode>* aNode,
627
                       int32_t* aOffset,
628
                       nsAtom* aProperty,
629
                       nsAtom* aAttribute)
630
0
{
631
0
  nsCOMPtr<nsIContent> leftNode, rightNode;
632
0
  nsresult rv = SplitStyleAbovePoint(aNode, aOffset, aProperty,
633
0
                                     aAttribute, getter_AddRefs(leftNode),
634
0
                                     getter_AddRefs(rightNode));
635
0
  NS_ENSURE_SUCCESS(rv, rv);
636
0
637
0
  if (leftNode) {
638
0
    bool bIsEmptyNode;
639
0
    IsEmptyNode(leftNode, &bIsEmptyNode, false, true);
640
0
    if (bIsEmptyNode) {
641
0
      // delete leftNode if it became empty
642
0
      rv = DeleteNodeWithTransaction(*leftNode);
643
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
644
0
        return rv;
645
0
      }
646
0
    }
647
0
  }
648
0
  if (rightNode) {
649
0
    nsCOMPtr<nsINode> secondSplitParent = GetLeftmostChild(rightNode);
650
0
    // don't try to split non-containers (br's, images, hr's, etc.)
651
0
    if (!secondSplitParent) {
652
0
      secondSplitParent = rightNode;
653
0
    }
654
0
    nsCOMPtr<Element> savedBR;
655
0
    if (!IsContainer(secondSplitParent)) {
656
0
      if (TextEditUtils::IsBreak(secondSplitParent)) {
657
0
        savedBR = do_QueryInterface(secondSplitParent);
658
0
        NS_ENSURE_STATE(savedBR);
659
0
      }
660
0
661
0
      secondSplitParent = secondSplitParent->GetParentNode();
662
0
    }
663
0
    *aOffset = 0;
664
0
    rv = SplitStyleAbovePoint(address_of(secondSplitParent),
665
0
                              aOffset, aProperty, aAttribute,
666
0
                              getter_AddRefs(leftNode),
667
0
                              getter_AddRefs(rightNode));
668
0
    NS_ENSURE_SUCCESS(rv, rv);
669
0
670
0
    if (rightNode) {
671
0
      bool bIsEmptyNode;
672
0
      IsEmptyNode(rightNode, &bIsEmptyNode, false, true);
673
0
      if (bIsEmptyNode) {
674
0
        // delete rightNode if it became empty
675
0
        rv = DeleteNodeWithTransaction(*rightNode);
676
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
677
0
          return rv;
678
0
        }
679
0
      }
680
0
    }
681
0
682
0
    if (!leftNode) {
683
0
      return NS_OK;
684
0
    }
685
0
686
0
    // should be impossible to not get a new leftnode here
687
0
    nsCOMPtr<nsINode> newSelParent = GetLeftmostChild(leftNode);
688
0
    if (!newSelParent) {
689
0
      newSelParent = leftNode;
690
0
    }
691
0
    // If rightNode starts with a br, suck it out of right node and into
692
0
    // leftNode.  This is so we you don't revert back to the previous style
693
0
    // if you happen to click at the end of a line.
694
0
    if (savedBR) {
695
0
      rv = MoveNodeWithTransaction(*savedBR,
696
0
                                   EditorRawDOMPoint(newSelParent, 0));
697
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
698
0
        return rv;
699
0
      }
700
0
    }
701
0
    // remove the style on this new hierarchy
702
0
    int32_t newSelOffset = 0;
703
0
    {
704
0
      // Track the point at the new hierarchy.  This is so we can know where
705
0
      // to put the selection after we call RemoveStyleInside().
706
0
      // RemoveStyleInside() could remove any and all of those nodes, so I
707
0
      // have to use the range tracking system to find the right spot to put
708
0
      // selection.
709
0
      AutoTrackDOMPoint tracker(mRangeUpdater,
710
0
                                address_of(newSelParent), &newSelOffset);
711
0
      rv = RemoveStyleInside(*leftNode, aProperty, aAttribute);
712
0
      NS_ENSURE_SUCCESS(rv, rv);
713
0
    }
714
0
    // reset our node offset values to the resulting new sel point
715
0
    *aNode = newSelParent;
716
0
    *aOffset = newSelOffset;
717
0
  }
718
0
719
0
  return NS_OK;
720
0
}
721
722
bool
723
HTMLEditor::NodeIsProperty(nsINode& aNode)
724
0
{
725
0
  return IsContainer(&aNode) && IsEditable(&aNode) && !IsBlockNode(&aNode) &&
726
0
         !aNode.IsHTMLElement(nsGkAtoms::a);
727
0
}
728
729
nsresult
730
HTMLEditor::RemoveStyleInside(nsIContent& aNode,
731
                              nsAtom* aProperty,
732
                              nsAtom* aAttribute,
733
                              const bool aChildrenOnly /* = false */)
734
0
{
735
0
  if (!aNode.IsElement()) {
736
0
    return NS_OK;
737
0
  }
738
0
739
0
  // first process the children
740
0
  RefPtr<nsIContent> child = aNode.GetFirstChild();
741
0
  while (child) {
742
0
    // cache next sibling since we might remove child
743
0
    nsCOMPtr<nsIContent> next = child->GetNextSibling();
744
0
    nsresult rv = RemoveStyleInside(*child, aProperty, aAttribute);
745
0
    NS_ENSURE_SUCCESS(rv, rv);
746
0
    child = next.forget();
747
0
  }
748
0
749
0
  // then process the node itself
750
0
  if (!aChildrenOnly &&
751
0
       // node is prop we asked for
752
0
      ((aProperty && aNode.NodeInfo()->NameAtom() == aProperty) ||
753
0
       // but check for link (<a href=...)
754
0
       (aProperty == nsGkAtoms::href && HTMLEditUtils::IsLink(&aNode)) ||
755
0
       // and for named anchors
756
0
       (aProperty == nsGkAtoms::name && HTMLEditUtils::IsNamedAnchor(&aNode)) ||
757
0
       // or node is any prop and we asked for that
758
0
       (!aProperty && NodeIsProperty(aNode)))) {
759
0
    // if we weren't passed an attribute, then we want to
760
0
    // remove any matching inlinestyles entirely
761
0
    if (!aAttribute) {
762
0
      bool hasStyleAttr =
763
0
        aNode.AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::style);
764
0
      bool hasClassAttr =
765
0
        aNode.AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::_class);
766
0
      if (aProperty && (hasStyleAttr || hasClassAttr)) {
767
0
        // aNode carries inline styles or a class attribute so we can't
768
0
        // just remove the element... We need to create above the element
769
0
        // a span that will carry those styles or class, then we can delete
770
0
        // the node.
771
0
        RefPtr<Element> spanNode =
772
0
          InsertContainerWithTransaction(aNode, *nsGkAtoms::span);
773
0
        if (NS_WARN_IF(!spanNode)) {
774
0
          return NS_ERROR_FAILURE;
775
0
        }
776
0
        nsresult rv =
777
0
          CloneAttributeWithTransaction(*nsGkAtoms::style, *spanNode,
778
0
                                        *aNode.AsElement());
779
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
780
0
          return rv;
781
0
        }
782
0
        rv = CloneAttributeWithTransaction(*nsGkAtoms::_class, *spanNode,
783
0
                                           *aNode.AsElement());
784
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
785
0
          return rv;
786
0
        }
787
0
      }
788
0
      nsresult rv = RemoveContainerWithTransaction(*aNode.AsElement());
789
0
      NS_ENSURE_SUCCESS(rv, rv);
790
0
    } else if (aNode.IsElement()) {
791
0
      // otherwise we just want to eliminate the attribute
792
0
      if (aNode.AsElement()->HasAttr(kNameSpaceID_None, aAttribute)) {
793
0
        // if this matching attribute is the ONLY one on the node,
794
0
        // then remove the whole node.  Otherwise just nix the attribute.
795
0
        if (IsOnlyAttribute(aNode.AsElement(), aAttribute)) {
796
0
          nsresult rv = RemoveContainerWithTransaction(*aNode.AsElement());
797
0
          if (NS_WARN_IF(NS_FAILED(rv))) {
798
0
            return rv;
799
0
          }
800
0
        } else {
801
0
          nsresult rv =
802
0
            RemoveAttributeWithTransaction(*aNode.AsElement(), *aAttribute);
803
0
          if (NS_WARN_IF(NS_FAILED(rv))) {
804
0
            return rv;
805
0
          }
806
0
        }
807
0
      }
808
0
    }
809
0
  }
810
0
811
0
  if (!aChildrenOnly &&
812
0
      CSSEditUtils::IsCSSEditableProperty(&aNode, aProperty, aAttribute)) {
813
0
    // the HTML style defined by aProperty/aAttribute has a CSS equivalence in
814
0
    // this implementation for the node aNode; let's check if it carries those
815
0
    // css styles
816
0
    if (aNode.IsElement()) {
817
0
      bool hasAttribute =
818
0
        CSSEditUtils::HaveCSSEquivalentStyles(
819
0
                        aNode, aProperty, aAttribute,
820
0
                        CSSEditUtils::eSpecified);
821
0
      if (hasAttribute) {
822
0
        // yes, tmp has the corresponding css declarations in its style
823
0
        // attribute
824
0
        // let's remove them
825
0
        mCSSEditUtils->RemoveCSSEquivalentToHTMLStyle(aNode.AsElement(),
826
0
                                                      aProperty,
827
0
                                                      aAttribute,
828
0
                                                      nullptr,
829
0
                                                      false);
830
0
        // remove the node if it is a span or font, if its style attribute is
831
0
        // empty or absent, and if it does not have a class nor an id
832
0
        RemoveElementIfNoStyleOrIdOrClass(*aNode.AsElement());
833
0
      }
834
0
    }
835
0
  }
836
0
837
0
  // Or node is big or small and we are setting font size
838
0
  if (aChildrenOnly) {
839
0
    return NS_OK;
840
0
  }
841
0
  if (aProperty == nsGkAtoms::font &&
842
0
      (aNode.IsHTMLElement(nsGkAtoms::big) ||
843
0
       aNode.IsHTMLElement(nsGkAtoms::small)) &&
844
0
      aAttribute == nsGkAtoms::size) {
845
0
    // if we are setting font size, remove any nested bigs and smalls
846
0
    return RemoveContainerWithTransaction(*aNode.AsElement());
847
0
  }
848
0
  return NS_OK;
849
0
}
850
851
bool
852
HTMLEditor::IsOnlyAttribute(const Element* aElement,
853
                            nsAtom* aAttribute)
854
0
{
855
0
  MOZ_ASSERT(aElement);
856
0
857
0
  uint32_t attrCount = aElement->GetAttrCount();
858
0
  for (uint32_t i = 0; i < attrCount; ++i) {
859
0
    const nsAttrName* name = aElement->GetAttrNameAt(i);
860
0
    if (!name->NamespaceEquals(kNameSpaceID_None)) {
861
0
      return false;
862
0
    }
863
0
864
0
    // if it's the attribute we know about, or a special _moz attribute,
865
0
    // keep looking
866
0
    if (name->LocalName() != aAttribute) {
867
0
      nsAutoString attrString;
868
0
      name->LocalName()->ToString(attrString);
869
0
      if (!StringBeginsWith(attrString, NS_LITERAL_STRING("_moz"))) {
870
0
        return false;
871
0
      }
872
0
    }
873
0
  }
874
0
  // if we made it through all of them without finding a real attribute
875
0
  // other than aAttribute, then return true
876
0
  return true;
877
0
}
878
879
nsresult
880
HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor(nsRange& aRange)
881
0
{
882
0
  // We assume that <a> is not nested.
883
0
  nsCOMPtr<nsINode> startNode = aRange.GetStartContainer();
884
0
  int32_t startOffset = aRange.StartOffset();
885
0
  nsCOMPtr<nsINode> endNode = aRange.GetEndContainer();
886
0
  int32_t endOffset = aRange.EndOffset();
887
0
888
0
  nsCOMPtr<nsINode> parent = startNode;
889
0
890
0
  while (parent && !parent->IsHTMLElement(nsGkAtoms::body) &&
891
0
         !HTMLEditUtils::IsNamedAnchor(parent)) {
892
0
    parent = parent->GetParentNode();
893
0
  }
894
0
  NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
895
0
896
0
  if (HTMLEditUtils::IsNamedAnchor(parent)) {
897
0
    startNode = parent->GetParentNode();
898
0
    startOffset = startNode ? startNode->ComputeIndexOf(parent) : -1;
899
0
  }
900
0
901
0
  parent = endNode;
902
0
  while (parent && !parent->IsHTMLElement(nsGkAtoms::body) &&
903
0
         !HTMLEditUtils::IsNamedAnchor(parent)) {
904
0
    parent = parent->GetParentNode();
905
0
  }
906
0
  NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
907
0
908
0
  if (HTMLEditUtils::IsNamedAnchor(parent)) {
909
0
    endNode = parent->GetParentNode();
910
0
    endOffset = endNode ? endNode->ComputeIndexOf(parent) + 1 : 0;
911
0
  }
912
0
913
0
  nsresult rv = aRange.SetStartAndEnd(startNode, startOffset,
914
0
                                      endNode, endOffset);
915
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
916
0
    return rv;
917
0
  }
918
0
919
0
  return NS_OK;
920
0
}
921
922
nsresult
923
HTMLEditor::PromoteInlineRange(nsRange& aRange)
924
0
{
925
0
  nsCOMPtr<nsINode> startNode = aRange.GetStartContainer();
926
0
  int32_t startOffset = aRange.StartOffset();
927
0
  nsCOMPtr<nsINode> endNode = aRange.GetEndContainer();
928
0
  int32_t endOffset = aRange.EndOffset();
929
0
930
0
  while (startNode && !startNode->IsHTMLElement(nsGkAtoms::body) &&
931
0
         IsEditable(startNode) && IsAtFrontOfNode(*startNode, startOffset)) {
932
0
    nsCOMPtr<nsINode> parent = startNode->GetParentNode();
933
0
    NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
934
0
    startOffset = parent->ComputeIndexOf(startNode);
935
0
    startNode = parent;
936
0
  }
937
0
938
0
  while (endNode && !endNode->IsHTMLElement(nsGkAtoms::body) &&
939
0
         IsEditable(endNode) && IsAtEndOfNode(*endNode, endOffset)) {
940
0
    nsCOMPtr<nsINode> parent = endNode->GetParentNode();
941
0
    NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
942
0
    // We are AFTER this node
943
0
    endOffset = 1 + parent->ComputeIndexOf(endNode);
944
0
    endNode = parent;
945
0
  }
946
0
947
0
  nsresult rv = aRange.SetStartAndEnd(startNode, startOffset,
948
0
                                      endNode, endOffset);
949
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
950
0
    return rv;
951
0
  }
952
0
953
0
  return NS_OK;
954
0
}
955
956
bool
957
HTMLEditor::IsAtFrontOfNode(nsINode& aNode,
958
                            int32_t aOffset)
959
0
{
960
0
  if (!aOffset) {
961
0
    return true;
962
0
  }
963
0
964
0
  if (IsTextNode(&aNode)) {
965
0
    return false;
966
0
  }
967
0
968
0
  nsCOMPtr<nsIContent> firstNode = GetFirstEditableChild(aNode);
969
0
  NS_ENSURE_TRUE(firstNode, true);
970
0
  if (aNode.ComputeIndexOf(firstNode) < aOffset) {
971
0
    return false;
972
0
  }
973
0
  return true;
974
0
}
975
976
bool
977
HTMLEditor::IsAtEndOfNode(nsINode& aNode,
978
                          int32_t aOffset)
979
0
{
980
0
  if (aOffset == (int32_t)aNode.Length()) {
981
0
    return true;
982
0
  }
983
0
984
0
  if (IsTextNode(&aNode)) {
985
0
    return false;
986
0
  }
987
0
988
0
  nsCOMPtr<nsIContent> lastNode = GetLastEditableChild(aNode);
989
0
  NS_ENSURE_TRUE(lastNode, true);
990
0
  if (aNode.ComputeIndexOf(lastNode) < aOffset) {
991
0
    return true;
992
0
  }
993
0
  return false;
994
0
}
995
996
997
nsresult
998
HTMLEditor::GetInlinePropertyBase(nsAtom& aProperty,
999
                                  nsAtom* aAttribute,
1000
                                  const nsAString* aValue,
1001
                                  bool* aFirst,
1002
                                  bool* aAny,
1003
                                  bool* aAll,
1004
                                  nsAString* outValue)
1005
0
{
1006
0
  *aAny = false;
1007
0
  *aAll = true;
1008
0
  *aFirst = false;
1009
0
  bool first = true;
1010
0
1011
0
  RefPtr<Selection> selection = GetSelection();
1012
0
  NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1013
0
1014
0
  bool isCollapsed = selection->IsCollapsed();
1015
0
  RefPtr<nsRange> range = selection->GetRangeAt(0);
1016
0
  // XXX: Should be a while loop, to get each separate range
1017
0
  // XXX: ERROR_HANDLING can currentItem be null?
1018
0
  if (range) {
1019
0
    // For each range, set a flag
1020
0
    bool firstNodeInRange = true;
1021
0
1022
0
    if (isCollapsed) {
1023
0
      nsCOMPtr<nsINode> collapsedNode = range->GetStartContainer();
1024
0
      NS_ENSURE_TRUE(collapsedNode, NS_ERROR_FAILURE);
1025
0
      bool isSet, theSetting;
1026
0
      nsString tOutString;
1027
0
      if (aAttribute) {
1028
0
        mTypeInState->GetTypingState(isSet, theSetting, &aProperty, aAttribute,
1029
0
                                     &tOutString);
1030
0
        if (outValue) {
1031
0
          outValue->Assign(tOutString);
1032
0
        }
1033
0
      } else {
1034
0
        mTypeInState->GetTypingState(isSet, theSetting, &aProperty);
1035
0
      }
1036
0
      if (isSet) {
1037
0
        *aFirst = *aAny = *aAll = theSetting;
1038
0
        return NS_OK;
1039
0
      }
1040
0
1041
0
      if (CSSEditUtils::IsCSSEditableProperty(collapsedNode, &aProperty,
1042
0
                                              aAttribute)) {
1043
0
        if (aValue) {
1044
0
          tOutString.Assign(*aValue);
1045
0
        }
1046
0
        *aFirst = *aAny = *aAll =
1047
0
          CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(
1048
0
                          collapsedNode, &aProperty, aAttribute, tOutString,
1049
0
                          CSSEditUtils::eComputed);
1050
0
        if (outValue) {
1051
0
          outValue->Assign(tOutString);
1052
0
        }
1053
0
        return NS_OK;
1054
0
      }
1055
0
1056
0
      isSet = IsTextPropertySetByContent(collapsedNode, &aProperty,
1057
0
                                         aAttribute, aValue, outValue);
1058
0
      *aFirst = *aAny = *aAll = isSet;
1059
0
      return NS_OK;
1060
0
    }
1061
0
1062
0
    // Non-collapsed selection
1063
0
    nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
1064
0
1065
0
    nsAutoString firstValue, theValue;
1066
0
1067
0
    nsCOMPtr<nsINode> endNode = range->GetEndContainer();
1068
0
    int32_t endOffset = range->EndOffset();
1069
0
1070
0
    for (iter->Init(range); !iter->IsDone(); iter->Next()) {
1071
0
      if (!iter->GetCurrentNode()->IsContent()) {
1072
0
        continue;
1073
0
      }
1074
0
      nsCOMPtr<nsIContent> content = iter->GetCurrentNode()->AsContent();
1075
0
1076
0
      if (content->IsHTMLElement(nsGkAtoms::body)) {
1077
0
        break;
1078
0
      }
1079
0
1080
0
      // just ignore any non-editable nodes
1081
0
      if (content->GetAsText() && (!IsEditable(content) ||
1082
0
                                   IsEmptyTextNode(*content))) {
1083
0
        continue;
1084
0
      }
1085
0
      if (content->GetAsText()) {
1086
0
        if (!isCollapsed && first && firstNodeInRange) {
1087
0
          firstNodeInRange = false;
1088
0
          if (range->StartOffset() == content->Length()) {
1089
0
            continue;
1090
0
          }
1091
0
        } else if (content == endNode && !endOffset) {
1092
0
          continue;
1093
0
        }
1094
0
      } else if (content->IsElement()) {
1095
0
        // handle non-text leaf nodes here
1096
0
        continue;
1097
0
      }
1098
0
1099
0
      bool isSet = false;
1100
0
      if (first) {
1101
0
        if (CSSEditUtils::IsCSSEditableProperty(content, &aProperty,
1102
0
                                                aAttribute)) {
1103
0
          // The HTML styles defined by aProperty/aAttribute have a CSS
1104
0
          // equivalence in this implementation for node; let's check if it
1105
0
          // carries those CSS styles
1106
0
          if (aValue) {
1107
0
            firstValue.Assign(*aValue);
1108
0
          }
1109
0
          isSet = CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(
1110
0
                    content, &aProperty, aAttribute, firstValue,
1111
0
                    CSSEditUtils::eComputed);
1112
0
        } else {
1113
0
          isSet = IsTextPropertySetByContent(content, &aProperty, aAttribute,
1114
0
                                             aValue, &firstValue);
1115
0
        }
1116
0
        *aFirst = isSet;
1117
0
        first = false;
1118
0
        if (outValue) {
1119
0
          *outValue = firstValue;
1120
0
        }
1121
0
      } else {
1122
0
        if (CSSEditUtils::IsCSSEditableProperty(content, &aProperty,
1123
0
                                                aAttribute)) {
1124
0
          // The HTML styles defined by aProperty/aAttribute have a CSS
1125
0
          // equivalence in this implementation for node; let's check if it
1126
0
          // carries those CSS styles
1127
0
          if (aValue) {
1128
0
            theValue.Assign(*aValue);
1129
0
          }
1130
0
          isSet = CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(
1131
0
              content, &aProperty, aAttribute, theValue, CSSEditUtils::eComputed);
1132
0
        } else {
1133
0
          isSet = IsTextPropertySetByContent(content, &aProperty, aAttribute,
1134
0
                                             aValue, &theValue);
1135
0
        }
1136
0
        if (firstValue != theValue) {
1137
0
          *aAll = false;
1138
0
        }
1139
0
      }
1140
0
1141
0
      if (isSet) {
1142
0
        *aAny = true;
1143
0
      } else {
1144
0
        *aAll = false;
1145
0
      }
1146
0
    }
1147
0
  }
1148
0
  if (!*aAny) {
1149
0
    // make sure that if none of the selection is set, we don't report all is
1150
0
    // set
1151
0
    *aAll = false;
1152
0
  }
1153
0
  return NS_OK;
1154
0
}
1155
1156
NS_IMETHODIMP
1157
HTMLEditor::GetInlineProperty(const nsAString& aProperty,
1158
                              const nsAString& aAttribute,
1159
                              const nsAString& aValue,
1160
                              bool* aFirst,
1161
                              bool* aAny,
1162
                              bool* aAll)
1163
0
{
1164
0
  RefPtr<nsAtom> property = NS_Atomize(aProperty);
1165
0
  RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
1166
0
  return GetInlineProperty(property, attribute, aValue, aFirst, aAny, aAll);
1167
0
}
1168
1169
nsresult
1170
HTMLEditor::GetInlineProperty(nsAtom* aProperty,
1171
                              nsAtom* aAttribute,
1172
                              const nsAString& aValue,
1173
                              bool* aFirst,
1174
                              bool* aAny,
1175
                              bool* aAll)
1176
0
{
1177
0
  NS_ENSURE_TRUE(aProperty && aFirst && aAny && aAll, NS_ERROR_NULL_POINTER);
1178
0
  const nsAString *val = nullptr;
1179
0
  if (!aValue.IsEmpty())
1180
0
    val = &aValue;
1181
0
  return GetInlinePropertyBase(*aProperty, aAttribute, val, aFirst, aAny, aAll,
1182
0
                               nullptr);
1183
0
}
1184
1185
NS_IMETHODIMP
1186
HTMLEditor::GetInlinePropertyWithAttrValue(const nsAString& aProperty,
1187
                                           const nsAString& aAttribute,
1188
                                           const nsAString& aValue,
1189
                                           bool* aFirst,
1190
                                           bool* aAny,
1191
                                           bool* aAll,
1192
                                           nsAString& outValue)
1193
0
{
1194
0
  RefPtr<nsAtom> property = NS_Atomize(aProperty);
1195
0
  RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
1196
0
  return GetInlinePropertyWithAttrValue(property, attribute, aValue, aFirst,
1197
0
                                        aAny, aAll, outValue);
1198
0
}
1199
1200
nsresult
1201
HTMLEditor::GetInlinePropertyWithAttrValue(nsAtom* aProperty,
1202
                                           nsAtom* aAttribute,
1203
                                           const nsAString& aValue,
1204
                                           bool* aFirst,
1205
                                           bool* aAny,
1206
                                           bool* aAll,
1207
                                           nsAString& outValue)
1208
0
{
1209
0
  NS_ENSURE_TRUE(aProperty && aFirst && aAny && aAll, NS_ERROR_NULL_POINTER);
1210
0
  const nsAString *val = nullptr;
1211
0
  if (!aValue.IsEmpty())
1212
0
    val = &aValue;
1213
0
  return GetInlinePropertyBase(*aProperty, aAttribute, val, aFirst, aAny, aAll, &outValue);
1214
0
}
1215
1216
NS_IMETHODIMP
1217
HTMLEditor::RemoveAllInlineProperties()
1218
0
{
1219
0
  AutoPlaceholderBatch batchIt(this);
1220
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
1221
0
                                      *this,
1222
0
                                      EditSubAction::eRemoveAllTextProperties,
1223
0
                                      nsIEditor::eNext);
1224
0
1225
0
  nsresult rv = RemoveInlinePropertyInternal(nullptr, nullptr);
1226
0
  NS_ENSURE_SUCCESS(rv, rv);
1227
0
  return NS_OK;
1228
0
}
1229
1230
NS_IMETHODIMP
1231
HTMLEditor::RemoveInlineProperty(const nsAString& aProperty,
1232
                                 const nsAString& aAttribute)
1233
0
{
1234
0
  RefPtr<nsAtom> property = NS_Atomize(aProperty);
1235
0
  RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
1236
0
  return RemoveInlinePropertyInternal(property, attribute);
1237
0
}
1238
1239
nsresult
1240
HTMLEditor::RemoveInlinePropertyInternal(nsAtom* aProperty,
1241
                                         nsAtom* aAttribute)
1242
0
{
1243
0
  if (NS_WARN_IF(!mRules)) {
1244
0
    return NS_ERROR_NOT_INITIALIZED;
1245
0
  }
1246
0
1247
0
  CommitComposition();
1248
0
1249
0
  RefPtr<Selection> selection = GetSelection();
1250
0
  if (NS_WARN_IF(!selection)) {
1251
0
    return NS_ERROR_FAILURE;
1252
0
  }
1253
0
1254
0
  if (selection->IsCollapsed()) {
1255
0
    // Manipulating text attributes on a collapsed selection only sets state
1256
0
    // for the next text insertion
1257
0
1258
0
    // For links, aProperty uses "href", use "a" instead
1259
0
    if (aProperty == nsGkAtoms::href || aProperty == nsGkAtoms::name) {
1260
0
      aProperty = nsGkAtoms::a;
1261
0
    }
1262
0
1263
0
    if (aProperty) {
1264
0
      mTypeInState->ClearProp(aProperty, aAttribute);
1265
0
    } else {
1266
0
      mTypeInState->ClearAllProps();
1267
0
    }
1268
0
    return NS_OK;
1269
0
  }
1270
0
1271
0
  AutoPlaceholderBatch batchIt(this);
1272
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
1273
0
                                      *this, EditSubAction::eRemoveTextProperty,
1274
0
                                      nsIEditor::eNext);
1275
0
  AutoSelectionRestorer selectionRestorer(selection, this);
1276
0
  AutoTransactionsConserveSelection dontChangeMySelection(*this);
1277
0
1278
0
  bool cancel, handled;
1279
0
  EditSubActionInfo subActionInfo(EditSubAction::eRemoveTextProperty);
1280
0
  // Protect the edit rules object from dying
1281
0
  RefPtr<TextEditRules> rules(mRules);
1282
0
  nsresult rv =
1283
0
    rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
1284
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1285
0
    return rv;
1286
0
  }
1287
0
  if (!cancel && !handled) {
1288
0
    // Loop through the ranges in the selection
1289
0
    // Since ranges might be modified by SplitStyleAboveRange, we need hold
1290
0
    // current ranges
1291
0
    AutoRangeArray arrayOfRanges(selection);
1292
0
    for (auto& range : arrayOfRanges.mRanges) {
1293
0
      if (aProperty == nsGkAtoms::name) {
1294
0
        // Promote range if it starts or end in a named anchor and we want to
1295
0
        // remove named anchors
1296
0
        rv = PromoteRangeIfStartsOrEndsInNamedAnchor(*range);
1297
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
1298
0
          return rv;
1299
0
        }
1300
0
      } else {
1301
0
        // Adjust range to include any ancestors whose children are entirely
1302
0
        // selected
1303
0
        rv = PromoteInlineRange(*range);
1304
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
1305
0
          return rv;
1306
0
        }
1307
0
      }
1308
0
1309
0
      // Remove this style from ancestors of our range endpoints, splitting
1310
0
      // them as appropriate
1311
0
      rv = SplitStyleAboveRange(range, aProperty, aAttribute);
1312
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
1313
0
        return rv;
1314
0
      }
1315
0
1316
0
      // Check for easy case: both range endpoints in same text node
1317
0
      nsCOMPtr<nsINode> startNode = range->GetStartContainer();
1318
0
      nsCOMPtr<nsINode> endNode = range->GetEndContainer();
1319
0
      if (startNode && startNode == endNode && startNode->GetAsText()) {
1320
0
        // We're done with this range!
1321
0
        if (IsCSSEnabled() &&
1322
0
            CSSEditUtils::IsCSSEditableProperty(startNode, aProperty,
1323
0
                                                aAttribute)) {
1324
0
          // The HTML style defined by aProperty/aAttribute has a CSS
1325
0
          // equivalence in this implementation for startNode
1326
0
          if (CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(
1327
0
                              startNode, aProperty, aAttribute, EmptyString(),
1328
0
                              CSSEditUtils::eComputed)) {
1329
0
            // startNode's computed style indicates the CSS equivalence to the
1330
0
            // HTML style to remove is applied; but we found no element in the
1331
0
            // ancestors of startNode carrying specified styles; assume it
1332
0
            // comes from a rule and try to insert a span "inverting" the style
1333
0
            if (CSSEditUtils::IsCSSInvertible(*aProperty, aAttribute)) {
1334
0
              NS_NAMED_LITERAL_STRING(value, "-moz-editor-invert-value");
1335
0
              SetInlinePropertyOnTextNode(*startNode->GetAsText(),
1336
0
                                          range->StartOffset(),
1337
0
                                          range->EndOffset(), *aProperty,
1338
0
                                          aAttribute, value);
1339
0
            }
1340
0
          }
1341
0
        }
1342
0
      } else {
1343
0
        // Not the easy case.  Range not contained in single text node.
1344
0
        nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
1345
0
1346
0
        nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
1347
0
1348
0
        // Iterate range and build up array
1349
0
        for (iter->Init(range); !iter->IsDone(); iter->Next()) {
1350
0
          nsCOMPtr<nsINode> node = iter->GetCurrentNode();
1351
0
          if (NS_WARN_IF(!node)) {
1352
0
            return NS_ERROR_FAILURE;
1353
0
          }
1354
0
          if (IsEditable(node) && node->IsContent()) {
1355
0
            arrayOfNodes.AppendElement(*node->AsContent());
1356
0
          }
1357
0
        }
1358
0
1359
0
        // Loop through the list, remove the property on each node
1360
0
        for (auto& node : arrayOfNodes) {
1361
0
          rv = RemoveStyleInside(node, aProperty, aAttribute);
1362
0
          if (NS_WARN_IF(NS_FAILED(rv))) {
1363
0
            return rv;
1364
0
          }
1365
0
          if (IsCSSEnabled() &&
1366
0
              CSSEditUtils::IsCSSEditableProperty(node, aProperty,
1367
0
                                                  aAttribute) &&
1368
0
              CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(
1369
0
                              node, aProperty, aAttribute, EmptyString(),
1370
0
                              CSSEditUtils::eComputed) &&
1371
0
              // startNode's computed style indicates the CSS equivalence to
1372
0
              // the HTML style to remove is applied; but we found no element
1373
0
              // in the ancestors of startNode carrying specified styles;
1374
0
              // assume it comes from a rule and let's try to insert a span
1375
0
              // "inverting" the style
1376
0
              CSSEditUtils::IsCSSInvertible(*aProperty, aAttribute)) {
1377
0
            NS_NAMED_LITERAL_STRING(value, "-moz-editor-invert-value");
1378
0
            SetInlinePropertyOnNode(node, *aProperty, aAttribute, value);
1379
0
          }
1380
0
        }
1381
0
      }
1382
0
    }
1383
0
  }
1384
0
1385
0
  if (cancel) {
1386
0
    return NS_OK;
1387
0
  }
1388
0
1389
0
  rv = rules->DidDoAction(selection, subActionInfo, rv);
1390
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1391
0
    return rv;
1392
0
  }
1393
0
  return NS_OK;
1394
0
}
1395
1396
NS_IMETHODIMP
1397
HTMLEditor::IncreaseFontSize()
1398
0
{
1399
0
  return RelativeFontChange(FontSize::incr);
1400
0
}
1401
1402
NS_IMETHODIMP
1403
HTMLEditor::DecreaseFontSize()
1404
0
{
1405
0
  return RelativeFontChange(FontSize::decr);
1406
0
}
1407
1408
nsresult
1409
HTMLEditor::RelativeFontChange(FontSize aDir)
1410
0
{
1411
0
  CommitComposition();
1412
0
1413
0
  // Get the selection
1414
0
  RefPtr<Selection> selection = GetSelection();
1415
0
  NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1416
0
  // If selection is collapsed, set typing state
1417
0
  if (selection->IsCollapsed()) {
1418
0
    nsAtom& atom = aDir == FontSize::incr ? *nsGkAtoms::big :
1419
0
                                             *nsGkAtoms::small;
1420
0
1421
0
    // Let's see in what kind of element the selection is
1422
0
    NS_ENSURE_TRUE(selection->RangeCount() &&
1423
0
                   selection->GetRangeAt(0)->GetStartContainer(), NS_OK);
1424
0
    OwningNonNull<nsINode> selectedNode =
1425
0
      *selection->GetRangeAt(0)->GetStartContainer();
1426
0
    if (IsTextNode(selectedNode)) {
1427
0
      NS_ENSURE_TRUE(selectedNode->GetParentNode(), NS_OK);
1428
0
      selectedNode = *selectedNode->GetParentNode();
1429
0
    }
1430
0
    if (!CanContainTag(selectedNode, atom)) {
1431
0
      return NS_OK;
1432
0
    }
1433
0
1434
0
    // Manipulating text attributes on a collapsed selection only sets state
1435
0
    // for the next text insertion
1436
0
    mTypeInState->SetProp(&atom, nullptr, EmptyString());
1437
0
    return NS_OK;
1438
0
  }
1439
0
1440
0
  // Wrap with txn batching, rules sniffing, and selection preservation code
1441
0
  AutoPlaceholderBatch batchIt(this);
1442
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
1443
0
                                      *this, EditSubAction::eSetTextProperty,
1444
0
                                      nsIEditor::eNext);
1445
0
  AutoSelectionRestorer selectionRestorer(selection, this);
1446
0
  AutoTransactionsConserveSelection dontChangeMySelection(*this);
1447
0
1448
0
  // Loop through the ranges in the selection
1449
0
  AutoRangeArray arrayOfRanges(selection);
1450
0
  for (auto& range : arrayOfRanges.mRanges) {
1451
0
    // Adjust range to include any ancestors with entirely selected children
1452
0
    nsresult rv = PromoteInlineRange(*range);
1453
0
    NS_ENSURE_SUCCESS(rv, rv);
1454
0
1455
0
    // Check for easy case: both range endpoints in same text node
1456
0
    nsCOMPtr<nsINode> startNode = range->GetStartContainer();
1457
0
    nsCOMPtr<nsINode> endNode = range->GetEndContainer();
1458
0
    if (startNode == endNode && IsTextNode(startNode)) {
1459
0
      rv = RelativeFontChangeOnTextNode(aDir, *startNode->GetAsText(),
1460
0
                                        range->StartOffset(),
1461
0
                                        range->EndOffset());
1462
0
      NS_ENSURE_SUCCESS(rv, rv);
1463
0
    } else {
1464
0
      // Not the easy case.  Range not contained in single text node.  There
1465
0
      // are up to three phases here.  There are all the nodes reported by the
1466
0
      // subtree iterator to be processed.  And there are potentially a
1467
0
      // starting textnode and an ending textnode which are only partially
1468
0
      // contained by the range.
1469
0
1470
0
      // Let's handle the nodes reported by the iterator.  These nodes are
1471
0
      // entirely contained in the selection range.  We build up a list of them
1472
0
      // (since doing operations on the document during iteration would perturb
1473
0
      // the iterator).
1474
0
1475
0
      OwningNonNull<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
1476
0
1477
0
      // Iterate range and build up array
1478
0
      rv = iter->Init(range);
1479
0
      if (NS_SUCCEEDED(rv)) {
1480
0
        nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
1481
0
        for (; !iter->IsDone(); iter->Next()) {
1482
0
          NS_ENSURE_TRUE(iter->GetCurrentNode()->IsContent(), NS_ERROR_FAILURE);
1483
0
          OwningNonNull<nsIContent> node = *iter->GetCurrentNode()->AsContent();
1484
0
1485
0
          if (IsEditable(node)) {
1486
0
            arrayOfNodes.AppendElement(node);
1487
0
          }
1488
0
        }
1489
0
1490
0
        // Now that we have the list, do the font size change on each node
1491
0
        for (auto& node : arrayOfNodes) {
1492
0
          rv = RelativeFontChangeOnNode(aDir == FontSize::incr ? +1 : -1, node);
1493
0
          NS_ENSURE_SUCCESS(rv, rv);
1494
0
        }
1495
0
      }
1496
0
      // Now check the start and end parents of the range to see if they need
1497
0
      // to be separately handled (they do if they are text nodes, due to how
1498
0
      // the subtree iterator works - it will not have reported them).
1499
0
      if (IsTextNode(startNode) && IsEditable(startNode)) {
1500
0
        rv = RelativeFontChangeOnTextNode(aDir, *startNode->GetAsText(),
1501
0
                                          range->StartOffset(),
1502
0
                                          startNode->Length());
1503
0
        NS_ENSURE_SUCCESS(rv, rv);
1504
0
      }
1505
0
      if (IsTextNode(endNode) && IsEditable(endNode)) {
1506
0
        rv = RelativeFontChangeOnTextNode(aDir, *endNode->GetAsText(), 0,
1507
0
                                          range->EndOffset());
1508
0
        NS_ENSURE_SUCCESS(rv, rv);
1509
0
      }
1510
0
    }
1511
0
  }
1512
0
1513
0
  return NS_OK;
1514
0
}
1515
1516
nsresult
1517
HTMLEditor::RelativeFontChangeOnTextNode(FontSize aDir,
1518
                                         Text& aTextNode,
1519
                                         int32_t aStartOffset,
1520
                                         int32_t aEndOffset)
1521
0
{
1522
0
  // Don't need to do anything if no characters actually selected
1523
0
  if (aStartOffset == aEndOffset) {
1524
0
    return NS_OK;
1525
0
  }
1526
0
1527
0
  if (!aTextNode.GetParentNode() ||
1528
0
      !CanContainTag(*aTextNode.GetParentNode(), *nsGkAtoms::big)) {
1529
0
    return NS_OK;
1530
0
  }
1531
0
1532
0
  // -1 is a magic value meaning to the end of node
1533
0
  if (aEndOffset == -1) {
1534
0
    aEndOffset = aTextNode.Length();
1535
0
  }
1536
0
1537
0
  // Make the range an independent node.
1538
0
  nsCOMPtr<nsIContent> textNodeForTheRange = &aTextNode;
1539
0
1540
0
  // Split at the end of the range.
1541
0
  EditorRawDOMPoint atEnd(textNodeForTheRange, aEndOffset);
1542
0
  if (!atEnd.IsEndOfContainer()) {
1543
0
    // We need to split off back of text node
1544
0
    ErrorResult error;
1545
0
    textNodeForTheRange = SplitNodeWithTransaction(atEnd, error);
1546
0
    if (NS_WARN_IF(error.Failed())) {
1547
0
      return error.StealNSResult();
1548
0
    }
1549
0
  }
1550
0
1551
0
  // Split at the start of the range.
1552
0
  EditorRawDOMPoint atStart(textNodeForTheRange, aStartOffset);
1553
0
  if (!atStart.IsStartOfContainer()) {
1554
0
    // We need to split off front of text node
1555
0
    ErrorResult error;
1556
0
    nsCOMPtr<nsIContent> newLeftNode = SplitNodeWithTransaction(atStart, error);
1557
0
    if (NS_WARN_IF(error.Failed())) {
1558
0
      return error.StealNSResult();
1559
0
    }
1560
0
    Unused << newLeftNode;
1561
0
  }
1562
0
1563
0
  // Look for siblings that are correct type of node
1564
0
  nsAtom* nodeType = aDir == FontSize::incr ? nsGkAtoms::big
1565
0
                                             : nsGkAtoms::small;
1566
0
  nsCOMPtr<nsIContent> sibling = GetPriorHTMLSibling(textNodeForTheRange);
1567
0
  if (sibling && sibling->IsHTMLElement(nodeType)) {
1568
0
    // Previous sib is already right kind of inline node; slide this over
1569
0
    nsresult rv = MoveNodeToEndWithTransaction(*textNodeForTheRange, *sibling);
1570
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1571
0
      return rv;
1572
0
    }
1573
0
    return NS_OK;
1574
0
  }
1575
0
  sibling = GetNextHTMLSibling(textNodeForTheRange);
1576
0
  if (sibling && sibling->IsHTMLElement(nodeType)) {
1577
0
    // Following sib is already right kind of inline node; slide this over
1578
0
    nsresult rv = MoveNodeWithTransaction(*textNodeForTheRange,
1579
0
                                          EditorRawDOMPoint(sibling, 0));
1580
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1581
0
      return rv;
1582
0
    }
1583
0
    return NS_OK;
1584
0
  }
1585
0
1586
0
  // Else reparent the node inside font node with appropriate relative size
1587
0
  RefPtr<Element> newElement =
1588
0
    InsertContainerWithTransaction(*textNodeForTheRange, *nodeType);
1589
0
  if (NS_WARN_IF(!newElement)) {
1590
0
    return NS_ERROR_FAILURE;
1591
0
  }
1592
0
  return NS_OK;
1593
0
}
1594
1595
nsresult
1596
HTMLEditor::RelativeFontChangeHelper(int32_t aSizeChange,
1597
                                     nsINode* aNode)
1598
0
{
1599
0
  MOZ_ASSERT(aNode);
1600
0
1601
0
  /*  This routine looks for all the font nodes in the tree rooted by aNode,
1602
0
      including aNode itself, looking for font nodes that have the size attr
1603
0
      set.  Any such nodes need to have big or small put inside them, since
1604
0
      they override any big/small that are above them.
1605
0
  */
1606
0
1607
0
  // Can only change font size by + or - 1
1608
0
  if (aSizeChange != 1 && aSizeChange != -1) {
1609
0
    return NS_ERROR_ILLEGAL_VALUE;
1610
0
  }
1611
0
1612
0
  // If this is a font node with size, put big/small inside it.
1613
0
  if (aNode->IsHTMLElement(nsGkAtoms::font) &&
1614
0
      aNode->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::size)) {
1615
0
    // Cycle through children and adjust relative font size.
1616
0
    AutoTArray<nsCOMPtr<nsIContent>, 10> childList;
1617
0
    for (nsIContent* child = aNode->GetFirstChild();
1618
0
         child; child = child->GetNextSibling()) {
1619
0
      childList.AppendElement(child);
1620
0
    }
1621
0
1622
0
    for (const auto& child: childList) {
1623
0
      nsresult rv = RelativeFontChangeOnNode(aSizeChange, child);
1624
0
      NS_ENSURE_SUCCESS(rv, rv);
1625
0
    }
1626
0
1627
0
    // RelativeFontChangeOnNode already calls us recursively,
1628
0
    // so we don't need to check our children again.
1629
0
    return NS_OK;
1630
0
  }
1631
0
1632
0
  // Otherwise cycle through the children.
1633
0
  AutoTArray<nsCOMPtr<nsIContent>, 10> childList;
1634
0
  for (nsIContent* child = aNode->GetFirstChild();
1635
0
       child; child = child->GetNextSibling()) {
1636
0
    childList.AppendElement(child);
1637
0
  }
1638
0
1639
0
  for (const auto& child: childList) {
1640
0
    nsresult rv = RelativeFontChangeHelper(aSizeChange, child);
1641
0
    NS_ENSURE_SUCCESS(rv, rv);
1642
0
  }
1643
0
1644
0
  return NS_OK;
1645
0
}
1646
1647
nsresult
1648
HTMLEditor::RelativeFontChangeOnNode(int32_t aSizeChange,
1649
                                     nsIContent* aNode)
1650
0
{
1651
0
  MOZ_ASSERT(aNode);
1652
0
  // Can only change font size by + or - 1
1653
0
  if (aSizeChange != 1 && aSizeChange != -1) {
1654
0
    return NS_ERROR_ILLEGAL_VALUE;
1655
0
  }
1656
0
1657
0
  nsAtom* atom;
1658
0
  if (aSizeChange == 1) {
1659
0
    atom = nsGkAtoms::big;
1660
0
  } else {
1661
0
    atom = nsGkAtoms::small;
1662
0
  }
1663
0
1664
0
  // Is it the opposite of what we want?
1665
0
  if ((aSizeChange == 1 && aNode->IsHTMLElement(nsGkAtoms::small)) ||
1666
0
       (aSizeChange == -1 && aNode->IsHTMLElement(nsGkAtoms::big))) {
1667
0
    // first populate any nested font tags that have the size attr set
1668
0
    nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode);
1669
0
    NS_ENSURE_SUCCESS(rv, rv);
1670
0
    // in that case, just remove this node and pull up the children
1671
0
    return RemoveContainerWithTransaction(*aNode->AsElement());
1672
0
  }
1673
0
1674
0
  // can it be put inside a "big" or "small"?
1675
0
  if (TagCanContain(*atom, *aNode)) {
1676
0
    // first populate any nested font tags that have the size attr set
1677
0
    nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode);
1678
0
    NS_ENSURE_SUCCESS(rv, rv);
1679
0
1680
0
    // ok, chuck it in.
1681
0
    // first look at siblings of aNode for matching bigs or smalls.
1682
0
    // if we find one, move aNode into it.
1683
0
    nsIContent* sibling = GetPriorHTMLSibling(aNode);
1684
0
    if (sibling && sibling->IsHTMLElement(atom)) {
1685
0
      // previous sib is already right kind of inline node; slide this over into it
1686
0
      return MoveNodeToEndWithTransaction(*aNode, *sibling);
1687
0
    }
1688
0
1689
0
    sibling = GetNextHTMLSibling(aNode);
1690
0
    if (sibling && sibling->IsHTMLElement(atom)) {
1691
0
      // following sib is already right kind of inline node; slide this over into it
1692
0
      return MoveNodeWithTransaction(*aNode, EditorRawDOMPoint(sibling, 0));
1693
0
    }
1694
0
1695
0
    // else insert it above aNode
1696
0
    RefPtr<Element> newElement = InsertContainerWithTransaction(*aNode, *atom);
1697
0
    if (NS_WARN_IF(!newElement)) {
1698
0
      return NS_ERROR_FAILURE;
1699
0
    }
1700
0
    return NS_OK;
1701
0
  }
1702
0
1703
0
  // none of the above?  then cycle through the children.
1704
0
  // MOOSE: we should group the children together if possible
1705
0
  // into a single "big" or "small".  For the moment they are
1706
0
  // each getting their own.
1707
0
  AutoTArray<nsCOMPtr<nsIContent>, 10> childList;
1708
0
  for (nsIContent* child = aNode->GetFirstChild();
1709
0
       child; child = child->GetNextSibling()) {
1710
0
    childList.AppendElement(child);
1711
0
  }
1712
0
1713
0
  for (const auto& child: childList) {
1714
0
    nsresult rv = RelativeFontChangeOnNode(aSizeChange, child);
1715
0
    NS_ENSURE_SUCCESS(rv, rv);
1716
0
  }
1717
0
1718
0
  return NS_OK;
1719
0
}
1720
1721
NS_IMETHODIMP
1722
HTMLEditor::GetFontFaceState(bool* aMixed,
1723
                             nsAString& outFace)
1724
0
{
1725
0
  NS_ENSURE_TRUE(aMixed, NS_ERROR_FAILURE);
1726
0
  *aMixed = true;
1727
0
  outFace.Truncate();
1728
0
1729
0
  bool first, any, all;
1730
0
1731
0
  nsresult rv =
1732
0
    GetInlinePropertyBase(*nsGkAtoms::font, nsGkAtoms::face, nullptr, &first,
1733
0
                          &any, &all, &outFace);
1734
0
  NS_ENSURE_SUCCESS(rv, rv);
1735
0
  if (any && !all) {
1736
0
    return NS_OK; // mixed
1737
0
  }
1738
0
  if (all) {
1739
0
    *aMixed = false;
1740
0
    return NS_OK;
1741
0
  }
1742
0
1743
0
  // if there is no font face, check for tt
1744
0
  rv = GetInlinePropertyBase(*nsGkAtoms::tt, nullptr, nullptr, &first, &any,
1745
0
                             &all,nullptr);
1746
0
  NS_ENSURE_SUCCESS(rv, rv);
1747
0
  if (any && !all) {
1748
0
    return rv; // mixed
1749
0
  }
1750
0
  if (all) {
1751
0
    *aMixed = false;
1752
0
    outFace.AssignLiteral("tt");
1753
0
  }
1754
0
1755
0
  if (!any) {
1756
0
    // there was no font face attrs of any kind.  We are in normal font.
1757
0
    outFace.Truncate();
1758
0
    *aMixed = false;
1759
0
  }
1760
0
  return NS_OK;
1761
0
}
1762
1763
nsresult
1764
HTMLEditor::GetFontColorState(bool* aMixed,
1765
                              nsAString& aOutColor)
1766
0
{
1767
0
  if (NS_WARN_IF(!aMixed)) {
1768
0
    return NS_ERROR_INVALID_ARG;
1769
0
  }
1770
0
1771
0
  *aMixed = true;
1772
0
  aOutColor.Truncate();
1773
0
1774
0
  bool first, any, all;
1775
0
  nsresult rv =
1776
0
    GetInlinePropertyBase(*nsGkAtoms::font, nsGkAtoms::color, nullptr,
1777
0
                          &first, &any, &all, &aOutColor);
1778
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1779
0
    return rv;
1780
0
  }
1781
0
1782
0
  if (any && !all) {
1783
0
    return NS_OK; // mixed
1784
0
  }
1785
0
  if (all) {
1786
0
    *aMixed = false;
1787
0
    return NS_OK;
1788
0
  }
1789
0
1790
0
  if (!any) {
1791
0
    // there was no font color attrs of any kind..
1792
0
    aOutColor.Truncate();
1793
0
    *aMixed = false;
1794
0
  }
1795
0
  return NS_OK;
1796
0
}
1797
1798
// the return value is true only if the instance of the HTML editor we created
1799
// can handle CSS styles (for instance, Composer can, Messenger can't) and if
1800
// the CSS preference is checked
1801
NS_IMETHODIMP
1802
HTMLEditor::GetIsCSSEnabled(bool* aIsCSSEnabled)
1803
0
{
1804
0
  *aIsCSSEnabled = IsCSSEnabled();
1805
0
  return NS_OK;
1806
0
}
1807
1808
static bool
1809
HasNonEmptyAttribute(Element* aElement,
1810
                     nsAtom* aName)
1811
0
{
1812
0
  MOZ_ASSERT(aElement);
1813
0
1814
0
  nsAutoString value;
1815
0
  return aElement->GetAttr(kNameSpaceID_None, aName, value) && !value.IsEmpty();
1816
0
}
1817
1818
bool
1819
HTMLEditor::HasStyleOrIdOrClass(Element* aElement)
1820
0
{
1821
0
  MOZ_ASSERT(aElement);
1822
0
1823
0
  // remove the node if its style attribute is empty or absent,
1824
0
  // and if it does not have a class nor an id
1825
0
  return HasNonEmptyAttribute(aElement, nsGkAtoms::style) ||
1826
0
         HasNonEmptyAttribute(aElement, nsGkAtoms::_class) ||
1827
0
         HasNonEmptyAttribute(aElement, nsGkAtoms::id);
1828
0
}
1829
1830
nsresult
1831
HTMLEditor::RemoveElementIfNoStyleOrIdOrClass(Element& aElement)
1832
0
{
1833
0
  // early way out if node is not the right kind of element
1834
0
  if ((!aElement.IsHTMLElement(nsGkAtoms::span) &&
1835
0
       !aElement.IsHTMLElement(nsGkAtoms::font)) ||
1836
0
      HasStyleOrIdOrClass(&aElement)) {
1837
0
    return NS_OK;
1838
0
  }
1839
0
1840
0
  return RemoveContainerWithTransaction(aElement);
1841
0
}
1842
1843
} // namespace mozilla