| // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview CFInstall.js provides a set of utilities for managing |
| * the Chrome Frame detection and installation process. |
| * @author slightlyoff@google.com (Alex Russell) |
| */ |
| |
| (function(scope) { |
| // bail if we'd be over-writing an existing CFInstall object |
| if (scope['CFInstall']) { |
| return; |
| } |
| |
| /** |
| * returns an item based on DOM ID. Optionally a document may be provided to |
| * specify the scope to search in. If a node is passed, it's returned as-is. |
| * @param {string|Node} id The ID of the node to be located or a node |
| * @param {Node} doc Optional A document to search for id. |
| * @return {Node} |
| */ |
| var byId = function(id, doc) { |
| return (typeof id == 'string') ? (doc || document).getElementById(id) : id; |
| }; |
| |
| ///////////////////////////////////////////////////////////////////////////// |
| // Plugin Detection |
| ///////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Checks to find out if ChromeFrame is available as a plugin |
| * @return {Boolean} |
| */ |
| var isAvailable = function() { |
| // For testing purposes. |
| if (scope.CFInstall._force) { |
| return scope.CFInstall._forceValue; |
| } |
| |
| // Look for CF in the User Agent before trying more expensive checks |
| var ua = navigator.userAgent.toLowerCase(); |
| if (ua.indexOf("chromeframe") >= 0) { |
| return true; |
| } |
| |
| if (typeof window['ActiveXObject'] != 'undefined') { |
| try { |
| var obj = new ActiveXObject('ChromeTab.ChromeFrame'); |
| if (obj) { |
| obj.registerBhoIfNeeded(); |
| return true; |
| } |
| } catch(e) { |
| // squelch |
| } |
| } |
| return false; |
| }; |
| |
| /** |
| * Creates a style sheet in the document containing the passed rules. |
| */ |
| var injectStyleSheet = function(rules) { |
| try { |
| var ss = document.createElement('style'); |
| ss.setAttribute('type', 'text/css'); |
| if (ss.styleSheet) { |
| ss.styleSheet.cssText = rules; |
| } else { |
| ss.appendChild(document.createTextNode(rules)); |
| } |
| var h = document.getElementsByTagName('head')[0]; |
| var firstChild = h.firstChild; |
| h.insertBefore(ss, firstChild); |
| } catch (e) { |
| // squelch |
| } |
| }; |
| |
| /** @type {boolean} */ |
| var cfStyleTagInjected = false; |
| /** @type {boolean} */ |
| var cfHiddenInjected = false; |
| |
| /** |
| * Injects style rules into the document to handle formatting of Chrome Frame |
| * prompt. Multiple calls have no effect. |
| */ |
| var injectCFStyleTag = function() { |
| if (cfStyleTagInjected) { |
| // Once and only once |
| return; |
| } |
| var rules = '.chromeFrameInstallDefaultStyle {' + |
| 'width: 800px;' + |
| 'height: 600px;' + |
| 'position: absolute;' + |
| 'left: 50%;' + |
| 'top: 50%;' + |
| 'margin-left: -400px;' + |
| 'margin-top: -300px;' + |
| '}' + |
| '.chromeFrameOverlayContent {' + |
| 'position: absolute;' + |
| 'margin-left: -400px;' + |
| 'margin-top: -300px;' + |
| 'left: 50%;' + |
| 'top: 50%;' + |
| 'border: 1px solid #93B4D9;' + |
| 'background-color: white;' + |
| 'z-index: 2001;' + |
| '}' + |
| '.chromeFrameOverlayContent iframe {' + |
| 'width: 800px;' + |
| 'height: 600px;' + |
| 'border: none;' + |
| '}' + |
| '.chromeFrameOverlayCloseBar {' + |
| 'height: 1em;' + |
| 'text-align: right;' + |
| 'background-color: #CADEF4;' + |
| '}' + |
| '.chromeFrameOverlayUnderlay {' + |
| 'position: absolute;' + |
| 'width: 100%;' + |
| 'height: 100%;' + |
| 'background-color: white;' + |
| 'opacity: 0.5;' + |
| '-moz-opacity: 0.5;' + |
| '-webkit-opacity: 0.5;' + |
| '-ms-filter: ' + |
| '"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";' + |
| 'filter: alpha(opacity=50);' + |
| 'z-index: 2000;' + |
| '}'; |
| injectStyleSheet(rules); |
| cfStyleTagInjected = true; |
| }; |
| |
| /** |
| * Injects style rules to hide the overlay version of the GCF prompt. |
| * Multiple calls have no effect. |
| */ |
| var closeOverlay = function() { |
| // IE has a limit to the # of <style> tags allowed, so we avoid |
| // tempting the fates. |
| if (cfHiddenInjected) { |
| return; |
| } |
| var rules = '.chromeFrameOverlayContent { display: none; }' + |
| '.chromeFrameOverlayUnderlay { display: none; }'; |
| injectStyleSheet(rules); |
| // Hide the dialog for a year (or until cookies are deleted). |
| var age = 365 * 24 * 60 * 60 * 1000; |
| document.cookie = "disableGCFCheck=1;path=/;max-age="+age; |
| cfHiddenInjected = true; |
| }; |
| |
| /** |
| * Plucks properties from the passed arguments and sets them on the passed |
| * DOM node |
| * @param {Node} node The node to set properties on |
| * @param {Object} args A map of user-specified properties to set |
| */ |
| var setProperties = function(node, args) { |
| |
| var srcNode = byId(args['node']); |
| |
| node.id = args['id'] || (srcNode ? srcNode['id'] || getUid(srcNode) : ''); |
| |
| // TODO(slightlyoff): Opera compat? need to test there |
| var cssText = args['cssText'] || ''; |
| node.style.cssText = ' ' + cssText; |
| |
| var classText = args['className'] || ''; |
| node.className = classText; |
| |
| // default if the browser doesn't so we don't show sad-tab |
| var src = args['src'] || 'about:blank'; |
| |
| node.src = src; |
| |
| if (srcNode) { |
| srcNode.parentNode.replaceChild(node, srcNode); |
| } |
| }; |
| |
| /** |
| * Creates an iframe. |
| * @param {Object} args A bag of configuration properties, including values |
| * like 'node', 'cssText', 'className', 'id', 'src', etc. |
| * @return {Node} |
| */ |
| var makeIframe = function(args) { |
| var el = document.createElement('iframe'); |
| el.setAttribute('frameborder', '0'); |
| el.setAttribute('border', '0'); |
| setProperties(el, args); |
| return el; |
| }; |
| |
| /** |
| * Adds an unadorned iframe into the page, taking arguments to customize it. |
| * @param {Object} args A map of user-specified properties to set |
| */ |
| var makeInlinePrompt = function(args) { |
| args.className = 'chromeFrameInstallDefaultStyle ' + |
| (args.className || ''); |
| var ifr = makeIframe(args); |
| // TODO(slightlyoff): handle placement more elegantly! |
| if (!ifr.parentNode) { |
| var firstChild = document.body.firstChild; |
| document.body.insertBefore(ifr, firstChild); |
| } |
| }; |
| |
| /** |
| * Adds a styled, closable iframe into the page with a background that |
| * emulates a modal dialog. |
| * @param {Object} args A map of user-specified properties to set |
| */ |
| var makeOverlayPrompt = function(args) { |
| if (byId('chromeFrameOverlayContent')) { |
| return; // Was previously created. Bail. |
| } |
| |
| var n = document.createElement('span'); |
| n.innerHTML = '<div class="chromeFrameOverlayUnderlay"></div>' + |
| '<table class="chromeFrameOverlayContent"' + |
| 'id="chromeFrameOverlayContent"' + |
| 'cellpadding="0" cellspacing="0">' + |
| '<tr class="chromeFrameOverlayCloseBar">' + |
| '<td>' + |
| // TODO(slightlyoff): i18n |
| '<button id="chromeFrameCloseButton">close</button>' + |
| '</td>' + |
| '</tr>' + |
| '<tr>' + |
| '<td id="chromeFrameIframeHolder"></td>' + |
| '</tr>' + |
| '</table>'; |
| |
| var b = document.body; |
| // Insert underlay nodes into the document in the right order. |
| while (n.firstChild) { |
| b.insertBefore(n.lastChild, b.firstChild); |
| } |
| var ifr = makeIframe(args); |
| byId('chromeFrameIframeHolder').appendChild(ifr); |
| byId('chromeFrameCloseButton').onclick = closeOverlay; |
| }; |
| |
| var CFInstall = {}; |
| |
| /** |
| * Checks to see if Chrome Frame is available, if not, prompts the user to |
| * install. Once installation is begun, a background timer starts, |
| * checkinging for a successful install every 2 seconds. Upon detection of |
| * successful installation, the current page is reloaded, or if a |
| * 'destination' parameter is passed, the page navigates there instead. |
| * @param {Object} args A bag of configuration properties. Respected |
| * properties are: 'mode', 'url', 'destination', 'node', 'onmissing', |
| * 'preventPrompt', 'oninstall', 'preventInstallDetection', 'cssText', and |
| * 'className'. |
| * @public |
| */ |
| CFInstall.check = function(args) { |
| args = args || {}; |
| |
| // We currently only support CF in IE |
| // TODO(slightlyoff): Update this should we support other browsers! |
| var ua = navigator.userAgent; |
| var ieRe = /MSIE (\S+); Windows NT/; |
| var bail = false; |
| if (ieRe.test(ua)) { |
| // We also only support Win2003/XPSP2 or better. See: |
| // http://msdn.microsoft.com/en-us/library/ms537503%28VS.85%29.aspx |
| if (parseFloat(ieRe.exec(ua)[1]) < 6 && |
| // 'SV1' indicates SP2, only bail if not SP2 or Win2K3 |
| ua.indexOf('SV1') < 0) { |
| bail = true; |
| } |
| } else { |
| // Not IE |
| bail = true; |
| } |
| if (bail) { |
| return; |
| } |
| |
| // Inject the default styles |
| injectCFStyleTag(); |
| |
| if (document.cookie.indexOf("disableGCFCheck=1") >=0) { |
| // If we're supposed to hide the overlay prompt, add the rules to do it. |
| closeOverlay(); |
| } |
| |
| // When loaded in an alternate protocol (e.g., "file:"), still call out to |
| // the right location. |
| var currentProtocol = document.location.protocol; |
| var protocol = (currentProtocol == 'https:') ? 'https:' : 'http:'; |
| // TODO(slightlyoff): Update this URL when a mini-installer page is |
| // available. |
| var installUrl = protocol + '//www.google.com/chromeframe'; |
| if (!isAvailable()) { |
| if (args.onmissing) { |
| args.onmissing(); |
| } |
| |
| args.src = args.url || installUrl; |
| var mode = args.mode || 'inline'; |
| var preventPrompt = args.preventPrompt || false; |
| |
| if (!preventPrompt) { |
| if (mode == 'inline') { |
| makeInlinePrompt(args); |
| } else if (mode == 'overlay') { |
| makeOverlayPrompt(args); |
| } else { |
| window.open(args.src); |
| } |
| } |
| |
| if (args.preventInstallDetection) { |
| return; |
| } |
| |
| // Begin polling for install success. |
| var installTimer = setInterval(function() { |
| // every 2 seconds, look to see if CF is available, if so, proceed on |
| // to our destination |
| if (isAvailable()) { |
| if (args.oninstall) { |
| args.oninstall(); |
| } |
| |
| clearInterval(installTimer); |
| // TODO(slightlyoff): add a way to prevent navigation or make it |
| // contingent on oninstall? |
| window.location = args.destination || window.location; |
| } |
| }, 2000); |
| } |
| }; |
| |
| CFInstall._force = false; |
| CFInstall._forceValue = false; |
| CFInstall.isAvailable = isAvailable; |
| |
| // expose CFInstall to the external scope. We've already checked to make |
| // sure we're not going to blow existing objects away. |
| scope.CFInstall = CFInstall; |
| |
| })(this['ChromeFrameInstallScope'] || this); |