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