Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(runtime): mock querySelectorAll #6175

Merged
merged 6 commits into from
Feb 25, 2025
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: 3 additions & 2 deletions src/client/client-patch-browser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BUILD, NAMESPACE } from '@app-data';
import { consoleDevInfo, doc, H, promiseResolve } from '@platform';
import { consoleDevInfo, H, promiseResolve, win } from '@platform';

import type * as d from '../declarations';

Expand All @@ -15,7 +15,8 @@ export const patchBrowser = (): Promise<d.CustomElementsDefineOptions> => {
}

const scriptElm = BUILD.scriptDataOpts
? Array.from(doc.querySelectorAll('script')).find(
? win.document &&
Array.from(win.document.querySelectorAll('script')).find(
(s) =>
new RegExp(`\/${NAMESPACE}(\\.esm)?\\.js($|\\?|#)`).test(s.src) ||
s.getAttribute('data-stencil-namespace') === NAMESPACE,
Expand Down
8 changes: 5 additions & 3 deletions src/client/client-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { BUILD } from '@app-data';

import type * as d from '../declarations';

export const win = typeof window !== 'undefined' ? window : ({} as Window);
interface StencilWindow extends Omit<Window, 'document'> {
document?: Document;
}

export const doc = win.document || ({ head: {} } as Document);
export const win = (typeof window !== 'undefined' ? window : ({} as StencilWindow)) as StencilWindow;

export const H = ((win as any).HTMLElement || (class {} as any)) as HTMLElement;

Expand Down Expand Up @@ -33,7 +35,7 @@ export const supportsShadow = BUILD.shadowDom;
export const supportsListenerOptions = /*@__PURE__*/ (() => {
let supportsListenerOptions = false;
try {
doc.addEventListener(
win.document?.addEventListener(
'e',
null,
Object.defineProperty({}, 'passive', {
Expand Down
12 changes: 6 additions & 6 deletions src/compiler/docs/style-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,22 @@ function parseCssComment(styleDocs: d.StyleDoc[], comment: string, mode: string
const docs = comment.split(CSS_PROP_ANNOTATION);

docs.forEach((d) => {
const doc = d.trim();
const cssDocument = d.trim();

if (!doc.startsWith(`--`)) {
if (!cssDocument.startsWith(`--`)) {
return;
}

const splt = doc.split(`:`);
const cssDoc: d.StyleDoc = {
const splt = cssDocument.split(`:`);
const styleDoc: d.StyleDoc = {
name: splt[0].trim(),
docs: (splt.shift() && splt.join(`:`)).trim(),
annotation: 'prop',
mode,
};

if (!styleDocs.some((c) => c.name === cssDoc.name && c.annotation === 'prop')) {
styleDocs.push(cssDoc);
if (!styleDocs.some((c) => c.name === styleDoc.name && c.annotation === 'prop')) {
styleDocs.push(styleDoc);
}
});
}
Expand Down
4 changes: 2 additions & 2 deletions src/hydrate/platform/hydrate-app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { globalScripts } from '@app-globals';
import { addHostEventListeners, doc, getHostRef, loadModule, plt, registerHost } from '@platform';
import { addHostEventListeners, getHostRef, loadModule, plt, registerHost } from '@platform';
import { connectedCallback, insertVdomAnnotations } from '@runtime';
import { CMP_FLAGS } from '@utils';

Expand Down Expand Up @@ -169,7 +169,7 @@ export function hydrateApp(
// ensure we use NodeJS's native setTimeout, not the mocked hydrate app scoped one
tmrId = globalThis.setTimeout(timeoutExceeded, opts.timeout);

plt.$resourcesUrl$ = new URL(opts.resourcesUrl || './', doc.baseURI).href;
plt.$resourcesUrl$ = new URL(opts.resourcesUrl || './', win.document.baseURI).href;

globalScripts();

Expand Down
2 changes: 0 additions & 2 deletions src/hydrate/platform/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ export const registerComponents = (Cstrs: d.ComponentNativeConstructor[]) => {

export const win = window;

export const doc = win.document;

export const readTask = (cb: Function) => {
nextTick(() => {
try {
Expand Down
15 changes: 10 additions & 5 deletions src/runtime/bootstrap-lazy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BUILD } from '@app-data';
import { doc, getHostRef, plt, registerHost, supportsShadow, win } from '@platform';
import { getHostRef, plt, registerHost, supportsShadow, win } from '@platform';
import { addHostEventListeners } from '@runtime';
import { CMP_FLAGS, queryNonceMetaTagContent } from '@utils';

Expand Down Expand Up @@ -27,19 +27,24 @@ export const bootstrapLazy = (lazyBundles: d.LazyBundlesRuntimeData, options: d.
}
installDevTools();

if (!win.document) {
console.warn('Stencil: No document found. Skipping bootstrapping lazy components.');
return;
}

const endBootstrap = createTime('bootstrapLazy');
const cmpTags: string[] = [];
const exclude = options.exclude || [];
const customElements = win.customElements;
const head = doc.head;
const head = win.document.head;
const metaCharset = /*@__PURE__*/ head.querySelector('meta[charset]');
const dataStyles = /*@__PURE__*/ doc.createElement('style');
const dataStyles = /*@__PURE__*/ win.document.createElement('style');
const deferredConnectedCallbacks: { connectedCallback: () => void }[] = [];
let appLoadFallback: any;
let isBootstrapping = true;

Object.assign(plt, options);
plt.$resourcesUrl$ = new URL(options.resourcesUrl || './', doc.baseURI).href;
plt.$resourcesUrl$ = new URL(options.resourcesUrl || './', win.document.baseURI).href;
if (BUILD.asyncQueue) {
if (options.syncQueue) {
plt.$flags$ |= PLATFORM_FLAGS.queueSync;
Expand Down Expand Up @@ -259,7 +264,7 @@ export const bootstrapLazy = (lazyBundles: d.LazyBundlesRuntimeData, options: d.
dataStyles.setAttribute('data-styles', '');

// Apply CSP nonce to the style tag if it exists
const nonce = plt.$nonce$ ?? queryNonceMetaTagContent(doc);
const nonce = plt.$nonce$ ?? queryNonceMetaTagContent(win.document);
if (nonce != null) {
dataStyles.setAttribute('nonce', nonce);
}
Expand Down
10 changes: 5 additions & 5 deletions src/runtime/client-hydrate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BUILD } from '@app-data';
import { doc, plt } from '@platform';
import { plt, win } from '@platform';
import { CMP_FLAGS } from '@utils';

import type * as d from '../declarations';
Expand Down Expand Up @@ -64,10 +64,10 @@ export const initializeClientHydrate = (
}
}

if (!plt.$orgLocNodes$ || !plt.$orgLocNodes$.size) {
if (win.document && (!plt.$orgLocNodes$ || !plt.$orgLocNodes$.size)) {
// This is the first pass over of this whole document;
// does a scrape to construct a 'bare-bones' tree of what elements we have and where content has been moved from
initializeDocumentHydrate(doc.body, (plt.$orgLocNodes$ = new Map()));
initializeDocumentHydrate(win.document.body, (plt.$orgLocNodes$ = new Map()));
}

hostElm[HYDRATE_ID] = hostId;
Expand Down Expand Up @@ -578,11 +578,11 @@ function addSlot(
// Important because where it is now in the constructed SSR markup might be different to where to *should* be
const parentNodeId = parentVNode?.$elm$ ? parentVNode.$elm$['s-id'] || parentVNode.$elm$.getAttribute('s-id') : '';

if (BUILD.shadowDom && shadowRootNodes) {
if (BUILD.shadowDom && shadowRootNodes && win.document) {
/* SHADOW */

// Browser supports shadowRoot and this is a shadow dom component; create an actual slot element
const slot = (childVNode.$elm$ = doc.createElement(childVNode.$tag$ as string) as d.RenderNode);
const slot = (childVNode.$elm$ = win.document.createElement(childVNode.$tag$ as string) as d.RenderNode);

if (childVNode.$name$) {
// Add the slot name attribute
Expand Down
8 changes: 6 additions & 2 deletions src/runtime/connected-callback.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BUILD } from '@app-data';
import { addHostEventListeners, doc, getHostRef, nextTick, plt, supportsShadow } from '@platform';
import { addHostEventListeners, getHostRef, nextTick, plt, supportsShadow, win } from '@platform';
import { CMP_FLAGS, HOST_FLAGS, MEMBER_FLAGS } from '@utils';

import type * as d from '../declarations';
Expand Down Expand Up @@ -124,13 +124,17 @@ export const connectedCallback = (elm: d.HostElement) => {
};

const setContentReference = (elm: d.HostElement) => {
if (!win.document) {
return;
}

// only required when we're NOT using native shadow dom (slot)
// or this browser doesn't support native shadow dom
// and this host element was NOT created with SSR
// let's pick out the inner content for slot projection
// create a node to represent where the original
// content was first placed, which is useful later on
const contentRefElm = (elm['s-cr'] = doc.createComment(
const contentRefElm = (elm['s-cr'] = win.document.createComment(
BUILD.isDebug ? `content-ref (host=${elm.localName})` : '',
) as any);
contentRefElm['s-cn'] = true;
Expand Down
24 changes: 16 additions & 8 deletions src/runtime/host-listener.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BUILD } from '@app-data';
import { consoleError, doc, plt, supportsListenerOptions, win } from '@platform';
import { consoleError, plt, supportsListenerOptions, win } from '@platform';
import { HOST_FLAGS, LISTENER_FLAGS } from '@utils';

import type * as d from '../declarations';
Expand All @@ -10,7 +10,7 @@ export const addHostEventListeners = (
listeners?: d.ComponentRuntimeHostListener[],
attachParentListeners?: boolean,
) => {
if (BUILD.hostListener && listeners) {
if (BUILD.hostListener && listeners && win.document) {
// this is called immediately within the element's constructor
// initialize our event listeners on the host element
// we do this now so that we can listen to events that may
Expand All @@ -32,7 +32,7 @@ export const addHostEventListeners = (
}

listeners.map(([flags, name, method]) => {
const target = BUILD.hostListenerTarget ? getHostListenerTarget(elm, flags) : elm;
const target = BUILD.hostListenerTarget ? getHostListenerTarget(win.document, elm, flags) : elm;
const handler = hostListenerProxy(hostRef, method);
const opts = hostListenerOpts(flags);
plt.ael(target, name, handler, opts);
Expand All @@ -58,12 +58,20 @@ const hostListenerProxy = (hostRef: d.HostRef, methodName: string) => (ev: Event
}
};

const getHostListenerTarget = (elm: Element, flags: number): EventTarget => {
if (BUILD.hostListenerTargetDocument && flags & LISTENER_FLAGS.TargetDocument) return doc;
if (BUILD.hostListenerTargetWindow && flags & LISTENER_FLAGS.TargetWindow) return win;
if (BUILD.hostListenerTargetBody && flags & LISTENER_FLAGS.TargetBody) return doc.body;
if (BUILD.hostListenerTargetParent && flags & LISTENER_FLAGS.TargetParent && elm.parentElement)
const getHostListenerTarget = (doc: Document, elm: Element, flags: number): EventTarget => {
if (BUILD.hostListenerTargetDocument && flags & LISTENER_FLAGS.TargetDocument) {
return doc;
}
if (BUILD.hostListenerTargetWindow && flags & LISTENER_FLAGS.TargetWindow) {
return win;
}
if (BUILD.hostListenerTargetBody && flags & LISTENER_FLAGS.TargetBody) {
return doc.body;
}
if (BUILD.hostListenerTargetParent && flags & LISTENER_FLAGS.TargetParent && elm.parentElement) {
return elm.parentElement;
}

return elm;
};

Expand Down
17 changes: 11 additions & 6 deletions src/runtime/styles.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BUILD } from '@app-data';
import { doc, plt, styles, supportsConstructableStylesheets, supportsShadow } from '@platform';
import { plt, styles, supportsConstructableStylesheets, supportsShadow, win } from '@platform';
import { CMP_FLAGS, queryNonceMetaTagContent } from '@utils';

import type * as d from '../declarations';
Expand Down Expand Up @@ -51,12 +51,12 @@ export const addStyle = (styleContainerNode: any, cmpMeta: d.ComponentRuntimeMet
const scopeId = getScopeId(cmpMeta, mode);
const style = styles.get(scopeId);

if (!BUILD.attachStyles) {
if (!BUILD.attachStyles || !win.document) {
return scopeId;
}
// if an element is NOT connected then getRootNode() will return the wrong root node
// so the fallback is to always use the document for the root node in those cases
styleContainerNode = styleContainerNode.nodeType === NODE_TYPE.DocumentFragment ? styleContainerNode : doc;
styleContainerNode = styleContainerNode.nodeType === NODE_TYPE.DocumentFragment ? styleContainerNode : win.document;

if (style) {
if (typeof style === 'string') {
Expand All @@ -75,11 +75,12 @@ export const addStyle = (styleContainerNode: any, cmpMeta: d.ComponentRuntimeMet
// This is only happening on native shadow-dom, do not needs CSS var shim
styleElm.innerHTML = style;
} else {
styleElm = document.querySelector(`[${HYDRATED_STYLE_ID}="${scopeId}"]`) || doc.createElement('style');
styleElm =
document.querySelector(`[${HYDRATED_STYLE_ID}="${scopeId}"]`) || win.document.createElement('style');
styleElm.innerHTML = style;

// Apply CSP nonce to the style tag if it exists
const nonce = plt.$nonce$ ?? queryNonceMetaTagContent(doc);
const nonce = plt.$nonce$ ?? queryNonceMetaTagContent(win.document);
if (nonce != null) {
styleElm.setAttribute('nonce', nonce);
}
Expand Down Expand Up @@ -247,7 +248,11 @@ export const convertScopedToShadow = (css: string) => css.replace(/\/\*!@([^\/]+
* and add them to a constructable stylesheet.
*/
export const hydrateScopedToShadow = () => {
const styles = doc.querySelectorAll(`[${HYDRATED_STYLE_ID}]`);
if (!win.document) {
return;
}

const styles = win.document.querySelectorAll(`[${HYDRATED_STYLE_ID}]`);
let i = 0;
for (; i < styles.length; i++) {
registerStyle(styles[i].getAttribute(HYDRATED_STYLE_ID), convertScopedToShadow(styles[i].innerHTML), true);
Expand Down
6 changes: 3 additions & 3 deletions src/runtime/test/bootstrap-lazy.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { doc } from '@platform';
import { win } from '@platform';

import { LazyBundlesRuntimeData } from '../../internal';
import { bootstrapLazy } from '../bootstrap-lazy';

describe('bootstrap lazy', () => {
it('should not inject invalid CSS when no lazy bundles are provided', () => {
const spy = jest.spyOn(doc.head, 'insertBefore');
const spy = jest.spyOn(win.document.head, 'insertBefore');

bootstrapLazy([]);

Expand All @@ -25,7 +25,7 @@ describe('bootstrap lazy', () => {
});

it('should not inject invalid CSS when components are already in custom element registry', () => {
const spy = jest.spyOn(doc.head, 'insertBefore');
const spy = jest.spyOn(win.document.head, 'insertBefore');

const lazyBundles: LazyBundlesRuntimeData = [
['my-component', [[0, 'my-component', { first: [1], middle: [1], last: [1] }]]],
Expand Down
Loading
Loading