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