1ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Use of this source code is governed by a BSD-style license that can be
3ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// found in the LICENSE file.
4ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
5ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen/**
6ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @fileoverview New tab page
7ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * This is the main code for the new tab page used by touch-enabled Chrome
8ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * browsers.  For now this is still a prototype.
9ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */
10ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
11ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Use an anonymous function to enable strict mode just for this file (which
12ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// will be concatenated with other files when embedded in Chrome
13ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsencr.define('ntp4', function() {
14ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  'use strict';
15ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
16ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
17ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * The CardSlider object to use for changing app pages.
18ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {CardSlider|undefined}
19ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
20ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  var cardSlider;
21ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
22ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
23ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Template to use for creating new 'dot' elements
24ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {!Element|undefined}
25ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
26ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  var dotTemplate;
27ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
28ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
29ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * The 'page-list' element.
30ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {!Element|undefined}
31ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
32ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  var pageList;
33ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
34ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
35ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * A list of all 'tile-page' elements.
36ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {!NodeList|undefined}
37ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
38ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  var tilePages;
39ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
40ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
41ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * The Most Visited page.
42ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {!Element|undefined}
43ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
44ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  var mostVisitedPage;
45ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
46ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
47ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * A list of all 'apps-page' elements.
48ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {!NodeList|undefined}
49ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
50ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  var appsPages;
51ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
52ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
53ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * The 'dots-list' element.
54ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {!Element|undefined}
55ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
56ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  var dotList;
57ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
58ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
59ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * A list of all 'dots' elements.
60ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {!NodeList|undefined}
61ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
62ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  var dots;
63ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
64ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
65ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * The 'trash' element.  Note that technically this is unnecessary,
66ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * JavaScript creates the object for us based on the id.  But I don't want
67ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * to rely on the ID being the same, and JSCompiler doesn't know about it.
68ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {!Element|undefined}
69ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
70ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  var trash;
71ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
72ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
73ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * The time in milliseconds for most transitions.  This should match what's
74ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * in new_tab.css.  Unfortunately there's no better way to try to time
75ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * something to occur until after a transition has completed.
76ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {number}
77ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @const
78ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
79ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  var DEFAULT_TRANSITION_TIME = 500;
80ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
81ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
82ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * All the Grabber objects currently in use on the page
83ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {Array.<Grabber>}
84ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
85ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  var grabbers = [];
86ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
87ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
88ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Invoked at startup once the DOM is available to initialize the app.
89ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
90ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function initialize() {
91ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Load the current theme colors.
92ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    themeChanged(false);
93ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
94ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    dotList = getRequiredElement('dot-list');
95ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    pageList = getRequiredElement('page-list');
96ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    trash = getRequiredElement('trash');
97ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    trash.hidden = true;
98ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
99ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Request data on the apps so we can fill them in.
100ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Note that this is kicked off asynchronously.  'getAppsCallback' will be
101ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // invoked at some point after this function returns.
102ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    chrome.send('getApps');
103ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
104ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Prevent touch events from triggering any sort of native scrolling
105ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    document.addEventListener('touchmove', function(e) {
106ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      e.preventDefault();
107ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }, true);
108ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
109ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Get the template elements and remove them from the DOM.  Things are
110ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // simpler if we start with 0 pages and 0 apps and don't leave hidden
111ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // template elements behind in the DOM.
112ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    dots = dotList.getElementsByClassName('dot');
113ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    assert(dots.length == 1,
114ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen           'Expected exactly one dot in the dots-list.');
115ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    dotTemplate = dots[0];
116ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    dotList.removeChild(dots[0]);
117ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
118ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    tilePages = pageList.getElementsByClassName('tile-page');
119ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    appsPages = pageList.getElementsByClassName('apps-page');
120ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
121ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Initialize the cardSlider without any cards at the moment
122ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var sliderFrame = getRequiredElement('card-slider-frame');
123ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    cardSlider = new CardSlider(sliderFrame, pageList, [], 0,
124ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                sliderFrame.offsetWidth);
125ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    cardSlider.initialize();
126ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
127ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Ensure the slider is resized appropriately with the window
128ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    window.addEventListener('resize', function() {
129ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      cardSlider.resize(sliderFrame.offsetWidth);
130ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    });
131ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
132ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Handle the page being changed
133ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    pageList.addEventListener(
134ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        CardSlider.EventType.CARD_CHANGED,
135ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        function(e) {
136ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          // Update the active dot
137ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          var curDot = dotList.getElementsByClassName('selected')[0];
138ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          if (curDot)
139ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen            curDot.classList.remove('selected');
140ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          var newPageIndex = e.cardSlider.currentCard;
141ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          dots[newPageIndex].classList.add('selected');
142ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          // If an app was being dragged, move it to the end of the new page
143ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          if (draggingAppContainer)
144ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen            appsPages[newPageIndex].appendChild(draggingAppContainer);
145ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        });
146ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
147ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Add a drag handler to the body (for drags that don't land on an existing
148ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // app)
149ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    document.addEventListener(Grabber.EventType.DRAG_ENTER, appDragEnter);
150ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
151ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Handle dropping an app anywhere other than on the trash
152ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    document.addEventListener(Grabber.EventType.DROP, appDrop);
153ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
154ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Add handles to manage the transition into/out-of rearrange mode
155ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Note that we assume here that we only use a Grabber for moving apps,
156ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // so ANY GRAB event means we're enterring rearrange mode.
157ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    sliderFrame.addEventListener(Grabber.EventType.GRAB, enterRearrangeMode);
158ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    sliderFrame.addEventListener(Grabber.EventType.RELEASE, leaveRearrangeMode);
159ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
160ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Add handlers for the tash can
161ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    trash.addEventListener(Grabber.EventType.DRAG_ENTER, function(e) {
162ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      trash.classList.add('hover');
163ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      e.grabbedElement.classList.add('trashing');
164ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      e.stopPropagation();
165ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    });
166ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    trash.addEventListener(Grabber.EventType.DRAG_LEAVE, function(e) {
167ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      e.grabbedElement.classList.remove('trashing');
168ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      trash.classList.remove('hover');
169ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    });
170ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    trash.addEventListener(Grabber.EventType.DROP, appTrash);
171ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
172ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    cr.ui.decorate($('recently-closed-menu-button'), ntp4.RecentMenuButton);
173ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    chrome.send('getRecentlyClosedTabs');
174ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
175ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    mostVisitedPage = new ntp4.MostVisitedPage('Most Visited');
176ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    appendTilePage(mostVisitedPage);
177ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    chrome.send('getMostVisited');
178ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
179ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
180ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
181ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Simple common assertion API
182ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {*} condition The condition to test.  Note that this may be used to
183ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   *     test whether a value is defined or not, and we don't want to force a
184ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   *     cast to Boolean.
185ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {string=} opt_message A message to use in any error.
186ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
187ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function assert(condition, opt_message) {
188ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    'use strict';
189ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if (!condition) {
190ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      var msg = 'Assertion failed';
191ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if (opt_message)
192ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        msg = msg + ': ' + opt_message;
193ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      throw new Error(msg);
194ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }
195ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
196ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
197ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
198ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Get an element that's known to exist by its ID. We use this instead of just
199ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * calling getElementById and not checking the result because this lets us
200ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * satisfy the JSCompiler type system.
201ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {string} id The identifier name.
202ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @return {!Element} the Element.
203ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
204ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function getRequiredElement(id) {
205ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var element = document.getElementById(id);
206ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    assert(element, 'Missing required element: ' + id);
207ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    return element;
208ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
209ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
210ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
211ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Callback invoked by chrome with the apps available.
212ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   *
213ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Note that calls to this function can occur at any time, not just in
214ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * response to a getApps request. For example, when a user installs/uninstalls
215ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * an app on another synchronized devices.
216ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {Object} data An object with all the data on available
217ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   *        applications.
218ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
219ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function getAppsCallback(data) {
220ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Clean up any existing grabber objects - cancelling any outstanding drag.
221ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Ideally an async app update wouldn't disrupt an active drag but
222ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // that would require us to re-use existing elements and detect how the apps
223ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // have changed, which would be a lot of work.
224ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Note that we have to explicitly clean up the grabber objects so they stop
225ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // listening to events and break the DOM<->JS cycles necessary to enable
226ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // collection of all these objects.
227ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    grabbers.forEach(function(g) {
228ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // Note that this may raise DRAG_END/RELEASE events to clean up an
229ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // oustanding drag.
230ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      g.dispose();
231ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    });
232ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    assert(!draggingAppContainer && !draggingAppOriginalPosition &&
233ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen           !draggingAppOriginalPage);
234ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    grabbers = [];
235ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
236ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Clear any existing apps pages and dots.
237ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // TODO(rbyers): It might be nice to preserve animation of dots after an
238ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // uninstall. Could we re-use the existing page and dot elements?  It seems
239ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // unfortunate to have Chrome send us the entire apps list after an
240ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // uninstall.
241ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    for (var i = 0; i < appsPages.length; i++) {
242ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      var page = appsPages[i];
243ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      var dot = page.navigationDot;
244ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
245ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      page.tearDown();
246ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      page.parentNode.removeChild(page);
247ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      dot.parentNode.removeChild(dot);
248ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }
249ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
250ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Get the array of apps and add any special synthesized entries
251ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var apps = data.apps;
252ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
253ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Sort by launch index
254ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    apps.sort(function(a, b) {
255ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      return a.app_launch_index - b.app_launch_index;
256ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    });
257ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
258ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Add the apps, creating pages as necessary
259ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    for (var i = 0; i < apps.length; i++) {
260ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      var app = apps[i];
261ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      var pageIndex = (app.page_index || 0);
262ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      while (pageIndex >= appsPages.length) {
263ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        var origPageCount = appsPages.length;
264ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        appendTilePage(new ntp4.AppsPage('Apps'));
265ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        // Confirm that appsPages is a live object, updated when a new page is
266ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        // added (otherwise we'd have an infinite loop)
267ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        assert(appsPages.length == origPageCount + 1, 'expected new page');
268ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      }
269ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
270ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      appsPages[pageIndex].appendApp(app);
271ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }
272ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
273ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Add a couple blank apps pages for testing. TODO(estade): remove this.
274ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    appendTilePage(new ntp4.AppsPage('Foo'));
275ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    appendTilePage(new ntp4.AppsPage('Bar'));
276ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
277ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Tell the slider about the pages
278ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    updateSliderCards();
279ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
280ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Mark the current page
281ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    dots[cardSlider.currentCard].classList.add('selected');
282ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
283ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
284ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
285ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Make a synthesized app object representing the chrome web store.  It seems
286ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * like this could just as easily come from the back-end, and then would
287ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * support being rearranged, etc.
288ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @return {Object} The app object as would be sent from the webui back-end.
289ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
290ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function makeWebstoreApp() {
291ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    return {
292ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      id: '',   // Empty ID signifies this is a special synthesized app
293ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      page_index: 0,
294ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      app_launch_index: -1,   // always first
295ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      name: templateData.web_store_title,
296ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      launch_url: templateData.web_store_url,
297ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      icon_big: getThemeUrl('IDR_WEBSTORE_ICON')
298ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    };
299ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
300ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
301ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
302ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Given a theme resource name, construct a URL for it.
303ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {string} resourceName The name of the resource.
304ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @return {string} A url which can be used to load the resource.
305ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
306ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function getThemeUrl(resourceName) {
307ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    return 'chrome://theme/' + resourceName;
308ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
309ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
310ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
311ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Callback invoked by chrome whenever an app preference changes.
312ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * The normal NTP uses this to keep track of the current launch-type of an
313ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * app, updating the choices in the context menu.  We don't have such a menu
314ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * so don't use this at all (but it still needs to be here for chrome to
315ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * call).
316ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {Object} data An object with all the data on available
317ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   *        applications.
318ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
319ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function appsPrefChangeCallback(data) {
320ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
321ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
322ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
323ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Invoked whenever the pages in apps-page-list have changed so that
324ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * the Slider knows about the new elements.
325ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
326ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function updateSliderCards() {
327ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var pageNo = cardSlider.currentCard;
328ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if (pageNo >= tilePages.length)
329ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      pageNo = tilePages.length - 1;
330ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var pageArray = [];
331ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    for (var i = 0; i < tilePages.length; i++)
332ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      pageArray[i] = tilePages[i];
333ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    cardSlider.setCards(pageArray, pageNo);
334ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
335ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
336ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
337ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Appends a tile page (for apps or most visited).
338ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   *
339ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {TilePage} page The page element.
340ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {boolean=} opt_animate If true, add the class 'new' to the created
341ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   *        dot.
342ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
343ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function appendTilePage(page, opt_animate) {
344ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    pageList.appendChild(page);
345ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
346ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Make a deep copy of the dot template to add a new one.
347ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var dotCount = dots.length;
348ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var newDot = dotTemplate.cloneNode(true);
349ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    newDot.querySelector('span').textContent = page.pageName;
350ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if (opt_animate)
351ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      newDot.classList.add('new');
352ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    dotList.appendChild(newDot);
353ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    page.navigationDot = newDot;
354ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
355ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Add click handler to the dot to change the page.
356ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // TODO(rbyers): Perhaps this should be TouchHandler.START_EVENT_ (so we
357ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // don't rely on synthesized click events, and the change takes effect
358ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // before releasing). However, click events seems to be synthesized for a
359ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // region outside the border, and a 10px box is too small to require touch
360ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // events to fall inside of. We could get around this by adding a box around
361ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // the dot for accepting the touch events.
362ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    function switchPage(e) {
363ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      cardSlider.selectCard(dotCount, true);
364ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      e.stopPropagation();
365ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }
366ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    newDot.addEventListener('click', switchPage);
367ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
368ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Change pages whenever an app is dragged over a dot.
369ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    newDot.addEventListener(Grabber.EventType.DRAG_ENTER, switchPage);
370ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
371ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
372ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Search an elements ancestor chain for the nearest element that is a member
373ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * of the specified class.
374ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {!Element} element The element to start searching from.
375ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {string} className The name of the class to locate.
376ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @return {Element} The first ancestor of the specified class or null.
377ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
378ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function getParentByClassName(element, className) {
379ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    for (var e = element; e; e = e.parentElement) {
380ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if (e.classList.contains(className))
381ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        return e;
382ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }
383ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    return null;
384ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
385ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
386ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
387ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * The container where the app currently being dragged came from.
388ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {!Element|undefined}
389ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
390ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  var draggingAppContainer;
391ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
392ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
393ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * The apps-page that the app currently being dragged camed from.
394ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {!Element|undefined}
395ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
396ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  var draggingAppOriginalPage;
397ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
398ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
399ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * The element that was originally after the app currently being dragged (or
400ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * null if it was the last on the page).
401ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {!Element|undefined}
402ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
403ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  var draggingAppOriginalPosition;
404ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
405ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
406ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Invoked when app dragging begins.
407ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {Grabber.Event} e The event from the Grabber indicating the drag.
408ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
409ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function appDragStart(e) {
410ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Pull the element out to the sliderFrame using fixed positioning. This
411ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // ensures that the app is not affected (remains under the finger) if the
412ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // slider changes cards and is translated.  An alternate approach would be
413ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // to use fixed positioning for the slider (so that changes to its position
414ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // don't affect children that aren't positioned relative to it), but we
415ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // don't yet have GPU acceleration for this.
416ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var element = e.grabbedElement;
417ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
418ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var pos = element.getBoundingClientRect();
419ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    element.style.webkitTransform = '';
420ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
421ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    element.style.position = 'fixed';
422ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Don't want to zoom around the middle since the left/top co-ordinates
423ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // are post-transform values.
424ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    element.style.webkitTransformOrigin = 'left top';
425ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    element.style.left = pos.left + 'px';
426ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    element.style.top = pos.top + 'px';
427ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
428ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Keep track of what app is being dragged and where it came from
429ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    assert(!draggingAppContainer, 'got DRAG_START without DRAG_END');
430ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    draggingAppContainer = element.parentNode;
431ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    assert(draggingAppContainer.classList.contains('app-container'));
432ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    draggingAppOriginalPosition = draggingAppContainer.nextSibling;
433ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    draggingAppOriginalPage = draggingAppContainer.parentNode;
434ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
435ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Move the app out of the container
436ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Note that appendChild also removes the element from its current parent.
437ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    sliderFrame.appendChild(element);
438ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
439ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
440ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
441ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Invoked when app dragging terminates (either successfully or not)
442ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {Grabber.Event} e The event from the Grabber.
443ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
444ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function appDragEnd(e) {
445ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Stop floating the app
446ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var appBeingDragged = e.grabbedElement;
447ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    assert(appBeingDragged.classList.contains('app'));
448ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    appBeingDragged.style.position = '';
449ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    appBeingDragged.style.webkitTransformOrigin = '';
450ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    appBeingDragged.style.left = '';
451ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    appBeingDragged.style.top = '';
452ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
453ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Ensure the trash can is not active (we won't necessarily get a DRAG_LEAVE
454ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // for it - eg. if we drop on it, or the drag is cancelled)
455ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    trash.classList.remove('hover');
456ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    appBeingDragged.classList.remove('trashing');
457ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
458ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // If we have an active drag (i.e. it wasn't aborted by an app update)
459ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if (draggingAppContainer) {
460ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // Put the app back into it's container
461ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if (appBeingDragged.parentNode != draggingAppContainer)
462ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        draggingAppContainer.appendChild(appBeingDragged);
463ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
464ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // If we care about the container's original position
465ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if (draggingAppOriginalPage)
466ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      {
467ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        // Then put the container back where it came from
468ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        if (draggingAppOriginalPosition) {
469ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          draggingAppOriginalPage.insertBefore(draggingAppContainer,
470ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                               draggingAppOriginalPosition);
471ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        } else {
472ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          draggingAppOriginalPage.appendChild(draggingAppContainer);
473ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        }
474ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      }
475ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }
476ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
477ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    draggingAppContainer = undefined;
478ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    draggingAppOriginalPage = undefined;
479ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    draggingAppOriginalPosition = undefined;
480ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
481ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
482ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
483ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Invoked when an app is dragged over another app.  Updates the DOM to affect
484ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * the rearrangement (but doesn't commit the change until the app is dropped).
485ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {Grabber.Event} e The event from the Grabber indicating the drag.
486ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
487ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function appDragEnter(e)
488ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  {
489ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    assert(draggingAppContainer, 'expected stored container');
490ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var sourceContainer = draggingAppContainer;
491ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
492ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Ensure enter events delivered to an app-container don't also get
493ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // delivered to the document.
494ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    e.stopPropagation();
495ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
496ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var curPage = appsPages[cardSlider.currentCard];
497ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var followingContainer = null;
498ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
499ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // If we dragged over a specific app, determine which one to insert before
500ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if (e.currentTarget != document) {
501ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
502ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // Start by assuming we'll insert the app before the one dragged over
503ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      followingContainer = e.currentTarget;
504ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      assert(followingContainer.classList.contains('app-container'),
505ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen             'expected drag over container');
506ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      assert(followingContainer.parentNode == curPage);
507ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if (followingContainer == draggingAppContainer)
508ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        return;
509ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
510ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // But if it's after the current container position then we'll need to
511ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // move ahead by one to account for the container being removed.
512ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if (curPage == draggingAppContainer.parentNode) {
513ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        for (var c = draggingAppContainer; c; c = c.nextElementSibling) {
514ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          if (c == followingContainer) {
515ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen            followingContainer = followingContainer.nextElementSibling;
516ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen            break;
517ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          }
518ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        }
519ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      }
520ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }
521ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
522ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Move the container to the appropriate place on the page
523ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    curPage.insertBefore(draggingAppContainer, followingContainer);
524ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
525ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
526ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
527ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Invoked when an app is dropped on the trash
528ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {Grabber.Event} e The event from the Grabber indicating the drop.
529ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
530ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function appTrash(e) {
531ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var appElement = e.grabbedElement;
532ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    assert(appElement.classList.contains('app'));
533ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var appId = appElement.getAttribute('app-id');
534ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    assert(appId);
535ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
536ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Mark this drop as handled so that the catch-all drop handler
537ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // on the document doesn't see this event.
538ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    e.stopPropagation();
539ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
540ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Tell chrome to uninstall the app (prompting the user)
541ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    chrome.send('uninstallApp', [appId]);
542ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
543ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
544ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
545ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Called when an app is dropped anywhere other than the trash can.  Commits
546ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * any movement that has occurred.
547ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {Grabber.Event} e The event from the Grabber indicating the drop.
548ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
549ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function appDrop(e) {
550ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if (!draggingAppContainer)
551ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // Drag was aborted (eg. due to an app update) - do nothing
552ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      return;
553ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
554ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // If the app is dropped back into it's original position then do nothing
555ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    assert(draggingAppOriginalPage);
556ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if (draggingAppContainer.parentNode == draggingAppOriginalPage &&
557ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        draggingAppContainer.nextSibling == draggingAppOriginalPosition)
558ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      return;
559ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
560ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Determine which app was being dragged
561ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var appElement = e.grabbedElement;
562ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    assert(appElement.classList.contains('app'));
563ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var appId = appElement.getAttribute('app-id');
564ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    assert(appId);
565ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
566ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Update the page index for the app if it's changed.  This doesn't trigger
567ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // a call to getAppsCallback so we want to do it before reorderApps
568ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var pageIndex = cardSlider.currentCard;
569ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    assert(pageIndex >= 0 && pageIndex < appsPages.length,
570ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen           'page number out of range');
571ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if (appsPages[pageIndex] != draggingAppOriginalPage)
572ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      chrome.send('setPageIndex', [appId, pageIndex]);
573ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
574ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Put the app being dragged back into it's container
575ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    draggingAppContainer.appendChild(appElement);
576ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
577ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Create a list of all appIds in the order now present in the DOM
578ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var appIds = [];
579ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    for (var page = 0; page < appsPages.length; page++) {
580ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      var appsOnPage = appsPages[page].getElementsByClassName('app');
581ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      for (var i = 0; i < appsOnPage.length; i++) {
582ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        var id = appsOnPage[i].getAttribute('app-id');
583ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        if (id)
584ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          appIds.push(id);
585ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      }
586ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }
587ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
588ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // We are going to commit this repositioning - clear the original position
589ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    draggingAppOriginalPage = undefined;
590ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    draggingAppOriginalPosition = undefined;
591ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
592ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Tell chrome to update its database to persist this new order of apps This
593ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // will cause getAppsCallback to be invoked and the apps to be redrawn.
594ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    chrome.send('reorderApps', [appId, appIds]);
595ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    appMoved = true;
596ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
597ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
598ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
599ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Set to true if we're currently in rearrange mode and an app has
600ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * been successfully dropped to a new location.  This indicates that
601ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * a getAppsCallback call is pending and we can rely on the DOM being
602ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * updated by that.
603ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {boolean}
604ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
605ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  var appMoved = false;
606ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
607ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
608ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Invoked whenever some app is grabbed
609ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {Grabber.Event} e The Grabber Grab event.
610ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
611ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function enterRearrangeMode(e)
612ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  {
613ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Stop the slider from sliding for this touch
614ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    cardSlider.cancelTouch();
615ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
616ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Add an extra blank page in case the user wants to create a new page
617ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    appendTilePage(new ntp4.AppsPage(''), true);
618ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var pageAdded = appsPages.length - 1;
619ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    window.setTimeout(function() {
620ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      dots[pageAdded].classList.remove('new');
621ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }, 0);
622ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
623ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    updateSliderCards();
624ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
625ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Cause the dot-list to grow
626ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    getRequiredElement('footer').classList.add('rearrange-mode');
627ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
628ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    assert(!appMoved, 'appMoved should not be set yet');
629ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
630ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
631ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
632ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Invoked whenever some app is released
633ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {Grabber.Event} e The Grabber RELEASE event.
634ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
635ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function leaveRearrangeMode(e)
636ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  {
637ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Return the dot-list to normal
638ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    getRequiredElement('footer').classList.remove('rearrange-mode');
639ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
640ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // If we didn't successfully re-arrange an app, then we won't be
641ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // refreshing the app view in getAppCallback and need to explicitly remove
642ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // the extra empty page we added.  We don't want to do this in the normal
643ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // case because if we did actually drop an app there, we want to retain that
644ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // page as our current page number.
645ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if (!appMoved) {
646ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      assert(appsPages[appsPages.length - 1].
647ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen             getElementsByClassName('app-container').length == 0,
648ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen             'Last app page should be empty');
649ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      removePage(appsPages.length - 1);
650ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }
651ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    appMoved = false;
652ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
653ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
654ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
655ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Remove the page with the specified index and update the slider.
656ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {number} pageNo The index of the page to remove.
657ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
658ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function removePage(pageNo) {
659ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    pageList.removeChild(tilePages[pageNo]);
660ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
661ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Remove the corresponding dot
662ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Need to give it a chance to animate though
663ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    var dot = dots[pageNo];
664ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    dot.classList.add('new');
665ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    window.setTimeout(function() {
666ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // If we've re-created the apps (eg. because an app was uninstalled) then
667ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // we will have removed the old dots from the document already, so skip.
668ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if (dot.parentNode)
669ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        dot.parentNode.removeChild(dot);
670ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }, DEFAULT_TRANSITION_TIME);
671ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
672ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    updateSliderCards();
673ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
674ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
675ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  // TODO(estade): remove |hasAttribution|.
676ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  // TODO(estade): rename newtab.css to new_tab_theme.css
677ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function themeChanged(hasAttribution) {
678ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    $('themecss').href = 'chrome://theme/css/newtab.css?' + Date.now();
679ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
680ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
681ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function setRecentlyClosedTabs(dataItems) {
682ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    $('recently-closed-menu-button').dataItems = dataItems;
683ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
684ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
685ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function setMostVisitedPages(data, firstRun, hasBlacklistedUrls) {
686ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    mostVisitedPage.data = data;
687ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
688ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
689ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  // Return an object with all the exports
690ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  return {
691ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    assert: assert,
692ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    appsPrefChangeCallback: appsPrefChangeCallback,
693ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    getAppsCallback: getAppsCallback,
694ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    initialize: initialize,
695ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    themeChanged: themeChanged,
696ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    setRecentlyClosedTabs: setRecentlyClosedTabs,
697ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    setMostVisitedPages: setMostVisitedPages,
698ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  };
699ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen});
700ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
701ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// publish ntp globals
702ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// TODO(estade): update the content handlers to use ntp namespace instead of
703ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// making these global.
704ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenvar assert = ntp4.assert;
705ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenvar getAppsCallback = ntp4.getAppsCallback;
706ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenvar appsPrefChangeCallback = ntp4.appsPrefChangeCallback;
707ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenvar themeChanged = ntp4.themeChanged;
708ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenvar recentlyClosedTabs = ntp4.setRecentlyClosedTabs;
709ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenvar mostVisitedPages = ntp4.setMostVisitedPages;
710ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
711ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsendocument.addEventListener('DOMContentLoaded', ntp4.initialize);
712