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

Side by Side Diff: chrome/browser/resources/ntp4/other_sessions.js

Issue 1140813003: NTP Zombie Code Slayer III, The Saga Continues: Foreign Sessions (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@suggestions-page
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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.
4
5 /**
6 * @fileoverview The menu that shows tabs from sessions on other devices.
7 */
8
9 /**
10 * @typedef {{collapsed: boolean,
11 * deviceType: string,
12 * modifiedTime: string,
13 * name: string,
14 * tag: string,
15 * windows: Array<WindowData>}}
16 * @see chrome/browser/ui/webui/ntp/foreign_session_handler.cc
17 */
18 var SessionData;
19
20 /**
21 * @typedef {{sessionId: number,
22 * tabs: Array,
23 * timestamp: number,
24 * type: string,
25 * userVisibleTimestamp: string}}
26 * @see chrome/browser/ui/webui/ntp/foreign_session_handler.cc
27 */
28 var WindowData;
29
30 cr.define('ntp', function() {
31 'use strict';
32
33 /** @const */ var ContextMenuButton = cr.ui.ContextMenuButton;
34 /** @const */ var Menu = cr.ui.Menu;
35 /** @const */ var MenuItem = cr.ui.MenuItem;
36 /** @const */ var MenuButton = cr.ui.MenuButton;
37
38 /**
39 * @constructor
40 * @extends {cr.ui.MenuButton}
41 */
42 var OtherSessionsMenuButton = cr.ui.define('button');
43
44 // Histogram buckets for UMA tracking of menu usage.
45 /** @const */ var HISTOGRAM_EVENT = {
46 INITIALIZED: 0,
47 SHOW_MENU: 1,
48 LINK_CLICKED: 2,
49 LINK_RIGHT_CLICKED: 3,
50 SESSION_NAME_RIGHT_CLICKED: 4,
51 SHOW_SESSION_MENU: 5,
52 COLLAPSE_SESSION: 6,
53 EXPAND_SESSION: 7,
54 OPEN_ALL: 8
55 };
56 /** @const */ var HISTOGRAM_EVENT_LIMIT =
57 HISTOGRAM_EVENT.OPEN_ALL + 1;
58
59 /**
60 * Record an event in the UMA histogram.
61 * @param {number} eventId The id of the event to be recorded.
62 * @private
63 */
64 function recordUmaEvent_(eventId) {
65 chrome.send('metricsHandler:recordInHistogram',
66 ['NewTabPage.OtherSessionsMenu', eventId, HISTOGRAM_EVENT_LIMIT]);
67 }
68
69 OtherSessionsMenuButton.prototype = {
70 __proto__: MenuButton.prototype,
71
72 decorate: function() {
73 MenuButton.prototype.decorate.call(this);
74 this.menu = new Menu;
75 this.menu.menuItemSelector = '[role=menuitem]'; // before decoration
76 cr.ui.decorate(this.menu, Menu);
77 this.menu.classList.add('footer-menu');
78 this.menu.addEventListener('contextmenu',
79 this.onContextMenu_.bind(this), true);
80 document.body.appendChild(this.menu);
81
82 // Create the context menu that appears when the user right clicks
83 // on a device name.
84 this.deviceContextMenu_ = DeviceContextMenuController.getInstance().menu;
85 document.body.appendChild(this.deviceContextMenu_);
86
87 this.promoMessage_ = $('other-sessions-promo-template').cloneNode(true);
88 this.promoMessage_.removeAttribute('id'); // Prevent a duplicate id.
89
90 this.sessions_ = [];
91 this.anchorType = cr.ui.AnchorType.ABOVE;
92 this.invertLeftRight = true;
93
94 // Initialize the images for the drop-down buttons that appear beside the
95 // session names.
96 MenuButton.createDropDownArrows();
97
98 recordUmaEvent_(HISTOGRAM_EVENT.INITIALIZED);
99 },
100
101 /**
102 * Initialize this element.
103 * @param {boolean} signedIn Is the current user signed in?
104 */
105 initialize: function(signedIn) {
106 this.updateSignInState(signedIn);
107 },
108
109 /**
110 * Handle a context menu event for an object in the menu's DOM subtree.
111 */
112 onContextMenu_: function(e) {
113 // Only record the action if it occurred in one of the menu items or
114 // on one of the session headings.
115 if (findAncestorByClass(e.target, 'footer-menu-item')) {
116 recordUmaEvent_(HISTOGRAM_EVENT.LINK_RIGHT_CLICKED);
117 } else {
118 var heading = findAncestorByClass(e.target, 'session-heading');
119 if (heading) {
120 recordUmaEvent_(HISTOGRAM_EVENT.SESSION_NAME_RIGHT_CLICKED);
121
122 // Let the context menu know which session it was invoked on,
123 // since they all share the same instance of the menu.
124 DeviceContextMenuController.getInstance().setSession(
125 heading.sessionData_);
126 }
127 }
128 },
129
130 /**
131 * Hides the menu.
132 * @override
133 */
134 hideMenu: function() {
135 // Don't hide if the device context menu is currently showing.
136 if (this.deviceContextMenu_.hidden)
137 MenuButton.prototype.hideMenu.call(this);
138 },
139
140 /**
141 * Shows the menu, first rebuilding it if necessary.
142 * TODO(estade): the right of the menu should align with the right of the
143 * button.
144 * @override
145 */
146 showMenu: function(shouldSetFocus) {
147 if (this.sessions_.length == 0)
148 chrome.send('getForeignSessions');
149 recordUmaEvent_(HISTOGRAM_EVENT.SHOW_MENU);
150 MenuButton.prototype.showMenu.apply(this, arguments);
151
152 // Work around https://bugs.webkit.org/show_bug.cgi?id=85884.
153 this.menu.scrollTop = 0;
154 },
155
156 /**
157 * Reset the menu contents to the default state.
158 * @private
159 */
160 resetMenuContents_: function() {
161 this.menu.innerHTML = '';
162 this.menu.appendChild(this.promoMessage_);
163 },
164
165 /**
166 * Create a custom click handler for a link, so that clicking on a link
167 * restores the session (including back stack) rather than just opening
168 * the URL.
169 */
170 makeClickHandler_: function(sessionTag, windowId, tabId) {
171 var self = this;
172 return function(e) {
173 recordUmaEvent_(HISTOGRAM_EVENT.LINK_CLICKED);
174 chrome.send('openForeignSession', [sessionTag, windowId, tabId,
175 e.button, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey]);
176 e.preventDefault();
177 };
178 },
179
180 /**
181 * Add the UI for a foreign session to the menu.
182 * @param {SessionData} session Object describing the foreign session.
183 */
184 addSession_: function(session) {
185 var doc = this.ownerDocument;
186
187 var section = doc.createElement('section');
188 this.menu.appendChild(section);
189
190 var heading = doc.createElement('h3');
191 heading.className = 'session-heading';
192 heading.textContent = session.name;
193 heading.sessionData_ = session;
194 section.appendChild(heading);
195
196 var dropDownButton = new ContextMenuButton;
197 dropDownButton.classList.add('drop-down');
198 // Keep track of the drop down that triggered the menu, so we know
199 // which element to apply the command to.
200 function handleDropDownFocus(e) {
201 DeviceContextMenuController.getInstance().setSession(session);
202 }
203 dropDownButton.addEventListener('mousedown', handleDropDownFocus);
204 dropDownButton.addEventListener('focus', handleDropDownFocus);
205 heading.appendChild(dropDownButton);
206
207 var timeSpan = doc.createElement('span');
208 timeSpan.className = 'details';
209 timeSpan.textContent = session.modifiedTime;
210 heading.appendChild(timeSpan);
211
212 cr.ui.contextMenuHandler.setContextMenu(heading,
213 this.deviceContextMenu_);
214
215 if (!session.collapsed)
216 section.appendChild(this.createSessionContents_(session));
217 },
218
219 /**
220 * Create the DOM tree representing the tabs and windows in a session.
221 * @param {SessionData} session The session model object.
222 * @return {Element} A single div containing the list of tabs & windows.
223 * @private
224 */
225 createSessionContents_: function(session) {
226 var doc = this.ownerDocument;
227 var contents = doc.createElement('div');
228
229 for (var i = 0; i < session.windows.length; i++) {
230 var window = session.windows[i];
231
232 // Show a separator between multiple windows in the same session.
233 if (i > 0)
234 contents.appendChild(doc.createElement('hr'));
235
236 for (var j = 0; j < window.tabs.length; j++) {
237 var tab = window.tabs[j];
238 var a = doc.createElement('a');
239 a.className = 'footer-menu-item';
240 a.textContent = tab.title;
241 a.href = tab.url;
242 a.style.backgroundImage = getFaviconImageSet(tab.url);
243
244 var clickHandler = this.makeClickHandler_(
245 session.tag, String(window.sessionId), String(tab.sessionId));
246 a.addEventListener('click', clickHandler);
247 contents.appendChild(a);
248 cr.ui.decorate(a, MenuItem);
249 }
250 }
251
252 return contents;
253 },
254
255 /**
256 * Sets the menu model data. An empty list means that either there are no
257 * foreign sessions, or tab sync is disabled for this profile.
258 * |isTabSyncEnabled| makes it possible to distinguish between the cases.
259 *
260 * @param {Array<SessionData>} sessionList Array of objects describing the
261 * sessions from other devices.
262 * @param {boolean} isTabSyncEnabled Is tab sync enabled for this profile?
263 */
264 setForeignSessions: function(sessionList, isTabSyncEnabled) {
265 this.sessions_ = sessionList;
266 this.resetMenuContents_();
267 if (sessionList.length > 0) {
268 // Rebuild the menu with the new data.
269 for (var i = 0; i < sessionList.length; i++) {
270 this.addSession_(sessionList[i]);
271 }
272 }
273
274 // The menu button is shown iff tab sync is enabled.
275 this.hidden = !isTabSyncEnabled;
276 },
277
278 /**
279 * Called when this element is initialized, and from the new tab page when
280 * the user's signed in state changes,
281 * @param {boolean} signedIn Is the user currently signed in?
282 */
283 updateSignInState: function(signedIn) {
284 if (signedIn)
285 chrome.send('getForeignSessions');
286 else
287 this.hidden = true;
288 },
289 };
290
291 /**
292 * Controller for the context menu for device names in the list of sessions.
293 * This class is designed to be used as a singleton.
294 *
295 * @constructor
296 */
297 function DeviceContextMenuController() {
298 this.__proto__ = DeviceContextMenuController.prototype;
299 this.initialize();
300 }
301 cr.addSingletonGetter(DeviceContextMenuController);
302
303 DeviceContextMenuController.prototype = {
304
305 initialize: function() {
306 var menu = new cr.ui.Menu;
307 cr.ui.decorate(menu, cr.ui.Menu);
308 menu.classList.add('device-context-menu');
309 menu.classList.add('footer-menu-context-menu');
310 this.menu = menu;
311 this.collapseItem_ = this.appendMenuItem_('collapseSessionMenuItemText');
312 this.collapseItem_.addEventListener('activate',
313 this.onCollapseOrExpand_.bind(this));
314 this.expandItem_ = this.appendMenuItem_('expandSessionMenuItemText');
315 this.expandItem_.addEventListener('activate',
316 this.onCollapseOrExpand_.bind(this));
317 this.openAllItem_ = this.appendMenuItem_('restoreSessionMenuItemText');
318 this.openAllItem_.addEventListener('activate',
319 this.onOpenAll_.bind(this));
320 },
321
322 /**
323 * Appends a menu item to |this.menu|.
324 * @param {string} textId The ID for the localized string that acts as
325 * the item's label.
326 */
327 appendMenuItem_: function(textId) {
328 var button = cr.doc.createElement('button');
329 this.menu.appendChild(button);
330 cr.ui.decorate(button, cr.ui.MenuItem);
331 button.textContent = loadTimeData.getString(textId);
332 return button;
333 },
334
335 /**
336 * Handler for the 'Collapse' and 'Expand' menu items.
337 * @param {Event} e The activation event.
338 * @private
339 */
340 onCollapseOrExpand_: function(e) {
341 this.session_.collapsed = !this.session_.collapsed;
342 this.updateMenuItems_();
343 chrome.send('setForeignSessionCollapsed',
344 [this.session_.tag, this.session_.collapsed]);
345 chrome.send('getForeignSessions'); // Refresh the list.
346
347 var eventId = this.session_.collapsed ?
348 HISTOGRAM_EVENT.COLLAPSE_SESSION : HISTOGRAM_EVENT.EXPAND_SESSION;
349 recordUmaEvent_(eventId);
350 },
351
352 /**
353 * Handler for the 'Open all' menu item.
354 * @param {Event} e The activation event.
355 * @private
356 */
357 onOpenAll_: function(e) {
358 chrome.send('openForeignSession', [this.session_.tag]);
359 recordUmaEvent_(HISTOGRAM_EVENT.OPEN_ALL);
360 },
361
362 /**
363 * Set the session data for the session the context menu was invoked on.
364 * This should never be called when the menu is visible.
365 * @param {Object} session The model object for the session.
366 */
367 setSession: function(session) {
368 this.session_ = session;
369 this.updateMenuItems_();
370 },
371
372 /**
373 * Set the visibility of the Expand/Collapse menu items based on the state
374 * of the session that this menu is currently associated with.
375 * @private
376 */
377 updateMenuItems_: function() {
378 this.collapseItem_.hidden = this.session_.collapsed;
379 this.expandItem_.hidden = !this.session_.collapsed;
380 }
381 };
382
383 return {
384 OtherSessionsMenuButton: OtherSessionsMenuButton,
385 };
386 });
OLDNEW
« no previous file with comments | « chrome/browser/resources/ntp4/new_tab.js ('k') | chrome/browser/ui/webui/foreign_session_handler.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698