Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/pretty-glasses-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@qwik.dev/core': patch
---

fix: resuming app with non-qwik elements inside
10 changes: 5 additions & 5 deletions packages/qwik/src/core/client/process-vnode-data.unit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ describe('processVnodeData', () => {
<div q:shadowRoot>
<template>
<div q:container="paused">
<button>
<button :>
0
</button>
<script type="qwik/vnode">
<script : type="qwik/vnode">
~{1}!~
</script>
</div>
Expand Down Expand Up @@ -66,7 +66,7 @@ describe('processVnodeData', () => {
<html q:container="paused">
<head :></head>
<body :>
<div q:container="html"><span></span></div>
<div q:container="html" :><span></span></div>
<b :>HelloWorld</b>
${encodeVNode({ 2: '2', 4: 'FF' })}
</body>
Expand All @@ -91,7 +91,7 @@ describe('processVnodeData', () => {
<html q:container="paused">
<head :></head>
<body :>
<div q:container="html"><span></span></div>
<div q:container="html" :><span></span></div>
<div>ignore this</div>
<b :>HelloWorld</b>
${encodeVNode({ 2: '3', 4: 'FF' })}
Expand Down Expand Up @@ -119,7 +119,7 @@ describe('processVnodeData', () => {
<head :></head>
<body :>
Before
<div q:container="paused">
<div q:container="paused" :>
Foo<b :>Bar!</b>
${encodeVNode({ 0: 'D1', 1: 'DB' })}
</div>
Expand Down
45 changes: 33 additions & 12 deletions packages/qwik/src/core/client/vnode-namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,22 +100,36 @@ export function vnode_getDomChildrenWithCorrectNamespacesToInsert(
return domChildren;
}

/** This function clones an element with a different namespace, but without the children. */
function cloneElementWithNamespace(
/** This function clones an element with a different namespace, including the children */
function cloneDomTreeWithNamespace(
element: Element,
elementName: string,
namespace: string
namespace: string,
deep = false
): Element {
const newElement = element.ownerDocument.createElementNS(namespace, elementName);
const attributes = element.attributes;
for (const attribute of attributes) {
const name = attribute.name;
const value = attribute.value;
if (!name || name === Q_PROPS_SEPARATOR) {
continue;

// Copy all attributes
for (const attr of element.attributes) {
if (attr.name !== Q_PROPS_SEPARATOR) {
newElement.setAttribute(attr.name, attr.value);
}
newElement.setAttribute(name, value);
}

if (deep) {
// Recursively clone all child nodes
for (const child of element.childNodes) {
const nodeType = child.nodeType;
if (nodeType === 3 /* Node.TEXT_NODE */) {
newElement.appendChild(child.cloneNode());
} else if (nodeType === 1 /* Node.ELEMENT_NODE */) {
newElement.appendChild(
cloneDomTreeWithNamespace(child as Element, (child as Element).localName, namespace, deep)
);
}
}
}

return newElement;
}

Expand Down Expand Up @@ -159,8 +173,15 @@ function vnode_cloneElementWithNamespace(
namespace = namespaceData.elementNamespace;
namespaceFlag = namespaceData.elementNamespaceFlag;
}
const vFirstChild = vnode_getFirstChild(vCursor);

newChildElement = cloneElementWithNamespace(childElement, childElementTag, namespace);
newChildElement = cloneDomTreeWithNamespace(
childElement,
childElementTag,
namespace,
// deep if there is no vnode children, children are probably inserted via innerHTML
!vFirstChild
);

childElement.remove();

Expand All @@ -173,7 +194,7 @@ function vnode_cloneElementWithNamespace(

// Descend into children
// We need first get the first child, if any
const vFirstChild = vnode_getFirstChild(vCursor);

// Then we can overwrite the cursor with newly created element.
// This is because we need to materialize the children before we assign new element
vCursor.element = newChildElement;
Expand Down
49 changes: 30 additions & 19 deletions packages/qwik/src/core/client/vnode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1478,27 +1478,28 @@ const fastGetter = <T>(prototype: any, name: string): T => {
);
};

const isQStyleElement = (node: Node | null): node is Element => {
const hasQStyleAttribute = (element: Element): boolean => {
return (
isElement(node) &&
node.nodeName === 'STYLE' &&
(node.hasAttribute(QScopedStyle) || node.hasAttribute(QStyle))
element.nodeName === 'STYLE' &&
(element.hasAttribute(QScopedStyle) || element.hasAttribute(QStyle))
);
};

const hasPropsSeparator = (element: Element): boolean => {
return element.hasAttribute(Q_PROPS_SEPARATOR);
};

const materializeFromDOM = (vParent: ElementVNode, firstChild: Node | null, vData?: string) => {
let vFirstChild: VNode | null = null;

const skipStyleElements = () => {
while (isQStyleElement(child)) {
// skip over style elements, as those need to be moved to the head.
// VNode pretends that `<style q:style q:sstyle>` elements do not exist.
const skipElements = () => {
while (isElement(child) && shouldSkipElement(child)) {
child = fastNextSibling(child);
}
};
// materialize from DOM
let child = firstChild;
skipStyleElements();
skipElements();
let vChild: VNode | null = null;
while (child) {
const nodeType = fastNodeType(child);
Expand All @@ -1518,7 +1519,7 @@ const materializeFromDOM = (vParent: ElementVNode, firstChild: Node | null, vDat
vParent.firstChild = vFirstChild = vChild;
}
child = fastNextSibling(child);
skipStyleElements();
skipElements();
}
vParent.lastChild = vChild || null;
vParent.firstChild = vFirstChild;
Expand Down Expand Up @@ -1785,6 +1786,17 @@ export function vnode_toString(
const isNumber = (ch: number) => /* `0` */ 48 <= ch && ch <= 57; /* `9` */
const isLowercase = (ch: number) => /* `a` */ 97 <= ch && ch <= 122; /* `z` */

function shouldSkipElement(element: Element) {
return (
// Skip over elements that don't have a props separator. They are not rendered by Qwik.
!hasPropsSeparator(element) ||
// We pretend that style element's don't exist as they can get moved out.
// skip over style elements, as those need to be moved to the head
// and are not included in the counts.
hasQStyleAttribute(element)
);
}

const stack: any[] = [];
function materializeFromVNodeData(
vParent: ElementVNode | VirtualVNode,
Expand Down Expand Up @@ -1813,16 +1825,15 @@ function materializeFromVNodeData(
let combinedText: string | null = null;
let container: ClientContainer | null = null;

const shouldSkipNode = (node: Node | null) => {
const nodeIsElement = isElement(node);
return !nodeIsElement || (nodeIsElement && shouldSkipElement(node));
};

processVNodeData(vData, (peek, consumeValue, consume, getChar, nextToConsumeIdx) => {
if (isNumber(peek())) {
// Element counts get encoded as numbers.
while (
!isElement(child) ||
// We pretend that style element's don't exist as they can get moved out.
// skip over style elements, as those need to be moved to the head
// and are not included in the counts.
isQStyleElement(child)
) {
while (shouldSkipNode(child)) {
child = fastNextSibling(child);
if (!child) {
throw qError(QError.materializeVNodeDataError, [vData, peek(), nextToConsumeIdx]);
Expand Down Expand Up @@ -1902,8 +1913,8 @@ function materializeFromVNodeData(
} else if (peek() === VNodeDataChar.SLOT) {
vParent.setAttr(QSlot, consumeValue(), null);
} else {
// skip over style elements in front of text nodes, where text node is the first child (except the style node)
while (isQStyleElement(child)) {
// skip over style or non-qwik elements in front of text nodes, where text node is the first child (except the style node)
while (isElement(child) && shouldSkipElement(child)) {
child = fastNextSibling(child);
}
const textNode =
Expand Down
Loading
Loading