Updated drag and drop thumbnails.
[chromium-blink-merge.git] / chrome / browser / resources / ntp4 / other_sessions.js
blobe536e6ef3d51b6cbbba29352abb6f416cd9e652f
1 // Copyright (c) 2012 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.
5 /**
6 * @fileoverview The menu that shows tabs from sessions on other devices.
7 */
9 cr.define('ntp', function() {
10 'use strict';
12 /** @const */ var ContextMenuButton = cr.ui.ContextMenuButton;
13 /** @const */ var Menu = cr.ui.Menu;
14 /** @const */ var MenuItem = cr.ui.MenuItem;
15 /** @const */ var MenuButton = cr.ui.MenuButton;
16 /** @const */ var OtherSessionsMenuButton = cr.ui.define('button');
18 // Histogram buckets for UMA tracking of menu usage.
19 /** @const */ var HISTOGRAM_EVENT = {
20 INITIALIZED: 0,
21 SHOW_MENU: 1,
22 LINK_CLICKED: 2,
23 LINK_RIGHT_CLICKED: 3,
24 SESSION_NAME_RIGHT_CLICKED: 4,
25 SHOW_SESSION_MENU: 5,
26 COLLAPSE_SESSION: 6,
27 EXPAND_SESSION: 7,
28 OPEN_ALL: 8
30 /** @const */ var HISTOGRAM_EVENT_LIMIT =
31 HISTOGRAM_EVENT.OPEN_ALL + 1;
33 /**
34 * Record an event in the UMA histogram.
35 * @param {number} eventId The id of the event to be recorded.
36 * @private
38 function recordUmaEvent_(eventId) {
39 chrome.send('metricsHandler:recordInHistogram',
40 ['NewTabPage.OtherSessionsMenu', eventId, HISTOGRAM_EVENT_LIMIT]);
43 OtherSessionsMenuButton.prototype = {
44 __proto__: MenuButton.prototype,
46 decorate: function() {
47 MenuButton.prototype.decorate.call(this);
48 this.menu = new Menu;
49 cr.ui.decorate(this.menu, Menu);
50 this.menu.classList.add('footer-menu');
51 this.menu.addEventListener('contextmenu',
52 this.onContextMenu_.bind(this), true);
53 document.body.appendChild(this.menu);
55 // Create the context menu that appears when the user right clicks
56 // on a device name.
57 this.deviceContextMenu_ = DeviceContextMenuController.getInstance().menu;
58 document.body.appendChild(this.deviceContextMenu_);
60 this.promoMessage_ = $('other-sessions-promo-template').cloneNode(true);
61 this.promoMessage_.removeAttribute('id'); // Prevent a duplicate id.
63 this.sessions_ = [];
64 this.anchorType = cr.ui.AnchorType.ABOVE;
65 this.invertLeftRight = true;
67 // Initialize the images for the drop-down buttons that appear beside the
68 // session names.
69 MenuButton.createDropDownArrows();
71 recordUmaEvent_(HISTOGRAM_EVENT.INITIALIZED);
74 /**
75 * Initialize this element.
76 * @param {boolean} signedIn Is the current user signed in?
78 initialize: function(signedIn) {
79 this.updateSignInState(signedIn);
82 /**
83 * Handle a context menu event for an object in the menu's DOM subtree.
85 onContextMenu_: function(e) {
86 // Only record the action if it occurred in one of the menu items or
87 // on one of the session headings.
88 if (findAncestorByClass(e.target, 'footer-menu-item')) {
89 recordUmaEvent_(HISTOGRAM_EVENT.LINK_RIGHT_CLICKED);
90 } else {
91 var heading = findAncestorByClass(e.target, 'session-heading');
92 if (heading) {
93 recordUmaEvent_(HISTOGRAM_EVENT.SESSION_NAME_RIGHT_CLICKED);
95 // Let the context menu know which session it was invoked on,
96 // since they all share the same instance of the menu.
97 DeviceContextMenuController.getInstance().setSession(
98 heading.sessionData_);
104 * Hides the menu.
105 * @override
107 hideMenu: function() {
108 // Don't hide if the device context menu is currently showing.
109 if (this.deviceContextMenu_.hidden)
110 MenuButton.prototype.hideMenu.call(this);
114 * Shows the menu, first rebuilding it if necessary.
115 * TODO(estade): the right of the menu should align with the right of the
116 * button.
117 * @override
119 showMenu: function() {
120 if (this.sessions_.length == 0)
121 chrome.send('getForeignSessions');
122 recordUmaEvent_(HISTOGRAM_EVENT.SHOW_MENU);
123 MenuButton.prototype.showMenu.call(this);
125 // Work around https://bugs.webkit.org/show_bug.cgi?id=85884.
126 this.menu.scrollTop = 0;
130 * Reset the menu contents to the default state.
131 * @private
133 resetMenuContents_: function() {
134 this.menu.innerHTML = '';
135 this.menu.appendChild(this.promoMessage_);
139 * Create a custom click handler for a link, so that clicking on a link
140 * restores the session (including back stack) rather than just opening
141 * the URL.
143 makeClickHandler_: function(sessionTag, windowId, tabId) {
144 var self = this;
145 return function(e) {
146 recordUmaEvent_(HISTOGRAM_EVENT.LINK_CLICKED);
147 chrome.send('openForeignSession', [sessionTag, windowId, tabId,
148 e.button, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey]);
149 e.preventDefault();
154 * Add the UI for a foreign session to the menu.
155 * @param {Object} session Object describing the foreign session.
157 addSession_: function(session) {
158 var doc = this.ownerDocument;
160 var section = doc.createElement('section');
161 this.menu.appendChild(section);
163 var heading = doc.createElement('h3');
164 heading.className = 'session-heading';
165 heading.textContent = session.name;
166 heading.sessionData_ = session;
167 section.appendChild(heading);
169 var dropDownButton = new ContextMenuButton;
170 dropDownButton.classList.add('drop-down');
171 // Keep track of the drop down that triggered the menu, so we know
172 // which element to apply the command to.
173 function handleDropDownFocus(e) {
174 DeviceContextMenuController.getInstance().setSession(session);
176 dropDownButton.addEventListener('mousedown', handleDropDownFocus);
177 dropDownButton.addEventListener('focus', handleDropDownFocus);
178 heading.appendChild(dropDownButton);
180 var timeSpan = doc.createElement('span');
181 timeSpan.className = 'details';
182 timeSpan.textContent = session.modifiedTime;
183 heading.appendChild(timeSpan);
185 cr.ui.contextMenuHandler.setContextMenu(heading,
186 this.deviceContextMenu_);
188 if (!session.collapsed)
189 section.appendChild(this.createSessionContents_(session));
193 * Create the DOM tree representing the tabs and windows in a session.
194 * @param {Object} session The session model object.
195 * @return {Element} A single div containing the list of tabs & windows.
196 * @private
198 createSessionContents_: function(session) {
199 var doc = this.ownerDocument;
200 var contents = doc.createElement('div');
202 for (var i = 0; i < session.windows.length; i++) {
203 var window = session.windows[i];
205 // Show a separator between multiple windows in the same session.
206 if (i > 0)
207 contents.appendChild(doc.createElement('hr'));
209 for (var j = 0; j < window.tabs.length; j++) {
210 var tab = window.tabs[j];
211 var a = doc.createElement('a');
212 a.className = 'footer-menu-item';
213 a.textContent = tab.title;
214 a.href = tab.url;
215 a.style.backgroundImage =
216 getFaviconImageSet(tab.url, 16, /* session-favicon */ true);
218 var clickHandler = this.makeClickHandler_(
219 session.tag, String(window.sessionId), String(tab.sessionId));
220 a.addEventListener('click', clickHandler);
221 contents.appendChild(a);
225 return contents;
229 * Sets the menu model data. An empty list means that either there are no
230 * foreign sessions, or tab sync is disabled for this profile.
231 * |isTabSyncEnabled| makes it possible to distinguish between the cases.
233 * @param {Array} sessionList Array of objects describing the sessions
234 * from other devices.
235 * @param {boolean} isTabSyncEnabled Is tab sync enabled for this profile?
237 setForeignSessions: function(sessionList, isTabSyncEnabled) {
238 this.sessions_ = sessionList;
239 this.resetMenuContents_();
240 if (sessionList.length > 0) {
241 // Rebuild the menu with the new data.
242 for (var i = 0; i < sessionList.length; i++) {
243 this.addSession_(sessionList[i]);
247 // The menu button is shown iff tab sync is enabled.
248 this.hidden = !isTabSyncEnabled;
252 * Called when this element is initialized, and from the new tab page when
253 * the user's signed in state changes,
254 * @param {boolean} signedIn Is the user currently signed in?
256 updateSignInState: function(signedIn) {
257 if (signedIn)
258 chrome.send('getForeignSessions');
259 else
260 this.hidden = true;
265 * Controller for the context menu for device names in the list of sessions.
266 * This class is designed to be used as a singleton.
268 * @constructor
270 function DeviceContextMenuController() {
271 this.__proto__ = DeviceContextMenuController.prototype;
272 this.initialize();
274 cr.addSingletonGetter(DeviceContextMenuController);
276 DeviceContextMenuController.prototype = {
278 initialize: function() {
279 var menu = new cr.ui.Menu;
280 cr.ui.decorate(menu, cr.ui.Menu);
281 menu.classList.add('device-context-menu');
282 menu.classList.add('footer-menu-context-menu');
283 this.menu = menu;
284 this.collapseItem_ = this.appendMenuItem_('collapseSessionMenuItemText');
285 this.collapseItem_.addEventListener('activate',
286 this.onCollapseOrExpand_.bind(this));
287 this.expandItem_ = this.appendMenuItem_('expandSessionMenuItemText');
288 this.expandItem_.addEventListener('activate',
289 this.onCollapseOrExpand_.bind(this));
290 this.openAllItem_ = this.appendMenuItem_('restoreSessionMenuItemText');
291 this.openAllItem_.addEventListener('activate',
292 this.onOpenAll_.bind(this));
296 * Appends a menu item to |this.menu|.
297 * @param {string} textId The ID for the localized string that acts as
298 * the item's label.
300 appendMenuItem_: function(textId) {
301 var button = cr.doc.createElement('button');
302 this.menu.appendChild(button);
303 cr.ui.decorate(button, cr.ui.MenuItem);
304 button.textContent = loadTimeData.getString(textId);
305 return button;
309 * Handler for the 'Collapse' and 'Expand' menu items.
310 * @param {Event} e The activation event.
311 * @private
313 onCollapseOrExpand_: function(e) {
314 this.session_.collapsed = !this.session_.collapsed;
315 this.updateMenuItems_();
316 chrome.send('setForeignSessionCollapsed',
317 [this.session_.tag, this.session_.collapsed]);
318 chrome.send('getForeignSessions'); // Refresh the list.
320 var eventId = this.session_.collapsed ?
321 HISTOGRAM_EVENT.COLLAPSE_SESSION : HISTOGRAM_EVENT.EXPAND_SESSION;
322 recordUmaEvent_(eventId);
326 * Handler for the 'Open all' menu item.
327 * @param {Event} e The activation event.
328 * @private
330 onOpenAll_: function(e) {
331 chrome.send('openForeignSession', [this.session_.tag]);
332 recordUmaEvent_(HISTOGRAM_EVENT.OPEN_ALL);
336 * Set the session data for the session the context menu was invoked on.
337 * This should never be called when the menu is visible.
338 * @param {Object} session The model object for the session.
340 setSession: function(session) {
341 this.session_ = session;
342 this.updateMenuItems_();
346 * Set the visibility of the Expand/Collapse menu items based on the state
347 * of the session that this menu is currently associated with.
348 * @private
350 updateMenuItems_: function() {
351 this.collapseItem_.hidden = this.session_.collapsed;
352 this.expandItem_.hidden = !this.session_.collapsed;
356 return {
357 OtherSessionsMenuButton: OtherSessionsMenuButton,