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

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

Powered by Google App Engine
This is Rietveld 408576698