1// Copyright (c) 2011 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 LIS Standalone hack
7 *  This file contains the code necessary to make the Touch LIS work
8 *  as a stand-alone application (as opposed to being embedded into chrome).
9 *  This is useful for rapid development and testing, but does not actually form
10 *  part of the product.
11 */
12
13// Note that this file never gets concatenated and embeded into Chrome, so we
14// can enable strict mode for the whole file just like normal.
15'use strict';
16
17/**
18 * For non-Chrome browsers, create a dummy chrome object
19 */
20if (!window.chrome) {
21  var chrome = {};
22}
23
24/**
25 *  A replacement chrome.send method that supplies static data for the
26 *  key APIs used by the LIS.
27 *
28 *  Note that the real chrome object also supplies data for most-viewed and
29 *  recently-closed pages, but the tangent LIS doesn't use that data so we
30 *  don't bother simulating it here.
31 *
32 *  We create this object by applying an anonymous function so that we can have
33 *  local variables (avoid polluting the global object)
34 */
35chrome.send = (function() {
36  var users = [
37  {
38    name: 'Alan Beaker',
39    emailAddress: 'beaker@chromium.org',
40    imageUrl: '../../app/theme/avatar_beaker.png',
41    canRemove: false
42  },
43  {
44    name: 'Alex Briefcase',
45    emailAddress: 'briefcase@chromium.org',
46    imageUrl: '../../app/theme/avatar_briefcase.png',
47    canRemove: true
48  },
49  {
50    name: 'Alex Circles',
51    emailAddress: 'circles@chromium.org',
52    imageUrl: '../../app/theme/avatar_circles.png',
53    canRemove: true
54  },
55  {
56    name: 'Guest',
57    emailAddress: '',
58    imageUrl: '',
59    canRemove: false
60  }
61  ];
62
63  /**
64   * Invoke the getAppsCallback function with a snapshot of the current app
65   * database.
66   */
67  function sendGetUsersCallback()
68  {
69    // We don't want to hand out our array directly because the NTP will
70    // assume it owns the array and is free to modify it.  For now we make a
71    // one-level deep copy of the array (since cloning the whole thing is
72    // more work and unnecessary at the moment).
73    getUsersCallback(users.slice(0));
74  }
75
76  /**
77   * Like Array.prototype.indexOf but calls a predicate to test for match
78   *
79   * @param {Array} array The array to search.
80   * @param {function(Object): boolean} predicate The function to invoke on
81   *     each element.
82   * @return {number} First index at which predicate returned true, or -1.
83   */
84  function indexOfPred(array, predicate) {
85    for (var i = 0; i < array.length; i++) {
86      if (predicate(array[i]))
87        return i;
88    }
89    return -1;
90  }
91
92  /**
93   * Get index into apps of an application object
94   * Requires the specified app to be present
95   *
96   * @param {string} id The ID of the application to locate.
97   * @return {number} The index in apps for an object with the specified ID.
98   */
99  function getUserIndex(name) {
100    var i = indexOfPred(apps, function(e) { return e.name === name;});
101    if (i == -1)
102      alert('Error: got unexpected App ID');
103    return i;
104  }
105
106  /**
107   * Get an user object given the user name
108   * Requires
109   * @param {string} name The user name to search for.
110   * @return {Object} The corresponding user object.
111   */
112  function getUser(name) {
113    return users[getUserIndex(name)];
114  }
115
116  /**
117   * Simlulate the login of a user
118   *
119   * @param {string} email_address the email address of the user logging in.
120   * @param {string} password the password of the user logging in.
121   */
122  function login(email_address, password) {
123    console.log('password', password);
124    if (password == 'correct') {
125      return true;
126    }
127    return false;
128  }
129
130  /**
131   * The chrome server communication entrypoint.
132   *
133   * @param {string} command Name of the command to send.
134   * @param {Array} args Array of command-specific arguments.
135   */
136  return function(command, args) {
137    // Chrome API is async
138    window.setTimeout(function() {
139      switch (command) {
140        // called to populate the list of applications
141        case 'GetUsers':
142          sendGetUsersCallback();
143          break;
144
145        // Called when a user is removed.
146        case 'RemoveUser':
147          break;
148
149        // Called when a user attempts to login.
150        case 'Login':
151          login(args[0], args[1]);
152          break;
153
154        // Called when an app is moved to a different page
155        case 'MoveUser':
156          break;
157
158        case 'SetGuestPosition':
159          break;
160
161        default:
162          throw new Error('Unexpected chrome command: ' + command);
163          break;
164      }
165    }, 0);
166  };
167})();
168
169/*
170 * On iOS we need a hack to avoid spurious click events
171 * In particular, if the user delays briefly between first touching and starting
172 * to drag, when the user releases a click event will be generated.
173 * Note that this seems to happen regardless of whether we do preventDefault on
174 * touchmove events.
175 */
176if (/iPhone|iPod|iPad/.test(navigator.userAgent) &&
177    !(/Chrome/.test(navigator.userAgent))) {
178  // We have a real iOS device (no a ChromeOS device pretending to be iOS)
179  (function() {
180    // True if a gesture is occuring that should cause clicks to be swallowed
181    var gestureActive = false;
182
183    // The position a touch was last started
184    var lastTouchStartPosition;
185
186    // Distance which a touch needs to move to be considered a drag
187    var DRAG_DISTANCE = 3;
188
189    document.addEventListener('touchstart', function(event) {
190      lastTouchStartPosition = {
191        x: event.touches[0].clientX,
192        y: event.touches[0].clientY
193      };
194      // A touchstart ALWAYS preceeds a click (valid or not), so cancel any
195      // outstanding gesture. Also, any multi-touch is a gesture that should
196      // prevent clicks.
197      gestureActive = event.touches.length > 1;
198    }, true);
199
200    document.addEventListener('touchmove', function(event) {
201      // When we see a move, measure the distance from the last touchStart
202      // If this is a multi-touch then the work here is irrelevant
203      // (gestureActive is already true)
204      var t = event.touches[0];
205      if (Math.abs(t.clientX - lastTouchStartPosition.x) > DRAG_DISTANCE ||
206          Math.abs(t.clientY - lastTouchStartPosition.y) > DRAG_DISTANCE) {
207        gestureActive = true;
208      }
209    }, true);
210
211    document.addEventListener('click', function(event) {
212      // If we got here without gestureActive being set then it means we had
213      // a touchStart without any real dragging before touchEnd - we can allow
214      // the click to proceed.
215      if (gestureActive) {
216        event.preventDefault();
217        event.stopPropagation();
218      }
219    }, true);
220  })();
221}
222
223/*  Hack to add Element.classList to older browsers that don't yet support it.
224    From https://developer.mozilla.org/en/DOM/element.classList.
225*/
226if (typeof Element !== 'undefined' &&
227    !Element.prototype.hasOwnProperty('classList')) {
228  (function() {
229    var classListProp = 'classList',
230        protoProp = 'prototype',
231        elemCtrProto = Element[protoProp],
232        objCtr = Object,
233        strTrim = String[protoProp].trim || function() {
234          return this.replace(/^\s+|\s+$/g, '');
235        },
236        arrIndexOf = Array[protoProp].indexOf || function(item) {
237          for (var i = 0, len = this.length; i < len; i++) {
238            if (i in this && this[i] === item) {
239              return i;
240            }
241          }
242          return -1;
243        },
244        // Vendors: please allow content code to instantiate DOMExceptions
245        /** @constructor  */
246        DOMEx = function(type, message) {
247          this.name = type;
248          this.code = DOMException[type];
249          this.message = message;
250        },
251        checkTokenAndGetIndex = function(classList, token) {
252          if (token === '') {
253            throw new DOMEx(
254                'SYNTAX_ERR',
255                'An invalid or illegal string was specified'
256            );
257          }
258          if (/\s/.test(token)) {
259            throw new DOMEx(
260                'INVALID_CHARACTER_ERR',
261                'String contains an invalid character'
262            );
263          }
264          return arrIndexOf.call(classList, token);
265        },
266        /** @constructor
267         *  @extends {Array} */
268        ClassList = function(elem) {
269          var trimmedClasses = strTrim.call(elem.className),
270              classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [];
271
272          for (var i = 0, len = classes.length; i < len; i++) {
273            this.push(classes[i]);
274          }
275          this._updateClassName = function() {
276            elem.className = this.toString();
277          };
278        },
279        classListProto = ClassList[protoProp] = [],
280        classListGetter = function() {
281          return new ClassList(this);
282        };
283
284    // Most DOMException implementations don't allow calling DOMException's
285    // toString() on non-DOMExceptions. Error's toString() is sufficient here.
286    DOMEx[protoProp] = Error[protoProp];
287    classListProto.item = function(i) {
288      return this[i] || null;
289    };
290    classListProto.contains = function(token) {
291      token += '';
292      return checkTokenAndGetIndex(this, token) !== -1;
293    };
294    classListProto.add = function(token) {
295      token += '';
296      if (checkTokenAndGetIndex(this, token) === -1) {
297        this.push(token);
298        this._updateClassName();
299      }
300    };
301    classListProto.remove = function(token) {
302      token += '';
303      var index = checkTokenAndGetIndex(this, token);
304      if (index !== -1) {
305        this.splice(index, 1);
306        this._updateClassName();
307      }
308    };
309    classListProto.toggle = function(token) {
310      token += '';
311      if (checkTokenAndGetIndex(this, token) === -1) {
312        this.add(token);
313      } else {
314        this.remove(token);
315      }
316    };
317    classListProto.toString = function() {
318      return this.join(' ');
319    };
320
321    if (objCtr.defineProperty) {
322      var classListDescriptor = {
323        get: classListGetter,
324        enumerable: true,
325        configurable: true
326      };
327      objCtr.defineProperty(elemCtrProto, classListProp, classListDescriptor);
328    } else if (objCtr[protoProp].__defineGetter__) {
329      elemCtrProto.__defineGetter__(classListProp, classListGetter);
330    }
331  }());
332}
333
334/* Hack to add Function.bind to older browsers that don't yet support it. From:
335   https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
336*/
337if (!Function.prototype.bind) {
338  /**
339   * @param {Object} selfObj Specifies the object which |this| should
340   *     point to when the function is run. If the value is null or undefined,
341   *     it will default to the global object.
342   * @param {...*} var_args Additional arguments that are partially
343   *     applied to the function.
344   * @return {!Function} A partially-applied form of the function bind() was
345   *     invoked as a method of.
346   *  @suppress {duplicate}
347   */
348  Function.prototype.bind = function(selfObj, var_args) {
349    var slice = [].slice,
350        args = slice.call(arguments, 1),
351        self = this,
352        /** @constructor  */
353        nop = function() {},
354        bound = function() {
355          return self.apply(this instanceof nop ? this : (selfObj || {}),
356                              args.concat(slice.call(arguments)));
357        };
358    nop.prototype = self.prototype;
359    bound.prototype = new nop();
360    return bound;
361  };
362}
363
364