| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013 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 section of the history page that shows tabs from sessions | |
| 7 on other devices. | |
| 8 */ | |
| 9 | |
| 10 /////////////////////////////////////////////////////////////////////////////// | |
| 11 // Globals: | |
| 12 /** @const */ var MAX_NUM_COLUMNS = 3; | |
| 13 /** @const */ var NB_ENTRIES_FIRST_ROW_COLUMN = 6; | |
| 14 /** @const */ var NB_ENTRIES_OTHER_ROWS_COLUMN = 0; | |
| 15 | |
| 16 // Histogram buckets for UMA tracking of menu usage. | |
| 17 /** @const */ var HISTOGRAM_EVENT = { | |
| 18 INITIALIZED: 0, | |
| 19 SHOW_MENU: 1, | |
| 20 LINK_CLICKED: 2, | |
| 21 LINK_RIGHT_CLICKED: 3, | |
| 22 SESSION_NAME_RIGHT_CLICKED: 4, | |
| 23 SHOW_SESSION_MENU: 5, | |
| 24 COLLAPSE_SESSION: 6, | |
| 25 EXPAND_SESSION: 7, | |
| 26 OPEN_ALL: 8, | |
| 27 HAS_FOREIGN_DATA: 9, | |
| 28 LIMIT: 10 // Should always be the last one. | |
| 29 }; | |
| 30 | |
| 31 /** | |
| 32 * Record an event in the UMA histogram. | |
| 33 * @param {number} eventId The id of the event to be recorded. | |
| 34 * @private | |
| 35 */ | |
| 36 function recordUmaEvent_(eventId) { | |
| 37 chrome.send('metricsHandler:recordInHistogram', | |
| 38 ['HistoryPage.OtherDevicesMenu', eventId, HISTOGRAM_EVENT.LIMIT]); | |
| 39 } | |
| 40 | |
| 41 /////////////////////////////////////////////////////////////////////////////// | |
| 42 // DeviceContextMenuController: | |
| 43 | |
| 44 /** | |
| 45 * Controller for the context menu for device names in the list of sessions. | |
| 46 * @constructor | |
| 47 */ | |
| 48 function DeviceContextMenuController() { | |
| 49 this.__proto__ = DeviceContextMenuController.prototype; | |
| 50 this.initialize(); | |
| 51 } | |
| 52 cr.addSingletonGetter(DeviceContextMenuController); | |
| 53 | |
| 54 // DeviceContextMenuController, Public: --------------------------------------- | |
| 55 | |
| 56 /** | |
| 57 * Initialize the context menu for device names in the list of sessions. | |
| 58 */ | |
| 59 DeviceContextMenuController.prototype.initialize = function() { | |
| 60 var menu = new cr.ui.Menu; | |
| 61 cr.ui.decorate(menu, cr.ui.Menu); | |
| 62 this.menu = menu; | |
| 63 this.collapseItem_ = this.appendMenuItem_('collapseSessionMenuItemText'); | |
| 64 this.collapseItem_.addEventListener('activate', | |
| 65 this.onCollapseOrExpand_.bind(this)); | |
| 66 this.expandItem_ = this.appendMenuItem_('expandSessionMenuItemText'); | |
| 67 this.expandItem_.addEventListener('activate', | |
| 68 this.onCollapseOrExpand_.bind(this)); | |
| 69 this.openAllItem_ = this.appendMenuItem_('restoreSessionMenuItemText'); | |
| 70 this.openAllItem_.addEventListener('activate', | |
| 71 this.onOpenAll_.bind(this)); | |
| 72 }; | |
| 73 | |
| 74 /** | |
| 75 * Set the session data for the session the context menu was invoked on. | |
| 76 * This should never be called when the menu is visible. | |
| 77 * @param {Object} session The model object for the session. | |
| 78 */ | |
| 79 DeviceContextMenuController.prototype.setSession = function(session) { | |
| 80 this.session_ = session; | |
| 81 this.updateMenuItems_(); | |
| 82 }; | |
| 83 | |
| 84 // DeviceContextMenuController, Private: -------------------------------------- | |
| 85 | |
| 86 /** | |
| 87 * Appends a menu item to |this.menu|. | |
| 88 * @param {string} textId The ID for the localized string that acts as | |
| 89 * the item's label. | |
| 90 * @return {Element} The button used for a given menu option. | |
| 91 * @private | |
| 92 */ | |
| 93 DeviceContextMenuController.prototype.appendMenuItem_ = function(textId) { | |
| 94 var button = document.createElement('button'); | |
| 95 this.menu.appendChild(button); | |
| 96 cr.ui.decorate(button, cr.ui.MenuItem); | |
| 97 button.textContent = loadTimeData.getString(textId); | |
| 98 return button; | |
| 99 }; | |
| 100 | |
| 101 /** | |
| 102 * Handler for the 'Collapse' and 'Expand' menu items. | |
| 103 * @param {Event} e The activation event. | |
| 104 * @private | |
| 105 */ | |
| 106 DeviceContextMenuController.prototype.onCollapseOrExpand_ = function(e) { | |
| 107 this.session_.collapsed = !this.session_.collapsed; | |
| 108 this.updateMenuItems_(); | |
| 109 chrome.send('setForeignSessionCollapsed', | |
| 110 [this.session_.tag, this.session_.collapsed]); | |
| 111 chrome.send('getForeignSessions'); // Refresh the list. | |
| 112 | |
| 113 var eventId = this.session_.collapsed ? | |
| 114 HISTOGRAM_EVENT.COLLAPSE_SESSION : HISTOGRAM_EVENT.EXPAND_SESSION; | |
| 115 recordUmaEvent_(eventId); | |
| 116 }; | |
| 117 | |
| 118 /** | |
| 119 * Handler for the 'Open all' menu item. | |
| 120 * @param {Event} e The activation event. | |
| 121 * @private | |
| 122 */ | |
| 123 DeviceContextMenuController.prototype.onOpenAll_ = function(e) { | |
| 124 chrome.send('openForeignSession', [this.session_.tag]); | |
| 125 recordUmaEvent_(HISTOGRAM_EVENT.OPEN_ALL); | |
| 126 }; | |
| 127 | |
| 128 /** | |
| 129 * Set the visibility of the Expand/Collapse menu items based on the state | |
| 130 * of the session that this menu is currently associated with. | |
| 131 * @private | |
| 132 */ | |
| 133 DeviceContextMenuController.prototype.updateMenuItems_ = function() { | |
| 134 this.collapseItem_.hidden = this.session_.collapsed; | |
| 135 this.expandItem_.hidden = !this.session_.collapsed; | |
| 136 this.menu.selectedItem = this.menu.querySelector(':not([hidden])'); | |
| 137 }; | |
| 138 | |
| 139 | |
| 140 /////////////////////////////////////////////////////////////////////////////// | |
| 141 // Device: | |
| 142 | |
| 143 /** | |
| 144 * Class to hold all the information about a device entry and generate a DOM | |
| 145 * node for it. | |
| 146 * @param {Object} session An object containing the device's session data. | |
| 147 * @param {DevicesView} view The view object this entry belongs to. | |
| 148 * @constructor | |
| 149 */ | |
| 150 function Device(session, view) { | |
| 151 this.view_ = view; | |
| 152 this.session_ = session; | |
| 153 this.searchText_ = view.getSearchText(); | |
| 154 } | |
| 155 | |
| 156 // Device, Public: ------------------------------------------------------------ | |
| 157 | |
| 158 /** | |
| 159 * Get the DOM node to display this device. | |
| 160 * @param {int} maxNumTabs The maximum number of tabs to display. | |
| 161 * @param {int} row The row in which this device is displayed. | |
| 162 * @return {Object} A DOM node to draw the device. | |
| 163 */ | |
| 164 Device.prototype.getDOMNode = function(maxNumTabs, row) { | |
| 165 var deviceDiv = createElementWithClassName('div', 'device'); | |
| 166 this.row_ = row; | |
| 167 if (!this.session_) | |
| 168 return deviceDiv; | |
| 169 | |
| 170 // Name heading | |
| 171 var heading = document.createElement('h3'); | |
| 172 var name = heading.appendChild( | |
| 173 createElementWithClassName('span', 'device-name')); | |
| 174 name.textContent = this.session_.name; | |
| 175 heading.sessionData_ = this.session_; | |
| 176 deviceDiv.appendChild(heading); | |
| 177 | |
| 178 // Keep track of the drop down that triggered the menu, so we know | |
| 179 // which element to apply the command to. | |
| 180 var session = this.session_; | |
| 181 function handleDropDownFocus(e) { | |
| 182 DeviceContextMenuController.getInstance().setSession(session); | |
| 183 } | |
| 184 heading.addEventListener('contextmenu', handleDropDownFocus); | |
| 185 | |
| 186 var dropDownButton = new cr.ui.ContextMenuButton; | |
| 187 dropDownButton.tabIndex = 0; | |
| 188 dropDownButton.classList.add('drop-down'); | |
| 189 dropDownButton.title = loadTimeData.getString('actionMenuDescription'); | |
| 190 dropDownButton.addEventListener('mousedown', function(event) { | |
| 191 handleDropDownFocus(event); | |
| 192 // Mousedown handling of cr.ui.MenuButton.handleEvent calls | |
| 193 // preventDefault, which prevents blur of the focused element. We need to | |
| 194 // do blur manually. | |
| 195 document.activeElement.blur(); | |
| 196 }); | |
| 197 dropDownButton.addEventListener('focus', handleDropDownFocus); | |
| 198 heading.appendChild(dropDownButton); | |
| 199 | |
| 200 var timeSpan = createElementWithClassName('div', 'device-timestamp'); | |
| 201 timeSpan.textContent = this.session_.modifiedTime; | |
| 202 deviceDiv.appendChild(timeSpan); | |
| 203 | |
| 204 cr.ui.contextMenuHandler.setContextMenu( | |
| 205 heading, DeviceContextMenuController.getInstance().menu); | |
| 206 if (!this.session_.collapsed) | |
| 207 deviceDiv.appendChild(this.createSessionContents_(maxNumTabs)); | |
| 208 | |
| 209 return deviceDiv; | |
| 210 }; | |
| 211 | |
| 212 /** | |
| 213 * Marks tabs as hidden or not in our session based on the given searchText. | |
| 214 * @param {string} searchText The search text used to filter the content. | |
| 215 */ | |
| 216 Device.prototype.setSearchText = function(searchText) { | |
| 217 this.searchText_ = searchText.toLowerCase(); | |
| 218 for (var i = 0; i < this.session_.windows.length; i++) { | |
| 219 var win = this.session_.windows[i]; | |
| 220 var foundMatch = false; | |
| 221 for (var j = 0; j < win.tabs.length; j++) { | |
| 222 var tab = win.tabs[j]; | |
| 223 if (tab.title.toLowerCase().indexOf(this.searchText_) != -1) { | |
| 224 foundMatch = true; | |
| 225 tab.hidden = false; | |
| 226 } else { | |
| 227 tab.hidden = true; | |
| 228 } | |
| 229 } | |
| 230 win.hidden = !foundMatch; | |
| 231 } | |
| 232 }; | |
| 233 | |
| 234 // Device, Private ------------------------------------------------------------ | |
| 235 | |
| 236 /** | |
| 237 * Create the DOM tree representing the tabs and windows of this device. | |
| 238 * @param {int} maxNumTabs The maximum number of tabs to display. | |
| 239 * @return {Element} A single div containing the list of tabs & windows. | |
| 240 * @private | |
| 241 */ | |
| 242 Device.prototype.createSessionContents_ = function(maxNumTabs) { | |
| 243 var contents = createElementWithClassName('ol', 'device-contents'); | |
| 244 | |
| 245 var sessionTag = this.session_.tag; | |
| 246 var numTabsShown = 0; | |
| 247 var numTabsHidden = 0; | |
| 248 for (var i = 0; i < this.session_.windows.length; i++) { | |
| 249 var win = this.session_.windows[i]; | |
| 250 if (win.hidden) | |
| 251 continue; | |
| 252 | |
| 253 // Show a separator between multiple windows in the same session. | |
| 254 if (i > 0 && numTabsShown < maxNumTabs) | |
| 255 contents.appendChild(document.createElement('hr')); | |
| 256 | |
| 257 for (var j = 0; j < win.tabs.length; j++) { | |
| 258 var tab = win.tabs[j]; | |
| 259 if (tab.hidden) | |
| 260 continue; | |
| 261 | |
| 262 if (numTabsShown < maxNumTabs) { | |
| 263 numTabsShown++; | |
| 264 var a = createElementWithClassName('a', 'device-tab-entry'); | |
| 265 a.href = tab.url; | |
| 266 a.style.backgroundImage = cr.icon.getFavicon(tab.url); | |
| 267 this.addHighlightedText_(a, tab.title); | |
| 268 // Add a tooltip, since it might be ellipsized. The ones that are not | |
| 269 // necessary will be removed once added to the document, so we can | |
| 270 // compute sizes. | |
| 271 a.title = tab.title; | |
| 272 | |
| 273 // We need to use this to not lose the ids as we go through other loop | |
| 274 // turns. | |
| 275 function makeClickHandler(sessionTag, windowId, tabId) { | |
| 276 return function(e) { | |
| 277 recordUmaEvent_(HISTOGRAM_EVENT.LINK_CLICKED); | |
| 278 chrome.send('openForeignSession', [sessionTag, windowId, tabId, | |
| 279 e.button, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey]); | |
| 280 e.preventDefault(); | |
| 281 }; | |
| 282 }; | |
| 283 a.addEventListener('click', makeClickHandler(sessionTag, | |
| 284 String(win.sessionId), | |
| 285 String(tab.sessionId))); | |
| 286 var wrapper = createElementWithClassName('div', 'device-tab-wrapper'); | |
| 287 wrapper.appendChild(a); | |
| 288 contents.appendChild(wrapper); | |
| 289 } else { | |
| 290 numTabsHidden++; | |
| 291 } | |
| 292 } | |
| 293 } | |
| 294 | |
| 295 if (numTabsHidden > 0) { | |
| 296 var moreLink = document.createElement('a', 'action-link'); | |
| 297 moreLink.classList.add('device-show-more-tabs'); | |
| 298 moreLink.addEventListener('click', this.view_.increaseRowHeight.bind( | |
| 299 this.view_, this.row_, numTabsHidden)); | |
| 300 // TODO(jshin): Use plural message formatter when available in JS. | |
| 301 moreLink.textContent = loadTimeData.getStringF('xMore', | |
| 302 numTabsHidden.toLocaleString()); | |
| 303 var moreWrapper = createElementWithClassName('div', 'more-wrapper'); | |
| 304 moreWrapper.appendChild(moreLink); | |
| 305 contents.appendChild(moreWrapper); | |
| 306 } | |
| 307 | |
| 308 return contents; | |
| 309 }; | |
| 310 | |
| 311 /** | |
| 312 * Add child text nodes to a node such that occurrences of this.searchText_ are | |
| 313 * highlighted. | |
| 314 * @param {Node} node The node under which new text nodes will be made as | |
| 315 * children. | |
| 316 * @param {string} content Text to be added beneath |node| as one or more | |
| 317 * text nodes. | |
| 318 * @private | |
| 319 */ | |
| 320 Device.prototype.addHighlightedText_ = function(node, content) { | |
| 321 var endOfPreviousMatch = 0; | |
| 322 if (this.searchText_) { | |
| 323 var lowerContent = content.toLowerCase(); | |
| 324 var searchTextLenght = this.searchText_.length; | |
| 325 var newMatch = lowerContent.indexOf(this.searchText_, 0); | |
| 326 while (newMatch != -1) { | |
| 327 if (newMatch > endOfPreviousMatch) { | |
| 328 node.appendChild(document.createTextNode( | |
| 329 content.slice(endOfPreviousMatch, newMatch))); | |
| 330 } | |
| 331 endOfPreviousMatch = newMatch + searchTextLenght; | |
| 332 // Mark the highlighted text in bold. | |
| 333 var b = document.createElement('b'); | |
| 334 b.textContent = content.substring(newMatch, endOfPreviousMatch); | |
| 335 node.appendChild(b); | |
| 336 newMatch = lowerContent.indexOf(this.searchText_, endOfPreviousMatch); | |
| 337 } | |
| 338 } | |
| 339 if (endOfPreviousMatch < content.length) { | |
| 340 node.appendChild(document.createTextNode( | |
| 341 content.slice(endOfPreviousMatch))); | |
| 342 } | |
| 343 }; | |
| 344 | |
| 345 /////////////////////////////////////////////////////////////////////////////// | |
| 346 // DevicesView: | |
| 347 | |
| 348 /** | |
| 349 * Functions and state for populating the page with HTML. | |
| 350 * @constructor | |
| 351 */ | |
| 352 function DevicesView() { | |
| 353 this.devices_ = []; // List of individual devices. | |
| 354 this.resultDiv_ = $('other-devices'); | |
| 355 this.searchText_ = ''; | |
| 356 this.rowHeights_ = [NB_ENTRIES_FIRST_ROW_COLUMN]; | |
| 357 this.focusGrids_ = []; | |
| 358 this.updateSignInState(loadTimeData.getBoolean('isUserSignedIn')); | |
| 359 this.hasSeenForeignData_ = false; | |
| 360 recordUmaEvent_(HISTOGRAM_EVENT.INITIALIZED); | |
| 361 } | |
| 362 | |
| 363 // DevicesView, public: ------------------------------------------------------- | |
| 364 | |
| 365 /** | |
| 366 * Updates our sign in state by clearing the view is not signed in or sending | |
| 367 * a request to get the data to display otherwise. | |
| 368 * @param {boolean} signedIn Whether the user is signed in or not. | |
| 369 */ | |
| 370 DevicesView.prototype.updateSignInState = function(signedIn) { | |
| 371 if (signedIn) | |
| 372 chrome.send('getForeignSessions'); | |
| 373 else | |
| 374 this.clearDOM(); | |
| 375 }; | |
| 376 | |
| 377 /** | |
| 378 * Resets the view sessions. | |
| 379 * @param {Object} sessionList The sessions to add. | |
| 380 */ | |
| 381 DevicesView.prototype.setSessionList = function(sessionList) { | |
| 382 this.devices_ = []; | |
| 383 for (var i = 0; i < sessionList.length; i++) | |
| 384 this.devices_.push(new Device(sessionList[i], this)); | |
| 385 this.displayResults_(); | |
| 386 | |
| 387 // This metric should only be emitted if we see foreign data, and it should | |
| 388 // only be emitted once per page refresh. Flip flag to remember because this | |
| 389 // method is called upon any update. | |
| 390 if (!this.hasSeenForeignData_ && sessionList.length > 0) { | |
| 391 this.hasSeenForeignData_ = true; | |
| 392 recordUmaEvent_(HISTOGRAM_EVENT.HAS_FOREIGN_DATA); | |
| 393 } | |
| 394 }; | |
| 395 | |
| 396 | |
| 397 /** | |
| 398 * Sets the current search text. | |
| 399 * @param {string} searchText The text to search. | |
| 400 */ | |
| 401 DevicesView.prototype.setSearchText = function(searchText) { | |
| 402 if (this.searchText_ != searchText) { | |
| 403 this.searchText_ = searchText; | |
| 404 for (var i = 0; i < this.devices_.length; i++) | |
| 405 this.devices_[i].setSearchText(searchText); | |
| 406 this.displayResults_(); | |
| 407 } | |
| 408 }; | |
| 409 | |
| 410 /** | |
| 411 * @return {string} The current search text. | |
| 412 */ | |
| 413 DevicesView.prototype.getSearchText = function() { | |
| 414 return this.searchText_; | |
| 415 }; | |
| 416 | |
| 417 /** | |
| 418 * Clears the DOM content of the view. | |
| 419 */ | |
| 420 DevicesView.prototype.clearDOM = function() { | |
| 421 while (this.resultDiv_.hasChildNodes()) { | |
| 422 this.resultDiv_.removeChild(this.resultDiv_.lastChild); | |
| 423 } | |
| 424 }; | |
| 425 | |
| 426 /** | |
| 427 * Increase the height of a row by the given amount. | |
| 428 * @param {int} row The row number. | |
| 429 * @param {int} height The extra height to add to the givent row. | |
| 430 */ | |
| 431 DevicesView.prototype.increaseRowHeight = function(row, height) { | |
| 432 for (var i = this.rowHeights_.length; i <= row; i++) | |
| 433 this.rowHeights_.push(NB_ENTRIES_OTHER_ROWS_COLUMN); | |
| 434 this.rowHeights_[row] += height; | |
| 435 this.displayResults_(); | |
| 436 }; | |
| 437 | |
| 438 // DevicesView, Private ------------------------------------------------------- | |
| 439 | |
| 440 /** | |
| 441 * @param {!Element} root | |
| 442 * @param {?Node} boundary | |
| 443 * @constructor | |
| 444 * @extends {cr.ui.FocusRow} | |
| 445 */ | |
| 446 function DevicesViewFocusRow(root, boundary) { | |
| 447 cr.ui.FocusRow.call(this, root, boundary); | |
| 448 assert(this.addItem('menu-button', 'button.drop-down') || | |
| 449 this.addItem('device-tab', '.device-tab-entry') || | |
| 450 this.addItem('more-tabs', '.device-show-more-tabs')); | |
| 451 } | |
| 452 | |
| 453 DevicesViewFocusRow.prototype = {__proto__: cr.ui.FocusRow.prototype}; | |
| 454 | |
| 455 /** | |
| 456 * Update the page with results. | |
| 457 * @private | |
| 458 */ | |
| 459 DevicesView.prototype.displayResults_ = function() { | |
| 460 this.clearDOM(); | |
| 461 var resultsFragment = document.createDocumentFragment(); | |
| 462 if (this.devices_.length == 0) | |
| 463 return; | |
| 464 | |
| 465 // We'll increase to 0 as we create the first row. | |
| 466 var rowIndex = -1; | |
| 467 // We need to access the last row and device when we get out of the loop. | |
| 468 var currentRowElement; | |
| 469 // This is only set when changing rows, yet used on all device columns. | |
| 470 var maxNumTabs; | |
| 471 for (var i = 0; i < this.devices_.length; i++) { | |
| 472 var device = this.devices_[i]; | |
| 473 // Should we start a new row? | |
| 474 if (i % MAX_NUM_COLUMNS == 0) { | |
| 475 if (currentRowElement) | |
| 476 resultsFragment.appendChild(currentRowElement); | |
| 477 currentRowElement = createElementWithClassName('div', 'device-row'); | |
| 478 rowIndex++; | |
| 479 if (rowIndex < this.rowHeights_.length) | |
| 480 maxNumTabs = this.rowHeights_[rowIndex]; | |
| 481 else | |
| 482 maxNumTabs = 0; | |
| 483 } | |
| 484 | |
| 485 currentRowElement.appendChild(device.getDOMNode(maxNumTabs, rowIndex)); | |
| 486 } | |
| 487 if (currentRowElement) | |
| 488 resultsFragment.appendChild(currentRowElement); | |
| 489 | |
| 490 this.resultDiv_.appendChild(resultsFragment); | |
| 491 // Remove the tootltip on all lines that don't need it. It's easier to | |
| 492 // remove them here, after adding them all above, since we have the data | |
| 493 // handy above, but we don't have the width yet. Whereas here, we have the | |
| 494 // width, and the nodeValue could contain sub nodes for highlighting, which | |
| 495 // makes it harder to extract the text data here. | |
| 496 tabs = document.getElementsByClassName('device-tab-entry'); | |
| 497 for (var i = 0; i < tabs.length; i++) { | |
| 498 if (tabs[i].scrollWidth <= tabs[i].clientWidth) | |
| 499 tabs[i].title = ''; | |
| 500 } | |
| 501 | |
| 502 this.resultDiv_.appendChild( | |
| 503 createElementWithClassName('div', 'other-devices-bottom')); | |
| 504 | |
| 505 this.focusGrids_.forEach(function(grid) { grid.destroy(); }); | |
| 506 this.focusGrids_.length = 0; | |
| 507 | |
| 508 var devices = this.resultDiv_.querySelectorAll('.device-contents'); | |
| 509 for (var i = 0; i < devices.length; ++i) { | |
| 510 var rows = devices[i].querySelectorAll( | |
| 511 'h3, .device-tab-wrapper, .more-wrapper'); | |
| 512 if (!rows.length) | |
| 513 continue; | |
| 514 | |
| 515 var grid = new cr.ui.FocusGrid(); | |
| 516 for (var j = 0; j < rows.length; ++j) { | |
| 517 grid.addRow(new DevicesViewFocusRow(rows[j], devices[i])); | |
| 518 } | |
| 519 grid.ensureRowActive(); | |
| 520 this.focusGrids_.push(grid); | |
| 521 } | |
| 522 }; | |
| 523 | |
| 524 /** | |
| 525 * Sets the menu model data. An empty list means that either there are no | |
| 526 * foreign sessions, or tab sync is disabled for this profile. | |
| 527 * |isTabSyncEnabled| makes it possible to distinguish between the cases. | |
| 528 * | |
| 529 * @param {Array} sessionList Array of objects describing the sessions | |
| 530 * from other devices. | |
| 531 * @param {boolean} isTabSyncEnabled Is tab sync enabled for this profile? | |
| 532 */ | |
| 533 function setForeignSessions(sessionList, isTabSyncEnabled) { | |
| 534 // The other devices is shown iff tab sync is enabled. | |
| 535 if (isTabSyncEnabled) | |
| 536 devicesView.setSessionList(sessionList); | |
| 537 else | |
| 538 devicesView.clearDOM(); | |
| 539 } | |
| 540 | |
| 541 /** | |
| 542 * Called when initialized or the user's signed in state changes, | |
| 543 * @param {boolean} isUserSignedIn Is the user currently signed in? | |
| 544 */ | |
| 545 function updateSignInState(isUserSignedIn) { | |
| 546 if (devicesView) | |
| 547 devicesView.updateSignInState(isUserSignedIn); | |
| 548 } | |
| 549 | |
| 550 /////////////////////////////////////////////////////////////////////////////// | |
| 551 // Document Functions: | |
| 552 /** | |
| 553 * Window onload handler, sets up the other devices view. | |
| 554 */ | |
| 555 function load() { | |
| 556 if (!loadTimeData.getBoolean('isInstantExtendedApiEnabled')) | |
| 557 return; | |
| 558 | |
| 559 devicesView = new DevicesView(); | |
| 560 | |
| 561 // Create the context menu that appears when the user right clicks | |
| 562 // on a device name or hit click on the button besides the device name | |
| 563 document.body.appendChild(DeviceContextMenuController.getInstance().menu); | |
| 564 | |
| 565 var doSearch = function(e) { | |
| 566 devicesView.setSearchText($('search-field').value); | |
| 567 }; | |
| 568 $('search-field').addEventListener('search', doSearch); | |
| 569 $('search-button').addEventListener('click', doSearch); | |
| 570 | |
| 571 chrome.send('otherDevicesInitialized'); | |
| 572 } | |
| 573 | |
| 574 // Add handlers to HTML elements. | |
| 575 document.addEventListener('DOMContentLoaded', load); | |
| OLD | NEW |