blob: fa0dd0ec63028265a51312b8dacdc328af61cf7e [file] [log] [blame]
// Copyright 2015 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.
/**
* @param {!Element} root
* @param {?Element} boundary
* @param {cr.ui.FocusRow.Delegate} delegate
* @constructor
* @extends {cr.ui.FocusRow}
*/
function HistoryFocusRow(root, boundary, delegate) {
cr.ui.FocusRow.call(this, root, boundary, delegate);
this.addItems();
}
HistoryFocusRow.prototype = {
__proto__: cr.ui.FocusRow.prototype,
/** @override */
getCustomEquivalent: function(sampleElement) {
let equivalent;
if (this.getTypeForElement(sampleElement) == 'star')
equivalent = this.getFirstFocusable('title');
return equivalent ||
cr.ui.FocusRow.prototype.getCustomEquivalent.call(this, sampleElement);
},
addItems: function() {
this.destroy();
assert(this.addItem('checkbox', '#checkbox'));
assert(this.addItem('title', '#title'));
assert(this.addItem('menu-button', '#menu-button'));
this.addItem('star', '#bookmark-star');
},
};
cr.define('md_history', function() {
/**
* @param {{lastFocused: Object}} historyItemElement
* @constructor
* @implements {cr.ui.FocusRow.Delegate}
*/
function FocusRowDelegate(historyItemElement) {
this.historyItemElement = historyItemElement;
}
FocusRowDelegate.prototype = {
/**
* @override
* @param {!cr.ui.FocusRow} row
* @param {!Event} e
*/
onFocus: function(row, e) {
this.historyItemElement.lastFocused = e.path[0];
},
/**
* @override
* @param {!cr.ui.FocusRow} row The row that detected a keydown.
* @param {!Event} e
* @return {boolean} Whether the event was handled.
*/
onKeydown: function(row, e) {
// Allow Home and End to move the history list.
if (e.key == 'Home' || e.key == 'End')
return true;
// Prevent iron-list from changing the focus on enter.
if (e.key == 'Enter')
e.stopPropagation();
return false;
},
};
const HistoryItem = Polymer({
is: 'history-item',
properties: {
// Underlying HistoryEntry data for this item. Contains read-only fields
// from the history backend, as well as fields computed by history-list.
item: {
type: Object,
observer: 'itemChanged_',
},
selected: {
type: Boolean,
reflectToAttribute: true,
},
isCardStart: {
type: Boolean,
reflectToAttribute: true,
},
isCardEnd: {
type: Boolean,
reflectToAttribute: true,
},
/** @type {Element} */
lastFocused: {
type: Object,
notify: true,
},
ironListTabIndex: {
type: Number,
observer: 'ironListTabIndexChanged_',
},
selectionNotAllowed_: {
type: Boolean,
value: !loadTimeData.getBoolean('allowDeletingHistory'),
},
hasTimeGap: Boolean,
index: Number,
numberOfItems: Number,
// Search term used to obtain this history-item.
searchTerm: String,
},
/** @private {?HistoryFocusRow} */
row_: null,
/** @private {boolean} */
mouseDown_: false,
/** @override */
attached: function() {
Polymer.RenderStatus.afterNextRender(this, function() {
this.row_ = new HistoryFocusRow(
this.$['main-container'], null, new FocusRowDelegate(this));
this.row_.makeActive(this.ironListTabIndex == 0);
this.listen(this, 'focus', 'onFocus_');
this.listen(this, 'dom-change', 'onDomChange_');
});
},
/** @override */
detached: function() {
this.unlisten(this, 'focus', 'onFocus_');
this.unlisten(this, 'dom-change', 'onDomChange_');
if (this.row_)
this.row_.destroy();
},
/**
* @private
*/
onFocus_: function() {
// Don't change the focus while the mouse is down, as it prevents text
// selection. Not changing focus here is acceptable because the checkbox
// will be focused in onItemClick_() anyway.
if (this.mouseDown_)
return;
if (this.lastFocused)
this.row_.getEquivalentElement(this.lastFocused).focus();
else
this.row_.getFirstFocusable().focus();
this.tabIndex = -1;
},
/**
* @private
*/
ironListTabIndexChanged_: function() {
if (this.row_)
this.row_.makeActive(this.ironListTabIndex == 0);
},
/**
* @private
*/
onDomChange_: function() {
if (this.row_)
this.row_.addItems();
},
/**
* Toggle item selection whenever the checkbox or any non-interactive part
* of the item is clicked.
* @param {MouseEvent} e
* @private
*/
onItemClick_: function(e) {
for (let i = 0; i < e.path.length; i++) {
const elem = e.path[i];
if (elem.id != 'checkbox' &&
(elem.nodeName == 'A' || elem.nodeName == 'BUTTON')) {
return;
}
}
if (this.selectionNotAllowed_)
return;
this.$.checkbox.focus();
this.fire('history-checkbox-select', {
index: this.index,
shiftKey: e.shiftKey,
});
},
/**
* @param {MouseEvent} e
* @private
*/
onItemMousedown_: function(e) {
this.mouseDown_ = true;
listenOnce(document, 'mouseup', () => {
this.mouseDown_ = false;
});
// Prevent shift clicking a checkbox from selecting text.
if (e.shiftKey)
e.preventDefault();
},
/**
* @private
* @return {string}
*/
getEntrySummary_: function() {
const item = this.item;
return loadTimeData.getStringF(
'entrySummary', item.dateTimeOfDay,
item.starred ? loadTimeData.getString('bookmarked') : '', item.title,
item.domain);
},
/**
* @param {boolean} selected
* @return {string}
* @private
*/
getAriaChecked_: function(selected) {
return selected ? 'true' : 'false';
},
/**
* Remove bookmark of current item when bookmark-star is clicked.
* @private
*/
onRemoveBookmarkTap_: function() {
if (!this.item.starred)
return;
if (this.$$('#bookmark-star') == this.root.activeElement)
this.$['menu-button'].focus();
const browserService = md_history.BrowserService.getInstance();
browserService.removeBookmark(this.item.url);
browserService.recordAction('BookmarkStarClicked');
this.fire('remove-bookmark-stars', this.item.url);
},
/**
* Fires a custom event when the menu button is clicked. Sends the details
* of the history item and where the menu should appear.
*/
onMenuButtonTap_: function(e) {
this.fire('open-menu', {
target: Polymer.dom(e).localTarget,
index: this.index,
item: this.item,
});
// Stops the 'tap' event from closing the menu when it opens.
e.stopPropagation();
},
/**
* Record metrics when a result is clicked. This is deliberately tied to
* on-click rather than on-tap, as on-click triggers from middle clicks.
*/
onLinkClick_: function() {
const browserService = md_history.BrowserService.getInstance();
browserService.recordAction('EntryLinkClick');
if (this.searchTerm)
browserService.recordAction('SearchResultClick');
if (this.index == undefined)
return;
browserService.recordHistogram(
'HistoryPage.ClickPosition',
Math.min(this.index, UMA_MAX_BUCKET_VALUE), UMA_MAX_BUCKET_VALUE);
if (this.index <= UMA_MAX_SUBSET_BUCKET_VALUE) {
browserService.recordHistogram(
'HistoryPage.ClickPositionSubset', this.index,
UMA_MAX_SUBSET_BUCKET_VALUE);
}
},
onLinkRightClick_: function() {
md_history.BrowserService.getInstance().recordAction(
'EntryLinkRightClick');
},
/**
* Set the favicon image, based on the URL of the history item.
* @private
*/
itemChanged_: function() {
this.$.icon.style.backgroundImage = cr.icon.getFavicon(this.item.url);
this.listen(this.$['time-accessed'], 'mouseover', 'addTimeTitle_');
},
/**
* @param {number} numberOfItems The number of items in the card.
* @param {string} historyDate Date of the current result.
* @param {string} search The search term associated with these results.
* @return {string} The title for this history card.
* @private
*/
cardTitle_: function(numberOfItems, historyDate, search) {
if (!search)
return this.item.dateRelativeDay;
return HistoryItem.searchResultsTitle(numberOfItems, search);
},
/** @private */
addTimeTitle_: function() {
const el = this.$['time-accessed'];
el.setAttribute('title', new Date(this.item.time).toString());
this.unlisten(el, 'mouseover', 'addTimeTitle_');
},
});
/**
* @param {number} numberOfResults
* @param {string} searchTerm
* @return {string} The title for a page of search results.
*/
HistoryItem.searchResultsTitle = function(numberOfResults, searchTerm) {
const resultId = numberOfResults == 1 ? 'searchResult' : 'searchResults';
return loadTimeData.getStringF(
'foundSearchResults', numberOfResults, loadTimeData.getString(resultId),
searchTerm);
};
return {HistoryItem: HistoryItem};
});