diff --git a/public/app/features/plugins/sandbox/document_sandbox.ts b/public/app/features/plugins/sandbox/document_sandbox.ts index e0750a9a785..12fd4853e1e 100644 --- a/public/app/features/plugins/sandbox/document_sandbox.ts +++ b/public/app/features/plugins/sandbox/document_sandbox.ts @@ -1,6 +1,7 @@ -import { ProxyTarget } from '@locker/near-membrane-shared'; +import { isNearMembraneProxy, ProxyTarget } from '@locker/near-membrane-shared'; import { forbiddenElements } from './constants'; +import { isReactClassComponent } from './utils'; // IMPORTANT: NEVER export this symbol from a public (e.g `@grafana/*`) package const SANDBOX_LIVE_VALUE = Symbol.for('@@SANDBOX_LIVE_VALUE'); @@ -72,6 +73,31 @@ export function markDomElementStyleAsALiveTarget(el: Element) { } } +/** + * Some specific near membrane proxies interfere with plugins + * an example of this is React class components state and their fast life cycles + * with cached objects. + * + * This function marks an object as a live target inside the sandbox + * but not all objects, only the ones that are allowed to be modified + */ +export function patchObjectAsLiveTarget(obj: unknown) { + if ( + obj && + // do not define it twice + !Object.hasOwn(obj, SANDBOX_LIVE_VALUE) && + // only for proxies + isNearMembraneProxy(obj) && + // do not patch functions + !(obj instanceof Function) && + // conditions for allowed objects + // react class components + isReactClassComponent(obj) + ) { + Reflect.defineProperty(obj, SANDBOX_LIVE_VALUE, {}); + } +} + export function isLiveTarget(el: ProxyTarget) { return Object.hasOwn(el, SANDBOX_LIVE_VALUE); } diff --git a/public/app/features/plugins/sandbox/sandbox_plugin_loader.ts b/public/app/features/plugins/sandbox/sandbox_plugin_loader.ts index f66209b2cde..89c3746307a 100644 --- a/public/app/features/plugins/sandbox/sandbox_plugin_loader.ts +++ b/public/app/features/plugins/sandbox/sandbox_plugin_loader.ts @@ -11,6 +11,7 @@ import { isDomElement, isLiveTarget, markDomElementStyleAsALiveTarget, + patchObjectAsLiveTarget, } from './document_sandbox'; import { sandboxPluginDependencies } from './plugin_dependencies'; import { sandboxPluginComponents } from './sandbox_components'; @@ -48,6 +49,8 @@ async function doImportPluginModuleInSandbox(meta: PluginMeta): Promise // the element.style attribute should be a live target to work in chrome markDomElementStyleAsALiveTarget(element); return element; + } else { + patchObjectAsLiveTarget(originalValue); } const distortion = generalDistortionMap.get(originalValue); if (distortion) { diff --git a/public/app/features/plugins/sandbox/utils.ts b/public/app/features/plugins/sandbox/utils.ts index 78c8073746a..21237b7d4c2 100644 --- a/public/app/features/plugins/sandbox/utils.ts +++ b/public/app/features/plugins/sandbox/utils.ts @@ -1,3 +1,5 @@ +import React from 'react'; + import { SandboxedPluginObject } from './types'; export function isSandboxedPluginObject(value: unknown): value is SandboxedPluginObject { @@ -7,3 +9,7 @@ export function isSandboxedPluginObject(value: unknown): value is SandboxedPlugi export function assertNever(x: never): never { throw new Error(`Unexpected object: ${x}. This should never happen.`); } + +export function isReactClassComponent(obj: unknown): obj is React.Component { + return obj instanceof React.Component; +}