Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

event doesn't trigger #1758

Closed
adipiciu opened this issue Apr 7, 2023 · 13 comments
Closed

event doesn't trigger #1758

adipiciu opened this issue Apr 7, 2023 · 13 comments

Comments

@adipiciu
Copy link

adipiciu commented Apr 7, 2023

Expected Behavior

For example:
element.dispatchEvent(new Event('change', {bubbles:true}))

Event should trigger. With Greasemonkey the event is triggered ok.
Also works if the script is loaded in developer mode.

Actual Behavior

The events are not triggered with Tampermonkey.

Specifications

  • Chrome 111/Firefox 111
  • TM: 4.18.1
  • OS: Windows
@adipiciu
Copy link
Author

Actually the event is dispatched, but does not bubble up. As I said, it works if I load the extension in chrome developer mode, or in Greasemonkey.
I tried with Tampermonkey beta, still does not work.

@derjanb
Copy link
Member

derjanb commented Apr 16, 2023

Can you please post some example code that isn't working?

@adipiciu
Copy link
Author

I fixed this with the workaround posted here: facebook/react#11488 (comment)

But I still don't know why it works without the hack/workaround in Greasemonkey or the script loaded directly in developer mode.

@7nik
Copy link

7nik commented Apr 16, 2023

Here is a test case:

// ==UserScript==
// @name         Test React Input Event
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://react.dev/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=react.dev
// @grant        GM_registerMenuCommand
// ==/UserScript==

(function() {
    'use strict';

    GM_registerMenuCommand("change search value (naive)", () => {
        const input = document.getElementById("docsearch-input");
        if (!input) {
            console.log("no input");
            return;
        }
        input.value = "test";
        input.dispatchEvent(new Event('input', { bubbles: true }));
    });

    GM_registerMenuCommand("change search value (hack)", () => {
        const input = document.getElementById("docsearch-input");
        if (!input) {
            console.log("no input");
            return;
        }
        Object.getOwnPropertyDescriptor(Object.getPrototypeOf(input), "value").set.call(input, "test");
        input.dispatchEvent(new Event('input', { bubbles: true }));
    });
})();

Go to https://react.dev/, set focus on the search field, and then run one of the menu commands.

React does some shit by replacing the value prop with a custom setter and getter to track it. Later, when React compares the new value with the tracked one, it doesn't see changes and ignores the event.

Thoughts about GM: if it uses FF's UserScript API to create a sandbox, the script probably receives an unmodified element with the original value property.

@tophf
Copy link

tophf commented Apr 16, 2023

The event actually bubbles up as can be seen by adding a listener on document or by running monitorEvents(document) in Chrome's devtools console.

React does some shit by replacing the value prop with a custom setter and getter to track it

Sounds plausible.

Personally, I prefer the old document.execCommand to trigger the site's handlers reliably:

      input.focus();
      if (input.value) input.select();
      document.execCommand('insertText', false, 'test');

@adipiciu
Copy link
Author

document.execCommand

This command is deprecated, so no longer recommended.

Thoughts about GM: if it uses FF's UserScript API to create a sandbox, the script probably receives an unmodified element with the original value property.

It works even on Chrome developer mode (with manifest v3, didn't tried with v2). I thought about sandboxing, but I tried every Tampermonkey sandbox setting and still doesn't work. https://www.tampermonkey.net/documentation.php?locale=en#meta:sandbox

@tophf
Copy link

tophf commented Apr 18, 2023

This command is deprecated, so no longer recommended

A new replacement API isn't yet created, so execCommand will work for many years more, maybe even decades... Note that execCommand adds to the undo stack of the input element, which is especially important in a textarea or contenteditable, which both have a multi-undo stack, and we can't imitate it by setting the value directly or by adding a DOM element inside.

@tophf
Copy link

tophf commented Apr 18, 2023

BTW the first command in @7nik's comment starts working in Violentmonkey after adding @inject-into content, i.e. the full sandboxing same as Greasemonkey/Firemonkey's default mode or Chrome's direct installation.

@derjanb
Copy link
Member

derjanb commented Apr 18, 2023

I tried every Tampermonkey sandbox setting and still doesn't work.

// @sandbox JavaScript

uses userScripts API if possible. Therefore this will work in Firefox only.

// @sandbox DOM

will work at Firefox (falls back to the userScripts API) and also Chrome, but you have to set "Sandbox Mode" to "All" first.

Warning: "DOM" scripts are injected into the extension context and can install new scripts by modifying the extension storage.

@derjanb derjanb closed this as completed Apr 18, 2023
@7nik
Copy link

7nik commented Apr 18, 2023

In TM, the first command forks with @sandbox DOM (need to set in the settings Security -> Sandbox Mode: -> All).

So, executing the script in ISOLEATED_WORLD allows it to get the original unmodified element.

Chrome developer mode

What do you mean by this? Auto-converting userscript into extension? Then it, of course, will work - the script is executed in ISOLEATED_WORLD.

I am personally not sure that this issue is in the Userscript manager's responsibility - any lib can mess up with elements/type prototypes, and here is the solution - execute the script in ISOLEATED_WORLD:

// @sandbox DOM
// @inject-into content

P.S.: I'm a bit too late.

@zon3d
Copy link

zon3d commented May 26, 2023

        const input = document.getElementById("docsearch-input");
        if (!input) {
            console.log("no input");
            return;
        }
        Object.getOwnPropertyDescriptor(Object.getPrototypeOf(input), "value").set.call(input, "test");
        input.dispatchEvent(new Event('input', { bubbles: true }));

Using @7nik's code above, how would I set the value of a contenteditable div (eg: message box in Instagram messages)? I assume it's not supposed to be "value" since it's a div, right? What should it be?

Because I'm getting this error: "Uncaught TypeError: Cannot read properties of undefined (reading 'set')"

@7nik
Copy link

7nik commented May 26, 2023

For contenteditable elements, it's innerHTML or textContent if you set a plain text. They can likely be used directly.

@zon3d
Copy link

zon3d commented May 30, 2023

For contenteditable elements, it's innerHTML or textContent if you set a plain text. They can likely be used directly.

Using either of those 2 things doesn't work with the contenteditable div element in Instagram messages on the web? I assume it's because it's using react and therefore doesn't detect those kind of DOM changes. Any suggestions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants