Fix dictionary image support (#1526)
* Fix content security policy for images * Add createBlobFromBase64Content to MediaUtil * Update MediaLoader to use MediaUtil * Use blob URLs when importing dictionaries * Update VM's URL to support createObjectURL and revokeObjectURL * Fix test
This commit is contained in:
parent
52a4d874ea
commit
07df1e0117
@ -114,7 +114,7 @@
|
||||
"popup.html",
|
||||
"template-renderer.html"
|
||||
],
|
||||
"content_security_policy": "default-src 'self'; style-src 'self' 'unsafe-inline'; media-src *; connect-src *"
|
||||
"content_security_policy": "default-src 'self'; img-src blob: 'self'; style-src 'self' 'unsafe-inline'; media-src *; connect-src *"
|
||||
},
|
||||
"variants": [
|
||||
{
|
||||
@ -194,7 +194,7 @@
|
||||
{
|
||||
"action": "set",
|
||||
"path": ["content_security_policy"],
|
||||
"value": "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; connect-src *"
|
||||
"value": "default-src 'self'; script-src 'self' 'unsafe-eval'; img-src blob: 'self'; style-src 'self' 'unsafe-inline'; media-src *; connect-src *"
|
||||
},
|
||||
{
|
||||
"action": "set",
|
||||
|
@ -76,6 +76,13 @@ class Image {
|
||||
}
|
||||
}
|
||||
|
||||
class Blob {
|
||||
constructor(array, options) {
|
||||
this._array = array;
|
||||
this._options = options;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetch(url2) {
|
||||
const filePath = url.fileURLToPath(url2);
|
||||
await Promise.resolve();
|
||||
@ -89,15 +96,21 @@ async function fetch(url2) {
|
||||
};
|
||||
}
|
||||
|
||||
function atob(data) {
|
||||
return Buffer.from(data, 'base64').toString('ascii');
|
||||
}
|
||||
|
||||
class DatabaseVM extends VM {
|
||||
constructor() {
|
||||
super({
|
||||
chrome,
|
||||
Image,
|
||||
Blob,
|
||||
fetch,
|
||||
indexedDB: global.indexedDB,
|
||||
IDBKeyRange: global.IDBKeyRange,
|
||||
JSZip
|
||||
JSZip,
|
||||
atob
|
||||
});
|
||||
this.context.window = this.context;
|
||||
this.indexedDB = global.indexedDB;
|
||||
|
22
dev/vm.js
22
dev/vm.js
@ -19,6 +19,7 @@ const fs = require('fs');
|
||||
const vm = require('vm');
|
||||
const path = require('path');
|
||||
const assert = require('assert');
|
||||
const crypto = require('crypto');
|
||||
|
||||
|
||||
function getContextEnvironmentRecords(context, names) {
|
||||
@ -115,9 +116,9 @@ function deepStrictEqual(actual, expected) {
|
||||
}
|
||||
|
||||
|
||||
function createURLClass() {
|
||||
function createURLClass(urlMap) {
|
||||
const BaseURL = URL;
|
||||
return function URL(url) {
|
||||
const result = function URL(url) {
|
||||
const u = new BaseURL(url);
|
||||
this.hash = u.hash;
|
||||
this.host = u.host;
|
||||
@ -132,12 +133,23 @@ function createURLClass() {
|
||||
this.searchParams = u.searchParams;
|
||||
this.username = u.username;
|
||||
};
|
||||
result.createObjectURL = (object) => {
|
||||
const id = crypto.randomBytes(16).toString('hex');
|
||||
const url = `blob:${id}`;
|
||||
urlMap.set(url, object);
|
||||
return url;
|
||||
};
|
||||
result.revokeObjectURL = (url) => {
|
||||
urlMap.delete(url);
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
class VM {
|
||||
constructor(context={}) {
|
||||
context.URL = createURLClass();
|
||||
this._urlMap = new Map();
|
||||
context.URL = createURLClass(this._urlMap);
|
||||
this._context = vm.createContext(context);
|
||||
this._assert = {
|
||||
deepStrictEqual
|
||||
@ -186,6 +198,10 @@ class VM {
|
||||
|
||||
return single ? results[0] : results;
|
||||
}
|
||||
|
||||
getUrlObject(url) {
|
||||
return this._urlMap.get(url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -400,7 +400,9 @@ class DictionaryImporter {
|
||||
eventListeners.removeAllEventListeners();
|
||||
reject(new Error('Image failed to load'));
|
||||
}, false);
|
||||
image.src = `data:${mediaType};base64,${content}`;
|
||||
const blob = MediaUtil.createBlobFromBase64Content(content, mediaType);
|
||||
const url = URL.createObjectURL(blob);
|
||||
image.src = url;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,10 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* global
|
||||
* MediaUtil
|
||||
*/
|
||||
|
||||
class MediaLoader {
|
||||
constructor() {
|
||||
this._token = {};
|
||||
@ -82,22 +86,11 @@ class MediaLoader {
|
||||
const token = this._token;
|
||||
const data = (await yomichan.api.getMedia([{path, dictionaryName}]))[0];
|
||||
if (token === this._token && data !== null) {
|
||||
const contentArrayBuffer = this._base64ToArrayBuffer(data.content);
|
||||
const blob = new Blob([contentArrayBuffer], {type: data.mediaType});
|
||||
const blob = MediaUtil.createBlobFromBase64Content(data.content, data.mediaType);
|
||||
const url = URL.createObjectURL(blob);
|
||||
cachedData.data = data;
|
||||
cachedData.url = url;
|
||||
}
|
||||
return cachedData;
|
||||
}
|
||||
|
||||
_base64ToArrayBuffer(content) {
|
||||
const binaryContent = window.atob(content);
|
||||
const length = binaryContent.length;
|
||||
const array = new Uint8Array(length);
|
||||
for (let i = 0; i < length; ++i) {
|
||||
array[i] = binaryContent.charCodeAt(i);
|
||||
}
|
||||
return array.buffer;
|
||||
}
|
||||
}
|
||||
|
@ -129,4 +129,20 @@ class MediaUtil {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new `Blob` object from a base64 string of content.
|
||||
* @param content The binary content string encoded in base64.
|
||||
* @param mediaType The type of the media.
|
||||
* @returns A new `Blob` object corresponding to the specified content.
|
||||
*/
|
||||
static createBlobFromBase64Content(content, mediaType) {
|
||||
const binaryContent = atob(content);
|
||||
const length = binaryContent.length;
|
||||
const array = new Uint8Array(length);
|
||||
for (let i = 0; i < length; ++i) {
|
||||
array[i] = binaryContent.charCodeAt(i);
|
||||
}
|
||||
return new Blob([array.buffer], {type: mediaType});
|
||||
}
|
||||
}
|
||||
|
@ -113,5 +113,5 @@
|
||||
"popup.html",
|
||||
"template-renderer.html"
|
||||
],
|
||||
"content_security_policy": "default-src 'self'; style-src 'self' 'unsafe-inline'; media-src *; connect-src *"
|
||||
"content_security_policy": "default-src 'self'; img-src blob: 'self'; style-src 'self' 'unsafe-inline'; media-src *; connect-src *"
|
||||
}
|
||||
|
@ -123,6 +123,7 @@
|
||||
<script src="/js/language/text-scanner.js"></script>
|
||||
<script src="/js/media/audio-system.js"></script>
|
||||
<script src="/js/media/media-loader.js"></script>
|
||||
<script src="/js/media/media-util.js"></script>
|
||||
<script src="/js/media/text-to-speech-audio.js"></script>
|
||||
<script src="/js/script/dynamic-loader.js"></script>
|
||||
<script src="/js/templates/template-renderer-proxy.js"></script>
|
||||
|
@ -107,6 +107,7 @@
|
||||
<script src="/js/language/text-scanner.js"></script>
|
||||
<script src="/js/media/audio-system.js"></script>
|
||||
<script src="/js/media/media-loader.js"></script>
|
||||
<script src="/js/media/media-util.js"></script>
|
||||
<script src="/js/media/text-to-speech-audio.js"></script>
|
||||
<script src="/js/script/dynamic-loader.js"></script>
|
||||
<script src="/js/templates/template-renderer-proxy.js"></script>
|
||||
|
Loading…
Reference in New Issue
Block a user