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

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

Powered by Google App Engine
This is Rietveld 408576698