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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/resources/touch_ntp/standalone/standalone_hack.js
diff --git a/chrome/browser/resources/touch_ntp/standalone/standalone_hack.js b/chrome/browser/resources/touch_ntp/standalone/standalone_hack.js
new file mode 100644
index 0000000000000000000000000000000000000000..d636f999c76a314da2bbe1357ed42dde7c7460c7
--- /dev/null
+++ b/chrome/browser/resources/touch_ntp/standalone/standalone_hack.js
@@ -0,0 +1,562 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview NTP Standalone hack
+ * This file contains the code necessary to make the Touch NTP work
+ * as a stand-alone application (as opposed to being embedded into chrome).
+ * This is useful for rapid development and testing, but does not actually form
+ * part of the product.
+ *
+ * Note that, while the product portion of the touch NTP is designed to work
+ * just in the latest version of Chrome, this hack attempts to add some support
+ * for working in older browsers to enable testing and demonstration on
+ * existing tablet platforms. In particular, this code has been tested to work
+ * on Mobile Safari in iOS 4.2. The goal is that the need to support any other
+ * browser should not leak out of this file - and so we will hack global JS
+ * objects as necessary here to present the illusion of running on the latest
+ * version of Chrome.
+ */
+
+// Note that this file never gets concatenated and embeded into Chrome, so we
+// can enable strict mode for the whole file just like normal.
+'use strict';
+
+
+/**
+ * For non-Chrome browsers, create a dummy chrome object
+ */
+if (!window.chrome) {
+ var chrome = {};
+}
+
+
+/**
+ * A replacement chrome.send method that supplies static data for the
+ * key APIs used by the NTP.
+ *
+ * Note that the real chrome object also supplies data for most-viewed and
+ * recently-closed pages, but the tangent NTP doesn't use that data so we
+ * don't bother simulating it here.
+ *
+ * We create this object by applying an anonymous function so that we can have
+ * local variables (avoid polluting the global object)
+ */
+chrome.send = (function() {
+ var apps = [{
+ app_launch_index: 2,
+ description: 'The prickly puzzle game where popping balloons has ' +
+ 'never been so much fun!',
+ icon_big: 'standalone/poppit-icon.png',
+ icon_small: 'standalone/poppit-favicon.png',
+ id: 'mcbkbpnkkkipelfledbfocopglifcfmi',
+ launch_container: 2,
+ launch_type: 1,
+ launch_url: 'http://poppit.pogo.com/hd/PoppitHD.html',
+ name: 'Poppit',
+ options_url: ''
+ },
+ {
+ app_launch_index: 1,
+ description: 'Fast, searchable email with less spam.',
+ icon_big: 'standalone/gmail-icon.png',
+ icon_small: 'standalone/gmail-favicon.png',
+ id: 'pjkljhegncpnkpknbcohdijeoejaedia',
+ launch_container: 2,
+ launch_type: 1,
+ launch_url: 'https://mail.google.com/',
+ name: 'Gmail',
+ options_url: 'https://mail.google.com/mail/#settings'
+ },
+ {
+ app_launch_index: 3,
+ description: 'Read over 3 million Google eBooks on the web.',
+ icon_big: 'standalone/googlebooks-icon.png',
+ icon_small: 'standalone/googlebooks-favicon.png',
+ id: 'mmimngoggfoobjdlefbcabngfnmieonb',
+ launch_container: 2,
+ launch_type: 1,
+ launch_url: 'http://books.google.com/ebooks?source=chrome-app',
+ name: 'Google Books',
+ options_url: ''
+ },
+ {
+ app_launch_index: 4,
+ description: 'Find local business information, directions, and ' +
+ 'street-level imagery around the world with Google Maps.',
+ icon_big: 'standalone/googlemaps-icon.png',
+ icon_small: 'standalone/googlemaps-favicon.png',
+ id: 'lneaknkopdijkpnocmklfnjbeapigfbh',
+ launch_container: 2,
+ launch_type: 1,
+ launch_url: 'http://maps.google.com/',
+ name: 'Google Maps',
+ options_url: ''
+ },
+ {
+ app_launch_index: 5,
+ description: 'Create the longest path possible and challenge your ' +
+ 'friends in the game of Entanglement.',
+ icon_big: 'standalone/entaglement-icon.png',
+ id: 'aciahcmjmecflokailenpkdchphgkefd',
+ launch_container: 2,
+ launch_type: 1,
+ launch_url: 'http://entanglement.gopherwoodstudios.com/',
+ name: 'Entanglement',
+ options_url: ''
+ },
+ {
+ name: 'NYTimes',
+ app_launch_index: 6,
+ description: 'The New York Times App for the Chrome Web Store.',
+ icon_big: 'standalone/nytimes-icon.png',
+ id: 'ecmphppfkcfflgglcokcbdkofpfegoel',
+ launch_container: 2,
+ launch_type: 1,
+ launch_url: 'http://www.nytimes.com/chrome/',
+ options_url: '',
+ page_index: 2
+ },
+ {
+ app_launch_index: 7,
+ description: 'The world\'s most popular online video community.',
+ id: 'blpcfgokakmgnkcojhhkbfbldkacnbeo',
+ icon_big: 'standalone/youtube-icon.png',
+ launch_container: 2,
+ launch_type: 1,
+ launch_url: 'http://www.youtube.com/',
+ name: 'YouTube',
+ options_url: '',
+ page_index: 3
+ }];
+
+ // For testing
+ apps = spamApps(apps);
+
+ /**
+ * Invoke the getAppsCallback function with a snapshot of the current app
+ * database.
+ */
+ function sendGetAppsCallback()
+ {
+ // We don't want to hand out our array directly because the NTP will
+ // assume it owns the array and is free to modify it. For now we make a
+ // one-level deep copy of the array (since cloning the whole thing is
+ // more work and unnecessary at the moment).
+ var appsData = {
+ showPromo: false,
+ showLauncher: true,
+ apps: apps.slice(0)
+ };
+ getAppsCallback(appsData);
+ }
+
+ /**
+ * To make testing real-world scenarios easier, this expands our list of
+ * apps by duplicating them a number of times
+ */
+ function spamApps(apps)
+ {
+ // Create an object that extends another object
+ // This is an easy/efficient way to make slightly modified copies of our
+ // app objects without having to do a deep copy
+ function createObject(proto) {
+ /** @constructor */
+ var F = function() {};
+ F.prototype = proto;
+ return new F();
+ }
+
+ var newApps = [];
+ var pages = Math.floor(Math.random() * 8) + 1;
+ var idx = 1;
+ for (var p = 0; p < pages; p++) {
+ var count = Math.floor(Math.random() * 18) + 1;
+ for (var a = 0; a < count; a++) {
+ var i = Math.floor(Math.random() * apps.length);
+ var newApp = createObject(apps[i]);
+ newApp.page_index = p;
+ newApp.app_launch_index = idx;
+ // Uniqify the ID
+ newApp.id = apps[i].id + '-' + idx;
+ idx++;
+ newApps.push(newApp);
+ }
+ }
+ return newApps;
+ }
+
+ /**
+ * Like Array.prototype.indexOf but calls a predicate to test for match
+ *
+ * @param {Array} array The array to search.
+ * @param {function(Object): boolean} predicate The function to invoke on
+ * each element.
+ * @return {number} First index at which predicate returned true, or -1.
+ */
+ function indexOfPred(array, predicate) {
+ for (var i = 0; i < array.length; i++) {
+ if (predicate(array[i]))
+ return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Get index into apps of an application object
+ * Requires the specified app to be present
+ *
+ * @param {string} id The ID of the application to locate.
+ * @return {number} The index in apps for an object with the specified ID.
+ */
+ function getAppIndex(id) {
+ var i = indexOfPred(apps, function(e) { return e.id === id;});
+ if (i == -1)
+ alert('Error: got unexpected App ID');
+ return i;
+ }
+
+ /**
+ * Get an application object given the application ID
+ * Requires
+ * @param {string} id The application ID to search for.
+ * @return {Object} The corresponding application object.
+ */
+ function getApp(id) {
+ return apps[getAppIndex(id)];
+ }
+
+ /**
+ * Simlulate the launching of an application
+ *
+ * @param {string} id The ID of the application to launch.
+ */
+ function launchApp(id) {
+ // Note that we don't do anything with the icon location.
+ // That's used by Chrome only on Windows to animate the icon during
+ // launch.
+ var app = getApp(id);
+ switch (parseInt(app.launch_type, 10)) {
+ case 0: // pinned
+ case 1: // regular
+ // Replace the current tab with the app.
+ // Pinned seems to omit the tab title, but I doubt it's
+ // possible for us to do that here
+ window.location = (app.launch_url);
+ break;
+
+ case 2: // fullscreen
+ case 3: // window
+ // attempt to launch in a new window
+ window.close();
+ window.open(app.launch_url, app.name,
+ 'resizable=yes,scrollbars=yes,status=yes');
+ break;
+
+ default:
+ alert('Unexpected launch type: ' + app.launch_type);
+ }
+ }
+
+ /**
+ * Simulate uninstall of an app
+ * @param {string} id The ID of the application to uninstall.
+ */
+ function uninstallApp(id) {
+ var i = getAppIndex(id);
+ // This confirmation dialog doesn't look exactly the same as the
+ // standard NTP one, but it's close enough.
+ if (window.confirm('Uninstall \"' + apps[i].name + '\"?')) {
+ apps.splice(i, 1);
+ sendGetAppsCallback();
+ }
+ }
+
+ /**
+ * Update the app_launch_index of all apps
+ * @param {Array.<string>} appIds All app IDs in their desired order.
+ */
+ function reorderApps(movedAppId, appIds) {
+ assert(apps.length == appIds.length, 'Expected all apps in reorderApps');
+
+ // Clear the launch indicies so we can easily verify no dups
+ apps.forEach(function(a) {
+ a.app_launch_index = -1;
+ });
+
+ for (var i = 0; i < appIds.length; i++) {
+ var a = getApp(appIds[i]);
+ assert(a.app_launch_index == -1,
+ 'Found duplicate appId in reorderApps');
+ a.app_launch_index = i;
+ }
+ sendGetAppsCallback();
+ }
+
+ /**
+ * Update the page number of an app
+ * @param {string} id The ID of the application to move.
+ * @param {number} page The page index to place the app.
+ */
+ function setPageIndex(id, page) {
+ var app = getApp(id);
+ app.page_index = page;
+ }
+
+ // The 'send' function
+ /**
+ * The chrome server communication entrypoint.
+ *
+ * @param {string} command Name of the command to send.
+ * @param {Array} args Array of command-specific arguments.
+ */
+ return function(command, args) {
+ // Chrome API is async
+ window.setTimeout(function() {
+ switch (command) {
+ // called to populate the list of applications
+ case 'getApps':
+ sendGetAppsCallback();
+ break;
+
+ // Called when an app is launched
+ // Ignore additional arguments - they've been changing over time and
+ // we don't use them in our NTP anyway.
+ case 'launchApp':
+ launchApp(args[0]);
+ break;
+
+ // Called when an app is uninstalled
+ case 'uninstallApp':
+ uninstallApp(args[0]);
+ break;
+
+ // Called when an app is repositioned in the touch NTP
+ case 'reorderApps':
+ reorderApps(args[0], args[1]);
+ break;
+
+ // Called when an app is moved to a different page
+ case 'setPageIndex':
+ setPageIndex(args[0], parseInt(args[1], 10));
+ break;
+
+ default:
+ throw new Error('Unexpected chrome command: ' + command);
+ break;
+ }
+ }, 0);
+ };
+})();
+
+/* A static templateData with english resources */
+var templateData = {
+ title: 'Standalone New Tab',
+ web_store_title: 'Web Store',
+ web_store_url: 'https://chrome.google.com/webstore?hl=en-US'
+};
+
+/* Hook construction of chrome://theme URLs */
+function themeUrlMapper(resourceName) {
+ if (resourceName == 'IDR_WEBSTORE_ICON') {
+ return 'standalone/webstore_icon.png';
+ }
+ return undefined;
+}
+
+/*
+ * On iOS we need a hack to avoid spurious click events
+ * In particular, if the user delays briefly between first touching and starting
+ * to drag, when the user releases a click event will be generated.
+ * Note that this seems to happen regardless of whether we do preventDefault on
+ * touchmove events.
+ */
+if (/iPhone|iPod|iPad/.test(navigator.userAgent) &&
+ !(/Chrome/.test(navigator.userAgent))) {
+ // We have a real iOS device (no a ChromeOS device pretending to be iOS)
+ (function() {
+ // True if a gesture is occuring that should cause clicks to be swallowed
+ var gestureActive = false;
+
+ // The position a touch was last started
+ var lastTouchStartPosition;
+
+ // Distance which a touch needs to move to be considered a drag
+ var DRAG_DISTANCE = 3;
+
+ document.addEventListener('touchstart', function(event) {
+ lastTouchStartPosition = {
+ x: event.touches[0].clientX,
+ y: event.touches[0].clientY
+ };
+ // A touchstart ALWAYS preceeds a click (valid or not), so cancel any
+ // outstanding gesture. Also, any multi-touch is a gesture that should
+ // prevent clicks.
+ gestureActive = event.touches.length > 1;
+ }, true);
+
+ document.addEventListener('touchmove', function(event) {
+ // When we see a move, measure the distance from the last touchStart
+ // If this is a multi-touch then the work here is irrelevant
+ // (gestureActive is already true)
+ var t = event.touches[0];
+ if (Math.abs(t.clientX - lastTouchStartPosition.x) > DRAG_DISTANCE ||
+ Math.abs(t.clientY - lastTouchStartPosition.y) > DRAG_DISTANCE) {
+ gestureActive = true;
+ }
+ }, true);
+
+ document.addEventListener('click', function(event) {
+ // If we got here without gestureActive being set then it means we had
+ // a touchStart without any real dragging before touchEnd - we can allow
+ // the click to proceed.
+ if (gestureActive) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }, true);
+ })();
+}
+
+/* Hack to add Element.classList to older browsers that don't yet support it.
+ From https://developer.mozilla.org/en/DOM/element.classList.
+*/
+if (typeof Element !== 'undefined' &&
+ !Element.prototype.hasOwnProperty('classList')) {
+ (function() {
+ var classListProp = 'classList',
+ protoProp = 'prototype',
+ elemCtrProto = Element[protoProp],
+ objCtr = Object,
+ strTrim = String[protoProp].trim || function() {
+ return this.replace(/^\s+|\s+$/g, '');
+ },
+ arrIndexOf = Array[protoProp].indexOf || function(item) {
+ for (var i = 0, len = this.length; i < len; i++) {
+ if (i in this && this[i] === item) {
+ return i;
+ }
+ }
+ return -1;
+ },
+ // Vendors: please allow content code to instantiate DOMExceptions
+ /** @constructor */
+ DOMEx = function(type, message) {
+ this.name = type;
+ this.code = DOMException[type];
+ this.message = message;
+ },
+ checkTokenAndGetIndex = function(classList, token) {
+ if (token === '') {
+ throw new DOMEx(
+ 'SYNTAX_ERR',
+ 'An invalid or illegal string was specified'
+ );
+ }
+ if (/\s/.test(token)) {
+ throw new DOMEx(
+ 'INVALID_CHARACTER_ERR',
+ 'String contains an invalid character'
+ );
+ }
+ return arrIndexOf.call(classList, token);
+ },
+ /** @constructor
+ * @extends {Array} */
+ ClassList = function(elem) {
+ var trimmedClasses = strTrim.call(elem.className),
+ classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [];
+
+ for (var i = 0, len = classes.length; i < len; i++) {
+ this.push(classes[i]);
+ }
+ this._updateClassName = function() {
+ elem.className = this.toString();
+ };
+ },
+ classListProto = ClassList[protoProp] = [],
+ classListGetter = function() {
+ return new ClassList(this);
+ };
+
+ // Most DOMException implementations don't allow calling DOMException's
+ // toString() on non-DOMExceptions. Error's toString() is sufficient here.
+ DOMEx[protoProp] = Error[protoProp];
+ classListProto.item = function(i) {
+ return this[i] || null;
+ };
+ classListProto.contains = function(token) {
+ token += '';
+ return checkTokenAndGetIndex(this, token) !== -1;
+ };
+ classListProto.add = function(token) {
+ token += '';
+ if (checkTokenAndGetIndex(this, token) === -1) {
+ this.push(token);
+ this._updateClassName();
+ }
+ };
+ classListProto.remove = function(token) {
+ token += '';
+ var index = checkTokenAndGetIndex(this, token);
+ if (index !== -1) {
+ this.splice(index, 1);
+ this._updateClassName();
+ }
+ };
+ classListProto.toggle = function(token) {
+ token += '';
+ if (checkTokenAndGetIndex(this, token) === -1) {
+ this.add(token);
+ } else {
+ this.remove(token);
+ }
+ };
+ classListProto.toString = function() {
+ return this.join(' ');
+ };
+
+ if (objCtr.defineProperty) {
+ var classListDescriptor = {
+ get: classListGetter,
+ enumerable: true,
+ configurable: true
+ };
+ objCtr.defineProperty(elemCtrProto, classListProp, classListDescriptor);
+ } else if (objCtr[protoProp].__defineGetter__) {
+ elemCtrProto.__defineGetter__(classListProp, classListGetter);
+ }
+ }());
+}
+
+/* Hack to add Function.bind to older browsers that don't yet support it. From:
+ https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
+*/
+if (!Function.prototype.bind) {
+ /**
+ * @param {Object} selfObj Specifies the object which |this| should
+ * point to when the function is run. If the value is null or undefined,
+ * it will default to the global object.
+ * @param {...*} var_args Additional arguments that are partially
+ * applied to the function.
+ * @return {!Function} A partially-applied form of the function bind() was
+ * invoked as a method of.
+ * @suppress {duplicate}
+ */
+ Function.prototype.bind = function(selfObj, var_args) {
+ var slice = [].slice,
+ args = slice.call(arguments, 1),
+ self = this,
+ /** @constructor */
+ nop = function() {},
+ bound = function() {
+ return self.apply(this instanceof nop ? this : (selfObj || {}),
+ args.concat(slice.call(arguments)));
+ };
+ nop.prototype = self.prototype;
+ bound.prototype = new nop();
+ return bound;
+ };
+}
+

Powered by Google App Engine
This is Rietveld 408576698