summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authormad@chromium.org <mad@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-15 23:44:53 +0000
committermad@chromium.org <mad@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-15 23:44:53 +0000
commita04e82bfb87e06462b018ab05053319e41c1db00 (patch)
tree9db9f906ce8d995483487466fd174680848abdac /chrome
parent70fe277044e7768cfe91375749b33cb94ae00981 (diff)
downloadchromium_src-a04e82bfb87e06462b018ab05053319e41c1db00.zip
chromium_src-a04e82bfb87e06462b018ab05053319e41c1db00.tar.gz
chromium_src-a04e82bfb87e06462b018ab05053319e41c1db00.tar.bz2
Implement separate area of the history page for full list of other devices.
BUG=154655 TEST=Open the history page and play with it... Review URL: https://chromiumcodereview.appspot.com/12390024 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@188507 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/app/generated_resources.grd4
-rw-r--r--chrome/browser/browser_resources.grd1
-rw-r--r--chrome/browser/resources/history/history.html13
-rw-r--r--chrome/browser/resources/history/other_devices.css84
-rw-r--r--chrome/browser/resources/history/other_devices.js525
-rw-r--r--chrome/browser/resources/uber/uber_shared.css4
-rw-r--r--chrome/browser/ui/webui/history_ui.cc41
-rw-r--r--chrome/browser/ui/webui/history_ui.h3
8 files changed, 657 insertions, 18 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 3483a18..698e615 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -11279,6 +11279,10 @@ experiment id: "<ph name="EXPERIMENT_ID">$5<ex>ar1</ex></ph>"
desc="In the 'Other Sessions' menu on the New Tab Page, the label for the command to open all tabs and windows from a session.">
Open all
</message>
+ <message name="IDS_OTHER_DEVICES_X_MORE"
+ desc="In the 'Other Sessions' section of the history page, the label for showing that X more tabs are available for a session.">
+ <ph name="NUM_TABS_MORE">$1<ex>42</ex></ph> more...
+ </message>
<message name="IDS_NEW_TAB_OTHER_SESSIONS_COLLAPSE_SESSION"
desc="In the 'Other Sessions' menu on the New Tab Page, the label for the command to collapse (hide) the list of windows and tabs in a session.">
Collapse list
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index efcdf9f..3e4c478 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -122,6 +122,7 @@
<include name="IDR_HELP_JS" file="resources\help\help.js" flattenhtml="true" type="BINDATA" />
<include name="IDR_HISTORY_HTML" file="resources\history\history.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
<include name="IDR_HISTORY_JS" file="resources\history\history.js" flattenhtml="true" type="BINDATA" />
+ <include name="IDR_OTHER_DEVICES_JS" file="resources\history\other_devices.js" flattenhtml="true" type="BINDATA" />
<include name="IDR_INSPECT_CSS" file="resources\inspect\inspect.css" flattenhtml="true" type="BINDATA" />
<include name="IDR_INSPECT_HTML" file="resources\inspect\inspect.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
<include name="IDR_INSPECT_JS" file="resources\inspect\inspect.js" flattenhtml="true" type="BINDATA" />
diff --git a/chrome/browser/resources/history/history.html b/chrome/browser/resources/history/history.html
index a4124fa..b93dc11 100644
--- a/chrome/browser/resources/history/history.html
+++ b/chrome/browser/resources/history/history.html
@@ -16,6 +16,9 @@
<link rel="stylesheet" href="../uber/uber_shared.css">
</if>
<link rel="stylesheet" href="history.css">
+<if expr="not pp_ifdef('android')">
+<link rel="stylesheet" href="other_devices.css">
+</if>
<script src="chrome://resources/js/event_tracker.js"></script>
<script src="chrome://resources/js/cr.js"></script>
@@ -25,10 +28,17 @@
<script src="chrome://resources/js/cr/ui/menu.js"></script>
<script src="chrome://resources/js/cr/ui/position_util.js"></script>
<script src="chrome://resources/js/cr/ui/menu_button.js"></script>
+<script src="chrome://resources/js/cr/ui/context_menu_button.js"></script>
+<script src="chrome://resources/js/cr/event_target.js"></script>
+<script src="chrome://resources/js/cr/ui/context_menu_handler.js"></script>
+
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://history-frame/history.js"></script>
+<if expr="not pp_ifdef('android')">
+<script src="chrome://history-frame/other_devices.js"></script>
+</if>
</head>
@@ -74,6 +84,9 @@
</label>
</div>
</div>
+<if expr="not pp_ifdef('android')">
+ <div id="other-devices" class="other-devices"></div>
+</if>
<div id="editing-controls">
<button id="clear-browsing-data" i18n-content="clearAllHistory"></button>
<button id="remove-selected" disabled="disabled"
diff --git a/chrome/browser/resources/history/other_devices.css b/chrome/browser/resources/history/other_devices.css
new file mode 100644
index 0000000..4163e12
--- /dev/null
+++ b/chrome/browser/resources/history/other_devices.css
@@ -0,0 +1,84 @@
+/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+.other-devices {
+ width: 738px; /* Same as body.uber-frame header max-width */
+}
+
+.devices-row {
+ float: left;
+}
+
+.device {
+ float: left;
+ width: 246px; /* 1/3 of body.uber-frame header max-width */
+}
+
+.device h3 {
+ font-weight: bold;
+ margin-top: 8px;
+}
+
+.device-timestamp {
+ color: rgb(151, 156, 160);
+ font-weight: normal;
+}
+
+.device-tab-entry {
+ -webkit-margin-end: 8px;
+ -webkit-margin-start: 0;
+ -webkit-padding-end: 0;
+ -webkit-padding-start: 22px;
+ background: no-repeat 0 50%;
+ background-color: transparent !important;
+ background-size: 16px 16px;
+ box-sizing: border-box;
+ display: block;
+ line-height: 1.5em;
+ margin-bottom: 0.5em;
+ margin-top: 0.5em;
+ max-width: 450px;
+ outline: none;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.device-tab-entry:not(:hover),
+.device-tab-entry:not(:focus) {
+ text-decoration: none;
+}
+
+.device-tab-entry:hover,
+.device-tab-entry:focus {
+ text-decoration: underline;
+}
+
+.device-tab-entry:active,
+.device-tab-entry:visited,
+.device-tab-entry:link {
+ color: rgb(48, 57, 66);
+ text-decoration: none;
+}
+
+.device-show-more-tabs:not(:hover),
+.device-show-more-tabs:not(:focus) {
+ text-decoration: none;
+}
+
+.device-show-more-tabs:hover,
+.device-show-more-tabs:focus {
+ text-decoration: underline;
+}
+
+.device-show-more-tabs,
+.device-show-more-tabs:active,
+.device-show-more-tabs:visited,
+.device-show-more-tabs:link {
+ color: rgb(151, 156, 160);
+}
+
+.other-devices-bottom {
+ clear: both;
+}
diff --git a/chrome/browser/resources/history/other_devices.js b/chrome/browser/resources/history/other_devices.js
new file mode 100644
index 0000000..a9eecf3
--- /dev/null
+++ b/chrome/browser/resources/history/other_devices.js
@@ -0,0 +1,525 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview The section of the history page that shows tabs from sessions
+ on other devices.
+ */
+
+///////////////////////////////////////////////////////////////////////////////
+// Globals:
+/** @const */ var MAX_NUM_COLUMNS = 3;
+/** @const */ var NB_ENTRIES_FIRST_ROW_COLUMN = 6;
+/** @const */ var NB_ENTRIES_OTHER_ROWS_COLUMN = 0;
+
+// Histogram buckets for UMA tracking of menu usage.
+// Using the same values as the Other Devices button in the NTP.
+/** @const */ var HISTOGRAM_EVENT = {
+ INITIALIZED: 0,
+ SHOW_MENU: 1,
+ LINK_CLICKED: 2,
+ LINK_RIGHT_CLICKED: 3,
+ SESSION_NAME_RIGHT_CLICKED: 4,
+ SHOW_SESSION_MENU: 5,
+ COLLAPSE_SESSION: 6,
+ EXPAND_SESSION: 7,
+ OPEN_ALL: 8,
+ LIMIT: 9 // Should always be the last one.
+};
+
+/**
+ * Record an event in the UMA histogram.
+ * @param {number} eventId The id of the event to be recorded.
+ * @private
+ */
+function recordUmaEvent_(eventId) {
+ chrome.send('metricsHandler:recordInHistogram',
+ ['HistoryPage.OtherDevicesMenu', eventId, HISTOGRAM_EVENT.LIMIT]);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DeviceContextMenuController:
+
+/**
+ * Controller for the context menu for device names in the list of sessions.
+ * This class is designed to be used as a singleton. Also copied from existing
+ * other devices button in NTP.
+ * TODO(mad): Should we extract/reuse/share with ntp4/other_sessions.js?
+ *
+ * @constructor
+ */
+function DeviceContextMenuController() {
+ this.__proto__ = DeviceContextMenuController.prototype;
+ this.initialize();
+}
+cr.addSingletonGetter(DeviceContextMenuController);
+
+// DeviceContextMenuController, Public: ---------------------------------------
+
+/**
+ * Initialize the context menu for device names in the list of sessions.
+ */
+DeviceContextMenuController.prototype.initialize = function() {
+ var menu = new cr.ui.Menu;
+ cr.ui.decorate(menu, cr.ui.Menu);
+ this.menu = menu;
+ this.collapseItem_ = this.appendMenuItem_('collapseSessionMenuItemText');
+ this.collapseItem_.addEventListener('activate',
+ this.onCollapseOrExpand_.bind(this));
+ this.expandItem_ = this.appendMenuItem_('expandSessionMenuItemText');
+ this.expandItem_.addEventListener('activate',
+ this.onCollapseOrExpand_.bind(this));
+ this.openAllItem_ = this.appendMenuItem_('restoreSessionMenuItemText');
+ this.openAllItem_.addEventListener('activate',
+ this.onOpenAll_.bind(this));
+};
+
+/**
+ * Set the session data for the session the context menu was invoked on.
+ * This should never be called when the menu is visible.
+ * @param {Object} session The model object for the session.
+ */
+DeviceContextMenuController.prototype.setSession = function(session) {
+ this.session_ = session;
+ this.updateMenuItems_();
+};
+
+// DeviceContextMenuController, Private: --------------------------------------
+
+/**
+ * Appends a menu item to |this.menu|.
+ * @param {string} textId The ID for the localized string that acts as
+ * the item's label.
+ * @return {Element} The button used for a given menu option.
+ * @private
+ */
+DeviceContextMenuController.prototype.appendMenuItem_ = function(textId) {
+ var button = document.createElement('button');
+ this.menu.appendChild(button);
+ cr.ui.decorate(button, cr.ui.MenuItem);
+ button.textContent = loadTimeData.getString(textId);
+ return button;
+};
+
+/**
+ * Handler for the 'Collapse' and 'Expand' menu items.
+ * @param {Event} e The activation event.
+ * @private
+ */
+DeviceContextMenuController.prototype.onCollapseOrExpand_ = function(e) {
+ this.session_.collapsed = !this.session_.collapsed;
+ this.updateMenuItems_();
+ chrome.send('setForeignSessionCollapsed',
+ [this.session_.tag, this.session_.collapsed]);
+ chrome.send('getForeignSessions'); // Refresh the list.
+
+ var eventId = this.session_.collapsed ?
+ HISTOGRAM_EVENT.COLLAPSE_SESSION : HISTOGRAM_EVENT.EXPAND_SESSION;
+ recordUmaEvent_(eventId);
+};
+
+/**
+ * Handler for the 'Open all' menu item.
+ * @param {Event} e The activation event.
+ * @private
+ */
+DeviceContextMenuController.prototype.onOpenAll_ = function(e) {
+ chrome.send('openForeignSession', [this.session_.tag]);
+ recordUmaEvent_(HISTOGRAM_EVENT.OPEN_ALL);
+};
+
+/**
+ * Set the visibility of the Expand/Collapse menu items based on the state
+ * of the session that this menu is currently associated with.
+ * @private
+ */
+DeviceContextMenuController.prototype.updateMenuItems_ = function() {
+ this.collapseItem_.hidden = this.session_.collapsed;
+ this.expandItem_.hidden = !this.session_.collapsed;
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Device:
+
+/**
+ * Class to hold all the information about a device entry and generate a DOM
+ * node for it.
+ * @param {Object} session An object containing the device's session data.
+ * @param {DevicesView} view The view object this entry belongs to.
+ * @constructor
+ */
+function Device(session, view) {
+ this.view_ = view;
+ this.session_ = session;
+ this.searchText_ = view.getSearchText();
+}
+
+// Device, Public: ------------------------------------------------------------
+
+/**
+ * Get the DOM node to display this device.
+ * @param {int} maxNumTabs The maximum number of tabs to display.
+ * @param {int} row The row in which this device is displayed.
+ * @return {Object} A DOM node to draw the device.
+ */
+Device.prototype.getDOMNode = function(maxNumTabs, row) {
+ var deviceDiv = createElementWithClassName('div', 'device');
+ this.row_ = row;
+ if (!this.session_)
+ return deviceDiv;
+
+ // Name heading
+ var heading = document.createElement('h3');
+ heading.textContent = this.session_.name;
+ heading.sessionData_ = this.session_;
+ deviceDiv.appendChild(heading);
+
+ // Keep track of the drop down that triggered the menu, so we know
+ // which element to apply the command to.
+ var session = this.session_;
+ function handleDropDownFocus(e) {
+ DeviceContextMenuController.getInstance().setSession(session);
+ }
+ heading.addEventListener('contextmenu', handleDropDownFocus);
+
+ var dropDownButton = new cr.ui.ContextMenuButton;
+ dropDownButton.classList.add('drop-down');
+ dropDownButton.addEventListener('mousedown', handleDropDownFocus);
+ dropDownButton.addEventListener('focus', handleDropDownFocus);
+ heading.appendChild(dropDownButton);
+
+ var timeSpan = createElementWithClassName('div', 'device-timestamp');
+ timeSpan.textContent = this.session_.modifiedTime;
+ heading.appendChild(timeSpan);
+
+ cr.ui.contextMenuHandler.setContextMenu(
+ heading, DeviceContextMenuController.getInstance().menu);
+ if (!this.session_.collapsed)
+ deviceDiv.appendChild(this.createSessionContents_(maxNumTabs));
+
+ return deviceDiv;
+};
+
+/**
+ * Marks tabs as hidden or not in our session based on the given searchText.
+ * @param {string} searchText The search text used to filter the content.
+ */
+Device.prototype.setSearchText = function(searchText) {
+ this.searchText_ = searchText.toLowerCase();
+ for (var i = 0; i < this.session_.windows.length; i++) {
+ var win = this.session_.windows[i];
+ var foundMatch = false;
+ for (var j = 0; j < win.tabs.length; j++) {
+ var tab = win.tabs[j];
+ if (tab.title.toLowerCase().indexOf(this.searchText_) != -1) {
+ foundMatch = true;
+ tab.hidden = false;
+ } else {
+ tab.hidden = true;
+ }
+ }
+ win.hidden = !foundMatch;
+ }
+};
+
+// Device, Private ------------------------------------------------------------
+
+/**
+ * Create the DOM tree representing the tabs and windows of this device.
+ * @param {int} maxNumTabs The maximum number of tabs to display.
+ * @return {Element} A single div containing the list of tabs & windows.
+ * @private
+ */
+Device.prototype.createSessionContents_ = function(maxNumTabs) {
+ var contents = createElementWithClassName('div', 'device-contents');
+
+ var sessionTag = this.session_.tag;
+ var numTabsShown = 0;
+ var numTabsHidden = 0;
+ for (var i = 0; i < this.session_.windows.length; i++) {
+ var win = this.session_.windows[i];
+ if (win.hidden)
+ continue;
+
+ // Show a separator between multiple windows in the same session.
+ if (i > 0 && numTabsShown < maxNumTabs)
+ contents.appendChild(document.createElement('hr'));
+
+ for (var j = 0; j < win.tabs.length; j++) {
+ var tab = win.tabs[j];
+ if (tab.hidden)
+ continue;
+
+ if (numTabsShown < maxNumTabs) {
+ numTabsShown++;
+ var a = createElementWithClassName('a', 'device-tab-entry');
+ a.href = tab.url;
+ a.style.backgroundImage =
+ getFaviconImageSet(tab.url, 16, /* session-favicon */ true);
+ this.addHighlightedText_(a, tab.title);
+ // Add a tooltip, since it might be ellipsized. The ones that are not
+ // necessary will be removed once added to the document, so we can
+ // compute sizes.
+ a.title = tab.title;
+ a.addEventListener('click', function(e) {
+ recordUmaEvent_(HISTOGRAM_EVENT.LINK_CLICKED);
+ chrome.send(
+ 'openForeignSession',
+ [sessionTag, String(win.sessionId), String(tab.sessionId),
+ e.button, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey]);
+ e.preventDefault();
+ });
+ contents.appendChild(a);
+ } else {
+ numTabsHidden++;
+ }
+ }
+ }
+
+ if (numTabsHidden > 0) {
+ var a = createElementWithClassName('a', 'device-show-more-tabs');
+ a.addEventListener('click', this.view_.increaseRowHeight.bind(
+ this.view_, this.row_, numTabsHidden));
+ var xMore = loadTimeData.getString('xMore');
+ a.appendChild(document.createTextNode(xMore.replace('$1', numTabsHidden)));
+ contents.appendChild(a);
+ }
+
+ return contents;
+};
+
+/**
+ * Add child text nodes to a node such that occurrences of this.searchText_ are
+ * highlighted.
+ * @param {Node} node The node under which new text nodes will be made as
+ * children.
+ * @param {string} content Text to be added beneath |node| as one or more
+ * text nodes.
+ * @private
+ */
+Device.prototype.addHighlightedText_ = function(node, content) {
+ var endOfPreviousMatch = 0;
+ if (this.searchText_) {
+ var lowerContent = content.toLowerCase();
+ var searchTextLenght = this.searchText_.length;
+ var newMatch = lowerContent.indexOf(this.searchText_, 0);
+ while (newMatch != -1) {
+ if (newMatch > endOfPreviousMatch) {
+ node.appendChild(document.createTextNode(
+ content.slice(endOfPreviousMatch, newMatch)));
+ }
+ endOfPreviousMatch = newMatch + searchTextLenght;
+ // Mark the highlighted text in bold.
+ var b = document.createElement('b');
+ b.textContent = content.substring(newMatch, endOfPreviousMatch);
+ node.appendChild(b);
+ newMatch = lowerContent.indexOf(this.searchText_, endOfPreviousMatch);
+ }
+ }
+ if (endOfPreviousMatch < content.length) {
+ node.appendChild(document.createTextNode(
+ content.slice(endOfPreviousMatch)));
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// DevicesView:
+
+/**
+ * Functions and state for populating the page with HTML.
+ * @constructor
+ */
+function DevicesView() {
+ this.devices_ = []; // List of individual devices.
+ this.resultDiv_ = $('other-devices');
+ this.searchText_ = '';
+ this.rowHeights_ = [NB_ENTRIES_FIRST_ROW_COLUMN];
+ this.updateSignInState(loadTimeData.getBoolean('isUserSignedIn'));
+ recordUmaEvent_(HISTOGRAM_EVENT.INITIALIZED);
+}
+
+// DevicesView, public: -------------------------------------------------------
+
+/**
+ * Updates our sign in state by clearing the view is not signed in or sending
+ * a request to get the data to display otherwise.
+ * @param {boolean} signedIn Whether the user is signed in or not.
+ */
+DevicesView.prototype.updateSignInState = function(signedIn) {
+ if (signedIn)
+ chrome.send('getForeignSessions');
+ else
+ this.clearDOM();
+};
+
+/**
+ * Resets the view sessions.
+ * @param {Object} sessionList The sessions to add.
+ */
+DevicesView.prototype.setSessionList = function(sessionList) {
+ this.devices_ = [];
+ for (var i = 0; i < sessionList.length; i++)
+ this.devices_.push(new Device(sessionList[i], this));
+ this.displayResults_();
+};
+
+
+/**
+ * Sets the current search text.
+ * @param {string} searchText The text to search.
+ */
+DevicesView.prototype.setSearchText = function(searchText) {
+ if (this.searchText_ != searchText) {
+ this.searchText_ = searchText;
+ for (var i = 0; i < this.devices_.length; i++)
+ this.devices_[i].setSearchText(searchText);
+ this.displayResults_();
+ }
+};
+
+/**
+ * @return {string} The current search text.
+ */
+DevicesView.prototype.getSearchText = function() {
+ return this.searchText_;
+};
+
+/**
+ * Clears the DOM content of the view.
+ */
+DevicesView.prototype.clearDOM = function() {
+ while (this.resultDiv_.hasChildNodes()) {
+ this.resultDiv_.removeChild(this.resultDiv_.lastChild);
+ }
+};
+
+/**
+ * Increase the height of a row by the given amount.
+ * @param {int} row The row number.
+ * @param {int} height The extra height to add to the givent row.
+ */
+DevicesView.prototype.increaseRowHeight = function(row, height) {
+ for (var i = this.rowHeights_.length; i <= row; i++)
+ this.rowHeights_.push(NB_ENTRIES_OTHER_ROWS_COLUMN);
+ this.rowHeights_[row] += height;
+ this.displayResults_();
+};
+
+// DevicesView, Private -------------------------------------------------------
+
+/**
+ * Update the page with results.
+ * @private
+ */
+DevicesView.prototype.displayResults_ = function() {
+ this.clearDOM();
+ var resultsFragment = document.createDocumentFragment();
+ if (this.devices_.length == 0)
+ return;
+
+ // We'll increase to 0 as we create the first row.
+ var rowIndex = -1;
+ // We need to access the last row and device when we get out of the loop.
+ var currentRowElement;
+ // This is only set when changing rows, yet used on all device columns.
+ var maxNumTabs;
+ for (var i = 0; i < this.devices_.length; i++) {
+ var device = this.devices_[i];
+ // Should we start a new row?
+ if (i % MAX_NUM_COLUMNS == 0) {
+ if (currentRowElement)
+ resultsFragment.appendChild(currentRowElement);
+ currentRowElement = createElementWithClassName('div', 'devices-row');
+ rowIndex++;
+ if (rowIndex < this.rowHeights_.length)
+ maxNumTabs = this.rowHeights_[rowIndex];
+ else
+ maxNumTabs = 0;
+ }
+
+ currentRowElement.appendChild(device.getDOMNode(maxNumTabs, rowIndex));
+ }
+ if (currentRowElement)
+ resultsFragment.appendChild(currentRowElement);
+
+ this.resultDiv_.appendChild(resultsFragment);
+ // Remove the tootltip on all lines that don't need it. It's easier to
+ // remove them here, after adding them all above, since we have the data
+ // handy above, but we don't have the width yet. Whereas here, we have the
+ // width, and the nodeValue could contain sub nodes for highlighting, which
+ // makes it harder to extract the text data here.
+ tabs = document.getElementsByClassName('device-tab-entry');
+ for (var i = 0; i < tabs.length; i++) {
+ if (tabs[i].scrollWidth <= tabs[i].clientWidth)
+ tabs[i].title = '';
+ }
+
+ this.resultDiv_.appendChild(
+ createElementWithClassName('div', 'other-devices-bottom'));
+};
+
+// We must use this namespace to reuse the handler code for foreign session and
+// login.
+cr.define('ntp', function() {
+ 'use strict';
+
+ /**
+ * Sets the menu model data. An empty list means that either there are no
+ * foreign sessions, or tab sync is disabled for this profile.
+ * |isTabSyncEnabled| makes it possible to distinguish between the cases.
+ *
+ * @param {Array} sessionList Array of objects describing the sessions
+ * from other devices.
+ * @param {boolean} isTabSyncEnabled Is tab sync enabled for this profile?
+ */
+ function setForeignSessions(sessionList, isTabSyncEnabled) {
+ // The other devices is shown iff tab sync is enabled.
+ if (isTabSyncEnabled)
+ devicesView.setSessionList(sessionList);
+ else
+ devicesView.clearDOM();
+ }
+
+ /**
+ * Called when this element is initialized, and from the new tab page when
+ * the user's signed in state changes,
+ * @param {string} header The first line of text (unused here).
+ * @param {string} subHeader The second line of text (unused here).
+ * @param {string} iconURL The url for the login status icon. If this is null
+ then the login status icon is hidden (unused here).
+ * @param {boolean} isUserSignedIn Is the user currently signed in?
+ */
+ function updateSignInState(header, subHeader, iconURL, isUserSignedIn) {
+ if (devicesView)
+ devicesView.updateSignInState(isUserSignedIn);
+ }
+
+ return {
+ setForeignSessions: setForeignSessions,
+ updateLogin: updateSignInState
+ };
+});
+
+///////////////////////////////////////////////////////////////////////////////
+// Document Functions:
+/**
+ * Window onload handler, sets up the other devices view.
+ */
+function load() {
+ devicesView = new DevicesView();
+
+ // Create the context menu that appears when the user right clicks
+ // on a device name or hit click on the button besides the device name
+ document.body.appendChild(DeviceContextMenuController.getInstance().menu);
+
+ var doSearch = function(e) {
+ devicesView.setSearchText($('search-field').value);
+ };
+ $('search-field').addEventListener('search', doSearch);
+ $('search-button').addEventListener('click', doSearch);
+}
+
+// Add handlers to HTML elements.
+document.addEventListener('DOMContentLoaded', load);
diff --git a/chrome/browser/resources/uber/uber_shared.css b/chrome/browser/resources/uber/uber_shared.css
index 34da968..64d2bbd 100644
--- a/chrome/browser/resources/uber/uber_shared.css
+++ b/chrome/browser/resources/uber/uber_shared.css
@@ -33,7 +33,9 @@ body.uber-frame header {
/* <section>s in options currently amount to 638px total, broken up into
* 600px max-width + 18px -webkit-padding-start + 20px -webkit-margin-end
* so we mirror this value here so the headers match width and horizontal
- * alignment when scrolling sideways. */
+ * alignment when scrolling sideways.
+ * other-devices.css' .device width depends on this, please keep in sync.
+ */
max-width: 738px;
min-width: 600px;
position: fixed;
diff --git a/chrome/browser/ui/webui/history_ui.cc b/chrome/browser/ui/webui/history_ui.cc
index 13c89fb..b07e178 100644
--- a/chrome/browser/ui/webui/history_ui.cc
+++ b/chrome/browser/ui/webui/history_ui.cc
@@ -14,10 +14,9 @@
#include "base/memory/singleton.h"
#include "base/message_loop.h"
#include "base/metrics/histogram.h"
+#include "base/prefs/pref_service.h"
#include "base/string16.h"
-#include "base/string_piece.h"
#include "base/strings/string_number_conversions.h"
-#include "base/threading/thread.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
@@ -29,30 +28,26 @@
#include "chrome/browser/history/history_types.h"
#include "chrome/browser/history/web_history_service.h"
#include "chrome/browser/history/web_history_service_factory.h"
+#include "chrome/browser/instant/search.h"
#include "chrome/browser/managed_mode/managed_mode_url_filter.h"
#include "chrome/browser/managed_mode/managed_user_service.h"
#include "chrome/browser/managed_mode/managed_user_service_factory.h"
#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/webui/favicon_source.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
#include "chrome/common/time_format.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/url_data_source.h"
-#include "content/public/browser/user_metrics.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "grit/browser_resources.h"
-#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
-#include "grit/locale_settings.h"
#include "grit/theme_resources.h"
#include "net/base/escape.h"
#include "sync/protocol/history_delete_directive_specifics.pb.h"
@@ -62,13 +57,14 @@
#if defined(OS_ANDROID)
#include "chrome/browser/ui/android/tab_model/tab_model.h"
#include "chrome/browser/ui/android/tab_model/tab_model_list.h"
+#else
+#include "chrome/browser/ui/webui/ntp/foreign_session_handler.h"
+#include "chrome/browser/ui/webui/ntp/ntp_login_handler.h"
#endif
-using content::UserMetricsAction;
-using content::WebContents;
-
static const char kStringsJsFile[] = "strings.js";
static const char kHistoryJsFile[] = "history.js";
+static const char kOtherDevicesJsFile[] = "other_devices.js";
// The amount of time to wait for a response from the WebHistoryService.
static const int kWebHistoryTimeoutSeconds = 3;
@@ -97,6 +93,15 @@ const char kIncognitoModeShortcut[] = "(Shift+Ctrl+N)";
content::WebUIDataSource* CreateHistoryUIHTMLSource(Profile* profile) {
content::WebUIDataSource* source =
content::WebUIDataSource::Create(chrome::kChromeUIHistoryFrameHost);
+ source->AddBoolean("isUserSignedIn",
+ !profile->GetPrefs()->GetString(prefs::kGoogleServicesUsername).empty());
+ source->AddLocalizedString("collapseSessionMenuItemText",
+ IDS_NEW_TAB_OTHER_SESSIONS_COLLAPSE_SESSION);
+ source->AddLocalizedString("expandSessionMenuItemText",
+ IDS_NEW_TAB_OTHER_SESSIONS_EXPAND_SESSION);
+ source->AddLocalizedString("restoreSessionMenuItemText",
+ IDS_NEW_TAB_OTHER_SESSIONS_OPEN_ALL);
+ source->AddLocalizedString("xMore", IDS_OTHER_DEVICES_X_MORE);
source->AddLocalizedString("loading", IDS_HISTORY_LOADING);
source->AddLocalizedString("title", IDS_HISTORY_TITLE);
source->AddLocalizedString("newest", IDS_HISTORY_NEWEST);
@@ -148,6 +153,7 @@ content::WebUIDataSource* CreateHistoryUIHTMLSource(Profile* profile) {
switches::kHistoryEnableGroupByDomain));
source->SetJsonPath(kStringsJsFile);
source->AddResourcePath(kHistoryJsFile, IDR_HISTORY_JS);
+ source->AddResourcePath(kOtherDevicesJsFile, IDR_OTHER_DEVICES_JS);
source->SetDefaultResource(IDR_HISTORY_HTML);
source->SetUseJsonJSFormatV2();
source->DisableDenyXFrameOptions();
@@ -1085,10 +1091,17 @@ void BrowsingHistoryHandler::Observe(
HistoryUI::HistoryUI(content::WebUI* web_ui) : WebUIController(web_ui) {
web_ui->AddMessageHandler(new BrowsingHistoryHandler());
+// Android deals with foreign sessions differently.
+#if !defined(OS_ANDROID)
+ if (chrome::search::IsInstantExtendedAPIEnabled()) {
+ web_ui->AddMessageHandler(new browser_sync::ForeignSessionHandler());
+ web_ui->AddMessageHandler(new NTPLoginHandler());
+ }
+#endif // !defined(OS_ANDROID)
+
// Set up the chrome://history-frame/ source.
- content::WebUIDataSource::Add(
- Profile::FromWebUI(web_ui),
- CreateHistoryUIHTMLSource(Profile::FromWebUI(web_ui)));
+ Profile* profile = Profile::FromWebUI(web_ui);
+ content::WebUIDataSource::Add(profile, CreateHistoryUIHTMLSource(profile));
}
// static
diff --git a/chrome/browser/ui/webui/history_ui.h b/chrome/browser/ui/webui/history_ui.h
index b73b773..0d17f2a 100644
--- a/chrome/browser/ui/webui/history_ui.h
+++ b/chrome/browser/ui/webui/history_ui.h
@@ -5,8 +5,6 @@
#ifndef CHROME_BROWSER_UI_WEBUI_HISTORY_UI_H_
#define CHROME_BROWSER_UI_WEBUI_HISTORY_UI_H_
-#include <string>
-
#include "base/string16.h"
#include "base/timer.h"
#include "base/values.h"
@@ -17,7 +15,6 @@
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/web_ui_controller.h"
#include "content/public/browser/web_ui_message_handler.h"
-#include "ui/base/layout.h"
class BookmarkModel;
class ManagedUserService;