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