/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 |