Commit 1ec12653 authored by Dan Beam's avatar Dan Beam Committed by Commit Bot

Downloads: detect failed file icons and show default thumbnail

On the downloads page, we consult the system for a themed image for the
type of file downloads. Ex: zip files -> archive icon.

When a system doesn't know why type of icon to show for a specific file
type, it can either return a default or nothing. Right now, Linux
returns nothing, so we show nothing.  chrome://fileicon tries to warn
us by giving a network error (which has been ignored until now).

So, when a chrome://fileicon fails to load, show a default filetype
icon in respond.

Before & after screenshots: https://imgur.com/a/txRbfIG

R=dpapad@chromium.org
BUG=855259

Change-Id: I370fd8ea119b194ad8e1ba7e0dfad32c6cea1bbe
Reviewed-on: https://chromium-review.googlesource.com/c/1132560
Commit-Queue: Dan Beam <dbeam@chromium.org>
Reviewed-by: default avatarDemetrios Papadopoulos <dpapad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#614581}
parent e3248122
...@@ -179,6 +179,8 @@ ...@@ -179,6 +179,8 @@
<include name="IDR_MD_DOWNLOADS_CONSTANTS_JS" file="resources\md_downloads\constants.js" type="BINDATA" /> <include name="IDR_MD_DOWNLOADS_CONSTANTS_JS" file="resources\md_downloads\constants.js" type="BINDATA" />
<include name="IDR_MD_DOWNLOADS_DOWNLOADS_JS" file="resources\md_downloads\downloads.js" type="BINDATA" /> <include name="IDR_MD_DOWNLOADS_DOWNLOADS_JS" file="resources\md_downloads\downloads.js" type="BINDATA" />
<include name="IDR_MD_DOWNLOADS_I18N_SETUP_HTML" file="resources\md_downloads\i18n_setup.html" type="BINDATA" /> <include name="IDR_MD_DOWNLOADS_I18N_SETUP_HTML" file="resources\md_downloads\i18n_setup.html" type="BINDATA" />
<include name="IDR_MD_DOWNLOADS_ICON_LOADER_HTML" file="resources\md_downloads\icon_loader.html" type="BINDATA" />
<include name="IDR_MD_DOWNLOADS_ICON_LOADER_JS" file="resources\md_downloads\icon_loader.js" type="BINDATA" />
<include name="IDR_MD_DOWNLOADS_ICONS_HTML" file="resources\md_downloads\icons.html" type="BINDATA" /> <include name="IDR_MD_DOWNLOADS_ICONS_HTML" file="resources\md_downloads\icons.html" type="BINDATA" />
<include name="IDR_MD_DOWNLOADS_ITEM_HTML" file="resources\md_downloads\item.html" type="BINDATA" /> <include name="IDR_MD_DOWNLOADS_ITEM_HTML" file="resources\md_downloads\item.html" type="BINDATA" />
<include name="IDR_MD_DOWNLOADS_ITEM_JS" file="resources\md_downloads\item.js" type="BINDATA" /> <include name="IDR_MD_DOWNLOADS_ITEM_JS" file="resources\md_downloads\item.js" type="BINDATA" />
......
...@@ -61,11 +61,20 @@ js_library("externs") { ...@@ -61,11 +61,20 @@ js_library("externs") {
] ]
} }
js_library("icon_loader") {
deps = [
"//ui/webui/resources/js:assert",
"//ui/webui/resources/js:cr",
"//ui/webui/resources/js:icon",
]
}
js_library("item") { js_library("item") {
deps = [ deps = [
":browser_proxy", ":browser_proxy",
":constants", ":constants",
":externs", ":externs",
":icon_loader",
"//third_party/polymer/v1_0/components-chromium/paper-behaviors:paper-inky-focus-behavior-extracted", "//third_party/polymer/v1_0/components-chromium/paper-behaviors:paper-inky-focus-behavior-extracted",
"//ui/webui/resources/js:cr", "//ui/webui/resources/js:cr",
"//ui/webui/resources/js:load_time_data", "//ui/webui/resources/js:load_time_data",
......
<link rel="import" href="chrome://resources/html/assert.html">
<link rel="import" href="chrome://resources/html/cr.html">
<link rel="import" href="chrome://resources/html/icon.html">
<script src="chrome://downloads/icon_loader.js"></script>
// Copyright 2018 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.
cr.define('downloads', function() {
class IconLoader {
constructor() {
/** @private {!Object<!PromiseResolver<boolean>>} */
this.iconResolvers_ = {};
/** @private {!Set<!HTMLImageElement>} */
this.listeningImages_ = new Set();
}
/**
* @param {!HTMLImageElement} imageEl
* @param {string} filePath
* @return {!Promise<boolean>} Whether or not the icon loaded successfully.
*/
loadIcon(imageEl, filePath) {
const url = cr.icon.getFileIconUrl(filePath);
if (!this.iconResolvers_[url]) {
this.iconResolvers_[url] = new PromiseResolver();
}
if (!this.listeningImages_.has(imageEl)) {
imageEl.addEventListener('load', this.finishedLoading_.bind(this));
imageEl.addEventListener('error', this.finishedLoading_.bind(this));
this.listeningImages_.add(imageEl);
}
imageEl.src = url;
return assert(this.iconResolvers_[url]).promise;
}
/**
* @param {!Event} e
* @private
*/
finishedLoading_(e) {
const resolver = assert(this.iconResolvers_[e.currentTarget.src]);
if (!resolver.isFulfilled)
resolver.resolve(e.type == 'load');
}
}
cr.addSingletonGetter(IconLoader);
return {IconLoader: IconLoader};
});
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
<link rel="import" href="chrome://downloads/browser_proxy.html"> <link rel="import" href="chrome://downloads/browser_proxy.html">
<link rel="import" href="chrome://downloads/constants.html"> <link rel="import" href="chrome://downloads/constants.html">
<link rel="import" href="chrome://downloads/i18n_setup.html"> <link rel="import" href="chrome://downloads/i18n_setup.html">
<link rel="import" href="chrome://downloads/icon_loader.html">
<link rel="import" href="chrome://downloads/icons.html"> <link rel="import" href="chrome://downloads/icons.html">
<dom-module id="downloads-item"> <dom-module id="downloads-item">
...@@ -93,11 +94,16 @@ ...@@ -93,11 +94,16 @@
margin: 0 24px; margin: 0 24px;
} }
.icon { .icon,
#file-icon-wrapper {
height: 32px; height: 32px;
width: 32px; width: 32px;
} }
#file-icon-wrapper {
overflow: hidden; /* Reduces file icon flicker on initial load. */
}
#content:-webkit-any(.show-progress, .dangerous) #file-icon-wrapper { #content:-webkit-any(.show-progress, .dangerous) #file-icon-wrapper {
/* TODO(dbeam): animate from top-aligned to centered when items finish? /* TODO(dbeam): animate from top-aligned to centered when items finish?
*/ */
...@@ -110,12 +116,11 @@ ...@@ -110,12 +116,11 @@
opacity: .5; opacity: .5;
} }
#danger-icon { #file-icon-wrapper iron-icon[icon='cr:insert-drive-file'] {
height: 32px; color: var(--paper-grey-400);
width: 32px;
} }
#danger-icon[icon='cr:warning'], #file-icon-wrapper iron-icon[icon='cr:warning'],
.dangerous #description { .dangerous #description {
color: var(--google-red-700); color: var(--google-red-700);
} }
...@@ -233,10 +238,10 @@ ...@@ -233,10 +238,10 @@
<div id="content" on-dragstart="onDragStart_" <div id="content" on-dragstart="onDragStart_"
class$="[[computeClass_(isActive_, isDangerous_, showProgress_)]]"> class$="[[computeClass_(isActive_, isDangerous_, showProgress_)]]">
<div id="file-icon-wrapper" class="icon-wrapper"> <div id="file-icon-wrapper" class="icon-wrapper">
<img class="icon" id="file-icon" alt="" hidden="[[isDangerous_]]"> <img class="icon" id="file-icon" alt="" hidden="[[!useFileIcon_]]">
<iron-icon id="danger-icon" <iron-icon class="icon" icon$="[[computeIcon_(
icon$="[[computeDangerIcon_(isDangerous_, data.dangerType)]]" isDangerous_, data.dangerType, useFileIcon_)]]"
hidden="[[!isDangerous_]]"></iron-icon> hidden="[[useFileIcon_]]"></iron-icon>
</div> </div>
<div id="details"> <div id="details">
......
...@@ -85,6 +85,8 @@ cr.define('downloads', function() { ...@@ -85,6 +85,8 @@ cr.define('downloads', function() {
type: Boolean, type: Boolean,
value: false, value: false,
}, },
useFileIcon_: Boolean,
}, },
observers: [ observers: [
...@@ -103,6 +105,11 @@ cr.define('downloads', function() { ...@@ -103,6 +105,11 @@ cr.define('downloads', function() {
this.content = this.$.content; this.content = this.$.content;
}, },
/** @return {!HTMLElement} */
getFileIcon: function() {
return assert(this.$['file-icon']);
},
/** /**
* @param {string} url * @param {string} url
* @return {string} A reasonably long URL. * @return {string} A reasonably long URL.
...@@ -159,14 +166,6 @@ cr.define('downloads', function() { ...@@ -159,14 +166,6 @@ cr.define('downloads', function() {
'controlRemoveFromListAriaLabel', this.data.fileName); 'controlRemoveFromListAriaLabel', this.data.fileName);
}, },
/**
* @return {string}
* @private
*/
computeDangerIcon_: function() {
return this.isDangerous_ ? 'cr:warning' : '';
},
/** /**
* @return {string} * @return {string}
* @private * @private
...@@ -213,6 +212,18 @@ cr.define('downloads', function() { ...@@ -213,6 +212,18 @@ cr.define('downloads', function() {
return ''; return '';
}, },
/**
* @return {string}
* @private
*/
computeIcon_: function() {
if (this.isDangerous_)
return 'cr:warning';
if (!this.useFileIcon_)
return 'cr:insert-drive-file';
return '';
},
/** /**
* @return {boolean} * @return {boolean}
* @private * @private
...@@ -345,11 +356,16 @@ cr.define('downloads', function() { ...@@ -345,11 +356,16 @@ cr.define('downloads', function() {
if (this.isDangerous_) { if (this.isDangerous_) {
this.$.url.removeAttribute('href'); this.$.url.removeAttribute('href');
this.useFileIcon_ = false;
} else { } else {
this.$.url.href = assert(this.data.url); this.$.url.href = assert(this.data.url);
const filePath = encodeURIComponent(this.data.filePath); const path = this.data.filePath;
const scaleFactor = `?scale=${window.devicePixelRatio}x`; downloads.IconLoader.getInstance()
this.$['file-icon'].src = `chrome://fileicon/${filePath}${scaleFactor}`; .loadIcon(this.$['file-icon'], path)
.then(success => {
if (path == this.data.filePath)
this.useFileIcon_ = success;
});
} }
}, },
......
...@@ -666,7 +666,7 @@ cr.define('print_preview', function() { ...@@ -666,7 +666,7 @@ cr.define('print_preview', function() {
return 'print-preview:save-to-drive'; return 'print-preview:save-to-drive';
} }
if (this.id_ == Destination.GooglePromotedId.SAVE_AS_PDF) { if (this.id_ == Destination.GooglePromotedId.SAVE_AS_PDF) {
return 'print-preview:insert-drive-file'; return 'cr:insert-drive-file';
} }
if (this.isEnterprisePrinter) { if (this.isEnterprisePrinter) {
return 'print-preview:business'; return 'print-preview:business';
......
...@@ -27,7 +27,6 @@ ...@@ -27,7 +27,6 @@
See http://goo.gl/Y1OdAq for instructions on adding additional icons. See http://goo.gl/Y1OdAq for instructions on adding additional icons.
--> -->
<g id="business"><path d="M12 7V3H2v18h20V7H12zM6 19H4v-2h2v2zm0-4H4v-2h2v2zm0-4H4V9h2v2zm0-4H4V5h2v2zm4 12H8v-2h2v2zm0-4H8v-2h2v2zm0-4H8V9h2v2zm0-4H8V5h2v2zm10 12h-8v-2h2v-2h-2v-2h2v-2h-2V9h8v10zm-2-8h-2v2h2v-2zm0 4h-2v2h2v-2z"></path></g> <g id="business"><path d="M12 7V3H2v18h20V7H12zM6 19H4v-2h2v2zm0-4H4v-2h2v2zm0-4H4V9h2v2zm0-4H4V5h2v2zm4 12H8v-2h2v2zm0-4H8v-2h2v2zm0-4H8V9h2v2zm0-4H8V5h2v2zm10 12h-8v-2h2v-2h-2v-2h2v-2h-2V9h8v10zm-2-8h-2v2h2v-2zm0 4h-2v2h2v-2z"></path></g>
<g id="insert-drive-file"><path d="M6 2c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6H6zm7 7V3.5L18.5 9H13z"></path></g>
<g id="smartphone"><path d="M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14z"></path></g> <g id="smartphone"><path d="M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14z"></path></g>
<g id="cloud-queue"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4s1.79-4 4-4h.71C7.37 7.69 9.48 6 12 6c3.04 0 5.5 2.46 5.5 5.5v.5H19c1.66 0 3 1.34 3 3s-1.34 3-3 3z"></path></g> <g id="cloud-queue"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4s1.79-4 4-4h.71C7.37 7.69 9.48 6 12 6c3.04 0 5.5 2.46 5.5 5.5v.5H19c1.66 0 3 1.34 3 3s-1.34 3-3 3z"></path></g>
<g id="print"><path d="M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z"></path></g> <g id="print"><path d="M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z"></path></g>
......
<link rel="import" href="chrome://resources/html/polymer.html"> <link rel="import" href="chrome://resources/html/polymer.html">
<link rel="import" href="chrome://resources/cr_elements/cr_container_shadow_behavior.html"> <link rel="import" href="chrome://resources/cr_elements/cr_container_shadow_behavior.html">
<link rel="import" href="chrome://resources/cr_elements/icons.html">
<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> <link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> <link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
<link rel="import" href="chrome://resources/html/cr.html"> <link rel="import" href="chrome://resources/html/cr.html">
......
...@@ -127,6 +127,9 @@ content::WebUIDataSource* CreateDownloadsUIHTMLSource(Profile* profile) { ...@@ -127,6 +127,9 @@ content::WebUIDataSource* CreateDownloadsUIHTMLSource(Profile* profile) {
source->AddResourcePath("constants.js", IDR_MD_DOWNLOADS_CONSTANTS_JS); source->AddResourcePath("constants.js", IDR_MD_DOWNLOADS_CONSTANTS_JS);
source->AddResourcePath("downloads.js", IDR_MD_DOWNLOADS_DOWNLOADS_JS); source->AddResourcePath("downloads.js", IDR_MD_DOWNLOADS_DOWNLOADS_JS);
source->AddResourcePath("i18n_setup.html", IDR_MD_DOWNLOADS_I18N_SETUP_HTML); source->AddResourcePath("i18n_setup.html", IDR_MD_DOWNLOADS_I18N_SETUP_HTML);
source->AddResourcePath("icon_loader.html",
IDR_MD_DOWNLOADS_ICON_LOADER_HTML);
source->AddResourcePath("icon_loader.js", IDR_MD_DOWNLOADS_ICON_LOADER_JS);
source->AddResourcePath("icons.html", IDR_MD_DOWNLOADS_ICONS_HTML); source->AddResourcePath("icons.html", IDR_MD_DOWNLOADS_ICONS_HTML);
source->AddResourcePath("item.html", IDR_MD_DOWNLOADS_ITEM_HTML); source->AddResourcePath("item.html", IDR_MD_DOWNLOADS_ITEM_HTML);
source->AddResourcePath("item.js", IDR_MD_DOWNLOADS_ITEM_JS); source->AddResourcePath("item.js", IDR_MD_DOWNLOADS_ITEM_JS);
......
...@@ -33,6 +33,12 @@ function testGetFavicon_IconUrl() { ...@@ -33,6 +33,12 @@ function testGetFavicon_IconUrl() {
assertEquals(expected, cr.icon.getFavicon(url)); assertEquals(expected, cr.icon.getFavicon(url));
} }
function testGetFileIconUrl() {
assertEquals(cr.icon.getFileIconUrl('file path'),
'chrome://fileicon/file%20path?scale=' +
window.devicePixelRatio + 'x');
}
</script> </script>
</body> </body>
</html> </html>
...@@ -47,6 +47,9 @@ DownloadsItemTest.prototype = { ...@@ -47,6 +47,9 @@ DownloadsItemTest.prototype = {
/** @override */ /** @override */
extraLibraries: DownloadsTest.prototype.extraLibraries.concat([ extraLibraries: DownloadsTest.prototype.extraLibraries.concat([
ROOT_PATH + 'ui/webui/resources/js/cr.js',
'../test_browser_proxy.js',
'test_support.js',
'item_tests.js', 'item_tests.js',
]), ]),
}; };
......
...@@ -6,23 +6,52 @@ suite('item tests', function() { ...@@ -6,23 +6,52 @@ suite('item tests', function() {
/** @type {!downloads.Item} */ /** @type {!downloads.Item} */
let item; let item;
/** @type {!TestIconLoader} */
let testIconLoader;
setup(function() { setup(function() {
PolymerTest.clearBody(); PolymerTest.clearBody();
// This isn't strictly necessary, but is a probably good idea.
downloads.BrowserProxy.instance_ = new TestDownloadsProxy;
testIconLoader = new TestIconLoader;
downloads.IconLoader.instance_ = testIconLoader;
item = document.createElement('downloads-item'); item = document.createElement('downloads-item');
document.body.appendChild(item); document.body.appendChild(item);
}); });
test('dangerous downloads aren\'t linkable', function() { test('dangerous downloads aren\'t linkable', () => {
item.set('data', { item.set('data', createDownload({
dangerType: downloads.DangerType.DANGEROUS_FILE, dangerType: downloads.DangerType.DANGEROUS_FILE,
fileExternallyRemoved: false, fileExternallyRemoved: false,
hideDate: true, hideDate: true,
state: downloads.States.DANGEROUS, state: downloads.States.DANGEROUS,
url: 'http://evil.com' url: 'http://evil.com'
}); }));
Polymer.dom.flush(); Polymer.dom.flush();
assertTrue(item.$['file-link'].hidden); assertTrue(item.$['file-link'].hidden);
assertFalse(item.$.url.hasAttribute('href')); assertFalse(item.$.url.hasAttribute('href'));
}); });
test('icon loads successfully', async () => {
testIconLoader.setShouldIconsLoad(true);
item.set('data', createDownload({filePath: 'unique1', hideDate: false}));
const loadedPath = await testIconLoader.whenCalled('loadIcon');
assertEquals(loadedPath, 'unique1');
Polymer.dom.flush();
assertFalse(item.getFileIcon().hidden);
});
test('icon fails to load', async () => {
testIconLoader.setShouldIconsLoad(false);
item.set('data', createDownload({filePath: 'unique2', hideDate: false}));
item.set('data', createDownload({hideDate: false}));
const loadedPath = await testIconLoader.whenCalled('loadIcon');
assertEquals(loadedPath, 'unique2');
Polymer.dom.flush();
assertTrue(item.getFileIcon().hidden);
});
}); });
...@@ -81,6 +81,29 @@ class TestDownloadsMojoHandler { ...@@ -81,6 +81,29 @@ class TestDownloadsMojoHandler {
openDownloadsFolderRequiringGesture() {} openDownloadsFolderRequiringGesture() {}
} }
class TestIconLoader extends TestBrowserProxy {
constructor() {
super(['loadIcon']);
/** @private */
this.shouldIconsLoad_ = true;
}
/** @param {boolean} shouldIconsLoad */
setShouldIconsLoad(shouldIconsLoad) {
this.shouldIconsLoad_ = shouldIconsLoad;
}
/**
* @param {!HTMLImageElement} imageEl
* @param {string} filePath
*/
loadIcon(imageEl, filePath) {
this.methodCalled('loadIcon', filePath);
return Promise.resolve(this.shouldIconsLoad_);
}
}
/** /**
* @param {Object=} config * @param {Object=} config
* @return {!downloads.Data} * @return {!downloads.Data}
......
...@@ -52,6 +52,7 @@ blurry at 20 px). Please use 20 px icons when available. ...@@ -52,6 +52,7 @@ blurry at 20 px). Please use 20 px icons when available.
<g id="fullscreen"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"></path></g> <g id="fullscreen"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"></path></g>
<g id="group"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"></path></g> <g id="group"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"></path></g>
<g id="info"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"></path></g> <g id="info"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"></path></g>
<g id="insert-drive-file"><path d="M6 2c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6H6zm7 7V3.5L18.5 9H13z"></path></g>
<g id="more-vert"><path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"></path></g> <g id="more-vert"><path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"></path></g>
<g id="open-in-new"><path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"></path></g> <g id="open-in-new"><path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"></path></g>
<g id="person"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"></path></g> <g id="person"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"></path></g>
......
...@@ -30,6 +30,16 @@ cr.define('cr.icon', function() { ...@@ -30,6 +30,16 @@ cr.define('cr.icon', function() {
return supportedScaleFactors; return supportedScaleFactors;
} }
/**
* A URL for the filetype icon for |filePath|. OS and theme dependent.
* @param {string} filePath
* @return {string}
*/
function getFileIconUrl(filePath) {
return 'chrome://fileicon/' + encodeURIComponent(filePath) +
'?scale=' + window.devicePixelRatio + 'x';
}
/** /**
* Generates a CSS -webkit-image-set for a chrome:// url. * Generates a CSS -webkit-image-set for a chrome:// url.
* An entry in the image set is added for each of getSupportedScaleFactors(). * An entry in the image set is added for each of getSupportedScaleFactors().
...@@ -100,5 +110,6 @@ cr.define('cr.icon', function() { ...@@ -100,5 +110,6 @@ cr.define('cr.icon', function() {
return { return {
getImage: getImage, getImage: getImage,
getFavicon: getFavicon, getFavicon: getFavicon,
getFileIconUrl: getFileIconUrl,
}; };
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment