]> sipb.mit.edu Git - wiki.git/blob - names/winchan.js
add name change tool
[wiki.git] / names / winchan.js
1 ;WinChan = (function() {
2   var RELAY_FRAME_NAME = "__winchan_relay_frame";
3   var CLOSE_CMD = "die";
4
5   // a portable addListener implementation
6   function addListener(w, event, cb) {
7     if(w.attachEvent) w.attachEvent('on' + event, cb);
8     else if (w.addEventListener) w.addEventListener(event, cb, false);
9   }
10
11   // a portable removeListener implementation
12   function removeListener(w, event, cb) {
13     if(w.detachEvent) w.detachEvent('on' + event, cb);
14     else if (w.removeEventListener) w.removeEventListener(event, cb, false);
15   }
16
17   // checking for IE8 or above
18   function isInternetExplorer() {
19     var rv = -1; // Return value assumes failure.
20     if (navigator.appName === 'Microsoft Internet Explorer') {
21       var ua = navigator.userAgent;
22       var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
23       if (re.exec(ua) != null)
24         rv = parseFloat(RegExp.$1);
25     }
26     return rv >= 8;
27   }
28
29   // checking Mobile Firefox (Fennec)
30   function isFennec() {
31     try {
32       // We must check for both XUL and Java versions of Fennec.  Both have
33       // distinct UA strings.
34       var userAgent = navigator.userAgent;
35       return (userAgent.indexOf('Fennec/') != -1) ||  // XUL
36              (userAgent.indexOf('Firefox/') != -1 && userAgent.indexOf('Android') != -1);   // Java
37     } catch(e) {};
38     return false;
39   }
40
41   // feature checking to see if this platform is supported at all
42   function isSupported() {
43     return (window.JSON && window.JSON.stringify &&
44             window.JSON.parse && window.postMessage);
45   }
46
47   // given a URL, extract the origin
48   function extractOrigin(url) {
49     if (!/^https?:\/\//.test(url)) url = window.location.href;
50     var m = /^(https?:\/\/[\-_a-zA-Z\.0-9:]+)/.exec(url);
51     if (m) return m[1];
52     return url;
53   }
54
55   // find the relay iframe in the opener
56   function findRelay() {
57     var loc = window.location;
58     var frames = window.opener.frames;
59     var origin = loc.protocol + '//' + loc.host;
60     for (var i = frames.length - 1; i >= 0; i--) {
61       try {
62         if (frames[i].location.href.indexOf(origin) === 0 &&
63             frames[i].name === RELAY_FRAME_NAME)
64         {
65           return frames[i];
66         }
67       } catch(e) { }
68     }
69     return;
70   }
71
72   var isIE = isInternetExplorer();
73
74   if (isSupported()) {
75     /*  General flow:
76      *                  0. user clicks
77      *  (IE SPECIFIC)   1. caller adds relay iframe (served from trusted domain) to DOM
78      *                  2. caller opens window (with content from trusted domain)
79      *                  3. window on opening adds a listener to 'message'
80      *  (IE SPECIFIC)   4. window on opening finds iframe
81      *                  5. window checks if iframe is "loaded" - has a 'doPost' function yet
82      *  (IE SPECIFIC5)  5a. if iframe.doPost exists, window uses it to send ready event to caller
83      *  (IE SPECIFIC5)  5b. if iframe.doPost doesn't exist, window waits for frame ready
84      *  (IE SPECIFIC5)  5bi. once ready, window calls iframe.doPost to send ready event
85      *                  6. caller upon reciept of 'ready', sends args
86      */
87     return {
88       open: function(opts, cb) {
89         if (!cb) throw "missing required callback argument";
90
91         // test required options
92         var err;
93         if (!opts.url) err = "missing required 'url' parameter";
94         if (!opts.relay_url) err = "missing required 'relay_url' parameter";
95         if (err) setTimeout(function() { cb(err); }, 0);
96
97         // supply default options
98         if (!opts.window_name) opts.window_name = null;
99         if (!opts.window_features || isFennec()) opts.window_features = undefined;
100
101         // opts.params may be undefined
102
103         var iframe;
104
105         // sanity check, are url and relay_url the same origin?
106         var origin = extractOrigin(opts.url);
107         if (origin !== extractOrigin(opts.relay_url)) {
108           return setTimeout(function() {
109             cb('invalid arguments: origin of url and relay_url must match');
110           }, 0);
111         }
112
113         var messageTarget;
114
115         if (isIE) {
116           // first we need to add a "relay" iframe to the document that's served
117           // from the target domain.  We can postmessage into a iframe, but not a
118           // window
119           iframe = document.createElement("iframe");
120           // iframe.setAttribute('name', framename);
121           iframe.setAttribute('src', opts.relay_url);
122           iframe.style.display = "none";
123           iframe.setAttribute('name', RELAY_FRAME_NAME);
124           document.body.appendChild(iframe);
125           messageTarget = iframe.contentWindow;
126         }
127
128         var w = window.open(opts.url, opts.window_name, opts.window_features);
129
130         if (!messageTarget) messageTarget = w;
131
132         var req = JSON.stringify({a: 'request', d: opts.params});
133
134         // cleanup on unload
135         function cleanup() {
136           if (iframe) document.body.removeChild(iframe);
137           iframe = undefined;
138           if (w) {
139             try {
140               w.close();
141             } catch (securityViolation) {
142               // This happens in Opera 12 sometimes
143               // see https://github.com/mozilla/browserid/issues/1844
144               messageTarget.postMessage(CLOSE_CMD, origin);
145             }
146           }
147           w = messageTarget = undefined;
148         }
149
150         addListener(window, 'unload', cleanup);
151
152         function onMessage(e) {
153           try {
154             var d = JSON.parse(e.data);
155             if (d.a === 'ready') messageTarget.postMessage(req, origin);
156             else if (d.a === 'error') {
157               if (cb) {
158                 cb(d.d);
159                 cb = null;
160               }
161             } else if (d.a === 'response') {
162               removeListener(window, 'message', onMessage);
163               removeListener(window, 'unload', cleanup);
164               cleanup();
165               if (cb) {
166                 cb(null, d.d);
167                 cb = null;
168               }
169             }
170           } catch(err) { }
171         }
172
173         addListener(window, 'message', onMessage);
174
175         return {
176           close: cleanup,
177           focus: function() {
178             if (w) {
179               try {
180                 w.focus();
181               } catch (e) {
182                 // IE7 blows up here, do nothing
183               }
184             }
185           }
186         };
187       },
188       onOpen: function(cb) {
189         var o = "*";
190         var msgTarget = isIE ? findRelay() : window.opener;
191         if (!msgTarget) throw "can't find relay frame";
192         function doPost(msg) {
193           msg = JSON.stringify(msg);
194           if (isIE) msgTarget.doPost(msg, o);
195           else msgTarget.postMessage(msg, o);
196         }
197
198         function onMessage(e) {
199           // only one message gets through, but let's make sure it's actually
200           // the message we're looking for (other code may be using
201           // postmessage) - we do this by ensuring the payload can
202           // be parsed, and it's got an 'a' (action) value of 'request'.
203           var d;
204           try {
205             d = JSON.parse(e.data);
206           } catch(err) { }
207           if (!d || d.a !== 'request') return;
208           removeListener(window, 'message', onMessage);
209           o = e.origin;
210           if (cb) {
211             // this setTimeout is critically important for IE8 -
212             // in ie8 sometimes addListener for 'message' can synchronously
213             // cause your callback to be invoked.  awesome.
214             setTimeout(function() {
215               cb(o, d.d, function(r) {
216                 cb = undefined;
217                 doPost({a: 'response', d: r});
218               });
219             }, 0);
220           }
221         }
222
223         function onDie(e) {
224           if (e.data === CLOSE_CMD) {
225             try { window.close(); } catch (o_O) {}
226           }
227         }
228         addListener(isIE ? msgTarget : window, 'message', onMessage);
229         addListener(isIE ? msgTarget : window, 'message', onDie);
230
231         // we cannot post to our parent that we're ready before the iframe
232         // is loaded. (IE specific possible failure)
233         try {
234           doPost({a: "ready"});
235         } catch(e) {
236           // this code should never be exectued outside IE
237           addListener(msgTarget, 'load', function(e) {
238             doPost({a: "ready"});
239           });
240         }
241
242         // if window is unloaded and the client hasn't called cb, it's an error
243         var onUnload = function() {
244           try {
245             // IE8 doesn't like this...
246             removeListener(isIE ? msgTarget : window, 'message', onDie);
247           } catch (ohWell) { }
248           if (cb) doPost({ a: 'error', d: 'client closed window' });
249           cb = undefined;
250           // explicitly close the window, in case the client is trying to reload or nav
251           try { window.close(); } catch (e) { }
252         };
253         addListener(window, 'unload', onUnload);
254         return {
255           detach: function() {
256             removeListener(window, 'unload', onUnload);
257           }
258         };
259       }
260     };
261   } else {
262     return {
263       open: function(url, winopts, arg, cb) {
264         setTimeout(function() { cb("unsupported browser"); }, 0);
265       },
266       onOpen: function(cb) {
267         setTimeout(function() { cb("unsupported browser"); }, 0);
268       }
269     };
270   }
271 })();