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;
16  
17  import org.htmlunit.WebDriverTestCase;
18  import org.htmlunit.junit.annotation.Alerts;
19  import org.htmlunit.junit.annotation.HtmlUnitNYI;
20  import org.junit.jupiter.api.Test;
21  import org.openqa.selenium.WebDriverException;
22  
23  /**
24   * Tests for general scriptable objects in the browser context.
25   *
26   * @author Jake Cobb
27   * @author Ronald Brill
28   */
29  public class ScriptableObjectTest extends WebDriverTestCase {
30  
31      /**
32       * Tests that writing a property which is a read-only in the prototype
33       * behaves as expected (<a href="https://sourceforge.net/p/htmlunit/bugs/1633/">
34       * https://sourceforge.net/p/htmlunit/bugs/1633/</a>.
35       * @throws Exception on failure
36       */
37      @Test
38      @Alerts({"default", "default", "default"})
39      public void readOnlyPrototype() throws Exception {
40          final String html = DOCTYPE_HTML
41              + "<html><body>\n"
42              + "<script>\n"
43              + LOG_TITLE_FUNCTION
44              + "  var proto = Object.create(Object.prototype, {\n"
45              + "    myProp: {\n"
46              + "        get: function() { return 'default'; }\n"
47              + "    }\n"
48              + "  });\n"
49              + "  var o1 = Object.create(proto);\n"
50              + "  var o2 = Object.create(proto);\n"
51              + "  o2.myProp = 'bar';\n"
52              + "  log(o2.myProp);\n"
53              + "  log(o1.myProp);\n"
54              + "  log(proto.myProp)"
55              + "</script>\n"
56              + "</body>\n"
57              + "</html>\n";
58  
59          loadPageVerifyTitle2(html);
60      }
61  
62      /**
63       * @throws Exception on failure
64       */
65      @Test
66      @Alerts({"2", "symbol", "symbol", "1", "c"})
67      public void getOwnPropertySymbols() throws Exception {
68          final String html = DOCTYPE_HTML
69                  + "<html><body>\n"
70                  + "<script>\n"
71                  + LOG_TITLE_FUNCTION
72                  + "  if (Object.getOwnPropertySymbols) {\n"
73  
74                  + "    var obj = {};\n"
75                  + "    var a = Symbol('a');\n"
76                  + "    var b = Symbol.for('b');\n"
77  
78                  + "    obj[a] = 'localSymbol';\n"
79                  + "    obj[b] = 'globalSymbol';\n"
80                  + "    obj['c'] = 'something else';\n"
81  
82                  + "    var objectSymbols = Object.getOwnPropertySymbols(obj);\n"
83                  + "    log(objectSymbols.length);\n"
84                  + "    log(typeof objectSymbols[0]);\n"
85                  + "    log(typeof objectSymbols[1]);\n"
86  
87                  + "    var objectNames = Object.getOwnPropertyNames(obj);\n"
88                  + "    log(objectNames.length);\n"
89                  + "    log(objectNames[0]);\n"
90  
91                  + "  } else { log('not defined'); }\n"
92                  + "</script>\n"
93                  + "</body>\n"
94                  + "</html>\n";
95  
96          loadPageVerifyTitle2(html);
97      }
98  
99      /**
100      * @throws Exception on failure
101      */
102     @Test
103     @Alerts({"TypeError", "true", "true"})
104     public void ctorNotChangeableForPrimitives() throws Exception {
105         final String html = DOCTYPE_HTML
106                 + "<html><body>\n"
107                 + "<script>\n"
108                 + LOG_TITLE_FUNCTION
109                 + "  let val = null;\n"
110                 + "  try {\n"
111                 + "    val.constructor = 1;\n"
112                 + "  } catch(e) { logEx(e); }\n"
113 
114                 + "  val = 'abc';\n"
115                 + "  val.constructor = Number;"
116                 + "  log(val.constructor === String)\n"
117 
118                 // An implicit instance of String('abc') was created and assigned the prop foo
119                 + "  val.foo = 'bar';\n"
120                 // true, since a new instance of String('abc') was created for this comparison,
121                 // which doesn't have the foo property
122                 + "  log (val.foo === undefined);\n"
123 
124                 + "</script>\n"
125                 + "</body>\n"
126                 + "</html>\n";
127 
128         loadPageVerifyTitle2(html);
129     }
130 
131 
132     /**
133      * @throws Exception on failure
134      */
135     @Test
136     @Alerts({"TypeError", "true", "true"})
137     @HtmlUnitNYI(CHROME = "org.htmlunit.ScriptException: TypeError: Cannot set property \"constructor\" of abc",
138             EDGE = "org.htmlunit.ScriptException: TypeError: Cannot set property \"constructor\" of abc",
139             FF = "org.htmlunit.ScriptException: TypeError: Cannot set property \"constructor\" of abc",
140             FF_ESR = "org.htmlunit.ScriptException: TypeError: Cannot set property \"constructor\" of abc")
141     public void ctorNotChangeableForPrimitivesStrict() throws Exception {
142         final String html = DOCTYPE_HTML
143                 + "<html><body>\n"
144                 + "<script>\n"
145                 + LOG_TITLE_FUNCTION
146 
147                 + "  'use strict';\n"
148 
149                 + "  let val = null;\n"
150                 + "  try {\n"
151                 + "    val.constructor = 1;\n"
152                 + "  } catch(e) { logEx(e); }\n"
153 
154                 + "  val = 'abc';\n"
155                 + "  val.constructor = Number;"
156                 + "  log(val.constructor === String)\n"
157 
158                 // An implicit instance of String('abc') was created and assigned the prop foo
159                 + "  val.foo = 'bar';\n"
160                 // true, since a new instance of String('abc') was created for this comparison,
161                 // which doesn't have the foo property
162                 + "  log (val.foo === undefined);\n"
163 
164                 + "</script>\n"
165                 + "</body>\n"
166                 + "</html>\n";
167 
168         try {
169             loadPageVerifyTitle2(html);
170         }
171         catch (final WebDriverException e) {
172             assertTrue(e.getMessage(), e.getMessage().startsWith(getExpectedAlerts()[0]));
173         }
174     }
175 
176     /**
177      * @throws Exception on failure
178      */
179     @Test
180     @Alerts({"true", "false", "true", "ctor", "true"})
181     public void ctorChangeableHasNoEffectForTypeOf() throws Exception {
182         final String html = DOCTYPE_HTML
183                 + "<html><body>\n"
184                 + "<script>\n"
185                 + LOG_TITLE_FUNCTION
186 
187                 + "  let a = [];\n"
188                 + "  a.constructor = String\n"
189                 + "  log(a.constructor === String);\n"
190                 + "  log(a instanceof String);\n"
191                 + "  log(a instanceof Array);\n"
192 
193                 + "  try {\n"
194                 + "    a = new Event('test');\n"
195                 + "    log('ctor');\n"
196                 + "    a.constructor = 'bar';\n"
197                 + "    log(a.constructor === 'bar');\n"
198                 + "  } catch(e) { logEx(e) }\n"
199 
200                 + "</script>\n"
201                 + "</body>\n"
202                 + "</html>\n";
203 
204         loadPageVerifyTitle2(html);
205     }
206 
207     /**
208      * @throws Exception on failure
209      */
210     @Test
211     @Alerts({"true", "false", "true", "ctor", "true"})
212     public void ctorChangeableHasNoEffectForTypeOfStrict() throws Exception {
213         final String html = DOCTYPE_HTML
214                 + "<html><body>\n"
215                 + "<script>\n"
216                 + "  'use strict';\n"
217                 + LOG_TITLE_FUNCTION
218 
219                 + "  let a = [];\n"
220                 + "  a.constructor = String\n"
221                 + "  log(a.constructor === String);\n"
222                 + "  log(a instanceof String);\n"
223                 + "  log(a instanceof Array);\n"
224 
225                 + "  try {\n"
226                 + "    a = new Event('test');\n"
227                 + "    log('ctor');\n"
228                 + "    a.constructor = 'bar';\n"
229                 + "    log(a.constructor === 'bar');\n"
230                 + "  } catch(e) { logEx(e) }\n"
231 
232                 + "</script>\n"
233                 + "</body>\n"
234                 + "</html>\n";
235 
236         loadPageVerifyTitle2(html);
237     }
238 
239     /**
240      * @throws Exception on failure
241      */
242     @Test
243     @Alerts("true")
244     public void ctorChangeableHasNoEffectForSealed() throws Exception {
245         final String html = DOCTYPE_HTML
246                 + "<html><body>\n"
247                 + "<script>\n"
248                 + LOG_TITLE_FUNCTION
249 
250                 + "  let a = Object.seal({});\n"
251                 + "  a.constructor = Number;\n"
252                 + "  log(a.constructor === Object);\n"
253 
254                 + "</script>\n"
255                 + "</body>\n"
256                 + "</html>\n";
257 
258         loadPageVerifyTitle2(html);
259     }
260 
261     /**
262      * @throws Exception on failure
263      */
264     @Test
265     @Alerts("true")
266     @HtmlUnitNYI(CHROME = "TypeError",
267             EDGE = "TypeError",
268             FF = "TypeError",
269             FF_ESR = "TypeError")
270     public void ctorChangeableHasNoEffectForSealedStrict() throws Exception {
271         final String html = DOCTYPE_HTML
272                 + "<html><body>\n"
273                 + "<script>\n"
274                 + LOG_TITLE_FUNCTION
275 
276                 + "  'use strict';\n"
277 
278                 + "  let a = Object.seal({});\n"
279                 + "  try {\n"
280                 + "    a.constructor = Number;\n"
281                 + "    log(a.constructor === Object);\n"
282                 + "  } catch(e) { logEx(e) }\n"
283 
284                 + "</script>\n"
285                 + "</body>\n"
286                 + "</html>\n";
287 
288         loadPageVerifyTitle2(html);
289     }
290 }