1

Writing a Tampermonkey script that injects forms inline into a webpage that can't be edited directly. The forms communicate with a separate backend.

Built the prototype using jQuery but am looking to put React to work to support a better UX. In the interest of keeping it consistent with other work, I'd like to use webpack and jsx as well.

Doing one thing at a time, the script is trivial:

import React from 'react';
import ReactDOM from 'react-dom';

GM_addStyle(require(__dirname + '/styles/app.css'));

console.log("webpacked script is working!");

const Hello = () => <div>hello</div>

const graph = document.querySelector('a[href*="url_here"]');

console.log(graph);

ReactDOM.render(<Hello />, graph)

I have tried querySelectorAll, byTagName, just in case those would be somehow different.

However, the generated script fails with a "maximum update depth succeeded" after a number of these calls:

console output

The "webpacked script is working!" is printed to console, and the target element is removed from the dom, but the <Hello /> div is not rendered in its place.

That offending line, webpacked/transpiled but not minified, and starting with fakeNode, is here:

     if ("undefined" != typeof window && "function" == typeof window.dispatchEvent && "undefined" != typeof document && "function" == typeof document.createEvent) {
        var fakeNode = document.createElement("react");
        invokeGuardedCallbackImpl = function(name, func, context, a, b, c, d, e, f) {
            "undefined" == typeof document && invariant(!1, "The `document` global was defined when React was initialized, but is not defined anymore. This can happen in a test environment if a component schedules an update from an asynchronous callback, but the test has already finished running. To solve this, you can either unmount the component at the end of your test (and ensure that any asynchronous operations get canceled in `componentWillUnmount`), or you can change the test itself to be asynchronous.");
            var evt = document.createEvent("Event"), didError = !0, windowEvent = window.event, windowEventDescriptor = Object.getOwnPropertyDescriptor(window, "event"), funcArgs = Array.prototype.slice.call(arguments, 3);
            var error = void 0, didSetError = !1, isCrossOriginError = !1;
            function handleWindowError(event) {
                if (error = event.error, didSetError = !0, null === error && 0 === event.colno && 0 === event.lineno && (isCrossOriginError = !0), 
                event.defaultPrevented && null != error && "object" == typeof error) try {
                    error._suppressLogging = !0;
                } catch (inner) {}
            }
            var evtType = "react-" + (name || "invokeguardedcallback");
            window.addEventListener("error", handleWindowError), fakeNode.addEventListener(evtType, function callCallback() {
                fakeNode.removeEventListener(evtType, callCallback, !1), void 0 !== window.event && window.hasOwnProperty("event") && (window.event = windowEvent), 
                func.apply(context, funcArgs), didError = !1;
            }, !1), evt.initEvent(evtType, !1, !1), fakeNode.dispatchEvent(evt), windowEventDescriptor && Object.defineProperty(window, "event", windowEventDescriptor), 
            didError && (didSetError ? isCrossOriginError && (error = new Error("A cross-origin error was thrown. React doesn't have access to the actual error object in development. See ... for more information.")) : error = new Error("An error was thrown inside one of your components, but React doesn't know what it was. This is likely due to browser flakiness. React does its best to preserve the \"Pause on exceptions\" behavior of the DevTools, which requires some DEV-mode only tricks. It's possible that these don't work in your browser. Try triggering the error in production mode, or switching to a modern browser. If you suspect that this is actually an issue with React, please file an issue."), 
            this.onError(error)), window.removeEventListener("error", handleWindowError);
        };
    }

It looks to me like ReactDOM is not getting access to the window object it expects, probably proxied by Tampermonkey somewhere.

Is that the case, or is this a different problem?

Is there a way I can get ReactDOM to render correctly within this Tampermonkey script?

Brock Adams
  • 90,639
  • 22
  • 233
  • 295
kalzen
  • 343
  • 4
  • 12
  • Nearly a duplicate of https://stackoverflow.com/a/1732454/331508. – Brock Adams Jan 07 '19 at 21:38
  • Haha, not sure if you mean to say it's illegible, hilarious, or you meant to post a different link, but in any case it turns out that I found my solution in an answer of yours from 2012. I guess I can't say thanks now, so [redacted]! – kalzen Jan 08 '19 at 04:29

1 Answers1

1

The quick fix: window = unsafeWindow at the top of the tampermonkey script, easily added with webpack's BannerPlugin.

For other options/background: Why is window (and unsafeWindow) not the same from a userscript as from a <script> tag?

kalzen
  • 343
  • 4
  • 12