aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.3/packages/api-utils/tests/test-content-proxy.js
blob: 47e168a346ac8c7d1e8f22823ffd00af6f6578b3 (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
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
const xulApp = require("xul-app");
const proxy = require("content/content-proxy");
const hiddenFrames = require("hidden-frame");

exports.testProxy = function (test) {
  let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' + 
             '<input id="input2" type="checkbox" />' + 
             '<script>var documentGlobal = true</script>' +
             '<iframe id="iframe" name="test" src="data:text/html," />';
  let url = 'data:text/html,' + encodeURI(html);
  test.waitUntilDone();
  
  let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({
    onReady: function () {
      
      function onDOMReady() {
        hiddenFrame.element.removeEventListener("DOMContentLoaded", onDOMReady,
                                                false);
        let win = hiddenFrame.element.contentWindow.wrappedJSObject;
        
        test.assert(win.documentGlobal, "`win` object is unwrapped");
        
        let wrapped = proxy.create(win);
        let document = wrapped.document;
        let body = document.body;


        // Ensure that postMessage is working correctly.
        // 1/ Check across documents with an iframe
        let ifWindow = win.document.getElementById("iframe").contentWindow;
        // Listen without proxies, to check that it will work in regular case
        // simulate listening from a web document.
        ifWindow.addEventListener("message", function listener(event) {
          ifWindow.removeEventListener("message", listener, false);
          // As we are in system principal, event is an XrayWrapper
          test.assertEqual(event.source.wrappedJSObject, ifWindow,
                           "event.source is the iframe window");
          test.assertEqual(event.origin, "null", "origin is null");
          test.assertEqual(event.data, "ok", "message data is correct");
        }, false);
        // Dispatch a message from the content script proxy
        document.getElementById("iframe").contentWindow.postMessage("ok", "*");

        test.assertEqual(wrapped.postMessage, wrapped.postMessage,
          "verify that we doesn't generate multiple functions for the same method");


        // Test objects being given as event listener
        let input = document.getElementById("input2");
        let myClickListener = {
          called: false,
          handleEvent: function(event) {
            test.assertEqual(this, myClickListener, "`this` is the original object");
            test.assert(!this.called, "called only once");
            this.called = true;
            test.assertNotEqual(event.valueOf(), event.valueOf(proxy.UNWRAP_ACCESS_KEY), "event is wrapped");
            test.assertEqual(event.target, input, "event.target is the wrapped window");
          }
        };

        wrapped.addEventListener("click", myClickListener, true);
        input.click();
        wrapped.removeEventListener("click", myClickListener, true);

        // Verify object as DOM event listener
        let myMessageListener = {
          called: false,
          handleEvent: function(event) {
            wrapped.removeEventListener("message", myMessageListener, true);

            test.assertEqual(this, myMessageListener, "`this` is the original object");
            test.assert(!this.called, "called only once");
            this.called = true;
            test.assertNotEqual(event.valueOf(), event.valueOf(proxy.UNWRAP_ACCESS_KEY), "event is wrapped");
            test.assertEqual(event.target, wrapped, "event.target is the wrapped window");
            test.assertEqual(event.source, wrapped, "event.source is the wrapped window");
            test.assertEqual(event.origin, "null", "origin is null");
            test.assertEqual(event.data, "ok", "message data is correct");
          }
        };

        wrapped.addEventListener("message", myMessageListener, true);
        wrapped.postMessage("ok", '*');


        // RightJS is hacking around String.prototype, and do similar thing:
        // Pass `this` from a String prototype method.
        // It is funny because typeof this == "object"!
        // So that when we pass `this` to a native method,
        // our proxy code can fail on another even more crazy thing.
        // See following test to see what fails around proxies.
        String.prototype.update = function () {
          test.assertEqual(typeof this, "object");
          test.assertEqual(this.toString(), "input");
          return document.querySelectorAll(this);
        };
        test.assertEqual("input".update().length, 3, "String.prototype overload works");

        // Proxy - toString error
        let originalString = "string";
        let p = Proxy.create({
          get: function(receiver, name) {
            if (name == "binded")
              return originalString.toString.bind(originalString);
            return originalString[name];
          }
        });
        test.assertRaises(function () {
          p.toString();
        },
        /String.prototype.toString called on incompatible Proxy/,
        "toString can't be called with this being the proxy");
        test.assertEqual(p.binded(), "string", "but it works if we bind this to the original string");


        // Check mozMatchesSelector XrayWrappers bug:
        // mozMatchesSelector returns bad results when we are not calling it from the node itself
        // SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers
        test.assert(document.createElement( "div" ).mozMatchesSelector("div"), 
                    "mozMatchesSelector works while being called from the node");
        test.assert(document.documentElement.mozMatchesSelector.call(
                      document.createElement( "div" ), 
                      "div" 
                    ), 
                    "mozMatchesSelector works while being called from a " +
                    "function reference to " +
                    "document.documentElement.mozMatchesSelector.call");
        
        // If we add a "____proxy" attribute on XrayWrappers in order to store
        // the related proxy to create an unique proxy for each wrapper;
        // we end up setting this attribute to prototype objects :x
        // And so, instances created with such prototype will be considered 
        // as equal to the prototype ...
        //   // Internal method that return the proxy for a given XrayWrapper
        //   function proxify(obj) {
        //     if (obj._proxy) return obj._proxy;
        //     return obj._proxy = Proxy.create(...);
        //   }
        //   
        //   // Get a proxy of a XrayWrapper prototype object
        //   let proto = proxify(xpcProto);
        //   
        //   // Use this proxy as a prototype 
        //   function Constr() {}
        //   Constr.proto = proto;
        //   
        //   // Try to create an instance using this prototype
        //   let xpcInstance = new Constr();
        //   let wrapper = proxify(xpcInstance)
        //
        //   xpcProto._proxy = proto and as xpcInstance.__proto__ = xpcProto,
        //   xpcInstance._proxy = proto ... and profixy(xpcInstance) = proto :(
        //
        let proto = wrapped.document.createEvent('HTMLEvents').__proto__;
        wrapped.Event.prototype = proto;
        let event = document.createEvent('HTMLEvents');
        test.assertNotEqual(event, proto, "Event should not be equal to its prototype");
        event.initEvent('dataavailable', true, true);
        test.assertEqual(event.type, 'dataavailable', "Events are working fine");
        
        // XrayWrappers has a bug when you set an attribute on it,
        // in some cases, it creates an unnecessary wrapper that introduces
        // a different object that refers to the same original object
        // Check that our wrappers don't reproduce this bug
        // SEE BUG 658560: Fix identity problem with CrossOriginWrappers
        let o = {sandboxObject:true};
        wrapped.nested = o;
        o.foo = true;
        test.assertEqual(o, wrapped.nested, "Nested attribute to sandbox object should not be proxified");
        wrapped.nested = document;
        test.assertEqual(wrapped.nested, document, "Nested attribute to proxy should not be double proxified");
        
        // Check form[nodeName]
        let form = document.createElement("form");
        let input = document.createElement("input");
        input.setAttribute("name", "test");
        form.appendChild(input);
        body.appendChild(form);
        test.assertEqual(form.test, input, "form[nodeName] is valid");
        body.removeChild(form);
        
        // Check localStorage:
        test.assert(wrapped.localStorage, "has access to localStorage");
        wrapped.localStorage.name = 1;
        test.assertEqual(wrapped.localStorage.name, 1, "localStorage appears to work");
        test.assertEqual(win.localStorage.name, 1, "localStorage really works");
        wrapped.localStorage.clear();
        test.assertEqual(wrapped.localStorage.name, undefined, "localStorage really, really works");
        
        // Check sessionStorage:
        /*
          // Does not work on data: uri
        console.log(wrapped.sessionStorage);
        //test.assert(wrapped.sessionStorage, "has access to localStorage");
        wrapped.sessionStorage.setItem('name', 1);
        test.assertEqual(wrapped.sessionStorage.getItem('name'), 1, "localStorage appears to work");
        test.assertEqual(win.sessionStorage.getItem('name'), 1, "localStorage really work");
        wrapped.sessionStorage.clear();
        test.assert(!wrapped.sessionStorage.hasItem('name'), "localStorage really,really work");
        */
        
        // Setting a custom object to a proxy attribute is not wrapped when we get it afterward
        let object = {custom: true, enumerable: false};
        body.customAttribute = object;
        test.assertEqual(body.customAttribute.valueOf(), body.customAttribute.valueOf(proxy.UNWRAP_ACCESS_KEY), "custom JS attributes are not wrapped");
        test.assertEqual(object, body.customAttribute, "custom JS attributes are not wrapped");
        
        /*
        let originalToString = win.Object.prototype.toString;
        win.Object.prototype.toString = function overloadedToString() {
          return originalToString.apply(this.valueOf(proxy.UNWRAP_ACCESS_KEY));
        };
        */
        // <object>, <embed> and other tags return typeof 'function'
        let flash = document.createElement("object");
        test.assertEqual(typeof flash, "function", "<object> is typeof 'function'");
        test.assertMatches(flash.toString(), /\[object HTMLObjectElement.*\]/, "<object> is HTMLObjectElement");
        test.assert("setAttribute" in flash, "<object> has a setAttribute method");
        flash.setAttribute("classid", "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000");
        // This is how jquery call toString:
        test.assertMatches(win.Object.prototype.toString.call(""), /\[object String.*\]/, "strings are strings");
        test.assertMatches(win.Object.prototype.toString.call({}), /\[object Object.*\]/, "objects are objects");
        // We do not have any workaround this particular use of toString
        // applied on <object> elements. So disable this test until we found one!
        //test.assertEqual(win.Object.prototype.toString.call(flash), "[object HTMLObjectElement]", "<object> is HTMLObjectElement");
        function f() {};
        test.assertMatches(Object.prototype.toString.call(f), /\[object Function.*\]/, "functions are functions 1");
        // Make sure to pass a function from the same compartments
        // or toString will return [object Object] on FF8+
        let f2 = win.eval("(function () {})");
        test.assertMatches(win.Object.prototype.toString.call(f2), /\[object Function.*\]/, "functions are functions 2");
        
        // Verify isolated JS values
        test.assert(!wrapped.documentGlobal, "proxy doesn't expose document variable");
        
        // Check document[tagName]
        let div = document.createElement("div");
        div.setAttribute("name", "test");
        body.appendChild(div);
        test.assert(!document.test, "document[divName] is undefined");
        body.removeChild(div);
        
        let form = document.createElement("form");
        form.setAttribute("name", "test");
        body.appendChild(form);
        test.assertEqual(document.test, form, "document[formName] is valid");
        body.removeChild(form);
        
        let img = document.createElement("img");
        img.setAttribute("name", "test");
        body.appendChild(img);
        test.assertEqual(document.test, img, "document[imgName] is valid");
        body.removeChild(img);
        
        // Check window[frameName] and window.frames[i]
        let iframe = document.getElementById("iframe");
        test.assertEqual(wrapped.frames.length, 1, "The iframe is reported in window.frames check1");
        test.assertEqual(wrapped.frames[0], iframe.contentWindow, "The iframe is reported in window.frames check2");
        test.assertEqual(wrapped.test, iframe.contentWindow, "window[frameName] is valid");
        
        // Highlight XPCNativeWrapper bug with HTMLCollection
        // tds[0] is only defined on first access :o
        let div = document.createElement("div");
        body.appendChild(div);
        div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
        let tds = div.getElementsByTagName("td");
        test.assertEqual(tds[0], tds[0], "We can get array element multiple times");
        body.removeChild(div);
        
        // Verify that NodeList/HTMLCollection are working fine
        let inputs = body.getElementsByTagName("input");
        test.assertEqual(body.childNodes.length, 5, "body.childNodes length is correct");
        test.assertEqual(inputs.length, 3, "inputs.length is correct");
        test.assertEqual(body.childNodes[0], inputs[0], "body.childNodes[0] is correct");
        test.assertEqual(body.childNodes[1], inputs[1], "body.childNodes[1] is correct");
        test.assertEqual(body.childNodes[2], inputs[2], "body.childNodes[2] is correct");
        let count = 0;
        for(let i in body.childNodes) {
          count++;
        }
        test.assertEqual(count, 5, "body.childNodes is iterable");
        
        // Check internal use of valueOf()
        test.assertMatches(wrapped.valueOf().toString(), /\[object Window.*\]/, "proxy.valueOf() returns the wrapped version");
        test.assertMatches(wrapped.valueOf({}).toString(), /\[object Window.*\]/, "proxy.valueOf({}) returns the wrapped version");
        test.assertMatches(wrapped.valueOf(proxy.UNWRAP_ACCESS_KEY).toString(), /\[object XrayWrapper \[object Window.*\].*\]/, "proxy.valueOf(UNWRAP_ACCESS_KEY) returns the unwrapped version");
        
        // XMLHttpRequest doesn't support XMLHttpRequest.apply,
        // that may break our proxy code
        test.assert(wrapped.XMLHttpRequest(), "we are able to instantiate XMLHttpRequest object");
        
        // Check XPathResult bug with constants being undefined on 
        // XPCNativeWrapper
        let value = 
          win.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE;
        let xpcXPathResult = XPCNativeWrapper(win).XPathResult;
        test.assertEqual(xpcXPathResult.wrappedJSObject.
                           UNORDERED_NODE_SNAPSHOT_TYPE, 
                         value, 
                         "XPathResult's constants are valid on unwrapped node");

        if (xulApp.versionInRange(xulApp.platformVersion, "10.0a1", "*")) {
          test.assertEqual(xpcXPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, 6, 
                           "XPathResult's constants are defined on " + 
                           "XPCNativeWrapper (platform bug #)");
        }
        else {
          test.assertEqual(xpcXPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, 
                           undefined, 
                           "XPathResult's constants are undefined on " + 
                           "XPCNativeWrapper (platform bug #665279)");
          // Check that our work around is working:
          test.assertEqual(wrapped.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, 
                           value, "XPathResult works correctly on Proxies");
        }

        // Verify that inherited prototype function like initEvent 
        // are handled correctly. (e2.type will return an error if it's not the case)
        let event1 = document.createEvent( 'MouseEvents' );
        event1.initEvent( "click", true, true );
        let event2 = document.createEvent( 'MouseEvents' );
        event2.initEvent( "click", true, true );
        test.assert(event2.type, "click", "We are able to create an event");
        
        // Check basic usage of functions
        win.callFunction = function (f) f();
        let closure2 = function () {return "ok";};
        test.assertEqual(wrapped.wrappedJSObject.callFunction(closure2), "ok", "Function references work");
        
        // Ensure that functions are cached when being wrapped to native code
        win.isEqual = function (a, b) a == b;
        let closure = function () {};
        test.assert(wrapped.wrappedJSObject.isEqual(closure, closure), "Function references are cached before being wrapped to native");
        
        // Verify listeners:
        let input = document.getElementById("input2");
        test.assert(input, "proxy.getElementById works");
        
        function onclick() {};
        input.onclick = onclick;
        test.assertEqual(input.onclick, onclick, "on* attributes are equal to original function set");
        
        let addEventListenerCalled = false;
        let expandoCalled = false;
        input.addEventListener("click", function onclick(event) {
          input.removeEventListener("click", onclick, true);
          
          test.assert(!addEventListenerCalled, "closure given to addEventListener is called once");
          if (addEventListenerCalled)
            return;
          addEventListenerCalled = true;
          
          test.assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals");
          test.assert("__isWrappedProxy" in event.target, "event object is a proxy");
          
          let input2 = document.getElementById("input2");
          
          input.onclick = function (event) {
            input.onclick = null;
            test.assert(!expandoCalled, "closure set to expando is called once");
            if (expandoCalled) return;
            expandoCalled = true;
            
            test.assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals");
            test.assert("__isWrappedProxy" in event.target, "event object is a proxy");
            
            require("timer").setTimeout(function () {
              input.click();
              
              // Next test:
              checkRequestAnimation();
            }, 0);
            
          }
          
          require("timer").setTimeout(function () {
            input.click();
          }, 0);
          
        }, true);
        
        input.click();
        
        // Check ContentScriptFunction with function as "this" object
        // For example, window.mozRequestAnimationFrame call its callback 
        // argument with `this` being the callback itself.
        function checkRequestAnimation() {
          wrapped.mozRequestAnimationFrame(function callback() {
            test.assertEqual(callback, this, "callback is equal to `this`");
            
            end();
          });
        }
        
        function end() {
          hiddenFrames.remove(hiddenFrame);
          test.done();
        }
      }
      
      hiddenFrame.element.addEventListener("DOMContentLoaded", onDOMReady, false);
      hiddenFrame.element.setAttribute("src", url);
      
    }
  }));
}