forked from ampproject/worker-dom
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce sandboxed iframe mode. (ampproject#1042)
* Introduce sandbox (iframe) mode for the Worker * Reduce filesize via IS_AMP * address the nits * Add jsdoc, and move iframe contract comment
- Loading branch information
Showing
12 changed files
with
354 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<title>IframeWorker Sandbox</title> | ||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
<script src="/dist/amp-debug/main.mjs" type="module"></script> | ||
</head> | ||
<body> | ||
<div src="/hello-world/hello-world.js" id="upgrade-me"> | ||
<div class="root"> | ||
<button>Insert Hello World!</button> | ||
</div> | ||
</div> | ||
<script type="module"> | ||
import { upgradeElement } from '/dist/amp-debug/main.mjs'; | ||
upgradeElement( | ||
document.getElementById('upgrade-me'), | ||
'/dist/amp-debug/worker/worker.mjs', | ||
() => {}, | ||
undefined, | ||
{ iframeUrl: 'sandbox-iframe.html' } | ||
).then((worker) => { | ||
worker.onmessage = (msg) => | ||
console.log(`onmessage: ${JSON.stringify(msg)}`); | ||
worker.onmessageerror = (msg) => | ||
console.error(`msgerror ${JSON.stringify(msg)}`); | ||
worker.onerror = (msg) => | ||
console.error(`error: ${JSON.stringify(msg)}`); | ||
}); | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<html> | ||
<script> | ||
/** | ||
* See `iframe-worker.ts` for the iframe proxy contract. | ||
*/ | ||
let parentOrigin = '*'; | ||
const MESSAGE_TYPES = { | ||
ready: 'iframe-ready', | ||
init: 'init-worker', | ||
onmessage: 'onmessage', | ||
onerror: 'onerror', | ||
onmessageerror: 'onmessageerror', | ||
postMessage: 'postMessage', | ||
}; | ||
function send(type, message) { | ||
if (type !== MESSAGE_TYPES.ready && parentOrigin === '*') { | ||
throw new Error('Broadcast banned except for iframe-ready message.'); | ||
} | ||
parent.postMessage({ type, message }, parentOrigin); | ||
} | ||
|
||
function listen(type, handler) { | ||
window.addEventListener('message', (event) => { | ||
if (event.source !== parent) { | ||
return; | ||
} | ||
parentOrigin = event.origin; | ||
|
||
if (event.data.type === type) { | ||
handler(event.data); | ||
} | ||
}); | ||
} | ||
|
||
// Send initialization. | ||
send(MESSAGE_TYPES.ready); | ||
|
||
let worker = null; | ||
// Listen for Worker Init. | ||
listen(MESSAGE_TYPES.init, ({ code }) => { | ||
if (worker) { | ||
return; | ||
} | ||
worker = new Worker(URL.createObjectURL(new Blob([code]))); | ||
|
||
// Proxy messages Worker to parent Window. | ||
worker.onmessage = (e) => send(MESSAGE_TYPES.onmessage, e.data); | ||
worker.onmessageerror = (e) => send(MESSAGE_TYPES.onmessageerror, e.data); | ||
worker.onerror = (e) => | ||
send(MESSAGE_TYPES.onerror, { | ||
lineno: e.lineno, | ||
colno: e.colno, | ||
message: e.message, | ||
filename: e.filename, | ||
}); | ||
|
||
// Proxy message from parent Window to Worker. | ||
listen(MESSAGE_TYPES.postMessage, ({ message }) => | ||
worker.postMessage(message) | ||
); | ||
}); | ||
</script> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
/** | ||
* Copyright 2021 The AMP HTML Authors. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS-IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
type MessageFromWorker = { | ||
type: 'onmessage' | 'onerror' | 'onmessageerror'; | ||
message: any; | ||
}; | ||
export type MessageFromIframe = { type: 'iframe-ready' } | MessageFromWorker; | ||
export type MessageToIframe = { type: 'terminate' } | { type: 'init-worker'; code: string } | { type: 'postMessage'; message: any }; | ||
|
||
/** | ||
* An almost drop-in replacement for a standard Web Worker, although this one | ||
* within a sandboxed cross-origin iframe for a heightened security boundary. | ||
* For more details on Worker, see: https://developer.mozilla.org/en-US/docs/Web/API/Worker | ||
* | ||
* The iframe used for sandboxing must follow a specific contract. It: | ||
* 1. Must send a ready message to the main-thread. | ||
* 2. Must listen for a message from main-thread with the code to initialize a Worker with. | ||
* 3. Must proxy all messages between the Worker and Parent, including errors. | ||
*/ | ||
class IframeWorker { | ||
// Public Worker API | ||
public onerror: (this: IframeWorker, ev: ErrorEvent) => any; | ||
public onmessage: (this: IframeWorker, ev: MessageEvent) => any; | ||
public onmessageerror: (this: IframeWorker, ev: MessageEvent) => any; | ||
|
||
// Internal variables. | ||
private iframe: HTMLIFrameElement; | ||
|
||
/** | ||
* @param url The URL to initiate the worker from. | ||
* @param iframeUrl The URL of the iframe to use as the worker proxy. | ||
*/ | ||
constructor(private url: string | URL, iframeUrl: string) { | ||
this.iframe = window.document.createElement('iframe'); | ||
this.iframe.setAttribute('sandbox', 'allow-scripts'); | ||
this.iframe.setAttribute('style', 'display:none'); | ||
this.iframe.setAttribute('src', iframeUrl); | ||
this.url = url; | ||
|
||
this.setupInit(); | ||
this.proxyFromWorker(); | ||
window.document.body.appendChild(this.iframe); | ||
} | ||
|
||
private setupInit() { | ||
const listener = async (event: MessageEvent) => { | ||
if (event.source != this.iframe.contentWindow) { | ||
return; | ||
} | ||
|
||
const code = await fetch(this.url.toString()).then((res) => res.text()); | ||
if ((event.data as MessageFromIframe).type == 'iframe-ready') { | ||
const msg: MessageToIframe = { type: 'init-worker', code }; | ||
this.iframe.contentWindow!.postMessage(msg, '*'); | ||
} | ||
window.removeEventListener('message', listener); | ||
}; | ||
window.addEventListener('message', listener); | ||
} | ||
|
||
private proxyFromWorker() { | ||
window.addEventListener('message', (event: MessageEvent) => { | ||
if (event.source != this.iframe.contentWindow) { | ||
return; | ||
} | ||
|
||
const { type, message } = event.data as MessageFromWorker; | ||
if (type == 'onmessage' && this.onmessage) { | ||
this.onmessage({ ...event, data: message }); | ||
} else if (type === 'onerror' && this.onerror) { | ||
this.onerror(message); | ||
} else if (type === 'onmessageerror' && this.onmessageerror) { | ||
this.onmessageerror({ ...event, data: message }); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* See https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage | ||
* @param message | ||
* @param transferables | ||
*/ | ||
postMessage(message: any, transferables?: Array<Transferable>) { | ||
const msg: MessageToIframe = { type: 'postMessage', message }; | ||
this.iframe.contentWindow!.postMessage(msg, '*', transferables); | ||
} | ||
|
||
/** | ||
* See https://developer.mozilla.org/en-US/docs/Web/API/Worker/terminate | ||
*/ | ||
terminate() { | ||
const msg: MessageToIframe = { type: 'terminate' }; | ||
this.iframe.contentWindow!.postMessage(msg, '*'); | ||
this.iframe.remove(); | ||
} | ||
} | ||
|
||
export { IframeWorker }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.