Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(270)

Side by Side Diff: chrome/browser/resources/md_downloads/crisper.js

Issue 1375333004: MD Downloads: use <iron-list> to render download items (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@iron-list2
Patch Set: merge Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 Polymer = {dom: 'shadow'}; 5 Polymer = {dom: 'shadow'};
6 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 6 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
7 // Use of this source code is governed by a BSD-style license that can be 7 // Use of this source code is governed by a BSD-style license that can be
8 // found in the LICENSE file. 8 // found in the LICENSE file.
9 9
10 /** 10 /**
(...skipping 2335 matching lines...) Expand 10 before | Expand all | Expand 10 after
2346 removeAttribute: function(attr) { 2346 removeAttribute: function(attr) {
2347 if (attr.toLowerCase() == 'disabled') 2347 if (attr.toLowerCase() == 'disabled')
2348 this.disabled = false; 2348 this.disabled = false;
2349 else 2349 else
2350 HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments); 2350 HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments);
2351 }, 2351 },
2352 }, 2352 },
2353 2353
2354 extends: 'a', 2354 extends: 'a',
2355 }); 2355 });
2356 // Copyright 2015 The Chromium Authors. All rights reserved.
2357 // Use of this source code is governed by a BSD-style license that can be
2358 // found in the LICENSE file.
2359
2360 /** @typedef {{img: HTMLImageElement, url: string}} */
2361 var LoadIconRequest;
2362
2363 cr.define('downloads', function() {
2364 /**
2365 * @param {number} maxAllowed The maximum number of simultaneous downloads
2366 * allowed.
2367 * @constructor
2368 */
2369 function ThrottledIconLoader(maxAllowed) {
2370 assert(maxAllowed > 0);
2371
2372 /** @private {number} */
2373 this.maxAllowed_ = maxAllowed;
2374
2375 /** @private {!Array<!LoadIconRequest>} */
2376 this.requests_ = [];
2377 }
2378
2379 ThrottledIconLoader.prototype = {
2380 /** @private {number} */
2381 loading_: 0,
2382
2383 /**
2384 * Load the provided |url| into |img.src| after appending ?scale=.
2385 * @param {!HTMLImageElement} img An <img> to show the loaded image in.
2386 * @param {string} url A remote image URL to load.
2387 */
2388 loadScaledIcon: function(img, url) {
2389 var scaledUrl = url + '?scale=' + window.devicePixelRatio + 'x';
2390 if (img.src == scaledUrl)
2391 return;
2392
2393 this.requests_.push({img: img, url: scaledUrl});
2394 this.loadNextIcon_();
2395 },
2396
2397 /** @private */
2398 loadNextIcon_: function() {
2399 if (this.loading_ > this.maxAllowed_ || !this.requests_.length)
2400 return;
2401
2402 var request = this.requests_.shift();
2403 var img = request.img;
2404
2405 img.onabort = img.onerror = img.onload = function() {
2406 this.loading_--;
2407 this.loadNextIcon_();
2408 }.bind(this);
2409
2410 this.loading_++;
2411 img.src = request.url;
2412 },
2413 };
2414
2415 return {ThrottledIconLoader: ThrottledIconLoader};
2416 });
2417 // Copyright 2014 Google Inc. All rights reserved. 2356 // Copyright 2014 Google Inc. All rights reserved.
2418 // 2357 //
2419 // Licensed under the Apache License, Version 2.0 (the "License"); 2358 // Licensed under the Apache License, Version 2.0 (the "License");
2420 // you may not use this file except in compliance with the License. 2359 // you may not use this file except in compliance with the License.
2421 // You may obtain a copy of the License at 2360 // You may obtain a copy of the License at
2422 // 2361 //
2423 // http://www.apache.org/licenses/LICENSE-2.0 2362 // http://www.apache.org/licenses/LICENSE-2.0
2424 // 2363 //
2425 // Unless required by applicable law or agreed to in writing, software 2364 // Unless required by applicable law or agreed to in writing, software
2426 // distributed under the License is distributed on an "AS IS" BASIS, 2365 // distributed under the License is distributed on an "AS IS" BASIS,
(...skipping 6374 matching lines...) Expand 10 before | Expand all | Expand 10 after
8801 this._prepBehaviors(); 8740 this._prepBehaviors();
8802 this._prepConfigure(); 8741 this._prepConfigure();
8803 this._prepBindings(); 8742 this._prepBindings();
8804 Polymer.Base._initFeatures.call(this); 8743 Polymer.Base._initFeatures.call(this);
8805 this._children = Array.prototype.slice.call(this.root.childNodes); 8744 this._children = Array.prototype.slice.call(this.root.childNodes);
8806 } 8745 }
8807 this._insertChildren(); 8746 this._insertChildren();
8808 this.fire('dom-change'); 8747 this.fire('dom-change');
8809 } 8748 }
8810 }); 8749 });
8750 /**
8751 * `IronResizableBehavior` is a behavior that can be used in Polymer elements to
8752 * coordinate the flow of resize events between "resizers" (elements that cont rol the
8753 * size or hidden state of their children) and "resizables" (elements that nee d to be
8754 * notified when they are resized or un-hidden by their parents in order to ta ke
8755 * action on their new measurements).
8756 * Elements that perform measurement should add the `IronResizableBehavior` be havior to
8757 * their element definition and listen for the `iron-resize` event on themselv es.
8758 * This event will be fired when they become showing after having been hidden,
8759 * when they are resized explicitly by another resizable, or when the window h as been
8760 * resized.
8761 * Note, the `iron-resize` event is non-bubbling.
8762 *
8763 * @polymerBehavior Polymer.IronResizableBehavior
8764 * @demo demo/index.html
8765 **/
8766 Polymer.IronResizableBehavior = {
8767 properties: {
8768 /**
8769 * The closest ancestor element that implements `IronResizableBehavior`.
8770 */
8771 _parentResizable: {
8772 type: Object,
8773 observer: '_parentResizableChanged'
8774 },
8775
8776 /**
8777 * True if this element is currently notifying its descedant elements of
8778 * resize.
8779 */
8780 _notifyingDescendant: {
8781 type: Boolean,
8782 value: false
8783 }
8784 },
8785
8786 listeners: {
8787 'iron-request-resize-notifications': '_onIronRequestResizeNotifications'
8788 },
8789
8790 created: function() {
8791 // We don't really need property effects on these, and also we want them
8792 // to be created before the `_parentResizable` observer fires:
8793 this._interestedResizables = [];
8794 this._boundNotifyResize = this.notifyResize.bind(this);
8795 },
8796
8797 attached: function() {
8798 this.fire('iron-request-resize-notifications', null, {
8799 node: this,
8800 bubbles: true,
8801 cancelable: true
8802 });
8803
8804 if (!this._parentResizable) {
8805 window.addEventListener('resize', this._boundNotifyResize);
8806 this.notifyResize();
8807 }
8808 },
8809
8810 detached: function() {
8811 if (this._parentResizable) {
8812 this._parentResizable.stopResizeNotificationsFor(this);
8813 } else {
8814 window.removeEventListener('resize', this._boundNotifyResize);
8815 }
8816
8817 this._parentResizable = null;
8818 },
8819
8820 /**
8821 * Can be called to manually notify a resizable and its descendant
8822 * resizables of a resize change.
8823 */
8824 notifyResize: function() {
8825 if (!this.isAttached) {
8826 return;
8827 }
8828
8829 this._interestedResizables.forEach(function(resizable) {
8830 if (this.resizerShouldNotify(resizable)) {
8831 this._notifyDescendant(resizable);
8832 }
8833 }, this);
8834
8835 this._fireResize();
8836 },
8837
8838 /**
8839 * Used to assign the closest resizable ancestor to this resizable
8840 * if the ancestor detects a request for notifications.
8841 */
8842 assignParentResizable: function(parentResizable) {
8843 this._parentResizable = parentResizable;
8844 },
8845
8846 /**
8847 * Used to remove a resizable descendant from the list of descendants
8848 * that should be notified of a resize change.
8849 */
8850 stopResizeNotificationsFor: function(target) {
8851 var index = this._interestedResizables.indexOf(target);
8852
8853 if (index > -1) {
8854 this._interestedResizables.splice(index, 1);
8855 this.unlisten(target, 'iron-resize', '_onDescendantIronResize');
8856 }
8857 },
8858
8859 /**
8860 * This method can be overridden to filter nested elements that should or
8861 * should not be notified by the current element. Return true if an element
8862 * should be notified, or false if it should not be notified.
8863 *
8864 * @param {HTMLElement} element A candidate descendant element that
8865 * implements `IronResizableBehavior`.
8866 * @return {boolean} True if the `element` should be notified of resize.
8867 */
8868 resizerShouldNotify: function(element) { return true; },
8869
8870 _onDescendantIronResize: function(event) {
8871 if (this._notifyingDescendant) {
8872 event.stopPropagation();
8873 return;
8874 }
8875
8876 // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the
8877 // otherwise non-bubbling event "just work." We do it manually here for
8878 // the case where Polymer is not using shadow roots for whatever reason:
8879 if (!Polymer.Settings.useShadow) {
8880 this._fireResize();
8881 }
8882 },
8883
8884 _fireResize: function() {
8885 this.fire('iron-resize', null, {
8886 node: this,
8887 bubbles: false
8888 });
8889 },
8890
8891 _onIronRequestResizeNotifications: function(event) {
8892 var target = event.path ? event.path[0] : event.target;
8893
8894 if (target === this) {
8895 return;
8896 }
8897
8898 if (this._interestedResizables.indexOf(target) === -1) {
8899 this._interestedResizables.push(target);
8900 this.listen(target, 'iron-resize', '_onDescendantIronResize');
8901 }
8902
8903 target.assignParentResizable(this);
8904 this._notifyDescendant(target);
8905
8906 event.stopPropagation();
8907 },
8908
8909 _parentResizableChanged: function(parentResizable) {
8910 if (parentResizable) {
8911 window.removeEventListener('resize', this._boundNotifyResize);
8912 }
8913 },
8914
8915 _notifyDescendant: function(descendant) {
8916 // NOTE(cdata): In IE10, attached is fired on children first, so it's
8917 // important not to notify them if the parent is not attached yet (or
8918 // else they will get redundantly notified when the parent attaches).
8919 if (!this.isAttached) {
8920 return;
8921 }
8922
8923 this._notifyingDescendant = true;
8924 descendant.notifyResize();
8925 this._notifyingDescendant = false;
8926 }
8927 };
8811 (function() { 8928 (function() {
8812 8929
8930 var IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/);
8931 var IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8;
8932 var DEFAULT_PHYSICAL_COUNT = 20;
8933 var MAX_PHYSICAL_COUNT = 500;
8934
8935 Polymer({
8936
8937 is: 'iron-list',
8938
8939 properties: {
8940
8941 /**
8942 * An array containing items determining how many instances of the templat e
8943 * to stamp and that that each template instance should bind to.
8944 */
8945 items: {
8946 type: Array
8947 },
8948
8949 /**
8950 * The name of the variable to add to the binding scope for the array
8951 * element associated with a given template instance.
8952 */
8953 as: {
8954 type: String,
8955 value: 'item'
8956 },
8957
8958 /**
8959 * The name of the variable to add to the binding scope with the index
8960 * for the row. If `sort` is provided, the index will reflect the
8961 * sorted order (rather than the original array order).
8962 */
8963 indexAs: {
8964 type: String,
8965 value: 'index'
8966 },
8967
8968 /**
8969 * The name of the variable to add to the binding scope to indicate
8970 * if the row is selected.
8971 */
8972 selectedAs: {
8973 type: String,
8974 value: 'selected'
8975 },
8976
8977 /**
8978 * When true, tapping a row will select the item, placing its data model
8979 * in the set of selected items retrievable via the selection property.
8980 *
8981 * Note that tapping focusable elements within the list item will not
8982 * result in selection, since they are presumed to have their * own action .
8983 */
8984 selectionEnabled: {
8985 type: Boolean,
8986 value: false
8987 },
8988
8989 /**
8990 * When `multiSelection` is false, this is the currently selected item, or `null`
8991 * if no item is selected.
8992 */
8993 selectedItem: {
8994 type: Object,
8995 notify: true
8996 },
8997
8998 /**
8999 * When `multiSelection` is true, this is an array that contains the selec ted items.
9000 */
9001 selectedItems: {
9002 type: Object,
9003 notify: true
9004 },
9005
9006 /**
9007 * When `true`, multiple items may be selected at once (in this case,
9008 * `selected` is an array of currently selected items). When `false`,
9009 * only one item may be selected at a time.
9010 */
9011 multiSelection: {
9012 type: Boolean,
9013 value: false
9014 }
9015 },
9016
9017 observers: [
9018 '_itemsChanged(items.*)',
9019 '_selectionEnabledChanged(selectionEnabled)',
9020 '_multiSelectionChanged(multiSelection)'
9021 ],
9022
9023 behaviors: [
9024 Polymer.Templatizer,
9025 Polymer.IronResizableBehavior
9026 ],
9027
9028 listeners: {
9029 'iron-resize': '_resizeHandler'
9030 },
9031
9032 /**
9033 * The ratio of hidden tiles that should remain in the scroll direction.
9034 * Recommended value ~0.5, so it will distribute tiles evely in both directi ons.
9035 */
9036 _ratio: 0.5,
9037
9038 /**
9039 * The element that controls the scroll
9040 * @type {?Element}
9041 */
9042 _scroller: null,
9043
9044 /**
9045 * The padding-top value of the `scroller` element
9046 */
9047 _scrollerPaddingTop: 0,
9048
9049 /**
9050 * This value is the same as `scrollTop`.
9051 */
9052 _scrollPosition: 0,
9053
9054 /**
9055 * The number of tiles in the DOM.
9056 */
9057 _physicalCount: 0,
9058
9059 /**
9060 * The k-th tile that is at the top of the scrolling list.
9061 */
9062 _physicalStart: 0,
9063
9064 /**
9065 * The k-th tile that is at the bottom of the scrolling list.
9066 */
9067 _physicalEnd: 0,
9068
9069 /**
9070 * The sum of the heights of all the tiles in the DOM.
9071 */
9072 _physicalSize: 0,
9073
9074 /**
9075 * The average `offsetHeight` of the tiles observed till now.
9076 */
9077 _physicalAverage: 0,
9078
9079 /**
9080 * The number of tiles which `offsetHeight` > 0 observed until now.
9081 */
9082 _physicalAverageCount: 0,
9083
9084 /**
9085 * The Y position of the item rendered in the `_physicalStart`
9086 * tile relative to the scrolling list.
9087 */
9088 _physicalTop: 0,
9089
9090 /**
9091 * The number of items in the list.
9092 */
9093 _virtualCount: 0,
9094
9095 /**
9096 * The n-th item rendered in the `_physicalStart` tile.
9097 */
9098 _virtualStartVal: 0,
9099
9100 /**
9101 * A map between an item key and its physical item index
9102 */
9103 _physicalIndexForKey: null,
9104
9105 /**
9106 * The estimated scroll height based on `_physicalAverage`
9107 */
9108 _estScrollHeight: 0,
9109
9110 /**
9111 * The scroll height of the dom node
9112 */
9113 _scrollHeight: 0,
9114
9115 /**
9116 * The size of the viewport
9117 */
9118 _viewportSize: 0,
9119
9120 /**
9121 * An array of DOM nodes that are currently in the tree
9122 * @type {?Array<!TemplatizerNode>}
9123 */
9124 _physicalItems: null,
9125
9126 /**
9127 * An array of heights for each item in `_physicalItems`
9128 * @type {?Array<number>}
9129 */
9130 _physicalSizes: null,
9131
9132 /**
9133 * A cached value for the visible index.
9134 * See `firstVisibleIndex`
9135 * @type {?number}
9136 */
9137 _firstVisibleIndexVal: null,
9138
9139 /**
9140 * A Polymer collection for the items.
9141 * @type {?Polymer.Collection}
9142 */
9143 _collection: null,
9144
9145 /**
9146 * True if the current item list was rendered for the first time
9147 * after attached.
9148 */
9149 _itemsRendered: false,
9150
9151 /**
9152 * The bottom of the physical content.
9153 */
9154 get _physicalBottom() {
9155 return this._physicalTop + this._physicalSize;
9156 },
9157
9158 /**
9159 * The n-th item rendered in the last physical item.
9160 */
9161 get _virtualEnd() {
9162 return this._virtualStartVal + this._physicalCount - 1;
9163 },
9164
9165 /**
9166 * The lowest n-th value for an item such that it can be rendered in `_physi calStart`.
9167 */
9168 _minVirtualStart: 0,
9169
9170 /**
9171 * The largest n-th value for an item such that it can be rendered in `_phys icalStart`.
9172 */
9173 get _maxVirtualStart() {
9174 return this._virtualCount < this._physicalCount ?
9175 this._virtualCount : this._virtualCount - this._physicalCount;
9176 },
9177
9178 /**
9179 * The height of the physical content that isn't on the screen.
9180 */
9181 get _hiddenContentSize() {
9182 return this._physicalSize - this._viewportSize;
9183 },
9184
9185 /**
9186 * The maximum scroll top value.
9187 */
9188 get _maxScrollTop() {
9189 return this._estScrollHeight - this._viewportSize;
9190 },
9191
9192 /**
9193 * Sets the n-th item rendered in `_physicalStart`
9194 */
9195 set _virtualStart(val) {
9196 // clamp the value so that _minVirtualStart <= val <= _maxVirtualStart
9197 this._virtualStartVal = Math.min(this._maxVirtualStart, Math.max(this._min VirtualStart, val));
9198 this._physicalStart = this._virtualStartVal % this._physicalCount;
9199 this._physicalEnd = (this._physicalStart + this._physicalCount - 1) % this ._physicalCount;
9200 },
9201
9202 /**
9203 * Gets the n-th item rendered in `_physicalStart`
9204 */
9205 get _virtualStart() {
9206 return this._virtualStartVal;
9207 },
9208
9209 /**
9210 * An optimal physical size such that we will have enough physical items
9211 * to fill up the viewport and recycle when the user scrolls.
9212 *
9213 * This default value assumes that we will at least have the equivalent
9214 * to a viewport of physical items above and below the user's viewport.
9215 */
9216 get _optPhysicalSize() {
9217 return this._viewportSize * 3;
9218 },
9219
9220 /**
9221 * True if the current list is visible.
9222 */
9223 get _isVisible() {
9224 return this._scroller && Boolean(this._scroller.offsetWidth || this._scrol ler.offsetHeight);
9225 },
9226
9227 /**
9228 * Gets the first visible item in the viewport.
9229 *
9230 * @type {number}
9231 */
9232 get firstVisibleIndex() {
9233 var physicalOffset;
9234
9235 if (this._firstVisibleIndexVal === null) {
9236 physicalOffset = this._physicalTop;
9237
9238 this._firstVisibleIndexVal = this._iterateItems(
9239 function(pidx, vidx) {
9240 physicalOffset += this._physicalSizes[pidx];
9241
9242 if (physicalOffset > this._scrollPosition) {
9243 return vidx;
9244 }
9245 }) || 0;
9246 }
9247
9248 return this._firstVisibleIndexVal;
9249 },
9250
9251 ready: function() {
9252 if (IOS_TOUCH_SCROLLING) {
9253 this._scrollListener = function() {
9254 requestAnimationFrame(this._scrollHandler.bind(this));
9255 }.bind(this);
9256 } else {
9257 this._scrollListener = this._scrollHandler.bind(this);
9258 }
9259 },
9260
9261 /**
9262 * When the element has been attached to the DOM tree.
9263 */
9264 attached: function() {
9265 // delegate to the parent's scroller
9266 // e.g. paper-scroll-header-panel
9267 var el = Polymer.dom(this);
9268
9269 var parentNode = /** @type {?{scroller: ?Element}} */ (el.parentNode);
9270 if (parentNode && parentNode.scroller) {
9271 this._scroller = parentNode.scroller;
9272 } else {
9273 this._scroller = this;
9274 this.classList.add('has-scroller');
9275 }
9276
9277 if (IOS_TOUCH_SCROLLING) {
9278 this._scroller.style.webkitOverflowScrolling = 'touch';
9279 }
9280
9281 this._scroller.addEventListener('scroll', this._scrollListener);
9282
9283 this.updateViewportBoundaries();
9284 this._render();
9285 },
9286
9287 /**
9288 * When the element has been removed from the DOM tree.
9289 */
9290 detached: function() {
9291 this._itemsRendered = false;
9292 if (this._scroller) {
9293 this._scroller.removeEventListener('scroll', this._scrollListener);
9294 }
9295 },
9296
9297 /**
9298 * Invoke this method if you dynamically update the viewport's
9299 * size or CSS padding.
9300 *
9301 * @method updateViewportBoundaries
9302 */
9303 updateViewportBoundaries: function() {
9304 var scrollerStyle = window.getComputedStyle(this._scroller);
9305 this._scrollerPaddingTop = parseInt(scrollerStyle['padding-top'], 10);
9306 this._viewportSize = this._scroller.offsetHeight;
9307 },
9308
9309 /**
9310 * Update the models, the position of the
9311 * items in the viewport and recycle tiles as needed.
9312 */
9313 _refresh: function() {
9314 var SCROLL_DIRECTION_UP = -1;
9315 var SCROLL_DIRECTION_DOWN = 1;
9316 var SCROLL_DIRECTION_NONE = 0;
9317
9318 // clamp the `scrollTop` value
9319 // IE 10|11 scrollTop may go above `_maxScrollTop`
9320 // iOS `scrollTop` may go below 0 and above `_maxScrollTop`
9321 var scrollTop = Math.max(0, Math.min(this._maxScrollTop, this._scroller.sc rollTop));
9322
9323 var tileHeight, kth, recycledTileSet;
9324 var ratio = this._ratio;
9325 var delta = scrollTop - this._scrollPosition;
9326 var direction = SCROLL_DIRECTION_NONE;
9327 var recycledTiles = 0;
9328 var hiddenContentSize = this._hiddenContentSize;
9329 var currentRatio = ratio;
9330 var movingUp = [];
9331
9332 // track the last `scrollTop`
9333 this._scrollPosition = scrollTop;
9334
9335 // clear cached visible index
9336 this._firstVisibleIndexVal = null;
9337
9338 // random access
9339 if (Math.abs(delta) > this._physicalSize) {
9340 this._physicalTop += delta;
9341 direction = SCROLL_DIRECTION_NONE;
9342 recycledTiles = Math.round(delta / this._physicalAverage);
9343 }
9344 // scroll up
9345 else if (delta < 0) {
9346 var topSpace = scrollTop - this._physicalTop;
9347 var virtualStart = this._virtualStart;
9348
9349 direction = SCROLL_DIRECTION_UP;
9350 recycledTileSet = [];
9351
9352 kth = this._physicalEnd;
9353 currentRatio = topSpace / hiddenContentSize;
9354
9355 // move tiles from bottom to top
9356 while (
9357 // approximate `currentRatio` to `ratio`
9358 currentRatio < ratio &&
9359 // recycle less physical items than the total
9360 recycledTiles < this._physicalCount &&
9361 // ensure that these recycled tiles are needed
9362 virtualStart - recycledTiles > 0
9363 ) {
9364
9365 tileHeight = this._physicalSizes[kth] || this._physicalAverage;
9366 currentRatio += tileHeight / hiddenContentSize;
9367
9368 recycledTileSet.push(kth);
9369 recycledTiles++;
9370 kth = (kth === 0) ? this._physicalCount - 1 : kth - 1;
9371 }
9372
9373 movingUp = recycledTileSet;
9374 recycledTiles = -recycledTiles;
9375
9376 }
9377 // scroll down
9378 else if (delta > 0) {
9379 var bottomSpace = this._physicalBottom - (scrollTop + this._viewportSize );
9380 var virtualEnd = this._virtualEnd;
9381 var lastVirtualItemIndex = this._virtualCount-1;
9382
9383 direction = SCROLL_DIRECTION_DOWN;
9384 recycledTileSet = [];
9385
9386 kth = this._physicalStart;
9387 currentRatio = bottomSpace / hiddenContentSize;
9388
9389 // move tiles from top to bottom
9390 while (
9391 // approximate `currentRatio` to `ratio`
9392 currentRatio < ratio &&
9393 // recycle less physical items than the total
9394 recycledTiles < this._physicalCount &&
9395 // ensure that these recycled tiles are needed
9396 virtualEnd + recycledTiles < lastVirtualItemIndex
9397 ) {
9398
9399 tileHeight = this._physicalSizes[kth] || this._physicalAverage;
9400 currentRatio += tileHeight / hiddenContentSize;
9401
9402 this._physicalTop += tileHeight;
9403 recycledTileSet.push(kth);
9404 recycledTiles++;
9405 kth = (kth + 1) % this._physicalCount;
9406 }
9407 }
9408
9409 if (recycledTiles !== 0) {
9410 this._virtualStart = this._virtualStart + recycledTiles;
9411 this._update(recycledTileSet, movingUp);
9412 }
9413 },
9414
9415 /**
9416 * Update the list of items, starting from the `_virtualStartVal` item.
9417 * @param {!Array<number>=} itemSet
9418 * @param {!Array<number>=} movingUp
9419 */
9420 _update: function(itemSet, movingUp) {
9421 // update models
9422 this._assignModels(itemSet);
9423
9424 // measure heights
9425 this._updateMetrics(itemSet);
9426
9427 // adjust offset after measuring
9428 if (movingUp) {
9429 while (movingUp.length) {
9430 this._physicalTop -= this._physicalSizes[movingUp.pop()];
9431 }
9432 }
9433 // update the position of the items
9434 this._positionItems();
9435
9436 // set the scroller size
9437 this._updateScrollerSize();
9438
9439 // increase the pool of physical items if needed
9440 if (this._increasePoolIfNeeded()) {
9441 // set models to the new items
9442 this.async(this._update);
9443 }
9444 },
9445
9446 /**
9447 * Creates a pool of DOM elements and attaches them to the local dom.
9448 */
9449 _createPool: function(size) {
9450 var physicalItems = new Array(size);
9451
9452 this._ensureTemplatized();
9453
9454 for (var i = 0; i < size; i++) {
9455 var inst = this.stamp(null);
9456
9457 // First element child is item; Safari doesn't support children[0]
9458 // on a doc fragment
9459 physicalItems[i] = inst.root.querySelector('*');
9460 Polymer.dom(this).appendChild(inst.root);
9461 }
9462
9463 return physicalItems;
9464 },
9465
9466 /**
9467 * Increases the pool size. That is, the physical items in the DOM.
9468 * This function will allocate additional physical items
9469 * (limited by `MAX_PHYSICAL_COUNT`) if the content size is shorter than
9470 * `_optPhysicalSize`
9471 *
9472 * @return boolean
9473 */
9474 _increasePoolIfNeeded: function() {
9475 if (this._physicalSize >= this._optPhysicalSize || this._physicalAverage = == 0) {
9476 return false;
9477 }
9478
9479 // the estimated number of physical items that we will need to reach
9480 // the cap established by `_optPhysicalSize`.
9481 var missingItems = Math.round(
9482 (this._optPhysicalSize - this._physicalSize) * 1.2 / this._physicalAve rage
9483 );
9484
9485 // limit the size
9486 var nextPhysicalCount = Math.min(
9487 this._physicalCount + missingItems,
9488 this._virtualCount,
9489 MAX_PHYSICAL_COUNT
9490 );
9491
9492 var prevPhysicalCount = this._physicalCount;
9493 var delta = nextPhysicalCount - prevPhysicalCount;
9494
9495 if (delta <= 0) {
9496 return false;
9497 }
9498
9499 var newPhysicalItems = this._createPool(delta);
9500 var emptyArray = new Array(delta);
9501
9502 [].push.apply(this._physicalItems, newPhysicalItems);
9503 [].push.apply(this._physicalSizes, emptyArray);
9504
9505 this._physicalCount = prevPhysicalCount + delta;
9506
9507 return true;
9508 },
9509
9510 /**
9511 * Render a new list of items. This method does exactly the same as `update` ,
9512 * but it also ensures that only one `update` cycle is created.
9513 */
9514 _render: function() {
9515 var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0;
9516
9517 if (this.isAttached && !this._itemsRendered && this._isVisible && requires Update) {
9518 this._update();
9519 this._itemsRendered = true;
9520 }
9521 },
9522
9523 /**
9524 * Templetizes the user template.
9525 */
9526 _ensureTemplatized: function() {
9527 if (!this.ctor) {
9528 // Template instance props that should be excluded from forwarding
9529 var props = {};
9530
9531 props.__key__ = true;
9532 props[this.as] = true;
9533 props[this.indexAs] = true;
9534 props[this.selectedAs] = true;
9535
9536 this._instanceProps = props;
9537 this._userTemplate = Polymer.dom(this).querySelector('template');
9538
9539 if (this._userTemplate) {
9540 this.templatize(this._userTemplate);
9541 } else {
9542 console.warn('iron-list requires a template to be provided in light-do m');
9543 }
9544 }
9545 },
9546
9547 /**
9548 * Implements extension point from Templatizer mixin.
9549 */
9550 _getStampedChildren: function() {
9551 return this._physicalItems;
9552 },
9553
9554 /**
9555 * Implements extension point from Templatizer
9556 * Called as a side effect of a template instance path change, responsible
9557 * for notifying items.<key-for-instance>.<path> change up to host.
9558 */
9559 _forwardInstancePath: function(inst, path, value) {
9560 if (path.indexOf(this.as + '.') === 0) {
9561 this.notifyPath('items.' + inst.__key__ + '.' +
9562 path.slice(this.as.length + 1), value);
9563 }
9564 },
9565
9566 /**
9567 * Implements extension point from Templatizer mixin
9568 * Called as side-effect of a host property change, responsible for
9569 * notifying parent path change on each row.
9570 */
9571 _forwardParentProp: function(prop, value) {
9572 if (this._physicalItems) {
9573 this._physicalItems.forEach(function(item) {
9574 item._templateInstance[prop] = value;
9575 }, this);
9576 }
9577 },
9578
9579 /**
9580 * Implements extension point from Templatizer
9581 * Called as side-effect of a host path change, responsible for
9582 * notifying parent.<path> path change on each row.
9583 */
9584 _forwardParentPath: function(path, value) {
9585 if (this._physicalItems) {
9586 this._physicalItems.forEach(function(item) {
9587 item._templateInstance.notifyPath(path, value, true);
9588 }, this);
9589 }
9590 },
9591
9592 /**
9593 * Called as a side effect of a host items.<key>.<path> path change,
9594 * responsible for notifying item.<path> changes to row for key.
9595 */
9596 _forwardItemPath: function(path, value) {
9597 if (this._physicalIndexForKey) {
9598 var dot = path.indexOf('.');
9599 var key = path.substring(0, dot < 0 ? path.length : dot);
9600 var idx = this._physicalIndexForKey[key];
9601 var row = this._physicalItems[idx];
9602 if (row) {
9603 var inst = row._templateInstance;
9604 if (dot >= 0) {
9605 path = this.as + '.' + path.substring(dot+1);
9606 inst.notifyPath(path, value, true);
9607 } else {
9608 inst[this.as] = value;
9609 }
9610 }
9611 }
9612 },
9613
9614 /**
9615 * Called when the items have changed. That is, ressignments
9616 * to `items`, splices or updates to a single item.
9617 */
9618 _itemsChanged: function(change) {
9619 if (change.path === 'items') {
9620 // render the new set
9621 this._itemsRendered = false;
9622
9623 // update the whole set
9624 this._virtualStartVal = 0;
9625 this._physicalTop = 0;
9626 this._virtualCount = this.items ? this.items.length : 0;
9627 this._collection = this.items ? Polymer.Collection.get(this.items) : nul l;
9628 this._physicalIndexForKey = {};
9629
9630 // scroll to the top
9631 this._resetScrollPosition(0);
9632
9633 // create the initial physical items
9634 if (!this._physicalItems) {
9635 this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, thi s._virtualCount));
9636 this._physicalItems = this._createPool(this._physicalCount);
9637 this._physicalSizes = new Array(this._physicalCount);
9638 }
9639
9640 this.debounce('refresh', this._render);
9641
9642 } else if (change.path === 'items.splices') {
9643 // render the new set
9644 this._itemsRendered = false;
9645
9646 this._adjustVirtualIndex(change.value.indexSplices);
9647 this._virtualCount = this.items ? this.items.length : 0;
9648
9649 this.debounce('refresh', this._render);
9650
9651 } else {
9652 // update a single item
9653 this._forwardItemPath(change.path.split('.').slice(1).join('.'), change. value);
9654 }
9655 },
9656
9657 /**
9658 * @param {!Array<!PolymerSplice>} splices
9659 */
9660 _adjustVirtualIndex: function(splices) {
9661 var i, splice, idx;
9662
9663 for (i = 0; i < splices.length; i++) {
9664 splice = splices[i];
9665
9666 // deselect removed items
9667 splice.removed.forEach(this.$.selector.deselect, this.$.selector);
9668
9669 idx = splice.index;
9670 // We only need to care about changes happening above the current positi on
9671 if (idx >= this._virtualStartVal) {
9672 break;
9673 }
9674
9675 this._virtualStart = this._virtualStart +
9676 Math.max(splice.addedCount - splice.removed.length, idx - this._virt ualStartVal);
9677 }
9678 },
9679
9680 _scrollHandler: function() {
9681 this._refresh();
9682 },
9683
9684 /**
9685 * Executes a provided function per every physical index in `itemSet`
9686 * `itemSet` default value is equivalent to the entire set of physical index es.
9687 *
9688 * @param {!function(number, number)} fn
9689 * @param {!Array<number>=} itemSet
9690 */
9691 _iterateItems: function(fn, itemSet) {
9692 var pidx, vidx, rtn, i;
9693
9694 if (arguments.length === 2 && itemSet) {
9695 for (i = 0; i < itemSet.length; i++) {
9696 pidx = itemSet[i];
9697 if (pidx >= this._physicalStart) {
9698 vidx = this._virtualStartVal + (pidx - this._physicalStart);
9699 } else {
9700 vidx = this._virtualStartVal + (this._physicalCount - this._physical Start) + pidx;
9701 }
9702 if ((rtn = fn.call(this, pidx, vidx)) != null) {
9703 return rtn;
9704 }
9705 }
9706 } else {
9707 pidx = this._physicalStart;
9708 vidx = this._virtualStartVal;
9709
9710 for (; pidx < this._physicalCount; pidx++, vidx++) {
9711 if ((rtn = fn.call(this, pidx, vidx)) != null) {
9712 return rtn;
9713 }
9714 }
9715
9716 pidx = 0;
9717
9718 for (; pidx < this._physicalStart; pidx++, vidx++) {
9719 if ((rtn = fn.call(this, pidx, vidx)) != null) {
9720 return rtn;
9721 }
9722 }
9723 }
9724 },
9725
9726 /**
9727 * Assigns the data models to a given set of items.
9728 * @param {!Array<number>=} itemSet
9729 */
9730 _assignModels: function(itemSet) {
9731 this._iterateItems(function(pidx, vidx) {
9732 var el = this._physicalItems[pidx];
9733 var inst = el._templateInstance;
9734 var item = this.items && this.items[vidx];
9735
9736 if (item) {
9737 inst[this.as] = item;
9738 inst.__key__ = this._collection.getKey(item);
9739 inst[this.selectedAs] =
9740 /** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(it em);
9741 inst[this.indexAs] = vidx;
9742 el.removeAttribute('hidden');
9743 this._physicalIndexForKey[inst.__key__] = pidx;
9744 } else {
9745 inst.__key__ = null;
9746 el.setAttribute('hidden', '');
9747 }
9748
9749 }, itemSet);
9750 },
9751
9752 /**
9753 * Updates the height for a given set of items.
9754 *
9755 * @param {!Array<number>=} itemSet
9756 */
9757 _updateMetrics: function(itemSet) {
9758 var newPhysicalSize = 0;
9759 var oldPhysicalSize = 0;
9760 var prevAvgCount = this._physicalAverageCount;
9761 var prevPhysicalAvg = this._physicalAverage;
9762 // Make sure we distributed all the physical items
9763 // so we can measure them
9764 Polymer.dom.flush();
9765
9766 this._iterateItems(function(pidx, vidx) {
9767 oldPhysicalSize += this._physicalSizes[pidx] || 0;
9768 this._physicalSizes[pidx] = this._physicalItems[pidx].offsetHeight;
9769 newPhysicalSize += this._physicalSizes[pidx];
9770 this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0;
9771 }, itemSet);
9772
9773 this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalSiz e;
9774 this._viewportSize = this._scroller.offsetHeight;
9775
9776 // update the average if we measured something
9777 if (this._physicalAverageCount !== prevAvgCount) {
9778 this._physicalAverage = Math.round(
9779 ((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) /
9780 this._physicalAverageCount);
9781 }
9782 },
9783
9784 /**
9785 * Updates the position of the physical items.
9786 */
9787 _positionItems: function() {
9788 this._adjustScrollPosition();
9789
9790 var y = this._physicalTop;
9791
9792 this._iterateItems(function(pidx) {
9793
9794 this.transform('translate3d(0, ' + y + 'px, 0)', this._physicalItems[pid x]);
9795 y += this._physicalSizes[pidx];
9796
9797 });
9798 },
9799
9800 /**
9801 * Adjusts the scroll position when it was overestimated.
9802 */
9803 _adjustScrollPosition: function() {
9804 var deltaHeight = this._virtualStartVal === 0 ? this._physicalTop :
9805 Math.min(this._scrollPosition + this._physicalTop, 0);
9806
9807 if (deltaHeight) {
9808 this._physicalTop = this._physicalTop - deltaHeight;
9809
9810 // juking scroll position during interial scrolling on iOS is no bueno
9811 if (!IOS_TOUCH_SCROLLING) {
9812 this._resetScrollPosition(this._scroller.scrollTop - deltaHeight);
9813 }
9814 }
9815 },
9816
9817 /**
9818 * Sets the position of the scroll.
9819 */
9820 _resetScrollPosition: function(pos) {
9821 if (this._scroller) {
9822 this._scroller.scrollTop = pos;
9823 this._scrollPosition = this._scroller.scrollTop;
9824 }
9825 },
9826
9827 /**
9828 * Sets the scroll height, that's the height of the content,
9829 *
9830 * @param {boolean=} forceUpdate If true, updates the height no matter what.
9831 */
9832 _updateScrollerSize: function(forceUpdate) {
9833 this._estScrollHeight = (this._physicalBottom +
9834 Math.max(this._virtualCount - this._physicalCount - this._virtualStart Val, 0) * this._physicalAverage);
9835
9836 forceUpdate = forceUpdate || this._scrollHeight === 0;
9837 forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize;
9838
9839 // amortize height adjustment, so it won't trigger repaints very often
9840 if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._optPhysicalSize) {
9841 this.$.items.style.height = this._estScrollHeight + 'px';
9842 this._scrollHeight = this._estScrollHeight;
9843 }
9844 },
9845
9846 /**
9847 * Scroll to a specific item in the virtual list regardless
9848 * of the physical items in the DOM tree.
9849 *
9850 * @method scrollToIndex
9851 * @param {number} idx The index of the item
9852 */
9853 scrollToIndex: function(idx) {
9854 if (typeof idx !== 'number') {
9855 return;
9856 }
9857
9858 var firstVisible = this.firstVisibleIndex;
9859
9860 idx = Math.min(Math.max(idx, 0), this._virtualCount-1);
9861
9862 // start at the previous virtual item
9863 // so we have a item above the first visible item
9864 this._virtualStart = idx - 1;
9865
9866 // assign new models
9867 this._assignModels();
9868
9869 // measure the new sizes
9870 this._updateMetrics();
9871
9872 // estimate new physical offset
9873 this._physicalTop = this._virtualStart * this._physicalAverage;
9874
9875 var currentTopItem = this._physicalStart;
9876 var currentVirtualItem = this._virtualStart;
9877 var targetOffsetTop = 0;
9878 var hiddenContentSize = this._hiddenContentSize;
9879
9880 // scroll to the item as much as we can
9881 while (currentVirtualItem !== idx && targetOffsetTop < hiddenContentSize) {
9882 targetOffsetTop = targetOffsetTop + this._physicalSizes[currentTopItem];
9883 currentTopItem = (currentTopItem + 1) % this._physicalCount;
9884 currentVirtualItem++;
9885 }
9886
9887 // update the scroller size
9888 this._updateScrollerSize(true);
9889
9890 // update the position of the items
9891 this._positionItems();
9892
9893 // set the new scroll position
9894 this._resetScrollPosition(this._physicalTop + targetOffsetTop + 1);
9895
9896 // increase the pool of physical items if needed
9897 if (this._increasePoolIfNeeded()) {
9898 // set models to the new items
9899 this.async(this._update);
9900 }
9901
9902 // clear cached visible index
9903 this._firstVisibleIndexVal = null;
9904 },
9905
9906 /**
9907 * Reset the physical average and the average count.
9908 */
9909 _resetAverage: function() {
9910 this._physicalAverage = 0;
9911 this._physicalAverageCount = 0;
9912 },
9913
9914 /**
9915 * A handler for the `iron-resize` event triggered by `IronResizableBehavior `
9916 * when the element is resized.
9917 */
9918 _resizeHandler: function() {
9919 this.debounce('resize', function() {
9920 this._render();
9921 if (this._itemsRendered && this._physicalItems && this._isVisible) {
9922 this._resetAverage();
9923 this.updateViewportBoundaries();
9924 this.scrollToIndex(this.firstVisibleIndex);
9925 }
9926 });
9927 },
9928
9929 _getModelFromItem: function(item) {
9930 var key = this._collection.getKey(item);
9931 var pidx = this._physicalIndexForKey[key];
9932
9933 if (pidx !== undefined) {
9934 return this._physicalItems[pidx]._templateInstance;
9935 }
9936 return null;
9937 },
9938
9939 /**
9940 * Gets a valid item instance from its index or the object value.
9941 *
9942 * @param {(Object|number)} item The item object or its index
9943 */
9944 _getNormalizedItem: function(item) {
9945 if (typeof item === 'number') {
9946 item = this.items[item];
9947 if (!item) {
9948 throw new RangeError('<item> not found');
9949 }
9950 } else if (this._collection.getKey(item) === undefined) {
9951 throw new TypeError('<item> should be a valid item');
9952 }
9953 return item;
9954 },
9955
9956 /**
9957 * Select the list item at the given index.
9958 *
9959 * @method selectItem
9960 * @param {(Object|number)} item The item object or its index
9961 */
9962 selectItem: function(item) {
9963 item = this._getNormalizedItem(item);
9964 var model = this._getModelFromItem(item);
9965
9966 if (!this.multiSelection && this.selectedItem) {
9967 this.deselectItem(this.selectedItem);
9968 }
9969 if (model) {
9970 model[this.selectedAs] = true;
9971 }
9972 this.$.selector.select(item);
9973 },
9974
9975 /**
9976 * Deselects the given item list if it is already selected.
9977 *
9978
9979 * @method deselect
9980 * @param {(Object|number)} item The item object or its index
9981 */
9982 deselectItem: function(item) {
9983 item = this._getNormalizedItem(item);
9984 var model = this._getModelFromItem(item);
9985
9986 if (model) {
9987 model[this.selectedAs] = false;
9988 }
9989 this.$.selector.deselect(item);
9990 },
9991
9992 /**
9993 * Select or deselect a given item depending on whether the item
9994 * has already been selected.
9995 *
9996 * @method toggleSelectionForItem
9997 * @param {(Object|number)} item The item object or its index
9998 */
9999 toggleSelectionForItem: function(item) {
10000 item = this._getNormalizedItem(item);
10001 if (/** @type {!ArraySelectorElement} */ (this.$.selector).isSelected(item )) {
10002 this.deselectItem(item);
10003 } else {
10004 this.selectItem(item);
10005 }
10006 },
10007
10008 /**
10009 * Clears the current selection state of the list.
10010 *
10011 * @method clearSelection
10012 */
10013 clearSelection: function() {
10014 function unselect(item) {
10015 var model = this._getModelFromItem(item);
10016 if (model) {
10017 model[this.selectedAs] = false;
10018 }
10019 }
10020
10021 if (Array.isArray(this.selectedItems)) {
10022 this.selectedItems.forEach(unselect, this);
10023 } else if (this.selectedItem) {
10024 unselect.call(this, this.selectedItem);
10025 }
10026
10027 /** @type {!ArraySelectorElement} */ (this.$.selector).clearSelection();
10028 },
10029
10030 /**
10031 * Add an event listener to `tap` if `selectionEnabled` is true,
10032 * it will remove the listener otherwise.
10033 */
10034 _selectionEnabledChanged: function(selectionEnabled) {
10035 if (selectionEnabled) {
10036 this.listen(this, 'tap', '_selectionHandler');
10037 this.listen(this, 'keypress', '_selectionHandler');
10038 } else {
10039 this.unlisten(this, 'tap', '_selectionHandler');
10040 this.unlisten(this, 'keypress', '_selectionHandler');
10041 }
10042 },
10043
10044 /**
10045 * Select an item from an event object.
10046 */
10047 _selectionHandler: function(e) {
10048 if (e.type !== 'keypress' || e.keyCode === 13) {
10049 var model = this.modelForElement(e.target);
10050 if (model) {
10051 this.toggleSelectionForItem(model[this.as]);
10052 }
10053 }
10054 },
10055
10056 _multiSelectionChanged: function(multiSelection) {
10057 this.clearSelection();
10058 this.$.selector.multi = multiSelection;
10059 },
10060
10061 /**
10062 * Updates the size of an item.
10063 *
10064 * @method updateSizeForItem
10065 * @param {(Object|number)} item The item object or its index
10066 */
10067 updateSizeForItem: function(item) {
10068 item = this._getNormalizedItem(item);
10069 var key = this._collection.getKey(item);
10070 var pidx = this._physicalIndexForKey[key];
10071
10072 if (pidx !== undefined) {
10073 this._updateMetrics([pidx]);
10074 this._positionItems();
10075 }
10076 }
10077 });
10078
10079 })();
10080 (function() {
10081
8813 'use strict'; 10082 'use strict';
8814 10083
8815 var SHADOW_WHEN_SCROLLING = 1; 10084 var SHADOW_WHEN_SCROLLING = 1;
8816 var SHADOW_ALWAYS = 2; 10085 var SHADOW_ALWAYS = 2;
8817 10086
8818 10087
8819 var MODE_CONFIGS = { 10088 var MODE_CONFIGS = {
8820 10089
8821 outerScroll: { 10090 outerScroll: {
8822 'scroll': true 10091 'scroll': true
(...skipping 2488 matching lines...) Expand 10 before | Expand all | Expand 10 after
11311 12580
11312 }); 12581 });
11313 // Copyright 2015 The Chromium Authors. All rights reserved. 12582 // Copyright 2015 The Chromium Authors. All rights reserved.
11314 // Use of this source code is governed by a BSD-style license that can be 12583 // Use of this source code is governed by a BSD-style license that can be
11315 // found in the LICENSE file. 12584 // found in the LICENSE file.
11316 12585
11317 cr.define('downloads', function() { 12586 cr.define('downloads', function() {
11318 var Item = Polymer({ 12587 var Item = Polymer({
11319 is: 'downloads-item', 12588 is: 'downloads-item',
11320 12589
11321 /**
11322 * @param {!downloads.ThrottledIconLoader} iconLoader
11323 */
11324 factoryImpl: function(iconLoader) {
11325 /** @private {!downloads.ThrottledIconLoader} */
11326 this.iconLoader_ = iconLoader;
11327 },
11328
11329 properties: { 12590 properties: {
11330 data: { 12591 data: {
11331 type: Object, 12592 type: Object,
11332 }, 12593 },
11333 12594
11334 hideDate: { 12595 hideDate: {
11335 type: Boolean, 12596 type: Boolean,
11336 value: true, 12597 value: true,
11337 }, 12598 },
11338 12599
11339 readyPromise: {
11340 type: Object,
11341 value: function() {
11342 return new Promise(function(resolve, reject) {
11343 this.resolveReadyPromise_ = resolve;
11344 }.bind(this));
11345 },
11346 },
11347
11348 completelyOnDisk_: { 12600 completelyOnDisk_: {
11349 computed: 'computeCompletelyOnDisk_(' + 12601 computed: 'computeCompletelyOnDisk_(' +
11350 'data.state, data.file_externally_removed)', 12602 'data.state, data.file_externally_removed)',
11351 type: Boolean, 12603 type: Boolean,
11352 value: true, 12604 value: true,
11353 }, 12605 },
11354 12606
11355 controlledBy_: { 12607 controlledBy_: {
11356 computed: 'computeControlledBy_(data.by_ext_id, data.by_ext_name)', 12608 computed: 'computeControlledBy_(data.by_ext_id, data.by_ext_name)',
11357 type: String, 12609 type: String,
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
11410 computed: 'computeIsMalware_(isDangerous_, data.danger_type)', 12662 computed: 'computeIsMalware_(isDangerous_, data.danger_type)',
11411 type: Boolean, 12663 type: Boolean,
11412 value: false, 12664 value: false,
11413 }, 12665 },
11414 }, 12666 },
11415 12667
11416 observers: [ 12668 observers: [
11417 // TODO(dbeam): this gets called way more when I observe data.by_ext_id 12669 // TODO(dbeam): this gets called way more when I observe data.by_ext_id
11418 // and data.by_ext_name directly. Why? 12670 // and data.by_ext_name directly. Why?
11419 'observeControlledBy_(controlledBy_)', 12671 'observeControlledBy_(controlledBy_)',
12672 'observeIsDangerous_(isDangerous_, data.file_path)',
11420 ], 12673 ],
11421 12674
11422 ready: function() { 12675 ready: function() {
11423 this.content = this.$.content; 12676 this.content = this.$.content;
11424 this.resolveReadyPromise_();
11425 },
11426
11427 /** @param {!downloads.Data} data */
11428 update: function(data) {
11429 this.data = data;
11430
11431 if (!this.isDangerous_) {
11432 var icon = 'chrome://fileicon/' + encodeURIComponent(data.file_path);
11433 this.iconLoader_.loadScaledIcon(this.$['file-icon'], icon);
11434 }
11435 }, 12677 },
11436 12678
11437 /** @private */ 12679 /** @private */
11438 computeClass_: function() { 12680 computeClass_: function() {
11439 var classes = []; 12681 var classes = [];
11440 12682
11441 if (this.isActive_) 12683 if (this.isActive_)
11442 classes.push('is-active'); 12684 classes.push('is-active');
11443 12685
11444 if (this.isDangerous_) 12686 if (this.isDangerous_)
(...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after
11585 isIndeterminate_: function() { 12827 isIndeterminate_: function() {
11586 return this.data.percent == -1; 12828 return this.data.percent == -1;
11587 }, 12829 },
11588 12830
11589 /** @private */ 12831 /** @private */
11590 observeControlledBy_: function() { 12832 observeControlledBy_: function() {
11591 this.$['controlled-by'].innerHTML = this.controlledBy_; 12833 this.$['controlled-by'].innerHTML = this.controlledBy_;
11592 }, 12834 },
11593 12835
11594 /** @private */ 12836 /** @private */
12837 observeIsDangerous_: function() {
12838 if (this.data && !this.isDangerous_) {
12839 var filePath = encodeURIComponent(this.data.file_path);
12840 this.$['file-icon'].src = 'chrome://fileicon/' + filePath;
12841 }
12842 },
12843
12844 /** @private */
11595 onCancelTap_: function() { 12845 onCancelTap_: function() {
11596 downloads.ActionService.getInstance().cancel(this.data.id); 12846 downloads.ActionService.getInstance().cancel(this.data.id);
11597 }, 12847 },
11598 12848
11599 /** @private */ 12849 /** @private */
11600 onDiscardDangerousTap_: function() { 12850 onDiscardDangerousTap_: function() {
11601 downloads.ActionService.getInstance().discardDangerous(this.data.id); 12851 downloads.ActionService.getInstance().discardDangerous(this.data.id);
11602 }, 12852 },
11603 12853
11604 /** 12854 /**
(...skipping 884 matching lines...) Expand 10 before | Expand all | Expand 10 after
12489 is: 'paper-menu', 13739 is: 'paper-menu',
12490 13740
12491 behaviors: [ 13741 behaviors: [
12492 Polymer.IronMenuBehavior 13742 Polymer.IronMenuBehavior
12493 ] 13743 ]
12494 13744
12495 }); 13745 });
12496 13746
12497 })(); 13747 })();
12498 /** 13748 /**
12499 * `IronResizableBehavior` is a behavior that can be used in Polymer elements to
12500 * coordinate the flow of resize events between "resizers" (elements that cont rol the
12501 * size or hidden state of their children) and "resizables" (elements that nee d to be
12502 * notified when they are resized or un-hidden by their parents in order to ta ke
12503 * action on their new measurements).
12504 * Elements that perform measurement should add the `IronResizableBehavior` be havior to
12505 * their element definition and listen for the `iron-resize` event on themselv es.
12506 * This event will be fired when they become showing after having been hidden,
12507 * when they are resized explicitly by another resizable, or when the window h as been
12508 * resized.
12509 * Note, the `iron-resize` event is non-bubbling.
12510 *
12511 * @polymerBehavior Polymer.IronResizableBehavior
12512 * @demo demo/index.html
12513 **/
12514 Polymer.IronResizableBehavior = {
12515 properties: {
12516 /**
12517 * The closest ancestor element that implements `IronResizableBehavior`.
12518 */
12519 _parentResizable: {
12520 type: Object,
12521 observer: '_parentResizableChanged'
12522 },
12523
12524 /**
12525 * True if this element is currently notifying its descedant elements of
12526 * resize.
12527 */
12528 _notifyingDescendant: {
12529 type: Boolean,
12530 value: false
12531 }
12532 },
12533
12534 listeners: {
12535 'iron-request-resize-notifications': '_onIronRequestResizeNotifications'
12536 },
12537
12538 created: function() {
12539 // We don't really need property effects on these, and also we want them
12540 // to be created before the `_parentResizable` observer fires:
12541 this._interestedResizables = [];
12542 this._boundNotifyResize = this.notifyResize.bind(this);
12543 },
12544
12545 attached: function() {
12546 this.fire('iron-request-resize-notifications', null, {
12547 node: this,
12548 bubbles: true,
12549 cancelable: true
12550 });
12551
12552 if (!this._parentResizable) {
12553 window.addEventListener('resize', this._boundNotifyResize);
12554 this.notifyResize();
12555 }
12556 },
12557
12558 detached: function() {
12559 if (this._parentResizable) {
12560 this._parentResizable.stopResizeNotificationsFor(this);
12561 } else {
12562 window.removeEventListener('resize', this._boundNotifyResize);
12563 }
12564
12565 this._parentResizable = null;
12566 },
12567
12568 /**
12569 * Can be called to manually notify a resizable and its descendant
12570 * resizables of a resize change.
12571 */
12572 notifyResize: function() {
12573 if (!this.isAttached) {
12574 return;
12575 }
12576
12577 this._interestedResizables.forEach(function(resizable) {
12578 if (this.resizerShouldNotify(resizable)) {
12579 this._notifyDescendant(resizable);
12580 }
12581 }, this);
12582
12583 this._fireResize();
12584 },
12585
12586 /**
12587 * Used to assign the closest resizable ancestor to this resizable
12588 * if the ancestor detects a request for notifications.
12589 */
12590 assignParentResizable: function(parentResizable) {
12591 this._parentResizable = parentResizable;
12592 },
12593
12594 /**
12595 * Used to remove a resizable descendant from the list of descendants
12596 * that should be notified of a resize change.
12597 */
12598 stopResizeNotificationsFor: function(target) {
12599 var index = this._interestedResizables.indexOf(target);
12600
12601 if (index > -1) {
12602 this._interestedResizables.splice(index, 1);
12603 this.unlisten(target, 'iron-resize', '_onDescendantIronResize');
12604 }
12605 },
12606
12607 /**
12608 * This method can be overridden to filter nested elements that should or
12609 * should not be notified by the current element. Return true if an element
12610 * should be notified, or false if it should not be notified.
12611 *
12612 * @param {HTMLElement} element A candidate descendant element that
12613 * implements `IronResizableBehavior`.
12614 * @return {boolean} True if the `element` should be notified of resize.
12615 */
12616 resizerShouldNotify: function(element) { return true; },
12617
12618 _onDescendantIronResize: function(event) {
12619 if (this._notifyingDescendant) {
12620 event.stopPropagation();
12621 return;
12622 }
12623
12624 // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the
12625 // otherwise non-bubbling event "just work." We do it manually here for
12626 // the case where Polymer is not using shadow roots for whatever reason:
12627 if (!Polymer.Settings.useShadow) {
12628 this._fireResize();
12629 }
12630 },
12631
12632 _fireResize: function() {
12633 this.fire('iron-resize', null, {
12634 node: this,
12635 bubbles: false
12636 });
12637 },
12638
12639 _onIronRequestResizeNotifications: function(event) {
12640 var target = event.path ? event.path[0] : event.target;
12641
12642 if (target === this) {
12643 return;
12644 }
12645
12646 if (this._interestedResizables.indexOf(target) === -1) {
12647 this._interestedResizables.push(target);
12648 this.listen(target, 'iron-resize', '_onDescendantIronResize');
12649 }
12650
12651 target.assignParentResizable(this);
12652 this._notifyDescendant(target);
12653
12654 event.stopPropagation();
12655 },
12656
12657 _parentResizableChanged: function(parentResizable) {
12658 if (parentResizable) {
12659 window.removeEventListener('resize', this._boundNotifyResize);
12660 }
12661 },
12662
12663 _notifyDescendant: function(descendant) {
12664 // NOTE(cdata): In IE10, attached is fired on children first, so it's
12665 // important not to notify them if the parent is not attached yet (or
12666 // else they will get redundantly notified when the parent attaches).
12667 if (!this.isAttached) {
12668 return;
12669 }
12670
12671 this._notifyingDescendant = true;
12672 descendant.notifyResize();
12673 this._notifyingDescendant = false;
12674 }
12675 };
12676 /**
12677 Polymer.IronFitBehavior fits an element in another element using `max-height` an d `max-width`, and 13749 Polymer.IronFitBehavior fits an element in another element using `max-height` an d `max-width`, and
12678 optionally centers it in the window or another element. 13750 optionally centers it in the window or another element.
12679 13751
12680 The element will only be sized and/or positioned if it has not already been size d and/or positioned 13752 The element will only be sized and/or positioned if it has not already been size d and/or positioned
12681 by CSS. 13753 by CSS.
12682 13754
12683 CSS properties | Action 13755 CSS properties | Action
12684 -----------------------------|------------------------------------------- 13756 -----------------------------|-------------------------------------------
12685 `position` set | Element is not centered horizontally or verticall y 13757 `position` set | Element is not centered horizontally or verticall y
12686 `top` or `bottom` set | Element is not vertically centered 13758 `top` or `bottom` set | Element is not vertically centered
(...skipping 2998 matching lines...) Expand 10 before | Expand all | Expand 10 after
15685 16757
15686 cr.define('downloads', function() { 16758 cr.define('downloads', function() {
15687 var Manager = Polymer({ 16759 var Manager = Polymer({
15688 is: 'downloads-manager', 16760 is: 'downloads-manager',
15689 16761
15690 properties: { 16762 properties: {
15691 hasDownloads_: { 16763 hasDownloads_: {
15692 type: Boolean, 16764 type: Boolean,
15693 value: false, 16765 value: false,
15694 }, 16766 },
16767
16768 items_: {
16769 type: Array,
16770 },
15695 }, 16771 },
15696 16772
15697 /** 16773 /**
15698 * @return {number} A guess at how many items could be visible at once.
15699 * @private
15700 */
15701 guesstimateNumberOfVisibleItems_: function() {
15702 var toolbarHeight = this.$.toolbar.offsetHeight;
15703 return Math.floor((window.innerHeight - toolbarHeight) / 46) + 1;
15704 },
15705
15706 /**
15707 * @param {Event} e 16774 * @param {Event} e
15708 * @private 16775 * @private
15709 */ 16776 */
15710 onCanExecute_: function(e) { 16777 onCanExecute_: function(e) {
15711 e = /** @type {cr.ui.CanExecuteEvent} */(e); 16778 e = /** @type {cr.ui.CanExecuteEvent} */(e);
15712 switch (e.command.id) { 16779 switch (e.command.id) {
15713 case 'undo-command': 16780 case 'undo-command':
15714 e.canExecute = this.$.toolbar.canUndo(); 16781 e.canExecute = this.$.toolbar.canUndo();
15715 break; 16782 break;
15716 case 'clear-all-command': 16783 case 'clear-all-command':
(...skipping 16 matching lines...) Expand all
15733 /** @private */ 16800 /** @private */
15734 onLoad_: function() { 16801 onLoad_: function() {
15735 cr.ui.decorate('command', cr.ui.Command); 16802 cr.ui.decorate('command', cr.ui.Command);
15736 document.addEventListener('canExecute', this.onCanExecute_.bind(this)); 16803 document.addEventListener('canExecute', this.onCanExecute_.bind(this));
15737 document.addEventListener('command', this.onCommand_.bind(this)); 16804 document.addEventListener('command', this.onCommand_.bind(this));
15738 16805
15739 // Shows all downloads. 16806 // Shows all downloads.
15740 downloads.ActionService.getInstance().search(''); 16807 downloads.ActionService.getInstance().search('');
15741 }, 16808 },
15742 16809
15743 /** @private */
15744 rebuildFocusGrid_: function() {
15745 var activeElement = this.shadowRoot.activeElement;
15746
15747 var activeItem;
15748 if (activeElement && activeElement.tagName == 'downloads-item')
15749 activeItem = activeElement;
15750
15751 var activeControl = activeItem && activeItem.shadowRoot.activeElement;
15752
15753 /** @private {!cr.ui.FocusGrid} */
15754 this.focusGrid_ = this.focusGrid_ || new cr.ui.FocusGrid;
15755 this.focusGrid_.destroy();
15756
15757 var boundary = this.$['downloads-list'];
15758
15759 this.items_.forEach(function(item) {
15760 var focusRow = new downloads.FocusRow(item.content, boundary);
15761 this.focusGrid_.addRow(focusRow);
15762
15763 if (item == activeItem && !cr.ui.FocusRow.isFocusable(activeControl))
15764 focusRow.getEquivalentElement(activeControl).focus();
15765 }, this);
15766
15767 this.focusGrid_.ensureRowActive();
15768 },
15769
15770 /** 16810 /**
15771 * @return {number} The number of downloads shown on the page. 16811 * @return {number} The number of downloads shown on the page.
15772 * @private 16812 * @private
15773 */ 16813 */
15774 size_: function() { 16814 size_: function() {
15775 return this.items_.length; 16815 return this.items_.length;
15776 }, 16816 },
15777 16817
15778 /** 16818 /**
15779 * Called when all items need to be updated. 16819 * Called when all items need to be updated.
15780 * @param {!Array<!downloads.Data>} list A list of new download data. 16820 * @param {!Array<!downloads.Data>} list A list of new download data.
15781 * @private 16821 * @private
15782 */ 16822 */
15783 updateAll_: function(list) { 16823 updateAll_: function(list) {
15784 var oldIdMap = this.idMap_ || {}; 16824 /** @private {!Object<number>} */
15785 16825 this.idToIndex_ = {};
15786 /** @private {!Object<!downloads.Item>} */
15787 this.idMap_ = {};
15788
15789 /** @private {!Array<!downloads.Item>} */
15790 this.items_ = [];
15791
15792 if (!this.iconLoader_) {
15793 var guesstimate = Math.max(this.guesstimateNumberOfVisibleItems_(), 1);
15794 /** @private {downloads.ThrottledIconLoader} */
15795 this.iconLoader_ = new downloads.ThrottledIconLoader(guesstimate);
15796 }
15797 16826
15798 for (var i = 0; i < list.length; ++i) { 16827 for (var i = 0; i < list.length; ++i) {
15799 var data = list[i]; 16828 var data = list[i];
15800 var id = data.id;
15801 16829
15802 // Re-use old items when possible (saves work, preserves focus). 16830 this.idToIndex_[data.id] = data.index = i;
15803 var item = oldIdMap[id] || new downloads.Item(this.iconLoader_);
15804 16831
15805 this.idMap_[id] = item; // Associated by ID for fast lookup.
15806 this.items_.push(item); // Add to sorted list for order.
15807
15808 // Render |item| but don't actually add to the DOM yet. |this.items_|
15809 // must be fully created to be able to find the right spot to insert.
15810 item.update(data);
15811
15812 // Collapse redundant dates.
15813 var prev = list[i - 1]; 16832 var prev = list[i - 1];
15814 item.hideDate = !!prev && prev.date_string == data.date_string; 16833 data.hideDate = !!prev && prev.date_string == data.date_string;
15815
15816 delete oldIdMap[id];
15817 } 16834 }
15818 16835
15819 // Remove stale, previously rendered items from the DOM. 16836 // TODO(dbeam): this resets the scroll position, which is a huge bummer.
15820 for (var id in oldIdMap) { 16837 // Removing something from the bottom of the list should not scroll you
15821 if (oldIdMap[id].parentNode) 16838 // back to the top. The grand plan is to restructure how the C++ sends the
15822 oldIdMap[id].parentNode.removeChild(oldIdMap[id]); 16839 // JS data so that it only gets updates (rather than the most recent set
15823 delete oldIdMap[id]; 16840 // of items). TL;DR - we can't ship with this bug.
15824 } 16841 this.items_ = list;
15825
15826 for (var i = 0; i < this.items_.length; ++i) {
15827 var item = this.items_[i];
15828 if (item.parentNode) // Already in the DOM; skip.
15829 continue;
15830
15831 var before = null;
15832 // Find the next rendered item after this one, and insert before it.
15833 for (var j = i + 1; !before && j < this.items_.length; ++j) {
15834 if (this.items_[j].parentNode)
15835 before = this.items_[j];
15836 }
15837 // If |before| is null, |item| will just get added at the end.
15838 this.$['downloads-list'].insertBefore(item, before);
15839 }
15840 16842
15841 var hasDownloads = this.size_() > 0; 16843 var hasDownloads = this.size_() > 0;
15842 if (!hasDownloads) { 16844 if (!hasDownloads) {
15843 var isSearching = downloads.ActionService.getInstance().isSearching(); 16845 var isSearching = downloads.ActionService.getInstance().isSearching();
15844 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads'; 16846 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads';
15845 this.$['no-downloads'].querySelector('span').textContent = 16847 this.$['no-downloads'].querySelector('span').textContent =
15846 loadTimeData.getString(messageToShow); 16848 loadTimeData.getString(messageToShow);
15847 } 16849 }
15848 this.hasDownloads_ = hasDownloads; 16850 this.hasDownloads_ = hasDownloads;
15849 16851
15850 if (loadTimeData.getBoolean('allowDeletingHistory')) 16852 if (loadTimeData.getBoolean('allowDeletingHistory'))
15851 this.$.toolbar.downloadsShowing = this.hasDownloads_; 16853 this.$.toolbar.downloadsShowing = this.hasDownloads_;
15852 16854
15853 this.$.panel.classList.remove('loading'); 16855 this.$.panel.classList.remove('loading');
15854
15855 var allReady = this.items_.map(function(i) { return i.readyPromise; });
15856 Promise.all(allReady).then(this.rebuildFocusGrid_.bind(this));
15857 }, 16856 },
15858 16857
15859 /** 16858 /**
15860 * @param {!downloads.Data} data 16859 * @param {!downloads.Data} data
15861 * @private 16860 * @private
15862 */ 16861 */
15863 updateItem_: function(data) { 16862 updateItem_: function(data) {
15864 var item = this.idMap_[data.id]; 16863 var index = this.idToIndex_[data.id];
15865 16864 this.set('items_.' + index, data);
15866 var activeControl = this.shadowRoot.activeElement == item ? 16865 this.$['downloads-list'].updateSizeForItem(index);
15867 item.shadowRoot.activeElement : null;
15868
15869 item.update(data);
15870
15871 this.async(function() {
15872 if (activeControl && !cr.ui.FocusRow.isFocusable(activeControl)) {
15873 var focusRow = this.focusGrid_.getRowForRoot(item.content);
15874 focusRow.getEquivalentElement(activeControl).focus();
15875 }
15876 }.bind(this));
15877 }, 16866 },
15878 }); 16867 });
15879 16868
15880 Manager.size = function() { 16869 Manager.size = function() {
15881 return document.querySelector('downloads-manager').size_(); 16870 return document.querySelector('downloads-manager').size_();
15882 }; 16871 };
15883 16872
15884 Manager.updateAll = function(list) { 16873 Manager.updateAll = function(list) {
15885 document.querySelector('downloads-manager').updateAll_(list); 16874 document.querySelector('downloads-manager').updateAll_(list);
15886 }; 16875 };
15887 16876
15888 Manager.updateItem = function(item) { 16877 Manager.updateItem = function(item) {
15889 document.querySelector('downloads-manager').updateItem_(item); 16878 document.querySelector('downloads-manager').updateItem_(item);
15890 }; 16879 };
15891 16880
15892 Manager.onLoad = function() { 16881 Manager.onLoad = function() {
15893 document.querySelector('downloads-manager').onLoad_(); 16882 document.querySelector('downloads-manager').onLoad_();
15894 }; 16883 };
15895 16884
15896 return {Manager: Manager}; 16885 return {Manager: Manager};
15897 }); 16886 });
15898 16887
15899 window.addEventListener('load', downloads.Manager.onLoad); 16888 window.addEventListener('load', downloads.Manager.onLoad);
OLDNEW
« no previous file with comments | « chrome/browser/resources/md_downloads/compiled_resources.gyp ('k') | chrome/browser/resources/md_downloads/item.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698