summaryrefslogtreecommitdiffstats
path: root/chrome_frame/CFInstall.js
blob: 915f958ec8e7f2b67e124d073401d3acb0411733 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// 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
  /////////////////////////////////////////////////////////////////////////////
  
  var cachedAvailable;

  /** 
   * Checks to find out if ChromeFrame is available as a plugin
   * @return {Boolean} 
   */
  var isAvailable = function() {
    if (typeof cachedAvailable != 'undefined') {
      return cachedAvailable;
    }

    cachedAvailable = false;

    // Look for CF in the User Agent before trying more expensive checks
    var ua = navigator.userAgent.toLowerCase();
    if (ua.indexOf("chromeframe") >= 0 || ua.indexOf("x-clock") >= 0) {
      cachedAvailable = true;
      return cachedAvailable;
    }

    if (typeof window['ActiveXObject'] != 'undefined') {
      try {
        var obj = new ActiveXObject('ChromeTab.ChromeFrame');
        if (obj) {
          cachedAvailable = true;
        }
      } catch(e) {
        // squelch
      }
    }
    return cachedAvailable;
  };


  /** @type {boolean} */
  var cfStyleTagInjected = false;

  /** 
   * Creates a style sheet in the document which provides default styling for
   * ChromeFrame instances. Successive calls should have no additive effect.
   */
  var injectCFStyleTag = function() {
    if (cfStyleTagInjected) {
      // Once and only once
      return;
    }
    try {
      var rule = '.chromeFrameInstallDefaultStyle {' +
                    'width: 500px;' +
                    'height: 400px;' +
                    'padding: 0;' +
                    'border: 1px solid #0028c4;' +
                    'margin: 0;' +
                  '}';
      var ss = document.createElement('style');
      ss.setAttribute('type', 'text/css');
      if (ss.styleSheet) {
        ss.styleSheet.cssText = rule;
      } else {
        ss.appendChild(document.createTextNode(rule));
      }
      var h = document.getElementsByTagName('head')[0];
      var firstChild = h.firstChild;
      h.insertBefore(ss, firstChild);
      cfStyleTagInjected = true;
    } catch (e) {
      // squelch
    }
  };


  /** 
   * 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) {
    injectCFStyleTag();

    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 = 'chromeFrameInstallDefaultStyle ' + 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');
    setProperties(el, args);
    return el;
  };

  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 ieRe = /MSIE (\S+)/;
    if (!ieRe.test(navigator.userAgent)) {
      return;
    }


    // TODO(slightlyoff): Update this URL when a mini-installer page is
    //   available.
    var installUrl = '//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') {
          var ifr = makeIframe(args);
          // TODO(slightlyoff): handle placement more elegantly!
          if (!ifr.parentNode) {
            var firstChild = document.body.firstChild;
            document.body.insertBefore(ifr, firstChild);
          }
        } 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.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);