diff --git a/scripts/build.mjs b/scripts/build.mjs index c184bcb..030612b 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -49,7 +49,7 @@ const config = { window: "globalThis", __DEV__: JSON.stringify(releaseBranch !== "main"), }, - inject: ["./shims/asyncIteratorSymbol.js", "./shims/promiseAllSettled.js"], + inject: ["./shims/asyncIteratorSymbol.js", "./shims/promiseAllSettled.js", "./shims/weakref.js"], legalComments: "none", alias: { "!ShiggyCord-deps-shim!": "./shims/depsModule.ts", diff --git a/shims/weakref.js b/shims/weakref.js new file mode 100644 index 0000000..98cabf0 --- /dev/null +++ b/shims/weakref.js @@ -0,0 +1,128 @@ +// @ts-nocheck +/** + * WeakRef / FinalizationRegistry shim + * + * Purpose: + * - Provide a safe fallback when the runtime doesn't implement WeakRef or FinalizationRegistry. + * - This fallback avoids a ReferenceError and keeps code paths functional, but it does NOT + * emulate true weak-reference GC behavior. Use it only as a compatibility fallback. + * + */ + +let _shimWarned = false; + +function _warnOnce(msg) { + if (!_shimWarned) { + // keep this quiet by default; print only when fallback is actually used + try { + // Prefer console.warn when available + if (typeof console !== "undefined" && typeof console.warn === "function") { + console.warn(`[weakref-shim] ${msg}`); + } + } catch (_) {} + _shimWarned = true; + } +} + +/** + * WeakRef fallback + * - Keeps a strong reference to the target (so it will not be GC'd). + * - Implements the minimal `deref()` API. + */ +class WeakRefFallback { + constructor(value) { + // Keep a strong reference: this is *not* weak. + this.__value = value; + _warnOnce( + "WeakRef is not available in this runtime. Using a strong-reference fallback — memory usage/semantics may differ." + ); + } + + deref() { + return this.__value; + } +} + +/** + * FinalizationRegistry fallback + * - No-op registry: register/unregister do nothing. + * - Implement `cleanupSome` as a best-effort sync call when a callback is provided, + * but it cannot be tied to GC. + */ +class FinalizationRegistryFallback { + constructor(cleanupCallback) { + // store callback to allow optional manual invocation via cleanupSome + this.__cleanupCallback = typeof cleanupCallback === "function" ? cleanupCallback : null; + _warnOnce( + "FinalizationRegistry is not available in this runtime. Using a no-op fallback — finalizers will not run based on GC." + ); + } + + /** + * register(target, heldValue, unregisterToken?) + * - No-op in fallback. We keep no strong references here on purpose (can't emulate GC). + */ + register(_target /*, _heldValue, _unregisterToken */) { + // intentionally no-op + } + + /** + * unregister(unregisterToken) + * - No-op in fallback. + */ + unregister(_unregisterToken) { + // intentionally no-op + } + + /** + * cleanupSome(callback?) + * - If provided a callback or a stored cleanup callback exists, call it immediately. + * - This is NOT equivalent to real FinalizationRegistry cleanup; it's only a manual helper. + */ + cleanupSome(callback) { + const cb = typeof callback === "function" ? callback : this.__cleanupCallback; + if (typeof cb === "function") { + try { + cb(); + } catch (e) { + try { + if (typeof console !== "undefined" && typeof console.error === "function") { + console.error("[weakref-shim] error running cleanupSome callback:", e); + } + } catch (_) {} + } + } + } +} + +// If native implementations exist, prefer them; otherwise install fallbacks. +// We assign to globalThis so code using `WeakRef`/`FinalizationRegistry` globals doesn't crash. +const NativeWeakRef = + typeof globalThis !== "undefined" && typeof globalThis.WeakRef === "function" ? globalThis.WeakRef : undefined; + +const NativeFinalizationRegistry = + typeof globalThis !== "undefined" && typeof globalThis.FinalizationRegistry === "function" + ? globalThis.FinalizationRegistry + : undefined; + +const WeakRefImpl = NativeWeakRef ?? WeakRefFallback; +const FinalizationRegistryImpl = NativeFinalizationRegistry ?? FinalizationRegistryFallback; + +try { + if (typeof globalThis !== "undefined") { + if (typeof globalThis.WeakRef === "undefined") { + // Install fallback + globalThis.WeakRef = WeakRefImpl; + } + if (typeof globalThis.FinalizationRegistry === "undefined") { + globalThis.FinalizationRegistry = FinalizationRegistryImpl; + } + } +} catch (e) { + // Some embedder environments may forbid writing globals; ignore in that case. + _warnOnce("Unable to assign WeakRef/FinalizationRegistry to globalThis: " + (e && e.message)); +} + +// Export the implementations so the inject mechanism (or explicit imports) can reference them. +// Export names are sorted to satisfy lint rules that require sorted exports. +export { FinalizationRegistryImpl as FinalizationRegistry, WeakRefImpl as WeakRef }; diff --git a/src/core/ui/settings/pages/Plugins/CorePlugins.tsx b/src/core/ui/settings/pages/Plugins/CorePlugins.tsx deleted file mode 100644 index c08e4c2..0000000 --- a/src/core/ui/settings/pages/Plugins/CorePlugins.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { React } from "@metro/common"; -import AddonPage from "@core/ui/components/AddonPage"; -import PluginCard from "@core/ui/settings/pages/Plugins/components/PluginCard"; -import { corePluginInstances, registeredPlugins } from "@lib/addons/plugins"; -import { UnifiedPluginModel } from "@core/ui/settings/pages/Plugins/models"; -import unifyBunnyPlugin from "@core/ui/settings/pages/Plugins/models/bunny"; -import { Strings } from "@core/i18n"; - -/** - * Core plugins settings page - * - * This page shows only the built-in core plugins. Core plugins are always - * installed (can't be uninstalled) and are registered in `corePluginInstances`. - * - * We reuse the existing `AddonPage` + `PluginCard` components and feed them - * the normalized `UnifiedPluginModel` objects produced by `unifyBunnyPlugin`. - */ - -export default function CorePlugins() { - return ( - - CardComponent={PluginCard} - title={Strings.PLUGINS} - searchKeywords={[ - "name", - "description", - (p) => - p.authors - ?.map((a: any) => (typeof a === "string" ? a : a.name)) - .join() || "", - ]} - // Provide simple sort options so the Search input renders round and a sort menu appears - sortOptions={{ - "Name (A-Z)": (a, b) => a.name.localeCompare(b.name), - "Name (Z-A)": (a, b) => b.name.localeCompare(a.name), - }} - // Only core plugins: map corePluginInstances -> registeredPlugins -> unify - items={[...corePluginInstances.keys()] - .map((id) => registeredPlugins.get(id)) - .filter((m): m is any => m !== undefined) - .map(unifyBunnyPlugin)} - ListHeaderComponent={() => null} - /> - ); -} diff --git a/src/core/ui/settings/pages/Plugins/index.tsx b/src/core/ui/settings/pages/Plugins/index.tsx index 2c2514b..97e3e68 100644 --- a/src/core/ui/settings/pages/Plugins/index.tsx +++ b/src/core/ui/settings/pages/Plugins/index.tsx @@ -702,7 +702,7 @@ export default function Plugins() { // Use the insertion order of pluginSettings so newly-installed plugins // (which add entries to pluginSettings) appear first. const installedBnIds = Object.keys(pluginSettings || {}) - .filter((id) => registeredPlugins.has(id) && !isCorePlugin(id)) + .filter((id) => registeredPlugins.has(id)) .slice() .reverse(); @@ -710,12 +710,12 @@ export default function Plugins() { .map((id) => registeredPlugins.get(id)!) .map(unifyBunnyPlugin); - // Merge lists: show Vendetta-managed plugins first (recent first), + // Merge lists: show Vendetta-managed plugins first, // then Bunny-installed externals (recent first). const allPlugins = [...vdPlugins, ...bnPlugins]; - // Filter out core plugins from the list - return allPlugins.filter((plugin) => !isCorePlugin(plugin.id)); + // Include core plugins in the list + return allPlugins; }} ListHeaderComponent={() => null} installAction={{