Skip to content

Commit beef40b

Browse files
committed
vwindow stuff
1 parent 0a912e1 commit beef40b

File tree

13 files changed

+717
-238
lines changed

13 files changed

+717
-238
lines changed

docs/demos/mdu/extensions/nodeunit-helper.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
2-
Likely to be removed: A helper class that implements an old nodeunit
3-
interface to make porting unit tests slightly easier.
2+
Likely to be removed: A helper class that implements a function in
3+
node unit to make porting unit tests slightly easier.
44
*/
55
Modulo.utils.NodeUnitHelper = class NodeUnitHelper {
66
constructor() {

docs/demos/mdu/utils/taglex.js

+13-13
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
if (!Modulo.utils.taglex) {
1+
if (!modulo.registry.taglex) {
22
const ROOT = 'tag_root';
33
const TEXT_NODE = 1;
44
const TAG_NODE = 2;
55
const POP = 1;
66
const DOUBLE_POP = 2;
77
const NOOP = 3;
8-
Modulo.utils.taglex = { ROOT, TEXT_NODE, TAG_NODE, POP, DOUBLE_POP, NOOP };
8+
modulo.registry.taglex = { ROOT, TEXT_NODE, TAG_NODE, POP, DOUBLE_POP, NOOP };
99
}
1010

1111
// Very simple internal EventEmitter implementation, subclassed below
12-
Modulo.utils.taglex.EventEmitter = class EventEmitter {
12+
modulo.registry.taglex.EventEmitter = class EventEmitter {
1313
constructor() {
1414
this._callbacks = {};
1515
}
@@ -27,11 +27,11 @@ Modulo.utils.taglex.EventEmitter = class EventEmitter {
2727
}
2828

2929

30-
Modulo.utils.escapeForRegExp = function escapeForRegExp (str) {
30+
modulo.registry.utils.escapeForRegExp = function escapeForRegExp (str) {
3131
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
3232
};
3333

34-
Modulo.utils.taglex.Lexer = class Lexer extends Modulo.utils.taglex.EventEmitter {
34+
modulo.registry.taglex.Lexer = class Lexer extends modulo.registry.taglex.EventEmitter {
3535
constructor(ruleset, default_state, on_func) {
3636
super();
3737
this.ruleset = ruleset;
@@ -65,7 +65,7 @@ Modulo.utils.taglex.Lexer = class Lexer extends Modulo.utils.taglex.EventEmitter
6565
}
6666

6767
_emit_and_token(next_state, initial_text, normalized, token) {
68-
const { TEXT_NODE, TAG_NODE, NOOP } = Modulo.utils.taglex;
68+
const { TEXT_NODE, TAG_NODE, NOOP } = modulo.registry.taglex;
6969

7070
// Check state for error conditions
7171
if (next_state === null) {
@@ -89,7 +89,7 @@ Modulo.utils.taglex.Lexer = class Lexer extends Modulo.utils.taglex.EventEmitter
8989
}
9090

9191
_process(text, state) {
92-
const { TEXT_NODE } = Modulo.utils.taglex;
92+
const { TEXT_NODE } = modulo.registry.taglex;
9393

9494
// Process 1 text node, or 1 text node and 1 tag node
9595
let regexps = this.ruleset.regexps;
@@ -141,7 +141,7 @@ Modulo.utils.taglex.Lexer = class Lexer extends Modulo.utils.taglex.EventEmitter
141141
}
142142
}
143143

144-
Modulo.utils.taglex.StackParser = class StackParser extends Modulo.utils.taglex.Lexer {
144+
modulo.registry.taglex.StackParser = class StackParser extends modulo.registry.taglex.Lexer {
145145
constructor(ruleset, default_state, on_func) {
146146
super(ruleset, default_state, on_func);
147147
this.reset();
@@ -172,7 +172,7 @@ Modulo.utils.taglex.StackParser = class StackParser extends Modulo.utils.taglex.
172172
}
173173

174174
_transition_state_stack(state, next_state, initial_text, normalized, token) {
175-
const { TEXT_NODE, TAG_NODE, POP, DOUBLE_POP, NOOP } = Modulo.utils.taglex;
175+
const { TEXT_NODE, TAG_NODE, POP, DOUBLE_POP, NOOP } = modulo.registry.taglex;
176176
/////////////////////////////////
177177
// Collapse feature logic
178178
// A collapse action pops all the way up to target
@@ -267,15 +267,15 @@ Modulo.utils.taglex.StackParser = class StackParser extends Modulo.utils.taglex.
267267
}
268268
}
269269

270-
Modulo.utils.taglex.TagParser = class TagParser extends Modulo.utils.taglex.StackParser {
270+
modulo.registry.taglex.TagParser = class TagParser extends modulo.registry.taglex.StackParser {
271271
// Improved version of StackParser that emits more info based on TagRuleSet
272272
constructor(ruleset, default_state, on_func) {
273273
super(ruleset, default_state, on_func);
274274
this._prep_events();
275275
}
276276

277277
_prep_events() {
278-
const { TEXT_NODE } = Modulo.utils.taglex;
278+
const { TEXT_NODE } = modulo.registry.taglex;
279279

280280
let ruleset = this.ruleset;
281281
let me = this;
@@ -453,7 +453,7 @@ Modulo.utils.taglex.TagParser = class TagParser extends Modulo.utils.taglex.Stac
453453
};
454454

455455
this.regexps = {};
456-
const { escapeForRegExp } = Modulo.utils;
456+
const { escapeForRegExp } = modulo.registry.utils;
457457
for (let state_name in this._states) {
458458
let patterns = [];
459459
for (const token_name of this._states[state_name]) {
@@ -830,7 +830,7 @@ Modulo.utils.taglex.TagParser = class TagParser extends Modulo.utils.taglex.Stac
830830
exports.RuleSet = RuleSet;
831831
exports.TagRuleSet = TagRuleSet;
832832
exports.SourceBufferer = SourceBufferer;
833-
})(Modulo.utils.taglex);
833+
})(modulo.registry.taglex);
834834

835835
/*
836836
////////////////////////////////////// XXX DEAD CODE

docs/demos/mdu/utils/vwindow2.js

+87-7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ if (!modulo.registry.vwindow) {
33
}
44

55
modulo.registry.utils.parse = function parse(parentElem, text) {
6+
modulo.assert(parentElem !== undefined || text !== undefined, 'Must specify text to parse');
7+
if (text === undefined) { // Specify "null" to create a new document with a detached x tag
8+
text = parentElem;
9+
const vw = new modulo.registry.engines.VirtualWindow(modulo);
10+
parentElem = vw.document.createElement('x'); // Create a detached 'x' tag
11+
}
12+
613
/*
714
Simple recursive descent parser for HTML
815
*/
@@ -20,15 +27,15 @@ modulo.registry.utils.parse = function parse(parentElem, text) {
2027
};
2128
// If there's leading text, create a TextNode with that as content
2229
const pushText = (_textContent, opts) =>
23-
topOfStack.childNodes.push(new modulo.registry.vwindow.HTMLElement({
30+
topOfStack.append(new modulo.registry.vwindow.HTMLElement({
2431
nodeType: 3,
2532
_textContent,
2633
...opts,
2734
}));
2835

2936
const { ownerDocument } = parentElem;
3037
let elemClassesUC = {};
31-
if (ownerDocument.moduloVirtualWindow.customElements) {
38+
if (ownerDocument && ownerDocument.moduloVirtualWindow.customElements) {
3239
elemClassesUC = ownerDocument.moduloVirtualWindow.customElements.elemClassesUC;
3340
}
3441

@@ -119,6 +126,7 @@ modulo.registry.utils.parse = function parse(parentElem, text) {
119126
throw new Error('Over 9999 parsing steps');
120127
}
121128
}
129+
return parentElem;
122130
}
123131

124132
modulo.registry.vwindow.Element = class Element {
@@ -168,6 +176,9 @@ modulo.registry.vwindow.Event = class Event {
168176

169177

170178

179+
/* Synchronous Promise variant */
180+
modulo.registry.vwindow.Promise = class PausablePromise {
181+
}
171182

172183
modulo.registry.vwindow.HTMLElement = class HTMLElement extends modulo.registry.vwindow.Element {
173184
constructor(opts) {
@@ -304,6 +315,10 @@ modulo.registry.vwindow.HTMLElement = class HTMLElement extends modulo.registry.
304315
return this.childNodes.length > 0 ? this.childNodes[0] : null;
305316
}
306317

318+
get firstElementChild() {
319+
return this.children.length > 0 ? this.children[0] : null;
320+
}
321+
307322
get nextSibling() {
308323
if (!this.parentNode || (this._parentIndex + 1) >= this.parentNode.childNodes.length) {
309324
return null;
@@ -367,7 +382,7 @@ modulo.registry.vwindow.HTMLElement = class HTMLElement extends modulo.registry.
367382

368383
_fetch(url, callback) {
369384
const vw = this.ownerDocument.moduloVirtualWindow;
370-
vw.window.fetch(url)
385+
vw.fetch(url)
371386
.then(response => response.text())
372387
.then(callback);
373388
/*
@@ -683,13 +698,73 @@ modulo.register('engine', class VirtualWindow {
683698
Object.assign(this, win); // Expose some window properties at top as well
684699
this.window = Object.assign({}, vwindow, win); // Add in all vdom classes
685700
this.window.exec = this.exec.bind(this);
686-
//this.window.fetch = window.fetch.bind(window);
687701
this.window.fetch = this.fetch.bind(this);
702+
this.isVirtualFileSystem = false;
688703
this.cachedForBlocking = {};
704+
705+
// If the "virtual-file-system" configuration option is set, we can mount virtual files
706+
// TODO: Speed up idea: Have the entire source directory get read into
707+
// memory and mounted on this (even binaries, if specified!), then when
708+
// doing SSG just work on the memory copy, for lighting fast
709+
// prebuilding!
710+
// Eventually, when binary operation become more normal (e.g.
711+
// <ImageCanvas> type elements), then have VirtualWindow be capable of
712+
// running those remotely via a RPC bridge, or existing HTTP API or
713+
// equivalent (e.g. an express server that sits around with a
714+
// Pupeteer). Or standard ipc stuff?
715+
if (this.modulo.config.virtualwindow.virtualFileSystem) {
716+
this.setVFS(this.modulo.config.virtualwindow.virtualFileSystem);
717+
}
689718
}
690719

691720
fetch(...args) {
692-
return window.fetch(...args);
721+
// The single "fetch" choke point, used by both VirtualWindow and by it's children
722+
if (this.isVirtualFileSystem) {
723+
return this.fetchVFS(...args);
724+
} else {
725+
return window.fetch(...args);
726+
}
727+
}
728+
fetchVFS(...args) {
729+
class Response {
730+
constructor(url, text) {
731+
this._url = url;
732+
this._textBody = text;
733+
}
734+
json() {
735+
return new Promise((resolve, reject) => {
736+
try {
737+
resolve(JSON.parse(this._textBody));
738+
} catch (e) {
739+
reject(e);
740+
}
741+
});
742+
}
743+
text() {
744+
return new Promise((resolve, reject) => {
745+
resolve(this._textBody);
746+
});
747+
}
748+
}
749+
return new Promise((resolve, reject) => {
750+
const request = {};
751+
const response = {};
752+
let url = args[0] || '';
753+
if (url.toLowerCase().startsWith('file://')) {
754+
// Local file protocol, good!
755+
url = url.substr(7);
756+
}
757+
if (url.toLowerCase().startsWith('http://')) {
758+
// Local file protocol, good!
759+
url = url.substr(7);
760+
}
761+
762+
if (!(url in this.cachedForBlocking)) {
763+
reject(new Error(`V404| Virtual FileSystem entry for ${ url } not found`));
764+
} else {
765+
resolve(new Response(url, this.cachedForBlocking[url]));
766+
}
767+
});
693768
}
694769

695770
makeCustomElements() {
@@ -743,7 +818,7 @@ modulo.register('engine', class VirtualWindow {
743818
return url;
744819
}
745820
}, new this.window.URL(url));
746-
return window.fetch(url)
821+
return this.fetch(url)
747822
.then(response => response.text())
748823
.then(this.execHTML.bind(this))
749824
}
@@ -754,6 +829,11 @@ modulo.register('engine', class VirtualWindow {
754829
return func(this.window, this.document, this.HTMLElement);
755830
}
756831

832+
setVFS(sources) {
833+
this.isVirtualFileSystem = true;
834+
Object.assign(this.cachedForBlocking, sources);
835+
}
836+
757837
execHTML(pageContent) {
758838
// Start by stripping doctype
759839
const [ doctype, html ] = this._splitDoctype(pageContent);
@@ -770,7 +850,7 @@ modulo.register('engine', class VirtualWindow {
770850
const sources = /<SCRIPT[^>]*\sSRC\s*=\s*["']([^"']+)["']/gi;
771851
const promises = [];
772852
for (const match of pageContent.matchAll(sources)) {
773-
promises.push(window.fetch(match[1])
853+
promises.push(this.fetch(match[1])
774854
.then(response => response.text())
775855
.then(text => {
776856
data[match[1]] = text;

docs/demos/tests/mdu/index.html

+57-40
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,67 @@
1+
<!DOCTYPE html>
2+
<template Modulo>
3+
<Library -src="/demos/tests/mdu/utils-vwindow.html"></Library>
4+
<Library -src="/demos/tests/mdu/utils-taglex.html"></Library>
5+
</template>
6+
17
<script src="/js/Modulo.js"></script>
28
<script src="/demos/mdu/cparts/TestSuite.js"></script>
39
<script src="/demos/mdu/utils/taglex.js"></script>
4-
<script src="/demos/mdu/extensions/virtualdom.js"></script>
5-
<script src="/demos/mdu/extensions/nodeunit-helper.js"></script>
6-
7-
<template modulo-embed>
8-
<!-- Load tests for MDU -->
9-
<Library src="/demos/tests/mdu/utils-taglex.html"></Library>
10-
11-
<Component name="TestReporter">
12-
<Template>
13-
{% if state.success != null %}
14-
{% if state.success %}
15-
<h1 style="background: seagreen">OK (MDU)</h1>
16-
{% else %}
17-
<h1 style="background: red">FAILURE (MDU)</h1>
18-
{% endif %}
19-
{% else %}
20-
<button @click:=script.doTests>Run MDU tests</button>
21-
{% endif %}
22-
<p><em>Tip: Use the Developer Console to see the output.</em></p>
23-
</Template>
24-
<State
25-
success:=null
26-
></State>
27-
<Script>
28-
function doTests() {
29-
if (state.success === null) { // prevent double runs
30-
state.success = Modulo.cmd.test();
31-
}
32-
}
10+
<script src="/demos/mdu/utils/vwindow2.js"></script>
11+
<script>
3312

34-
const getParams = String(window.location.search || '').substr(1);
35-
function initializedCallback() {
36-
if (getParams.includes('run=y')) {
37-
doTests();
38-
}
39-
}
40-
</Script>
41-
</Component>
13+
/*
14+
Likely to be removed: A helper class that implements a function in
15+
node unit to make porting unit tests slightly easier.
16+
*/
17+
modulo.registry.utils.NodeUnitHelper = class NodeUnitHelper {
18+
constructor() {
19+
this.failures = [];
20+
this.failed = false;
21+
}
4222

23+
_deepEqual(left, right) {
24+
if (left === right) { // Same reference
25+
return true;
26+
}
4327

44-
</template>
28+
if (!left || !right || left !== Object(left) || right !== Object(right)) {
29+
return left === right; // Falsy values and other primitives
30+
}
31+
32+
if (Object.keys(left).length !== Object.keys(right).length) {
33+
return false;
34+
}
35+
36+
for (const key in left) { // Recurse into each property
37+
if (!(key in right) || !this._deepEqual(left[key], right[key])) {
38+
return false;
39+
}
40+
}
41+
return true;
42+
}
43+
44+
deepEqual(left, right, message = null) {
45+
if (message === null) {
46+
if (!this._inequalCount) {
47+
this._inequalCount = 1; // first
48+
} else {
49+
this._inequalCount++;
50+
}
51+
message = 'deepEqual#' + this._inequalCount;
52+
}
53+
const result = this._deepEqual(left, right);
54+
if (!result) {
55+
this.failures.push(message);
56+
this.failed = JSON.stringify(this.failures);
57+
}
58+
}
4559

46-
<script> Modulo.defineAll() </script>
60+
done() {
61+
// presently just a no-op
62+
}
63+
}
4764

48-
<x-TestReporter></x-TestReporter>
65+
</script>
4966

5067

0 commit comments

Comments
 (0)