Skip to content
This repository has been archived by the owner on Feb 25, 2023. It is now read-only.

Handlebars sandbox #612

Merged
merged 4 commits into from
Sep 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion dev/data/manifest-variants.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@
"page": "bg/settings.html",
"open_in_tab": true
},
"sandbox": {
"pages": [
"bg/template-renderer.html"
],
"content_security_policy": "sandbox allow-scripts; script-src 'self' 'unsafe-eval'; object-src 'self'"
},
"permissions": [
"<all_urls>",
"storage",
Expand Down Expand Up @@ -94,7 +100,8 @@
}
},
"web_accessible_resources": [
"fg/float.html"
"fg/float.html",
"bg/template-renderer.html"
],
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"applications": {
Expand Down
4 changes: 2 additions & 2 deletions ext/bg/js/settings/anki-templates-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

/* global
* AnkiNoteBuilder
* TemplateRenderer
* TemplateRendererProxy
* api
*/

Expand All @@ -28,7 +28,7 @@ class AnkiTemplatesController {
this._cachedDefinitionValue = null;
this._cachedDefinitionText = null;
this._defaultFieldTemplates = null;
this._templateRenderer = new TemplateRenderer();
this._templateRenderer = new TemplateRendererProxy();
}

async prepare() {
Expand Down
76 changes: 76 additions & 0 deletions ext/bg/js/template-renderer-frame-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (C) 2020 Yomichan Authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

class TemplateRendererFrameApi {
constructor(templateRenderer) {
this._templateRenderer = templateRenderer;
this._windowMessageHandlers = new Map([
['renderHandlebarsTemplate', {async: true, handler: this._onRenderHandlebarsTemplate.bind(this)}]
]);
}

prepare() {
window.addEventListener('message', this._onWindowMessage.bind(this), false);
}

_onWindowMessage(e) {
const {source, data: {action, params, id}} = e;
const messageHandler = this._windowMessageHandlers.get(action);
if (typeof messageHandler === 'undefined') { return; }

this._onWindowMessageInner(messageHandler, action, params, source, id);
}

async _onWindowMessageInner({handler, async}, action, params, source, id) {
let response;
try {
let result = handler(params);
if (async) {
result = await result;
}
response = {result};
} catch (error) {
response = {error: this._errorToJson(error)};
}

if (typeof id === undefined) { return; }
source.postMessage({action: `${action}.response`, params: response, id}, '*');
}

async _onRenderHandlebarsTemplate({template, data, marker}) {
return await this._templateRenderer.render(template, data, marker);
}

_errorToJson(error) {
try {
if (error !== null && typeof error === 'object') {
return {
name: error.name,
message: error.message,
stack: error.stack,
data: error.data
};
}
} catch (e) {
// NOP
}
return {
value: error,
hasValue: true
};
}
}
27 changes: 27 additions & 0 deletions ext/bg/js/template-renderer-frame-main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2020 Yomichan Authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

/* globals
* TemplateRenderer
* TemplateRendererFrameApi
*/

(() => {
const templateRenderer = new TemplateRenderer();
const api = new TemplateRendererFrameApi(templateRenderer);
api.prepare();
})();
153 changes: 153 additions & 0 deletions ext/bg/js/template-renderer-proxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright (C) 2020 Yomichan Authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

class TemplateRendererProxy {
constructor() {
this._frame = null;
this._frameNeedsLoad = true;
this._frameLoading = false;
this._frameLoadPromise = null;
this._frameUrl = chrome.runtime.getURL('/bg/template-renderer.html');
this._invocations = new Set();
}

async render(template, data, marker) {
await this._prepareFrame();
return await this._invoke('renderHandlebarsTemplate', {template, data, marker});
}

// Private

async _prepareFrame() {
if (this._frame === null) {
this._frame = document.createElement('iframe');
this._frame.addEventListener('load', this._onFrameLoad.bind(this), false);
const style = this._frame.style;
style.opacity = '0';
style.width = '0';
style.height = '0';
style.position = 'absolute';
}
if (this._frameNeedsLoad) {
this._frameNeedsLoad = false;
this._frameLoading = true;
this._frameLoadPromise = this._loadFrame(this._frame, this._frameUrl)
.finally(() => { this._frameLoading = false; });
}
await this._frameLoadPromise;
}

_loadFrame(frame, url, timeout=5000) {
return new Promise((resolve, reject) => {
let ready = false;
const cleanup = () => {
frame.removeEventListener('load', onLoad, false);
if (timer !== null) {
clearTimeout(timer);
timer = null;
}
};
const onLoad = () => {
if (!ready) { return; }
cleanup();
resolve();
};

let timer = setTimeout(() => {
cleanup();
reject(new Error('Timeout'));
}, timeout);

frame.removeAttribute('src');
frame.removeAttribute('srcdoc');
frame.addEventListener('load', onLoad, false);
try {
document.body.appendChild(frame);
ready = true;
frame.contentDocument.location.href = url;
} catch (e) {
cleanup();
reject(e);
}
});
}

_invoke(action, params, timeout=null) {
return new Promise((resolve, reject) => {
const frameWindow = (this._frame !== null ? this._frame.contentWindow : null);
if (frameWindow === null) {
reject(new Error('Frame not set up'));
return;
}

const id = generateId(16);
const invocation = {
cancel: () => {
cleanup();
reject(new Error('Terminated'));
}
};

const cleanup = () => {
this._invocations.delete(invocation);
window.removeEventListener('message', onMessage, false);
if (timer !== null) {
clearTimeout(timer);
timer = null;
}
};
const onMessage = (e) => {
if (
e.source !== frameWindow ||
e.data.id !== id ||
e.data.action !== `${action}.response`
) {
return;
}

const response = e.data.params;
cleanup();
const {error} = response;
if (error) {
reject(jsonToError(error));
} else {
resolve(response.result);
}
};

let timer = (typeof timeout === 'number' ? setTimeout(() => {
cleanup();
reject(new Error('Timeout'));
}, timeout) : null);

this._invocations.add(invocation);

window.addEventListener('message', onMessage, false);
frameWindow.postMessage({action, params, id}, '*');
});
}

_onFrameLoad() {
if (this._frameLoading) { return; }
this._frameNeedsLoad = true;

for (const invocation of this._invocations) {
invocation.cancel();
}
this._invocations.clear();
}
}
3 changes: 1 addition & 2 deletions ext/bg/search.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ <h1>Yomichan Search</h1>
</div>
</div>

<script src="/mixed/lib/handlebars.min.js"></script>
<script src="/mixed/lib/wanakana.min.js"></script>

<script src="/mixed/js/core.js"></script>
Expand All @@ -92,7 +91,7 @@ <h1>Yomichan Search</h1>
<script src="/mixed/js/text-to-speech-audio.js"></script>

<script src="/bg/js/anki-note-builder.js"></script>
<script src="/bg/js/template-renderer.js"></script>
<script src="/bg/js/template-renderer-proxy.js"></script>

<script src="/bg/js/query-parser-generator.js"></script>
<script src="/bg/js/query-parser.js"></script>
Expand Down
3 changes: 1 addition & 2 deletions ext/bg/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -1186,7 +1186,6 @@ <h3>Support Development</h3>

<script src="/mixed/lib/jquery.min.js"></script>
<script src="/mixed/lib/bootstrap/js/bootstrap.min.js"></script>
<script src="/mixed/lib/handlebars.min.js"></script>
<script src="/mixed/lib/jszip.min.js"></script>
<script src="/mixed/lib/wanakana.min.js"></script>

Expand Down Expand Up @@ -1214,7 +1213,7 @@ <h3>Support Development</h3>
<script src="/bg/js/dictionary-importer.js"></script>
<script src="/bg/js/json-schema.js"></script>
<script src="/bg/js/media-utility.js"></script>
<script src="/bg/js/template-renderer.js"></script>
<script src="/bg/js/template-renderer-proxy.js"></script>

<script src="/bg/js/settings/keyboard-mouse-input-field.js"></script>
<script src="/bg/js/settings/profile-conditions-ui.js"></script>
Expand Down
22 changes: 22 additions & 0 deletions ext/bg/template-renderer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Yomichan Handlebars Sandbox</title>
<link rel="icon" type="image/png" href="/mixed/img/icon16.png" sizes="16x16">
<link rel="icon" type="image/png" href="/mixed/img/icon19.png" sizes="19x19">
<link rel="icon" type="image/png" href="/mixed/img/icon32.png" sizes="32x32">
<link rel="icon" type="image/png" href="/mixed/img/icon38.png" sizes="38x38">
<link rel="icon" type="image/png" href="/mixed/img/icon48.png" sizes="48x48">
<link rel="icon" type="image/png" href="/mixed/img/icon64.png" sizes="64x64">
<link rel="icon" type="image/png" href="/mixed/img/icon128.png" sizes="128x128">
</head>
<body>
<script src="/mixed/lib/handlebars.min.js"></script>
<script src="/mixed/js/japanese.js"></script>
<script src="/bg/js/template-renderer.js"></script>
<script src="/bg/js/template-renderer-frame-api.js"></script>
<script src="/bg/js/template-renderer-frame-main.js"></script>
</body>
</html>
4 changes: 1 addition & 3 deletions ext/fg/float.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ <h1>Yomichan Updated!</h1>
</div>
</div>

<script src="/mixed/lib/handlebars.min.js"></script>

<script src="/mixed/js/core.js"></script>
<script src="/mixed/js/yomichan.js"></script>
<script src="/mixed/js/comm.js"></script>
Expand All @@ -69,7 +67,7 @@ <h1>Yomichan Updated!</h1>
<script src="/mixed/js/text-to-speech-audio.js"></script>

<script src="/bg/js/anki-note-builder.js"></script>
<script src="/bg/js/template-renderer.js"></script>
<script src="/bg/js/template-renderer-proxy.js"></script>

<script src="/bg/js/query-parser-generator.js"></script>
<script src="/bg/js/query-parser.js"></script>
Expand Down
9 changes: 8 additions & 1 deletion ext/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@
"page": "bg/settings.html",
"open_in_tab": true
},
"sandbox": {
"pages": [
"bg/template-renderer.html"
],
"content_security_policy": "sandbox allow-scripts; script-src 'self' 'unsafe-eval'; object-src 'self'"
},
"permissions": [
"<all_urls>",
"storage",
Expand Down Expand Up @@ -93,7 +99,8 @@
}
},
"web_accessible_resources": [
"fg/float.html"
"fg/float.html",
"bg/template-renderer.html"
],
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"applications": {
Expand Down
Loading