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