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