Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(56)

Side by Side Diff: chrome/browser/resources/touch_ntp/standalone/standalone_hack.js

Issue 6661024: Use a specialized new tab page in TOUCH_UI builds (Closed) Base URL: http://git.chromium.org/git/chromium.git@trunk
Patch Set: Ensure strict mode is scoped, and fix the target of touch events Created 9 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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 NTP Standalone hack
7 * This file contains the code necessary to make the Touch NTP 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 * Note that, while the product portion of the touch NTP is designed to work
13 * just in the latest version of Chrome, this hack attempts to add some support
14 * for working in older browsers to enable testing and demonstration on
15 * existing tablet platforms. In particular, this code has been tested to work
16 * on Mobile Safari in iOS 4.2. The goal is that the need to support any other
17 * browser should not leak out of this file - and so we will hack global JS
18 * objects as necessary here to present the illusion of running on the latest
19 * version of Chrome.
20 */
21
22 // Note that this file never gets concatenated and embeded into Chrome, so we
23 // can enable strict mode for the whole file just like normal.
24 'use strict';
25
26 /**
27 * For non-Chrome browsers, create a dummy chrome object
28 */
29 if (!window.chrome) {
30 var chrome = {};
31 }
32
33 /**
34 * A replacement chrome.send method that supplies static data for the
35 * key APIs used by the NTP.
36 *
37 * Note that the real chrome object also supplies data for most-viewed and
38 * recently-closed pages, but the tangent NTP doesn't use that data so we
39 * don't bother simulating it here.
40 *
41 * We create this object by applying an anonymous function so that we can have
42 * local variables (avoid polluting the global object)
43 */
44 chrome.send = (function() {
45
46 var apps = [{
47 app_launch_index: 2,
48 description: 'The prickly puzzle game where popping balloons has ' +
49 'never been so much fun!',
50 icon_big: 'standalone/poppit-icon.png',
51 icon_small: 'standalone/poppit-favicon.png',
52 id: 'mcbkbpnkkkipelfledbfocopglifcfmi',
53 launch_container: 2,
54 launch_type: 1,
55 launch_url: 'http://poppit.pogo.com/hd/PoppitHD.html',
56 name: 'Poppit',
57 options_url: ''
58 },
59 {
60 app_launch_index: 1,
61 description: 'Fast, searchable email with less spam.',
62 icon_big: 'standalone/gmail-icon.png',
63 icon_small: 'standalone/gmail-favicon.png',
64 id: 'pjkljhegncpnkpknbcohdijeoejaedia',
65 launch_container: 2,
66 launch_type: 1,
67 launch_url: 'https://mail.google.com/',
68 name: 'Gmail',
69 options_url: 'https://mail.google.com/mail/#settings'
70 },
71 {
72 app_launch_index: 3,
73 description: 'Read over 3 million Google eBooks on the web.',
74 icon_big: 'standalone/googlebooks-icon.png',
75 icon_small: 'standalone/googlebooks-favicon.png',
76 id: 'mmimngoggfoobjdlefbcabngfnmieonb',
77 launch_container: 2,
78 launch_type: 1,
79 launch_url: 'http://books.google.com/ebooks?source=chrome-app',
80 name: 'Google Books',
81 options_url: ''
82 },
83 {
84 app_launch_index: 4,
85 description: 'Find local business information, directions, and ' +
86 'street-level imagery around the world with Google Maps.',
87 icon_big: 'standalone/googlemaps-icon.png',
88 icon_small: 'standalone/googlemaps-favicon.png',
89 id: 'lneaknkopdijkpnocmklfnjbeapigfbh',
90 launch_container: 2,
91 launch_type: 1,
92 launch_url: 'http://maps.google.com/',
93 name: 'Google Maps',
94 options_url: ''
95 },
96 {
97 app_launch_index: 5,
98 description: 'Create the longest path possible and challenge your ' +
99 'friends in the game of Entanglement.',
100 icon_big: 'standalone/entaglement-icon.png',
101 id: 'aciahcmjmecflokailenpkdchphgkefd',
102 launch_container: 2,
103 launch_type: 1,
104 launch_url: 'http://entanglement.gopherwoodstudios.com/',
105 name: 'Entanglement',
106 options_url: ''
107 },
108 {
109 name: 'NYTimes',
110 app_launch_index: 6,
111 description: 'The New York Times App for the Chrome Web Store.',
112 icon_big: 'standalone/nytimes-icon.png',
113 id: 'ecmphppfkcfflgglcokcbdkofpfegoel',
114 launch_container: 2,
115 launch_type: 1,
116 launch_url: 'http://www.nytimes.com/chrome/',
117 options_url: '',
118 page_index: 2
119 },
120 {
121 app_launch_index: 7,
122 description: 'The world\'s most popular online video community.',
123 id: 'blpcfgokakmgnkcojhhkbfbldkacnbeo',
124 icon_big: 'standalone/youtube-icon.png',
125 launch_container: 2,
126 launch_type: 1,
127 launch_url: 'http://www.youtube.com/',
128 name: 'YouTube',
129 options_url: '',
130 page_index: 3
131 }];
132
133 // For testing
134 apps = spamApps(apps);
135
136 /**
137 * Invoke the getAppsCallback function with a snapshot of the current app
138 * database.
139 */
140 function sendGetAppsCallback()
141 {
142 // We don't want to hand out our array directly because the NTP will
143 // assume it owns the array and is free to modify it. For now we make a
144 // one-level deep copy of the array (since cloning the whole thing is
145 // more work and unnecessary at the moment).
146 var appsData = {
147 showPromo: false,
148 showLauncher: true,
149 apps: apps.slice(0)
150 };
151 getAppsCallback(appsData);
152 }
153
154 /**
155 * To make testing real-world scenarios easier, this expands our list of
156 * apps by duplicating them a number of times
157 */
158 function spamApps(apps)
159 {
160 // Create an object that extends another object
161 // This is an easy/efficient way to make slightly modified copies of our
162 // app objects without having to do a deep copy
163 function createObject(proto) {
164 /** @constructor */
165 var F = function() {};
166 F.prototype = proto;
167 return new F();
168 }
169
170 var newApps = [];
171 var pages = Math.floor(Math.random() * 8) + 1;
172 var idx = 1;
173 for (var p = 0; p < pages; p++) {
174 var count = Math.floor(Math.random() * 18) + 1;
175 for (var a = 0; a < count; a++) {
176 var i = Math.floor(Math.random() * apps.length);
177 var newApp = createObject(apps[i]);
178 newApp.page_index = p;
179 newApp.app_launch_index = idx;
180 // Uniqify the ID
181 newApp.id = apps[i].id + '-' + idx;
182 idx++;
183 newApps.push(newApp);
184 }
185 }
186 return newApps;
187 }
188
189 /**
190 * Like Array.prototype.indexOf but calls a predicate to test for match
191 *
192 * @param {Array} array The array to search.
193 * @param {function(Object): boolean} predicate The function to invoke on
194 * each element.
195 * @return {number} First index at which predicate returned true, or -1.
196 */
197 function indexOfPred(array, predicate)
198 {
199 for (var i = 0; i < array.length; i++) {
200 if (predicate(array[i])) {
201 return i;
202 }
203 }
204 return -1;
205 }
206
207 /**
208 * Get index into apps of an application object
209 * Requires the specified app to be present
210 *
211 * @param {string} id The ID of the application to locate.
212 * @return {number} The index in apps for an object with the specified ID.
213 */
214 function getAppIndex(id)
215 {
216 var i = indexOfPred(apps, function(e) { return e.id === id;});
217 if (i == -1) {
218 alert('Error: got unexpected App ID');
219 }
220 return i;
221 }
222
223 /**
224 * Get an application object given the application ID
225 * Requires
226 * @param {string} id The application ID to search for.
227 * @return {Object} The corresponding application object.
228 */
229 function getApp(id)
230 {
231 return apps[getAppIndex(id)];
232 }
233
234 /**
235 * Simlulate the launching of an application
236 *
237 * @param {string} id The ID of the application to launch.
238 */
239 function launchApp(id)
240 {
241 // Note that we don't do anything with the icon location.
242 // That's used by Chrome only on Windows to animate the icon during
243 // launch.
244 var app = getApp(id);
245 switch (parseInt(app.launch_type, 10)) {
246 case 0: // pinned
247 case 1: // regular
248
249 // Replace the current tab with the app.
250 // Pinned seems to omit the tab title, but I doubt it's
251 // possible for us to do that here
252 window.location = (app.launch_url);
253 break;
254
255 case 2: // fullscreen
256 case 3: // window
257 // attempt to launch in a new window
258 window.close();
259 window.open(app.launch_url, app.name,
260 'resizable=yes,scrollbars=yes,status=yes');
261 break;
262
263 default:
264 alert('Unexpected launch type: ' + app.launch_type);
265 }
266 }
267
268 /**
269 * Simulate uninstall of an app
270 * @param {string} id The ID of the application to uninstall.
271 */
272 function uninstallApp(id) {
273 var i = getAppIndex(id);
274 // This confirmation dialog doesn't look exactly the same as the
275 // standard NTP one, but it's close enough.
276 if (window.confirm('Uninstall \"' + apps[i].name + '\"?')) {
277 apps.splice(i, 1);
278 sendGetAppsCallback();
279 }
280 }
281
282 /**
283 * Update the app_launch_index of all apps
284 * @param {Array.<string>} appIds All app IDs in their desired order.
285 */
286 function reorderApps(movedAppId, appIds) {
287 assert(apps.length == appIds.length, 'Expected all apps in reorderApps');
288
289 // Clear the launch indicies so we can easily verify no dups
290 apps.forEach(function(a) {
291 a.app_launch_index = -1;
292 });
293
294 for (var i = 0; i < appIds.length; i++) {
295 var a = getApp(appIds[i]);
296 assert(a.app_launch_index == -1,
297 'Found duplicate appId in reorderApps');
298 a.app_launch_index = i;
299 }
300 sendGetAppsCallback();
301 }
302
303 /**
304 * Update the page number of an app
305 * @param {string} id The ID of the application to move.
306 * @param {number} page The page index to place the app.
307 */
308 function setPageIndex(id, page) {
309 var app = getApp(id);
310 app.page_index = page;
311 }
312
313 // The 'send' function
314 /**
315 * The chrome server communication entrypoint.
316 *
317 * @param {string} command Name of the command to send.
318 * @param {Array} args Array of command-specific arguments.
319 */
320 return function(command, args) {
321 // Chrome API is async
322 window.setTimeout(function() {
323 switch (command) {
324 // called to populate the list of applications
325 case 'getApps':
326 sendGetAppsCallback();
327 break;
328
329 // Called when an app is launched
330 // Ignore additional arguments - they've been changing over time and
331 // we don't use them in our NTP anyway.
332 case 'launchApp':
333 launchApp(args[0]);
334 break;
335
336 // Called when an app is uninstalled
337 case 'uninstallApp':
338 uninstallApp(args[0]);
339 break;
340
341 // Called when an app is repositioned in the touch NTP
342 case 'reorderApps':
343 reorderApps(args[0], args[1]);
344 break;
345
346 // Called when an app is moved to a different page
347 case 'setPageIndex':
348 setPageIndex(args[0], parseInt(args[1], 10));
349 break;
350
351 default:
352 throw new Error('Unexpected chrome command: ' + command);
353 break;
354 }
355 }, 0);
356 };
357 })();
358
359 /* A static templateData with english resources */
360 var templateData = {
361 title: 'Standalone New Tab',
362 web_store_title: 'Web Store',
363 web_store_url: 'https://chrome.google.com/webstore?hl=en-US'
364 };
365
366 /* Hook construction of chrome://theme URLs */
367 function themeUrlMapper(resourceName) {
368 if (resourceName == 'IDR_WEBSTORE_ICON') {
369 return 'standalone/webstore_icon.png';
370 }
371 return undefined;
372 }
373
374 /*
375 * On iOS we need a hack to avoid spurious click events
376 * In particular, if the user delays briefly between first touching and starting
377 * to drag, when the user releases a click event will be generated.
378 * Note that this seems to happen regardless of whether we do preventDefault on
379 * touchmove events.
380 */
381 if (/iPhone|iPod|iPad/.test(navigator.userAgent) &&
382 !(/Chrome/.test(navigator.userAgent))) {
383 // We have a real iOS device (no a ChromeOS device pretending to be iOS)
384 (function() {
385 // True if a gesture is occuring that should cause clicks to be swallowed
386 var gestureActive = false;
387
388 // The position a touch was last started
389 var lastTouchStartPosition;
390
391 // Distance which a touch needs to move to be considered a drag
392 var DRAG_DISTANCE = 3;
393
394 document.addEventListener('touchstart', function(event) {
395 lastTouchStartPosition = {
396 x: event.touches[0].clientX,
397 y: event.touches[0].clientY
398 };
399 // A touchstart ALWAYS preceeds a click (valid or not), so cancel any
400 // outstanding gesture. Also, any multi-touch is a gesture that should
401 // prevent clicks.
402 gestureActive = event.touches.length > 1;
403 }, true);
404
405 document.addEventListener('touchmove', function(event) {
406 // When we see a move, measure the distance from the last touchStart
407 // If this is a multi-touch then the work here is irrelevant
408 // (gestureActive is already true)
409 var t = event.touches[0];
410 if (Math.abs(t.clientX - lastTouchStartPosition.x) > DRAG_DISTANCE ||
411 Math.abs(t.clientY - lastTouchStartPosition.y) > DRAG_DISTANCE) {
412 gestureActive = true;
413 }
414 }, true);
415
416 document.addEventListener('click', function(event) {
417 // If we got here without gestureActive being set then it means we had
418 // a touchStart without any real dragging before touchEnd - we can allow
419 // the click to proceed.
420 if (gestureActive) {
421 event.preventDefault();
422 event.stopPropagation();
423 }
424 }, true);
425
426 })();
427 }
428
429 /* Hack to add Element.classList to older browsers that don't yet support it.
430 From https://developer.mozilla.org/en/DOM/element.classList.
431 */
432 if (typeof Element !== 'undefined' &&
433 !Element.prototype.hasOwnProperty('classList')) {
434 (function() {
435 var classListProp = 'classList',
436 protoProp = 'prototype',
437 elemCtrProto = Element[protoProp],
438 objCtr = Object,
439 strTrim = String[protoProp].trim || function() {
440 return this.replace(/^\s+|\s+$/g, '');
441 },
442 arrIndexOf = Array[protoProp].indexOf || function(item) {
443 for (var i = 0, len = this.length; i < len; i++) {
444 if (i in this && this[i] === item) {
445 return i;
446 }
447 }
448 return -1;
449 },
450 // Vendors: please allow content code to instantiate DOMExceptions
451 /** @constructor */
452 DOMEx = function(type, message) {
453 this.name = type;
454 this.code = DOMException[type];
455 this.message = message;
456 },
457 checkTokenAndGetIndex = function(classList, token) {
458 if (token === '') {
459 throw new DOMEx(
460 'SYNTAX_ERR',
461 'An invalid or illegal string was specified'
462 );
463 }
464 if (/\s/.test(token)) {
465 throw new DOMEx(
466 'INVALID_CHARACTER_ERR',
467 'String contains an invalid character'
468 );
469 }
470 return arrIndexOf.call(classList, token);
471 },
472 /** @constructor
473 * @extends Array */
474 ClassList = function(elem) {
475 var trimmedClasses = strTrim.call(elem.className),
476 classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [];
477
478 for (var i = 0, len = classes.length; i < len; i++) {
479 this.push(classes[i]);
480 }
481 this._updateClassName = function() {
482 elem.className = this.toString();
483 };
484 },
485 classListProto = ClassList[protoProp] = [],
486 classListGetter = function() {
487 return new ClassList(this);
488 };
489
490 // Most DOMException implementations don't allow calling DOMException's
491 // toString() on non-DOMExceptions. Error's toString() is sufficient here.
492 DOMEx[protoProp] = Error[protoProp];
493 classListProto.item = function(i) {
494 return this[i] || null;
495 };
496 classListProto.contains = function(token) {
497 token += '';
498 return checkTokenAndGetIndex(this, token) !== -1;
499 };
500 classListProto.add = function(token) {
501 token += '';
502 if (checkTokenAndGetIndex(this, token) === -1) {
503 this.push(token);
504 this._updateClassName();
505 }
506 };
507 classListProto.remove = function(token) {
508 token += '';
509 var index = checkTokenAndGetIndex(this, token);
510 if (index !== -1) {
511 this.splice(index, 1);
512 this._updateClassName();
513 }
514 };
515 classListProto.toggle = function(token) {
516 token += '';
517 if (checkTokenAndGetIndex(this, token) === -1) {
518 this.add(token);
519 } else {
520 this.remove(token);
521 }
522 };
523 classListProto.toString = function() {
524 return this.join(' ');
525 };
526
527 if (objCtr.defineProperty) {
528 var classListDescriptor = {
529 get: classListGetter,
530 enumerable: true,
531 configurable: true
532 };
533 objCtr.defineProperty(elemCtrProto, classListProp, classListDescriptor);
534 } else if (objCtr[protoProp].__defineGetter__) {
535 elemCtrProto.__defineGetter__(classListProp, classListGetter);
536 }
537 }());
538 }
539
540 /* Hack to add Function.bind to older browsers that don't yet support it. From:
541 https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function /bind
542 */
543 if (!Function.prototype.bind) {
544 /**
545 * @param {Object} selfObj Specifies the object which |this| should
546 * point to when the function is run. If the value is null or undefined,
547 * it will default to the global object.
548 * @param {...*} var_args Additional arguments that are partially
549 * applied to the function.
550 * @return {!Function} A partially-applied form of the function bind() was
551 * invoked as a method of.
552 * @suppress {duplicate}
553 */
554 Function.prototype.bind = function(selfObj, var_args) {
555 var slice = [].slice,
556 args = slice.call(arguments, 1),
557 self = this,
558 /** @constructor */
559 nop = function() {},
560 bound = function() {
561 return self.apply(this instanceof nop ? this : (selfObj || {}),
562 args.concat(slice.call(arguments)));
563 };
564 nop.prototype = self.prototype;
565 bound.prototype = new nop();
566 return bound;
567 };
568 }
569
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698