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