diff options
Diffstat (limited to 'chromium/chrome/browser/resources/history/history.js')
-rw-r--r-- | chromium/chrome/browser/resources/history/history.js | 330 |
1 files changed, 209 insertions, 121 deletions
diff --git a/chromium/chrome/browser/resources/history/history.js b/chromium/chrome/browser/resources/history/history.js index edfcd913ff7..8cfb2f81744 100644 --- a/chromium/chrome/browser/resources/history/history.js +++ b/chromium/chrome/browser/resources/history/history.js @@ -29,8 +29,10 @@ var selectionAnchor = -1; var activeVisit = null; /** @const */ var Command = cr.ui.Command; +/** @const */ var FocusOutlineManager = cr.ui.FocusOutlineManager; /** @const */ var Menu = cr.ui.Menu; /** @const */ var MenuButton = cr.ui.MenuButton; +/** @const */ var MenuItem = cr.ui.MenuItem; /** * Enum that shows the filtering behavior for a host or URL to a supervised @@ -48,7 +50,7 @@ var SupervisedUserFilteringBehavior = { * The type of the history result object. The definition is based on * chrome/browser/ui/webui/history_ui.cc: * BrowsingHistoryHandler::HistoryEntry::ToValue() - * @typedef {{allTimestamps: Array.<number>, + * @typedef {{allTimestamps: Array<number>, * blockedVisit: (boolean|undefined), * dateRelativeDay: (string|undefined), * dateShort: string, @@ -190,8 +192,7 @@ Visit.prototype.getResultDOM = function(propertyBag) { this.id_ = this.model_.getNextVisitId(); var self = this; - // Only create the checkbox if it can be used either to delete an entry or to - // block/allow it. + // Only create the checkbox if it can be used to delete an entry. if (this.model_.editingEntriesAllowed) { var checkbox = document.createElement('input'); checkbox.type = 'checkbox'; @@ -248,6 +249,10 @@ Visit.prototype.getResultDOM = function(propertyBag) { e.preventDefault(); }.bind(this)); } + + if (focusless) + bookmarkSection.tabIndex = -1; + entryBox.appendChild(bookmarkSection); var visitEntryWrapper = /** @type {HTMLElement} */( @@ -271,19 +276,23 @@ Visit.prototype.getResultDOM = function(propertyBag) { } if (isMobileVersion()) { - var removeButton = createElementWithClassName('button', 'remove-entry'); - removeButton.setAttribute('aria-label', - loadTimeData.getString('removeFromHistory')); - removeButton.classList.add('custom-appearance'); - removeButton.addEventListener( - 'click', this.removeEntryFromHistory_.bind(this)); - entryBox.appendChild(removeButton); - - // Support clicking anywhere inside the entry box. - entryBox.addEventListener('click', function(e) { - if (!e.defaultPrevented) - self.titleLink.click(); - }); + if (this.model_.editingEntriesAllowed) { + var removeButton = createElementWithClassName('button', 'remove-entry'); + removeButton.setAttribute('aria-label', + loadTimeData.getString('removeFromHistory')); + removeButton.classList.add('custom-appearance'); + removeButton.addEventListener( + 'click', this.removeEntryFromHistory_.bind(this)); + entryBox.appendChild(removeButton); + + // Support clicking anywhere inside the entry box. + entryBox.addEventListener('click', function(e) { + if (!e.defaultPrevented) { + self.titleLink.focus(); + self.titleLink.click(); + } + }); + } } else { var dropDown = createElementWithClassName('button', 'drop-down'); dropDown.value = 'Open action menu'; @@ -625,7 +634,7 @@ HistoryModel.prototype.requestPage = function(page) { /** * Receiver for history query. * @param {HistoryQuery} info An object containing information about the query. - * @param {Array.<HistoryEntry>} results A list of results. + * @param {Array<HistoryEntry>} results A list of results. */ HistoryModel.prototype.addResults = function(info, results) { // If no requests are in flight then this was an old request so we drop the @@ -669,7 +678,7 @@ HistoryModel.prototype.getSize = function() { * Get a list of visits between specified index positions. * @param {number} start The start index. * @param {number} end The end index. - * @return {Array.<Visit>} A list of visits. + * @return {Array<Visit>} A list of visits. */ HistoryModel.prototype.getNumberedRange = function(start, end) { return this.visits_.slice(start, end); @@ -687,7 +696,7 @@ HistoryModel.prototype.hasMoreResults = function() { /** * Removes a list of visits from the history, and calls |callback| when the * removal has successfully completed. - * @param {Array.<Visit>} visits The visits to remove. + * @param {Array<Visit>} visits The visits to remove. * @param {Function} callback The function to call after removal succeeds. */ HistoryModel.prototype.removeVisitsFromHistory = function(visits, callback) { @@ -701,8 +710,8 @@ HistoryModel.prototype.removeVisitsFromHistory = function(visits, callback) { }); } - chrome.send('removeVisits', toBeRemoved); this.deleteCompleteCallback_ = callback; + chrome.send('removeVisits', toBeRemoved); }; /** @return {boolean} Whether the model is currently deleting a visit. */ @@ -882,55 +891,87 @@ HistoryModel.prototype.getGroupByDomain = function() { }; /////////////////////////////////////////////////////////////////////////////// -// HistoryFocusObserver: +// HistoryFocusRow: /** + * Provides an implementation for a single column grid. * @constructor - * @implements {cr.ui.FocusRow.Observer} + * @extends {cr.ui.FocusRow} */ -function HistoryFocusObserver() {} +function HistoryFocusRow() {} + +/** + * Decorates |rowElement| so that it can be treated as a HistoryFocusRow item. + * @param {Element} rowElement The element representing this row. + * @param {Node} boundary Focus events are ignored outside of this node. + */ +HistoryFocusRow.decorate = function(rowElement, boundary) { + rowElement.__proto__ = HistoryFocusRow.prototype; + rowElement.decorate(boundary); + + rowElement.addElementIfPresent_('.entry-box input', 'checkbox'); + rowElement.addElementIfPresent_('.domain-checkbox', 'checkbox'); + rowElement.addElementIfPresent_('.bookmark-section.starred', 'star'); + rowElement.addElementIfPresent_('[is="action-link"]', 'domain'); + rowElement.addElementIfPresent_('.title a', 'title'); + rowElement.addElementIfPresent_('.drop-down', 'menu'); +}; + +HistoryFocusRow.prototype = { + __proto__: cr.ui.FocusRow.prototype, -HistoryFocusObserver.prototype = { /** @override */ - onActivate: function(row) { - this.getActiveRowElement_(row).classList.add('active'); + onActiveStateChanged: function(state) { + this.classList.toggle('active', state); }, /** @override */ - onDeactivate: function(row) { - this.getActiveRowElement_(row).classList.remove('active'); + getEquivalentElement: function(element) { + if (this.contains(element)) + return element; + + // All elements default to another element with the same type. + var equivalent = this.getColumn_(element.getAttribute('column-type')); + + if (!equivalent) { + switch (element.getAttribute('column-type')) { + case 'star': + equivalent = this.getColumn_('title') || this.getColumn_('domain'); + break; + case 'domain': + equivalent = this.getColumn_('title'); + break; + case 'title': + equivalent = this.getColumn_('domain'); + break; + case 'menu': + return this.focusableElements[this.focusableElements.length - 1]; + } + } + + return equivalent || this.focusableElements[0]; }, /** - * @param {cr.ui.FocusRow} row The row to find an element for. - * @return {Element} |row|'s "active" element. + * @param {string} type The type of column to return. + * @return {?Element} The column matching the type. * @private */ - getActiveRowElement_: function(row) { - return findAncestorByClass(row.items[0], 'entry') || - findAncestorByClass(row.items[0], 'site-domain-wrapper'); + getColumn_: function(type) { + return this.querySelector('[column-type=' + type + ']'); }, -}; - -/////////////////////////////////////////////////////////////////////////////// -// HistoryFocusGrid: - -/** - * @param {Node=} opt_boundary - * @param {cr.ui.FocusRow.Observer=} opt_observer - * @constructor - * @extends {cr.ui.FocusGrid} - */ -function HistoryFocusGrid(opt_boundary, opt_observer) { - cr.ui.FocusGrid.apply(this, arguments); -} -HistoryFocusGrid.prototype = { - __proto__: cr.ui.FocusGrid.prototype, - - /** @override */ - onMousedown: function(row, e) { - return !!findAncestorByClass(e.target, 'menu-button'); + /** + * @param {string} query A query to select the appropriate element. + * @param {string} type The type to use for the element. + * @private + */ + addElementIfPresent_: function(query, type) { + var element = this.querySelector(query); + if (element) { + this.addFocusableElement(element); + element.setAttribute('column-type', type); + } }, }; @@ -948,8 +989,7 @@ function HistoryView(model) { this.editButtonTd_ = $('edit-button'); this.editingControlsDiv_ = $('editing-controls'); this.resultDiv_ = $('results-display'); - this.focusGrid_ = new HistoryFocusGrid(this.resultDiv_, - new HistoryFocusObserver); + this.focusGrid_ = new cr.ui.FocusGrid(); this.pageDiv_ = $('results-pagination'); this.model_ = model; this.pageIndex_ = 0; @@ -985,7 +1025,7 @@ function HistoryView(model) { $('timeframe-controls').onchange = function(e) { var value = parseInt(e.target.value, 10); - self.setRangeInDays(/** @type {HistoryModel.Range.<number>} */(value)); + self.setRangeInDays(/** @type {HistoryModel.Range<number>} */(value)); }; $('range-previous').addEventListener('click', function(e) { @@ -1181,14 +1221,14 @@ HistoryView.prototype.showNotification = function(innerHTML, isWarning) { HistoryView.prototype.onBeforeRemove = function(visit) { assert(this.currentVisits_.indexOf(visit) >= 0); - var pos = this.focusGrid_.getPositionForTarget(document.activeElement); - if (!pos) + var rowIndex = this.focusGrid_.getRowIndexForTarget(document.activeElement); + if (rowIndex == -1) return; - var row = this.focusGrid_.rows[pos.row + 1] || - this.focusGrid_.rows[pos.row - 1]; - if (row) - row.focusIndex(Math.min(pos.col, row.items.length - 1)); + var rowToFocus = this.focusGrid_.rows[rowIndex + 1] || + this.focusGrid_.rows[rowIndex - 1]; + if (rowToFocus) + rowToFocus.getEquivalentElement(document.activeElement).focus(); }; /** @param {Visit} visit The visit about to be unstarred. */ @@ -1196,9 +1236,12 @@ HistoryView.prototype.onBeforeUnstarred = function(visit) { assert(this.currentVisits_.indexOf(visit) >= 0); assert(visit.bookmarkStar == document.activeElement); - var pos = this.focusGrid_.getPositionForTarget(document.activeElement); - var row = this.focusGrid_.rows[pos.row]; - row.focusIndex(Math.min(pos.col + 1, row.items.length - 1)); + var rowIndex = this.focusGrid_.getRowIndexForTarget(document.activeElement); + var row = this.focusGrid_.rows[rowIndex]; + + // Focus the title or domain when the bookmarked star is removed because the + // star will no longer be focusable. + row.querySelector('[column-type=title], [column-type=domain]').focus(); }; /** @param {Visit} visit The visit that was just unstarred. */ @@ -1296,7 +1339,7 @@ HistoryView.prototype.positionNotificationBar = function() { * @return {boolean} Whether |el| is in |this.focusGrid_|. */ HistoryView.prototype.isInFocusGrid = function(el) { - return !!this.focusGrid_.getPositionForTarget(el); + return this.focusGrid_.getRowIndexForTarget(el) != -1; }; // HistoryView, private: ------------------------------------------------------ @@ -1311,7 +1354,11 @@ HistoryView.prototype.clear_ = function() { if (alertOverlay && alertOverlay.classList.contains('showing')) hideConfirmationOverlay(); - this.resultDiv_.textContent = ''; + // Remove everything but <h3 id="results-header"> (the first child). + while (this.resultDiv_.children.length > 1) { + this.resultDiv_.removeChild(this.resultDiv_.lastElementChild); + } + $('results-header').textContent = ''; this.currentVisits_.forEach(function(visit) { visit.isRendered = false; @@ -1573,13 +1620,19 @@ HistoryView.prototype.displayResults_ = function(doneLoading) { var groupByDomain = this.model_.getGroupByDomain(); if (searchText) { - // Add a header for the search results, if there isn't already one. - if (!this.resultDiv_.querySelector('h3')) { - var header = document.createElement('h3'); - header.textContent = loadTimeData.getStringF('searchResultsFor', - searchText); - this.resultDiv_.appendChild(header); + var headerText; + if (!doneLoading) { + headerText = loadTimeData.getStringF('searchResultsFor', searchText); + } else if (results.length == 0) { + headerText = loadTimeData.getString('noSearchResults'); + } else { + var resultId = results.length == 1 ? 'searchResult' : 'searchResults'; + headerText = loadTimeData.getStringF('foundSearchResults', + results.length, + loadTimeData.getString(resultId), + searchText); } + $('results-header').textContent = headerText; this.addTimeframeInterval_(this.resultDiv_); @@ -1589,11 +1642,7 @@ HistoryView.prototype.displayResults_ = function(doneLoading) { if (!this.model_.editingEntriesAllowed) searchResults.classList.add('no-checkboxes'); - if (results.length == 0 && doneLoading) { - var noSearchResults = searchResults.appendChild( - createElementWithClassName('div', 'no-results-message')); - noSearchResults.textContent = loadTimeData.getString('noSearchResults'); - } else { + if (doneLoading) { for (var i = 0, visit; visit = results[i]; i++) { if (!visit.isRendered) { searchResults.appendChild(visit.getResultDOM({ @@ -1610,13 +1659,12 @@ HistoryView.prototype.displayResults_ = function(doneLoading) { this.addTimeframeInterval_(resultsFragment); - if (results.length == 0 && doneLoading) { - var noResults = resultsFragment.appendChild( - createElementWithClassName('div', 'no-results-message')); - noResults.textContent = loadTimeData.getString('noResults'); - this.resultDiv_.appendChild(resultsFragment); + var noResults = results.length == 0 && doneLoading; + $('results-header').textContent = noResults ? + loadTimeData.getString('noResults') : ''; + + if (noResults) return; - } if (this.getRangeInDays() == HistoryModel.Range.MONTH && groupByDomain) { @@ -1653,26 +1701,17 @@ var focusGridRowSelector = [ '.site-domain-wrapper' ].join(', '); -var focusGridColumnSelector = [ - '.entry-box input', - '.bookmark-section.starred', - '.title a', - '.drop-down', - '.domain-checkbox', - '[is="action-link"]', -].join(', '); - /** @private */ HistoryView.prototype.updateFocusGrid_ = function() { var rows = this.resultDiv_.querySelectorAll(focusGridRowSelector); - var grid = []; + this.focusGrid_.destroy(); for (var i = 0; i < rows.length; ++i) { assert(rows[i].parentNode); - grid.push(rows[i].querySelectorAll(focusGridColumnSelector)); + HistoryFocusRow.decorate(rows[i], this.resultDiv_); + this.focusGrid_.addRow(rows[i]); } - - this.focusGrid_.setGrid(grid); + this.focusGrid_.ensureRowActive(); }; /** @@ -1682,9 +1721,9 @@ HistoryView.prototype.updateFocusGrid_ = function() { HistoryView.prototype.updateNavBar_ = function() { this.updateRangeButtons_(); - // Supervised users have the control bar on top, don't show it on the bottom - // as well. - if (!loadTimeData.getBoolean('isSupervisedProfile')) { + // If grouping by domain is enabled, there's a control bar on top, don't show + // the one on the bottom as well. + if (!loadTimeData.getBoolean('groupByDomain')) { $('newest-button').hidden = this.pageIndex_ == 0; $('newer-button').hidden = this.pageIndex_ == 0; $('older-button').hidden = @@ -1763,6 +1802,16 @@ HistoryView.prototype.toggleGroupedVisits_ = function(e) { } entry.classList.toggle('expand'); + + var row = entry.querySelector('.site-domain-wrapper'); + var activeRows = + this.resultDiv_.getElementsByClassName(cr.ui.FocusRow.ACTIVE_CLASS); + for (var i = 0; i < activeRows.length; ++i) { + if (activeRows[i] != row) // Ignore |row| to avoid flicker. + activeRows[i].makeActive(false); + } + row.makeActive(true); + this.updateFocusGrid_(); }; @@ -1885,6 +1934,7 @@ PageState.getHashString = function(term, page, range, offset) { */ function load() { uber.onContentFrameLoaded(); + FocusOutlineManager.forDocument(document); var searchField = $('search-field'); @@ -1914,18 +1964,14 @@ function load() { searchField.blur(); // Dismiss the keyboard. }; - var mayRemoveVisits = loadTimeData.getBoolean('allowDeletingHistory'); - $('remove-visit').disabled = !mayRemoveVisits; - - if (mayRemoveVisits) { - $('remove-visit').addEventListener('activate', function(e) { - activeVisit.removeFromHistory(); - activeVisit = null; - }); - } + var removeMenu = getRequiredElement('remove-visit'); + // Decorate remove-visit before disabling/hiding because the values are + // overwritten when decorating a MenuItem that has a Command. + cr.ui.decorate(removeMenu, MenuItem); + removeMenu.disabled = !loadTimeData.getBoolean('allowDeletingHistory'); + removeMenu.hidden = loadTimeData.getBoolean('hideDeleteVisitUI'); - if (!loadTimeData.getBoolean('showDeleteVisitUI')) - $('remove-visit').hidden = true; + document.addEventListener('command', handleCommand); searchField.addEventListener('search', doSearch); $('search-button').addEventListener('click', doSearch); @@ -1935,15 +1981,16 @@ function load() { activeVisit = null; }); - // Only show the controls if the command line switch is activated. - if (loadTimeData.getBoolean('groupByDomain') || - loadTimeData.getBoolean('isSupervisedProfile')) { - // Hide the top container which has the "Clear browsing data" and "Remove - // selected entries" buttons since they're unavailable for supervised users. - $('top-container').hidden = true; + // Only show the controls if the command line switch is activated or the user + // is supervised. + if (loadTimeData.getBoolean('groupByDomain')) { $('history-page').classList.add('big-topbar-page'); $('filter-controls').hidden = false; } + // Hide the top container which has the "Clear browsing data" and "Remove + // selected entries" buttons if deleting history is not allowed. + if (!loadTimeData.getBoolean('allowDeletingHistory')) + $('top-container').hidden = true; uber.setTitle(loadTimeData.getString('title')); @@ -1970,7 +2017,7 @@ function load() { $('history-page').appendChild($('clear-browsing-data')); } else { window.addEventListener('message', function(e) { - e = /** @type {!MessageEvent.<!{method: string}>} */(e); + e = /** @type {!MessageEvent<!{method: string}>} */(e); if (e.data.method == 'frameSelected') searchField.focus(); }); @@ -1995,6 +2042,26 @@ function load() { } /** + * Handle all commands in the history page. + * @param {!Event} e is a command event. + */ +function handleCommand(e) { + switch (e.command.id) { + case 'remove-visit-command': + // Removing visited items needs to be done with a command in order to have + // proper focus. This is because the command event is handled after the + // menu dialog is no longer visible and focus has returned to the history + // items. The activate event is handled when the menu dialog is still + // visible and focus is lost. + // removeEntryFromHistory_ will update activeVisit to the newly focused + // history item. + assert(!$('remove-visit').disabled); + activeVisit.removeEntryFromHistory_(e); + break; + } +} + +/** * Updates the filter status labels of a host/URL entry to the current value. * @param {Element} statusElement The div which contains the status labels. * @param {SupervisedUserFilteringBehavior} newStatus The filter status of the @@ -2032,10 +2099,23 @@ function showConfirmationOverlay() { $('history-page').setAttribute('aria-hidden', 'true'); uber.invokeMethodOnParent('beginInterceptingEvents'); - // If an element is focused behind the confirm overlay, blur it so focus - // doesn't accidentally get stuck behind it. + // Change focus to the overlay if any other control was focused by keyboard + // before. Otherwise, no one should have focus. + var focusOverlay = FocusOutlineManager.forDocument(document).visible && + document.activeElement != document.body; if ($('history-page').contains(document.activeElement)) document.activeElement.blur(); + + if (focusOverlay) { + // Wait until the browser knows the button has had a chance to become + // visible. + window.requestAnimationFrame(function() { + var button = cr.ui.overlay.getDefaultButton($('overlay')); + if (button) + button.focus(); + }); + } + $('alertOverlay').classList.toggle('focus-on-hide', focusOverlay); } /** @@ -2112,6 +2192,10 @@ function removeItems() { historyView.reload.bind(historyView)); $('overlay').removeEventListener('cancelOverlay', onCancelRemove); hideConfirmationOverlay(); + if ($('alertOverlay').classList.contains('focus-on-hide') && + FocusOutlineManager.forDocument(document).visible) { + $('search-field').focus(); + } } function onCancelRemove() { @@ -2126,6 +2210,10 @@ function removeItems() { } $('overlay').removeEventListener('cancelOverlay', onCancelRemove); hideConfirmationOverlay(); + if ($('alertOverlay').classList.contains('focus-on-hide') && + FocusOutlineManager.forDocument(document).visible) { + $('remove-selected').focus(); + } } if (checked.length) { @@ -2290,7 +2378,7 @@ function getFilteringStatusDOM(filteringBehavior) { /** * Our history system calls this function with results from searches. * @param {HistoryQuery} info An object containing information about the query. - * @param {Array.<HistoryEntry>} results A list of results. + * @param {Array<HistoryEntry>} results A list of results. */ function historyResult(info, results) { historyModel.addResults(info, results); |