summaryrefslogtreecommitdiffstats
path: root/dom/bindings/test/test_dom_xrays.html
blob: 6d65ab731525928af2038816cb5c67d810055626 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=787070
-->
<head>
  <meta charset="utf-8">
  <title>Test for Bug 787070</title>
  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=787070">Mozilla Bug 787070</a>
<p id="display"></p>
<div id="content" style="display: none">
<iframe id="t" src="http://example.org/tests/dom/bindings/test/file_dom_xrays.html"></iframe>
</div>
<pre id="test">
<script type="application/javascript">

/** Test for Bug 1021066 **/

// values should contain the values that the property should have on each of
// the objects on the prototype chain of obj. A value of undefined signals
// that the value should not be present on that prototype.
function checkXrayProperty(obj, name, values) {
  var instance = obj;
  do {
    var value = values.shift();
    if (typeof value == "undefined") {
      ok(!obj.hasOwnProperty(name), "hasOwnProperty shouldn't see \"" + String(name) + "\" through Xrays");
      is(Object.getOwnPropertyDescriptor(obj, name), undefined, "getOwnPropertyDescriptor shouldn't see \"" + String(name) + "\" through Xrays");
      ok(!Object.keys(obj).includes(name), "Enumerating the Xray should not return \"" + String(name) + "\"");
      ok(!Object.getOwnPropertyNames(obj).includes(name),
         `The Xray's property names should not include ${String(name)}`);
      ok(!Object.getOwnPropertySymbols(obj).includes(name),
         `The Xray's property symbols should not include ${String(name)}`);
    } else {
      ok(obj.hasOwnProperty(name), "hasOwnProperty should see \"" + String(name) + "\" through Xrays");
      var pd = Object.getOwnPropertyDescriptor(obj, name);
      ok(pd, "getOwnPropertyDescriptor should see \"" + String(name) + "\" through Xrays");
      if (pd && pd.get) {
        is(pd.get.call(instance), value, "Should get the right value for \"" + String(name) + "\" through Xrays");
      } else {
        is(obj[name], value, "Should get the right value for \"" + String(name) + "\" through Xrays");
      }
      if (pd) {
        if (pd.enumerable) {
          ok(Object.keys(obj).indexOf("" + name) > -1, "Enumerating the Xray should return \"" + String(name) + "\"");
        }
        if (typeof name == "symbol") {
          ok(Object.getOwnPropertySymbols(obj).includes(name),
             `The Xray's property symbols should include ${String(name)}`);
        } else {
          ok(Object.getOwnPropertyNames(obj).includes("" + name),
             `The Xray's property names should include ${name}`);
        }
      }
    }
  } while ((obj = Object.getPrototypeOf(obj)));
}

function checkWindowXrayProperty(win, name, { windowValue, windowPrototypeValue, namedPropertiesValue, eventTargetPrototypeValue }) {
  checkXrayProperty(win, name, [ windowValue, windowPrototypeValue, namedPropertiesValue, eventTargetPrototypeValue ]);
}
function checkDocumentXrayProperty(doc, name, { documentValue, htmlDocumentPrototypeValue, documentPrototypeValue, nodePrototypeValue, eventTargetPrototypeValue }) {
  checkXrayProperty(doc, name, [ documentValue, htmlDocumentPrototypeValue, documentPrototypeValue, nodePrototypeValue, eventTargetPrototypeValue ]);
}

function test() {
  // Window
  var win = document.getElementById("t").contentWindow;
  var doc = document.getElementById("t").contentDocument;

  var winProto = Object.getPrototypeOf(win);
  is(winProto, win.Window.prototype, "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");

  var namedPropertiesObject = Object.getPrototypeOf(winProto);
  is(Cu.getClassName(namedPropertiesObject, /* unwrap = */ true), "WindowProperties", "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");

  var eventTargetProto = Object.getPrototypeOf(namedPropertiesObject);
  is(eventTargetProto, win.EventTarget.prototype, "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");

  let docProto = Object.getPrototypeOf(doc);
  is(docProto, win.HTMLDocument.prototype, "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");

  // Xrays need to filter expandos.
  checkDocumentXrayProperty(doc, "expando", {});
  ok(!("expando" in doc), "Xrays should filter expandos");

  checkDocumentXrayProperty(doc, "shadowedIframe", {});
  ok(!("shadowedIframe" in doc), "Named properties should not be exposed through Xrays");

  // Named properties live on the named properties object for global objects,
  // but are not exposed via Xrays.
  checkWindowXrayProperty(win, "iframe", {});
  ok(!("iframe" in win), "Named properties should not be exposed through Xrays");

  // Window properties live on the instance, shadowing the properties of the named property object.
  checkWindowXrayProperty(win, "document", { windowValue: doc });
  ok("document" in win, "WebIDL properties should be exposed through Xrays");

  // Unforgeable properties live on the instance, shadowing the properties of the named property object.
  checkWindowXrayProperty(win, "self", { windowValue: win });
  ok("self" in win, "WebIDL properties should be exposed through Xrays");

  // Named properties live on the instance for non-global objects, but are not
  // exposed via Xrays.
  checkDocumentXrayProperty(doc, "iframe", {});
  ok(!("iframe" in doc), "Named properties should not be exposed through Xrays");

  // Object.prototype is at the end of the prototype chain.
  var obj = win;
  var proto;
  while ((proto = Object.getPrototypeOf(obj))) {
    obj = proto;
  }
  is(obj, win.Object.prototype, "Object.prototype should be at the end of the prototype chain");

  // Named properties shouldn't shadow WebIDL- or ECMAScript-defined properties.
  checkWindowXrayProperty(win, "addEventListener", { eventTargetPrototypeValue: eventTargetProto.addEventListener });
  is(win.addEventListener, eventTargetProto.addEventListener, "Named properties shouldn't shadow WebIDL-defined properties");

  is(win.toString, win.Object.prototype.toString, "Named properties shouldn't shadow ECMAScript-defined properties");

  // WebIDL interface names should be exposed.
  var waivedWin = Cu.waiveXrays(win);
  checkWindowXrayProperty(win, "Element", { windowValue: Cu.unwaiveXrays(waivedWin.Element) });

  // JS standard classes should be exposed.
  checkWindowXrayProperty(win, "Array", { windowValue: Cu.unwaiveXrays(waivedWin.Array) });

  // HTMLDocument
  // Unforgeable properties live on the instance.
  checkXrayProperty(doc, "location", [ win.location ]);
  is(String(win.location), document.getElementById("t").src,
     "Should have the right stringification");

  // HTMLHtmlElement
  var elem = doc.documentElement;

  var elemProto = Object.getPrototypeOf(elem);
  is(elemProto, win.HTMLHtmlElement.prototype, "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");

  elemProto = Object.getPrototypeOf(elemProto);
  is(elemProto, win.HTMLElement.prototype, "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");

  elemProto = Object.getPrototypeOf(elemProto);
  is(elemProto, win.Element.prototype, "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");

  elemProto = Object.getPrototypeOf(elemProto);
  is(elemProto, win.Node.prototype, "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");

  elemProto = Object.getPrototypeOf(elemProto);
  is(elemProto, win.EventTarget.prototype, "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");

  // Xrays need to filter expandos.
  ok(!("expando" in elem), "Xrays should filter expandos");

  // WebIDL-defined properties live on the prototype.
  checkXrayProperty(elem, "version", [ undefined, "" ]);
  is(elem.version, "", "WebIDL properties should be exposed through Xrays");

  // HTMLCollection
  var coll = doc.getElementsByTagName("iframe");

  // Named properties live on the instance for non-global objects.
  checkXrayProperty(coll, "iframe", [ doc.getElementById("iframe") ]);

  // Indexed properties live on the instance.
  checkXrayProperty(coll, 0, [ doc.getElementById("shadowedIframe") ]);

  // WebIDL-defined properties live on the prototype, overriding any named properties.
  checkXrayProperty(coll, "item", [ undefined, win.HTMLCollection.prototype.item ]);

  // ECMAScript-defined properties live on the prototype, overriding any named properties.
  checkXrayProperty(coll, "toString", [ undefined, undefined, win.Object.prototype.toString ]);

  // Frozen arrays should come from our compartment, not the target one.
  var languages1 = win.navigator.languages;
  isnot(languages1, undefined, "Must have .languages");
  ok(Array.isArray(languages1), ".languages should be an array");
  ok(Object.isFrozen(languages1), ".languages should be a frozen array");
  ok(!Cu.isXrayWrapper(languages1), "Should have our own version of array");
  is(Cu.getGlobalForObject(languages1), window,
     "languages1 should come from our window");
  // We want to get .languages in the content compartment, but without waiving
  // Xrays altogether.
  var languages2 = win.eval("navigator.languages");
  isnot(languages2, undefined, "Must still have .languages");
  ok(Array.isArray(languages2), ".languages should still be an array");
  ok(Cu.isXrayWrapper(languages2), "Should have xray for content version of array");
  is(Cu.getGlobalForObject(languages2), win,
     "languages2 come from the underlying window");
  ok(Object.isFrozen(languages2.wrappedJSObject),
     ".languages should still be a frozen array underneath");
  isnot(languages1, languages2, "Must have distinct arrays");
  isnot(languages1, languages2.wrappedJSObject,
        "Must have distinct arrays no matter how we slice it");

  // Check that deleters work correctly in the [OverrideBuiltins] case.
  elem = win.document.documentElement;
  var dataset = elem.dataset;
  is(dataset.foo, undefined, "Should not have a 'foo' property");
  ok(!("foo" in dataset), "Really should not have a 'foo' property");
  is(elem.getAttribute("data-foo"), null,
     "Should not have a 'data-foo' attribute");
  ok(!elem.hasAttribute("data-foo"),
     "Really should not have a 'data-foo' attribute");
  dataset.foo = "bar";
  is(dataset.foo, "bar", "Should now have a 'foo' property");
  ok("foo" in dataset, "Really should have a 'foo' property");
  is(elem.getAttribute("data-foo"), "bar",
     "Should have a 'data-foo' attribute");
  ok(elem.hasAttribute("data-foo"),
     "Really should have a 'data-foo' attribute");
  delete dataset.foo;
  is(dataset.foo, undefined, "Should not have a 'foo' property again");
  ok(!("foo" in dataset), "Really should not have a 'foo' property again");
  is(elem.getAttribute("data-foo"), null,
     "Should not have a 'data-foo' attribute again");
  ok(!elem.hasAttribute("data-foo"),
     "Really should not have a 'data-foo' attribute again");

  // Check that deleters work correctly in the non-[OverrideBuiltins] case.
  var storage = win.sessionStorage;
  is(storage.foo, undefined, "Should not have a 'foo' property");
  ok(!("foo" in storage), "Really should not have a 'foo' property");
  is(storage.getItem("foo"), null, "Should not have an item named 'foo'");
  storage.foo = "bar";
  is(storage.foo, "bar", "Should have a 'foo' property");
  ok("foo" in storage, "Really should have a 'foo' property");
  is(storage.getItem("foo"), "bar", "Should have an item named 'foo'");
  delete storage.foo;
  is(storage.foo, undefined, "Should not have a 'foo' property again");
  ok(!("foo" in storage), "Really should not have a 'foo' property again");
  is(storage.getItem("foo"), null, "Should not have an item named 'foo' again");

  // Non-static properties are not exposed on interface objects or instances.
  is(win.HTMLInputElement.checkValidity, undefined,
     "Shouldn't see non-static property on interface objects");
  is(Object.getOwnPropertyDescriptor(win.HTMLInputElement, "checkValidity"), undefined,
     "Shouldn't see non-static property on interface objects");
  is(Object.getOwnPropertyNames(win.HTMLInputElement).indexOf("checkValidity"), -1,
     "Shouldn't see non-static property on interface objects");
  isnot(typeof doc.createElement("input").checkValidity, "undefined",
        "Should see non-static property on prototype objects");
  is(Object.getOwnPropertyDescriptor(doc.createElement("input"), "checkValidity"), undefined,
     "Shouldn't see non-static property on instances");
  isnot(typeof Object.getOwnPropertyDescriptor(win.HTMLInputElement.prototype, "checkValidity"), "undefined",
        "Should see non-static property on prototype objects");

  // Static properties are not exposed on prototype objects or instances.
  isnot(typeof win.URL.createObjectURL, "undefined",
        "Should see static property on interface objects");
  isnot(typeof Object.getOwnPropertyDescriptor(win.URL, "createObjectURL"), "undefined",
        "Should see static property on interface objects");
  isnot(Object.getOwnPropertyNames(win.URL).indexOf("createObjectURL"), -1,
        "Should see static property on interface objects");
  is(new URL("http://example.org").createObjectURL, undefined,
     "Shouldn't see static property on instances and prototype ojbects");
  is(Object.getOwnPropertyDescriptor(new URL("http://example.org"), "createObjectURL"), undefined,
     "Shouldn't see static property on instances");
  is(Object.getOwnPropertyDescriptor(win.URL.prototype, "createObjectURL"), undefined,
     "Shouldn't see static property on prototype objects");

  // Unforgeable properties are not exposed on prototype objects or interface
  // objects.
  is(Window.document, undefined,
     "Shouldn't see unforgeable property on interface objects");
  is(Object.getOwnPropertyDescriptor(Window, "document"), undefined,
     "Shouldn't see unforgeable property on interface objects");
  is(Object.getOwnPropertyNames(Window).indexOf("document"), -1,
     "Shouldn't see unforgeable property on interface objects");
  isnot(typeof win.document, "undefined",
        "Should see unforgeable property on instances");
  isnot(typeof Object.getOwnPropertyDescriptor(win, "document"), "undefined",
        "Should see unforgeable property on instances");
  is(Object.getOwnPropertyDescriptor(Window.prototype, "document"), undefined,
     "Shouldn't see unforgeable property on prototype objects");

  // Constant properties are not exposted on instances.
  isnot(typeof win.Node.ELEMENT_NODE, "undefined",
        "Should see constant property on interface objects");
  isnot(typeof Object.getOwnPropertyDescriptor(win.Node, "ELEMENT_NODE"), "undefined",
        "Should see constant property on interface objects");
  isnot(Object.getOwnPropertyNames(win.Node).indexOf("ELEMENT_NODE"), -1,
        "Should see constant property on interface objects");
  isnot(typeof elem.ELEMENT_NODE, "undefined",
        "Should see constant property on prototype objects");
  is(Object.getOwnPropertyDescriptor(elem, "ELEMENT_NODE"), undefined,
     "Shouldn't see constant property on instances");
  isnot(typeof Object.getOwnPropertyDescriptor(win.Node.prototype, "ELEMENT_NODE"), "undefined",
        "Should see constant property on prototype objects");

  // Interfaces can have both static and non-static properties with the same name.
  isnot(typeof win.TestFunctions.staticAndNonStaticOverload, "undefined",
        "Should see static property on interface objects (even with non-static property with the same name)");
  isnot(typeof Object.getOwnPropertyDescriptor(win.TestFunctions, "staticAndNonStaticOverload"), "undefined",
        "Should see static property on interface objects (even with non-static property with the same name)");
  isnot(Object.getOwnPropertyNames(win.TestFunctions).indexOf("staticAndNonStaticOverload"), -1,
        "Should see static property on interface objects (even with non-static property with the same name)");
  isnot(typeof (new win.TestFunctions("")).staticAndNonStaticOverload, "undefined",
        "Should see non-static property on prototype objects (even with static property with the same name)");
  let testFunctions = new win.TestFunctions();
  is(Object.getOwnPropertyDescriptor(testFunctions, "staticAndNonStaticOverload"), undefined,
     "Shouldn't see non-static property on instances (even with static property with the same name)");
  ok(!testFunctions.staticAndNonStaticOverload(),
     "Should call the non-static overload on the instance");
  ok(win.TestFunctions.staticAndNonStaticOverload(),
     "Should call the static overload on the interface object");
  isnot(typeof Object.getOwnPropertyDescriptor(win.TestFunctions.prototype, "staticAndNonStaticOverload"), "undefined",
        "Should see non-static property on prototype objects (even with static property with the same name)");
  is(Object.getOwnPropertyDescriptor(win.TestFunctions, "staticAndNonStaticOverload").value,
     Object.getOwnPropertyDescriptor(win.TestFunctions, "staticAndNonStaticOverload").value,
     "Should get the same value when getting the static property twice");
  is(Object.getOwnPropertyDescriptor(win.TestFunctions.prototype, "staticAndNonStaticOverload").value,
     Object.getOwnPropertyDescriptor(win.TestFunctions.prototype, "staticAndNonStaticOverload").value,
     "Should get the same value when getting the non-static property twice");
  isnot(Object.getOwnPropertyDescriptor(win.TestFunctions, "staticAndNonStaticOverload").value,
        Object.getOwnPropertyDescriptor(win.TestFunctions.prototype, "staticAndNonStaticOverload").value,
        "Should get different values for static and non-static properties with the same name");

  // Adopting nodes should not lose expandos.
  elem = document.createElement("span");
  elem.expando = 5;
  is(elem.expando, 5, "We just set this property");
  document.adoptNode(elem);
  is(elem.wrappedJSObject, undefined, "Shouldn't be an Xray anymore");
  is(elem.expando, 5, "Expando should not get lost");

  // Instanceof tests
  var img = doc.createElement("img");
  var img2 = document.createElement("img");
  ok(img instanceof win.HTMLImageElement,
     "Should be an instance of HTMLImageElement from its global");
  ok(win.HTMLImageElement.isInstance(img), "isInstance should work");
  ok(HTMLImageElement.isInstance(img), "isInstance should work cross-global");
  ok(win.HTMLImageElement.isInstance(img2),
     "isInstance should work cross-global in the other direction");
  ok(img instanceof win.Image,
     "Should be an instance of Image, because Image.prototype == HTMLImageElement.prototype");
  ok(!win.Image.isInstance, "Shouldn't have an isInstance method here");
  // Image does not have a Symbol.hasInstance, but its proto
  // (Function.prototype) does.
  checkXrayProperty(win.Image, Symbol.hasInstance,
                    [undefined, win.Function.prototype[Symbol.hasInstance]]);

  // toString/@@toStringTag
  let imageConstructor = win.Image;
  is(win.Function.prototype.toString.apply(imageConstructor),
     Function.prototype.toString.apply(Image),
     "Applying Function.prototype.toString through an Xray should give the same result as applying it directly");
  isDeeply(Object.getOwnPropertyDescriptor(win.CSS, Symbol.toStringTag),
           Object.getOwnPropertyDescriptor(CSS, Symbol.toStringTag),
           "Getting @@toStringTag on a namespace object through an Xray should give the same result as getting it directly");

  // legacyCaller should work.
  ok(win.HTMLAllCollection.isInstance(doc.all),
     "HTMLDocument.all should be an instance of HTMLAllCollection");
  let element, threw;
  try {
    threw = false;
    element = doc.all(0);
  } catch (e) {
    threw = true;
  }
  ok(!threw,
     "Calling an instance object for an interface marked with legacycaller shouldn't throw");
  checkXrayProperty(doc.all, 0, [ element ]);

  SimpleTest.finish();
}

SimpleTest.waitForExplicitFinish();
SimpleTest.requestLongerTimeout(2);

addLoadEvent(() => {
  SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]},
                            test);
});

</script>
</pre>
</body>
</html>