diff --git a/res/css/views/messages/_MFileBody.scss b/res/css/views/messages/_MFileBody.scss index e219c0c5e4..b45126acf8 100644 --- a/res/css/views/messages/_MFileBody.scss +++ b/res/css/views/messages/_MFileBody.scss @@ -1,5 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd +Copyright 2015, 2016, 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,6 +16,19 @@ limitations under the License. .mx_MFileBody_download { color: $accent-color; + + .mx_MFileBody_download_icon { + // 12px instead of 14px to better match surrounding font size + width: 12px; + height: 12px; + mask-size: 12px; + + mask-position: center; + mask-repeat: no-repeat; + mask-image: url("$(res)/img/download.svg"); + background-color: $accent-color; + display: inline-block; + } } .mx_MFileBody_download a { diff --git a/src/components/views/messages/MFileBody.js b/src/components/views/messages/MFileBody.js index 39a03a1304..a9bedd1927 100644 --- a/src/components/views/messages/MFileBody.js +++ b/src/components/views/messages/MFileBody.js @@ -1,6 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2018 New Vector Ltd +Copyright 2015, 2016, 2018, 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,52 +17,24 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; import filesize from 'filesize'; -import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import {decryptFile} from '../../../utils/DecryptFile'; -import Tinter from '../../../Tinter'; -import request from 'browser-request'; import Modal from '../../../Modal'; import AccessibleButton from "../elements/AccessibleButton"; import {replaceableComponent} from "../../../utils/replaceableComponent"; import {mediaFromContent} from "../../../customisations/Media"; +import ErrorDialog from "../dialogs/ErrorDialog"; +let downloadIconUrl; // cached copy of the download.svg asset for the sandboxed iframe later on -// A cached tinted copy of require("../../../../res/img/download.svg") -let tintedDownloadImageURL; -// Track a list of mounted MFileBody instances so that we can update -// the require("../../../../res/img/download.svg") when the tint changes. -let nextMountId = 0; -const mounts = {}; - -/** - * Updates the tinted copy of require("../../../../res/img/download.svg") when the tint changes. - */ -function updateTintedDownloadImage() { - // Download the svg as an XML document. - // We could cache the XML response here, but since the tint rarely changes - // it's probably not worth it. - // Also note that we can't use fetch here because fetch doesn't support - // file URLs, which the download image will be if we're running from - // the filesystem (like in an Electron wrapper). - request({uri: require("../../../../res/img/download.svg")}, (err, response, body) => { - if (err) return; - - const svg = new DOMParser().parseFromString(body, "image/svg+xml"); - // Apply the fixups to the XML. - const fixups = Tinter.calcSvgFixups([{contentDocument: svg}]); - Tinter.applySvgFixups(fixups); - // Encoded the fixed up SVG as a data URL. - const svgString = new XMLSerializer().serializeToString(svg); - tintedDownloadImageURL = "data:image/svg+xml;base64," + window.btoa(svgString); - // Notify each mounted MFileBody that the URL has changed. - Object.keys(mounts).forEach(function(id) { - mounts[id].tint(); - }); - }); +async function cacheDownloadIcon() { + if (downloadIconUrl) return; // cached already + const svg = await fetch(require("../../../../res/img/download.svg")).then(r => r.text()); + downloadIconUrl = "data:image/svg+xml;base64," + window.btoa(svg); } -Tinter.registerTintable(updateTintedDownloadImage); +// Cache the asset immediately +cacheDownloadIcon(); // User supplied content can contain scripts, we have to be careful that // we don't accidentally run those script within the same origin as the @@ -106,6 +77,7 @@ function computedStyle(element) { } const style = window.getComputedStyle(element, null); let cssText = style.cssText; + // noinspection EqualityComparisonWithCoercionJS if (cssText == "") { // Firefox doesn't implement ".cssText" for computed styles. // https://bugzilla.mozilla.org/show_bug.cgi?id=137687 @@ -145,7 +117,6 @@ export default class MFileBody extends React.Component { this._iframe = createRef(); this._dummyLink = createRef(); - this._downloadImage = createRef(); } /** @@ -182,48 +153,18 @@ export default class MFileBody extends React.Component { return media.srcHttp; } - componentDidMount() { - // Add this to the list of mounted components to receive notifications - // when the tint changes. - this.id = nextMountId++; - mounts[this.id] = this; - this.tint(); - } - componentDidUpdate(prevProps, prevState) { if (this.props.onHeightChanged && !prevState.decryptedBlob && this.state.decryptedBlob) { this.props.onHeightChanged(); } } - componentWillUnmount() { - // Remove this from the list of mounted components - delete mounts[this.id]; - } - - tint = () => { - // Update our tinted copy of require("../../../../res/img/download.svg") - if (this._downloadImage.current) { - this._downloadImage.current.src = tintedDownloadImageURL; - } - if (this._iframe.current) { - // If the attachment is encrypted then the download image - // will be inside the iframe so we wont be able to update - // it directly. - this._iframe.current.contentWindow.postMessage({ - imgSrc: tintedDownloadImageURL, - style: computedStyle(this._dummyLink.current), - }, "*"); - } - }; - render() { const content = this.props.mxEvent.getContent(); const text = this.presentableTextForFile(content); const isEncrypted = content.file !== undefined; const fileName = content.body && content.body.length > 0 ? content.body : _t("Attachment"); const contentUrl = this._getContentUrl(); - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const fileSize = content.info ? content.info.size : null; const fileType = content.info ? content.info.mimetype : "application/octet-stream"; @@ -280,7 +221,7 @@ export default class MFileBody extends React.Component { // When the iframe loads we tell it to render a download link const onIframeLoad = (ev) => { ev.target.contentWindow.postMessage({ - imgSrc: tintedDownloadImageURL, + imgSrc: downloadIconUrl, style: computedStyle(this._dummyLink.current), blob: this.state.decryptedBlob, // Set a download attribute for encrypted files so that the file @@ -384,7 +325,7 @@ export default class MFileBody extends React.Component { {placeholder}
- + { _t("Download %(text)s", { text: text }) }
diff --git a/src/usercontent/index.js b/src/usercontent/index.js index 6ecd17dcd7..4850dba9b9 100644 --- a/src/usercontent/index.js +++ b/src/usercontent/index.js @@ -1,10 +1,8 @@ function remoteRender(event) { const data = event.data; - const img = document.createElement("img"); + const img = document.createElement("span"); // we'll mask it as an image img.id = "img"; - img.src = data.imgSrc; - img.style = data.imgStyle; const a = document.createElement("a"); a.id = "a"; @@ -16,6 +14,21 @@ function remoteRender(event) { a.appendChild(img); a.appendChild(document.createTextNode(data.textContent)); + // Apply image style after so we can steal the anchor's colour. + // Style copied from a rendered version of mx_MFileBody_download_icon + img.style = "" + + "width: 12px; height: 12px;" + + "-webkit-mask-size: 12px;" + + "mask-size: 12px;" + + "-webkit-mask-position: center;" + + "mask-position: center;" + + "-webkit-mask-repeat: no-repeat;" + + "mask-repeat: no-repeat;" + + `-webkit-mask-image: url('${data.imgSrc}');` + + `mask-image: url('${data.imgSrc}');` + + `background-color: ${a.style.color};` + + "display: inline-block;"; + const body = document.body; // Don't display scrollbars if the link takes more than one line to display. body.style = "margin: 0px; overflow: hidden"; @@ -26,20 +39,8 @@ function remoteRender(event) { } } -function remoteSetTint(event) { - const data = event.data; - - const img = document.getElementById("img"); - img.src = data.imgSrc; - img.style = data.imgStyle; - - const a = document.getElementById("a"); - a.style = data.style; -} - window.onmessage = function(e) { if (e.origin === window.location.origin) { if (e.data.blob) remoteRender(e); - else remoteSetTint(e); } };