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

Early JavaScript code cannot be intercepted even with "@run-at document-start", "Instant" injection mode needed #459

Open
biergaizi opened this issue Mar 30, 2023 · 1 comment
Labels
help wanted Extra attention is needed

Comments

@biergaizi
Copy link

biergaizi commented Mar 30, 2023

I'm trying to write a Userscript to change the behavior of websites by monkey-patching the built-in JavaScript functions, for example, replacing them with my custom versions to intercept their actions. This requires one to execute the Userscript before everything else. Unfortunately, I found even with @run-at document-start, early JavaScript function calls cannot be intercepted, since the Userscript is still running too late.

Demo

To replicate this problem, I selected the JavaScript alert() function as a demo. First, save the following webpage as test.html:

<head>
</head>

<body>
    <button id="test">Alert</button>
</body>


<script>
(function () {
  alert("Early call!");
}
)();

let button = document.querySelector('#test');
button.onclick = function () {
    alert("Late call!");
}
</script>

Then save the following Userscript as intercept-alert.user.js, which monkey-patches the JavaScript "alert()` function with a custom version:

// ==UserScript==
// @name     Intercept Test
// @version  1
// @run-at   document-start
// @include  http://127.0.0.1:8000/*
// @grant    none
// ==/UserScript==

function monkeyPatch() {
  let realAlert = window.alert;

  window.alert = function(text) {
    realAlert("alert has been intercepted: " + text);
  }
}

// inject the monkey patch into DOM
var script = document.createElement('script');
script.appendChild(document.createTextNode('('+ monkeyPatch +')();'));
(document.body || document.head || document.documentElement).appendChild(script);

Open the webpage, you would see that the Userscript cannot intercept the alert("Early call!");, only the alert("Late call!"); can be intercepted.

Discussion

In fact, one can also replicate this problem with TamperMonkey and ViolentMonkey on Chromium. To overcome this problem, both plugins implemented a workaround. TamperMonkey calls it "inject mode: instant", and ViolentMonkey calls it "Synchronous page mode`.

ViolentMonkey's description is:

Runs scripts at the real <document-start> reliably. Don't enable unless you do have a script that needs to run before the page starts loading and Violentmonkey is currently running it too late. This mode will be using the deprecated synchronous XHR so you'll see warnings in devtools console, although you can safely ignore them as the adverse affects it warns about are negligible in this case and you can hide the warnings for good by right-clicking one.

The workaround basically abuses document.cookie and a synchronous XMLHttpRequest call in a tricky way in order to force the browser to run the scripts in a synchronous manner, ensuring that the Userscript runs before everything else.

  1. The background script makes a Blob in the background script with the data, gets its URL via URL.createObjectURL, puts it into a Set-Cookie header via chrome.webRequest API.

  2. The content script reads the URL from document.cookie and uses it in a synchronous XMLHttpRequest to get the original data synchronously.

The source code of ViolentMonkey's implementation is available here: violentmonkey/violentmonkey#1100

Is it possible to implement the same feature in quoid/userscripts? Or is there an alternative method to monkey-patch and intercept JavaScript function without this limitation?

@biergaizi biergaizi changed the title Early JavaScript code cannot be intercepted even with "@run-at document-start" Early JavaScript code cannot be intercepted even with "@run-at document-start", Need "Instant" Injection Mode. Mar 30, 2023
@biergaizi biergaizi changed the title Early JavaScript code cannot be intercepted even with "@run-at document-start", Need "Instant" Injection Mode. Early JavaScript code cannot be intercepted even with "@run-at document-start", "Instant" Injection Mode Needed Mar 30, 2023
@biergaizi biergaizi changed the title Early JavaScript code cannot be intercepted even with "@run-at document-start", "Instant" Injection Mode Needed Early JavaScript code cannot be intercepted even with "@run-at document-start", "Instant" injection mode needed Mar 30, 2023
@ACTCD
Copy link
Collaborator

ACTCD commented Mar 30, 2023

Thanks for the feedback. Apple strictly restricts the API related to cookies, and as far as we know, it is impossible to achieve the same trick. Duplicate issue #184

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants