Remove dependance on global window and document (#2897)

* Remove dependance on global window and document

* Use any available document, prioritising parent.ownerDocument

* Fix mockDom for DocumentFragment, revert to better ownerDocument implementation

* Simplify activeElement usage
This commit is contained in:
Kory Nunn 2024-07-30 01:26:51 +10:00 committed by Claudia Meadows
parent 5488953436
commit 8e7ef427e0
No known key found for this signature in database
GPG key ID: C86B594396786760
2 changed files with 22 additions and 17 deletions

View file

@ -5,9 +5,7 @@ var df = require("../render/domFor")
var delayedRemoval = df.delayedRemoval var delayedRemoval = df.delayedRemoval
var domFor = df.domFor var domFor = df.domFor
module.exports = function($window) { module.exports = function() {
var $doc = $window && $window.document
var nameSpace = { var nameSpace = {
svg: "http://www.w3.org/2000/svg", svg: "http://www.w3.org/2000/svg",
math: "http://www.w3.org/1998/Math/MathML" math: "http://www.w3.org/1998/Math/MathML"
@ -16,6 +14,10 @@ module.exports = function($window) {
var currentRedraw var currentRedraw
var currentRender var currentRender
function getDocument(dom) {
return dom.ownerDocument;
}
function getNameSpace(vnode) { function getNameSpace(vnode) {
return vnode.attrs && vnode.attrs.xmlns || nameSpace[vnode.tag] return vnode.attrs && vnode.attrs.xmlns || nameSpace[vnode.tag]
} }
@ -40,9 +42,9 @@ module.exports = function($window) {
// IE11 (at least) throws an UnspecifiedError when accessing document.activeElement when // IE11 (at least) throws an UnspecifiedError when accessing document.activeElement when
// inside an iframe. Catch and swallow this error, and heavy-handidly return null. // inside an iframe. Catch and swallow this error, and heavy-handidly return null.
function activeElement() { function activeElement(dom) {
try { try {
return $doc.activeElement return getDocument(dom).activeElement
} catch (e) { } catch (e) {
return null return null
} }
@ -71,7 +73,7 @@ module.exports = function($window) {
else createComponent(parent, vnode, hooks, ns, nextSibling) else createComponent(parent, vnode, hooks, ns, nextSibling)
} }
function createText(parent, vnode, nextSibling) { function createText(parent, vnode, nextSibling) {
vnode.dom = $doc.createTextNode(vnode.children) vnode.dom = getDocument(parent).createTextNode(vnode.children)
insertDOM(parent, vnode.dom, nextSibling) insertDOM(parent, vnode.dom, nextSibling)
} }
var possibleParents = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"} var possibleParents = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"}
@ -82,7 +84,7 @@ module.exports = function($window) {
// div.innerHTML = "<td>i</td><td>j</td>" // div.innerHTML = "<td>i</td><td>j</td>"
// console.log(div.innerHTML) // console.log(div.innerHTML)
// --> "ij", no <td> in sight. // --> "ij", no <td> in sight.
var temp = $doc.createElement(possibleParents[match[1]] || "div") var temp = getDocument(parent).createElement(possibleParents[match[1]] || "div")
if (ns === "http://www.w3.org/2000/svg") { if (ns === "http://www.w3.org/2000/svg") {
temp.innerHTML = "<svg xmlns=\"http://www.w3.org/2000/svg\">" + vnode.children + "</svg>" temp.innerHTML = "<svg xmlns=\"http://www.w3.org/2000/svg\">" + vnode.children + "</svg>"
temp = temp.firstChild temp = temp.firstChild
@ -92,7 +94,7 @@ module.exports = function($window) {
vnode.dom = temp.firstChild vnode.dom = temp.firstChild
vnode.domSize = temp.childNodes.length vnode.domSize = temp.childNodes.length
// Capture nodes to remove, so we don't confuse them. // Capture nodes to remove, so we don't confuse them.
var fragment = $doc.createDocumentFragment() var fragment = getDocument(parent).createDocumentFragment()
var child var child
while (child = temp.firstChild) { while (child = temp.firstChild) {
fragment.appendChild(child) fragment.appendChild(child)
@ -100,7 +102,7 @@ module.exports = function($window) {
insertDOM(parent, fragment, nextSibling) insertDOM(parent, fragment, nextSibling)
} }
function createFragment(parent, vnode, hooks, ns, nextSibling) { function createFragment(parent, vnode, hooks, ns, nextSibling) {
var fragment = $doc.createDocumentFragment() var fragment = getDocument(parent).createDocumentFragment()
if (vnode.children != null) { if (vnode.children != null) {
var children = vnode.children var children = vnode.children
createNodes(fragment, children, 0, children.length, hooks, null, ns) createNodes(fragment, children, 0, children.length, hooks, null, ns)
@ -117,8 +119,8 @@ module.exports = function($window) {
ns = getNameSpace(vnode) || ns ns = getNameSpace(vnode) || ns
var element = ns ? var element = ns ?
is ? $doc.createElementNS(ns, tag, {is: is}) : $doc.createElementNS(ns, tag) : is ? getDocument(parent).createElementNS(ns, tag, {is: is}) : getDocument(parent).createElementNS(ns, tag) :
is ? $doc.createElement(tag, {is: is}) : $doc.createElement(tag) is ? getDocument(parent).createElement(tag, {is: is}) : getDocument(parent).createElement(tag)
vnode.dom = element vnode.dom = element
if (attrs != null) { if (attrs != null) {
@ -553,7 +555,7 @@ module.exports = function($window) {
// don't allocate for the common case // don't allocate for the common case
target = vnode.dom target = vnode.dom
} else { } else {
target = $doc.createDocumentFragment() target = getDocument(parent).createDocumentFragment()
for (var dom of domFor(vnode)) target.appendChild(dom) for (var dom of domFor(vnode)) target.appendChild(dom)
} }
insertDOM(parent, target, nextSibling) insertDOM(parent, target, nextSibling)
@ -693,7 +695,7 @@ module.exports = function($window) {
/* eslint-disable no-implicit-coercion */ /* eslint-disable no-implicit-coercion */
//setting input[value] to same value by typing on focused element moves cursor to end in Chrome //setting input[value] to same value by typing on focused element moves cursor to end in Chrome
//setting input[type=file][value] to same value causes an error to be generated if it's non-empty //setting input[type=file][value] to same value causes an error to be generated if it's non-empty
if ((vnode.tag === "input" || vnode.tag === "textarea") && vnode.dom.value === "" + value && (isFileInput || vnode.dom === activeElement())) return if ((vnode.tag === "input" || vnode.tag === "textarea") && vnode.dom.value === "" + value && (isFileInput || vnode.dom === activeElement(vnode.dom))) return
//setting select[value] to same value while having select open blinks select dropdown in Chrome //setting select[value] to same value while having select open blinks select dropdown in Chrome
if (vnode.tag === "select" && old !== null && vnode.dom.value === "" + value) return if (vnode.tag === "select" && old !== null && vnode.dom.value === "" + value) return
//setting option[value] to same value while having select open blinks select dropdown in Chrome //setting option[value] to same value while having select open blinks select dropdown in Chrome
@ -722,7 +724,7 @@ module.exports = function($window) {
&& key !== "title" // creates "null" as title && key !== "title" // creates "null" as title
&& !(key === "value" && ( && !(key === "value" && (
vnode.tag === "option" vnode.tag === "option"
|| vnode.tag === "select" && vnode.dom.selectedIndex === -1 && vnode.dom === activeElement() || vnode.tag === "select" && vnode.dom.selectedIndex === -1 && vnode.dom === activeElement(vnode.dom)
)) ))
&& !(vnode.tag === "input" && key === "type") && !(vnode.tag === "input" && key === "type")
) { ) {
@ -771,7 +773,7 @@ module.exports = function($window) {
} }
} }
function isFormAttribute(vnode, attr) { function isFormAttribute(vnode, attr) {
return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode.dom === activeElement() || vnode.tag === "option" && vnode.dom.parentNode === $doc.activeElement return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode.dom === activeElement(vnode.dom) || vnode.tag === "option" && vnode.dom.parentNode === activeElement(vnode.dom)
} }
function isLifecycleMethod(attr) { function isLifecycleMethod(attr) {
return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove" || attr === "onbeforeupdate" return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove" || attr === "onbeforeupdate"
@ -923,7 +925,7 @@ module.exports = function($window) {
var prevRedraw = currentRedraw var prevRedraw = currentRedraw
var prevDOM = currentDOM var prevDOM = currentDOM
var hooks = [] var hooks = []
var active = activeElement() var active = activeElement(dom)
var namespace = dom.namespaceURI var namespace = dom.namespaceURI
currentDOM = dom currentDOM = dom
@ -936,7 +938,7 @@ module.exports = function($window) {
updateNodes(dom, dom.vnodes, vnodes, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace) updateNodes(dom, dom.vnodes, vnodes, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace)
dom.vnodes = vnodes dom.vnodes = vnodes
// `document.activeElement` can return null: https://html.spec.whatwg.org/multipage/interaction.html#dom-document-activeelement // `document.activeElement` can return null: https://html.spec.whatwg.org/multipage/interaction.html#dom-document-activeelement
if (active != null && activeElement() !== active && typeof active.focus === "function") active.focus() if (active != null && activeElement(dom) !== active && typeof active.focus === "function") active.focus()
for (var i = 0; i < hooks.length; i++) hooks[i]() for (var i = 0; i < hooks.length; i++) hooks[i]()
} finally { } finally {
currentRedraw = prevRedraw currentRedraw = prevRedraw

View file

@ -307,6 +307,7 @@ module.exports = function(options) {
parentNode: null, parentNode: null,
childNodes: [], childNodes: [],
attributes: {}, attributes: {},
ownerDocument: $window.document,
contains: function(child) { contains: function(child) {
while (child != null) { while (child != null) {
if (child === this) return true if (child === this) return true
@ -717,6 +718,7 @@ module.exports = function(options) {
}, },
createDocumentFragment: function() { createDocumentFragment: function() {
return { return {
ownerDocument: $window.document,
nodeType: 11, nodeType: 11,
nodeName: "#document-fragment", nodeName: "#document-fragment",
appendChild: appendChild, appendChild: appendChild,
@ -738,6 +740,7 @@ module.exports = function(options) {
get activeElement() {return activeElement}, get activeElement() {return activeElement},
}, },
} }
$window.document.defaultView = $window
$window.document.documentElement = $window.document.createElement("html") $window.document.documentElement = $window.document.createElement("html")
appendChild.call($window.document.documentElement, $window.document.createElement("head")) appendChild.call($window.document.documentElement, $window.document.createElement("head"))
$window.document.body = $window.document.createElement("body") $window.document.body = $window.document.createElement("body")