7

I'm trying to change some behavior of the YouTube player, by changing some variables inside of the player_api script that is embedded into the html watch page of videos.

The embedded script i'm trying to modify

Problem is, whatever i try, the embedded script of the player always runs before my extension adds modifications to it. Thus keeping the behavior of the player the same.

I tried setting the run_at property in my manifest to document-start, but then the script didn't run at all.

What can i do to halt the execution of that script until i make changes to it?

PS: I tried changing the script by intercepting the html call and editing the body with Charles Proxy and the behavior of the player changed as i wanted. So it know it should work, if done at the right time.

.

manifest.json

{
    "manifest_version": 2,
    "name": "YouFit For YouTube",
    "version": "1",
    "content_scripts": [{
        "js": ["content.js"],
        "matches": ["https://*.youtube.com/watch?*",
        "https://*.youtube.com/watch?*"],
    }],
    "browser_action": {
        "default_icon": "icon.png"
    }
}

content.js

function changeBehavior() {
    var scriptElements = document.getElementsByTagName('script');
    for (var i = 14; i < scriptElements.length; i++) {
        var curScriptBody = scriptElements[i].outerHTML;
        // Find the script i'm interested in
        if (curScriptBody.indexOf("var ytplayer") != -1) {
            scriptElements[i].outerHTML = scriptElements[i].outerHTML.replace("<text>", "<replacement text>");
            alert("Replaced");
            break;
        }
    }
}
changeBehavior();
DarkMental
  • 482
  • 7
  • 26
  • 1
    Replacing script element's html accomplishes nothing because it happens after the script was executed and there's no way to do it beforehand in Chrome. It won't work like this. You'll have to cancel the script URL before it loads, download its contents yourself, change it, insert as a new script element. There should be an example of doing that somewhere, keep looking. – wOxxOm Feb 15 '18 at 02:19
  • The script isn't loaded from url. It's content is injected inside the script tag. Does that make a difference? – DarkMental Feb 15 '18 at 10:05
  • 1
    Then you'll need to overwrite the entire page: 1) `window.stop()` 2) fetch the original html vix XHR/fetch 3) do the text replacements 4) overwrite the page via `document.write()` – wOxxOm Feb 15 '18 at 11:10
  • I spent a lot of time trying to follow your suggestion. I couldn't though, i'm still new to chrome extensions and content scripts. I will be placing a 50 - 100 point bounty in 8 hours in hopes of getting a more guiding answer. Thank you for the suggestion :) – DarkMental Feb 16 '18 at 11:10
  • Something like [this script](https://greasyfork.org/en/scripts/14164-kinozal-tv-remove-external-scripts/code). – wOxxOm Feb 16 '18 at 15:09
  • Provide some more information, which url you are trying to capture, which variable to change? – Munim Munna Feb 18 '18 at 13:19
  • @MunimMunna Any `watch?id=videoID` url i want to add a tag to the keywords array inside the script that will stretch the video to fit 16x9 screens. I add the tag by simply replacing **"keywords":"** with **"keywords":"yt:stretch=16:9,** – DarkMental Feb 18 '18 at 22:12
  • Please, provide a goal you want to achieve, instead of solution you've tried. (https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) You can not achieve what you want (change inline script) without hacky hacks (as proposed by wOxxOm), yet your actual problem is probably solvable with less hacky hacks. – haldagan Feb 21 '18 at 07:48
  • @haldagan I'm okay with hacks. The goal i want to achieve is edit the inline script before it has a chance to run. – DarkMental Feb 21 '18 at 10:03
  • Actually, (If I get it right) your goal is to alter some yt player behaviour/defaults and not to modify inline script. However, on modifying inline-scripts: you can try digging into webRequest (https://developer.chrome.com/extensions/webRequest) to see if it can help intercepting and modifying "current page request", yet I'm not fully sure this will help you). UPD: Nah, it seems you can't modify the response. – haldagan Feb 21 '18 at 13:21
  • @haldagan After trying wOxxOm's suggestion another time. It actually did the trick of changing the script before it runs, However i can't find a way to convert the html from the `XHTTP.responseText` to a document, then do the replacement on the target inline script, then converting it back to an html text that i can inject using `document.write` Seems like doing a regex replace on the whole text html doesn't give any result. – DarkMental Feb 21 '18 at 15:00
  • I dont actually know what was the problem with that, but if you insist, that `document.write` doesn't do it's job, then, (falling deeper and deeper into hax) you can always try inserting an iframe to the page with `src` being equal to data-uri base64-encoded equivalent of the whole page you recieved. Yet, (as I suppose) the actual issue is somwhere near the `window.stop` command. I think you should've tried just wiping the `body` instead of this. – haldagan Feb 21 '18 at 19:13
  • @wOxxOm The bounty is ending, and your answer is the closest, could you put it as an answer so i reward you the bounty points? – DarkMental Feb 23 '18 at 16:49

2 Answers2

5

Did you try something like this?

content.js

var script = document.createElement('script');
script.textContent = "/* What you have in content.js right now */";
(document.head||document.documentElement).prepend(script);
Chris Cinelli
  • 4,679
  • 4
  • 28
  • 40
  • Running this at **document_start**, Seems like I don't have access to other script elements at that point just yet. Running it at **document_idle** doesn't do the replacement early enough. – DarkMental Feb 23 '18 at 11:07
  • 1
    Such an underrated answer. With "run_at":"document_start", this solution runs code in context of page right before any script execution and before any DOM element load. Exactly what i needed. – 0x384c0 Aug 08 '19 at 11:52
  • I have been searching for this all day ! Great answer thanks ! – xicocana Jun 06 '20 at 23:40
0

Add "run_at": "document_start" to the manifest file for the content script then modify your content script such that changeBehavior is called after the current call stack is exhausted using setTimeout(fn, 0). It will run just after the HTML document is rendered but before any embedded scripts.

This solution also avoids potential issues with running unsafe inline scripts when the content security policy is set.

Content.js
function changeBehavior() {
    ...
}

setTimeout(() => {
    changeBehavior();
}, 0);
  • Can you elaborate on the purpose of `setTimeout` here? I don't understand – X33 Jan 10 '23 at 12:07
  • The `setTimeout( ..., 0)` will ensure that the function is called _after_ the HTML documented is rendered. Without it, it will be called before. https://stackoverflow.com/questions/779379/why-is-settimeoutfn-0-sometimes-useful – Josh Bruflodt May 04 '23 at 13:53