Home | History | Annotate | Download | only in chrome_frame
      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.
      4 
      5 /**
      6  * @fileoverview CFInstall.js provides a set of utilities for managing
      7  * the Chrome Frame detection and installation process.
      8  * @author slightlyoff (a] google.com (Alex Russell)
      9  */
     10 
     11 (function(scope) {
     12   // bail if we'd be over-writing an existing CFInstall object
     13   if (scope['CFInstall']) {
     14     return;
     15   }
     16 
     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   };
     27 
     28   /////////////////////////////////////////////////////////////////////////////
     29   // Plugin Detection
     30   /////////////////////////////////////////////////////////////////////////////
     31 
     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     }
     41 
     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     }
     47 
     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   };
     61 
     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   };
     81 
     82   /** @type {boolean} */
     83   var cfStyleTagInjected = false;
     84   /** @type {boolean} */
     85   var cfHiddenInjected = false;
     86 
     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   };
    141 
    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   };
    160 
    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) {
    168 
    169     var srcNode = byId(args['node']);
    170 
    171     node.id = args['id'] || (srcNode ? srcNode['id'] || getUid(srcNode) : '');
    172 
    173     // TODO(slightlyoff): Opera compat? need to test there
    174     var cssText = args['cssText'] || '';
    175     node.style.cssText = ' ' + cssText;
    176 
    177     var classText = args['className'] || '';
    178     node.className = classText;
    179 
    180     // default if the browser doesn't so we don't show sad-tab
    181     var src = args['src'] || 'about:blank';
    182 
    183     node.src = src;
    184 
    185     if (srcNode) {
    186       srcNode.parentNode.replaceChild(node, srcNode);
    187     }
    188   };
    189 
    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   };
    203 
    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   };
    218 
    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     }
    228 
    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>';
    244 
    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   };
    254 
    255   var CFInstall = {};
    256 
    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 || {};
    271 
    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     }
    292 
    293     // Inject the default styles
    294     injectCFStyleTag();
    295 
    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     }
    300 
    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       }
    312 
    313       args.src = args.url || installUrl;
    314       var mode = args.mode || 'inline';
    315       var preventPrompt = args.preventPrompt || false;
    316 
    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       }
    326 
    327       if (args.preventInstallDetection) {
    328         return;
    329       }
    330 
    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             }
    339 
    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   };
    348 
    349   CFInstall._force = false;
    350   CFInstall._forceValue = false;
    351   CFInstall.isAvailable = isAvailable;
    352 
    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;
    356 
    357 })(this['ChromeFrameInstallScope'] || this);
    358