Merge pull request #2043 from halfnelson/fix/repl-sandbox

repl improvements
pull/2044/head
Rich Harris 6 years ago committed by GitHub
commit f43c567caf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,5 +1,5 @@
<script> <script>
import { onMount, createEventDispatcher } from 'svelte'; import { onMount, onDestroy, createEventDispatcher } from 'svelte';
import getLocationFromStack from '../../_utils/getLocationFromStack.js'; import getLocationFromStack from '../../_utils/getLocationFromStack.js';
import ReplProxy from '../../_utils/replProxy.js'; import ReplProxy from '../../_utils/replProxy.js';
import { decode } from 'sourcemap-codec'; import { decode } from 'sourcemap-codec';
@ -67,9 +67,14 @@
let createComponent; let createComponent;
let init; let init;
onDestroy(() => {
if (replProxy) {
replProxy.destroy();
}
});
onMount(() => { onMount(() => {
replProxy = new ReplProxy(refs.child); replProxy = new ReplProxy(refs.child);
refs.child.addEventListener('load', () => { refs.child.addEventListener('load', () => {
replProxy.onPropUpdate = (prop, value) => { replProxy.onPropUpdate = (prop, value) => {
@ -163,6 +168,9 @@
props: ${JSON.stringify($values_store)} props: ${JSON.stringify($values_store)}
}); });
`) `)
.then(()=> {
replProxy.bindProps(props);
})
.catch(e => { .catch(e => {
// TODO show in UI // TODO show in UI
hasComponent = false; hasComponent = false;
@ -196,6 +204,10 @@
// TODO do we need to clear out SSR HTML? // TODO do we need to clear out SSR HTML?
createComponent(); createComponent();
props_handler = props => {
replProxy.bindProps(props)
};
replProxy.bindProps(props);
}; };
} }
@ -209,9 +221,7 @@
init(); init();
}; };
props_handler = props => {
replProxy.bindProps(props)
};
}); });
}); });
@ -279,7 +289,7 @@
</style> </style>
<div class="iframe-container"> <div class="iframe-container">
<iframe title="Result" bind:this={refs.child} sandbox="allow-scripts allow-popups" class="{error || pending || pendingImports ? 'greyed-out' : ''}" srcdoc=' <iframe title="Result" bind:this={refs.child} sandbox="allow-scripts allow-popups allow-forms allow-pointer-lock allow-top-navigation" class="{error || pending || pendingImports ? 'greyed-out' : ''}" srcdoc='
<!doctype html> <!doctype html>
<html> <html>
<head> <head>

@ -5,20 +5,23 @@ export default class ReplProxy {
this.pendingCmds = new Map(); this.pendingCmds = new Map();
this.onPropUpdate = null; this.onPropUpdate = null;
this.onFetchProgress = null; this.onFetchProgress = null;
this.handle_event = (ev) => this.handleReplMessage(ev);
window.addEventListener("message", ev => this.handleReplMessage(ev), false); window.addEventListener("message", this.handle_event, false);
} }
destroy() {
window.removeEventListener("message", this.handle_event);
}
iframeCommand(command, args) { iframeCommand(command, args) {
return new Promise( (resolve, reject) => { return new Promise( (resolve, reject) => {
this.cmdId = this.cmdId + 1; this.cmdId = this.cmdId + 1;
this.pendingCmds.set(this.cmdId, { resolve: resolve, reject: reject });
this.iframe.contentWindow.postMessage({ this.iframe.contentWindow.postMessage({
action: command, action: command,
cmdId: this.cmdId, cmdId: this.cmdId,
args: args args: args
}, '*') }, '*')
this.pendingCmds.set(this.cmdId, { resolve: resolve, reject: reject });
}); });
} }
@ -27,21 +30,21 @@ export default class ReplProxy {
let id = cmdData.cmdId; let id = cmdData.cmdId;
let handler = this.pendingCmds.get(id); let handler = this.pendingCmds.get(id);
if (handler) { if (handler) {
this.pendingCmds.delete(id); this.pendingCmds.delete(id);
if (action == "cmdError") { if (action == "cmdError") {
let { message, stack } = cmdData; let { message, stack } = cmdData;
let e = new Error(message); let e = new Error(message);
e.stack = stack; e.stack = stack;
console.log("cmd fail"); console.log("repl cmd fail");
handler.reject(e) handler.reject(e)
} }
if (action == "cmdOk") { if (action == "cmdOk") {
console.log("cmd okay");
handler.resolve(cmdData.args) handler.resolve(cmdData.args)
} }
} else { } else {
console.error("command not found", id); console.error("command not found", id, cmdData, [...this.pendingCmds.keys()]);
} }
} }

@ -39,15 +39,14 @@ function handleMessage(ev) {
const sendOk = () => sendReply({ action: "cmdOk" }); const sendOk = () => sendReply({ action: "cmdOk" });
const sendError = (message, stack) => sendReply({ action: "cmdError", message, stack }) const sendError = (message, stack) => sendReply({ action: "cmdError", message, stack })
parent.postMessage({ action: "test" }, ev.origin);
if (action == "eval") { if (action == "eval") {
let { script } = ev.data.args; let { script } = ev.data.args;
try { try {
eval(script); eval(script);
sendOk(); sendOk();
} catch (e) { } catch (e) {
sendError(e.message, e.stack) sendError(e.message, e.stack);
} }
} }
@ -56,54 +55,71 @@ function handleMessage(ev) {
if (!window.component) { if (!window.component) {
// TODO can this happen? // TODO can this happen?
console.error(`no component to bind to`); console.warn('no component to bind to');
sendOk();
return; return;
} }
props.forEach(prop => { try {
// TODO should there be a public API for binding? props.forEach(prop => {
// e.g. `component.$watch(prop, handler)`? // TODO should there be a public API for binding?
// (answer: probably) // e.g. `component.$watch(prop, handler)`?
window.component.$$.bound[prop] = value => { // (answer: probably)
sendMessage({ action:"prop_update", args: { prop, value } }) window.component.$$.bound[prop] = value => {
}; sendMessage({ action:"prop_update", args: { prop, value } })
}); };
});
sendOk();
} catch (e) {
sendError(e.message, e.stack);
}
} }
if (action == "set_prop") { if (action == "set_prop") {
if (!window.component) { try {
return; if (!window.component) {
return;
}
let { prop, value } = ev.data.args;
component[prop] = value;
sendOk();
} catch (e) {
sendError(e.message, e.stack);
} }
let { prop, value } = ev.data.args;
component[prop] = value;
} }
if (action == "catch_clicks") { if (action == "catch_clicks") {
let topOrigin = ev.origin; try {
document.body.addEventListener('click', event => { let topOrigin = ev.origin;
if (event.which !== 1) return; document.body.addEventListener('click', event => {
if (event.metaKey || event.ctrlKey || event.shiftKey) return; if (event.which !== 1) return;
if (event.defaultPrevented) return; if (event.metaKey || event.ctrlKey || event.shiftKey) return;
if (event.defaultPrevented) return;
// ensure target is a link
let el = event.target; // ensure target is a link
while (el && el.nodeName !== 'A') el = el.parentNode; let el = event.target;
if (!el || el.nodeName !== 'A') return; while (el && el.nodeName !== 'A') el = el.parentNode;
if (!el || el.nodeName !== 'A') return;
if (el.hasAttribute('download') || el.getAttribute('rel') === 'external' || el.target) return;
if (el.hasAttribute('download') || el.getAttribute('rel') === 'external' || el.target) return;
event.preventDefault();
event.preventDefault();
if (el.href.startsWith(topOrigin)) {
const url = new URL(el.href); if (el.href.startsWith(topOrigin)) {
if (url.hash[0] === '#') { const url = new URL(el.href);
window.location.hash = url.hash; if (url.hash[0] === '#') {
return; window.location.hash = url.hash;
return;
}
} }
}
window.open(el.href, '_blank');
window.open(el.href, '_blank'); });
}); sendOk();
} catch(e) {
sendError(e.message, e.stack);
}
} }

Loading…
Cancel
Save