DOM + DocumentUtil merge (#727)

* Add DOM functions to DocumentUtil

* Use DocumentUtil instead of DOM

* Remove DOM

* Move document-util.js into mixed
This commit is contained in:
toasted-nutbread 2020-08-09 21:07:11 -04:00 committed by GitHub
parent 9f8f83508e
commit 2a86d66092
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 157 additions and 185 deletions

View File

@ -42,12 +42,11 @@
"mixed/js/core.js", "mixed/js/core.js",
"mixed/js/yomichan.js", "mixed/js/yomichan.js",
"mixed/js/comm.js", "mixed/js/comm.js",
"mixed/js/dom.js",
"mixed/js/api.js", "mixed/js/api.js",
"mixed/js/dynamic-loader.js", "mixed/js/dynamic-loader.js",
"mixed/js/frame-client.js", "mixed/js/frame-client.js",
"mixed/js/text-scanner.js", "mixed/js/text-scanner.js",
"fg/js/document-util.js", "mixed/js/document-util.js",
"fg/js/dom-text-scanner.js", "fg/js/dom-text-scanner.js",
"fg/js/popup.js", "fg/js/popup.js",
"fg/js/source.js", "fg/js/source.js",

View File

@ -17,8 +17,8 @@
/* global /* global
* ClipboardMonitor * ClipboardMonitor
* DOM
* Display * Display
* DocumentUtil
* api * api
* wanakana * wanakana
*/ */
@ -104,7 +104,7 @@ class DisplaySearch extends Display {
} }
onKeyDown(e) { onKeyDown(e) {
const key = DOM.getKeyFromEvent(e); const key = DocumentUtil.getKeyFromEvent(e);
const ignoreKeys = this._onKeyDownIgnoreKeys; const ignoreKeys = this._onKeyDownIgnoreKeys;
const activeModifierMap = new Map([ const activeModifierMap = new Map([

View File

@ -16,7 +16,7 @@
*/ */
/* global /* global
* DOM * DocumentUtil
* conditionsNormalizeOptionValue * conditionsNormalizeOptionValue
*/ */
@ -323,7 +323,7 @@ ConditionsUI.Condition = class Condition {
const pressedKeyIndices = new Set(); const pressedKeyIndices = new Set();
const onKeyDown = ({originalEvent}) => { const onKeyDown = ({originalEvent}) => {
const pressedKeyEventName = DOM.getKeyFromEvent(originalEvent); const pressedKeyEventName = DocumentUtil.getKeyFromEvent(originalEvent);
if (pressedKeyEventName === 'Escape' || pressedKeyEventName === 'Backspace') { if (pressedKeyEventName === 'Escape' || pressedKeyEventName === 'Backspace') {
pressedKeyIndices.clear(); pressedKeyIndices.clear();
inputInner.val(''); inputInner.val('');
@ -331,7 +331,7 @@ ConditionsUI.Condition = class Condition {
return; return;
} }
const pressedModifiers = DOM.getActiveModifiers(originalEvent); const pressedModifiers = DocumentUtil.getActiveModifiers(originalEvent);
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/metaKey // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/metaKey
// https://askubuntu.com/questions/567731/why-is-shift-alt-being-mapped-to-meta // https://askubuntu.com/questions/567731/why-is-shift-alt-being-mapped-to-meta
// It works with mouse events on some platforms, so try to determine if metaKey is pressed // It works with mouse events on some platforms, so try to determine if metaKey is pressed

View File

@ -73,12 +73,11 @@
<script src="/mixed/js/core.js"></script> <script src="/mixed/js/core.js"></script>
<script src="/mixed/js/yomichan.js"></script> <script src="/mixed/js/yomichan.js"></script>
<script src="/mixed/js/comm.js"></script> <script src="/mixed/js/comm.js"></script>
<script src="/mixed/js/dom.js"></script>
<script src="/mixed/js/api.js"></script> <script src="/mixed/js/api.js"></script>
<script src="/mixed/js/japanese.js"></script> <script src="/mixed/js/japanese.js"></script>
<script src="/bg/js/handlebars.js"></script> <script src="/bg/js/handlebars.js"></script>
<script src="/fg/js/document-util.js"></script> <script src="/mixed/js/document-util.js"></script>
<script src="/fg/js/dom-text-scanner.js"></script> <script src="/fg/js/dom-text-scanner.js"></script>
<script src="/fg/js/source.js"></script> <script src="/fg/js/source.js"></script>
<script src="/mixed/js/audio-system.js"></script> <script src="/mixed/js/audio-system.js"></script>

View File

@ -121,13 +121,12 @@
<script src="/mixed/js/core.js"></script> <script src="/mixed/js/core.js"></script>
<script src="/mixed/js/yomichan.js"></script> <script src="/mixed/js/yomichan.js"></script>
<script src="/mixed/js/comm.js"></script> <script src="/mixed/js/comm.js"></script>
<script src="/mixed/js/dom.js"></script>
<script src="/mixed/js/api.js"></script> <script src="/mixed/js/api.js"></script>
<script src="/mixed/js/dynamic-loader.js"></script> <script src="/mixed/js/dynamic-loader.js"></script>
<script src="/mixed/js/frame-client.js"></script> <script src="/mixed/js/frame-client.js"></script>
<script src="/mixed/js/text-scanner.js"></script> <script src="/mixed/js/text-scanner.js"></script>
<script src="/fg/js/document-util.js"></script> <script src="/mixed/js/document-util.js"></script>
<script src="/fg/js/dom-text-scanner.js"></script> <script src="/fg/js/dom-text-scanner.js"></script>
<script src="/fg/js/popup.js"></script> <script src="/fg/js/popup.js"></script>
<script src="/fg/js/source.js"></script> <script src="/fg/js/source.js"></script>

View File

@ -1135,7 +1135,6 @@
<script src="/mixed/js/core.js"></script> <script src="/mixed/js/core.js"></script>
<script src="/mixed/js/yomichan.js"></script> <script src="/mixed/js/yomichan.js"></script>
<script src="/mixed/js/comm.js"></script> <script src="/mixed/js/comm.js"></script>
<script src="/mixed/js/dom.js"></script>
<script src="/mixed/js/environment.js"></script> <script src="/mixed/js/environment.js"></script>
<script src="/mixed/js/api.js"></script> <script src="/mixed/js/api.js"></script>
<script src="/mixed/js/japanese.js"></script> <script src="/mixed/js/japanese.js"></script>
@ -1148,6 +1147,7 @@
<script src="/bg/js/profile-conditions.js"></script> <script src="/bg/js/profile-conditions.js"></script>
<script src="/bg/js/util.js"></script> <script src="/bg/js/util.js"></script>
<script src="/mixed/js/audio-system.js"></script> <script src="/mixed/js/audio-system.js"></script>
<script src="/mixed/js/document-util.js"></script>
<script src="/bg/js/settings/anki.js"></script> <script src="/bg/js/settings/anki.js"></script>
<script src="/bg/js/settings/anki-templates.js"></script> <script src="/bg/js/settings/anki-templates.js"></script>

View File

@ -47,11 +47,10 @@
<script src="/mixed/js/core.js"></script> <script src="/mixed/js/core.js"></script>
<script src="/mixed/js/yomichan.js"></script> <script src="/mixed/js/yomichan.js"></script>
<script src="/mixed/js/comm.js"></script> <script src="/mixed/js/comm.js"></script>
<script src="/mixed/js/dom.js"></script>
<script src="/mixed/js/api.js"></script> <script src="/mixed/js/api.js"></script>
<script src="/mixed/js/japanese.js"></script> <script src="/mixed/js/japanese.js"></script>
<script src="/fg/js/document-util.js"></script> <script src="/mixed/js/document-util.js"></script>
<script src="/fg/js/dom-text-scanner.js"></script> <script src="/fg/js/dom-text-scanner.js"></script>
<script src="/fg/js/source.js"></script> <script src="/fg/js/source.js"></script>
<script src="/mixed/js/audio-system.js"></script> <script src="/mixed/js/audio-system.js"></script>

View File

@ -16,7 +16,6 @@
*/ */
/* global /* global
* DOM
* DocumentUtil * DocumentUtil
* FrameOffsetForwarder * FrameOffsetForwarder
* PopupProxy * PopupProxy
@ -98,7 +97,7 @@ class Frontend {
this._textScanner.prepare(); this._textScanner.prepare();
window.addEventListener('resize', this._onResize.bind(this), false); window.addEventListener('resize', this._onResize.bind(this), false);
DOM.addFullscreenChangeEventListener(this._updatePopup.bind(this)); DocumentUtil.addFullscreenChangeEventListener(this._updatePopup.bind(this));
const visualViewport = window.visualViewport; const visualViewport = window.visualViewport;
if (visualViewport !== null && typeof visualViewport === 'object') { if (visualViewport !== null && typeof visualViewport === 'object') {
@ -274,7 +273,7 @@ class Frontend {
if ( if (
isIframe && isIframe &&
showIframePopupsInRootFrame && showIframePopupsInRootFrame &&
DOM.getFullscreenElement() === null && DocumentUtil.getFullscreenElement() === null &&
this._allowRootFramePopupProxy this._allowRootFramePopupProxy
) { ) {
popupPromise = this._popupCache.get('iframe'); popupPromise = this._popupCache.get('iframe');

View File

@ -16,7 +16,7 @@
*/ */
/* global /* global
* DOM * DocumentUtil
* FrameClient * FrameClient
* api * api
* dynamicLoader * dynamicLoader
@ -349,7 +349,7 @@ class Popup {
return; return;
} }
DOM.addFullscreenChangeEventListener(this._onFullscreenChanged.bind(this), this._fullscreenEventListeners); DocumentUtil.addFullscreenChangeEventListener(this._onFullscreenChanged.bind(this), this._fullscreenEventListeners);
} }
_onFullscreenChanged() { _onFullscreenChanged() {
@ -475,7 +475,7 @@ class Popup {
_getFrameParentElement() { _getFrameParentElement() {
const defaultParent = document.body; const defaultParent = document.body;
const fullscreenElement = DOM.getFullscreenElement(); const fullscreenElement = DocumentUtil.getFullscreenElement();
if ( if (
fullscreenElement === null || fullscreenElement === null ||
fullscreenElement.shadowRoot || fullscreenElement.shadowRoot ||

View File

@ -41,12 +41,11 @@
"mixed/js/core.js", "mixed/js/core.js",
"mixed/js/yomichan.js", "mixed/js/yomichan.js",
"mixed/js/comm.js", "mixed/js/comm.js",
"mixed/js/dom.js",
"mixed/js/api.js", "mixed/js/api.js",
"mixed/js/dynamic-loader.js", "mixed/js/dynamic-loader.js",
"mixed/js/frame-client.js", "mixed/js/frame-client.js",
"mixed/js/text-scanner.js", "mixed/js/text-scanner.js",
"fg/js/document-util.js", "mixed/js/document-util.js",
"fg/js/dom-text-scanner.js", "fg/js/dom-text-scanner.js",
"fg/js/popup.js", "fg/js/popup.js",
"fg/js/source.js", "fg/js/source.js",

View File

@ -17,7 +17,6 @@
/* global /* global
* AudioSystem * AudioSystem
* DOM
* DisplayGenerator * DisplayGenerator
* DisplayHistory * DisplayHistory
* DocumentUtil * DocumentUtil
@ -186,11 +185,11 @@ class Display extends EventDispatcher {
} }
onKeyDown(e) { onKeyDown(e) {
const key = DOM.getKeyFromEvent(e); const key = DocumentUtil.getKeyFromEvent(e);
const handlers = this._hotkeys.get(key); const handlers = this._hotkeys.get(key);
if (typeof handlers === 'undefined') { return false; } if (typeof handlers === 'undefined') { return false; }
const eventModifiers = DOM.getActiveModifiers(e); const eventModifiers = DocumentUtil.getActiveModifiers(e);
for (const {modifiers, action} of handlers) { for (const {modifiers, action} of handlers) {
if (getSetDifference(modifiers, eventModifiers).size !== 0) { continue; } if (getSetDifference(modifiers, eventModifiers).size !== 0) { continue; }
@ -558,7 +557,7 @@ class Display extends EventDispatcher {
} }
_onGlossaryMouseDown(e) { _onGlossaryMouseDown(e) {
if (DOM.isMouseButtonPressed(e, 'primary')) { if (DocumentUtil.isMouseButtonPressed(e, 'primary')) {
this._clickScanPrevent = false; this._clickScanPrevent = false;
} }
} }
@ -568,7 +567,7 @@ class Display extends EventDispatcher {
} }
_onGlossaryMouseUp(e) { _onGlossaryMouseUp(e) {
if (!this._clickScanPrevent && DOM.isMouseButtonPressed(e, 'primary')) { if (!this._clickScanPrevent && DocumentUtil.isMouseButtonPressed(e, 'primary')) {
try { try {
this._onTermLookup(e); this._onTermLookup(e);
} catch (error) { } catch (error) {

View File

@ -16,7 +16,6 @@
*/ */
/* global /* global
* DOM
* DOMTextScanner * DOMTextScanner
* TextSourceElement * TextSourceElement
* TextSourceRange * TextSourceRange
@ -134,6 +133,132 @@ class DocumentUtil {
}; };
} }
static isPointInRect(x, y, rect) {
return (
x >= rect.left && x < rect.right &&
y >= rect.top && y < rect.bottom
);
}
static isPointInAnyRect(x, y, rects) {
for (const rect of rects) {
if (this.isPointInRect(x, y, rect)) {
return true;
}
}
return false;
}
static isPointInSelection(x, y, selection) {
for (let i = 0; i < selection.rangeCount; ++i) {
const range = selection.getRangeAt(i);
if (this.isPointInAnyRect(x, y, range.getClientRects())) {
return true;
}
}
return false;
}
static isMouseButtonPressed(mouseEvent, button) {
const mouseEventButton = mouseEvent.button;
switch (button) {
case 'primary': return mouseEventButton === 0;
case 'secondary': return mouseEventButton === 2;
case 'auxiliary': return mouseEventButton === 1;
default: return false;
}
}
static isMouseButtonDown(mouseEvent, button) {
const mouseEventButtons = mouseEvent.buttons;
switch (button) {
case 'primary': return (mouseEventButtons & 0x1) !== 0x0;
case 'secondary': return (mouseEventButtons & 0x2) !== 0x0;
case 'auxiliary': return (mouseEventButtons & 0x4) !== 0x0;
default: return false;
}
}
static getActiveModifiers(event) {
const modifiers = new Set();
if (event.altKey) { modifiers.add('alt'); }
if (event.ctrlKey) { modifiers.add('ctrl'); }
if (event.metaKey) { modifiers.add('meta'); }
if (event.shiftKey) { modifiers.add('shift'); }
return modifiers;
}
static getKeyFromEvent(event) {
const key = event.key;
return (typeof key === 'string' ? (key.length === 1 ? key.toUpperCase() : key) : '');
}
static addFullscreenChangeEventListener(onFullscreenChanged, eventListenerCollection=null) {
const target = document;
const options = false;
const fullscreenEventNames = [
'fullscreenchange',
'MSFullscreenChange',
'mozfullscreenchange',
'webkitfullscreenchange'
];
for (const eventName of fullscreenEventNames) {
if (eventListenerCollection === null) {
target.addEventListener(eventName, onFullscreenChanged, options);
} else {
eventListenerCollection.addEventListener(target, eventName, onFullscreenChanged, options);
}
}
}
static getFullscreenElement() {
return (
document.fullscreenElement ||
document.msFullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement ||
null
);
}
static getNodesInRange(range) {
const end = range.endContainer;
const nodes = [];
for (let node = range.startContainer; node !== null; node = this.getNextNode(node)) {
nodes.push(node);
if (node === end) { break; }
}
return nodes;
}
static getNextNode(node) {
let next = node.firstChild;
if (next === null) {
while (true) {
next = node.nextSibling;
if (next !== null) { break; }
next = node.parentNode;
if (next === null) { break; }
node = next;
}
}
return next;
}
static anyNodeMatchesSelector(nodes, selector) {
const ELEMENT_NODE = Node.ELEMENT_NODE;
for (let node of nodes) {
for (; node !== null; node = node.parentNode) {
if (node.nodeType !== ELEMENT_NODE) { continue; }
if (node.matches(selector)) { return true; }
break;
}
}
return false;
}
// Private // Private
_setImposterStyle(style, propertyName, value) { _setImposterStyle(style, propertyName, value) {
@ -240,7 +365,7 @@ class DocumentUtil {
const {node, offset, content} = new DOMTextScanner(range.endContainer, range.endOffset, true, false).seek(1); const {node, offset, content} = new DOMTextScanner(range.endContainer, range.endOffset, true, false).seek(1);
range.setEnd(node, offset); range.setEnd(node, offset);
if (!this._isWhitespace(content) && DOM.isPointInAnyRect(x, y, range.getClientRects())) { if (!this._isWhitespace(content) && DocumentUtil.isPointInAnyRect(x, y, range.getClientRects())) {
return true; return true;
} }
} finally { } finally {
@ -251,7 +376,7 @@ class DocumentUtil {
const {node, offset, content} = new DOMTextScanner(range.startContainer, range.startOffset, true, false).seek(-1); const {node, offset, content} = new DOMTextScanner(range.startContainer, range.startOffset, true, false).seek(-1);
range.setStart(node, offset); range.setStart(node, offset);
if (!this._isWhitespace(content) && DOM.isPointInAnyRect(x, y, range.getClientRects())) { if (!this._isWhitespace(content) && DocumentUtil.isPointInAnyRect(x, y, range.getClientRects())) {
// This purposefully leaves the starting offset as modified and sets the range length to 0. // This purposefully leaves the starting offset as modified and sets the range length to 0.
range.setEnd(node, offset); range.setEnd(node, offset);
return true; return true;

View File

@ -1,145 +0,0 @@
/*
* Copyright (C) 2019-2020 Yomichan Authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
class DOM {
static isPointInRect(x, y, rect) {
return (
x >= rect.left && x < rect.right &&
y >= rect.top && y < rect.bottom
);
}
static isPointInAnyRect(x, y, rects) {
for (const rect of rects) {
if (DOM.isPointInRect(x, y, rect)) {
return true;
}
}
return false;
}
static isPointInSelection(x, y, selection) {
for (let i = 0; i < selection.rangeCount; ++i) {
const range = selection.getRangeAt(i);
if (DOM.isPointInAnyRect(x, y, range.getClientRects())) {
return true;
}
}
return false;
}
static isMouseButtonPressed(mouseEvent, button) {
const mouseEventButton = mouseEvent.button;
switch (button) {
case 'primary': return mouseEventButton === 0;
case 'secondary': return mouseEventButton === 2;
case 'auxiliary': return mouseEventButton === 1;
default: return false;
}
}
static isMouseButtonDown(mouseEvent, button) {
const mouseEventButtons = mouseEvent.buttons;
switch (button) {
case 'primary': return (mouseEventButtons & 0x1) !== 0x0;
case 'secondary': return (mouseEventButtons & 0x2) !== 0x0;
case 'auxiliary': return (mouseEventButtons & 0x4) !== 0x0;
default: return false;
}
}
static getActiveModifiers(event) {
const modifiers = new Set();
if (event.altKey) { modifiers.add('alt'); }
if (event.ctrlKey) { modifiers.add('ctrl'); }
if (event.metaKey) { modifiers.add('meta'); }
if (event.shiftKey) { modifiers.add('shift'); }
return modifiers;
}
static getKeyFromEvent(event) {
const key = event.key;
return (typeof key === 'string' ? (key.length === 1 ? key.toUpperCase() : key) : '');
}
static addFullscreenChangeEventListener(onFullscreenChanged, eventListenerCollection=null) {
const target = document;
const options = false;
const fullscreenEventNames = [
'fullscreenchange',
'MSFullscreenChange',
'mozfullscreenchange',
'webkitfullscreenchange'
];
for (const eventName of fullscreenEventNames) {
if (eventListenerCollection === null) {
target.addEventListener(eventName, onFullscreenChanged, options);
} else {
eventListenerCollection.addEventListener(target, eventName, onFullscreenChanged, options);
}
}
}
static getFullscreenElement() {
return (
document.fullscreenElement ||
document.msFullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement ||
null
);
}
static getNodesInRange(range) {
const end = range.endContainer;
const nodes = [];
for (let node = range.startContainer; node !== null; node = DOM.getNextNode(node)) {
nodes.push(node);
if (node === end) { break; }
}
return nodes;
}
static getNextNode(node) {
let next = node.firstChild;
if (next === null) {
while (true) {
next = node.nextSibling;
if (next !== null) { break; }
next = node.parentNode;
if (next === null) { break; }
node = next;
}
}
return next;
}
static anyNodeMatchesSelector(nodes, selector) {
const ELEMENT_NODE = Node.ELEMENT_NODE;
for (let node of nodes) {
for (; node !== null; node = node.parentNode) {
if (node.nodeType !== ELEMENT_NODE) { continue; }
if (node.matches(selector)) { return true; }
break;
}
}
return false;
}
}

View File

@ -16,7 +16,7 @@
*/ */
/* global /* global
* DOM * DocumentUtil
*/ */
class TextScanner extends EventDispatcher { class TextScanner extends EventDispatcher {
@ -155,8 +155,8 @@ class TextScanner extends EventDispatcher {
if (this._ignoreNodes !== null && clonedTextSource.range) { if (this._ignoreNodes !== null && clonedTextSource.range) {
length = clonedTextSource.text().length; length = clonedTextSource.text().length;
while (clonedTextSource.range && length > 0) { while (clonedTextSource.range && length > 0) {
const nodes = DOM.getNodesInRange(clonedTextSource.range); const nodes = DocumentUtil.getNodesInRange(clonedTextSource.range);
if (!DOM.anyNodeMatchesSelector(nodes, this._ignoreNodes)) { if (!DocumentUtil.anyNodeMatchesSelector(nodes, this._ignoreNodes)) {
break; break;
} }
--length; --length;
@ -204,16 +204,16 @@ class TextScanner extends EventDispatcher {
_onMouseMove(e) { _onMouseMove(e) {
this._scanTimerClear(); this._scanTimerClear();
if (this._pendingLookup || DOM.isMouseButtonDown(e, 'primary')) { if (this._pendingLookup || DocumentUtil.isMouseButtonDown(e, 'primary')) {
return; return;
} }
const modifiers = DOM.getActiveModifiers(e); const modifiers = DocumentUtil.getActiveModifiers(e);
this.trigger('activeModifiersChanged', {modifiers}); this.trigger('activeModifiersChanged', {modifiers});
if (!( if (!(
this._isScanningModifierPressed(this._modifier, e) || this._isScanningModifierPressed(this._modifier, e) ||
(this._useMiddleMouse && DOM.isMouseButtonDown(e, 'auxiliary')) (this._useMiddleMouse && DocumentUtil.isMouseButtonDown(e, 'auxiliary'))
)) { )) {
return; return;
} }
@ -241,7 +241,7 @@ class TextScanner extends EventDispatcher {
return false; return false;
} }
if (DOM.isMouseButtonDown(e, 'primary')) { if (DocumentUtil.isMouseButtonDown(e, 'primary')) {
this._scanTimerClear(); this._scanTimerClear();
this.clearSelection(false); this.clearSelection(false);
} }
@ -284,7 +284,7 @@ class TextScanner extends EventDispatcher {
this._preventNextClick = false; this._preventNextClick = false;
const primaryTouch = e.changedTouches[0]; const primaryTouch = e.changedTouches[0];
if (DOM.isPointInSelection(primaryTouch.clientX, primaryTouch.clientY, window.getSelection())) { if (DocumentUtil.isPointInSelection(primaryTouch.clientX, primaryTouch.clientY, window.getSelection())) {
return; return;
} }

View File

@ -93,10 +93,9 @@ async function testDocument1() {
const vm = new VM({document, window, Range, Node}); const vm = new VM({document, window, Range, Node});
vm.execute([ vm.execute([
'mixed/js/dom.js',
'fg/js/dom-text-scanner.js', 'fg/js/dom-text-scanner.js',
'fg/js/source.js', 'fg/js/source.js',
'fg/js/document-util.js' 'mixed/js/document-util.js'
]); ]);
const [DOMTextScanner, TextSourceRange, TextSourceElement, DocumentUtil] = vm.get([ const [DOMTextScanner, TextSourceRange, TextSourceElement, DocumentUtil] = vm.get([
'DOMTextScanner', 'DOMTextScanner',