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