| 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 955127bc5da5f135aafd87b6d73e59eeb1ac8d84..de974974530524d8a14aaa09fec6b7fe235a62f6 100644
|
| --- a/chrome/browser/resources/md_downloads/crisper.js
|
| +++ b/chrome/browser/resources/md_downloads/crisper.js
|
| @@ -2353,67 +2353,6 @@ var ActionLink = document.registerElement('action-link', {
|
|
|
| extends: 'a',
|
| });
|
| -// 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.
|
| -
|
| -/** @typedef {{img: HTMLImageElement, url: string}} */
|
| -var LoadIconRequest;
|
| -
|
| -cr.define('downloads', function() {
|
| - /**
|
| - * @param {number} maxAllowed The maximum number of simultaneous downloads
|
| - * allowed.
|
| - * @constructor
|
| - */
|
| - function ThrottledIconLoader(maxAllowed) {
|
| - assert(maxAllowed > 0);
|
| -
|
| - /** @private {number} */
|
| - this.maxAllowed_ = maxAllowed;
|
| -
|
| - /** @private {!Array<!LoadIconRequest>} */
|
| - this.requests_ = [];
|
| - }
|
| -
|
| - ThrottledIconLoader.prototype = {
|
| - /** @private {number} */
|
| - loading_: 0,
|
| -
|
| - /**
|
| - * Load the provided |url| into |img.src| after appending ?scale=.
|
| - * @param {!HTMLImageElement} img An <img> to show the loaded image in.
|
| - * @param {string} url A remote image URL to load.
|
| - */
|
| - loadScaledIcon: function(img, url) {
|
| - var scaledUrl = url + '?scale=' + window.devicePixelRatio + 'x';
|
| - if (img.src == scaledUrl)
|
| - return;
|
| -
|
| - this.requests_.push({img: img, url: scaledUrl});
|
| - this.loadNextIcon_();
|
| - },
|
| -
|
| - /** @private */
|
| - loadNextIcon_: function() {
|
| - if (this.loading_ > this.maxAllowed_ || !this.requests_.length)
|
| - return;
|
| -
|
| - var request = this.requests_.shift();
|
| - var img = request.img;
|
| -
|
| - img.onabort = img.onerror = img.onload = function() {
|
| - this.loading_--;
|
| - this.loadNextIcon_();
|
| - }.bind(this);
|
| -
|
| - this.loading_++;
|
| - img.src = request.url;
|
| - },
|
| - };
|
| -
|
| - return {ThrottledIconLoader: ThrottledIconLoader};
|
| -});
|
| // Copyright 2014 Google Inc. All rights reserved.
|
| //
|
| // Licensed under the Apache License, Version 2.0 (the "License");
|
| @@ -8808,6 +8747,1336 @@ this._insertChildren();
|
| this.fire('dom-change');
|
| }
|
| });
|
| +/**
|
| + * `IronResizableBehavior` is a behavior that can be used in Polymer elements to
|
| + * coordinate the flow of resize events between "resizers" (elements that control the
|
| + * size or hidden state of their children) and "resizables" (elements that need to be
|
| + * notified when they are resized or un-hidden by their parents in order to take
|
| + * action on their new measurements).
|
| + * Elements that perform measurement should add the `IronResizableBehavior` behavior to
|
| + * their element definition and listen for the `iron-resize` event on themselves.
|
| + * This event will be fired when they become showing after having been hidden,
|
| + * when they are resized explicitly by another resizable, or when the window has been
|
| + * resized.
|
| + * Note, the `iron-resize` event is non-bubbling.
|
| + *
|
| + * @polymerBehavior Polymer.IronResizableBehavior
|
| + * @demo demo/index.html
|
| + **/
|
| + Polymer.IronResizableBehavior = {
|
| + properties: {
|
| + /**
|
| + * The closest ancestor element that implements `IronResizableBehavior`.
|
| + */
|
| + _parentResizable: {
|
| + type: Object,
|
| + observer: '_parentResizableChanged'
|
| + },
|
| +
|
| + /**
|
| + * True if this element is currently notifying its descedant elements of
|
| + * resize.
|
| + */
|
| + _notifyingDescendant: {
|
| + type: Boolean,
|
| + value: false
|
| + }
|
| + },
|
| +
|
| + listeners: {
|
| + 'iron-request-resize-notifications': '_onIronRequestResizeNotifications'
|
| + },
|
| +
|
| + created: function() {
|
| + // We don't really need property effects on these, and also we want them
|
| + // to be created before the `_parentResizable` observer fires:
|
| + this._interestedResizables = [];
|
| + this._boundNotifyResize = this.notifyResize.bind(this);
|
| + },
|
| +
|
| + attached: function() {
|
| + this.fire('iron-request-resize-notifications', null, {
|
| + node: this,
|
| + bubbles: true,
|
| + cancelable: true
|
| + });
|
| +
|
| + if (!this._parentResizable) {
|
| + window.addEventListener('resize', this._boundNotifyResize);
|
| + this.notifyResize();
|
| + }
|
| + },
|
| +
|
| + detached: function() {
|
| + if (this._parentResizable) {
|
| + this._parentResizable.stopResizeNotificationsFor(this);
|
| + } else {
|
| + window.removeEventListener('resize', this._boundNotifyResize);
|
| + }
|
| +
|
| + this._parentResizable = null;
|
| + },
|
| +
|
| + /**
|
| + * Can be called to manually notify a resizable and its descendant
|
| + * resizables of a resize change.
|
| + */
|
| + notifyResize: function() {
|
| + if (!this.isAttached) {
|
| + return;
|
| + }
|
| +
|
| + this._interestedResizables.forEach(function(resizable) {
|
| + if (this.resizerShouldNotify(resizable)) {
|
| + this._notifyDescendant(resizable);
|
| + }
|
| + }, this);
|
| +
|
| + this._fireResize();
|
| + },
|
| +
|
| + /**
|
| + * Used to assign the closest resizable ancestor to this resizable
|
| + * if the ancestor detects a request for notifications.
|
| + */
|
| + assignParentResizable: function(parentResizable) {
|
| + this._parentResizable = parentResizable;
|
| + },
|
| +
|
| + /**
|
| + * Used to remove a resizable descendant from the list of descendants
|
| + * that should be notified of a resize change.
|
| + */
|
| + stopResizeNotificationsFor: function(target) {
|
| + var index = this._interestedResizables.indexOf(target);
|
| +
|
| + if (index > -1) {
|
| + this._interestedResizables.splice(index, 1);
|
| + this.unlisten(target, 'iron-resize', '_onDescendantIronResize');
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * This method can be overridden to filter nested elements that should or
|
| + * should not be notified by the current element. Return true if an element
|
| + * should be notified, or false if it should not be notified.
|
| + *
|
| + * @param {HTMLElement} element A candidate descendant element that
|
| + * implements `IronResizableBehavior`.
|
| + * @return {boolean} True if the `element` should be notified of resize.
|
| + */
|
| + resizerShouldNotify: function(element) { return true; },
|
| +
|
| + _onDescendantIronResize: function(event) {
|
| + if (this._notifyingDescendant) {
|
| + event.stopPropagation();
|
| + return;
|
| + }
|
| +
|
| + // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the
|
| + // otherwise non-bubbling event "just work." We do it manually here for
|
| + // the case where Polymer is not using shadow roots for whatever reason:
|
| + if (!Polymer.Settings.useShadow) {
|
| + this._fireResize();
|
| + }
|
| + },
|
| +
|
| + _fireResize: function() {
|
| + this.fire('iron-resize', null, {
|
| + node: this,
|
| + bubbles: false
|
| + });
|
| + },
|
| +
|
| + _onIronRequestResizeNotifications: function(event) {
|
| + var target = event.path ? event.path[0] : event.target;
|
| +
|
| + if (target === this) {
|
| + return;
|
| + }
|
| +
|
| + if (this._interestedResizables.indexOf(target) === -1) {
|
| + this._interestedResizables.push(target);
|
| + this.listen(target, 'iron-resize', '_onDescendantIronResize');
|
| + }
|
| +
|
| + target.assignParentResizable(this);
|
| + this._notifyDescendant(target);
|
| +
|
| + event.stopPropagation();
|
| + },
|
| +
|
| + _parentResizableChanged: function(parentResizable) {
|
| + if (parentResizable) {
|
| + window.removeEventListener('resize', this._boundNotifyResize);
|
| + }
|
| + },
|
| +
|
| + _notifyDescendant: function(descendant) {
|
| + // NOTE(cdata): In IE10, attached is fired on children first, so it's
|
| + // important not to notify them if the parent is not attached yet (or
|
| + // else they will get redundantly notified when the parent attaches).
|
| + if (!this.isAttached) {
|
| + return;
|
| + }
|
| +
|
| + this._notifyingDescendant = true;
|
| + descendant.notifyResize();
|
| + this._notifyingDescendant = false;
|
| + }
|
| + };
|
| +(function() {
|
| +
|
| + var IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/);
|
| + var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8;
|
| + var DEFAULT_PHYSICAL_COUNT = 20;
|
| + var MAX_PHYSICAL_COUNT = 500;
|
| +
|
| + Polymer({
|
| +
|
| + is: 'iron-list',
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * An array containing items determining how many instances of the template
|
| + * to stamp and that that each template instance should bind to.
|
| + */
|
| + items: {
|
| + type: Array
|
| + },
|
| +
|
| + /**
|
| + * The name of the variable to add to the binding scope for the array
|
| + * element associated with a given template instance.
|
| + */
|
| + as: {
|
| + type: String,
|
| + value: 'item'
|
| + },
|
| +
|
| + /**
|
| + * The name of the variable to add to the binding scope with the index
|
| + * for the row. If `sort` is provided, the index will reflect the
|
| + * sorted order (rather than the original array order).
|
| + */
|
| + indexAs: {
|
| + type: String,
|
| + value: 'index'
|
| + },
|
| +
|
| + /**
|
| + * The name of the variable to add to the binding scope to indicate
|
| + * if the row is selected.
|
| + */
|
| + selectedAs: {
|
| + type: String,
|
| + value: 'selected'
|
| + },
|
| +
|
| + /**
|
| + * When true, tapping a row will select the item, placing its data model
|
| + * in the set of selected items retrievable via the selection property.
|
| + *
|
| + * Note that tapping focusable elements within the list item will not
|
| + * result in selection, since they are presumed to have their * own action.
|
| + */
|
| + selectionEnabled: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * When `multiSelection` is false, this is the currently selected item, or `null`
|
| + * if no item is selected.
|
| + */
|
| + selectedItem: {
|
| + type: Object,
|
| + notify: true
|
| + },
|
| +
|
| + /**
|
| + * When `multiSelection` is true, this is an array that contains the selected items.
|
| + */
|
| + selectedItems: {
|
| + type: Object,
|
| + notify: true
|
| + },
|
| +
|
| + /**
|
| + * When `true`, multiple items may be selected at once (in this case,
|
| + * `selected` is an array of currently selected items). When `false`,
|
| + * only one item may be selected at a time.
|
| + */
|
| + multiSelection: {
|
| + type: Boolean,
|
| + value: false
|
| + }
|
| + },
|
| +
|
| + observers: [
|
| + '_itemsChanged(items.*)',
|
| + '_selectionEnabledChanged(selectionEnabled)',
|
| + '_multiSelectionChanged(multiSelection)'
|
| + ],
|
| +
|
| + behaviors: [
|
| + Polymer.Templatizer,
|
| + Polymer.IronResizableBehavior
|
| + ],
|
| +
|
| + listeners: {
|
| + 'iron-resize': '_resizeHandler'
|
| + },
|
| +
|
| + /**
|
| + * The ratio of hidden tiles that should remain in the scroll direction.
|
| + * Recommended value ~0.5, so it will distribute tiles evely in both directions.
|
| + */
|
| + _ratio: 0.5,
|
| +
|
| + /**
|
| + * The element that controls the scroll
|
| + * @type {?Element}
|
| + */
|
| + _scroller: null,
|
| +
|
| + /**
|
| + * The padding-top value of the `scroller` element
|
| + */
|
| + _scrollerPaddingTop: 0,
|
| +
|
| + /**
|
| + * This value is the same as `scrollTop`.
|
| + */
|
| + _scrollPosition: 0,
|
| +
|
| + /**
|
| + * The number of tiles in the DOM.
|
| + */
|
| + _physicalCount: 0,
|
| +
|
| + /**
|
| + * The k-th tile that is at the top of the scrolling list.
|
| + */
|
| + _physicalStart: 0,
|
| +
|
| + /**
|
| + * The k-th tile that is at the bottom of the scrolling list.
|
| + */
|
| + _physicalEnd: 0,
|
| +
|
| + /**
|
| + * The sum of the heights of all the tiles in the DOM.
|
| + */
|
| + _physicalSize: 0,
|
| +
|
| + /**
|
| + * The average `offsetHeight` of the tiles observed till now.
|
| + */
|
| + _physicalAverage: 0,
|
| +
|
| + /**
|
| + * The number of tiles which `offsetHeight` > 0 observed until now.
|
| + */
|
| + _physicalAverageCount: 0,
|
| +
|
| + /**
|
| + * The Y position of the item rendered in the `_physicalStart`
|
| + * tile relative to the scrolling list.
|
| + */
|
| + _physicalTop: 0,
|
| +
|
| + /**
|
| + * The number of items in the list.
|
| + */
|
| + _virtualCount: 0,
|
| +
|
| + /**
|
| + * The n-th item rendered in the `_physicalStart` tile.
|
| + */
|
| + _virtualStartVal: 0,
|
| +
|
| + /**
|
| + * A map between an item key and its physical item index
|
| + */
|
| + _physicalIndexForKey: null,
|
| +
|
| + /**
|
| + * The estimated scroll height based on `_physicalAverage`
|
| + */
|
| + _estScrollHeight: 0,
|
| +
|
| + /**
|
| + * The scroll height of the dom node
|
| + */
|
| + _scrollHeight: 0,
|
| +
|
| + /**
|
| + * The size of the viewport
|
| + */
|
| + _viewportSize: 0,
|
| +
|
| + /**
|
| + * An array of DOM nodes that are currently in the tree
|
| + * @type {?Array<!TemplatizerNode>}
|
| + */
|
| + _physicalItems: null,
|
| +
|
| + /**
|
| + * An array of heights for each item in `_physicalItems`
|
| + * @type {?Array<number>}
|
| + */
|
| + _physicalSizes: null,
|
| +
|
| + /**
|
| + * A cached value for the visible index.
|
| + * See `firstVisibleIndex`
|
| + * @type {?number}
|
| + */
|
| + _firstVisibleIndexVal: null,
|
| +
|
| + /**
|
| + * A Polymer collection for the items.
|
| + * @type {?Polymer.Collection}
|
| + */
|
| + _collection: null,
|
| +
|
| + /**
|
| + * True if the current item list was rendered for the first time
|
| + * after attached.
|
| + */
|
| + _itemsRendered: false,
|
| +
|
| + /**
|
| + * The bottom of the physical content.
|
| + */
|
| + get _physicalBottom() {
|
| + return this._physicalTop + this._physicalSize;
|
| + },
|
| +
|
| + /**
|
| + * The n-th item rendered in the last physical item.
|
| + */
|
| + get _virtualEnd() {
|
| + return this._virtualStartVal + this._physicalCount - 1;
|
| + },
|
| +
|
| + /**
|
| + * The lowest n-th value for an item such that it can be rendered in `_physicalStart`.
|
| + */
|
| + _minVirtualStart: 0,
|
| +
|
| + /**
|
| + * The largest n-th value for an item such that it can be rendered in `_physicalStart`.
|
| + */
|
| + get _maxVirtualStart() {
|
| + return this._virtualCount < this._physicalCount ?
|
| + this._virtualCount : this._virtualCount - this._physicalCount;
|
| + },
|
| +
|
| + /**
|
| + * The height of the physical content that isn't on the screen.
|
| + */
|
| + get _hiddenContentSize() {
|
| + return this._physicalSize - this._viewportSize;
|
| + },
|
| +
|
| + /**
|
| + * The maximum scroll top value.
|
| + */
|
| + get _maxScrollTop() {
|
| + return this._estScrollHeight - this._viewportSize;
|
| + },
|
| +
|
| + /**
|
| + * Sets the n-th item rendered in `_physicalStart`
|
| + */
|
| + set _virtualStart(val) {
|
| + // clamp the value so that _minVirtualStart <= val <= _maxVirtualStart
|
| + this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._minVirtualStart, val));
|
| + this._physicalStart = this._virtualStartVal % this._physicalCount;
|
| + this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this._physicalCount;
|
| + },
|
| +
|
| + /**
|
| + * Gets the n-th item rendered in `_physicalStart`
|
| + */
|
| + get _virtualStart() {
|
| + return this._virtualStartVal;
|
| + },
|
| +
|
| + /**
|
| + * An optimal physical size such that we will have enough physical items
|
| + * to fill up the viewport and recycle when the user scrolls.
|
| + *
|
| + * This default value assumes that we will at least have the equivalent
|
| + * to a viewport of physical items above and below the user's viewport.
|
| + */
|
| + get _optPhysicalSize() {
|
| + return this._viewportSize * 3;
|
| + },
|
| +
|
| + /**
|
| + * True if the current list is visible.
|
| + */
|
| + get _isVisible() {
|
| + return this._scroller && Boolean(this._scroller.offsetWidth || this._scroller.offsetHeight);
|
| + },
|
| +
|
| + /**
|
| + * Gets the first visible item in the viewport.
|
| + *
|
| + * @type {number}
|
| + */
|
| + get firstVisibleIndex() {
|
| + var physicalOffset;
|
| +
|
| + if (this._firstVisibleIndexVal === null) {
|
| + physicalOffset = this._physicalTop;
|
| +
|
| + this._firstVisibleIndexVal = this._iterateItems(
|
| + function(pidx, vidx) {
|
| + physicalOffset += this._physicalSizes[pidx];
|
| +
|
| + if (physicalOffset > this._scrollPosition) {
|
| + return vidx;
|
| + }
|
| + }) || 0;
|
| + }
|
| +
|
| + return this._firstVisibleIndexVal;
|
| + },
|
| +
|
| + ready: function() {
|
| + if (IOS_TOUCH_SCROLLING) {
|
| + this._scrollListener = function() {
|
| + requestAnimationFrame(this._scrollHandler.bind(this));
|
| + }.bind(this);
|
| + } else {
|
| + this._scrollListener = this._scrollHandler.bind(this);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * When the element has been attached to the DOM tree.
|
| + */
|
| + attached: function() {
|
| + // delegate to the parent's scroller
|
| + // e.g. paper-scroll-header-panel
|
| + var el = Polymer.dom(this);
|
| +
|
| + var parentNode = /** @type {?{scroller: ?Element}} */ (el.parentNode);
|
| + if (parentNode && parentNode.scroller) {
|
| + this._scroller = parentNode.scroller;
|
| + } else {
|
| + this._scroller = this;
|
| + this.classList.add('has-scroller');
|
| + }
|
| +
|
| + if (IOS_TOUCH_SCROLLING) {
|
| + this._scroller.style.webkitOverflowScrolling = 'touch';
|
| + }
|
| +
|
| + this._scroller.addEventListener('scroll', this._scrollListener);
|
| +
|
| + this.updateViewportBoundaries();
|
| + this._render();
|
| + },
|
| +
|
| + /**
|
| + * When the element has been removed from the DOM tree.
|
| + */
|
| + detached: function() {
|
| + this._itemsRendered = false;
|
| + if (this._scroller) {
|
| + this._scroller.removeEventListener('scroll', this._scrollListener);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Invoke this method if you dynamically update the viewport's
|
| + * size or CSS padding.
|
| + *
|
| + * @method updateViewportBoundaries
|
| + */
|
| + updateViewportBoundaries: function() {
|
| + var scrollerStyle = window.getComputedStyle(this._scroller);
|
| + this._scrollerPaddingTop = parseInt(scrollerStyle['padding-top'], 10);
|
| + this._viewportSize = this._scroller.offsetHeight;
|
| + },
|
| +
|
| + /**
|
| + * Update the models, the position of the
|
| + * items in the viewport and recycle tiles as needed.
|
| + */
|
| + _refresh: function() {
|
| + var SCROLL_DIRECTION_UP = -1;
|
| + var SCROLL_DIRECTION_DOWN = 1;
|
| + var SCROLL_DIRECTION_NONE = 0;
|
| +
|
| + // clamp the `scrollTop` value
|
| + // IE 10|11 scrollTop may go above `_maxScrollTop`
|
| + // iOS `scrollTop` may go below 0 and above `_maxScrollTop`
|
| + var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scroller.scrollTop));
|
| +
|
| + var tileHeight, kth, recycledTileSet;
|
| + var ratio = this._ratio;
|
| + var delta = scrollTop - this._scrollPosition;
|
| + var direction = SCROLL_DIRECTION_NONE;
|
| + var recycledTiles = 0;
|
| + var hiddenContentSize = this._hiddenContentSize;
|
| + var currentRatio = ratio;
|
| + var movingUp = [];
|
| +
|
| + // track the last `scrollTop`
|
| + this._scrollPosition = scrollTop;
|
| +
|
| + // clear cached visible index
|
| + this._firstVisibleIndexVal = null;
|
| +
|
| + // random access
|
| + if (Math.abs(delta) > this._physicalSize) {
|
| + this._physicalTop += delta;
|
| + direction = SCROLL_DIRECTION_NONE;
|
| + recycledTiles = Math.round(delta / this._physicalAverage);
|
| + }
|
| + // scroll up
|
| + else if (delta < 0) {
|
| + var topSpace = scrollTop - this._physicalTop;
|
| + var virtualStart = this._virtualStart;
|
| +
|
| + direction = SCROLL_DIRECTION_UP;
|
| + recycledTileSet = [];
|
| +
|
| + kth = this._physicalEnd;
|
| + currentRatio = topSpace / hiddenContentSize;
|
| +
|
| + // move tiles from bottom to top
|
| + while (
|
| + // approximate `currentRatio` to `ratio`
|
| + currentRatio < ratio &&
|
| + // recycle less physical items than the total
|
| + recycledTiles < this._physicalCount &&
|
| + // ensure that these recycled tiles are needed
|
| + virtualStart - recycledTiles > 0
|
| + ) {
|
| +
|
| + tileHeight = this._physicalSizes[kth] || this._physicalAverage;
|
| + currentRatio += tileHeight / hiddenContentSize;
|
| +
|
| + recycledTileSet.push(kth);
|
| + recycledTiles++;
|
| + kth = (kth === 0) ? this._physicalCount - 1 : kth - 1;
|
| + }
|
| +
|
| + movingUp = recycledTileSet;
|
| + recycledTiles = -recycledTiles;
|
| +
|
| + }
|
| + // scroll down
|
| + else if (delta > 0) {
|
| + var bottomSpace = this._physicalBottom - (scrollTop + this._viewportSize);
|
| + var virtualEnd = this._virtualEnd;
|
| + var lastVirtualItemIndex = this._virtualCount-1;
|
| +
|
| + direction = SCROLL_DIRECTION_DOWN;
|
| + recycledTileSet = [];
|
| +
|
| + kth = this._physicalStart;
|
| + currentRatio = bottomSpace / hiddenContentSize;
|
| +
|
| + // move tiles from top to bottom
|
| + while (
|
| + // approximate `currentRatio` to `ratio`
|
| + currentRatio < ratio &&
|
| + // recycle less physical items than the total
|
| + recycledTiles < this._physicalCount &&
|
| + // ensure that these recycled tiles are needed
|
| + virtualEnd + recycledTiles < lastVirtualItemIndex
|
| + ) {
|
| +
|
| + tileHeight = this._physicalSizes[kth] || this._physicalAverage;
|
| + currentRatio += tileHeight / hiddenContentSize;
|
| +
|
| + this._physicalTop += tileHeight;
|
| + recycledTileSet.push(kth);
|
| + recycledTiles++;
|
| + kth = (kth + 1) % this._physicalCount;
|
| + }
|
| + }
|
| +
|
| + if (recycledTiles !== 0) {
|
| + this._virtualStart = this._virtualStart + recycledTiles;
|
| + this._update(recycledTileSet, movingUp);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Update the list of items, starting from the `_virtualStartVal` item.
|
| + * @param {!Array<number>=} itemSet
|
| + * @param {!Array<number>=} movingUp
|
| + */
|
| + _update: function(itemSet, movingUp) {
|
| + // update models
|
| + this._assignModels(itemSet);
|
| +
|
| + // measure heights
|
| + this._updateMetrics(itemSet);
|
| +
|
| + // adjust offset after measuring
|
| + if (movingUp) {
|
| + while (movingUp.length) {
|
| + this._physicalTop -= this._physicalSizes[movingUp.pop()];
|
| + }
|
| + }
|
| + // update the position of the items
|
| + this._positionItems();
|
| +
|
| + // set the scroller size
|
| + this._updateScrollerSize();
|
| +
|
| + // increase the pool of physical items if needed
|
| + if (this._increasePoolIfNeeded()) {
|
| + // set models to the new items
|
| + this.async(this._update);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Creates a pool of DOM elements and attaches them to the local dom.
|
| + */
|
| + _createPool: function(size) {
|
| + var physicalItems = new Array(size);
|
| +
|
| + this._ensureTemplatized();
|
| +
|
| + for (var i = 0; i < size; i++) {
|
| + var inst = this.stamp(null);
|
| +
|
| + // First element child is item; Safari doesn't support children[0]
|
| + // on a doc fragment
|
| + physicalItems[i] = inst.root.querySelector('*');
|
| + Polymer.dom(this).appendChild(inst.root);
|
| + }
|
| +
|
| + return physicalItems;
|
| + },
|
| +
|
| + /**
|
| + * Increases the pool size. That is, the physical items in the DOM.
|
| + * This function will allocate additional physical items
|
| + * (limited by `MAX_PHYSICAL_COUNT`) if the content size is shorter than
|
| + * `_optPhysicalSize`
|
| + *
|
| + * @return boolean
|
| + */
|
| + _increasePoolIfNeeded: function() {
|
| + if (this._physicalSize >= this._optPhysicalSize || this._physicalAverage === 0) {
|
| + return false;
|
| + }
|
| +
|
| + // the estimated number of physical items that we will need to reach
|
| + // the cap established by `_optPhysicalSize`.
|
| + var missingItems = Math.round(
|
| + (this._optPhysicalSize - this._physicalSize) * 1.2 / this._physicalAverage
|
| + );
|
| +
|
| + // limit the size
|
| + var nextPhysicalCount = Math.min(
|
| + this._physicalCount + missingItems,
|
| + this._virtualCount,
|
| + MAX_PHYSICAL_COUNT
|
| + );
|
| +
|
| + var prevPhysicalCount = this._physicalCount;
|
| + var delta = nextPhysicalCount - prevPhysicalCount;
|
| +
|
| + if (delta <= 0) {
|
| + return false;
|
| + }
|
| +
|
| + var newPhysicalItems = this._createPool(delta);
|
| + var emptyArray = new Array(delta);
|
| +
|
| + [].push.apply(this._physicalItems, newPhysicalItems);
|
| + [].push.apply(this._physicalSizes, emptyArray);
|
| +
|
| + this._physicalCount = prevPhysicalCount + delta;
|
| +
|
| + return true;
|
| + },
|
| +
|
| + /**
|
| + * Render a new list of items. This method does exactly the same as `update`,
|
| + * but it also ensures that only one `update` cycle is created.
|
| + */
|
| + _render: function() {
|
| + var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0;
|
| +
|
| + if (this.isAttached && !this._itemsRendered && this._isVisible && requiresUpdate) {
|
| + this._update();
|
| + this._itemsRendered = true;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Templetizes the user template.
|
| + */
|
| + _ensureTemplatized: function() {
|
| + if (!this.ctor) {
|
| + // Template instance props that should be excluded from forwarding
|
| + var props = {};
|
| +
|
| + props.__key__ = true;
|
| + props[this.as] = true;
|
| + props[this.indexAs] = true;
|
| + props[this.selectedAs] = true;
|
| +
|
| + this._instanceProps = props;
|
| + this._userTemplate = Polymer.dom(this).querySelector('template');
|
| +
|
| + if (this._userTemplate) {
|
| + this.templatize(this._userTemplate);
|
| + } else {
|
| + console.warn('iron-list requires a template to be provided in light-dom');
|
| + }
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Implements extension point from Templatizer mixin.
|
| + */
|
| + _getStampedChildren: function() {
|
| + return this._physicalItems;
|
| + },
|
| +
|
| + /**
|
| + * Implements extension point from Templatizer
|
| + * Called as a side effect of a template instance path change, responsible
|
| + * for notifying items.<key-for-instance>.<path> change up to host.
|
| + */
|
| + _forwardInstancePath: function(inst, path, value) {
|
| + if (path.indexOf(this.as + '.') === 0) {
|
| + this.notifyPath('items.' + inst.__key__ + '.' +
|
| + path.slice(this.as.length + 1), value);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Implements extension point from Templatizer mixin
|
| + * Called as side-effect of a host property change, responsible for
|
| + * notifying parent path change on each row.
|
| + */
|
| + _forwardParentProp: function(prop, value) {
|
| + if (this._physicalItems) {
|
| + this._physicalItems.forEach(function(item) {
|
| + item._templateInstance[prop] = value;
|
| + }, this);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Implements extension point from Templatizer
|
| + * Called as side-effect of a host path change, responsible for
|
| + * notifying parent.<path> path change on each row.
|
| + */
|
| + _forwardParentPath: function(path, value) {
|
| + if (this._physicalItems) {
|
| + this._physicalItems.forEach(function(item) {
|
| + item._templateInstance.notifyPath(path, value, true);
|
| + }, this);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Called as a side effect of a host items.<key>.<path> path change,
|
| + * responsible for notifying item.<path> changes to row for key.
|
| + */
|
| + _forwardItemPath: function(path, value) {
|
| + if (this._physicalIndexForKey) {
|
| + var dot = path.indexOf('.');
|
| + var key = path.substring(0, dot < 0 ? path.length : dot);
|
| + var idx = this._physicalIndexForKey[key];
|
| + var row = this._physicalItems[idx];
|
| + if (row) {
|
| + var inst = row._templateInstance;
|
| + if (dot >= 0) {
|
| + path = this.as + '.' + path.substring(dot+1);
|
| + inst.notifyPath(path, value, true);
|
| + } else {
|
| + inst[this.as] = value;
|
| + }
|
| + }
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Called when the items have changed. That is, ressignments
|
| + * to `items`, splices or updates to a single item.
|
| + */
|
| + _itemsChanged: function(change) {
|
| + if (change.path === 'items') {
|
| + // render the new set
|
| + this._itemsRendered = false;
|
| +
|
| + // update the whole set
|
| + this._virtualStartVal = 0;
|
| + this._physicalTop = 0;
|
| + this._virtualCount = this.items ? this.items.length : 0;
|
| + this._collection = this.items ? Polymer.Collection.get(this.items) : null;
|
| + this._physicalIndexForKey = {};
|
| +
|
| + // scroll to the top
|
| + this._resetScrollPosition(0);
|
| +
|
| + // create the initial physical items
|
| + if (!this._physicalItems) {
|
| + this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, this._virtualCount));
|
| + this._physicalItems = this._createPool(this._physicalCount);
|
| + this._physicalSizes = new Array(this._physicalCount);
|
| + }
|
| +
|
| + this.debounce('refresh', this._render);
|
| +
|
| + } else if (change.path === 'items.splices') {
|
| + // render the new set
|
| + this._itemsRendered = false;
|
| +
|
| + this._adjustVirtualIndex(change.value.indexSplices);
|
| + this._virtualCount = this.items ? this.items.length : 0;
|
| +
|
| + this.debounce('refresh', this._render);
|
| +
|
| + } else {
|
| + // update a single item
|
| + this._forwardItemPath(change.path.split('.').slice(1).join('.'), change.value);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * @param {!Array<!PolymerSplice>} splices
|
| + */
|
| + _adjustVirtualIndex: function(splices) {
|
| + var i, splice, idx;
|
| +
|
| + for (i = 0; i < splices.length; i++) {
|
| + splice = splices[i];
|
| +
|
| + // deselect removed items
|
| + splice.removed.forEach(this.$.selector.deselect, this.$.selector);
|
| +
|
| + idx = splice.index;
|
| + // We only need to care about changes happening above the current position
|
| + if (idx >= this._virtualStartVal) {
|
| + break;
|
| + }
|
| +
|
| + this._virtualStart = this._virtualStart +
|
| + Math.max(splice.addedCount - splice.removed.length, idx - this._virtualStartVal);
|
| + }
|
| + },
|
| +
|
| + _scrollHandler: function() {
|
| + this._refresh();
|
| + },
|
| +
|
| + /**
|
| + * Executes a provided function per every physical index in `itemSet`
|
| + * `itemSet` default value is equivalent to the entire set of physical indexes.
|
| + *
|
| + * @param {!function(number, number)} fn
|
| + * @param {!Array<number>=} itemSet
|
| + */
|
| + _iterateItems: function(fn, itemSet) {
|
| + var pidx, vidx, rtn, i;
|
| +
|
| + if (arguments.length === 2 && itemSet) {
|
| + for (i = 0; i < itemSet.length; i++) {
|
| + pidx = itemSet[i];
|
| + if (pidx >= this._physicalStart) {
|
| + vidx = this._virtualStartVal + (pidx - this._physicalStart);
|
| + } else {
|
| + vidx = this._virtualStartVal + (this._physicalCount - this._physicalStart) + pidx;
|
| + }
|
| + if ((rtn = fn.call(this, pidx, vidx)) != null) {
|
| + return rtn;
|
| + }
|
| + }
|
| + } else {
|
| + pidx = this._physicalStart;
|
| + vidx = this._virtualStartVal;
|
| +
|
| + for (; pidx < this._physicalCount; pidx++, vidx++) {
|
| + if ((rtn = fn.call(this, pidx, vidx)) != null) {
|
| + return rtn;
|
| + }
|
| + }
|
| +
|
| + pidx = 0;
|
| +
|
| + for (; pidx < this._physicalStart; pidx++, vidx++) {
|
| + if ((rtn = fn.call(this, pidx, vidx)) != null) {
|
| + return rtn;
|
| + }
|
| + }
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Assigns the data models to a given set of items.
|
| + * @param {!Array<number>=} itemSet
|
| + */
|
| + _assignModels: function(itemSet) {
|
| + this._iterateItems(function(pidx, vidx) {
|
| + var el = this._physicalItems[pidx];
|
| + var inst = el._templateInstance;
|
| + var item = this.items && this.items[vidx];
|
| +
|
| + if (item) {
|
| + inst[this.as] = item;
|
| + inst.__key__ = this._collection.getKey(item);
|
| + inst[this.selectedAs] =
|
| + /** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item);
|
| + inst[this.indexAs] = vidx;
|
| + el.removeAttribute('hidden');
|
| + this._physicalIndexForKey[inst.__key__] = pidx;
|
| + } else {
|
| + inst.__key__ = null;
|
| + el.setAttribute('hidden', '');
|
| + }
|
| +
|
| + }, itemSet);
|
| + },
|
| +
|
| + /**
|
| + * Updates the height for a given set of items.
|
| + *
|
| + * @param {!Array<number>=} itemSet
|
| + */
|
| + _updateMetrics: function(itemSet) {
|
| + var newPhysicalSize = 0;
|
| + var oldPhysicalSize = 0;
|
| + var prevAvgCount = this._physicalAverageCount;
|
| + var prevPhysicalAvg = this._physicalAverage;
|
| + // Make sure we distributed all the physical items
|
| + // so we can measure them
|
| + Polymer.dom.flush();
|
| +
|
| + this._iterateItems(function(pidx, vidx) {
|
| + oldPhysicalSize += this._physicalSizes[pidx] || 0;
|
| + this._physicalSizes[pidx] = this._physicalItems[pidx].offsetHeight;
|
| + newPhysicalSize += this._physicalSizes[pidx];
|
| + this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0;
|
| + }, itemSet);
|
| +
|
| + this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalSize;
|
| + this._viewportSize = this._scroller.offsetHeight;
|
| +
|
| + // update the average if we measured something
|
| + if (this._physicalAverageCount !== prevAvgCount) {
|
| + this._physicalAverage = Math.round(
|
| + ((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) /
|
| + this._physicalAverageCount);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Updates the position of the physical items.
|
| + */
|
| + _positionItems: function() {
|
| + this._adjustScrollPosition();
|
| +
|
| + var y = this._physicalTop;
|
| +
|
| + this._iterateItems(function(pidx) {
|
| +
|
| + this.transform('translate3d(0, ' + y + 'px, 0)', this._physicalItems[pidx]);
|
| + y += this._physicalSizes[pidx];
|
| +
|
| + });
|
| + },
|
| +
|
| + /**
|
| + * Adjusts the scroll position when it was overestimated.
|
| + */
|
| + _adjustScrollPosition: function() {
|
| + var deltaHeight = this._virtualStartVal === 0 ? this._physicalTop :
|
| + Math.min(this._scrollPosition + this._physicalTop, 0);
|
| +
|
| + if (deltaHeight) {
|
| + this._physicalTop = this._physicalTop - deltaHeight;
|
| +
|
| + // juking scroll position during interial scrolling on iOS is no bueno
|
| + if (!IOS_TOUCH_SCROLLING) {
|
| + this._resetScrollPosition(this._scroller.scrollTop - deltaHeight);
|
| + }
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Sets the position of the scroll.
|
| + */
|
| + _resetScrollPosition: function(pos) {
|
| + if (this._scroller) {
|
| + this._scroller.scrollTop = pos;
|
| + this._scrollPosition = this._scroller.scrollTop;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Sets the scroll height, that's the height of the content,
|
| + *
|
| + * @param {boolean=} forceUpdate If true, updates the height no matter what.
|
| + */
|
| + _updateScrollerSize: function(forceUpdate) {
|
| + this._estScrollHeight = (this._physicalBottom +
|
| + Math.max(this._virtualCount - this._physicalCount - this._virtualStartVal, 0) * this._physicalAverage);
|
| +
|
| + forceUpdate = forceUpdate || this._scrollHeight === 0;
|
| + forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize;
|
| +
|
| + // amortize height adjustment, so it won't trigger repaints very often
|
| + if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._optPhysicalSize) {
|
| + this.$.items.style.height = this._estScrollHeight + 'px';
|
| + this._scrollHeight = this._estScrollHeight;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Scroll to a specific item in the virtual list regardless
|
| + * of the physical items in the DOM tree.
|
| + *
|
| + * @method scrollToIndex
|
| + * @param {number} idx The index of the item
|
| + */
|
| + scrollToIndex: function(idx) {
|
| + if (typeof idx !== 'number') {
|
| + return;
|
| + }
|
| +
|
| + var firstVisible = this.firstVisibleIndex;
|
| +
|
| + idx = Math.min(Math.max(idx, 0), this._virtualCount-1);
|
| +
|
| + // start at the previous virtual item
|
| + // so we have a item above the first visible item
|
| + this._virtualStart = idx - 1;
|
| +
|
| + // assign new models
|
| + this._assignModels();
|
| +
|
| + // measure the new sizes
|
| + this._updateMetrics();
|
| +
|
| + // estimate new physical offset
|
| + this._physicalTop = this._virtualStart * this._physicalAverage;
|
| +
|
| + var currentTopItem = this._physicalStart;
|
| + var currentVirtualItem = this._virtualStart;
|
| + var targetOffsetTop = 0;
|
| + var hiddenContentSize = this._hiddenContentSize;
|
| +
|
| + // scroll to the item as much as we can
|
| + while (currentVirtualItem !== idx && targetOffsetTop < hiddenContentSize) {
|
| + targetOffsetTop = targetOffsetTop + this._physicalSizes[currentTopItem];
|
| + currentTopItem = (currentTopItem + 1) % this._physicalCount;
|
| + currentVirtualItem++;
|
| + }
|
| +
|
| + // update the scroller size
|
| + this._updateScrollerSize(true);
|
| +
|
| + // update the position of the items
|
| + this._positionItems();
|
| +
|
| + // set the new scroll position
|
| + this._resetScrollPosition(this._physicalTop + targetOffsetTop + 1);
|
| +
|
| + // increase the pool of physical items if needed
|
| + if (this._increasePoolIfNeeded()) {
|
| + // set models to the new items
|
| + this.async(this._update);
|
| + }
|
| +
|
| + // clear cached visible index
|
| + this._firstVisibleIndexVal = null;
|
| + },
|
| +
|
| + /**
|
| + * Reset the physical average and the average count.
|
| + */
|
| + _resetAverage: function() {
|
| + this._physicalAverage = 0;
|
| + this._physicalAverageCount = 0;
|
| + },
|
| +
|
| + /**
|
| + * A handler for the `iron-resize` event triggered by `IronResizableBehavior`
|
| + * when the element is resized.
|
| + */
|
| + _resizeHandler: function() {
|
| + this.debounce('resize', function() {
|
| + this._render();
|
| + if (this._itemsRendered && this._physicalItems && this._isVisible) {
|
| + this._resetAverage();
|
| + this.updateViewportBoundaries();
|
| + this.scrollToIndex(this.firstVisibleIndex);
|
| + }
|
| + });
|
| + },
|
| +
|
| + _getModelFromItem: function(item) {
|
| + var key = this._collection.getKey(item);
|
| + var pidx = this._physicalIndexForKey[key];
|
| +
|
| + if (pidx !== undefined) {
|
| + return this._physicalItems[pidx]._templateInstance;
|
| + }
|
| + return null;
|
| + },
|
| +
|
| + /**
|
| + * Gets a valid item instance from its index or the object value.
|
| + *
|
| + * @param {(Object|number)} item The item object or its index
|
| + */
|
| + _getNormalizedItem: function(item) {
|
| + if (typeof item === 'number') {
|
| + item = this.items[item];
|
| + if (!item) {
|
| + throw new RangeError('<item> not found');
|
| + }
|
| + } else if (this._collection.getKey(item) === undefined) {
|
| + throw new TypeError('<item> should be a valid item');
|
| + }
|
| + return item;
|
| + },
|
| +
|
| + /**
|
| + * Select the list item at the given index.
|
| + *
|
| + * @method selectItem
|
| + * @param {(Object|number)} item The item object or its index
|
| + */
|
| + selectItem: function(item) {
|
| + item = this._getNormalizedItem(item);
|
| + var model = this._getModelFromItem(item);
|
| +
|
| + if (!this.multiSelection && this.selectedItem) {
|
| + this.deselectItem(this.selectedItem);
|
| + }
|
| + if (model) {
|
| + model[this.selectedAs] = true;
|
| + }
|
| + this.$.selector.select(item);
|
| + },
|
| +
|
| + /**
|
| + * Deselects the given item list if it is already selected.
|
| + *
|
| +
|
| + * @method deselect
|
| + * @param {(Object|number)} item The item object or its index
|
| + */
|
| + deselectItem: function(item) {
|
| + item = this._getNormalizedItem(item);
|
| + var model = this._getModelFromItem(item);
|
| +
|
| + if (model) {
|
| + model[this.selectedAs] = false;
|
| + }
|
| + this.$.selector.deselect(item);
|
| + },
|
| +
|
| + /**
|
| + * Select or deselect a given item depending on whether the item
|
| + * has already been selected.
|
| + *
|
| + * @method toggleSelectionForItem
|
| + * @param {(Object|number)} item The item object or its index
|
| + */
|
| + toggleSelectionForItem: function(item) {
|
| + item = this._getNormalizedItem(item);
|
| + if (/** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item)) {
|
| + this.deselectItem(item);
|
| + } else {
|
| + this.selectItem(item);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Clears the current selection state of the list.
|
| + *
|
| + * @method clearSelection
|
| + */
|
| + clearSelection: function() {
|
| + function unselect(item) {
|
| + var model = this._getModelFromItem(item);
|
| + if (model) {
|
| + model[this.selectedAs] = false;
|
| + }
|
| + }
|
| +
|
| + if (Array.isArray(this.selectedItems)) {
|
| + this.selectedItems.forEach(unselect, this);
|
| + } else if (this.selectedItem) {
|
| + unselect.call(this, this.selectedItem);
|
| + }
|
| +
|
| + /** @type {!ArraySelectorElement} */ (this.$.selector).clearSelection();
|
| + },
|
| +
|
| + /**
|
| + * Add an event listener to `tap` if `selectionEnabled` is true,
|
| + * it will remove the listener otherwise.
|
| + */
|
| + _selectionEnabledChanged: function(selectionEnabled) {
|
| + if (selectionEnabled) {
|
| + this.listen(this, 'tap', '_selectionHandler');
|
| + this.listen(this, 'keypress', '_selectionHandler');
|
| + } else {
|
| + this.unlisten(this, 'tap', '_selectionHandler');
|
| + this.unlisten(this, 'keypress', '_selectionHandler');
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Select an item from an event object.
|
| + */
|
| + _selectionHandler: function(e) {
|
| + if (e.type !== 'keypress' || e.keyCode === 13) {
|
| + var model = this.modelForElement(e.target);
|
| + if (model) {
|
| + this.toggleSelectionForItem(model[this.as]);
|
| + }
|
| + }
|
| + },
|
| +
|
| + _multiSelectionChanged: function(multiSelection) {
|
| + this.clearSelection();
|
| + this.$.selector.multi = multiSelection;
|
| + },
|
| +
|
| + /**
|
| + * Updates the size of an item.
|
| + *
|
| + * @method updateSizeForItem
|
| + * @param {(Object|number)} item The item object or its index
|
| + */
|
| + updateSizeForItem: function(item) {
|
| + item = this._getNormalizedItem(item);
|
| + var key = this._collection.getKey(item);
|
| + var pidx = this._physicalIndexForKey[key];
|
| +
|
| + if (pidx !== undefined) {
|
| + this._updateMetrics([pidx]);
|
| + this._positionItems();
|
| + }
|
| + }
|
| + });
|
| +
|
| +})();
|
| (function() {
|
|
|
| 'use strict';
|
| @@ -11318,14 +12587,6 @@ cr.define('downloads', function() {
|
| var Item = Polymer({
|
| is: 'downloads-item',
|
|
|
| - /**
|
| - * @param {!downloads.ThrottledIconLoader} iconLoader
|
| - */
|
| - factoryImpl: function(iconLoader) {
|
| - /** @private {!downloads.ThrottledIconLoader} */
|
| - this.iconLoader_ = iconLoader;
|
| - },
|
| -
|
| properties: {
|
| data: {
|
| type: Object,
|
| @@ -11336,15 +12597,6 @@ cr.define('downloads', function() {
|
| value: true,
|
| },
|
|
|
| - readyPromise: {
|
| - type: Object,
|
| - value: function() {
|
| - return new Promise(function(resolve, reject) {
|
| - this.resolveReadyPromise_ = resolve;
|
| - }.bind(this));
|
| - },
|
| - },
|
| -
|
| completelyOnDisk_: {
|
| computed: 'computeCompletelyOnDisk_(' +
|
| 'data.state, data.file_externally_removed)',
|
| @@ -11417,21 +12669,11 @@ cr.define('downloads', function() {
|
| // TODO(dbeam): this gets called way more when I observe data.by_ext_id
|
| // and data.by_ext_name directly. Why?
|
| 'observeControlledBy_(controlledBy_)',
|
| + 'observeIsDangerous_(isDangerous_, data.file_path)',
|
| ],
|
|
|
| ready: function() {
|
| this.content = this.$.content;
|
| - this.resolveReadyPromise_();
|
| - },
|
| -
|
| - /** @param {!downloads.Data} data */
|
| - update: function(data) {
|
| - this.data = data;
|
| -
|
| - if (!this.isDangerous_) {
|
| - var icon = 'chrome://fileicon/' + encodeURIComponent(data.file_path);
|
| - this.iconLoader_.loadScaledIcon(this.$['file-icon'], icon);
|
| - }
|
| },
|
|
|
| /** @private */
|
| @@ -11592,6 +12834,14 @@ cr.define('downloads', function() {
|
| },
|
|
|
| /** @private */
|
| + observeIsDangerous_: function() {
|
| + if (this.data && !this.isDangerous_) {
|
| + var filePath = encodeURIComponent(this.data.file_path);
|
| + this.$['file-icon'].src = 'chrome://fileicon/' + filePath;
|
| + }
|
| + },
|
| +
|
| + /** @private */
|
| onCancelTap_: function() {
|
| downloads.ActionService.getInstance().cancel(this.data.id);
|
| },
|
| @@ -12496,184 +13746,6 @@ Polymer({
|
|
|
| })();
|
| /**
|
| - * `IronResizableBehavior` is a behavior that can be used in Polymer elements to
|
| - * coordinate the flow of resize events between "resizers" (elements that control the
|
| - * size or hidden state of their children) and "resizables" (elements that need to be
|
| - * notified when they are resized or un-hidden by their parents in order to take
|
| - * action on their new measurements).
|
| - * Elements that perform measurement should add the `IronResizableBehavior` behavior to
|
| - * their element definition and listen for the `iron-resize` event on themselves.
|
| - * This event will be fired when they become showing after having been hidden,
|
| - * when they are resized explicitly by another resizable, or when the window has been
|
| - * resized.
|
| - * Note, the `iron-resize` event is non-bubbling.
|
| - *
|
| - * @polymerBehavior Polymer.IronResizableBehavior
|
| - * @demo demo/index.html
|
| - **/
|
| - Polymer.IronResizableBehavior = {
|
| - properties: {
|
| - /**
|
| - * The closest ancestor element that implements `IronResizableBehavior`.
|
| - */
|
| - _parentResizable: {
|
| - type: Object,
|
| - observer: '_parentResizableChanged'
|
| - },
|
| -
|
| - /**
|
| - * True if this element is currently notifying its descedant elements of
|
| - * resize.
|
| - */
|
| - _notifyingDescendant: {
|
| - type: Boolean,
|
| - value: false
|
| - }
|
| - },
|
| -
|
| - listeners: {
|
| - 'iron-request-resize-notifications': '_onIronRequestResizeNotifications'
|
| - },
|
| -
|
| - created: function() {
|
| - // We don't really need property effects on these, and also we want them
|
| - // to be created before the `_parentResizable` observer fires:
|
| - this._interestedResizables = [];
|
| - this._boundNotifyResize = this.notifyResize.bind(this);
|
| - },
|
| -
|
| - attached: function() {
|
| - this.fire('iron-request-resize-notifications', null, {
|
| - node: this,
|
| - bubbles: true,
|
| - cancelable: true
|
| - });
|
| -
|
| - if (!this._parentResizable) {
|
| - window.addEventListener('resize', this._boundNotifyResize);
|
| - this.notifyResize();
|
| - }
|
| - },
|
| -
|
| - detached: function() {
|
| - if (this._parentResizable) {
|
| - this._parentResizable.stopResizeNotificationsFor(this);
|
| - } else {
|
| - window.removeEventListener('resize', this._boundNotifyResize);
|
| - }
|
| -
|
| - this._parentResizable = null;
|
| - },
|
| -
|
| - /**
|
| - * Can be called to manually notify a resizable and its descendant
|
| - * resizables of a resize change.
|
| - */
|
| - notifyResize: function() {
|
| - if (!this.isAttached) {
|
| - return;
|
| - }
|
| -
|
| - this._interestedResizables.forEach(function(resizable) {
|
| - if (this.resizerShouldNotify(resizable)) {
|
| - this._notifyDescendant(resizable);
|
| - }
|
| - }, this);
|
| -
|
| - this._fireResize();
|
| - },
|
| -
|
| - /**
|
| - * Used to assign the closest resizable ancestor to this resizable
|
| - * if the ancestor detects a request for notifications.
|
| - */
|
| - assignParentResizable: function(parentResizable) {
|
| - this._parentResizable = parentResizable;
|
| - },
|
| -
|
| - /**
|
| - * Used to remove a resizable descendant from the list of descendants
|
| - * that should be notified of a resize change.
|
| - */
|
| - stopResizeNotificationsFor: function(target) {
|
| - var index = this._interestedResizables.indexOf(target);
|
| -
|
| - if (index > -1) {
|
| - this._interestedResizables.splice(index, 1);
|
| - this.unlisten(target, 'iron-resize', '_onDescendantIronResize');
|
| - }
|
| - },
|
| -
|
| - /**
|
| - * This method can be overridden to filter nested elements that should or
|
| - * should not be notified by the current element. Return true if an element
|
| - * should be notified, or false if it should not be notified.
|
| - *
|
| - * @param {HTMLElement} element A candidate descendant element that
|
| - * implements `IronResizableBehavior`.
|
| - * @return {boolean} True if the `element` should be notified of resize.
|
| - */
|
| - resizerShouldNotify: function(element) { return true; },
|
| -
|
| - _onDescendantIronResize: function(event) {
|
| - if (this._notifyingDescendant) {
|
| - event.stopPropagation();
|
| - return;
|
| - }
|
| -
|
| - // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the
|
| - // otherwise non-bubbling event "just work." We do it manually here for
|
| - // the case where Polymer is not using shadow roots for whatever reason:
|
| - if (!Polymer.Settings.useShadow) {
|
| - this._fireResize();
|
| - }
|
| - },
|
| -
|
| - _fireResize: function() {
|
| - this.fire('iron-resize', null, {
|
| - node: this,
|
| - bubbles: false
|
| - });
|
| - },
|
| -
|
| - _onIronRequestResizeNotifications: function(event) {
|
| - var target = event.path ? event.path[0] : event.target;
|
| -
|
| - if (target === this) {
|
| - return;
|
| - }
|
| -
|
| - if (this._interestedResizables.indexOf(target) === -1) {
|
| - this._interestedResizables.push(target);
|
| - this.listen(target, 'iron-resize', '_onDescendantIronResize');
|
| - }
|
| -
|
| - target.assignParentResizable(this);
|
| - this._notifyDescendant(target);
|
| -
|
| - event.stopPropagation();
|
| - },
|
| -
|
| - _parentResizableChanged: function(parentResizable) {
|
| - if (parentResizable) {
|
| - window.removeEventListener('resize', this._boundNotifyResize);
|
| - }
|
| - },
|
| -
|
| - _notifyDescendant: function(descendant) {
|
| - // NOTE(cdata): In IE10, attached is fired on children first, so it's
|
| - // important not to notify them if the parent is not attached yet (or
|
| - // else they will get redundantly notified when the parent attaches).
|
| - if (!this.isAttached) {
|
| - return;
|
| - }
|
| -
|
| - this._notifyingDescendant = true;
|
| - descendant.notifyResize();
|
| - this._notifyingDescendant = false;
|
| - }
|
| - };
|
| -/**
|
| Polymer.IronFitBehavior fits an element in another element using `max-height` and `max-width`, and
|
| optionally centers it in the window or another element.
|
|
|
| @@ -15692,15 +16764,10 @@ cr.define('downloads', function() {
|
| type: Boolean,
|
| value: false,
|
| },
|
| - },
|
|
|
| - /**
|
| - * @return {number} A guess at how many items could be visible at once.
|
| - * @private
|
| - */
|
| - guesstimateNumberOfVisibleItems_: function() {
|
| - var toolbarHeight = this.$.toolbar.offsetHeight;
|
| - return Math.floor((window.innerHeight - toolbarHeight) / 46) + 1;
|
| + items_: {
|
| + type: Array,
|
| + },
|
| },
|
|
|
| /**
|
| @@ -15740,33 +16807,6 @@ cr.define('downloads', function() {
|
| downloads.ActionService.getInstance().search('');
|
| },
|
|
|
| - /** @private */
|
| - rebuildFocusGrid_: function() {
|
| - var activeElement = this.shadowRoot.activeElement;
|
| -
|
| - var activeItem;
|
| - if (activeElement && activeElement.tagName == 'downloads-item')
|
| - activeItem = activeElement;
|
| -
|
| - var activeControl = activeItem && activeItem.shadowRoot.activeElement;
|
| -
|
| - /** @private {!cr.ui.FocusGrid} */
|
| - this.focusGrid_ = this.focusGrid_ || new cr.ui.FocusGrid;
|
| - this.focusGrid_.destroy();
|
| -
|
| - var boundary = this.$['downloads-list'];
|
| -
|
| - this.items_.forEach(function(item) {
|
| - var focusRow = new downloads.FocusRow(item.content, boundary);
|
| - this.focusGrid_.addRow(focusRow);
|
| -
|
| - if (item == activeItem && !cr.ui.FocusRow.isFocusable(activeControl))
|
| - focusRow.getEquivalentElement(activeControl).focus();
|
| - }, this);
|
| -
|
| - this.focusGrid_.ensureRowActive();
|
| - },
|
| -
|
| /**
|
| * @return {number} The number of downloads shown on the page.
|
| * @private
|
| @@ -15781,62 +16821,24 @@ cr.define('downloads', function() {
|
| * @private
|
| */
|
| updateAll_: function(list) {
|
| - var oldIdMap = this.idMap_ || {};
|
| -
|
| - /** @private {!Object<!downloads.Item>} */
|
| - this.idMap_ = {};
|
| -
|
| - /** @private {!Array<!downloads.Item>} */
|
| - this.items_ = [];
|
| -
|
| - if (!this.iconLoader_) {
|
| - var guesstimate = Math.max(this.guesstimateNumberOfVisibleItems_(), 1);
|
| - /** @private {downloads.ThrottledIconLoader} */
|
| - this.iconLoader_ = new downloads.ThrottledIconLoader(guesstimate);
|
| - }
|
| + /** @private {!Object<number>} */
|
| + this.idToIndex_ = {};
|
|
|
| for (var i = 0; i < list.length; ++i) {
|
| var data = list[i];
|
| - var id = data.id;
|
| -
|
| - // Re-use old items when possible (saves work, preserves focus).
|
| - var item = oldIdMap[id] || new downloads.Item(this.iconLoader_);
|
|
|
| - this.idMap_[id] = item; // Associated by ID for fast lookup.
|
| - this.items_.push(item); // Add to sorted list for order.
|
| + this.idToIndex_[data.id] = data.index = i;
|
|
|
| - // Render |item| but don't actually add to the DOM yet. |this.items_|
|
| - // must be fully created to be able to find the right spot to insert.
|
| - item.update(data);
|
| -
|
| - // Collapse redundant dates.
|
| var prev = list[i - 1];
|
| - item.hideDate = !!prev && prev.date_string == data.date_string;
|
| -
|
| - delete oldIdMap[id];
|
| + data.hideDate = !!prev && prev.date_string == data.date_string;
|
| }
|
|
|
| - // Remove stale, previously rendered items from the DOM.
|
| - for (var id in oldIdMap) {
|
| - if (oldIdMap[id].parentNode)
|
| - oldIdMap[id].parentNode.removeChild(oldIdMap[id]);
|
| - delete oldIdMap[id];
|
| - }
|
| -
|
| - for (var i = 0; i < this.items_.length; ++i) {
|
| - var item = this.items_[i];
|
| - if (item.parentNode) // Already in the DOM; skip.
|
| - continue;
|
| -
|
| - var before = null;
|
| - // Find the next rendered item after this one, and insert before it.
|
| - for (var j = i + 1; !before && j < this.items_.length; ++j) {
|
| - if (this.items_[j].parentNode)
|
| - before = this.items_[j];
|
| - }
|
| - // If |before| is null, |item| will just get added at the end.
|
| - this.$['downloads-list'].insertBefore(item, before);
|
| - }
|
| + // TODO(dbeam): this resets the scroll position, which is a huge bummer.
|
| + // Removing something from the bottom of the list should not scroll you
|
| + // back to the top. The grand plan is to restructure how the C++ sends the
|
| + // JS data so that it only gets updates (rather than the most recent set
|
| + // of items). TL;DR - we can't ship with this bug.
|
| + this.items_ = list;
|
|
|
| var hasDownloads = this.size_() > 0;
|
| if (!hasDownloads) {
|
| @@ -15851,9 +16853,6 @@ cr.define('downloads', function() {
|
| this.$.toolbar.downloadsShowing = this.hasDownloads_;
|
|
|
| this.$.panel.classList.remove('loading');
|
| -
|
| - var allReady = this.items_.map(function(i) { return i.readyPromise; });
|
| - Promise.all(allReady).then(this.rebuildFocusGrid_.bind(this));
|
| },
|
|
|
| /**
|
| @@ -15861,19 +16860,9 @@ cr.define('downloads', function() {
|
| * @private
|
| */
|
| updateItem_: function(data) {
|
| - var item = this.idMap_[data.id];
|
| -
|
| - var activeControl = this.shadowRoot.activeElement == item ?
|
| - item.shadowRoot.activeElement : null;
|
| -
|
| - item.update(data);
|
| -
|
| - this.async(function() {
|
| - if (activeControl && !cr.ui.FocusRow.isFocusable(activeControl)) {
|
| - var focusRow = this.focusGrid_.getRowForRoot(item.content);
|
| - focusRow.getEquivalentElement(activeControl).focus();
|
| - }
|
| - }.bind(this));
|
| + var index = this.idToIndex_[data.id];
|
| + this.set('items_.' + index, data);
|
| + this.$['downloads-list'].updateSizeForItem(index);
|
| },
|
| });
|
|
|
|
|