View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.host.css;
16  
17  import org.htmlunit.WebDriverTestCase;
18  import org.htmlunit.junit.BrowserRunner;
19  import org.htmlunit.junit.annotation.Alerts;
20  import org.junit.Test;
21  import org.junit.runner.RunWith;
22  import org.openqa.selenium.By;
23  import org.openqa.selenium.WebDriver;
24  
25  /**
26   * Tests for {@link CSSStyleDeclaration}.
27   *
28   * @author Ahmed Ashour
29   * @author Frank Danek
30   * @author Ronald Brill
31   */
32  @RunWith(BrowserRunner.class)
33  public class CSSStyleDeclaration2Test extends WebDriverTestCase {
34  
35      /*
36       Below is a page to see the different elements behavior
37  <html>
38    <head>
39      <script>
40        function test() {
41          //all properties of CSSStyleDeclaration in JavaScriptConfiguration.xml
42          var properties = ['azimuth','background','backgroundAttachment','backgroundColor','backgroundImage',
43          'backgroundPosition','backgroundPositionX','backgroundPositionY','backgroundRepeat','behavior','border',
44          'borderBottom','borderBottomColor','borderBottomStyle','borderBottomWidth','borderCollapse','borderColor',
45          'borderLeft','borderLeftColor','borderLeftStyle','borderLeftWidth','borderRight','borderRightColor',
46          'borderRightStyle','borderRightWidth','borderSpacing','borderStyle','borderTop','borderTopColor',
47          'borderTopStyle','borderTopWidth','borderWidth','bottom','captionSide','clear','clip','color','content',
48          'counterIncrement','counterReset','cssFloat','cssText','cue','cueAfter','cueBefore','cursor','direction',
49          'display','elevation','emptyCells','filter','font','fontFamily','fontSize','fontSizeAdjust','fontStretch',
50          'fontStyle','fontVariant','fontWeight','height','imeMode','layoutFlow','layoutGrid','layoutGridChar',
51          'layoutGridLine','layoutGridMode','layoutGridType','left','letterSpacing','lineBreak','lineHeight',
52          'listStyle','listStyleImage','listStylePosition','listStyleType','margin','marginBottom','marginLeft',
53          'marginRight','marginTop','markerOffset','marks','maxHeight','maxWidth','minHeight','minWidth',
54          'MozAppearance','MozBackgroundClip','MozBackgroundInlinePolicy','MozBackgroundOrigin','MozBinding',
55          'MozBorderBottomColors','MozBorderLeftColors','MozBorderRadius','MozBorderRadiusBottomleft',
56          'MozBorderRadiusBottomright','MozBorderRadiusTopleft','MozBorderRadiusTopright','MozBorderRightColors',
57          'MozBorderTopColors','MozBoxAlign','MozBoxDirection','MozBoxFlex','MozBoxOrdinalGroup','MozBoxOrient',
58          'MozBoxPack','MozBoxSizing','MozColumnCount','MozColumnGap','MozColumnWidth','MozFloatEdge',
59          'MozForceBrokenImageIcon','MozImageRegion','MozMarginEnd','MozMarginStart','MozOpacity','MozOutline',
60          'MozOutlineColor','MozOutlineOffset','MozOutlineRadius','MozOutlineRadiusBottomleft',
61          'MozOutlineRadiusBottomright','MozOutlineRadiusTopleft','MozOutlineRadiusTopright','MozOutlineStyle',
62          'MozOutlineWidth','MozPaddingEnd','MozPaddingStart','MozUserFocus','MozUserInput','MozUserModify',
63          'MozUserSelect','msInterpolationMode','opacity','orphans','outline','outlineColor','outlineOffset',
64          'outlineStyle','outlineWidth','overflow','overflowX','overflowY','padding','paddingBottom','paddingLeft',
65          'paddingRight','paddingTop','page','pageBreakAfter','pageBreakBefore','pageBreakInside','pause',
66          'pauseAfter','pauseBefore','pitch','pitchRange','pixelBottom','pixelLeft','pixelRight','pixelTop',
67          'posBottom','posHeight','position','posLeft','posRight','posTop','posWidth','quotes','richness',
68          'right','rubyAlign','rubyOverhang','rubyPosition','scrollbar3dLightColor','scrollbarArrowColor',
69          'scrollbarBaseColor','scrollbarDarkShadowColor','scrollbarFaceColor','scrollbarHighlightColor',
70          'scrollbarShadowColor','scrollbarTrackColor','size','speak','speakHeader','speakNumeral',
71          'speakPunctuation','speechRate','stress','styleFloat','tableLayout','textAlign','textAlignLast',
72          'textAutospace','textDecoration','textDecorationBlink','textDecorationLineThrough','textDecorationNone',
73          'textDecorationOverline','textDecorationUnderline','textIndent','textJustify','textJustifyTrim',
74          'textKashida','textKashidaSpace','textOverflow','textShadow','textTransform','textUnderlinePosition',
75          'top','unicodeBidi','verticalAlign','visibility','voiceFamily','volume','whiteSpace','widows','width',
76          'wordBreak','wordSpacing','wordWrap','writingMode','zIndex','zoom'];
77  
78      var ta = document.getElementById('myTextarea');
79      for (var prop in properties) {
80        prop = properties[prop];
81        var node = document.createElement('div');
82        var buffer = prop + ':';
83        try {
84          buffer += node.style[prop];
85          node.style[prop] = '42.0';
86          buffer += ',' + node.style[prop];
87          node.style[prop] = '42.7';
88          buffer += ',' + node.style[prop];
89          node.style[prop] = '42';
90          buffer += ',' + node.style[prop];
91        } catch(e) {
92            buffer += ',' + 'error';
93        }
94        ta.value += buffer + '\n';
95      }
96  }
97  </script></head>
98  <body onload='test()'>
99    <textarea id='myTextarea' cols='120' rows='40'></textarea>
100 </body></html>
101      */
102 
103     /**
104      * @throws Exception if the test fails
105      */
106     @Test
107     @Alerts({"bottom: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
108              "left: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
109              "right: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
110              "top: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]"})
111     public void width_like_properties() throws Exception {
112         width_like_properties("bottom", "left", "right", "top");
113     }
114 
115     /**
116      * @throws Exception if the test fails
117      */
118     @Test
119     @Alerts({"borderBottomWidth: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42em]",
120              "borderLeftWidth: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42em]",
121              "borderRightWidth: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42em]",
122              "borderTopWidth: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42em]"})
123     public void width_like_properties_border() throws Exception {
124         width_like_properties("borderBottomWidth", "borderLeftWidth", "borderRightWidth", "borderTopWidth");
125     }
126 
127     /**
128      * @throws Exception if the test fails
129      */
130     @Test
131     @Alerts({"marginBottom: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
132              "marginLeft: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
133              "marginRight: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
134              "marginTop: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]"})
135     public void width_like_properties_margin() throws Exception {
136         width_like_properties("marginBottom", "marginLeft", "marginRight", "marginTop");
137     }
138 
139     /**
140      * @throws Exception if the test fails
141      */
142     @Test
143     @Alerts({"paddingBottom: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
144              "paddingLeft: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
145              "paddingRight: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
146              "paddingTop: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]"})
147     public void width_like_properties_padding() throws Exception {
148         width_like_properties("paddingBottom", "paddingLeft", "paddingRight", "paddingTop");
149     }
150 
151     /**
152      * @throws Exception if the test fails
153      */
154     @Test
155     @Alerts({"height: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
156              "width: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
157              "maxHeight: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
158              "maxWidth: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
159              "minHeight: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
160              "minWidth: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]"})
161     public void width_like_properties_heightWidth() throws Exception {
162         width_like_properties("height", "width", "maxHeight", "maxWidth", "minHeight", "minWidth");
163     }
164 
165     /**
166      * @throws Exception if the test fails
167      */
168     @Test
169     @Alerts(DEFAULT = {"fontSize: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
170                        "letterSpacing: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42em]",
171                        "outlineWidth: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42em]",
172                        "textIndent: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
173                        "verticalAlign: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
174                        "wordSpacing: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42em]"},
175             FF = {"fontSize: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
176                   "letterSpacing: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
177                   "outlineWidth: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42em]",
178                   "textIndent: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
179                   "verticalAlign: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
180                   "wordSpacing: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]"},
181             FF_ESR = {"fontSize: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
182                       "letterSpacing: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42em]",
183                       "outlineWidth: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42em]",
184                       "textIndent: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
185                       "verticalAlign: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]",
186                       "wordSpacing: initial [] 42.0 [] 42.7 [] 42 [] 42px [42px] 42mm [42mm] 42em [42em] 42% [42%]"})
187     public void width_like_properties_font() throws Exception {
188         width_like_properties("fontSize", "letterSpacing", "outlineWidth", "textIndent",
189                         "verticalAlign", "wordSpacing");
190     }
191 
192     private void width_like_properties(final String... properties) throws Exception {
193         final String props = "'" + String.join("', '", properties) + "'";
194         final String html = DOCTYPE_HTML
195             + "<html><head><script>\n"
196             + LOG_TITLE_FUNCTION
197             + "function test() {\n"
198             + "  var properties = [" + props + "];\n"
199             + "\n"
200             + "  for (var prop in properties) {\n"
201             + "    prop = properties[prop];\n"
202             + "    var result = prop + ':';\n"
203 
204             + "    var node = document.createElement('div');\n"
205             + "    result += ' initial [' + node.style[prop] + ']';\n"
206 
207             + "    node.style[prop] = '42.0';\n"
208             + "    result += ' 42.0 [' + node.style[prop] + ']';\n"
209 
210             + "    node.style[prop] = '42.7';\n"
211             + "    result += ' 42.7 [' + node.style[prop] + ']';\n"
212 
213             + "    node.style[prop] = '42';\n"
214             + "    result += ' 42 [' + node.style[prop] + ']';\n"
215 
216             + "    node.style[prop] = '42px';\n"
217             + "    result += ' 42px [' + node.style[prop] + ']';\n"
218 
219             + "    node.style[prop] = '42mm';\n"
220             + "    result += ' 42mm [' + node.style[prop] + ']';\n"
221 
222             + "    node.style[prop] = '42em';\n"
223             + "    result += ' 42em [' + node.style[prop] + ']';\n"
224 
225             + "    node.style[prop] = '42%';\n"
226             + "    result += ' 42% [' + node.style[prop] + ']';\n"
227 
228             + "    log(result);\n"
229             + "  }\n"
230             + "}\n"
231             + "</script></head>\n"
232             + "<body onload='test()'></body></html>";
233 
234         loadPageVerifyTitle2(html);
235     }
236 
237     /**
238      * @throws Exception if the test fails
239      */
240     @Test
241     @Alerts({"", "baseline", "sub", "super", "text-top",
242              "text-bottom", "middle", "top", "bottom",
243              "1.7em", "4px", "32%",
244              "inherit", "initial", "revert", "unset",
245              "unset", "unset", "unset"})
246     public void verticalAlign() throws Exception {
247         checkPropertyValues("vertical-align",
248                 "baseline", "sub", "super", "text-top", "text-bottom", "middle", "top", "bottom",
249                 "1.7em", "4px", "32%",
250                 "inherit", "initial", "revert", "unset",
251                 "1 px", "7mond", "not-supported");
252         checkPropertyValuesDirect("verticalAlign",
253                 "baseline", "sub", "super", "text-top", "text-bottom", "middle", "top", "bottom",
254                 "1.7em", "4px", "32%",
255                 "inherit", "initial", "revert", "unset",
256                 "1 px", "7mond", "not-supported");
257     }
258 
259     private void checkPropertyValuesDirect(final String property, final String... propertyValues) throws Exception {
260         final String propValues = "'" + String.join("', '", propertyValues) + "'";
261         final String html = DOCTYPE_HTML
262             + "<html><head><script>\n"
263             + LOG_TITLE_FUNCTION
264             + "function test() {\n"
265             + "  var propValues = [" + propValues + "];\n"
266             + "\n"
267             + "  var node = document.createElement('div');\n"
268             + "  var styleVal = node.style." + property + ";\n"
269             + "  log(styleVal);\n"
270 
271             + "  propValues.forEach(propValue => {\n"
272             + "    node.style." + property + " = propValue;\n"
273             + "    styleVal = node.style." + property + ";\n"
274             + "    log(styleVal);\n"
275             + "  });\n"
276             + "}\n"
277             + "</script></head>\n"
278             + "<body onload='test()'></body></html>";
279 
280         loadPageVerifyTitle2(html);
281     }
282 
283     private void checkPropertyValues(final String property, final String... propertyValues) throws Exception {
284         final String propValues = "'" + String.join("', '", propertyValues) + "'";
285         final String html = DOCTYPE_HTML
286             + "<html><head><script>\n"
287             + LOG_TITLE_FUNCTION
288             + "function test() {\n"
289             + "  var propValues = [" + propValues + "];\n"
290             + "\n"
291             + "  var node = document.createElement('div');\n"
292             + "  var styleVal = node.style['" + property + "'];\n"
293             + "  log(styleVal);\n"
294 
295             + "  propValues.forEach(propValue => {\n"
296             + "    node.style['" + property + "'] = propValue;\n"
297             + "    styleVal = node.style['" + property + "'];\n"
298             + "    log(styleVal);\n"
299             + "  });\n"
300             + "}\n"
301             + "</script></head>\n"
302             + "<body onload='test()'></body></html>";
303 
304         loadPageVerifyTitle2(html);
305     }
306 
307     /**
308      * @throws Exception if the test fails
309      */
310     @Test
311     public void properties() throws Exception {
312         final String html = DOCTYPE_HTML
313             + "<html>\n"
314             + "<head><title>Tester</title>\n"
315             + "<script>\n"
316             + "function test() {\n"
317             + "  var style = document.getElementById('myDiv').style;\n"
318             + "  var array = [];\n"
319             + "  for (var i in style) {\n"
320             + "    try {\n"
321             + "      if (eval('style.' + i) == '') { array.push(i); }\n"
322             + "    } catch(e) {}\n" // ignore strange properties like '@@iterator'
323             + "  }\n"
324             + "  array.sort();\n"
325             + "  document.getElementById('myLog').value = array.join('\\n');\n"
326             + "}\n"
327             + "</script></head>\n"
328             + "<body onload='test()'>\n"
329             + "  <div id='myDiv'><br></div>\n"
330             + LOG_TEXTAREA
331             + "</body></html>";
332 
333         final String expected = loadExpectation("CSSStyleDeclaration2Test.properties", ".txt");
334 
335         final WebDriver driver = loadPage2(html);
336         verify(() -> driver.findElement(By.id("myLog")).getDomProperty("value"), expected);
337     }
338 
339     /**
340      * Test types of properties.
341      * @throws Exception if the test fails
342      */
343     @Test
344     public void properties2() throws Exception {
345         final String html = DOCTYPE_HTML
346             + "<html>\n"
347             + "<head>\n"
348             + "<script>\n"
349             + "function test() {\n"
350             + "  var style = document.getElementById('myDiv').style;\n"
351             + "  var array = [];\n"
352             + "  for (var i in style) {\n"
353             + "    try {\n"
354             + "      if (eval('style.' + i) === '') { array.push(i); }\n"
355             + "    } catch(e) {}\n" // ignore strange properties like '@@iterator'
356             + "  }\n"
357             + "  array.sort();\n"
358             + "  document.getElementById('myLog').value = array.join('\\n');\n"
359             + "}\n"
360             + "</script></head>\n"
361             + "<body onload='test()'>\n"
362             + "  <div id='myDiv'><br></div>\n"
363             + LOG_TEXTAREA
364             + "</body></html>";
365 
366         final String expected = loadExpectation("CSSStyleDeclaration2Test.properties2", ".txt");
367 
368         final WebDriver driver = loadPage2(html);
369         verify(() -> driver.findElement(By.id("myLog")).getDomProperty("value"), expected);
370     }
371 
372     /**
373      * @throws Exception if the test fails
374      */
375     @Test
376     @Alerts({"0", "0"})
377     public void setLength() throws Exception {
378         final String html = DOCTYPE_HTML
379             + "<html>\n"
380             + "<head>\n"
381             + "<script>\n"
382             + LOG_TITLE_FUNCTION
383             + "function test() {\n"
384             + "  var style = document.body.style;\n"
385             + "  try {\n"
386             + "    log(style.length);\n"
387             + "    style.length = 100;\n"
388             + "    log(style.length);\n"
389             + "  } catch(e) { log(e); }\n"
390             + "}\n"
391             + "</script></head>\n"
392             + "<body onload='test()'>\n"
393             + "</body></html>";
394 
395         loadPageVerifyTitle2(html);
396     }
397 
398     /**
399      * @throws Exception if the test fails
400      */
401     @Test
402     @Alerts({"0", "Type error"})
403     public void setLengthStrictMode() throws Exception {
404         final String html = DOCTYPE_HTML
405             + "<html>\n"
406             + "<head>\n"
407             + "<script>\n"
408             + LOG_TITLE_FUNCTION
409             + "function test() {\n"
410             + "  'use strict';\n"
411             + "  var style = document.body.style;\n"
412             + "  try {\n"
413             + "    log(style.length);\n"
414             + "    style.length = 100;\n"
415             + "    log(style.length);\n"
416             + "  } catch(e) { log('Type error'); }\n"
417             + "}\n"
418             + "</script></head>\n"
419             + "<body onload='test()'>\n"
420             + "</body></html>";
421 
422         loadPageVerifyTitle2(html);
423     }
424 
425     /**
426      * @throws Exception on test failure
427      */
428     @Test
429     @Alerts({"function values() { [native code] }", "no for..of", "display"})
430     public void iterator() throws Exception {
431         final String html = DOCTYPE_HTML
432                 + "<html><head>\n"
433                 + "</head>\n"
434                 + "<script>\n"
435                 + LOG_TITLE_FUNCTION
436                 + "  function test() {\n"
437                 + "    var style = document.body.style;\n"
438 
439                 + "    if (typeof Symbol != 'undefined') {\n"
440                 + "      log(style[Symbol.iterator]);\n"
441                 + "    }\n"
442 
443                 + "    if (!style.forEach) {\n"
444                 + "      log('no for..of');\n"
445                 + "    }\n"
446 
447                 + "    if (typeof Symbol === 'undefined') {\n"
448                 + "      return;\n"
449                 + "    }\n"
450 
451                 + "    for (var i of style) {\n"
452                 + "      log(i);\n"
453                 + "    }\n"
454                 + "  }\n"
455                 + "</script>\n"
456                 + "</head><body onload='test()' style='display: inline'>\n"
457                 + "  <div></div>\n"
458                 + "</body></html>";
459 
460         loadPageVerifyTitle2(html);
461     }
462 }