Skip to content

Commit

Permalink
Introduce "nodom" binary (ampproject#882)
Browse files Browse the repository at this point in the history
* new binary: lite version

* Create 'lite' version for protocol adapters use case

* fix tests

* make more lazy, fix more tests

* Introduce the concept of DocumentLite.

Sometimes developers may want to run arbitrary JS on a Worker safely/without access to a DOM.
In those situations, the whole vdom implementation is overkill.

This provides a DocumentLite with none of the implementation.

* example to use lite version

* reduce filesize

* remove main-thread lite binary changes, also remove long-task from worker-thread

* example no longer lite main-thread.

* self nits

* duped var

* remove duplication

* lite --> nodom, pull out to shared file

* organize imports

* missed a few spots

* ...compilePlugin

* Revert "...compilePlugin"

This reverts commit f5679a1.

* choumx updates

* fix local prettier settings
  • Loading branch information
samouri authored Jul 22, 2020
1 parent 64c8e44 commit 30efd65
Show file tree
Hide file tree
Showing 11 changed files with 300 additions and 133 deletions.
49 changes: 49 additions & 0 deletions config/rollup.worker-thread.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,55 @@ const ESModules = [
...compilePlugins,
],
},
{
input: 'output/worker-thread/index.nodom.amp.js',
output: {
file: 'dist/amp/worker/worker.nodom.mjs',
format: 'iife',
name: 'WorkerThread',
sourcemap: true,
banner: 'var WORKER_DOM_DEBUG = /log|development/i.test(location.hash);',
},
plugins: [
copy({
targets: [
{
src: 'config/dist-packaging/amp/worker/package.json',
dest: 'dist/amp/worker',
},
],
}),
babelPlugin({
transpileToES5: false,
allowConsole: true,
}),
],
},
{
input: 'output/worker-thread/index.nodom.amp.js',
output: {
file: 'dist/amp/worker/worker.nodom.js',
format: 'iife',
name: 'WorkerThread',
sourcemap: true,
banner: 'var WORKER_DOM_DEBUG = /log|development/i.test(location.hash);',
},
plugins: [
copy({
targets: [
{
src: 'config/dist-packaging/amp/worker/package.json',
dest: 'dist/amp/worker',
},
],
}),
babelPlugin({
transpileToES5: true,
allowConsole: true,
}),
...compilePlugins,
],
},
];

const IIFEModules = [
Expand Down
2 changes: 1 addition & 1 deletion demo/call-function/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import { upgradeElement } from "/dist/amp/main.mjs";
upgradeElement(
document.getElementById("upgrade-me"),
"/dist/amp/worker/worker.mjs"
"/dist/amp/worker/worker.nodom.mjs"
).then(async (worker) => {
worker
.callFunction("performComplexMath")
Expand Down
3 changes: 2 additions & 1 deletion src/worker-thread/MutationTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ import { Node } from './dom/Node';
import { Phase } from '../transfer/Phase';
import { phase, set as setPhase } from './phase';
import { Document } from './dom/Document';
import { DocumentStub } from './dom/DocumentLite';

let pending = false;
let pendingMutations: Array<number> = [];

// TODO(choumx): Change `mutation` to Array<Uint16> to prevent casting errors e.g. integer underflow, precision loss.
export function transfer(document: Document, mutation: Array<number>): void {
export function transfer(document: Document | DocumentStub, mutation: Array<number>): void {
if (phase > Phase.Initializing && document[TransferrableKeys.allowTransfer]) {
pending = true;
pendingMutations = pendingMutations.concat(mutation);
Expand Down
3 changes: 2 additions & 1 deletion src/worker-thread/Storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { StorageLocation } from '../transfer/TransferrableStorage';
import { TransferrableMutationType } from '../transfer/TransferrableMutation';
import { store } from './strings';
import { transfer } from './MutationTransfer';
import { DocumentStub } from './dom/DocumentLite';

/**
* @see https://developer.mozilla.org/en-US/docs/Web/API/Storage
Expand All @@ -38,7 +39,7 @@ export interface Storage {
* @param location
* @param data
*/
export function createStorage(document: Document, location: StorageLocation, data: { [key: string]: string }): Storage {
export function createStorage(document: Document | DocumentStub, location: StorageLocation, data: { [key: string]: string }): Storage {
const storage: any = Object.assign(Object.create(null), data);

// Define properties on a prototype-less object instead of a class so that
Expand Down
7 changes: 7 additions & 0 deletions src/worker-thread/WorkerDOMGlobalScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { CharacterData } from './dom/CharacterData';
import { DocumentFragment } from './dom/DocumentFragment';
import { DOMTokenList } from './dom/DOMTokenList';
import { Element } from './dom/Element';
import { DocumentStub } from './dom/DocumentLite';

/**
* Should only contain properties that exist on Window.
Expand Down Expand Up @@ -118,3 +119,9 @@ export interface WorkerDOMGlobalScope extends GlobalScope {
addEventListener: (type: string, handler: EventHandler) => void;
removeEventListener: (type: string, handler: EventHandler) => void;
}

export interface WorkerNoDOMGlobalScope {
document: DocumentStub;
localStorage?: Storage;
sessionStorage?: Storage;
}
5 changes: 3 additions & 2 deletions src/worker-thread/amp/amp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ import { TransferrableKeys } from '../../transfer/TransferrableKeys';
import { TransferrableMutationType } from '../../transfer/TransferrableMutation';
import { store } from '../strings';
import { transfer } from '../MutationTransfer';
import { DocumentStub } from '../dom/DocumentLite';

export class AMP {
private document: Document;
private document: Document | DocumentStub;

constructor(document: Document) {
constructor(document: Document | DocumentStub) {
this.document = document;
}

Expand Down
140 changes: 140 additions & 0 deletions src/worker-thread/amp/delete-globals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* Copyright 2020 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.
*/

const ALLOWLISTED_GLOBALS: { [key: string]: boolean } = {
Array: true,
ArrayBuffer: true,
BigInt: true,
BigInt64Array: true,
BigUint64Array: true,
Boolean: true,
Cache: true,
CustomEvent: true,
DataView: true,
Date: true,
Error: true,
EvalError: true,
Event: true,
EventTarget: true,
Float32Array: true,
Float64Array: true,
Function: true,
Infinity: true,
Int16Array: true,
Int32Array: true,
Int8Array: true,
Intl: true,
JSON: true,
Map: true,
Math: true,
NaN: true,
Number: true,
Object: true,
Promise: true,
Proxy: true,
RangeError: true,
ReferenceError: true,
Reflect: true,
RegExp: true,
Set: true,
String: true,
Symbol: true,
SyntaxError: true,
TextDecoder: true,
TextEncoder: true,
TypeError: true,
URIError: true,
URL: true,
Uint16Array: true,
Uint32Array: true,
Uint8Array: true,
Uint8ClampedArray: true,
WeakMap: true,
WeakSet: true,
WebAssembly: true,
WebSocket: true,
XMLHttpRequest: true,
atob: true,
addEventListener: true,
removeEventListener: true,
btoa: true,
caches: true,
clearInterval: true,
clearTimeout: true,
console: true,
decodeURI: true,
decodeURIComponent: true,
document: true,
encodeURI: true,
encodeURIComponent: true,
escape: true,
fetch: true,
indexedDB: true,
isFinite: true,
isNaN: true,
location: true,
navigator: true,
onerror: true,
onrejectionhandled: true,
onunhandledrejection: true,
parseFloat: true,
parseInt: true,
performance: true,
requestAnimationFrame: true,
cancelAnimationFrame: true,
self: true,
setTimeout: true,
setInterval: true,
unescape: true,
};

// Modify global scope by removing disallowed properties.
export function deleteGlobals(global: WorkerGlobalScope) {
/**
* @param object
* @param property
* @return True if property was deleted from object. Otherwise, false.
*/
const deleteUnsafe = (object: any, property: string): boolean => {
if (!ALLOWLISTED_GLOBALS.hasOwnProperty(property)) {
try {
delete object[property];
return true;
} catch (e) {}
}
return false;
};

// Walk up global's prototype chain and dereference non-allowlisted properties
// until EventTarget is reached.
let current = global;
while (current && current.constructor !== EventTarget) {
const deleted: string[] = [];
const failedToDelete: string[] = [];
Object.getOwnPropertyNames(current).forEach((prop) => {
if (deleteUnsafe(current, prop)) {
deleted.push(prop);
} else {
failedToDelete.push(prop);
}
});
console.info(`Removed ${deleted.length} references from`, current, ':', deleted);
if (failedToDelete.length) {
console.info(`Failed to remove ${failedToDelete.length} references from`, current, ':', failedToDelete);
}
current = Object.getPrototypeOf(current);
}
}
42 changes: 42 additions & 0 deletions src/worker-thread/dom/DocumentLite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Copyright 2020 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.
*/

import { PostMessage } from '../worker-thread';
import { Phase } from '../../transfer/Phase';
import { TransferrableKeys } from '../../transfer/TransferrableKeys';
import { set as setPhase } from '../phase';
import { WorkerNoDOMGlobalScope } from '../WorkerDOMGlobalScope';

/**
* A lightweight Document stub for the no-dom amp binary.
*/
export class DocumentStub {
// Internal variables.
public defaultView: WorkerNoDOMGlobalScope;
public postMessage: PostMessage;
public addGlobalEventListener: Function;
public removeGlobalEventListener: Function;
public [TransferrableKeys.allowTransfer]: boolean = true;
public [TransferrableKeys.index]: number = -1;

constructor() {
this.defaultView = { document: this };
}

public [TransferrableKeys.observe](): void {
setPhase(Phase.Mutating);
}
}
3 changes: 2 additions & 1 deletion src/worker-thread/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ import { MessageToWorker, MessageType, FunctionCallToWorker, ResolveOrReject } f
import { transfer } from './MutationTransfer';
import { TransferrableMutationType } from '../transfer/TransferrableMutation';
import { store } from './strings';
import { DocumentStub } from './dom/DocumentLite';

const exportedFunctions: { [fnIdent: string]: Function } = {};

export function callFunctionMessageHandler(event: MessageEvent, document: Document) {
export function callFunctionMessageHandler(event: MessageEvent, document: Document | DocumentStub) {
const msg = event.data as MessageToWorker;
if (msg[TransferrableKeys.type] !== MessageType.FUNCTION) {
return;
Expand Down
Loading

0 comments on commit 30efd65

Please sign in to comment.