roll skia to 4057
[chromium-blink-merge.git] / chrome_frame / CFInstall.js
blob98c70f44bf7a0b7eeb13252873e94a27d39ffcba
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6  * @fileoverview CFInstall.js provides a set of utilities for managing
7  * the Chrome Frame detection and installation process.
8  * @author slightlyoff@google.com (Alex Russell)
9  */
11 (function(scope) {
12   // bail if we'd be over-writing an existing CFInstall object
13   if (scope['CFInstall']) {
14     return;
15   }
17   /**
18    * returns an item based on DOM ID. Optionally a document may be provided to
19    * specify the scope to search in. If a node is passed, it's returned as-is.
20    * @param {string|Node} id The ID of the node to be located or a node
21    * @param {Node} doc Optional A document to search for id.
22    * @return {Node}
23    */
24   var byId = function(id, doc) {
25     return (typeof id == 'string') ? (doc || document).getElementById(id) : id;
26   };
28   /////////////////////////////////////////////////////////////////////////////
29   // Plugin Detection
30   /////////////////////////////////////////////////////////////////////////////
32   /**
33    * Checks to find out if ChromeFrame is available as a plugin
34    * @return {Boolean}
35    */
36   var isAvailable = function() {
37     // For testing purposes.
38     if (scope.CFInstall._force) {
39       return scope.CFInstall._forceValue;
40     }
42     // Look for CF in the User Agent before trying more expensive checks
43     var ua = navigator.userAgent.toLowerCase();
44     if (ua.indexOf("chromeframe") >= 0) {
45       return true;
46     }
48     if (typeof window['ActiveXObject'] != 'undefined') {
49       try {
50         var obj = new ActiveXObject('ChromeTab.ChromeFrame');
51         if (obj) {
52           obj.registerBhoIfNeeded();
53           return true;
54         }
55       } catch(e) {
56         // squelch
57       }
58     }
59     return false;
60   };
62   /**
63    * Creates a style sheet in the document containing the passed rules.
64    */
65   var injectStyleSheet = function(rules) {
66     try {
67       var ss = document.createElement('style');
68       ss.setAttribute('type', 'text/css');
69       if (ss.styleSheet) {
70         ss.styleSheet.cssText = rules;
71       } else {
72         ss.appendChild(document.createTextNode(rules));
73       }
74       var h = document.getElementsByTagName('head')[0];
75       var firstChild = h.firstChild;
76       h.insertBefore(ss, firstChild);
77     } catch (e) {
78       // squelch
79     }
80   };
82   /** @type {boolean} */
83   var cfStyleTagInjected = false;
84   /** @type {boolean} */
85   var cfHiddenInjected = false;
87   /**
88    * Injects style rules into the document to handle formatting of Chrome Frame
89    * prompt. Multiple calls have no effect.
90    */
91   var injectCFStyleTag = function() {
92     if (cfStyleTagInjected) {
93       // Once and only once
94       return;
95     }
96     var rules = '.chromeFrameInstallDefaultStyle {' +
97                    'width: 800px;' +
98                    'height: 600px;' +
99                    'position: absolute;' +
100                    'left: 50%;' +
101                    'top: 50%;' +
102                    'margin-left: -400px;' +
103                    'margin-top: -300px;' +
104                  '}' +
105                  '.chromeFrameOverlayContent {' +
106                    'position: absolute;' +
107                    'margin-left: -400px;' +
108                    'margin-top: -300px;' +
109                    'left: 50%;' +
110                    'top: 50%;' +
111                    'border: 1px solid #93B4D9;' +
112                    'background-color: white;' +
113                    'z-index: 2001;' +
114                  '}' +
115                  '.chromeFrameOverlayContent iframe {' +
116                    'width: 800px;' +
117                    'height: 600px;' +
118                    'border: none;' +
119                  '}' +
120                  '.chromeFrameOverlayCloseBar {' +
121                    'height: 1em;' +
122                    'text-align: right;' +
123                    'background-color: #CADEF4;' +
124                  '}' +
125                  '.chromeFrameOverlayUnderlay {' +
126                    'position: absolute;' +
127                    'width: 100%;' +
128                    'height: 100%;' +
129                    'background-color: white;' +
130                    'opacity: 0.5;' +
131                    '-moz-opacity: 0.5;' +
132                    '-webkit-opacity: 0.5;' +
133                    '-ms-filter: ' +
134                       '"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";' +
135                    'filter: alpha(opacity=50);' +
136                    'z-index: 2000;' +
137                  '}';
138     injectStyleSheet(rules);
139     cfStyleTagInjected = true;
140   };
142   /**
143    * Injects style rules to hide the overlay version of the GCF prompt.
144    * Multiple calls have no effect.
145    */
146   var closeOverlay = function() {
147     // IE has a limit to the # of <style> tags allowed, so we avoid
148     // tempting the fates.
149     if (cfHiddenInjected) {
150       return;
151     }
152     var rules = '.chromeFrameOverlayContent { display: none; }' +
153                 '.chromeFrameOverlayUnderlay { display: none; }';
154     injectStyleSheet(rules);
155     // Hide the dialog for a year (or until cookies are deleted).
156     var age = 365 * 24 * 60 * 60 * 1000;
157     document.cookie = "disableGCFCheck=1;path=/;max-age="+age;
158     cfHiddenInjected = true;
159   };
161   /**
162    * Plucks properties from the passed arguments and sets them on the passed
163    * DOM node
164    * @param {Node} node The node to set properties on
165    * @param {Object} args A map of user-specified properties to set
166    */
167   var setProperties = function(node, args) {
169     var srcNode = byId(args['node']);
171     node.id = args['id'] || (srcNode ? srcNode['id'] || getUid(srcNode) : '');
173     // TODO(slightlyoff): Opera compat? need to test there
174     var cssText = args['cssText'] || '';
175     node.style.cssText = ' ' + cssText;
177     var classText = args['className'] || '';
178     node.className = classText;
180     // default if the browser doesn't so we don't show sad-tab
181     var src = args['src'] || 'about:blank';
183     node.src = src;
185     if (srcNode) {
186       srcNode.parentNode.replaceChild(node, srcNode);
187     }
188   };
190   /**
191    * Creates an iframe.
192    * @param {Object} args A bag of configuration properties, including values
193    *    like 'node', 'cssText', 'className', 'id', 'src', etc.
194    * @return {Node}
195    */
196   var makeIframe = function(args) {
197     var el = document.createElement('iframe');
198     el.setAttribute('frameborder', '0');
199     el.setAttribute('border', '0');
200     setProperties(el, args);
201     return el;
202   };
204   /**
205    * Adds an unadorned iframe into the page, taking arguments to customize it.
206    * @param {Object} args A map of user-specified properties to set
207    */
208   var makeInlinePrompt = function(args) {
209     args.className = 'chromeFrameInstallDefaultStyle ' +
210                         (args.className || '');
211     var ifr = makeIframe(args);
212     // TODO(slightlyoff): handle placement more elegantly!
213     if (!ifr.parentNode) {
214       var firstChild = document.body.firstChild;
215       document.body.insertBefore(ifr, firstChild);
216     }
217   };
219   /**
220    * Adds a styled, closable iframe into the page with a background that
221    * emulates a modal dialog.
222    * @param {Object} args A map of user-specified properties to set
223    */
224   var makeOverlayPrompt = function(args) {
225     if (byId('chromeFrameOverlayContent')) {
226       return; // Was previously created. Bail.
227     }
229     var n = document.createElement('span');
230     n.innerHTML = '<div class="chromeFrameOverlayUnderlay"></div>' +
231       '<table class="chromeFrameOverlayContent"' +
232              'id="chromeFrameOverlayContent"' +
233              'cellpadding="0" cellspacing="0">' +
234         '<tr class="chromeFrameOverlayCloseBar">' +
235           '<td>' +
236             // TODO(slightlyoff): i18n
237             '<button id="chromeFrameCloseButton">close</button>' +
238           '</td>' +
239         '</tr>' +
240         '<tr>' +
241           '<td id="chromeFrameIframeHolder"></td>' +
242         '</tr>' +
243       '</table>';
245     var b = document.body;
246     // Insert underlay nodes into the document in the right order.
247     while (n.firstChild) {
248       b.insertBefore(n.lastChild, b.firstChild);
249     }
250     var ifr = makeIframe(args);
251     byId('chromeFrameIframeHolder').appendChild(ifr);
252     byId('chromeFrameCloseButton').onclick = closeOverlay;
253   };
255   var CFInstall = {};
257   /**
258    * Checks to see if Chrome Frame is available, if not, prompts the user to
259    * install. Once installation is begun, a background timer starts,
260    * checkinging for a successful install every 2 seconds. Upon detection of
261    * successful installation, the current page is reloaded, or if a
262    * 'destination' parameter is passed, the page navigates there instead.
263    * @param {Object} args A bag of configuration properties. Respected
264    *    properties are: 'mode', 'url', 'destination', 'node', 'onmissing',
265    *    'preventPrompt', 'oninstall', 'preventInstallDetection', 'cssText', and
266    *    'className'.
267    * @public
268    */
269   CFInstall.check = function(args) {
270     args = args || {};
272     // We currently only support CF in IE
273     // TODO(slightlyoff): Update this should we support other browsers!
274     var ua = navigator.userAgent;
275     var ieRe = /MSIE (\S+); Windows NT/;
276     var bail = false;
277     if (ieRe.test(ua)) {
278       // We also only support Win2003/XPSP2 or better. See:
279       //  http://msdn.microsoft.com/en-us/library/ms537503%28VS.85%29.aspx
280       if (parseFloat(ieRe.exec(ua)[1]) < 6 &&
281           // 'SV1' indicates SP2, only bail if not SP2 or Win2K3
282           ua.indexOf('SV1') < 0) {
283         bail = true;
284       }
285     } else {
286       // Not IE
287       bail = true;
288     }
289     if (bail) {
290       return;
291     }
293     // Inject the default styles
294     injectCFStyleTag();
296     if (document.cookie.indexOf("disableGCFCheck=1") >=0) {
297       // If we're supposed to hide the overlay prompt, add the rules to do it.
298       closeOverlay();
299     }
301     // When loaded in an alternate protocol (e.g., "file:"), still call out to
302     // the right location.
303     var currentProtocol = document.location.protocol;
304     var protocol = (currentProtocol == 'https:') ? 'https:' : 'http:';
305     // TODO(slightlyoff): Update this URL when a mini-installer page is
306     //   available.
307     var installUrl = protocol + '//www.google.com/chromeframe';
308     if (!isAvailable()) {
309       if (args.onmissing) {
310         args.onmissing();
311       }
313       args.src = args.url || installUrl;
314       var mode = args.mode || 'inline';
315       var preventPrompt = args.preventPrompt || false;
317       if (!preventPrompt) {
318         if (mode == 'inline') {
319           makeInlinePrompt(args);
320         } else if (mode == 'overlay') {
321           makeOverlayPrompt(args);
322         } else {
323           window.open(args.src);
324         }
325       }
327       if (args.preventInstallDetection) {
328         return;
329       }
331       // Begin polling for install success.
332       var installTimer = setInterval(function() {
333           // every 2 seconds, look to see if CF is available, if so, proceed on
334           // to our destination
335           if (isAvailable()) {
336             if (args.oninstall) {
337               args.oninstall();
338             }
340             clearInterval(installTimer);
341             // TODO(slightlyoff): add a way to prevent navigation or make it
342             //    contingent on oninstall?
343             window.location = args.destination || window.location;
344           }
345       }, 2000);
346     }
347   };
349   CFInstall._force = false;
350   CFInstall._forceValue = false;
351   CFInstall.isAvailable = isAvailable;
353   // expose CFInstall to the external scope. We've already checked to make
354   // sure we're not going to blow existing objects away.
355   scope.CFInstall = CFInstall;
357 })(this['ChromeFrameInstallScope'] || this);