// Copyright 2024 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. import type { ComponentClass } from 'preact'; import { options } from 'preact'; import { getCurrentVNode, getOwnerStack } from './shared/component-stack.js'; /* v8 ignore start */ export const noop: (...args: unknown[]) => unknown = () => {}; /* v8 ignore end */ export function isDirectOrDeepEqual(a: any, b: any): boolean { if (a === b) { return true; } try { if ( typeof a == 'object' && a !== null && typeof b == 'object' && b !== null && JSON.stringify(a) === JSON.stringify(b) ) { return true; } } catch (error) { if (__DEV__ && /circular|cyclic/i.test((error as Error).message)) { // JavaScript engines give this different errors name and messages: // PrimJS: "circular reference" // JavaScriptCore: "JSON.stringify cannot serialize cyclic structures" // V8: "Converting circular structure to JSON" const vnode = getCurrentVNode(); if (vnode) { const stack = getOwnerStack(vnode); (error as Error).message += `\n\n${stack}`; } } throw error; } return false; } export function isEmptyObject(obj?: object): obj is Record { for (const _ in obj) return false; return true; } export function isSdkVersionGt(major: number, minor: number): boolean { const lynxSdkVersion: string = SystemInfo.lynxSdkVersion ?? '1.0'; const version = lynxSdkVersion.split('.'); return Number(version[0]) > major || (Number(version[0]) == major && Number(version[1]) > minor); } export function pick(obj: T, keys: Iterable): Pick { const result: Partial> = {}; for (const key of keys) { if (key in obj) { result[key] = obj[key]; } } return result as Pick; } export function maybePromise(value: unknown): value is Promise { return ( typeof value === 'object' && value !== null // @ts-expect-error the check is safe && typeof value.then === 'function' ); } export function getDisplayName(type: ComponentClass): string { return type.displayName ?? type.name; } export function hook( object: T, key: K, fn: Required[K] extends (...args: infer P) => infer Q ? ((old?: T[K], ...args: P) => Q) : never, ): void { const oldFn = object[key]; object[key] = function(this: T, ...args: unknown[]) { return fn.call(this, oldFn, ...args); } as T[K]; } /** * Runs `fn` with Preact's after-paint scheduler switched to synchronous. * * Preact 11 defers passive-effect cleanups of unmounted components to that * flush. Page destroy is the one path with no later turn to run them in: * `callDestroyLifetimeFun` returns and native tears the runtime down, so a * deferred cleanup would never run at all. */ export function withSyncEffectFlush(fn: () => T): T { // eslint-disable-next-line @typescript-eslint/unbound-method const previousRequestAnimationFrame = options.requestAnimationFrame; options.requestAnimationFrame = cb => { cb(); }; try { return fn(); } finally { if (previousRequestAnimationFrame) { options.requestAnimationFrame = previousRequestAnimationFrame; } else { delete options.requestAnimationFrame; } } } export const lynxQueueMicrotask: typeof lynx.queueMicrotask = /* @__PURE__ */ (() => { if (lynx.queueMicrotask) { return (fn) => lynx.queueMicrotask(fn); } else if (typeof globalThis.Promise === 'function') { const resolved = globalThis.Promise.resolve(); /* v8 ignore start */ return (fn) => { // Schedule as a microtask, and surface exceptions like queueMicrotask would. resolved.then(fn).catch((err) => { setTimeout(() => { throw err; }, 0); }); }; } else { // Fallback to macrotask when microtasks aren't available. return (fn) => { setTimeout(fn, 0); }; } /* v8 ignore stop */ })();