class ReactFiber { constructor(selector, options = {}) { this.selector = selector; this.debug = options.debug || false; this.nodes = [...document.querySelectorAll(selector)]; // Support multiple elements this.fibers = this.nodes.map((node) => this.getFiberNode(node)); this.components = this.fibers.map((fiber) => this.getOwnerComponent(fiber)); if (this.debug) { console.log("Selected Nodes:", this.nodes); console.log("🔍 Found Fibers:", this.fibers); console.log("🛠 Found Components:", this.components); } } static find(selector, options = {}) { return new ReactFiber(selector, options); } getFiberNode(node) { if (!node) return null; const fiberKey = Object.getOwnPropertyNames(node).find( (name) => name.startsWith("__reactFiber") || name.startsWith("__reactInternalInstance"), ); return fiberKey ? node[fiberKey] : null; } getOwnerComponent(fiberNode) { let current = fiberNode; while (current) { if ( current.stateNode && (current.stateNode.setState || current.stateNode.forceUpdate) ) { return current.stateNode; } current = current.return; } return null; } getState(key) { if (!this.components.length) return null; const state = this.components[0]?.state || null; if (key === undefined) { return state; } else if (typeof key === "string") { return state?.[key]; } else if (Array.isArray(key)) { const filteredState = {}; for (const k of key) { if (state && Object.hasOwn(state, k)) { filteredState[k] = state[k]; } } return filteredState; } return null; } setState(update) { this.components.forEach((component) => { if (component?.setState) { if (typeof update === "function") { // Functional update component.setState((prevState) => { const newState = update(prevState); if (this.debug) console.log("✅ Updated State (Functional):", newState); return newState; }); } else { // Object update (merge with existing state) component.setState((prevState) => { const newState = { ...prevState, ...update, }; if (this.debug) console.log("✅ Updated State (Object Merge):", newState); return newState; }); } } }); return this; } getProp(propName) { if (!this.fibers.length) return null; if (propName === undefined) { return this.fibers[0]?.memoizedProps; } return this.fibers[0]?.memoizedProps?.[propName]; } setProp(propName) { this.fibers.forEach((fiber) => { if (fiber?.memoizedProps) { fiber.memoizedProps[propName] = value; } }); return this; // Enable chaining } forceUpdate() { this.components.forEach((component) => { if (component?.forceUpdate) { component.forceUpdate(); if (this.debug) console.log("🔄 Forced React Re-render"); } }); return this; // Enable chaining } } function makeSerializable(obj) { if (typeof obj !== "object" || obj === null) { return obj; } if (Array.isArray(obj)) { return obj.map((item) => makeSerializable(item)); } const serializableObj = {}; for (const key in obj) { if (Object.hasOwn(obj, key)) { let value = obj[key]; if (typeof value === "function") { value = "[Function]"; } else if (value instanceof HTMLElement) { value = { type: "HTMLElement", id: value.id, tagName: value.tagName, }; // Replace DOM node with ID/tag info } else if (typeof value === "symbol") { value = value.toString(); } else if (typeof value === "object" && value !== null) { value = makeSerializable(value); } serializableObj[key] = value; } } return serializableObj; } window.addEventListener("message", (event) => { if (event.data.type === "reactFiberRequest") { const { selector, action, payload, debug, messageId } = event.data; const fiberInstance = ReactFiber.find(selector, { debug, }); let response; switch (action) { case "getState": response = fiberInstance.getState(payload.key); break; case "setState": // Handle both function and object updates if (payload.updateFn) { const updateFn = new Function('return ' + payload.updateFn)(); fiberInstance.setState(updateFn); } else { fiberInstance.setState(payload.updateObject); } response = {}; break; case "getProp": response = fiberInstance.getProp(payload.propName); break; case "setProp": fiberInstance.setProp(payload.propName, payload.value); response = {}; break; case "forceUpdate": fiberInstance.forceUpdate(); response = {}; break; default: console.warn(`[pageState] Unknown action: ${action}`); response = null; } if (response !== null && typeof response === "object") { response = makeSerializable(response); } window.postMessage( { type: "reactFiberResponse", response, messageId, }, "*", ); } else if (event.data.type === "triggerKeyboardEvent") { // Handle keyboard event triggering from content script const { key, code, altKey, ctrlKey, metaKey, shiftKey, keyCode } = event.data; const keyboardEvent = new KeyboardEvent('keydown', { key, code, keyCode: keyCode || 0, which: keyCode || 0, altKey: altKey || false, ctrlKey: ctrlKey || false, metaKey: metaKey || false, shiftKey: shiftKey || false, bubbles: true, cancelable: true }); document.dispatchEvent(keyboardEvent); } else if (event.data.type === "ckeditorSetData") { // Handle CKEditor data setting const { editorId, content } = event.data; if (window.CKEDITOR && window.CKEDITOR.instances && window.CKEDITOR.instances[editorId]) { window.CKEDITOR.instances[editorId].setData(content); } else { console.warn(`[pageState] CKEditor instance '${editorId}' not found`); } } else if (event.data.type === "ckeditorGetData") { const { editorId } = event.data; if (window.CKEDITOR && window.CKEDITOR.instances && window.CKEDITOR.instances[editorId]) { const data = window.CKEDITOR.instances[editorId].getData(); window.postMessage({ type: "ckeditorGetDataResponse", data, }, "*"); } } });