summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2021-03-14 18:41:15 -0400
committerGitHub <noreply@github.com>2021-03-14 18:41:15 -0400
commit07df1e011794f5a77f7fb7da5cd9ea353a8747e2 (patch)
tree98a679d4ef07629b8f0121e244038c557c972bd8
parent52a4d874eada5be121e15d73d1d10e9a8d84bdb8 (diff)
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
-rw-r--r--dev/data/manifest-variants.json4
-rw-r--r--dev/database-vm.js15
-rw-r--r--dev/vm.js22
-rw-r--r--ext/js/language/dictionary-importer.js4
-rw-r--r--ext/js/media/media-loader.js17
-rw-r--r--ext/js/media/media-util.js16
-rw-r--r--ext/manifest.json2
-rw-r--r--ext/popup.html1
-rw-r--r--ext/search.html1
9 files changed, 62 insertions, 20 deletions
diff --git a/dev/data/manifest-variants.json b/dev/data/manifest-variants.json
index 14050241..14dd02e2 100644
--- a/dev/data/manifest-variants.json
+++ b/dev/data/manifest-variants.json
@@ -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",
diff --git a/dev/database-vm.js b/dev/database-vm.js
index e43daf2f..b682bca3 100644
--- a/dev/database-vm.js
+++ b/dev/database-vm.js
@@ -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;
diff --git a/dev/vm.js b/dev/vm.js
index b3fc63e7..98a1f7bf 100644
--- a/dev/vm.js
+++ b/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);
+ }
}
diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js
index b4429315..888d19b0 100644
--- a/ext/js/language/dictionary-importer.js
+++ b/ext/js/language/dictionary-importer.js
@@ -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;
});
}
}
diff --git a/ext/js/media/media-loader.js b/ext/js/media/media-loader.js
index d9d40a36..4ac733c5 100644
--- a/ext/js/media/media-loader.js
+++ b/ext/js/media/media-loader.js
@@ -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;
- }
}
diff --git a/ext/js/media/media-util.js b/ext/js/media/media-util.js
index 11172c5c..f783038a 100644
--- a/ext/js/media/media-util.js
+++ b/ext/js/media/media-util.js
@@ -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});
+ }
}
diff --git a/ext/manifest.json b/ext/manifest.json
index 7868924f..ee383e84 100644
--- a/ext/manifest.json
+++ b/ext/manifest.json
@@ -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 *"
}
diff --git a/ext/popup.html b/ext/popup.html
index dfe547b2..78e89997 100644
--- a/ext/popup.html
+++ b/ext/popup.html
@@ -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>
diff --git a/ext/search.html b/ext/search.html
index 9e4c8f2e..48abb8b7 100644
--- a/ext/search.html
+++ b/ext/search.html
@@ -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>