Skip to content

Commit f4109ae

Browse files
a-mountainmaksym.perevalov
authored and
maksym.perevalov
committed
Add browser support
1 parent 611a25f commit f4109ae

File tree

7 files changed

+808
-3
lines changed

7 files changed

+808
-3
lines changed

browser-test/vm.js

+285
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
'use strict';
2+
3+
const mt = require('metatests');
4+
const vm = require('../dist/vm.js');
5+
6+
const strict = (code) => `'use strict';\n` + code;
7+
8+
mt.test('option.timeout', (test) => {
9+
const name = 'option.timeout';
10+
const findIframes = () => document.getElementsByName(name);
11+
vm.runInNewContext(
12+
'',
13+
{},
14+
{
15+
contextName: name,
16+
timeout: 10,
17+
}
18+
);
19+
test.strictSame(findIframes().length, 1);
20+
setTimeout(() => {
21+
test.strictSame(findIframes().length, 0);
22+
test.end();
23+
}, 20);
24+
});
25+
26+
mt.test('options.name', (test) => {
27+
const expectedName = 'options.name';
28+
vm.runInNewContext(
29+
'',
30+
{},
31+
{
32+
timeout: 5,
33+
contextName: expectedName,
34+
}
35+
);
36+
test.strictSame(document.getElementsByName(expectedName).length, 1);
37+
test.end();
38+
});
39+
40+
mt.test('check deleting iframe if timeout == 0', (test) => {
41+
const name = 'deleting iframe';
42+
vm.runInNewContext('', {
43+
contextName: name,
44+
});
45+
const actual = document.getElementsByName(name);
46+
test.strictSame(actual.length, 0);
47+
test.end();
48+
});
49+
50+
mt.test('use not contextified object', (test) => {
51+
const msg =
52+
'The "contextifiedObject" argument must be an vm.Context.' +
53+
` Received an instance of object`;
54+
test.throws(() => vm.runInContext('', {}), new TypeError(msg));
55+
test.end();
56+
});
57+
58+
mt.test('run in this context', (test) => {
59+
window.a = 2;
60+
window.b = 2;
61+
const actual = vm.runInThisContext('a + b');
62+
test.strictSame(actual, 4);
63+
test.end();
64+
});
65+
66+
mt.test('has context property', (test) => {
67+
const code = 'a';
68+
const script = new vm.Script(code);
69+
const context = vm.createContext({ a: 2 });
70+
const result = script.runInContext(context);
71+
test.strictSame(result, 2);
72+
test.end();
73+
});
74+
75+
mt.test('change object property', (test) => {
76+
const context = vm.createContext({ a: { b: 1 } });
77+
vm.runInContext('a.b = 2', context);
78+
test.strictSame(context.a.b, 2);
79+
test.end();
80+
});
81+
82+
mt.test('reassign object property', (test) => {
83+
const context = vm.createContext({ a: { b: 1 } });
84+
vm.runInContext('a = {c: 2}', context);
85+
test.strictSame(context.a.c, 2);
86+
test.end();
87+
});
88+
89+
mt.test('change context primitive property', (test) => {
90+
const context = vm.createContext({ a: 1 });
91+
vm.runInContext('a = 2', context);
92+
test.strictSame(context.a, 2);
93+
test.end();
94+
});
95+
96+
mt.test('add new property', (test) => {
97+
const context = vm.createContext({});
98+
vm.runInContext('a = 1', context);
99+
test.strictSame(context.a, 1);
100+
test.end();
101+
});
102+
103+
mt.test('add default window property "name"', (test) => {
104+
const context = vm.createContext({ name: {} });
105+
vm.runInContext('name = "hello"', context);
106+
test.strictSame(context.name, 'hello');
107+
test.end();
108+
});
109+
110+
mt.test('frozen object', (test) => {
111+
const context = vm.createContext(Object.freeze({ a: 1 }));
112+
vm.runInContext('a = 2; b = 3', context);
113+
test.strictSame(context.a, 1);
114+
test.strictSame(context.b, undefined);
115+
test.end();
116+
});
117+
118+
mt.test('sealed object', (test) => {
119+
const context = vm.createContext(Object.seal({ a: 1 }));
120+
vm.runInContext('a = 2; b = 3', context);
121+
test.strictSame(context.a, 2);
122+
test.strictSame(context.b, undefined);
123+
test.end();
124+
});
125+
126+
mt.test('get html element', (test) => {
127+
const element = document.createElement('h1');
128+
document.body.appendChild(element);
129+
130+
const getElementsByTagName = (tag) => document.getElementsByTagName(tag);
131+
const code = 'getElementsByTagName("h1");';
132+
const context = vm.createContext({ getElementsByTagName });
133+
134+
const result = vm.runInContext(code, context);
135+
test.strictSame(result.length, 1);
136+
test.end();
137+
});
138+
139+
mt.test('block eval', (test) => {
140+
const context = vm.createContext(
141+
{},
142+
{
143+
codeGeneration: {
144+
strings: false,
145+
},
146+
}
147+
);
148+
const msg = 'Code generation from strings disallowed for this context';
149+
const withEval = () => {
150+
vm.runInContext(strict('eval("1 + 1")'), context);
151+
};
152+
test.throws(withEval, new EvalError(msg));
153+
test.end();
154+
});
155+
156+
mt.test('run code without eval with eval blocking', (test) => {
157+
const context = vm.createContext(
158+
{},
159+
{
160+
codeGeneration: {
161+
strings: false,
162+
},
163+
}
164+
);
165+
const actual = vm.runInContext('1 + 1', context);
166+
test.strictSame(actual, 2);
167+
test.end();
168+
});
169+
170+
mt.test('options.filename', (test) => {
171+
const context = vm.createContext({});
172+
const expectedFilename = 'filename';
173+
const expectedMessage = 'message';
174+
const errors = [
175+
'Error',
176+
'EvalError',
177+
'RangeError',
178+
'ReferenceError',
179+
'SyntaxError',
180+
'TypeError',
181+
'URIError',
182+
];
183+
for (const error of errors) {
184+
const code = `throw new ${error}("${expectedMessage}", "wrongFilename")`;
185+
try {
186+
vm.runInContext(code, context, expectedFilename);
187+
} catch (e) {
188+
test.strictSame(e.message, expectedMessage, error);
189+
test.strictSame(e.filename, expectedFilename, error);
190+
}
191+
}
192+
test.end();
193+
});
194+
195+
mt.test('block code generation with Function constructor', (test) => {
196+
const context = vm.createContext(
197+
{},
198+
{
199+
codeGeneration: {
200+
strings: false,
201+
},
202+
}
203+
);
204+
const msg = 'Code generation from strings disallowed for this context';
205+
const withEval = () => {
206+
vm.runInContext(strict('new Function("1 + 1")'), context);
207+
};
208+
test.throws(withEval, new EvalError(msg));
209+
test.end();
210+
});
211+
212+
mt.test('call function with blocking Function constructor', (test) => {
213+
const context = vm.createContext(
214+
{
215+
a: {
216+
b: 1,
217+
},
218+
},
219+
{
220+
codeGeneration: {
221+
strings: false,
222+
},
223+
}
224+
);
225+
const code = 'function f() {return this.b} f.call(a)';
226+
const result = vm.runInContext(strict(code), context);
227+
test.strictSame(result, 1);
228+
test.end();
229+
});
230+
231+
mt.test('name property with blocking Function constructor', (test) => {
232+
const context = vm.createContext(
233+
{
234+
a: {
235+
b: 1,
236+
},
237+
},
238+
{
239+
codeGeneration: {
240+
strings: false,
241+
},
242+
}
243+
);
244+
const code = 'function f() {return this.b} f.name';
245+
const result = vm.runInContext(strict(code), context);
246+
test.strictSame(result, 'f');
247+
test.end();
248+
});
249+
250+
mt.test('delete iframe by hand before timeout', (test) => {
251+
const name = 'delete iframe';
252+
vm.runInNewContext(
253+
'',
254+
{},
255+
{
256+
timeout: 5,
257+
contextName: name,
258+
}
259+
);
260+
const iframe = document.getElementsByName(name)[0];
261+
document.body.removeChild(iframe);
262+
setTimeout(() => {
263+
test.end();
264+
}, 10);
265+
});
266+
267+
/**
268+
* Blocking code generation for GeneratorFunction constructor
269+
* isn't implemented.
270+
*/
271+
//
272+
// mt.test('block code generation GeneratorFunction', (test) => {
273+
// const context = vm.createContext({}, {
274+
// codeGeneration: {
275+
// strings: false
276+
// }
277+
// });
278+
// const msg = 'Code generation from strings disallowed for this context';
279+
// const code = 'Object.getPrototypeOf(function *(a){return a}).constructor';
280+
// const withEval = () => {
281+
// vm.runInContext(strict(code), context);
282+
// };
283+
// test.throws(withEval, new EvalError(msg));
284+
// test.end();
285+
// });

dist/.eslintrc.json

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
{
2+
"env": {
3+
"node": false,
4+
"browser": true
5+
},
26
"parserOptions": {
37
"sourceType": "module"
48
}

dist/options.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const removeRedundantOptions = (template, options) => {
2+
for (const key of Object.keys(options)) {
3+
if (!(key in template)) {
4+
delete options[key];
5+
}
6+
}
7+
};
8+
9+
/**
10+
* Remove redundant options and add defaults for missing options.
11+
*/
12+
const normalizeOptions = (options, defaultOptions) => {
13+
removeRedundantOptions(defaultOptions, options);
14+
for (const [optionKey, defaultOption] of Object.entries(defaultOptions)) {
15+
const option = options[optionKey];
16+
const optionIsUndefined = typeof option === 'undefined';
17+
const isNestedOptions = typeof option === 'object';
18+
if (optionIsUndefined) {
19+
options[optionKey] = defaultOption;
20+
} else if (isNestedOptions) {
21+
normalizeOptions(defaultOption, option);
22+
}
23+
}
24+
};
25+
26+
export { normalizeOptions };

0 commit comments

Comments
 (0)