Home | History | Annotate | Download | only in standalone
      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  */
     20 if (!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  */
     35 chrome.send = (function() {
     36   var users = [
     37   {
     38     name: 'Alan Beaker',
     39     emailAddress: 'beaker (a] chromium.org',
     40     imageUrl: '../../app/theme/avatar_beaker.png',
     41     canRemove: false
     42   },
     43   {
     44     name: 'Alex Briefcase',
     45     emailAddress: 'briefcase (a] chromium.org',
     46     imageUrl: '../../app/theme/avatar_briefcase.png',
     47     canRemove: true
     48   },
     49   {
     50     name: 'Alex Circles',
     51     emailAddress: 'circles (a] 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  */
    176 if (/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 */
    226 if (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 */
    337 if (!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