Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "mozilla/CSSEditUtils.h"
7 :
8 : #include "mozilla/Assertions.h"
9 : #include "mozilla/ChangeStyleTransaction.h"
10 : #include "mozilla/HTMLEditor.h"
11 : #include "mozilla/Preferences.h"
12 : #include "mozilla/DeclarationBlockInlines.h"
13 : #include "mozilla/css/StyleRule.h"
14 : #include "mozilla/dom/Element.h"
15 : #include "mozilla/mozalloc.h"
16 : #include "nsAString.h"
17 : #include "nsCOMPtr.h"
18 : #include "nsColor.h"
19 : #include "nsComputedDOMStyle.h"
20 : #include "nsDebug.h"
21 : #include "nsDependentSubstring.h"
22 : #include "nsError.h"
23 : #include "nsGkAtoms.h"
24 : #include "nsIAtom.h"
25 : #include "nsIContent.h"
26 : #include "nsIDOMCSSStyleDeclaration.h"
27 : #include "nsIDOMElement.h"
28 : #include "nsIDOMNode.h"
29 : #include "nsIDOMWindow.h"
30 : #include "nsIDocument.h"
31 : #include "nsIEditor.h"
32 : #include "nsINode.h"
33 : #include "nsISupportsImpl.h"
34 : #include "nsISupportsUtils.h"
35 : #include "nsLiteralString.h"
36 : #include "nsPIDOMWindow.h"
37 : #include "nsReadableUtils.h"
38 : #include "nsString.h"
39 : #include "nsStringFwd.h"
40 : #include "nsStringIterator.h"
41 : #include "nsStyledElement.h"
42 : #include "nsSubstringTuple.h"
43 : #include "nsUnicharUtils.h"
44 :
45 : namespace mozilla {
46 :
47 : using namespace dom;
48 :
49 : static
50 0 : void ProcessBValue(const nsAString* aInputString,
51 : nsAString& aOutputString,
52 : const char* aDefaultValueString,
53 : const char* aPrependString,
54 : const char* aAppendString)
55 : {
56 0 : if (aInputString && aInputString->EqualsLiteral("-moz-editor-invert-value")) {
57 0 : aOutputString.AssignLiteral("normal");
58 : }
59 : else {
60 0 : aOutputString.AssignLiteral("bold");
61 : }
62 0 : }
63 :
64 : static
65 0 : void ProcessDefaultValue(const nsAString* aInputString,
66 : nsAString& aOutputString,
67 : const char* aDefaultValueString,
68 : const char* aPrependString,
69 : const char* aAppendString)
70 : {
71 0 : CopyASCIItoUTF16(aDefaultValueString, aOutputString);
72 0 : }
73 :
74 : static
75 0 : void ProcessSameValue(const nsAString* aInputString,
76 : nsAString & aOutputString,
77 : const char* aDefaultValueString,
78 : const char* aPrependString,
79 : const char* aAppendString)
80 : {
81 0 : if (aInputString) {
82 0 : aOutputString.Assign(*aInputString);
83 : }
84 : else
85 0 : aOutputString.Truncate();
86 0 : }
87 :
88 : static
89 0 : void ProcessExtendedValue(const nsAString* aInputString,
90 : nsAString& aOutputString,
91 : const char* aDefaultValueString,
92 : const char* aPrependString,
93 : const char* aAppendString)
94 : {
95 0 : aOutputString.Truncate();
96 0 : if (aInputString) {
97 0 : if (aPrependString) {
98 0 : AppendASCIItoUTF16(aPrependString, aOutputString);
99 : }
100 0 : aOutputString.Append(*aInputString);
101 0 : if (aAppendString) {
102 0 : AppendASCIItoUTF16(aAppendString, aOutputString);
103 : }
104 : }
105 0 : }
106 :
107 : static
108 0 : void ProcessLengthValue(const nsAString* aInputString,
109 : nsAString& aOutputString,
110 : const char* aDefaultValueString,
111 : const char* aPrependString,
112 : const char* aAppendString)
113 : {
114 0 : aOutputString.Truncate();
115 0 : if (aInputString) {
116 0 : aOutputString.Append(*aInputString);
117 0 : if (-1 == aOutputString.FindChar(char16_t('%'))) {
118 0 : aOutputString.AppendLiteral("px");
119 : }
120 : }
121 0 : }
122 :
123 : static
124 0 : void ProcessListStyleTypeValue(const nsAString* aInputString,
125 : nsAString& aOutputString,
126 : const char* aDefaultValueString,
127 : const char* aPrependString,
128 : const char* aAppendString)
129 : {
130 0 : aOutputString.Truncate();
131 0 : if (aInputString) {
132 0 : if (aInputString->EqualsLiteral("1")) {
133 0 : aOutputString.AppendLiteral("decimal");
134 : }
135 0 : else if (aInputString->EqualsLiteral("a")) {
136 0 : aOutputString.AppendLiteral("lower-alpha");
137 : }
138 0 : else if (aInputString->EqualsLiteral("A")) {
139 0 : aOutputString.AppendLiteral("upper-alpha");
140 : }
141 0 : else if (aInputString->EqualsLiteral("i")) {
142 0 : aOutputString.AppendLiteral("lower-roman");
143 : }
144 0 : else if (aInputString->EqualsLiteral("I")) {
145 0 : aOutputString.AppendLiteral("upper-roman");
146 : }
147 0 : else if (aInputString->EqualsLiteral("square")
148 0 : || aInputString->EqualsLiteral("circle")
149 0 : || aInputString->EqualsLiteral("disc")) {
150 0 : aOutputString.Append(*aInputString);
151 : }
152 : }
153 0 : }
154 :
155 : static
156 0 : void ProcessMarginLeftValue(const nsAString* aInputString,
157 : nsAString& aOutputString,
158 : const char* aDefaultValueString,
159 : const char* aPrependString,
160 : const char* aAppendString)
161 : {
162 0 : aOutputString.Truncate();
163 0 : if (aInputString) {
164 0 : if (aInputString->EqualsLiteral("center") ||
165 0 : aInputString->EqualsLiteral("-moz-center")) {
166 0 : aOutputString.AppendLiteral("auto");
167 : }
168 0 : else if (aInputString->EqualsLiteral("right") ||
169 0 : aInputString->EqualsLiteral("-moz-right")) {
170 0 : aOutputString.AppendLiteral("auto");
171 : }
172 : else {
173 0 : aOutputString.AppendLiteral("0px");
174 : }
175 : }
176 0 : }
177 :
178 : static
179 0 : void ProcessMarginRightValue(const nsAString* aInputString,
180 : nsAString& aOutputString,
181 : const char* aDefaultValueString,
182 : const char* aPrependString,
183 : const char* aAppendString)
184 : {
185 0 : aOutputString.Truncate();
186 0 : if (aInputString) {
187 0 : if (aInputString->EqualsLiteral("center") ||
188 0 : aInputString->EqualsLiteral("-moz-center")) {
189 0 : aOutputString.AppendLiteral("auto");
190 : }
191 0 : else if (aInputString->EqualsLiteral("left") ||
192 0 : aInputString->EqualsLiteral("-moz-left")) {
193 0 : aOutputString.AppendLiteral("auto");
194 : }
195 : else {
196 0 : aOutputString.AppendLiteral("0px");
197 : }
198 : }
199 0 : }
200 :
201 : const CSSEditUtils::CSSEquivTable boldEquivTable[] = {
202 : { CSSEditUtils::eCSSEditableProperty_font_weight, ProcessBValue, nullptr, nullptr, nullptr, true, false },
203 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
204 : };
205 :
206 : const CSSEditUtils::CSSEquivTable italicEquivTable[] = {
207 : { CSSEditUtils::eCSSEditableProperty_font_style, ProcessDefaultValue, "italic", nullptr, nullptr, true, false },
208 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
209 : };
210 :
211 : const CSSEditUtils::CSSEquivTable underlineEquivTable[] = {
212 : { CSSEditUtils::eCSSEditableProperty_text_decoration, ProcessDefaultValue, "underline", nullptr, nullptr, true, false },
213 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
214 : };
215 :
216 : const CSSEditUtils::CSSEquivTable strikeEquivTable[] = {
217 : { CSSEditUtils::eCSSEditableProperty_text_decoration, ProcessDefaultValue, "line-through", nullptr, nullptr, true, false },
218 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
219 : };
220 :
221 : const CSSEditUtils::CSSEquivTable ttEquivTable[] = {
222 : { CSSEditUtils::eCSSEditableProperty_font_family, ProcessDefaultValue, "monospace", nullptr, nullptr, true, false },
223 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
224 : };
225 :
226 : const CSSEditUtils::CSSEquivTable fontColorEquivTable[] = {
227 : { CSSEditUtils::eCSSEditableProperty_color, ProcessSameValue, nullptr, nullptr, nullptr, true, false },
228 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
229 : };
230 :
231 : const CSSEditUtils::CSSEquivTable fontFaceEquivTable[] = {
232 : { CSSEditUtils::eCSSEditableProperty_font_family, ProcessSameValue, nullptr, nullptr, nullptr, true, false },
233 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
234 : };
235 :
236 : const CSSEditUtils::CSSEquivTable bgcolorEquivTable[] = {
237 : { CSSEditUtils::eCSSEditableProperty_background_color, ProcessSameValue, nullptr, nullptr, nullptr, true, false },
238 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
239 : };
240 :
241 : const CSSEditUtils::CSSEquivTable backgroundImageEquivTable[] = {
242 : { CSSEditUtils::eCSSEditableProperty_background_image, ProcessExtendedValue, nullptr, "url(", ")", true, true },
243 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
244 : };
245 :
246 : const CSSEditUtils::CSSEquivTable textColorEquivTable[] = {
247 : { CSSEditUtils::eCSSEditableProperty_color, ProcessSameValue, nullptr, nullptr, nullptr, true, false },
248 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
249 : };
250 :
251 : const CSSEditUtils::CSSEquivTable borderEquivTable[] = {
252 : { CSSEditUtils::eCSSEditableProperty_border, ProcessExtendedValue, nullptr, nullptr, "px solid", true, false },
253 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
254 : };
255 :
256 : const CSSEditUtils::CSSEquivTable textAlignEquivTable[] = {
257 : { CSSEditUtils::eCSSEditableProperty_text_align, ProcessSameValue, nullptr, nullptr, nullptr, true, false },
258 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
259 : };
260 :
261 : const CSSEditUtils::CSSEquivTable captionAlignEquivTable[] = {
262 : { CSSEditUtils::eCSSEditableProperty_caption_side, ProcessSameValue, nullptr, nullptr, nullptr, true, false },
263 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
264 : };
265 :
266 : const CSSEditUtils::CSSEquivTable verticalAlignEquivTable[] = {
267 : { CSSEditUtils::eCSSEditableProperty_vertical_align, ProcessSameValue, nullptr, nullptr, nullptr, true, false },
268 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
269 : };
270 :
271 : const CSSEditUtils::CSSEquivTable nowrapEquivTable[] = {
272 : { CSSEditUtils::eCSSEditableProperty_whitespace, ProcessDefaultValue, "nowrap", nullptr, nullptr, true, false },
273 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
274 : };
275 :
276 : const CSSEditUtils::CSSEquivTable widthEquivTable[] = {
277 : { CSSEditUtils::eCSSEditableProperty_width, ProcessLengthValue, nullptr, nullptr, nullptr, true, false },
278 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
279 : };
280 :
281 : const CSSEditUtils::CSSEquivTable heightEquivTable[] = {
282 : { CSSEditUtils::eCSSEditableProperty_height, ProcessLengthValue, nullptr, nullptr, nullptr, true, false },
283 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
284 : };
285 :
286 : const CSSEditUtils::CSSEquivTable listStyleTypeEquivTable[] = {
287 : { CSSEditUtils::eCSSEditableProperty_list_style_type, ProcessListStyleTypeValue, nullptr, nullptr, nullptr, true, true },
288 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
289 : };
290 :
291 : const CSSEditUtils::CSSEquivTable tableAlignEquivTable[] = {
292 : { CSSEditUtils::eCSSEditableProperty_text_align, ProcessDefaultValue, "left", nullptr, nullptr, false, false },
293 : { CSSEditUtils::eCSSEditableProperty_margin_left, ProcessMarginLeftValue, nullptr, nullptr, nullptr, true, false },
294 : { CSSEditUtils::eCSSEditableProperty_margin_right, ProcessMarginRightValue, nullptr, nullptr, nullptr, true, false },
295 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
296 : };
297 :
298 : const CSSEditUtils::CSSEquivTable hrAlignEquivTable[] = {
299 : { CSSEditUtils::eCSSEditableProperty_margin_left, ProcessMarginLeftValue, nullptr, nullptr, nullptr, true, false },
300 : { CSSEditUtils::eCSSEditableProperty_margin_right, ProcessMarginRightValue, nullptr, nullptr, nullptr, true, false },
301 : { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
302 : };
303 :
304 0 : CSSEditUtils::CSSEditUtils(HTMLEditor* aHTMLEditor)
305 : : mHTMLEditor(aHTMLEditor)
306 0 : , mIsCSSPrefChecked(true)
307 : {
308 : // let's retrieve the value of the "CSS editing" pref
309 0 : mIsCSSPrefChecked = Preferences::GetBool("editor.use_css", mIsCSSPrefChecked);
310 0 : }
311 :
312 0 : CSSEditUtils::~CSSEditUtils()
313 : {
314 0 : }
315 :
316 : // Answers true if we have some CSS equivalence for the HTML style defined
317 : // by aProperty and/or aAttribute for the node aNode
318 : bool
319 0 : CSSEditUtils::IsCSSEditableProperty(nsINode* aNode,
320 : nsIAtom* aProperty,
321 : const nsAString* aAttribute)
322 : {
323 0 : nsCOMPtr<nsIAtom> attribute = aAttribute ? NS_Atomize(*aAttribute) : nullptr;
324 0 : return IsCSSEditableProperty(aNode, aProperty, attribute);
325 : }
326 :
327 : bool
328 0 : CSSEditUtils::IsCSSEditableProperty(nsINode* aNode,
329 : nsIAtom* aProperty,
330 : nsIAtom* aAttribute)
331 : {
332 0 : MOZ_ASSERT(aNode);
333 :
334 0 : nsINode* node = aNode;
335 : // we need an element node here
336 0 : if (node->NodeType() == nsIDOMNode::TEXT_NODE) {
337 0 : node = node->GetParentNode();
338 0 : NS_ENSURE_TRUE(node, false);
339 : }
340 :
341 : // html inline styles B I TT U STRIKE and COLOR/FACE on FONT
342 0 : if (nsGkAtoms::b == aProperty ||
343 0 : nsGkAtoms::i == aProperty ||
344 0 : nsGkAtoms::tt == aProperty ||
345 0 : nsGkAtoms::u == aProperty ||
346 0 : nsGkAtoms::strike == aProperty ||
347 0 : (nsGkAtoms::font == aProperty && aAttribute &&
348 0 : (aAttribute == nsGkAtoms::color || aAttribute == nsGkAtoms::face))) {
349 0 : return true;
350 : }
351 :
352 : // ALIGN attribute on elements supporting it
353 0 : if (aAttribute == nsGkAtoms::align &&
354 0 : node->IsAnyOfHTMLElements(nsGkAtoms::div,
355 : nsGkAtoms::p,
356 : nsGkAtoms::h1,
357 : nsGkAtoms::h2,
358 : nsGkAtoms::h3,
359 : nsGkAtoms::h4,
360 : nsGkAtoms::h5,
361 : nsGkAtoms::h6,
362 : nsGkAtoms::td,
363 : nsGkAtoms::th,
364 : nsGkAtoms::table,
365 : nsGkAtoms::hr,
366 : // For the above, why not use
367 : // HTMLEditUtils::SupportsAlignAttr?
368 : // It also checks for tbody, tfoot, thead.
369 : // Let's add the following elements here even
370 : // if "align" has a different meaning for them
371 : nsGkAtoms::legend,
372 : nsGkAtoms::caption)) {
373 0 : return true;
374 : }
375 :
376 0 : if (aAttribute == nsGkAtoms::valign &&
377 0 : node->IsAnyOfHTMLElements(nsGkAtoms::col,
378 : nsGkAtoms::colgroup,
379 : nsGkAtoms::tbody,
380 : nsGkAtoms::td,
381 : nsGkAtoms::th,
382 : nsGkAtoms::tfoot,
383 : nsGkAtoms::thead,
384 : nsGkAtoms::tr)) {
385 0 : return true;
386 : }
387 :
388 : // attributes TEXT, BACKGROUND and BGCOLOR on BODY
389 0 : if (node->IsHTMLElement(nsGkAtoms::body) &&
390 0 : (aAttribute == nsGkAtoms::text || aAttribute == nsGkAtoms::background ||
391 0 : aAttribute == nsGkAtoms::bgcolor)) {
392 0 : return true;
393 : }
394 :
395 : // attribute BGCOLOR on other elements
396 0 : if (aAttribute == nsGkAtoms::bgcolor) {
397 0 : return true;
398 : }
399 :
400 : // attributes HEIGHT, WIDTH and NOWRAP on TD and TH
401 0 : if (node->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th) &&
402 0 : (aAttribute == nsGkAtoms::height || aAttribute == nsGkAtoms::width ||
403 0 : aAttribute == nsGkAtoms::nowrap)) {
404 0 : return true;
405 : }
406 :
407 : // attributes HEIGHT and WIDTH on TABLE
408 0 : if (node->IsHTMLElement(nsGkAtoms::table) &&
409 0 : (aAttribute == nsGkAtoms::height || aAttribute == nsGkAtoms::width)) {
410 0 : return true;
411 : }
412 :
413 : // attributes SIZE and WIDTH on HR
414 0 : if (node->IsHTMLElement(nsGkAtoms::hr) &&
415 0 : (aAttribute == nsGkAtoms::size || aAttribute == nsGkAtoms::width)) {
416 0 : return true;
417 : }
418 :
419 : // attribute TYPE on OL UL LI
420 0 : if (node->IsAnyOfHTMLElements(nsGkAtoms::ol, nsGkAtoms::ul,
421 0 : nsGkAtoms::li) &&
422 0 : aAttribute == nsGkAtoms::type) {
423 0 : return true;
424 : }
425 :
426 0 : if (node->IsHTMLElement(nsGkAtoms::img) &&
427 0 : (aAttribute == nsGkAtoms::border || aAttribute == nsGkAtoms::width ||
428 0 : aAttribute == nsGkAtoms::height)) {
429 0 : return true;
430 : }
431 :
432 : // other elements that we can align using CSS even if they
433 : // can't carry the html ALIGN attribute
434 0 : if (aAttribute == nsGkAtoms::align &&
435 0 : node->IsAnyOfHTMLElements(nsGkAtoms::ul,
436 : nsGkAtoms::ol,
437 : nsGkAtoms::dl,
438 : nsGkAtoms::li,
439 : nsGkAtoms::dd,
440 : nsGkAtoms::dt,
441 : nsGkAtoms::address,
442 : nsGkAtoms::pre)) {
443 0 : return true;
444 : }
445 :
446 0 : return false;
447 : }
448 :
449 : // The lowest level above the transaction; adds the CSS declaration
450 : // "aProperty : aValue" to the inline styles carried by aElement
451 : nsresult
452 0 : CSSEditUtils::SetCSSProperty(Element& aElement,
453 : nsIAtom& aProperty,
454 : const nsAString& aValue,
455 : bool aSuppressTxn)
456 : {
457 : RefPtr<ChangeStyleTransaction> transaction =
458 0 : CreateCSSPropertyTxn(aElement, aProperty, aValue,
459 0 : ChangeStyleTransaction::eSet);
460 0 : if (aSuppressTxn) {
461 0 : return transaction->DoTransaction();
462 : }
463 0 : return mHTMLEditor->DoTransaction(transaction);
464 : }
465 :
466 : nsresult
467 0 : CSSEditUtils::SetCSSPropertyPixels(Element& aElement,
468 : nsIAtom& aProperty,
469 : int32_t aIntValue)
470 : {
471 0 : nsAutoString s;
472 0 : s.AppendInt(aIntValue);
473 0 : return SetCSSProperty(aElement, aProperty, s + NS_LITERAL_STRING("px"),
474 0 : /* suppress txn */ false);
475 : }
476 :
477 : // The lowest level above the transaction; removes the value aValue from the
478 : // list of values specified for the CSS property aProperty, or totally remove
479 : // the declaration if this property accepts only one value
480 : nsresult
481 0 : CSSEditUtils::RemoveCSSProperty(Element& aElement,
482 : nsIAtom& aProperty,
483 : const nsAString& aValue,
484 : bool aSuppressTxn)
485 : {
486 : RefPtr<ChangeStyleTransaction> transaction =
487 0 : CreateCSSPropertyTxn(aElement, aProperty, aValue,
488 0 : ChangeStyleTransaction::eRemove);
489 0 : if (aSuppressTxn) {
490 0 : return transaction->DoTransaction();
491 : }
492 0 : return mHTMLEditor->DoTransaction(transaction);
493 : }
494 :
495 : already_AddRefed<ChangeStyleTransaction>
496 0 : CSSEditUtils::CreateCSSPropertyTxn(
497 : Element& aElement,
498 : nsIAtom& aAttribute,
499 : const nsAString& aValue,
500 : ChangeStyleTransaction::EChangeType aChangeType)
501 : {
502 : RefPtr<ChangeStyleTransaction> transaction =
503 0 : new ChangeStyleTransaction(aElement, aAttribute, aValue, aChangeType);
504 0 : return transaction.forget();
505 : }
506 :
507 : nsresult
508 0 : CSSEditUtils::GetSpecifiedProperty(nsINode& aNode,
509 : nsIAtom& aProperty,
510 : nsAString& aValue)
511 : {
512 0 : return GetCSSInlinePropertyBase(&aNode, &aProperty, aValue, eSpecified);
513 : }
514 :
515 : nsresult
516 0 : CSSEditUtils::GetComputedProperty(nsINode& aNode,
517 : nsIAtom& aProperty,
518 : nsAString& aValue)
519 : {
520 0 : return GetCSSInlinePropertyBase(&aNode, &aProperty, aValue, eComputed);
521 : }
522 :
523 : nsresult
524 0 : CSSEditUtils::GetCSSInlinePropertyBase(nsINode* aNode,
525 : nsIAtom* aProperty,
526 : nsAString& aValue,
527 : StyleType aStyleType)
528 : {
529 0 : MOZ_ASSERT(aNode && aProperty);
530 0 : aValue.Truncate();
531 :
532 0 : nsCOMPtr<Element> element = GetElementContainerOrSelf(aNode);
533 0 : NS_ENSURE_TRUE(element, NS_ERROR_NULL_POINTER);
534 :
535 0 : if (aStyleType == eComputed) {
536 : // Get the all the computed css styles attached to the element node
537 0 : RefPtr<nsComputedDOMStyle> cssDecl = GetComputedStyle(element);
538 0 : NS_ENSURE_STATE(cssDecl);
539 :
540 : // from these declarations, get the one we want and that one only
541 0 : MOZ_ALWAYS_SUCCEEDS(
542 : cssDecl->GetPropertyValue(nsDependentAtomString(aProperty), aValue));
543 :
544 0 : return NS_OK;
545 : }
546 :
547 0 : MOZ_ASSERT(aStyleType == eSpecified);
548 0 : RefPtr<DeclarationBlock> decl = element->GetInlineStyleDeclaration();
549 0 : if (!decl) {
550 0 : return NS_OK;
551 : }
552 :
553 : nsCSSPropertyID prop =
554 0 : nsCSSProps::LookupProperty(nsDependentAtomString(aProperty),
555 0 : CSSEnabledState::eForAllContent);
556 0 : MOZ_ASSERT(prop != eCSSProperty_UNKNOWN);
557 :
558 0 : decl->GetPropertyValueByID(prop, aValue);
559 :
560 0 : return NS_OK;
561 : }
562 :
563 : already_AddRefed<nsComputedDOMStyle>
564 0 : CSSEditUtils::GetComputedStyle(Element* aElement)
565 : {
566 0 : MOZ_ASSERT(aElement);
567 :
568 0 : nsIDocument* doc = aElement->GetUncomposedDoc();
569 0 : NS_ENSURE_TRUE(doc, nullptr);
570 :
571 0 : nsIPresShell* presShell = doc->GetShell();
572 0 : NS_ENSURE_TRUE(presShell, nullptr);
573 :
574 : RefPtr<nsComputedDOMStyle> style =
575 0 : NS_NewComputedDOMStyle(aElement, EmptyString(), presShell);
576 :
577 0 : return style.forget();
578 : }
579 :
580 : // remove the CSS style "aProperty : aPropertyValue" and possibly remove the whole node
581 : // if it is a span and if its only attribute is _moz_dirty
582 : nsresult
583 0 : CSSEditUtils::RemoveCSSInlineStyle(nsINode& aNode,
584 : nsIAtom* aProperty,
585 : const nsAString& aPropertyValue)
586 : {
587 0 : RefPtr<Element> element = aNode.AsElement();
588 0 : NS_ENSURE_STATE(element);
589 :
590 : // remove the property from the style attribute
591 0 : nsresult rv = RemoveCSSProperty(*element, *aProperty, aPropertyValue);
592 0 : NS_ENSURE_SUCCESS(rv, rv);
593 :
594 0 : if (!element->IsHTMLElement(nsGkAtoms::span) ||
595 0 : HTMLEditor::HasAttributes(element)) {
596 0 : return NS_OK;
597 : }
598 :
599 0 : return mHTMLEditor->RemoveContainer(element);
600 : }
601 :
602 : // Answers true if the property can be removed by setting a "none" CSS value
603 : // on a node
604 : bool
605 0 : CSSEditUtils::IsCSSInvertible(nsIAtom& aProperty,
606 : const nsAString* aAttribute)
607 : {
608 0 : return nsGkAtoms::b == &aProperty;
609 : }
610 :
611 : // Get the default browser background color if we need it for GetCSSBackgroundColorState
612 : void
613 0 : CSSEditUtils::GetDefaultBackgroundColor(nsAString& aColor)
614 : {
615 0 : if (Preferences::GetBool("editor.use_custom_colors", false)) {
616 0 : nsresult rv = Preferences::GetString("editor.background_color", &aColor);
617 : // XXX Why don't you validate the pref value?
618 0 : if (NS_FAILED(rv)) {
619 0 : NS_WARNING("failed to get editor.background_color");
620 0 : aColor.AssignLiteral("#ffffff"); // Default to white
621 : }
622 0 : return;
623 : }
624 :
625 0 : if (Preferences::GetBool("browser.display.use_system_colors", false)) {
626 0 : return;
627 : }
628 :
629 : nsresult rv =
630 0 : Preferences::GetString("browser.display.background_color", &aColor);
631 : // XXX Why don't you validate the pref value?
632 0 : if (NS_FAILED(rv)) {
633 0 : NS_WARNING("failed to get browser.display.background_color");
634 0 : aColor.AssignLiteral("#ffffff"); // Default to white
635 : }
636 : }
637 :
638 : // Get the default length unit used for CSS Indent/Outdent
639 : void
640 0 : CSSEditUtils::GetDefaultLengthUnit(nsAString& aLengthUnit)
641 : {
642 : nsresult rv =
643 0 : Preferences::GetString("editor.css.default_length_unit", &aLengthUnit);
644 : // XXX Why don't you validate the pref value?
645 0 : if (NS_FAILED(rv)) {
646 0 : aLengthUnit.AssignLiteral("px");
647 : }
648 0 : }
649 :
650 : // Unfortunately, CSSStyleDeclaration::GetPropertyCSSValue is not yet
651 : // implemented... We need then a way to determine the number part and the unit
652 : // from aString, aString being the result of a GetPropertyValue query...
653 : void
654 0 : CSSEditUtils::ParseLength(const nsAString& aString,
655 : float* aValue,
656 : nsIAtom** aUnit)
657 : {
658 0 : if (aString.IsEmpty()) {
659 0 : *aValue = 0;
660 0 : *aUnit = NS_Atomize(aString).take();
661 0 : return;
662 : }
663 :
664 0 : nsAString::const_iterator iter;
665 0 : aString.BeginReading(iter);
666 :
667 0 : float a = 10.0f , b = 1.0f, value = 0;
668 0 : int8_t sign = 1;
669 0 : int32_t i = 0, j = aString.Length();
670 : char16_t c;
671 0 : bool floatingPointFound = false;
672 0 : c = *iter;
673 0 : if (char16_t('-') == c) { sign = -1; iter++; i++; }
674 0 : else if (char16_t('+') == c) { iter++; i++; }
675 0 : while (i < j) {
676 0 : c = *iter;
677 0 : if ((char16_t('0') == c) ||
678 0 : (char16_t('1') == c) ||
679 0 : (char16_t('2') == c) ||
680 0 : (char16_t('3') == c) ||
681 0 : (char16_t('4') == c) ||
682 0 : (char16_t('5') == c) ||
683 0 : (char16_t('6') == c) ||
684 0 : (char16_t('7') == c) ||
685 0 : (char16_t('8') == c) ||
686 : (char16_t('9') == c)) {
687 0 : value = (value * a) + (b * (c - char16_t('0')));
688 0 : b = b / 10 * a;
689 : }
690 0 : else if (!floatingPointFound && (char16_t('.') == c)) {
691 0 : floatingPointFound = true;
692 0 : a = 1.0f; b = 0.1f;
693 : }
694 : else break;
695 0 : iter++;
696 0 : i++;
697 : }
698 0 : *aValue = value * sign;
699 0 : *aUnit = NS_Atomize(StringTail(aString, j-i)).take();
700 : }
701 :
702 : void
703 0 : CSSEditUtils::GetCSSPropertyAtom(nsCSSEditableProperty aProperty,
704 : nsIAtom** aAtom)
705 : {
706 0 : *aAtom = nullptr;
707 0 : switch (aProperty) {
708 : case eCSSEditableProperty_background_color:
709 0 : *aAtom = nsGkAtoms::backgroundColor;
710 0 : break;
711 : case eCSSEditableProperty_background_image:
712 0 : *aAtom = nsGkAtoms::background_image;
713 0 : break;
714 : case eCSSEditableProperty_border:
715 0 : *aAtom = nsGkAtoms::border;
716 0 : break;
717 : case eCSSEditableProperty_caption_side:
718 0 : *aAtom = nsGkAtoms::caption_side;
719 0 : break;
720 : case eCSSEditableProperty_color:
721 0 : *aAtom = nsGkAtoms::color;
722 0 : break;
723 : case eCSSEditableProperty_float:
724 0 : *aAtom = nsGkAtoms::_float;
725 0 : break;
726 : case eCSSEditableProperty_font_family:
727 0 : *aAtom = nsGkAtoms::font_family;
728 0 : break;
729 : case eCSSEditableProperty_font_size:
730 0 : *aAtom = nsGkAtoms::font_size;
731 0 : break;
732 : case eCSSEditableProperty_font_style:
733 0 : *aAtom = nsGkAtoms::font_style;
734 0 : break;
735 : case eCSSEditableProperty_font_weight:
736 0 : *aAtom = nsGkAtoms::fontWeight;
737 0 : break;
738 : case eCSSEditableProperty_height:
739 0 : *aAtom = nsGkAtoms::height;
740 0 : break;
741 : case eCSSEditableProperty_list_style_type:
742 0 : *aAtom = nsGkAtoms::list_style_type;
743 0 : break;
744 : case eCSSEditableProperty_margin_left:
745 0 : *aAtom = nsGkAtoms::marginLeft;
746 0 : break;
747 : case eCSSEditableProperty_margin_right:
748 0 : *aAtom = nsGkAtoms::marginRight;
749 0 : break;
750 : case eCSSEditableProperty_text_align:
751 0 : *aAtom = nsGkAtoms::textAlign;
752 0 : break;
753 : case eCSSEditableProperty_text_decoration:
754 0 : *aAtom = nsGkAtoms::text_decoration;
755 0 : break;
756 : case eCSSEditableProperty_vertical_align:
757 0 : *aAtom = nsGkAtoms::vertical_align;
758 0 : break;
759 : case eCSSEditableProperty_whitespace:
760 0 : *aAtom = nsGkAtoms::white_space;
761 0 : break;
762 : case eCSSEditableProperty_width:
763 0 : *aAtom = nsGkAtoms::width;
764 0 : break;
765 : case eCSSEditableProperty_NONE:
766 : // intentionally empty
767 0 : break;
768 : }
769 0 : }
770 :
771 : // Populate aProperty and aValueArray with the CSS declarations equivalent to the
772 : // value aValue according to the equivalence table aEquivTable
773 : void
774 0 : CSSEditUtils::BuildCSSDeclarations(nsTArray<nsIAtom*>& aPropertyArray,
775 : nsTArray<nsString>& aValueArray,
776 : const CSSEquivTable* aEquivTable,
777 : const nsAString* aValue,
778 : bool aGetOrRemoveRequest)
779 : {
780 : // clear arrays
781 0 : aPropertyArray.Clear();
782 0 : aValueArray.Clear();
783 :
784 : // if we have an input value, let's use it
785 0 : nsAutoString value, lowerCasedValue;
786 0 : if (aValue) {
787 0 : value.Assign(*aValue);
788 0 : lowerCasedValue.Assign(*aValue);
789 0 : ToLowerCase(lowerCasedValue);
790 : }
791 :
792 0 : int8_t index = 0;
793 0 : nsCSSEditableProperty cssProperty = aEquivTable[index].cssProperty;
794 0 : while (cssProperty) {
795 0 : if (!aGetOrRemoveRequest|| aEquivTable[index].gettable) {
796 0 : nsAutoString cssValue, cssPropertyString;
797 : nsIAtom * cssPropertyAtom;
798 : // find the equivalent css value for the index-th property in
799 : // the equivalence table
800 0 : (*aEquivTable[index].processValueFunctor) ((!aGetOrRemoveRequest || aEquivTable[index].caseSensitiveValue) ? &value : &lowerCasedValue,
801 : cssValue,
802 0 : aEquivTable[index].defaultValue,
803 0 : aEquivTable[index].prependValue,
804 0 : aEquivTable[index].appendValue);
805 0 : GetCSSPropertyAtom(cssProperty, &cssPropertyAtom);
806 0 : aPropertyArray.AppendElement(cssPropertyAtom);
807 0 : aValueArray.AppendElement(cssValue);
808 : }
809 0 : index++;
810 0 : cssProperty = aEquivTable[index].cssProperty;
811 : }
812 0 : }
813 :
814 : // Populate cssPropertyArray and cssValueArray with the declarations equivalent
815 : // to aHTMLProperty/aAttribute/aValue for the node aNode
816 : void
817 0 : CSSEditUtils::GenerateCSSDeclarationsFromHTMLStyle(
818 : Element* aElement,
819 : nsIAtom* aHTMLProperty,
820 : nsIAtom* aAttribute,
821 : const nsAString* aValue,
822 : nsTArray<nsIAtom*>& cssPropertyArray,
823 : nsTArray<nsString>& cssValueArray,
824 : bool aGetOrRemoveRequest)
825 : {
826 0 : MOZ_ASSERT(aElement);
827 0 : const CSSEditUtils::CSSEquivTable* equivTable = nullptr;
828 :
829 0 : if (nsGkAtoms::b == aHTMLProperty) {
830 0 : equivTable = boldEquivTable;
831 0 : } else if (nsGkAtoms::i == aHTMLProperty) {
832 0 : equivTable = italicEquivTable;
833 0 : } else if (nsGkAtoms::u == aHTMLProperty) {
834 0 : equivTable = underlineEquivTable;
835 0 : } else if (nsGkAtoms::strike == aHTMLProperty) {
836 0 : equivTable = strikeEquivTable;
837 0 : } else if (nsGkAtoms::tt == aHTMLProperty) {
838 0 : equivTable = ttEquivTable;
839 0 : } else if (aAttribute) {
840 0 : if (nsGkAtoms::font == aHTMLProperty && aAttribute == nsGkAtoms::color) {
841 0 : equivTable = fontColorEquivTable;
842 0 : } else if (nsGkAtoms::font == aHTMLProperty &&
843 0 : aAttribute == nsGkAtoms::face) {
844 0 : equivTable = fontFaceEquivTable;
845 0 : } else if (aAttribute == nsGkAtoms::bgcolor) {
846 0 : equivTable = bgcolorEquivTable;
847 0 : } else if (aAttribute == nsGkAtoms::background) {
848 0 : equivTable = backgroundImageEquivTable;
849 0 : } else if (aAttribute == nsGkAtoms::text) {
850 0 : equivTable = textColorEquivTable;
851 0 : } else if (aAttribute == nsGkAtoms::border) {
852 0 : equivTable = borderEquivTable;
853 0 : } else if (aAttribute == nsGkAtoms::align) {
854 0 : if (aElement->IsHTMLElement(nsGkAtoms::table)) {
855 0 : equivTable = tableAlignEquivTable;
856 0 : } else if (aElement->IsHTMLElement(nsGkAtoms::hr)) {
857 0 : equivTable = hrAlignEquivTable;
858 0 : } else if (aElement->IsAnyOfHTMLElements(nsGkAtoms::legend,
859 : nsGkAtoms::caption)) {
860 0 : equivTable = captionAlignEquivTable;
861 : } else {
862 0 : equivTable = textAlignEquivTable;
863 : }
864 0 : } else if (aAttribute == nsGkAtoms::valign) {
865 0 : equivTable = verticalAlignEquivTable;
866 0 : } else if (aAttribute == nsGkAtoms::nowrap) {
867 0 : equivTable = nowrapEquivTable;
868 0 : } else if (aAttribute == nsGkAtoms::width) {
869 0 : equivTable = widthEquivTable;
870 0 : } else if (aAttribute == nsGkAtoms::height ||
871 0 : (aElement->IsHTMLElement(nsGkAtoms::hr) &&
872 0 : aAttribute == nsGkAtoms::size)) {
873 0 : equivTable = heightEquivTable;
874 0 : } else if (aAttribute == nsGkAtoms::type &&
875 0 : aElement->IsAnyOfHTMLElements(nsGkAtoms::ol,
876 : nsGkAtoms::ul,
877 : nsGkAtoms::li)) {
878 0 : equivTable = listStyleTypeEquivTable;
879 : }
880 : }
881 0 : if (equivTable) {
882 0 : BuildCSSDeclarations(cssPropertyArray, cssValueArray, equivTable,
883 0 : aValue, aGetOrRemoveRequest);
884 : }
885 0 : }
886 :
887 : // Add to aNode the CSS inline style equivalent to HTMLProperty/aAttribute/
888 : // aValue for the node, and return in aCount the number of CSS properties set
889 : // by the call. The Element version returns aCount instead.
890 : int32_t
891 0 : CSSEditUtils::SetCSSEquivalentToHTMLStyle(nsIDOMNode* aNode,
892 : nsIAtom* aProperty,
893 : const nsAString* aAttribute,
894 : const nsAString* aValue,
895 : bool aSuppressTransaction)
896 : {
897 0 : MOZ_ASSERT_IF(aAttribute, aValue);
898 : // This can only fail if SetCSSProperty fails, which should only happen if
899 : // something is pretty badly wrong. In this case we assert so that hopefully
900 : // someone will notice, but there's nothing more sensible to do than just
901 : // return the count and carry on.
902 0 : nsCOMPtr<Element> element = do_QueryInterface(aNode);
903 0 : return SetCSSEquivalentToHTMLStyle(element,
904 : aProperty, aAttribute,
905 0 : aValue, aSuppressTransaction);
906 : }
907 :
908 : int32_t
909 0 : CSSEditUtils::SetCSSEquivalentToHTMLStyle(Element* aElement,
910 : nsIAtom* aHTMLProperty,
911 : const nsAString* aAttribute,
912 : const nsAString* aValue,
913 : bool aSuppressTransaction)
914 : {
915 0 : nsCOMPtr<nsIAtom> attribute = aAttribute ? NS_Atomize(*aAttribute) : nullptr;
916 0 : return SetCSSEquivalentToHTMLStyle(aElement, aHTMLProperty, attribute,
917 0 : aValue, aSuppressTransaction);
918 : }
919 :
920 : int32_t
921 0 : CSSEditUtils::SetCSSEquivalentToHTMLStyle(Element* aElement,
922 : nsIAtom* aHTMLProperty,
923 : nsIAtom* aAttribute,
924 : const nsAString* aValue,
925 : bool aSuppressTransaction)
926 : {
927 0 : MOZ_ASSERT(aElement);
928 :
929 0 : if (!IsCSSEditableProperty(aElement, aHTMLProperty, aAttribute)) {
930 0 : return 0;
931 : }
932 :
933 : // we can apply the styles only if the node is an element and if we have
934 : // an equivalence for the requested HTML style in this implementation
935 :
936 : // Find the CSS equivalence to the HTML style
937 0 : nsTArray<nsIAtom*> cssPropertyArray;
938 0 : nsTArray<nsString> cssValueArray;
939 : GenerateCSSDeclarationsFromHTMLStyle(aElement, aHTMLProperty, aAttribute,
940 : aValue, cssPropertyArray, cssValueArray,
941 0 : false);
942 :
943 : // set the individual CSS inline styles
944 0 : size_t count = cssPropertyArray.Length();
945 0 : for (size_t index = 0; index < count; index++) {
946 0 : nsresult rv = SetCSSProperty(*aElement, *cssPropertyArray[index],
947 0 : cssValueArray[index], aSuppressTransaction);
948 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
949 0 : return 0;
950 : }
951 : }
952 0 : return count;
953 : }
954 :
955 : // Remove from aNode the CSS inline style equivalent to HTMLProperty/aAttribute/aValue for the node
956 : nsresult
957 0 : CSSEditUtils::RemoveCSSEquivalentToHTMLStyle(nsIDOMNode* aNode,
958 : nsIAtom* aHTMLProperty,
959 : const nsAString* aAttribute,
960 : const nsAString* aValue,
961 : bool aSuppressTransaction)
962 : {
963 0 : nsCOMPtr<Element> element = do_QueryInterface(aNode);
964 0 : nsCOMPtr<nsIAtom> attribute = aAttribute ? NS_Atomize(*aAttribute) : nullptr;
965 :
966 0 : return RemoveCSSEquivalentToHTMLStyle(element, aHTMLProperty, attribute,
967 0 : aValue, aSuppressTransaction);
968 : }
969 :
970 : nsresult
971 0 : CSSEditUtils::RemoveCSSEquivalentToHTMLStyle(Element* aElement,
972 : nsIAtom* aHTMLProperty,
973 : nsIAtom* aAttribute,
974 : const nsAString* aValue,
975 : bool aSuppressTransaction)
976 : {
977 0 : if (NS_WARN_IF(!aElement)) {
978 0 : return NS_OK;
979 : }
980 :
981 0 : if (!IsCSSEditableProperty(aElement, aHTMLProperty, aAttribute)) {
982 0 : return NS_OK;
983 : }
984 :
985 : // we can apply the styles only if the node is an element and if we have
986 : // an equivalence for the requested HTML style in this implementation
987 :
988 : // Find the CSS equivalence to the HTML style
989 0 : nsTArray<nsIAtom*> cssPropertyArray;
990 0 : nsTArray<nsString> cssValueArray;
991 : GenerateCSSDeclarationsFromHTMLStyle(aElement, aHTMLProperty, aAttribute,
992 : aValue, cssPropertyArray, cssValueArray,
993 0 : true);
994 :
995 : // remove the individual CSS inline styles
996 0 : int32_t count = cssPropertyArray.Length();
997 0 : for (int32_t index = 0; index < count; index++) {
998 0 : nsresult rv = RemoveCSSProperty(*aElement,
999 0 : *cssPropertyArray[index],
1000 0 : cssValueArray[index],
1001 0 : aSuppressTransaction);
1002 0 : NS_ENSURE_SUCCESS(rv, rv);
1003 : }
1004 0 : return NS_OK;
1005 : }
1006 :
1007 : // returns in aValueString the list of values for the CSS equivalences to
1008 : // the HTML style aHTMLProperty/aAttribute/aValueString for the node aNode;
1009 : // the value of aStyleType controls the styles we retrieve : specified or
1010 : // computed.
1011 : nsresult
1012 0 : CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSet(nsINode* aNode,
1013 : nsIAtom* aHTMLProperty,
1014 : nsIAtom* aAttribute,
1015 : nsAString& aValueString,
1016 : StyleType aStyleType)
1017 : {
1018 0 : aValueString.Truncate();
1019 0 : nsCOMPtr<Element> theElement = GetElementContainerOrSelf(aNode);
1020 0 : NS_ENSURE_TRUE(theElement, NS_ERROR_NULL_POINTER);
1021 :
1022 0 : if (!theElement || !IsCSSEditableProperty(theElement, aHTMLProperty, aAttribute)) {
1023 0 : return NS_OK;
1024 : }
1025 :
1026 : // Yes, the requested HTML style has a CSS equivalence in this implementation
1027 0 : nsTArray<nsIAtom*> cssPropertyArray;
1028 0 : nsTArray<nsString> cssValueArray;
1029 : // get the CSS equivalence with last param true indicating we want only the
1030 : // "gettable" properties
1031 0 : GenerateCSSDeclarationsFromHTMLStyle(theElement, aHTMLProperty, aAttribute,
1032 : nullptr,
1033 0 : cssPropertyArray, cssValueArray, true);
1034 0 : int32_t count = cssPropertyArray.Length();
1035 0 : for (int32_t index = 0; index < count; index++) {
1036 0 : nsAutoString valueString;
1037 : // retrieve the specified/computed value of the property
1038 0 : nsresult rv = GetCSSInlinePropertyBase(theElement, cssPropertyArray[index],
1039 0 : valueString, aStyleType);
1040 0 : NS_ENSURE_SUCCESS(rv, rv);
1041 : // append the value to aValueString (possibly with a leading whitespace)
1042 0 : if (index) {
1043 0 : aValueString.Append(char16_t(' '));
1044 : }
1045 0 : aValueString.Append(valueString);
1046 : }
1047 0 : return NS_OK;
1048 : }
1049 :
1050 : // Does the node aNode (or its parent, if it's not an element node) have a CSS
1051 : // style equivalent to the HTML style aHTMLProperty/aHTMLAttribute/valueString?
1052 : // The value of aStyleType controls the styles we retrieve: specified or
1053 : // computed. The return value aIsSet is true if the CSS styles are set.
1054 : //
1055 : // The nsIContent variant returns aIsSet instead of using an out parameter, and
1056 : // does not modify aValue.
1057 : bool
1058 0 : CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(nsINode* aNode,
1059 : nsIAtom* aProperty,
1060 : const nsAString* aAttribute,
1061 : const nsAString& aValue,
1062 : StyleType aStyleType)
1063 : {
1064 : // Use aValue as only an in param, not in-out
1065 0 : nsAutoString value(aValue);
1066 : return IsCSSEquivalentToHTMLInlineStyleSet(aNode, aProperty, aAttribute,
1067 0 : value, aStyleType);
1068 : }
1069 :
1070 : bool
1071 0 : CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(nsINode* aNode,
1072 : nsIAtom* aProperty,
1073 : const nsAString* aAttribute,
1074 : nsAString& aValue,
1075 : StyleType aStyleType)
1076 : {
1077 0 : MOZ_ASSERT(aNode && aProperty);
1078 0 : nsCOMPtr<nsIAtom> attribute = aAttribute ? NS_Atomize(*aAttribute) : nullptr;
1079 0 : return IsCSSEquivalentToHTMLInlineStyleSet(aNode,
1080 : aProperty, attribute,
1081 0 : aValue, aStyleType);
1082 : }
1083 :
1084 : bool
1085 0 : CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(nsIDOMNode* aNode,
1086 : nsIAtom* aProperty,
1087 : const nsAString* aAttribute,
1088 : nsAString& aValue,
1089 : StyleType aStyleType)
1090 : {
1091 0 : MOZ_ASSERT(aNode && aProperty);
1092 0 : nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
1093 0 : nsCOMPtr<nsIAtom> attribute = aAttribute ? NS_Atomize(*aAttribute) : nullptr;
1094 0 : return IsCSSEquivalentToHTMLInlineStyleSet(node, aProperty, attribute,
1095 0 : aValue, aStyleType);
1096 : }
1097 :
1098 : bool
1099 0 : CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(
1100 : nsINode* aNode,
1101 : nsIAtom* aHTMLProperty,
1102 : nsIAtom* aHTMLAttribute,
1103 : nsAString& valueString,
1104 : StyleType aStyleType)
1105 : {
1106 0 : NS_ENSURE_TRUE(aNode, false);
1107 :
1108 0 : nsAutoString htmlValueString(valueString);
1109 0 : bool isSet = false;
1110 0 : do {
1111 0 : valueString.Assign(htmlValueString);
1112 : // get the value of the CSS equivalent styles
1113 : nsresult rv =
1114 : GetCSSEquivalentToHTMLInlineStyleSet(aNode, aHTMLProperty, aHTMLAttribute,
1115 0 : valueString, aStyleType);
1116 0 : NS_ENSURE_SUCCESS(rv, false);
1117 :
1118 : // early way out if we can
1119 0 : if (valueString.IsEmpty()) {
1120 0 : return isSet;
1121 : }
1122 :
1123 0 : if (nsGkAtoms::b == aHTMLProperty) {
1124 0 : if (valueString.EqualsLiteral("bold")) {
1125 0 : isSet = true;
1126 0 : } else if (valueString.EqualsLiteral("normal")) {
1127 0 : isSet = false;
1128 0 : } else if (valueString.EqualsLiteral("bolder")) {
1129 0 : isSet = true;
1130 0 : valueString.AssignLiteral("bold");
1131 : } else {
1132 0 : int32_t weight = 0;
1133 : nsresult errorCode;
1134 0 : nsAutoString value(valueString);
1135 0 : weight = value.ToInteger(&errorCode);
1136 0 : if (400 < weight) {
1137 0 : isSet = true;
1138 0 : valueString.AssignLiteral("bold");
1139 : } else {
1140 0 : isSet = false;
1141 0 : valueString.AssignLiteral("normal");
1142 : }
1143 : }
1144 0 : } else if (nsGkAtoms::i == aHTMLProperty) {
1145 0 : if (valueString.EqualsLiteral("italic") ||
1146 0 : valueString.EqualsLiteral("oblique")) {
1147 0 : isSet = true;
1148 : }
1149 0 : } else if (nsGkAtoms::u == aHTMLProperty) {
1150 0 : nsAutoString val;
1151 0 : val.AssignLiteral("underline");
1152 0 : isSet = ChangeStyleTransaction::ValueIncludes(valueString, val);
1153 0 : } else if (nsGkAtoms::strike == aHTMLProperty) {
1154 0 : nsAutoString val;
1155 0 : val.AssignLiteral("line-through");
1156 0 : isSet = ChangeStyleTransaction::ValueIncludes(valueString, val);
1157 0 : } else if ((nsGkAtoms::font == aHTMLProperty &&
1158 0 : aHTMLAttribute == nsGkAtoms::color) ||
1159 0 : aHTMLAttribute == nsGkAtoms::bgcolor) {
1160 0 : if (htmlValueString.IsEmpty()) {
1161 0 : isSet = true;
1162 : } else {
1163 : nscolor rgba;
1164 0 : nsAutoString subStr;
1165 0 : htmlValueString.Right(subStr, htmlValueString.Length() - 1);
1166 0 : if (NS_ColorNameToRGB(htmlValueString, &rgba) ||
1167 0 : NS_HexToRGBA(subStr, nsHexColorType::NoAlpha, &rgba)) {
1168 0 : nsAutoString htmlColor, tmpStr;
1169 :
1170 0 : if (NS_GET_A(rgba) != 255) {
1171 : // This should only be hit by the "transparent" keyword, which
1172 : // currently serializes to "transparent" (not "rgba(0, 0, 0, 0)").
1173 0 : MOZ_ASSERT(NS_GET_R(rgba) == 0 && NS_GET_G(rgba) == 0 &&
1174 : NS_GET_B(rgba) == 0 && NS_GET_A(rgba) == 0);
1175 0 : htmlColor.AppendLiteral("transparent");
1176 : } else {
1177 0 : htmlColor.AppendLiteral("rgb(");
1178 :
1179 0 : NS_NAMED_LITERAL_STRING(comma, ", ");
1180 :
1181 0 : tmpStr.AppendInt(NS_GET_R(rgba), 10);
1182 0 : htmlColor.Append(tmpStr + comma);
1183 :
1184 0 : tmpStr.Truncate();
1185 0 : tmpStr.AppendInt(NS_GET_G(rgba), 10);
1186 0 : htmlColor.Append(tmpStr + comma);
1187 :
1188 0 : tmpStr.Truncate();
1189 0 : tmpStr.AppendInt(NS_GET_B(rgba), 10);
1190 0 : htmlColor.Append(tmpStr);
1191 :
1192 0 : htmlColor.Append(char16_t(')'));
1193 : }
1194 :
1195 0 : isSet = htmlColor.Equals(valueString,
1196 0 : nsCaseInsensitiveStringComparator());
1197 : } else {
1198 0 : isSet = htmlValueString.Equals(valueString,
1199 0 : nsCaseInsensitiveStringComparator());
1200 : }
1201 0 : }
1202 0 : } else if (nsGkAtoms::tt == aHTMLProperty) {
1203 0 : isSet = StringBeginsWith(valueString, NS_LITERAL_STRING("monospace"));
1204 0 : } else if (nsGkAtoms::font == aHTMLProperty && aHTMLAttribute &&
1205 0 : aHTMLAttribute == nsGkAtoms::face) {
1206 0 : if (!htmlValueString.IsEmpty()) {
1207 0 : const char16_t commaSpace[] = { char16_t(','), char16_t(' '), 0 };
1208 0 : const char16_t comma[] = { char16_t(','), 0 };
1209 0 : htmlValueString.ReplaceSubstring(commaSpace, comma);
1210 0 : nsAutoString valueStringNorm(valueString);
1211 0 : valueStringNorm.ReplaceSubstring(commaSpace, comma);
1212 : isSet = htmlValueString.Equals(valueStringNorm,
1213 0 : nsCaseInsensitiveStringComparator());
1214 : } else {
1215 0 : isSet = true;
1216 : }
1217 0 : return isSet;
1218 0 : } else if (aHTMLAttribute == nsGkAtoms::align) {
1219 0 : isSet = true;
1220 : } else {
1221 0 : return false;
1222 : }
1223 :
1224 0 : if (!htmlValueString.IsEmpty() &&
1225 0 : htmlValueString.Equals(valueString,
1226 0 : nsCaseInsensitiveStringComparator())) {
1227 0 : isSet = true;
1228 : }
1229 :
1230 0 : if (htmlValueString.EqualsLiteral("-moz-editor-invert-value")) {
1231 0 : isSet = !isSet;
1232 : }
1233 :
1234 0 : if (nsGkAtoms::u == aHTMLProperty || nsGkAtoms::strike == aHTMLProperty) {
1235 : // unfortunately, the value of the text-decoration property is not inherited.
1236 : // that means that we have to look at ancestors of node to see if they are underlined
1237 0 : aNode = aNode->GetParentElement(); // set to null if it's not a dom element
1238 : }
1239 0 : } while ((nsGkAtoms::u == aHTMLProperty ||
1240 0 : nsGkAtoms::strike == aHTMLProperty) && !isSet && aNode);
1241 0 : return isSet;
1242 : }
1243 :
1244 : bool
1245 0 : CSSEditUtils::HaveCSSEquivalentStyles(
1246 : nsINode& aNode,
1247 : nsIAtom* aHTMLProperty,
1248 : nsIAtom* aHTMLAttribute,
1249 : StyleType aStyleType)
1250 : {
1251 0 : nsAutoString valueString;
1252 0 : nsCOMPtr<nsINode> node = &aNode;
1253 0 : do {
1254 : // get the value of the CSS equivalent styles
1255 : nsresult rv =
1256 0 : GetCSSEquivalentToHTMLInlineStyleSet(node, aHTMLProperty, aHTMLAttribute,
1257 0 : valueString, aStyleType);
1258 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
1259 0 : return false;
1260 : }
1261 :
1262 0 : if (!valueString.IsEmpty()) {
1263 0 : return true;
1264 : }
1265 :
1266 0 : if (nsGkAtoms::u != aHTMLProperty && nsGkAtoms::strike != aHTMLProperty) {
1267 0 : return false;
1268 : }
1269 :
1270 : // unfortunately, the value of the text-decoration property is not
1271 : // inherited.
1272 : // that means that we have to look at ancestors of node to see if they
1273 : // are underlined
1274 :
1275 : // set to null if it's not a dom element
1276 0 : node = node->GetParentElement();
1277 : } while (node);
1278 :
1279 0 : return false;
1280 : }
1281 :
1282 : void
1283 0 : CSSEditUtils::SetCSSEnabled(bool aIsCSSPrefChecked)
1284 : {
1285 0 : mIsCSSPrefChecked = aIsCSSPrefChecked;
1286 0 : }
1287 :
1288 : bool
1289 0 : CSSEditUtils::IsCSSPrefChecked()
1290 : {
1291 0 : return mIsCSSPrefChecked ;
1292 : }
1293 :
1294 : // ElementsSameStyle compares two elements and checks if they have the same
1295 : // specified CSS declarations in the STYLE attribute
1296 : // The answer is always negative if at least one of them carries an ID or a class
1297 : bool
1298 0 : CSSEditUtils::ElementsSameStyle(nsIDOMNode* aFirstNode,
1299 : nsIDOMNode* aSecondNode)
1300 : {
1301 0 : nsCOMPtr<Element> firstElement = do_QueryInterface(aFirstNode);
1302 0 : nsCOMPtr<Element> secondElement = do_QueryInterface(aSecondNode);
1303 :
1304 0 : NS_ASSERTION((firstElement && secondElement), "Non element nodes passed to ElementsSameStyle.");
1305 0 : NS_ENSURE_TRUE(firstElement, false);
1306 0 : NS_ENSURE_TRUE(secondElement, false);
1307 :
1308 0 : return ElementsSameStyle(firstElement, secondElement);
1309 : }
1310 :
1311 : bool
1312 0 : CSSEditUtils::ElementsSameStyle(Element* aFirstElement,
1313 : Element* aSecondElement)
1314 : {
1315 0 : MOZ_ASSERT(aFirstElement);
1316 0 : MOZ_ASSERT(aSecondElement);
1317 :
1318 0 : if (aFirstElement->HasAttr(kNameSpaceID_None, nsGkAtoms::id) ||
1319 0 : aSecondElement->HasAttr(kNameSpaceID_None, nsGkAtoms::id)) {
1320 : // at least one of the spans carries an ID ; suspect a CSS rule applies to it and
1321 : // refuse to merge the nodes
1322 0 : return false;
1323 : }
1324 :
1325 0 : nsAutoString firstClass, secondClass;
1326 0 : bool isFirstClassSet = aFirstElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, firstClass);
1327 0 : bool isSecondClassSet = aSecondElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, secondClass);
1328 0 : if (isFirstClassSet && isSecondClassSet) {
1329 : // both spans carry a class, let's compare them
1330 0 : if (!firstClass.Equals(secondClass)) {
1331 : // WARNING : technically, the comparison just above is questionable :
1332 : // from a pure HTML/CSS point of view class="a b" is NOT the same than
1333 : // class="b a" because a CSS rule could test the exact value of the class
1334 : // attribute to be "a b" for instance ; from a user's point of view, a
1335 : // wysiwyg editor should probably NOT make any difference. CSS people
1336 : // need to discuss this issue before any modification.
1337 0 : return false;
1338 : }
1339 0 : } else if (isFirstClassSet || isSecondClassSet) {
1340 : // one span only carries a class, early way out
1341 0 : return false;
1342 : }
1343 :
1344 0 : nsCOMPtr<nsIDOMCSSStyleDeclaration> firstCSSDecl, secondCSSDecl;
1345 : uint32_t firstLength, secondLength;
1346 0 : nsresult rv = GetInlineStyles(aFirstElement, getter_AddRefs(firstCSSDecl), &firstLength);
1347 0 : if (NS_FAILED(rv) || !firstCSSDecl) {
1348 0 : return false;
1349 : }
1350 0 : rv = GetInlineStyles(aSecondElement, getter_AddRefs(secondCSSDecl), &secondLength);
1351 0 : if (NS_FAILED(rv) || !secondCSSDecl) {
1352 0 : return false;
1353 : }
1354 :
1355 0 : if (firstLength != secondLength) {
1356 : // early way out if we can
1357 0 : return false;
1358 : }
1359 :
1360 0 : if (!firstLength) {
1361 : // no inline style !
1362 0 : return true;
1363 : }
1364 :
1365 0 : nsAutoString propertyNameString;
1366 0 : nsAutoString firstValue, secondValue;
1367 0 : for (uint32_t i = 0; i < firstLength; i++) {
1368 0 : firstCSSDecl->Item(i, propertyNameString);
1369 0 : firstCSSDecl->GetPropertyValue(propertyNameString, firstValue);
1370 0 : secondCSSDecl->GetPropertyValue(propertyNameString, secondValue);
1371 0 : if (!firstValue.Equals(secondValue)) {
1372 0 : return false;
1373 : }
1374 : }
1375 0 : for (uint32_t i = 0; i < secondLength; i++) {
1376 0 : secondCSSDecl->Item(i, propertyNameString);
1377 0 : secondCSSDecl->GetPropertyValue(propertyNameString, secondValue);
1378 0 : firstCSSDecl->GetPropertyValue(propertyNameString, firstValue);
1379 0 : if (!firstValue.Equals(secondValue)) {
1380 0 : return false;
1381 : }
1382 : }
1383 :
1384 0 : return true;
1385 : }
1386 :
1387 : nsresult
1388 0 : CSSEditUtils::GetInlineStyles(Element* aElement,
1389 : nsIDOMCSSStyleDeclaration** aCssDecl,
1390 : uint32_t* aLength)
1391 : {
1392 0 : return GetInlineStyles(static_cast<nsISupports*>(aElement), aCssDecl, aLength);
1393 : }
1394 :
1395 : nsresult
1396 0 : CSSEditUtils::GetInlineStyles(nsIDOMElement* aElement,
1397 : nsIDOMCSSStyleDeclaration** aCssDecl,
1398 : uint32_t* aLength)
1399 : {
1400 0 : return GetInlineStyles(static_cast<nsISupports*>(aElement), aCssDecl, aLength);
1401 : }
1402 :
1403 : nsresult
1404 0 : CSSEditUtils::GetInlineStyles(nsISupports* aElement,
1405 : nsIDOMCSSStyleDeclaration** aCssDecl,
1406 : uint32_t* aLength)
1407 : {
1408 0 : NS_ENSURE_TRUE(aElement && aLength, NS_ERROR_NULL_POINTER);
1409 0 : *aLength = 0;
1410 0 : nsCOMPtr<nsStyledElement> inlineStyles = do_QueryInterface(aElement);
1411 0 : NS_ENSURE_TRUE(inlineStyles, NS_ERROR_NULL_POINTER);
1412 :
1413 : nsCOMPtr<nsIDOMCSSStyleDeclaration> cssDecl =
1414 0 : do_QueryInterface(inlineStyles->Style());
1415 0 : MOZ_ASSERT(cssDecl);
1416 :
1417 0 : cssDecl.forget(aCssDecl);
1418 0 : (*aCssDecl)->GetLength(aLength);
1419 0 : return NS_OK;
1420 : }
1421 :
1422 : already_AddRefed<nsIDOMElement>
1423 0 : CSSEditUtils::GetElementContainerOrSelf(nsIDOMNode* aNode)
1424 : {
1425 0 : nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
1426 0 : NS_ENSURE_TRUE(node, nullptr);
1427 : nsCOMPtr<nsIDOMElement> element =
1428 0 : do_QueryInterface(GetElementContainerOrSelf(node));
1429 0 : return element.forget();
1430 : }
1431 :
1432 : Element*
1433 0 : CSSEditUtils::GetElementContainerOrSelf(nsINode* aNode)
1434 : {
1435 0 : MOZ_ASSERT(aNode);
1436 0 : if (nsIDOMNode::DOCUMENT_NODE == aNode->NodeType()) {
1437 0 : return nullptr;
1438 : }
1439 :
1440 0 : nsINode* node = aNode;
1441 : // Loop until we find an element.
1442 0 : while (node && !node->IsElement()) {
1443 0 : node = node->GetParentNode();
1444 : }
1445 :
1446 0 : NS_ENSURE_TRUE(node, nullptr);
1447 0 : return node->AsElement();
1448 : }
1449 :
1450 : nsresult
1451 0 : CSSEditUtils::SetCSSProperty(nsIDOMElement* aElement,
1452 : const nsAString& aProperty,
1453 : const nsAString& aValue)
1454 : {
1455 0 : nsCOMPtr<nsIDOMCSSStyleDeclaration> cssDecl;
1456 : uint32_t length;
1457 0 : nsresult rv = GetInlineStyles(aElement, getter_AddRefs(cssDecl), &length);
1458 0 : if (NS_FAILED(rv) || !cssDecl) {
1459 0 : return rv;
1460 : }
1461 :
1462 0 : return cssDecl->SetProperty(aProperty,
1463 : aValue,
1464 0 : EmptyString());
1465 : }
1466 :
1467 : nsresult
1468 0 : CSSEditUtils::SetCSSPropertyPixels(nsIDOMElement* aElement,
1469 : const nsAString& aProperty,
1470 : int32_t aIntValue)
1471 : {
1472 0 : nsAutoString s;
1473 0 : s.AppendInt(aIntValue);
1474 0 : return SetCSSProperty(aElement, aProperty, s + NS_LITERAL_STRING("px"));
1475 : }
1476 :
1477 : } // namespace mozilla
|