Commit 5bdc2511 authored by Tibor Goldschwendt's avatar Tibor Goldschwendt Committed by Commit Bot

[webui][ntp] Support local images with ntp-img

That way we can assign both local and external URLs and ntp-img will
figure out whether to load the image directly or employ the
chrome://image source.

+ Use this new functionality for the middle slot promo.

Change-Id: I4855023ff5bdb946b14d2f2f2bf46f1bc29d2e7a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2404153Reviewed-by: default avatarRebekah Potter <rbpotter@chromium.org>
Commit-Queue: Tibor Goldschwendt <tiborg@chromium.org>
Auto-Submit: Tibor Goldschwendt <tiborg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#806280}
parent d71ad353
...@@ -3,42 +3,62 @@ ...@@ -3,42 +3,62 @@
// found in the LICENSE file. // found in the LICENSE file.
/** /**
* @fileoverview <ntp-img> is specialized <img> that lets you embed external * @fileoverview <ntp-img> is a specialized <img> that facilitates embedding
* images via its external-src attribute. Usage: * images into WebUIs via its auto-src attribute. <ntp-img> automatically
* determines if the image is local (e.g. data: or chrome://) or external (e.g.
* https://), and embeds the image directly or via the chrome://image data
* source accordingly. Usage:
* *
* 1. In C++ register |SanitizedImageSource| for your WebUI. * 1. In C++ register |SanitizedImageSource| for your WebUI.
* *
* 2. In HTML instantiate * 2. In HTML instantiate
* *
* <img is="ntp-img" external-src="https://foo.com/bar.png"></img> * <img is="ntp-img" auto-src="https://foo.com/bar.png"></img>
* *
* NOTE: Internally, <ntp-img> uses the chrome://image data source. This means * NOTE: Since <ntp-img> may use the chrome://image data source some images may
* the external image will be transcoded to PNG. * be transcoded to PNG.
*/ */
/** @type {string} */ /** @type {string} */
const EXTERNAL_SRC = 'external-src'; const AUTO_SRC = 'auto-src';
export class ImgElement extends HTMLImageElement { export class ImgElement extends HTMLImageElement {
static get observedAttributes() { static get observedAttributes() {
return [EXTERNAL_SRC]; return [AUTO_SRC];
} }
/** @override */ /** @override */
attributeChangedCallback(name, oldValue, newValue) { attributeChangedCallback(name, oldValue, newValue) {
if (name === EXTERNAL_SRC) { if (name !== AUTO_SRC) {
this.src = newValue ? 'chrome://image?' + newValue : ''; return;
}
let url = null;
try {
url = new URL(newValue || '');
} catch (_) {
}
if (!url || url.protocol === 'chrome-untrusted:') {
// Loading chrome-untrusted:// directly kills the renderer process.
// Loading chrome-untrusted:// via the chrome://image data source
// results in a broken image.
this.removeAttribute('src');
} else if (url.protocol === 'data:' || url.protocol === 'chrome:') {
this.src = url.href;
} else {
this.src = 'chrome://image?' + url.href;
} }
} }
/** @param {string} src */ /** @param {string} src */
set externalSrc(src) { set autoSrc(src) {
this.setAttribute(EXTERNAL_SRC, src); this.setAttribute(AUTO_SRC, src);
} }
/** @return {string} */ /** @return {string} */
get externalSrc() { get autoSrc() {
return this.getAttribute(EXTERNAL_SRC); return this.getAttribute(AUTO_SRC);
} }
} }
......
...@@ -6,7 +6,9 @@ import 'chrome://resources/cr_elements/hidden_style_css.m.js'; ...@@ -6,7 +6,9 @@ import 'chrome://resources/cr_elements/hidden_style_css.m.js';
import 'chrome://resources/cr_elements/shared_vars_css.m.js'; import 'chrome://resources/cr_elements/shared_vars_css.m.js';
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {BrowserProxy} from './browser_proxy.js'; import {BrowserProxy} from './browser_proxy.js';
import {ImgElement} from './img.js';
import {PromoBrowserCommandProxy} from './promo_browser_command_proxy.js'; import {PromoBrowserCommandProxy} from './promo_browser_command_proxy.js';
// Element that requests and renders the middle-slot promo. The element is // Element that requests and renders the middle-slot promo. The element is
...@@ -37,10 +39,8 @@ class MiddleSlotPromoElement extends PolymerElement { ...@@ -37,10 +39,8 @@ class MiddleSlotPromoElement extends PolymerElement {
promo.middleSlotParts.forEach(({image, link, text}) => { promo.middleSlotParts.forEach(({image, link, text}) => {
let el; let el;
if (image) { if (image) {
el = document.createElement('img'); el = new ImgElement();
el.src = image.imageUrl.url.startsWith('data:') ? el.autoSrc = image.imageUrl.url;
image.imageUrl.url :
`chrome://image?${image.imageUrl.url}`;
if (image.target) { if (image.target) {
const anchor = this.createAnchor_(image.target); const anchor = this.createAnchor_(image.target);
if (anchor) { if (anchor) {
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
<cr-grid id="tiles" columns="3"> <cr-grid id="tiles" columns="3">
<template id="tileList" is="dom-repeat" items="[[tiles]]"> <template id="tileList" is="dom-repeat" items="[[tiles]]">
<div class="tile-item" title="[[item.label]]"> <div class="tile-item" title="[[item.label]]">
<img is="ntp-img" external-src="[[item.imageUrl]]"></img> <img is="ntp-img" auto-src="[[item.imageUrl]]"></img>
<span>[[item.value]]</span> <span>[[item.value]]</span>
</div> </div>
</template> </template>
......
...@@ -16,23 +16,30 @@ suite('NewTabPageImgTest', () => { ...@@ -16,23 +16,30 @@ suite('NewTabPageImgTest', () => {
document.body.appendChild(img); document.body.appendChild(img);
}); });
test('setting externalSrc sets src', () => { [['https://foo.com/img.png', 'chrome://image/?https://foo.com/img.png'],
// Act. ['chrome://foo/img.png', 'chrome://foo/img.png'],
img.externalSrc = 'foo.com/img.png'; ['data:imge/png;base64,abc', 'data:imge/png;base64,abc'],
['', ''],
// Assert. ['chrome-untrusted://foo/img.png', ''],
assertEquals('foo.com/img.png', img.externalSrc); ].forEach(([autoSrc, src]) => {
assertEquals('foo.com/img.png', img.getAttribute('external-src')); test(`setting autoSrc to '${autoSrc}' sets src to '${src}'`, () => {
assertEquals('chrome://image/?foo.com/img.png', img.src); // Act.
}); img.autoSrc = autoSrc;
test('setting external-src sets src', () => { // Assert.
// Act. assertEquals(autoSrc, img.autoSrc);
img.setAttribute('external-src', 'foo.com/img.png'); assertEquals(autoSrc, img.getAttribute('auto-src'));
assertEquals(src, img.src);
// Assert. });
assertEquals('foo.com/img.png', img.externalSrc);
assertEquals('foo.com/img.png', img.getAttribute('external-src')); test(`setting auto-src to '${autoSrc}' sets src to '${src}'`, () => {
assertEquals('chrome://image/?foo.com/img.png', img.src); // Act.
img.setAttribute('auto-src', autoSrc);
// Assert.
assertEquals(autoSrc, img.autoSrc);
assertEquals(autoSrc, img.getAttribute('auto-src'));
assertEquals(src, img.src);
});
}); });
}); });
...@@ -67,15 +67,13 @@ suite('NewTabPageMiddleSlotPromoTest', () => { ...@@ -67,15 +67,13 @@ suite('NewTabPageMiddleSlotPromoTest', () => {
assertEquals(6, parts.length); assertEquals(6, parts.length);
const [image, imageWithLink, imageWithCommand, text, link, command] = parts; const [image, imageWithLink, imageWithCommand, text, link, command] = parts;
assertEquals('chrome://image/?https://image', image.src); assertEquals('https://image', image.autoSrc);
assertEquals('https://link/', imageWithLink.href); assertEquals('https://link/', imageWithLink.href);
assertEquals( assertEquals('https://image', imageWithLink.children[0].autoSrc);
'chrome://image/?https://image', imageWithLink.children[0].src);
assertEquals('', imageWithCommand.href); assertEquals('', imageWithCommand.href);
assertEquals( assertEquals('https://image', imageWithCommand.children[0].autoSrc);
'chrome://image/?https://image', imageWithCommand.children[0].src);
assertEquals('text', text.innerText); assertEquals('text', text.innerText);
assertEquals('red', text.style.color); assertEquals('red', text.style.color);
......
...@@ -28,6 +28,6 @@ suite('NewTabPageModulesDummyModuleTest', () => { ...@@ -28,6 +28,6 @@ suite('NewTabPageModulesDummyModuleTest', () => {
'4IP40Q18w6aDF4oS4WRnUj0MlCCKPK-vLHqSd4r-RfS6Jx' + '4IP40Q18w6aDF4oS4WRnUj0MlCCKPK-vLHqSd4r-RfS6Jx' +
'gblG5WJuRYpkJkoTzLMS0qv3Sxhf9wdaKkn3vHnyy6oe7Ah' + 'gblG5WJuRYpkJkoTzLMS0qv3Sxhf9wdaKkn3vHnyy6oe7Ah' +
'5y0=w170-h85-p-k-no-nd-mv', '5y0=w170-h85-p-k-no-nd-mv',
tiles[2].querySelector('img').externalSrc); tiles[2].querySelector('img').autoSrc);
}); });
}); });
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