Index: chrome/browser/resources/md_downloads/crisper.js |
diff --git a/chrome/browser/resources/md_downloads/crisper.js b/chrome/browser/resources/md_downloads/crisper.js |
index ee13e9ccc5999ff5cc0467850c70ed2c6112fb24..c2babd5e2d133eebb712ec59af837f16b4d46472 100644 |
--- a/chrome/browser/resources/md_downloads/crisper.js |
+++ b/chrome/browser/resources/md_downloads/crisper.js |
@@ -1553,6 +1553,9 @@ cr.define('downloads', function() { |
}; |
ActionService.prototype = { |
+ /** @private {Array<string>} */ |
+ searchTerms_: [], |
+ |
/** @param {string} id ID of the download to cancel. */ |
cancel: chromeSendWithId('cancel'), |
@@ -1578,15 +1581,17 @@ cr.define('downloads', function() { |
/** @param {string} id ID of the download that the user started dragging. */ |
drag: chromeSendWithId('drag'), |
- /** @private {boolean} */ |
- isSearching_: false, |
+ /** Loads more downloads with the current search terms. */ |
+ loadMore: function() { |
+ chrome.send('getDownloads', this.searchTerms_); |
+ }, |
/** |
* @return {boolean} Whether the user is currently searching for downloads |
* (i.e. has a non-empty search term). |
*/ |
isSearching: function() { |
- return this.isSearching_; |
+ return this.searchTerms_.length > 0; |
}, |
/** Opens the current local destination for downloads. */ |
@@ -1614,15 +1619,19 @@ cr.define('downloads', function() { |
/** @param {string} searchText What to search for. */ |
search: function(searchText) { |
- if (this.searchText_ == searchText) |
- return; |
+ var searchTerms = ActionService.splitTerms(searchText); |
+ var sameTerms = searchTerms.length == this.searchTerms_.length; |
- this.searchText_ = searchText; |
+ for (var i = 0; sameTerms && i < searchTerms.length; ++i) { |
+ if (searchTerms[i] != this.searchTerms_[i]) |
+ sameTerms = false; |
+ } |
- var terms = ActionService.splitTerms(searchText); |
- this.isSearching_ = terms.length > 0; |
+ if (sameTerms) |
+ return; |
- chrome.send('getDownloads', terms); |
+ this.searchTerms_ = searchTerms; |
+ this.loadMore(); |
}, |
/** |
@@ -10974,46 +10983,10 @@ Polymer({ |
* Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html#KeySet-Set |
*/ |
var KEY_IDENTIFIER = { |
+ 'U+0008': 'backspace', |
'U+0009': 'tab', |
'U+001B': 'esc', |
'U+0020': 'space', |
- 'U+002A': '*', |
- 'U+0030': '0', |
- 'U+0031': '1', |
- 'U+0032': '2', |
- 'U+0033': '3', |
- 'U+0034': '4', |
- 'U+0035': '5', |
- 'U+0036': '6', |
- 'U+0037': '7', |
- 'U+0038': '8', |
- 'U+0039': '9', |
- 'U+0041': 'a', |
- 'U+0042': 'b', |
- 'U+0043': 'c', |
- 'U+0044': 'd', |
- 'U+0045': 'e', |
- 'U+0046': 'f', |
- 'U+0047': 'g', |
- 'U+0048': 'h', |
- 'U+0049': 'i', |
- 'U+004A': 'j', |
- 'U+004B': 'k', |
- 'U+004C': 'l', |
- 'U+004D': 'm', |
- 'U+004E': 'n', |
- 'U+004F': 'o', |
- 'U+0050': 'p', |
- 'U+0051': 'q', |
- 'U+0052': 'r', |
- 'U+0053': 's', |
- 'U+0054': 't', |
- 'U+0055': 'u', |
- 'U+0056': 'v', |
- 'U+0057': 'w', |
- 'U+0058': 'x', |
- 'U+0059': 'y', |
- 'U+005A': 'z', |
'U+007F': 'del' |
}; |
@@ -11025,6 +10998,7 @@ Polymer({ |
* Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode#Value_of_keyCode |
*/ |
var KEY_CODE = { |
+ 8: 'backspace', |
9: 'tab', |
13: 'enter', |
27: 'esc', |
@@ -11054,16 +11028,6 @@ Polymer({ |
}; |
/** |
- * KeyboardEvent.key is mostly represented by printable character made by |
- * the keyboard, with unprintable keys labeled nicely. |
- * |
- * However, on OS X, Alt+char can make a Unicode character that follows an |
- * Apple-specific mapping. In this case, we |
- * fall back to .keyCode. |
- */ |
- var KEY_CHAR = /[a-z0-9*]/; |
- |
- /** |
* Matches a keyIdentifier string. |
*/ |
var IDENT_CHAR = /U\+/; |
@@ -11083,14 +11047,12 @@ Polymer({ |
var validKey = ''; |
if (key) { |
var lKey = key.toLowerCase(); |
- if (lKey.length == 1) { |
- if (KEY_CHAR.test(lKey)) { |
- validKey = lKey; |
- } |
+ if (lKey === ' ' || SPACE_KEY.test(lKey)) { |
+ validKey = 'space'; |
+ } else if (lKey.length == 1) { |
+ validKey = lKey; |
} else if (ARROW_KEY.test(lKey)) { |
validKey = lKey.replace('arrow', ''); |
- } else if (SPACE_KEY.test(lKey)) { |
- validKey = 'space'; |
} else if (lKey == 'multiply') { |
// numpad '*' can map to Multiply on IE/Windows |
validKey = '*'; |
@@ -11104,8 +11066,11 @@ Polymer({ |
function transformKeyIdentifier(keyIdent) { |
var validKey = ''; |
if (keyIdent) { |
- if (IDENT_CHAR.test(keyIdent)) { |
+ if (keyIdent in KEY_IDENTIFIER) { |
validKey = KEY_IDENTIFIER[keyIdent]; |
+ } else if (IDENT_CHAR.test(keyIdent)) { |
+ keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16); |
+ validKey = String.fromCharCode(keyIdent).toLowerCase(); |
} else { |
validKey = keyIdent.toLowerCase(); |
} |
@@ -11145,15 +11110,24 @@ Polymer({ |
transformKey(keyEvent.detail.key) || ''; |
} |
- function keyComboMatchesEvent(keyCombo, keyEvent) { |
- return normalizedKeyForEvent(keyEvent) === keyCombo.key && |
- !!keyEvent.shiftKey === !!keyCombo.shiftKey && |
- !!keyEvent.ctrlKey === !!keyCombo.ctrlKey && |
- !!keyEvent.altKey === !!keyCombo.altKey && |
- !!keyEvent.metaKey === !!keyCombo.metaKey; |
+ function keyComboMatchesEvent(keyCombo, event, eventKey) { |
+ return eventKey === keyCombo.key && |
+ (!keyCombo.hasModifiers || ( |
+ !!event.shiftKey === !!keyCombo.shiftKey && |
+ !!event.ctrlKey === !!keyCombo.ctrlKey && |
+ !!event.altKey === !!keyCombo.altKey && |
+ !!event.metaKey === !!keyCombo.metaKey) |
+ ); |
} |
function parseKeyComboString(keyComboString) { |
+ if (keyComboString.length === 1) { |
+ return { |
+ combo: keyComboString, |
+ key: keyComboString, |
+ event: 'keydown' |
+ }; |
+ } |
return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboPart) { |
var eventParts = keyComboPart.split(':'); |
var keyName = eventParts[0]; |
@@ -11161,6 +11135,7 @@ Polymer({ |
if (keyName in MODIFIER_KEYS) { |
parsedKeyCombo[MODIFIER_KEYS[keyName]] = true; |
+ parsedKeyCombo.hasModifiers = true; |
} else { |
parsedKeyCombo.key = keyName; |
parsedKeyCombo.event = event || 'keydown'; |
@@ -11173,12 +11148,11 @@ Polymer({ |
} |
function parseEventString(eventString) { |
- return eventString.split(' ').map(function(keyComboString) { |
+ return eventString.trim().split(' ').map(function(keyComboString) { |
return parseKeyComboString(keyComboString); |
}); |
} |
- |
/** |
* `Polymer.IronA11yKeysBehavior` provides a normalized interface for processing |
* keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3.org/TR/wai-aria-practices/#kbd_general_binding). |
@@ -11274,14 +11248,12 @@ Polymer({ |
keyboardEventMatchesKeys: function(event, eventString) { |
var keyCombos = parseEventString(eventString); |
- var index; |
- |
- for (index = 0; index < keyCombos.length; ++index) { |
- if (keyComboMatchesEvent(keyCombos[index], event)) { |
+ var eventKey = normalizedKeyForEvent(event); |
+ for (var i = 0; i < keyCombos.length; ++i) { |
+ if (keyComboMatchesEvent(keyCombos[i], event, eventKey)) { |
return true; |
} |
} |
- |
return false; |
}, |
@@ -11309,6 +11281,15 @@ Polymer({ |
for (var eventString in this._imperativeKeyBindings) { |
this._addKeyBinding(eventString, this._imperativeKeyBindings[eventString]); |
} |
+ |
+ // Give precedence to combos with modifiers to be checked first. |
+ for (var eventName in this._keyBindings) { |
+ this._keyBindings[eventName].sort(function (kb1, kb2) { |
+ var b1 = kb1[0].hasModifiers; |
+ var b2 = kb2[0].hasModifiers; |
+ return (b1 === b2) ? 0 : b1 ? -1 : 1; |
+ }) |
+ } |
}, |
_addKeyBinding: function(eventString, handlerName) { |
@@ -11364,14 +11345,23 @@ Polymer({ |
event.stopPropagation(); |
} |
- keyBindings.forEach(function(keyBinding) { |
- var keyCombo = keyBinding[0]; |
- var handlerName = keyBinding[1]; |
+ // if event has been already prevented, don't do anything |
+ if (event.defaultPrevented) { |
+ return; |
+ } |
- if (!event.defaultPrevented && keyComboMatchesEvent(keyCombo, event)) { |
+ var eventKey = normalizedKeyForEvent(event); |
+ for (var i = 0; i < keyBindings.length; i++) { |
+ var keyCombo = keyBindings[i][0]; |
+ var handlerName = keyBindings[i][1]; |
+ if (keyComboMatchesEvent(keyCombo, event, eventKey)) { |
this._triggerKeyHandler(keyCombo, handlerName, event); |
+ // exit the loop if eventDefault was prevented |
+ if (event.defaultPrevented) { |
+ return; |
+ } |
} |
- }, this); |
+ } |
}, |
_triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) { |
@@ -11609,7 +11599,7 @@ Polymer({ |
// Ignore the event if this is coming from a focused light child, since that |
// element will deal with it. |
- if (this.isLightDescendant(target)) |
+ if (this.isLightDescendant(/** @type {Node} */(target))) |
return; |
keyboardEvent.preventDefault(); |
@@ -11626,7 +11616,7 @@ Polymer({ |
// Ignore the event if this is coming from a focused light child, since that |
// element will deal with it. |
- if (this.isLightDescendant(target)) |
+ if (this.isLightDescendant(/** @type {Node} */(target))) |
return; |
if (this.pressed) { |
@@ -12579,6 +12569,15 @@ Polymer({ |
Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this); |
} |
} |
+ /** |
+ |
+ Fired when the animation finishes. |
+ This is useful if you want to wait until |
+ the ripple animation finishes to perform some action. |
+ |
+ @event transitionend |
+ @param {{node: Object}} detail Contains the animated node. |
+ */ |
}); |
/** |
* `iron-range-behavior` provides the behavior for something with a minimum to maximum range. |
@@ -13762,6 +13761,7 @@ Polymer({ |
for (var i = 0, item; item = this.items[i]; i++) { |
var attr = this.attrForItemTitle || 'textContent'; |
var title = item[attr] || item.getAttribute(attr); |
+ |
if (title && title.trim().charAt(0).toLowerCase() === String.fromCharCode(event.keyCode).toLowerCase()) { |
this._setFocusedItem(item); |
break; |
@@ -13802,7 +13802,6 @@ Polymer({ |
} else { |
item.removeAttribute('aria-selected'); |
} |
- |
Polymer.IronSelectableBehavior._applySelection.apply(this, arguments); |
}, |
@@ -13850,18 +13849,18 @@ Polymer({ |
* @param {CustomEvent} event A key combination event. |
*/ |
_onShiftTabDown: function(event) { |
- var oldTabIndex; |
+ var oldTabIndex = this.getAttribute('tabindex'); |
Polymer.IronMenuBehaviorImpl._shiftTabPressed = true; |
- oldTabIndex = this.getAttribute('tabindex'); |
+ this._setFocusedItem(null); |
this.setAttribute('tabindex', '-1'); |
this.async(function() { |
this.setAttribute('tabindex', oldTabIndex); |
Polymer.IronMenuBehaviorImpl._shiftTabPressed = false; |
- // NOTE(cdata): polymer/polymer#1305 |
+ // NOTE(cdata): polymer/polymer#1305 |
}, 1); |
}, |
@@ -13872,23 +13871,27 @@ Polymer({ |
*/ |
_onFocus: function(event) { |
if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) { |
+ // do not focus the menu itself |
return; |
} |
- // do not focus the menu itself |
+ |
this.blur(); |
+ |
// clear the cached focus item |
- this._setFocusedItem(null); |
this._defaultFocusAsync = this.async(function() { |
// focus the selected item when the menu receives focus, or the first item |
// if no item is selected |
var selectedItem = this.multi ? (this.selectedItems && this.selectedItems[0]) : this.selectedItem; |
+ |
+ this._setFocusedItem(null); |
+ |
if (selectedItem) { |
this._setFocusedItem(selectedItem); |
} else { |
this._setFocusedItem(this.items[0]); |
} |
- // async 100ms to wait for `select` to get called from `_itemActivate` |
- }, 100); |
+ // async 1ms to wait for `select` to get called from `_itemActivate` |
+ }, 1); |
}, |
/** |
@@ -13926,12 +13929,17 @@ Polymer({ |
* @param {KeyboardEvent} event A keyboard event. |
*/ |
_onKeydown: function(event) { |
- if (this.keyboardEventMatchesKeys(event, 'up down esc')) { |
- return; |
+ if (!this.keyboardEventMatchesKeys(event, 'up down esc')) { |
+ // all other keys focus the menu item starting with that character |
+ this._focusWithKeyboardEvent(event); |
} |
+ event.stopPropagation(); |
+ }, |
- // all other keys focus the menu item starting with that character |
- this._focusWithKeyboardEvent(event); |
+ // override _activateHandler |
+ _activateHandler: function(event) { |
+ Polymer.IronSelectableBehavior._activateHandler.call(this, event); |
+ event.stopPropagation(); |
} |
}; |
@@ -16976,6 +16984,10 @@ cr.define('downloads', function() { |
loading: true, |
}, |
+ listeners: { |
+ 'downloads-list.scroll': 'onListScroll_', |
+ }, |
+ |
observers: [ |
'itemsChanged_(items_.*)', |
], |
@@ -17043,14 +17055,25 @@ cr.define('downloads', function() { |
downloads.ActionService.getInstance().undo(); |
}, |
+ /** |
+ * @param {Event} e |
+ * @private |
+ */ |
+ onListScroll_: function(e) { |
+ var list = this.$['downloads-list']; |
+ if (list.scrollHeight - list.scrollTop - list.offsetHeight <= 100) { |
+ // Approaching the end of the scrollback. Attempt to load more items. |
+ downloads.ActionService.getInstance().loadMore(); |
+ } |
+ }, |
+ |
/** @private */ |
onLoad_: function() { |
cr.ui.decorate('command', cr.ui.Command); |
document.addEventListener('canExecute', this.onCanExecute_.bind(this)); |
document.addEventListener('command', this.onCommand_.bind(this)); |
- // Shows all downloads. |
- downloads.ActionService.getInstance().search(''); |
+ downloads.ActionService.getInstance().loadMore(); |
}, |
/** |
@@ -17060,6 +17083,7 @@ cr.define('downloads', function() { |
removeItem_: function(index) { |
this.splice('items_', index, 1); |
this.updateHideDates_(index, index); |
+ this.onListScroll_(); |
}, |
/** |