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",
|
"popup.html",
|
||||||
"template-renderer.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": [
|
"variants": [
|
||||||
{
|
{
|
||||||
@ -194,7 +194,7 @@
|
|||||||
{
|
{
|
||||||
"action": "set",
|
"action": "set",
|
||||||
"path": ["content_security_policy"],
|
"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",
|
"action": "set",
|
||||||
|
@ -76,6 +76,13 @@ class Image {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Blob {
|
||||||
|
constructor(array, options) {
|
||||||
|
this._array = array;
|
||||||
|
this._options = options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function fetch(url2) {
|
async function fetch(url2) {
|
||||||
const filePath = url.fileURLToPath(url2);
|
const filePath = url.fileURLToPath(url2);
|
||||||
await Promise.resolve();
|
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 {
|
class DatabaseVM extends VM {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
chrome,
|
chrome,
|
||||||
Image,
|
Image,
|
||||||
|
Blob,
|
||||||
fetch,
|
fetch,
|
||||||
indexedDB: global.indexedDB,
|
indexedDB: global.indexedDB,
|
||||||
IDBKeyRange: global.IDBKeyRange,
|
IDBKeyRange: global.IDBKeyRange,
|
||||||
JSZip
|
JSZip,
|
||||||
|
atob
|
||||||
});
|
});
|
||||||
this.context.window = this.context;
|
this.context.window = this.context;
|
||||||
this.indexedDB = global.indexedDB;
|
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 vm = require('vm');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
|
||||||
function getContextEnvironmentRecords(context, names) {
|
function getContextEnvironmentRecords(context, names) {
|
||||||
@ -115,9 +116,9 @@ function deepStrictEqual(actual, expected) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function createURLClass() {
|
function createURLClass(urlMap) {
|
||||||
const BaseURL = URL;
|
const BaseURL = URL;
|
||||||
return function URL(url) {
|
const result = function URL(url) {
|
||||||
const u = new BaseURL(url);
|
const u = new BaseURL(url);
|
||||||
this.hash = u.hash;
|
this.hash = u.hash;
|
||||||
this.host = u.host;
|
this.host = u.host;
|
||||||
@ -132,12 +133,23 @@ function createURLClass() {
|
|||||||
this.searchParams = u.searchParams;
|
this.searchParams = u.searchParams;
|
||||||
this.username = u.username;
|
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 {
|
class VM {
|
||||||
constructor(context={}) {
|
constructor(context={}) {
|
||||||
context.URL = createURLClass();
|
this._urlMap = new Map();
|
||||||
|
context.URL = createURLClass(this._urlMap);
|
||||||
this._context = vm.createContext(context);
|
this._context = vm.createContext(context);
|
||||||
this._assert = {
|
this._assert = {
|
||||||
deepStrictEqual
|
deepStrictEqual
|
||||||
@ -186,6 +198,10 @@ class VM {
|
|||||||
|
|
||||||
return single ? results[0] : results;
|
return single ? results[0] : results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUrlObject(url) {
|
||||||
|
return this._urlMap.get(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -400,7 +400,9 @@ class DictionaryImporter {
|
|||||||
eventListeners.removeAllEventListeners();
|
eventListeners.removeAllEventListeners();
|
||||||
reject(new Error('Image failed to load'));
|
reject(new Error('Image failed to load'));
|
||||||
}, false);
|
}, 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/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* global
|
||||||
|
* MediaUtil
|
||||||
|
*/
|
||||||
|
|
||||||
class MediaLoader {
|
class MediaLoader {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._token = {};
|
this._token = {};
|
||||||
@ -82,22 +86,11 @@ class MediaLoader {
|
|||||||
const token = this._token;
|
const token = this._token;
|
||||||
const data = (await yomichan.api.getMedia([{path, dictionaryName}]))[0];
|
const data = (await yomichan.api.getMedia([{path, dictionaryName}]))[0];
|
||||||
if (token === this._token && data !== null) {
|
if (token === this._token && data !== null) {
|
||||||
const contentArrayBuffer = this._base64ToArrayBuffer(data.content);
|
const blob = MediaUtil.createBlobFromBase64Content(data.content, data.mediaType);
|
||||||
const blob = new Blob([contentArrayBuffer], {type: data.mediaType});
|
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
cachedData.data = data;
|
cachedData.data = data;
|
||||||
cachedData.url = url;
|
cachedData.url = url;
|
||||||
}
|
}
|
||||||
return cachedData;
|
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;
|
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",
|
"popup.html",
|
||||||
"template-renderer.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/language/text-scanner.js"></script>
|
||||||
<script src="/js/media/audio-system.js"></script>
|
<script src="/js/media/audio-system.js"></script>
|
||||||
<script src="/js/media/media-loader.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/media/text-to-speech-audio.js"></script>
|
||||||
<script src="/js/script/dynamic-loader.js"></script>
|
<script src="/js/script/dynamic-loader.js"></script>
|
||||||
<script src="/js/templates/template-renderer-proxy.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/language/text-scanner.js"></script>
|
||||||
<script src="/js/media/audio-system.js"></script>
|
<script src="/js/media/audio-system.js"></script>
|
||||||
<script src="/js/media/media-loader.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/media/text-to-speech-audio.js"></script>
|
||||||
<script src="/js/script/dynamic-loader.js"></script>
|
<script src="/js/script/dynamic-loader.js"></script>
|
||||||
<script src="/js/templates/template-renderer-proxy.js"></script>
|
<script src="/js/templates/template-renderer-proxy.js"></script>
|
||||||
|
Loading…
Reference in New Issue
Block a user